From ee21aa47a00c19068c21d631fda6598a6e0648e9 Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Tue, 2 Mar 2021 10:12:56 -0500 Subject: [PATCH] octonions: replace yesterday's hacky octonion matrices with the real thing. --- mjo/octonions.py | 299 +++++++++++++++++++++++++---------------------- 1 file changed, 162 insertions(+), 137 deletions(-) diff --git a/mjo/octonions.py b/mjo/octonions.py index 3c3f474..352c4c5 100644 --- a/mjo/octonions.py +++ b/mjo/octonions.py @@ -1,3 +1,4 @@ +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 @@ -338,202 +339,226 @@ class Octonions(CombinatorialFreeModule): 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() ) -- 2.43.2