+from sage.misc.cachefunc import cached_method
from sage.algebras.quatalg.quaternion_algebra import QuaternionAlgebra
from sage.combinat.free_module import CombinatorialFreeModule
from sage.modules.with_basis.indexed_element import IndexedFreeModuleElement
return table(M, header_row=True, header_column=True, frame=True)
-class OctonionMatrix:
- r"""
- A pseudo-matrix class that supports octonion entries.
-
- Matrices in SageMath can't have base rings that are
- non-commutative, much less non-associative. The "matrix" scaling,
- addition, and multiplication operations for this class are all
- wholly inefficient, but are hand-written to guarantee that they
- are performed in the correct order. Of course, it can't guarantee
- that you won't write something visually ambiguous like
- `A*B*C`... but you already have that problem with the
- non-associative octonions themselves.
-
- This class is only as sophisticated as it need to be to implement
- the Jordan and inner-products in the space of Hermitian matrices
- with octonion entries, namely ``(X*Y+Y*X)/2`` and
- ``(X*Y).trace().real()`` for two octonion matrices ``X`` and
- ``Y``.
-
- .. WARNING:
-
- These are not matrices in the usual sense! Matrix
- multiplication is associative. Multiplication of octonion
- "matrices" cannot be, since the multiplication of the
- underlying octonions is not (consider two 1-by-1 matrices each
- containing a single octonion).
- """
- def __init__(self, entries):
- r"""
- Initialize this matrix with a list of lists in (row,column) order,
- just like in SageMath.
- """
- self._nrows = len(entries)
-
- if self._nrows == 0:
- self._ncols = 0
- else:
- # We don't check that you haven't supplied two rows (or
- # columns) of different lengths!
- self._ncols = len(entries[0])
-
- self._entries = entries
+class OctonionMatrix(IndexedFreeModuleElement):
+ def nrows(self):
+ return self.parent().nrows()
+ ncols = nrows
- def __getitem__(self, indices):
+ @cached_method
+ def to_list(self):
r"""
SETUP::
- sage: from mjo.octonions import Octonions, OctonionMatrix
+ sage: from mjo.octonions import OctonionMatrixAlgebra
EXAMPLES::
- sage: O = Octonions(field=QQ)
- sage: M = OctonionMatrix([ [O.one(), O.zero()],
- ....: [O.zero(), O.one() ] ])
- sage: M[0,0]
- e0
- sage: M[0,1]
- 0
- sage: M[1,0]
- 0
- sage: M[1,1]
- e0
+ sage: MS = OctonionMatrixAlgebra(3)
+ sage: E00e0 = MS.gens()[0]
+ sage: E00e0
+ +----+---+---+
+ | e0 | 0 | 0 |
+ +----+---+---+
+ | 0 | 0 | 0 |
+ +----+---+---+
+ | 0 | 0 | 0 |
+ +----+---+---+
+ sage: E00e3 = MS.gens()[3]
+ sage: E00e3
+ +----+---+---+
+ | e3 | 0 | 0 |
+ +----+---+---+
+ | 0 | 0 | 0 |
+ +----+---+---+
+ | 0 | 0 | 0 |
+ +----+---+---+
+ sage: (E00e0 + 2*E00e3).to_list()
+ [[e0 + 2*e3, 0, 0], [0, 0, 0], [0, 0, 0]]
+
"""
- i,j = indices
- return self._entries[i][j]
+ zero = self.parent().octonions().zero()
+ l = [[zero for j in range(self.ncols())] for i in range(self.nrows())]
+ for (k,v) in self.monomial_coefficients().items():
+ (i,j,e) = k
+ l[i][j] += v*e
+ return l
- def nrows(self):
+ def __repr__(self):
r"""
SETUP::
- sage: from mjo.octonions import Octonions, OctonionMatrix
+ sage: from mjo.octonions import OctonionMatrixAlgebra
EXAMPLES::
- sage: O = Octonions(field=QQ)
- sage: M = OctonionMatrix([ [O.one(), O.zero()],
- ....: [O.zero(), O.one() ],
- ....: [O.zero(), O.zero()] ])
- sage: M.nrows()
- 3
+ sage: OctonionMatrixAlgebra(3).one()
+ +----+----+----+
+ | e0 | 0 | 0 |
+ +----+----+----+
+ | 0 | e0 | 0 |
+ +----+----+----+
+ | 0 | 0 | e0 |
+ +----+----+----+
"""
- return self._nrows
+ return table(self.to_list(), frame=True)._repr_()
+
- def ncols(self):
+ def __getitem__(self, indices):
r"""
SETUP::
- sage: from mjo.octonions import Octonions, OctonionMatrix
+ sage: from mjo.octonions import OctonionMatrixAlgebra
EXAMPLES::
- sage: O = Octonions(field=QQ)
- sage: M = OctonionMatrix([ [O.one(), O.zero()],
- ....: [O.zero(), O.one() ],
- ....: [O.zero(), O.zero()] ])
- sage: M.ncols()
- 2
+ sage: MS = OctonionMatrixAlgebra(2)
+ sage: I = MS.one()
+ sage: I[0,0]
+ e0
+ sage: I[0,1]
+ 0
+ sage: I[1,0]
+ 0
+ sage: I[1,1]
+ e0
"""
- return self._ncols
-
- def __repr__(self):
- return table(self._entries, frame=True)._repr_()
+ i,j = indices
+ return self.to_list()[i][j]
- def __mul__(self,rhs):
+ def trace(self):
r"""
SETUP::
- sage: from mjo.octonions import Octonions, OctonionMatrix
+ sage: from mjo.octonions import OctonionMatrixAlgebra
EXAMPLES::
- sage: O = Octonions(QQ)
- sage: e1 = O.monomial(1)
- sage: e2 = O.monomial(2)
- sage: e1*e2
- e3
- sage: e2*e1
- -e3
- sage: E1 = OctonionMatrix([[e1]])
- sage: E2 = OctonionMatrix([[e2]])
- sage: E1*E2
- +----+
- | e3 |
- +----+
- sage: E2*E1
- +-----+
- | -e3 |
- +-----+
+ sage: MS = OctonionMatrixAlgebra(3)
+ sage: MS.one().trace()
+ 3*e0
"""
- if not self.ncols() == rhs.nrows():
- raise ValueError("dimension mismatch")
+ zero = self.parent().octonions().zero()
+ return sum( (self[i,i] for i in range(self.nrows())), zero )
+
- m = self.nrows()
- n = self.ncols()
- p = rhs.ncols()
+class OctonionMatrixAlgebra(CombinatorialFreeModule):
+ r"""
+ The algebra of ``n``-by-``n`` matrices with octonion entries over
+ (a subfield of) the real numbers.
+
+ The usual matrix spaces in SageMath don't support octonion entries
+ because they assume that the entries of the matrix come from a
+ commutative and associative ring (i.e. very NOT the octonions).
+ """
+ Element = OctonionMatrix
+
+ def __init__(self, n, field=AA, prefix="E", **kwargs):
+ # Not associative, not commutative
+ category = MagmaticAlgebras(field).FiniteDimensional()
+ category = category.WithBasis().Unital()
- C = lambda i,j: sum( self[i,k]*rhs[k,j] for k in range(n) )
- return OctonionMatrix([ [C(i,j) for j in range(m)]
- for i in range(p) ] )
+ self._nrows = n
- def __rmul__(self,scalar):
+ # Since the scalar field is real but the entries are octonion,
+ # sticking a "1" in each position doesn't give us a basis for
+ # the space. We actually need to stick each of e0, e1, ..., e7
+ # (a basis for the Octonions themselves) into each position.
+ from sage.sets.finite_enumerated_set import FiniteEnumeratedSet
+ from sage.categories.sets_cat import cartesian_product
+
+ I = FiniteEnumeratedSet(range(n))
+ J = FiniteEnumeratedSet(range(n))
+ self._octonions = Octonions(field=field)
+ entry_basis = self._octonions.gens()
+
+ basis_indices = cartesian_product([I,J,entry_basis])
+ super().__init__(field,
+ basis_indices,
+ category=category,
+ prefix=prefix,
+ bracket='(')
+
+ def octonions(self):
r"""
+ Return the Octonion algebra that our elements' entries come from.
+ """
+ return self._octonions
+ def nrows(self):
+ return self._nrows
+ ncols = nrows
+
+ def product_on_basis(self, mon1, mon2):
+ (i,j,oct1) = mon1
+ (k,l,oct2) = mon2
+ if j == k:
+ return self.monomial((i,l,oct1*oct2))
+ else:
+ return self.zero()
+
+ def one(self):
+ r"""
SETUP::
- sage: from mjo.octonions import Octonions, OctonionMatrix
+ sage: from mjo.octonions import OctonionMatrixAlgebra
- EXAMPLES::
+ TESTS::
- sage: O = Octonions(QQ)
- sage: M = OctonionMatrix([[O.one(), O.zero()],
- ....: [O.zero(),O.one() ] ])
- sage: 2*M
- +------+------+
- | 2*e0 | 0 |
- +------+------+
- | 0 | 2*e0 |
- +------+------+
+ sage: set_random_seed()
+ sage: MS = OctonionMatrixAlgebra(ZZ.random_element(10))
+ sage: x = MS.random_element()
+ sage: x*MS.one() == x and MS.one()*x == x
+ True
- r"""
- # SCALAR GOES ON THE LEFT HERE
- return OctonionMatrix([ [scalar*self[i,j]
- for j in range(self.ncols())]
- for i in range(self.nrows()) ])
+ """
+ return sum( (self.monomial((i,i,self._octonions.one()))
+ for i in range(self.nrows()) ),
+ self.zero() )
- def __add__(self,rhs):
+ def from_list(self, entries):
r"""
+ Construct an element of this algebra from a list of lists of
+ octonions.
+
SETUP::
- sage: from mjo.octonions import Octonions, OctonionMatrix
+ sage: from mjo.octonions import Octonions, OctonionMatrixAlgebra
EXAMPLES::
sage: O = Octonions(QQ)
sage: e0,e1,e2,e3,e4,e5,e6,e7 = O.gens()
- sage: A = OctonionMatrix([ [e0,e1],
- ....: [e2,e3] ])
- sage: B = OctonionMatrix([ [e4,e5],
- ....: [e6,e7] ])
- sage: A+B
+ sage: MS = OctonionMatrixAlgebra(2)
+ sage: MS.from_list([ [e0+e4, e1+e5],
+ ....: [e2-e6, e3-e7] ])
+---------+---------+
| e0 + e4 | e1 + e5 |
+---------+---------+
- | e2 + e6 | e3 + e7 |
+ | e2 - e6 | e3 - e7 |
+---------+---------+
"""
- if not self.ncols() == rhs.ncols():
- raise ValueError("column dimension mismatch")
- if not self.nrows() == rhs.nrows():
- raise ValueError("row dimension mismatch")
- return OctonionMatrix([ [self[i,j] + rhs[i,j]
- for j in range(self.ncols())]
- for i in range(self.nrows()) ])
+ nrows = len(entries)
+ ncols = 0
+ if nrows > 0:
+ ncols = len(entries[0])
+
+ if (not all( len(r) == ncols for r in entries )) or (ncols != nrows):
+ raise ValueError("list must be square")
+
+ def convert(e_ij):
+ # We have to pass through vectors to convert from the
+ # given octonion algebra to ours. Otherwise we can fail
+ # to convert an element of (for example) Octonions(QQ)
+ # to Octonions(AA).
+ return self.octonions().from_vector(e_ij.to_vector())
+
+ return sum( (self.monomial( (i,j, convert(entries[i][j])) )
+ for i in range(nrows)
+ for j in range(ncols) ),
+ self.zero() )