+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() )