--- /dev/null
+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() )
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"""
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.
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)