# we've specified a real embedding.
raise ValueError("scalar field is not real")
- from mjo.eja.eja_utils import _change_ring
- # If the basis given to us wasn't over the field that it's
- # supposed to be over, fix that. Or, you know, crash.
- basis = tuple( _change_ring(b, field) for b in basis )
-
if check_axioms:
# Check commutativity of the Jordan and inner-products.
# This has to be done before we build the multiplication
class MatrixEJA:
+ @staticmethod
+ def jordan_product(X,Y):
+ return (X*Y + Y*X)/2
+
+ @staticmethod
+ def trace_inner_product(X,Y):
+ r"""
+ A trace inner-product for matrices that aren't embedded in the
+ reals.
+ """
+ # We take the norm (absolute value) because Octonions() isn't
+ # smart enough yet to coerce its one() into the base field.
+ return (X*Y).trace().abs()
+
+class RealEmbeddedMatrixEJA(MatrixEJA):
@staticmethod
def dimension_over_reals():
r"""
raise ValueError("the matrix 'M' must be a real embedding")
return M
- @staticmethod
- def jordan_product(X,Y):
- return (X*Y + Y*X)/2
@classmethod
def trace_inner_product(cls,X,Y):
SETUP::
- sage: from mjo.eja.eja_algebra import (RealSymmetricEJA,
- ....: ComplexHermitianEJA,
+ sage: from mjo.eja.eja_algebra import (ComplexHermitianEJA,
....: QuaternionHermitianEJA)
EXAMPLES::
- This gives the same answer as it would if we computed the trace
- from the unembedded (original) matrices::
-
- sage: set_random_seed()
- sage: J = RealSymmetricEJA.random_instance()
- sage: x,y = J.random_elements(2)
- sage: Xe = x.to_matrix()
- sage: Ye = y.to_matrix()
- sage: X = J.real_unembed(Xe)
- sage: Y = J.real_unembed(Ye)
- sage: expected = (X*Y).trace()
- sage: actual = J.trace_inner_product(Xe,Ye)
- sage: actual == expected
- True
-
- ::
-
sage: set_random_seed()
sage: J = ComplexHermitianEJA.random_instance()
sage: x,y = J.random_elements(2)
# as a REAL matrix will be 2*a = 2*Re(z_1). And so forth.
return (X*Y).trace()/cls.dimension_over_reals()
-
-class RealMatrixEJA(MatrixEJA):
- @staticmethod
- def dimension_over_reals():
- return 1
-
-
-class RealSymmetricEJA(ConcreteEJA, RealMatrixEJA):
+class RealSymmetricEJA(ConcreteEJA, MatrixEJA):
"""
The rank-n simple EJA consisting of real symmetric n-by-n
matrices, the usual symmetric Jordan product, and the trace inner
"""
@classmethod
- def _denormalized_basis(cls, n):
+ def _denormalized_basis(cls, n, field):
"""
Return a basis for the space of real symmetric n-by-n matrices.
sage: set_random_seed()
sage: n = ZZ.random_element(1,5)
- sage: B = RealSymmetricEJA._denormalized_basis(n)
+ sage: B = RealSymmetricEJA._denormalized_basis(n,ZZ)
sage: all( M.is_symmetric() for M in B)
True
S = []
for i in range(n):
for j in range(i+1):
- Eij = matrix(ZZ, n, lambda k,l: k==i and l==j)
+ Eij = matrix(field, n, lambda k,l: k==i and l==j)
if i == j:
Sij = Eij
else:
n = ZZ.random_element(cls._max_random_instance_size() + 1)
return cls(n, **kwargs)
- def __init__(self, n, **kwargs):
+ def __init__(self, n, field=AA, **kwargs):
# We know this is a valid EJA, but will double-check
# if the user passes check_axioms=True.
if "check_axioms" not in kwargs: kwargs["check_axioms"] = False
if n <= 1:
associative = True
- super().__init__(self._denormalized_basis(n),
+ super().__init__(self._denormalized_basis(n,field),
self.jordan_product,
self.trace_inner_product,
+ field=field,
associative=associative,
**kwargs)
# because the MatrixEJA is not presently a subclass of the
# FDEJA class that defines rank() and one().
self.rank.set_cache(n)
- idV = matrix.identity(ZZ, self.dimension_over_reals()*n)
+ idV = self.matrix_space().one()
self.one.set_cache(self(idV))
-class ComplexMatrixEJA(MatrixEJA):
+class ComplexMatrixEJA(RealEmbeddedMatrixEJA):
# A manual dictionary-cache for the complex_extension() method,
# since apparently @classmethods can't also be @cached_methods.
_complex_extension = {}
"""
@classmethod
- def _denormalized_basis(cls, n):
+ def _denormalized_basis(cls, n, field):
"""
Returns a basis for the space of complex Hermitian n-by-n matrices.
sage: set_random_seed()
sage: n = ZZ.random_element(1,5)
- sage: B = ComplexHermitianEJA._denormalized_basis(n)
+ sage: B = ComplexHermitianEJA._denormalized_basis(n,ZZ)
sage: all( M.is_symmetric() for M in B)
True
"""
- field = ZZ
- R = PolynomialRing(field, 'z')
+ R = PolynomialRing(ZZ, 'z')
z = R.gen()
- F = field.extension(z**2 + 1, 'I')
+ F = ZZ.extension(z**2 + 1, 'I')
I = F.gen(1)
# This is like the symmetric case, but we need to be careful:
# "erase" E_ij
Eij[i,j] = 0
- # Since we embedded these, we can drop back to the "field" that we
- # started with instead of the complex extension "F".
+ # Since we embedded the entries, we can drop back to the
+ # desired real "field" instead of the extension "F".
return tuple( s.change_ring(field) for s in S )
- def __init__(self, n, **kwargs):
+ def __init__(self, n, field=AA, **kwargs):
# We know this is a valid EJA, but will double-check
# if the user passes check_axioms=True.
if "check_axioms" not in kwargs: kwargs["check_axioms"] = False
if n <= 1:
associative = True
- super().__init__(self._denormalized_basis(n),
+ super().__init__(self._denormalized_basis(n,field),
self.jordan_product,
self.trace_inner_product,
+ field=field,
associative=associative,
**kwargs)
# TODO: this could be factored out somehow, but is left here
n = ZZ.random_element(cls._max_random_instance_size() + 1)
return cls(n, **kwargs)
-class QuaternionMatrixEJA(MatrixEJA):
+class QuaternionMatrixEJA(RealEmbeddedMatrixEJA):
# A manual dictionary-cache for the quaternion_extension() method,
# since apparently @classmethods can't also be @cached_methods.
"""
@classmethod
- def _denormalized_basis(cls, n):
+ def _denormalized_basis(cls, n, field):
"""
Returns a basis for the space of quaternion Hermitian n-by-n matrices.
sage: set_random_seed()
sage: n = ZZ.random_element(1,5)
- sage: B = QuaternionHermitianEJA._denormalized_basis(n)
+ sage: B = QuaternionHermitianEJA._denormalized_basis(n,ZZ)
sage: all( M.is_symmetric() for M in B )
True
"""
- field = ZZ
Q = QuaternionAlgebra(QQ,-1,-1)
I,J,K = Q.gens()
# "erase" E_ij
Eij[i,j] = 0
- # Since we embedded these, we can drop back to the "field" that we
- # started with instead of the quaternion algebra "Q".
+ # Since we embedded the entries, we can drop back to the
+ # desired real "field" instead of the quaternion algebra "Q".
return tuple( s.change_ring(field) for s in S )
- def __init__(self, n, **kwargs):
+ def __init__(self, n, field=AA, **kwargs):
# We know this is a valid EJA, but will double-check
# if the user passes check_axioms=True.
if "check_axioms" not in kwargs: kwargs["check_axioms"] = False
if n <= 1:
associative = True
- super().__init__(self._denormalized_basis(n),
+ super().__init__(self._denormalized_basis(n,field),
self.jordan_product,
self.trace_inner_product,
+ field=field,
associative=associative,
**kwargs)
n = ZZ.random_element(cls._max_random_instance_size() + 1)
return cls(n, **kwargs)
+class OctonionHermitianEJA(FiniteDimensionalEJA, MatrixEJA):
+
+ def __init__(self, n, field=AA, **kwargs):
+ if n > 3:
+ # Otherwise we don't get an EJA.
+ raise ValueError("n cannot exceed 3")
+
+ # We know this is a valid EJA, but will double-check
+ # if the user passes check_axioms=True.
+ if "check_axioms" not in kwargs: kwargs["check_axioms"] = False
+
+ super().__init__(self._denormalized_basis(n,field),
+ self.jordan_product,
+ self.trace_inner_product,
+ **kwargs)
+
+ # TODO: this could be factored out somehow, but is left here
+ # because the MatrixEJA is not presently a subclass of the
+ # FDEJA class that defines rank() and one().
+ self.rank.set_cache(n)
+ idV = self.matrix_space().one()
+ self.one.set_cache(self(idV))
+
+
+ @classmethod
+ def _denormalized_basis(cls, n, field):
+ """
+ Returns a basis for the space of octonion Hermitian n-by-n
+ matrices.
+
+ SETUP::
+
+ sage: from mjo.eja.eja_algebra import OctonionHermitianEJA
+
+ EXAMPLES::
+
+ sage: B = OctonionHermitianEJA._denormalized_basis(3)
+ sage: all( M.is_hermitian() for M in B )
+ True
+ sage: len(B)
+ 27
+
+ """
+ from mjo.octonions import OctonionMatrixAlgebra
+ MS = OctonionMatrixAlgebra(n, scalars=field)
+ es = MS.entry_algebra().gens()
+
+ basis = []
+ for i in range(n):
+ for j in range(i+1):
+ if i == j:
+ E_ii = MS.monomial( (i,j,es[0]) )
+ basis.append(E_ii)
+ else:
+ for e in es:
+ E_ij = MS.monomial( (i,j,e) )
+ ec = e.conjugate()
+ # If the conjugate has a negative sign in front
+ # of it, (j,i,ec) won't be a monomial!
+ if (j,i,ec) in MS.indices():
+ E_ij += MS.monomial( (j,i,ec) )
+ else:
+ E_ij -= MS.monomial( (j,i,-ec) )
+ basis.append(E_ij)
+
+ return tuple( basis )
+
class HadamardEJA(ConcreteEJA):
"""
(r0, r1, r2)
"""
- def __init__(self, n, **kwargs):
+ def __init__(self, n, field=AA, **kwargs):
if n == 0:
jordan_product = lambda x,y: x
inner_product = lambda x,y: x
if "orthonormalize" not in kwargs: kwargs["orthonormalize"] = False
if "check_axioms" not in kwargs: kwargs["check_axioms"] = False
- column_basis = tuple( b.column() for b in FreeModule(ZZ, n).basis() )
+ column_basis = tuple( b.column()
+ for b in FreeModule(field, n).basis() )
super().__init__(column_basis,
jordan_product,
inner_product,
+ field=field,
associative=True,
**kwargs)
self.rank.set_cache(n)
True
"""
- def __init__(self, B, **kwargs):
+ def __init__(self, B, field=AA, **kwargs):
# The matrix "B" is supplied by the user in most cases,
# so it makes sense to check whether or not its positive-
# definite unless we are specifically asked not to...
return P([z0] + zbar.list())
n = B.nrows()
- column_basis = tuple( b.column() for b in FreeModule(ZZ, n).basis() )
+ column_basis = tuple( b.column()
+ for b in FreeModule(field, n).basis() )
# TODO: I haven't actually checked this, but it seems legit.
associative = False
super().__init__(column_basis,
jordan_product,
inner_product,
+ field=field,
associative=associative,
**kwargs)
True
"""
- def __init__(self, n, **kwargs):
+ def __init__(self, n, *args, **kwargs):
# This is a special case of the BilinearFormEJA with the
# identity matrix as its bilinear form.
B = matrix.identity(ZZ, n)
# But also don't pass check_field=False here, because the user
# can pass in a field!
- super().__init__(B, **kwargs)
+ super().__init__(B, *args, **kwargs)
@staticmethod
def _max_random_instance_size():