]> gitweb.michael.orlitzky.com - sage.d.git/blobdiff - mjo/octonions.py
octonions: add matrix_space() method for elements and _repr_ for parents.
[sage.d.git] / mjo / octonions.py
index f6e222eebf7734a968dfa46a478d64ea7631d6cf..73acf2db2ebf76258f47ed1156113925f4f257ed 100644 (file)
@@ -1,3 +1,5 @@
+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
@@ -171,6 +173,52 @@ class Octonion(IndexedFreeModuleElement):
             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::
@@ -289,3 +337,251 @@ class Octonions(CombinatorialFreeModule):
                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() )