+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
from sage.categories.magmatic_algebras import MagmaticAlgebras
raise ValueError("zero is not invertible")
return self.conjugate()/self._norm_squared()
+
+ def cayley_dickson(self, Q=None):
+ r"""
+ Return the Cayley-Dickson representation of this element in terms
+ of the quaternion algebra ``Q``.
+
+ The Cayley-Dickson representation is an identification of
+ octionions `x` and `y` with pairs of quaternions `(a,b)` and
+ `(c,d)` respectively such that:
+
+ * `x + y = (a+b, c+d)`
+ * `xy` = (ac - \bar{d}*b, da + b\bar{c})`
+ * `\bar{x} = (a,-b)`
+
+ where `\bar{x}` denotes the conjugate of `x`.
+
+ SETUP::
+
+ sage: from mjo.octonions import Octonions
+
+ EXAMPLES::
+
+ sage: O = Octonions()
+ sage: x = sum(O.gens())
+ sage: x.cayley_dickson()
+ (1 + i + j + k, 1 + i + j + k)
+
+ """
+ if Q is None:
+ Q = QuaternionAlgebra(self.base_ring(), -1, -1)
+
+ i,j,k = Q.gens()
+ a = (self.coefficient(0)*Q.one() +
+ self.coefficient(1)*i +
+ self.coefficient(2)*j +
+ self.coefficient(3)*k )
+ b = (self.coefficient(4)*Q.one() +
+ self.coefficient(5)*i +
+ self.coefficient(6)*j +
+ self.coefficient(7)*k )
+
+ from sage.categories.sets_cat import cartesian_product
+ P = cartesian_product([Q,Q])
+ return P((a,b))
+
+
class Octonions(CombinatorialFreeModule):
r"""
SETUP::
for i in range(n) ]
return table(M, header_row=True, header_column=True, frame=True)
+
+
+class OctonionMatrix(IndexedFreeModuleElement):
+ def nrows(self):
+ return self.parent().nrows()
+ ncols = nrows
+
+ @cached_method
+ def to_list(self):
+ r"""
+ SETUP::
+
+ sage: from mjo.octonions import OctonionMatrixAlgebra
+
+ EXAMPLES::
+
+ 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]]
+
+ """
+ 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 __repr__(self):
+ r"""
+ SETUP::
+
+ sage: from mjo.octonions import OctonionMatrixAlgebra
+
+ EXAMPLES::
+
+ sage: OctonionMatrixAlgebra(3).one()
+ +----+----+----+
+ | e0 | 0 | 0 |
+ +----+----+----+
+ | 0 | e0 | 0 |
+ +----+----+----+
+ | 0 | 0 | e0 |
+ +----+----+----+
+
+ """
+ return table(self.to_list(), frame=True)._repr_()
+
+
+ def __getitem__(self, indices):
+ r"""
+
+ SETUP::
+
+ sage: from mjo.octonions import OctonionMatrixAlgebra
+
+ EXAMPLES::
+
+ 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
+
+ """
+ i,j = indices
+ return self.to_list()[i][j]
+
+ def trace(self):
+ r"""
+
+ SETUP::
+
+ sage: from mjo.octonions import OctonionMatrixAlgebra
+
+ EXAMPLES::
+
+ sage: MS = OctonionMatrixAlgebra(3)
+ sage: MS.one().trace()
+ 3*e0
+
+ """
+ zero = self.parent().octonions().zero()
+ return sum( (self[i,i] for i in range(self.nrows())), zero )
+
+ def matrix_space(self):
+ r"""
+
+ SETUP::
+
+ sage: from mjo.octonions import OctonionMatrixAlgebra
+
+ TESTS::
+
+ sage: set_random_seed()
+ sage: MS = OctonionMatrixAlgebra(2)
+ sage: MS.random_element().matrix_space()
+ Module of 2 by 2 matrices with octonion entries over the
+ scalar ring Algebraic Real Field
+
+ """
+ return self.parent()
+
+
+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()
+
+ self._nrows = n
+
+ # 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 _repr_(self):
+ return ("Module of %d by %d matrices with octonion entries"
+ " over the scalar ring %s" %
+ (self.nrows(), self.ncols(), self.base_ring()) )
+
+ 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 OctonionMatrixAlgebra
+
+ TESTS::
+
+ 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
+
+ """
+ return sum( (self.monomial((i,i,self._octonions.one()))
+ for i in range(self.nrows()) ),
+ self.zero() )
+
+ 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, OctonionMatrixAlgebra
+
+ EXAMPLES::
+
+ sage: O = Octonions(QQ)
+ sage: e0,e1,e2,e3,e4,e5,e6,e7 = O.gens()
+ sage: MS = OctonionMatrixAlgebra(2)
+ sage: MS.from_list([ [e0+e4, e1+e5],
+ ....: [e2-e6, e3-e7] ])
+ +---------+---------+
+ | e0 + e4 | e1 + e5 |
+ +---------+---------+
+ | e2 - e6 | e3 - e7 |
+ +---------+---------+
+
+ """
+ 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() )