]> gitweb.michael.orlitzky.com - sage.d.git/commitdiff
matrix_algebra: factor out a generic matrix algebra class.
authorMichael Orlitzky <michael@orlitzky.com>
Tue, 2 Mar 2021 22:25:03 +0000 (17:25 -0500)
committerMichael Orlitzky <michael@orlitzky.com>
Tue, 2 Mar 2021 22:25:03 +0000 (17:25 -0500)
mjo/matrix_algebra.py [new file with mode: 0644]
mjo/octonions.py

diff --git a/mjo/matrix_algebra.py b/mjo/matrix_algebra.py
new file mode 100644 (file)
index 0000000..8f75ef2
--- /dev/null
@@ -0,0 +1,276 @@
+from sage.misc.table import table
+from sage.categories.magmatic_algebras import MagmaticAlgebras
+from sage.misc.cachefunc import cached_method
+from sage.combinat.free_module import CombinatorialFreeModule
+from sage.modules.with_basis.indexed_element import IndexedFreeModuleElement
+
+class MatrixAlgebraElement(IndexedFreeModuleElement):
+    def nrows(self):
+        return self.parent().nrows()
+    ncols = nrows
+
+    @cached_method
+    def rows(self):
+        r"""
+        SETUP::
+
+            sage: from mjo.matrix_algebra import MatrixAlgebra
+
+        EXAMPLES::
+
+            sage: M = MatrixAlgebra(QQbar,RDF,2)
+            sage: A = M.monomial((0,0,1)) + 4*M.monomial((0,1,1))
+            sage: A
+            +-----+-----+
+            | 1.0 | 4.0 |
+            +-----+-----+
+            | 0   | 0   |
+            +-----+-----+
+            sage: A.rows()
+            [[1.0, 4.0], [0, 0]]
+
+        """
+        zero = self.parent().entry_algebra().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"""
+        Display this matrix as a table.
+
+        The SageMath Matrix class representation is not easily reusable,
+        but using a table fakes it.
+
+        SETUP::
+
+            sage: from mjo.matrix_algebra import MatrixAlgebra
+
+        EXAMPLES::
+
+            sage: MatrixAlgebra(ZZ,ZZ,2).one()
+            +---+---+
+            | 1 | 0 |
+            +---+---+
+            | 0 | 1 |
+            +---+---+
+
+        """
+        return table(self.rows(), frame=True)._repr_()
+
+
+    def list(self):
+        r"""
+        Return one long list of this matrix's entries.
+
+        SETUP::
+
+            sage: from mjo.matrix_algebra import MatrixAlgebra
+
+        EXAMPLES::
+
+            sage: MatrixAlgebra(ZZ,ZZ,2).one().list()
+            [1, 0, 0, 1]
+
+        """
+        return sum( self.rows(), [] )
+
+
+    def __getitem__(self, indices):
+        r"""
+
+        SETUP::
+
+            sage: from mjo.matrix_algebra import MatrixAlgebra
+
+        EXAMPLES::
+
+            sage: M = MatrixAlgebra(ZZ,ZZ,2).one()
+            sage: M[0,0]
+            1
+            sage: M[0,1]
+            0
+            sage: M[1,0]
+            0
+            sage: M[1,1]
+            1
+
+        """
+        i,j = indices
+        return self.rows()[i][j]
+
+    def trace(self):
+        r"""
+        Return the sum of this matrix's diagonal entries.
+
+        SETUP::
+
+            sage: from mjo.matrix_algebra import MatrixAlgebra
+
+        EXAMPLES:
+
+        The trace (being a sum of entries) belongs to the same algebra
+        as those entries, and NOT the scalar ring::
+
+            sage: entries = MatrixSpace(ZZ,2)
+            sage: scalars = ZZ
+            sage: M = MatrixAlgebra(entries, scalars, 2)
+            sage: M.one().trace()
+            [2 0]
+            [0 2]
+
+        """
+        zero = self.parent().entry_algebra().zero()
+        return sum( (self[i,i] for i in range(self.nrows())), zero )
+
+    def matrix_space(self):
+        r"""
+
+        SETUP::
+
+            sage: from mjo.matrix_algebra import MatrixAlgebra
+
+        TESTS::
+
+            sage: set_random_seed()
+            sage: entries = QuaternionAlgebra(QQ,-1,-1)
+            sage: M = MatrixAlgebra(entries, QQ, 3)
+            sage: M.random_element().matrix_space() == M
+            True
+
+        """
+        return self.parent()
+
+    # onlt valid in HurwitzMatrixAlgebra subclass
+    # def is_hermitian(self):
+    #     r"""
+
+    #     SETUP::
+
+    #         sage: from mjo.octonions import OctonionMatrixAlgebra
+
+    #     EXAMPLES::
+
+    #         sage: MS = OctonionMatrixAlgebra(3)
+    #         sage: MS.one().is_hermitian()
+    #         True
+
+    #     """
+    #     return all( self[i,j] == self[j,i].conjugate()
+    #                 for i in range(self.nrows())
+    #                 for j in range(self.ncols()) )
+
+
+class MatrixAlgebra(CombinatorialFreeModule):
+    r"""
+    An algebra of ``n``-by-``n`` matrices over an arbitrary scalar
+    ring whose entries come from a magmatic algebra that need not
+    be the same as the scalars.
+
+    The usual matrix spaces in SageMath don't support separate spaces
+    for the entries and the scalars; in particular they assume that
+    the entries come from a commutative and associative ring. This
+    is problematic in several interesting matrix algebras, like those
+    where the entries are quaternions or octonions.
+    """
+    Element = MatrixAlgebraElement
+
+    def __init__(self, entry_algebra, scalars, n, prefix="A", **kwargs):
+
+        category = MagmaticAlgebras(scalars).FiniteDimensional()
+        category = category.WithBasis()
+
+        if "Unital" in entry_algebra.category().axioms():
+            category.Unital()
+        if "Associative" in entry_algebra.category().axioms():
+            category.Associative()
+
+        self._nrows = n
+
+        # Since the scalar ring is real but the entries are not,
+        # sticking a "1" in each position doesn't give us a basis for
+        # the space. We actually need to stick each of e0, e1, ...  (a
+        # basis for the entry algebra itself) 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._entry_algebra = entry_algebra
+        entry_basis = entry_algebra.gens()
+
+        basis_indices = cartesian_product([I,J,entry_basis])
+        super().__init__(scalars,
+                         basis_indices,
+                         category=category,
+                         prefix=prefix,
+                         bracket='(')
+
+    def _repr_(self):
+        return ("Module of %d by %d matrices with entries in %s"
+                " over the scalar ring %s" %
+                (self.nrows(),
+                 self.ncols(),
+                 self.entry_algebra(),
+                 self.base_ring()) )
+
+    def entry_algebra(self):
+        r"""
+        Return the algebra that our elements' entries come from.
+        """
+        return self._entry_algebra
+
+    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.matrix_algebra import MatrixAlgebra
+
+        """
+        return sum( (self.monomial((i,i,self.entry_algebra().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
+        entries.
+
+        SETUP::
+
+            sage: from mjo.matrix_algebra import MatrixAlgebra
+
+        """
+        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 entry algebra to ours. Otherwise we can fail
+            # to convert an element of (for example) Octonions(QQ)
+            # to Octonions(AA).
+            return self.entry_algebra().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() )
index 04aa318a3d8bed7158da61c26b0bb8b25f7cab0b..073765ccf1ef934ab6d9f6fc59435384bf832f36 100644 (file)
@@ -7,6 +7,8 @@ from sage.rings.all import AA, ZZ
 from sage.matrix.matrix_space import MatrixSpace
 from sage.misc.table import table
 
+from mjo.matrix_algebra import MatrixAlgebra
+
 class Octonion(IndexedFreeModuleElement):
     def conjugate(self):
         r"""
@@ -339,171 +341,8 @@ class Octonions(CombinatorialFreeModule):
         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_nested_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_nested_list()
-            [[e0 + 2*e3, 0, 0], [0, 0, 0], [0, 0, 0]]
-
-        """
-        zero = self.parent().entry_algebra().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_nested_list(), frame=True)._repr_()
-
-
-    def list(self):
-        r"""
-        Return one long list of this matrix's entries.
-
-        SETUP::
-
-            sage: from mjo.octonions import OctonionMatrixAlgebra
-
-        EXAMPLES::
-
-            sage: MS = OctonionMatrixAlgebra(3)
-            sage: E00e0 = MS.gens()[0]
-            sage: E00e3 = MS.gens()[3]
-            sage: (E00e0 + 2*E00e3).to_nested_list()
-            [[e0 + 2*e3, 0, 0], [0, 0, 0], [0, 0, 0]]
-            sage: (E00e0 + 2*E00e3).list()
-            [e0 + 2*e3, 0, 0, 0, 0, 0, 0, 0, 0]
-
-        """
-        return sum( self.to_nested_list(), [] )
-
-
-    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_nested_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().entry_algebra().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()
-
-    def is_hermitian(self):
-        r"""
-
-        SETUP::
-
-            sage: from mjo.octonions import OctonionMatrixAlgebra
-
-        EXAMPLES::
-
-            sage: MS = OctonionMatrixAlgebra(3)
-            sage: MS.one().is_hermitian()
-            True
-
-        """
-        return all( self[i,j] == self[j,i].conjugate()
-                    for i in range(self.nrows())
-                    for j in range(self.ncols()) )
 
-class OctonionMatrixAlgebra(CombinatorialFreeModule):
+class OctonionMatrixAlgebra(MatrixAlgebra):
     r"""
     The algebra of ``n``-by-``n`` matrices with octonion entries over
     (a subfield of) the real numbers.
@@ -511,116 +350,45 @@ class OctonionMatrixAlgebra(CombinatorialFreeModule):
     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, scalars=AA, prefix="E", **kwargs):
-        # Not associative, not commutative
-        category = MagmaticAlgebras(scalars).FiniteDimensional()
-        category = category.WithBasis().Unital()
+    SETUP::
 
-        self._nrows = n
+        sage: from mjo.octonions import Octonions, OctonionMatrixAlgebra
 
-        # Since the scalar ring is real but the entries are not,
-        # sticking a "1" in each position doesn't give us a basis for
-        # the space. We actually need to stick each of e0, e1, ...  (a
-        # basis for the entry algebra itself) into each position.
-        from sage.sets.finite_enumerated_set import FiniteEnumeratedSet
-        from sage.categories.sets_cat import cartesian_product
+    EXAMPLES::
 
-        I = FiniteEnumeratedSet(range(n))
-        J = FiniteEnumeratedSet(range(n))
-        self._entry_algebra = Octonions(field=scalars)
-        entry_basis = self._entry_algebra.gens()
+        sage: OctonionMatrixAlgebra(3)
+        Module of 3 by 3 matrices with entries in Octonion algebra with base
+        ring Algebraic Real Field over the scalar ring Algebraic Real Field
+        sage: OctonionMatrixAlgebra(3,QQ)
+        Module of 3 by 3 matrices with entries in Octonion algebra with base
+        ring Rational Field over the scalar ring Rational Field
+
+    ::
+
+        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 |
+        +---------+---------+
+
+    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
 
-        basis_indices = cartesian_product([I,J,entry_basis])
-        super().__init__(scalars,
-                         basis_indices,
-                         category=category,
+    """
+    def __init__(self, n, scalars=AA, prefix="E", **kwargs):
+        super().__init__(Octonions(field=scalars),
+                         scalars,
+                         n,
                          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 entry_algebra(self):
-        r"""
-        Return the algebra that our elements' entries come from.
-        """
-        return self._entry_algebra
-
-    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.entry_algebra().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.entry_algebra().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() )
+                         **kwargs)