Abstract Matrices

An AbstractMatrix is an array where the elements all come from a single Ring or Field.

import finite_algebras as alg
import numpy as np
from abstract_matrix import AbstractMatrix

import os
aa_path = os.path.join(os.getenv("PYPROJ"), "abstract_algebra")
alg_dir = os.path.join(aa_path, "Algebras")

Algebras for Examples & Tests

Get the Built-In Examples

>>> ex = alg.Examples(alg_dir)
======================================================================
                           Example Algebras
----------------------------------------------------------------------
  17 example algebras are available.
  Use "Examples[INDEX]" to retrieve a specific example,
  where INDEX is the first number on each line below:
----------------------------------------------------------------------
0: A4 -- Alternating group on 4 letters (AKA Tetrahedral group)
1: D3 -- https://en.wikipedia.org/wiki/Dihedral_group_of_order_6
2: D4 -- Dihedral group on four vertices
3: Pinter29 -- Non-abelian group, p.29, 'A Book of Abstract Algebra' by Charles C. Pinter
4: RPS -- Rock, Paper, Scissors Magma
5: S3 -- Symmetric group on 3 letters
6: S3X -- Another version of the symmetric group on 3 letters
7: V4 -- Klein-4 group
8: Z4 -- Cyclic group of order 4
9: F4 -- Field with 4 elements (from Wikipedia)
10: mag_id -- Magma with Identity
11: Example 1.4.1 -- See: Groupoids and Smarandache Groupoids by W. B. Vasantha Kandasamy
12: Ex6 -- Example 6: http://www-groups.mcs.st-andrews.ac.uk/~john/MT4517/Lectures/L3.html
13: Q8 -- Quaternion Group
14: SD16 -- Semidihedral group of order 16
15: A5 -- Alternating group on 5 letters
16: F2 -- Field with 2 elements from paper: 236w06fields.pdf
======================================================================

Two Very Small Fields from the Built-In Examples

>>> f4 = ex[9]
>>> f4.about()
** Field **
Name: F4
Instance ID: 4608036544
Description: Field with 4 elements (from Wikipedia)
Order: 4
Identity: '0'
Commutative? Yes
Cyclic?: Yes
Generators: ['1+a', 'a']
Elements:
   Index   Name   Inverse  Order
      0     '0'     '0'       1
      1     '1'     '1'       2
      2     'a'     'a'       2
      3   '1+a'   '1+a'       2
Cayley Table (showing indices):
[[0, 1, 2, 3], [1, 0, 3, 2], [2, 3, 0, 1], [3, 2, 1, 0]]
Mult. Identity: '1'
Mult. Commutative? Yes
Zero Divisors: None
Multiplicative Cayley Table (showing indices):
[[0, 0, 0, 0], [0, 1, 2, 3], [0, 2, 3, 1], [0, 3, 1, 2]]

A Small Powerset Ring

>>> ps3 = alg.generate_powerset_ring(3)
>>> ps3.about()
** Ring **
Name: PSRing3
Instance ID: 4607553808
Description: Autogenerated Ring on powerset of {0, 1, 2} w/ symm. diff. (add) & intersection (mult)
Order: 8
Identity: '{}'
Commutative? Yes
Cyclic?: No
Elements:
   Index   Name   Inverse  Order
      0    '{}'    '{}'       1
      1   '{0}'   '{0}'       2
      2   '{1}'   '{1}'       2
      3   '{2}'   '{2}'       2
      4 '{0, 1}' '{0, 1}'       2
      5 '{0, 2}' '{0, 2}'       2
      6 '{1, 2}' '{1, 2}'       2
      7 '{0, 1, 2}' '{0, 1, 2}'       2
Cayley Table (showing indices):
[[0, 1, 2, 3, 4, 5, 6, 7],
 [1, 0, 4, 5, 2, 3, 7, 6],
 [2, 4, 0, 6, 1, 7, 3, 5],
 [3, 5, 6, 0, 7, 1, 2, 4],
 [4, 2, 1, 7, 0, 6, 5, 3],
 [5, 3, 7, 1, 6, 0, 4, 2],
 [6, 7, 3, 2, 5, 4, 0, 1],
 [7, 6, 5, 4, 3, 2, 1, 0]]
Mult. Identity: '{0, 1, 2}'
Mult. Commutative? Yes
Zero Divisors: ['{0}', '{1}', '{2}', '{0, 1}', '{0, 2}', '{1, 2}']
Multiplicative Cayley Table (showing indices):
[[0, 0, 0, 0, 0, 0, 0, 0],
 [0, 1, 0, 0, 1, 1, 0, 1],
 [0, 0, 2, 0, 2, 0, 2, 2],
 [0, 0, 0, 3, 0, 3, 3, 3],
 [0, 1, 2, 0, 4, 1, 2, 4],
 [0, 1, 0, 3, 1, 5, 3, 5],
 [0, 0, 2, 3, 2, 3, 6, 6],
 [0, 1, 2, 3, 4, 5, 6, 7]]

Some Test Matrices

Abstract Matrices over a Ring

__repr__ does not produce a full representation here, since AbstractMatrix contains a NumPy ndarray, along with a Finite Algebra (Ring or Field), and it would be too cumbersome to include the entire Algebra. Consequently, __repr__ only produces a copy-and-paste-able version of the ndarray.

>>> arr0 = [['{1, 2}', '{0, 1, 2}'],
>>>         ['{0, 2}',        '{}']]

>>> mat0 = AbstractMatrix(arr0, ps3)
>>> mat0
[['{1, 2}', '{0, 1, 2}'],
 ['{0, 2}', '{}']]

__str__ produces a “pretty print” string version of the matrix’s array. NOTE: The elements in the array are delimited by spaces, not commas, so it cannot be easily copied-and-pasted.

>>> print(mat0)  # Here, only spaces delimit the lists and the elements they contain.
[['{1, 2}' '{0, 1, 2}']
 ['{0, 2}' '{}']]
>>> str(mat0)
"[['{1, 2}' '{0, 1, 2}']n ['{0, 2}' '{}']]"
>>> print(str(mat0))
[['{1, 2}' '{0, 1, 2}']
 ['{0, 2}' '{}']]
>>> mat0.array
array([['{1, 2}', '{0, 1, 2}'],
       ['{0, 2}', '{}']], dtype='<U32')

More Test Matrices

>>> arr1 = [['{1, 2}', '{0, 1, 2}', '{0, 2}'],
>>>         ['{0, 2}',        '{}',    '{1}'],
>>>         [   '{0}',       '{1}', '{0, 1}']]

>>> mat1 = AbstractMatrix(arr1, ps3)
>>> mat1
[['{1, 2}', '{0, 1, 2}', '{0, 2}'],
 ['{0, 2}', '{}', '{1}'],
 ['{0}', '{1}', '{0, 1}']]
>>> arr2 = [['{0, 1}', '{0, 2}', '{1, 2}'],
>>>         ['{0}'   , '{1, 2}',    '{2}'],
>>>         ['{0, 1}',     '{}',     '{}']]

>>> mat2 = AbstractMatrix(arr2, ps3)
>>> mat2
[['{0, 1}', '{0, 2}', '{1, 2}'],
 ['{0}', '{1, 2}', '{2}'],
 ['{0, 1}', '{}', '{}']]
>>> arr3 = [[    '{}', '{0, 1, 2}', '{0, 1, 2}',     '{}'],
>>>         ['{0, 1}',    '{0, 2}',    '{1, 2}',    '{2}'],
>>>         ['{0, 2}',        '{}',        '{}', '{0, 1}'],
>>>         [   '{1}',       '{0}',    '{0, 2}',     '{}']]

>>> mat3 = AbstractMatrix(arr3, ps3)
>>> mat3
[['{}', '{0, 1, 2}', '{0, 1, 2}', '{}'],
 ['{0, 1}', '{0, 2}', '{1, 2}', '{2}'],
 ['{0, 2}', '{}', '{}', '{0, 1}'],
 ['{1}', '{0}', '{0, 2}', '{}']]
>>> arr4 = [['{1, 2}']]

>>> mat4 = AbstractMatrix(arr4, ps3)
>>> mat4
[['{1, 2}']]

Abstract Matrix over a Field

>>> arr5 = [[  '0', '1',   'a'],
>>>         [  '1', 'a', '1+a'],
>>>         ['1+a', '0',   '1']]

>>> mat5 = AbstractMatrix(arr5, f4)
>>> mat5
[['0', '1', 'a'],
 ['1', 'a', '1+a'],
 ['1+a', '0', '1']]
>>> arr6 = [['1+a',   '0', 'a'],
>>>         ['1+a', '1+a', '0'],
>>>         [  '1',   '1', '0']]

>>> mat6 = AbstractMatrix(arr6, f4)
>>> mat6
[['1+a', '0', 'a'],
 ['1+a', '1+a', '0'],
 ['1', '1', '0']]

Matrix Addition & Subtraction

>>> mat1 + mat2
[['{0, 2}', '{1}', '{0, 1}'],
 ['{2}', '{1, 2}', '{1, 2}'],
 ['{1}', '{1}', '{0, 1}']]
>>> try:
>>>     mat2p3 = mat2 + mat3
>>>     print(mat2p3)
>>> except Exception as exc:
>>>     print(exc)
The array shapes are not equal: (3, 3) != (4, 4)
>>> mat5 + mat6
[['1+a', '1', '0'],
 ['a', '1', '1+a'],
 ['a', '1', '1']]
>>> mat2 - mat2
[['{}', '{}', '{}'],
 ['{}', '{}', '{}'],
 ['{}', '{}', '{}']]
>>> try:
>>>     mat2m5 = mat2 - mat5
>>>     print(mat2m5)
>>> except Exception as exc:
>>>     print(exc)
The array algebras must be equal

Matrix Multiplication

>>> mat1 * mat2
[['{1}', '{1}', '{1}'],
 ['{0, 1}', '{0, 2}', '{2}'],
 ['{1}', '{0, 1}', '{}']]
>>> mat2 * mat1
[['{0, 1, 2}', '{0}', '{0, 1}'],
 ['{2}', '{0}', '{0, 1}'],
 ['{1}', '{0, 1}', '{0}']]
>>> try:
>>>     mat2x3 = mat2 * mat3
>>>     print(mat2x3)
>>> except Exception as exc:
>>>     print(exc)
The array shapes are incompatible: 3 columns vs 4 rows
>>> mat5 * mat6
[['1', '1', '0'],
 ['1', 'a', 'a'],
 ['1+a', '1', '1']]

Abstract Matrix of “Zeros”

>>> matz = AbstractMatrix.zeros((2, 3), ps3)
>>> matz
[['{}', '{}', '{}'],
 ['{}', '{}', '{}']]
>>> matf = AbstractMatrix.zeros((3, 3), f4)
>>> matf
[['0', '0', '0'],
 ['0', '0', '0'],
 ['0', '0', '0']]

Abstract Identity Matrix

>>> AbstractMatrix.identity(4, ps3)
[['{0, 1, 2}', '{}', '{}', '{}'],
 ['{}', '{0, 1, 2}', '{}', '{}'],
 ['{}', '{}', '{0, 1, 2}', '{}'],
 ['{}', '{}', '{}', '{0, 1, 2}']]
>>> mat1
[['{1, 2}', '{0, 1, 2}', '{0, 2}'],
 ['{0, 2}', '{}', '{1}'],
 ['{0}', '{1}', '{0, 1}']]
>>> id3 = AbstractMatrix.identity(3, ps3)

>>> mat1 * id3 == mat1
True
>>> id3 * mat1 == mat1
True
>>> id3f = AbstractMatrix.identity(3, f4)
>>> id3f
[['1', '0', '0'],
 ['0', '1', '0'],
 ['0', '0', '1']]
>>> mat5 * id3f - mat5
[['0', '0', '0'],
 ['0', '0', '0'],
 ['0', '0', '0']]

Random Abstract Matrix

>>> AbstractMatrix.random((3, 3), ps3)
[['{1, 2}', '{1}', '{}'],
 ['{0, 2}', '{2}', '{0}'],
 ['{0, 1}', '{2}', '{}']]
>>> AbstractMatrix.random((3, 3), f4)
[['a', '1+a', '0'],
 ['0', '1+a', '1+a'],
 ['a', '1+a', 'a']]

Get/Set Matrix Element Values

>>> mat2
[['{0, 1}', '{0, 2}', '{1, 2}'],
 ['{0}', '{1, 2}', '{2}'],
 ['{0, 1}', '{}', '{}']]
>>> mat2[1, 2]
np.str_('{2}')
>>> mat2[1, 2] = '{0, 1, 2}'
>>> mat2
[['{0, 1}', '{0, 2}', '{1, 2}'],
 ['{0}', '{1, 2}', '{0, 1, 2}'],
 ['{0, 1}', '{}', '{}']]
>>> mat2[1, 2]
np.str_('{0, 1, 2}')

Put mat2 back the way it was…

>>> mat2[1, 2] = '{2}'
>>> mat2
[['{0, 1}', '{0, 2}', '{1, 2}'],
 ['{0}', '{1, 2}', '{2}'],
 ['{0, 1}', '{}', '{}']]

Matrix Minor

>>> mat2
[['{0, 1}', '{0, 2}', '{1, 2}'],
 ['{0}', '{1, 2}', '{2}'],
 ['{0, 1}', '{}', '{}']]
>>> mnr2 = mat2.minor(0,0)
>>> mnr2
[['{1, 2}', '{2}'],
 ['{}', '{}']]
>>> mat5
[['0', '1', 'a'],
 ['1', 'a', '1+a'],
 ['1+a', '0', '1']]
>>> mnr3 = mat5.minor(1, 1)
>>> mnr3
[['0', 'a'],
 ['1+a', '1']]

Abstract Cofactor Matrix

>>> cof2 = mat2.cofactor_matrix()
>>> cof2
[['{}', '{}', '{1}'],
 ['{}', '{1}', '{0}'],
 ['{1}', '{}', '{0, 1}']]
>>> cof3 = mat5.cofactor_matrix()
>>> cof3
[['a', '1+a', '1'],
 ['1', '1', '1+a'],
 ['0', 'a', '1']]

Abstract Matrix Transpose

>>> cof2_trans = cof2.transpose()
>>> cof2_trans
[['{}', '{}', '{1}'],
 ['{}', '{1}', '{}'],
 ['{1}', '{0}', '{0, 1}']]
>>> cof3.transpose()
[['a', '1', '0'],
 ['1+a', '1', 'a'],
 ['1', '1+a', '1']]

Abstract Matrix Determinant

>>> mat2
[['{0, 1}', '{0, 2}', '{1, 2}'],
 ['{0}', '{1, 2}', '{2}'],
 ['{0, 1}', '{}', '{}']]
>>> mat2.determinant()
'{1}'

Here’s a breaksdown of the basic computations required to get the determinant.

NOTE: Addition & multiplication for the ring, ps3, is commutative, so the order of addition and multiplication is irrelevant, below.

>>> minor_det_0 = ps3.sub(ps3.mult('{1, 2}', '{}'), ps3.mult('{}', '{2}'))
>>> minor_det_0
'{}'
>>> minor_det_1 = ps3.sub(ps3.mult('{0}', '{}'), ps3.mult('{2}', '{0, 1}'))
>>> minor_det_1
'{}'
>>> minor_det_2 = ps3.sub(ps3.mult('{0}', '{}'), ps3.mult('{1, 2}', '{0, 1}'))
>>> minor_det_2
'{1}'
>>> det = ps3.sub(ps3.add(ps3.mult('{0, 1}', minor_det_0),
>>>                       ps3.mult('{1, 2}', minor_det_2)),
>>>               ps3.mult('{0, 2}', minor_det_1))
>>> det
'{1}'

Determinant of an Abstract Matrix over a finite Field:

>>> mat5.determinant()
'1'

Abstract Matrix Inverse

An abstract matrix over a Ring cannot have a true inverse, but if we apply the Laplace expansion algorithm anyway, we obtain a matrix that, when multiplied by the original matrix, results in a diagonal matrix, just not an “identity matrix”, because Rings don’t necessarily have multiplicative identity elements.

>>> mat2_inv = mat2.inverse()
>>> mat2_inv
[['{}', '{}', '{1}'],
 ['{}', '{1}', '{}'],
 ['{1}', '{}', '{1}']]

The product of mat2_inv with mat2 yields a diagonal matrix:

>>> mat2 * mat2_inv
[['{1}', '{}', '{}'],
 ['{}', '{1}', '{}'],
 ['{}', '{}', '{1}']]
>>> mat2_inv * mat2
[['{1}', '{}', '{}'],
 ['{}', '{1}', '{}'],
 ['{}', '{}', '{1}']]

An Abstract Matrix over a Field can have an inverse, as long as its determinant is the Field’s multiplicative identity element:

>>> mat5.determinant()  # Based on the Field, f4, defined above
'1'
>>> mat5_inv = mat5.inverse()
>>> mat5_inv
[['a', '1', '0'],
 ['1+a', '1', 'a'],
 ['1', '1+a', '1']]
>>> mat5 * mat5_inv
[['1', '0', '0'],
 ['0', '1', '0'],
 ['0', '0', '1']]
>>> mat5_inv * mat5
[['1', '0', '0'],
 ['0', '1', '0'],
 ['0', '0', '1']]