from sage.algebras.quatalg.quaternion_algebra import QuaternionAlgebra
from sage.categories.magmatic_algebras import MagmaticAlgebras
from sage.categories.sets_cat import cartesian_product
-from sage.combinat.free_module import (CombinatorialFreeModule,
- CombinatorialFreeModule_CartesianProduct)
+from sage.combinat.free_module import CombinatorialFreeModule
from sage.matrix.constructor import matrix
from sage.matrix.matrix_space import MatrixSpace
from sage.misc.cachefunc import cached_method
generally rules out using the rationals as your ``field``, but
is required for spectral decompositions.
+ SETUP::
+
+ sage: from mjo.eja.eja_algebra import random_eja
+
+ TESTS:
+
+ We should compute that an element subalgebra is associative even
+ if we circumvent the element method::
+
+ sage: set_random_seed()
+ sage: J = random_eja(field=QQ,orthonormalize=False)
+ sage: x = J.random_element()
+ sage: A = x.subalgebra_generated_by(orthonormalize=False)
+ sage: basis = tuple(b.superalgebra_element() for b in A.basis())
+ sage: J.subalgebra(basis, orthonormalize=False).is_associative()
+ True
+
"""
Element = FiniteDimensionalEJAElement
inner_product,
field=AA,
orthonormalize=True,
- associative=False,
+ associative=None,
cartesian_product=False,
check_field=True,
check_axioms=True,
- prefix='e'):
-
- # Keep track of whether or not the matrix basis consists of
- # tuples, since we need special cases for them damned near
- # everywhere. This is INDEPENDENT of whether or not the
- # algebra is a cartesian product, since a subalgebra of a
- # cartesian product will have a basis of tuples, but will not
- # in general itself be a cartesian product algebra.
- self._matrix_basis_is_cartesian = False
+ prefix="b"):
+
n = len(basis)
- if n > 0:
- if hasattr(basis[0], 'cartesian_factors'):
- self._matrix_basis_is_cartesian = True
if check_field:
if not field.is_subring(RR):
# 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.
- if not cartesian_product:
- # The field for a cartesian product algebra comes from one
- # of its factors and is the same for all factors, so
- # there's no need to "reapply" it on product algebras.
- if self._matrix_basis_is_cartesian:
- # OK since if n == 0, the basis does not consist of tuples.
- P = basis[0].parent()
- basis = tuple( P(tuple(b_i.change_ring(field) for b_i in b))
- for b in basis )
- else:
- basis = tuple( b.change_ring(field) for b in basis )
-
+ basis = tuple( _change_ring(b, field) for b in basis )
if check_axioms:
# Check commutativity of the Jordan and inner-products.
category = MagmaticAlgebras(field).FiniteDimensional()
category = category.WithBasis().Unital().Commutative()
+ if associative is None:
+ # We should figure it out. As with check_axioms, we have to do
+ # this without the help of the _jordan_product_is_associative()
+ # method because we need to know the category before we
+ # initialize the algebra.
+ associative = all( jordan_product(jordan_product(bi,bj),bk)
+ ==
+ jordan_product(bi,jordan_product(bj,bk))
+ for bi in basis
+ for bj in basis
+ for bk in basis)
+
if associative:
# Element subalgebras can take advantage of this.
category = category.Associative()
if cartesian_product:
- category = category.CartesianProducts()
+ # Use join() here because otherwise we only get the
+ # "Cartesian product of..." and not the things themselves.
+ category = category.join([category,
+ category.CartesianProducts()])
# Call the superclass constructor so that we can use its from_vector()
# method to build our multiplication table.
# ambient vector space V that our (vectorized) basis lives in,
# as well as a subspace W of V spanned by those (vectorized)
# basis elements. The W-coordinates are the coefficients that
- # we see in things like x = 1*e1 + 2*e2.
+ # we see in things like x = 1*b1 + 2*b2.
vector_basis = basis
degree = 0
sage: set_random_seed()
sage: J = random_eja()
sage: n = J.dimension()
- sage: ei = J.zero()
- sage: ej = J.zero()
- sage: ei_ej = J.zero()*J.zero()
+ sage: bi = J.zero()
+ sage: bj = J.zero()
+ sage: bi_bj = J.zero()*J.zero()
sage: if n > 0:
....: i = ZZ.random_element(n)
....: j = ZZ.random_element(n)
- ....: ei = J.gens()[i]
- ....: ej = J.gens()[j]
- ....: ei_ej = J.product_on_basis(i,j)
- sage: ei*ej == ei_ej
+ ....: bi = J.monomial(i)
+ ....: bj = J.monomial(j)
+ ....: bi_bj = J.product_on_basis(i,j)
+ sage: bi*bj == bi_bj
True
"""
this algebra was constructed with ``check_axioms=False`` and
passed an invalid multiplication table.
"""
- return all( self.product_on_basis(i,j) == self.product_on_basis(i,j)
- for i in range(self.dimension())
- for j in range(self.dimension()) )
+ return all( x*y == y*x for x in self.gens() for y in self.gens() )
def _is_jordanian(self):
r"""
return ``True``, unless this algebra was constructed with
``check_axioms=False`` and passed an invalid multiplication table.
"""
- return all( (self.gens()[i]**2)*(self.gens()[i]*self.gens()[j])
+ return all( (self.monomial(i)**2)*(self.monomial(i)*self.monomial(j))
==
- (self.gens()[i])*((self.gens()[i]**2)*self.gens()[j])
+ (self.monomial(i))*((self.monomial(i)**2)*self.monomial(j))
for i in range(self.dimension())
for j in range(self.dimension()) )
SETUP::
- sage: from mjo.eja.eja_algebra import (RealSymmetricEJA,
+ sage: from mjo.eja.eja_algebra import (random_eja,
+ ....: RealSymmetricEJA,
....: ComplexHermitianEJA,
....: QuaternionHermitianEJA)
sage: A._jordan_product_is_associative()
True
+ TESTS:
+
+ The values we've presupplied to the constructors agree with
+ the computation::
+
+ sage: set_random_seed()
+ sage: J = random_eja()
+ sage: J.is_associative() == J._jordan_product_is_associative()
+ True
+
"""
R = self.base_ring()
for i in range(self.dimension()):
for j in range(self.dimension()):
for k in range(self.dimension()):
- x = self.gens()[i]
- y = self.gens()[j]
- z = self.gens()[k]
+ x = self.monomial(i)
+ y = self.monomial(j)
+ z = self.monomial(k)
diff = (x*y)*z - x*(y*z)
if diff.norm() > epsilon:
for i in range(self.dimension()):
for j in range(self.dimension()):
for k in range(self.dimension()):
- x = self.gens()[i]
- y = self.gens()[j]
- z = self.gens()[k]
+ x = self.monomial(i)
+ y = self.monomial(j)
+ z = self.monomial(k)
diff = (x*y).inner_product(z) - x.inner_product(y*z)
if diff.abs() > epsilon:
SETUP::
- sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
+ sage: from mjo.eja.eja_algebra import (random_eja,
+ ....: JordanSpinEJA,
....: HadamardEJA,
....: RealSymmetricEJA)
sage: J2 = RealSymmetricEJA(2)
sage: J = cartesian_product([J1,J2])
sage: J( (J1.matrix_basis()[1], J2.matrix_basis()[2]) )
- e(0, 1) + e(1, 2)
+ b1 + b5
TESTS:
sage: J = JordanSpinEJA(4)
sage: J.multiplication_table()
+----++----+----+----+----+
- | * || e0 | e1 | e2 | e3 |
+ | * || b0 | b1 | b2 | b3 |
+====++====+====+====+====+
- | e0 || e0 | e1 | e2 | e3 |
+ | b0 || b0 | b1 | b2 | b3 |
+----++----+----+----+----+
- | e1 || e1 | e0 | 0 | 0 |
+ | b1 || b1 | b0 | 0 | 0 |
+----++----+----+----+----+
- | e2 || e2 | 0 | e0 | 0 |
+ | b2 || b2 | 0 | b0 | 0 |
+----++----+----+----+----+
- | e3 || e3 | 0 | 0 | e0 |
+ | b3 || b3 | 0 | 0 | b0 |
+----++----+----+----+----+
"""
# And to each subsequent row, prepend an entry that belongs to
# the left-side "header column."
- M += [ [self.gens()[i]] + [ self.product_on_basis(i,j)
+ M += [ [self.monomial(i)] + [ self.monomial(i)*self.monomial(j)
for j in range(n) ]
for i in range(n) ]
sage: J = RealSymmetricEJA(2)
sage: J.basis()
- Finite family {0: e0, 1: e1, 2: e2}
+ Finite family {0: b0, 1: b1, 2: b2}
sage: J.matrix_basis()
(
[1 0] [ 0 0.7071067811865475?] [0 0]
sage: J = JordanSpinEJA(2)
sage: J.basis()
- Finite family {0: e0, 1: e1}
+ Finite family {0: b0, 1: b1}
sage: J.matrix_basis()
(
[1] [0]
sage: J = HadamardEJA(5)
sage: J.one()
- e0 + e1 + e2 + e3 + e4
+ b0 + b1 + b2 + b3 + b4
The unit element in the Hadamard EJA is inherited in the
subalgebras generated by its elements::
sage: J = HadamardEJA(5)
sage: J.one()
- e0 + e1 + e2 + e3 + e4
+ b0 + b1 + b2 + b3 + b4
sage: x = sum(J.gens())
sage: A = x.subalgebra_generated_by(orthonormalize=False)
sage: A.one()
- f0
+ c0
sage: A.one().superalgebra_element()
- e0 + e1 + e2 + e3 + e4
+ b0 + b1 + b2 + b3 + b4
TESTS:
def L_x_i_j(i,j):
# From a result in my book, these are the entries of the
# basis representation of L_x.
- return sum( vars[k]*self.gens()[k].operator().matrix()[i,j]
+ return sum( vars[k]*self.monomial(k).operator().matrix()[i,j]
for k in range(n) )
L_x = matrix(F, n, n, L_x_i_j)
if not all( all(b_i in QQ for b_i in b.list()) for b in basis ):
raise TypeError("basis not rational")
+ super().__init__(basis,
+ jordan_product,
+ inner_product,
+ field=field,
+ check_field=check_field,
+ **kwargs)
+
self._rational_algebra = None
if field is not QQ:
# There's no point in constructing the extra algebra if this
jordan_product,
inner_product,
field=QQ,
+ associative=self.is_associative(),
orthonormalize=False,
check_field=False,
check_axioms=False)
- super().__init__(basis,
- jordan_product,
- inner_product,
- field=field,
- check_field=check_field,
- **kwargs)
-
@cached_method
def _charpoly_coefficients(self):
r"""
True
"""
- Xu = cls.real_unembed(X)
- Yu = cls.real_unembed(Y)
- tr = (Xu*Yu).trace()
-
- try:
- # Works in QQ, AA, RDF, et cetera.
- return tr.real()
- except AttributeError:
- # A quaternion doesn't have a real() method, but does
- # have coefficient_tuple() method that returns the
- # coefficients of 1, i, j, and k -- in that order.
- return tr.coefficient_tuple()[0]
+ # This does in fact compute the real part of the trace.
+ # If we compute the trace of e.g. a complex matrix M,
+ # then we do so by adding up its diagonal entries --
+ # call them z_1 through z_n. The real embedding of z_1
+ # will be a 2-by-2 REAL matrix [a, b; -b, a] whose trace
+ # 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):
EXAMPLES::
sage: J = RealSymmetricEJA(2)
- sage: e0, e1, e2 = J.gens()
- sage: e0*e0
- e0
- sage: e1*e1
- 1/2*e0 + 1/2*e2
- sage: e2*e2
- e2
+ sage: b0, b1, b2 = J.gens()
+ sage: b0*b0
+ b0
+ sage: b1*b1
+ 1/2*b0 + 1/2*b2
+ sage: b2*b2
+ b2
In theory, our "field" can be any subfield of the reals::
# if the user passes check_axioms=True.
if "check_axioms" not in kwargs: kwargs["check_axioms"] = False
- super(RealSymmetricEJA, self).__init__(self._denormalized_basis(n),
- self.jordan_product,
- self.trace_inner_product,
- **kwargs)
+ associative = False
+ if n <= 1:
+ associative = True
+
+ super().__init__(self._denormalized_basis(n),
+ self.jordan_product,
+ self.trace_inner_product,
+ associative=associative,
+ **kwargs)
# TODO: this could be factored out somehow, but is left here
# because the MatrixEJA is not presently a subclass of the
True
"""
- super(ComplexMatrixEJA,cls).real_embed(M)
+ super().real_embed(M)
n = M.nrows()
# We don't need any adjoined elements...
True
"""
- super(ComplexMatrixEJA,cls).real_unembed(M)
+ super().real_unembed(M)
n = ZZ(M.nrows())
d = cls.dimension_over_reals()
F = cls.complex_extension(M.base_ring())
# if the user passes check_axioms=True.
if "check_axioms" not in kwargs: kwargs["check_axioms"] = False
- super(ComplexHermitianEJA, self).__init__(self._denormalized_basis(n),
- self.jordan_product,
- self.trace_inner_product,
- **kwargs)
+ associative = False
+ if n <= 1:
+ associative = True
+
+ super().__init__(self._denormalized_basis(n),
+ self.jordan_product,
+ self.trace_inner_product,
+ associative=associative,
+ **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().
True
"""
- super(QuaternionMatrixEJA,cls).real_embed(M)
+ super().real_embed(M)
quaternions = M.base_ring()
n = M.nrows()
True
"""
- super(QuaternionMatrixEJA,cls).real_unembed(M)
+ super().real_unembed(M)
n = ZZ(M.nrows())
d = cls.dimension_over_reals()
# if the user passes check_axioms=True.
if "check_axioms" not in kwargs: kwargs["check_axioms"] = False
- super(QuaternionHermitianEJA, self).__init__(self._denormalized_basis(n),
- self.jordan_product,
- self.trace_inner_product,
- **kwargs)
+ associative = False
+ if n <= 1:
+ associative = True
+
+ super().__init__(self._denormalized_basis(n),
+ self.jordan_product,
+ self.trace_inner_product,
+ associative=associative,
+ **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().
This multiplication table can be verified by hand::
sage: J = HadamardEJA(3)
- sage: e0,e1,e2 = J.gens()
- sage: e0*e0
- e0
- sage: e0*e1
+ sage: b0,b1,b2 = J.gens()
+ sage: b0*b0
+ b0
+ sage: b0*b1
0
- sage: e0*e2
+ sage: b0*b2
0
- sage: e1*e1
- e1
- sage: e1*e2
+ sage: b1*b1
+ b1
+ sage: b1*b2
0
- sage: e2*e2
- e2
+ sage: b2*b2
+ b2
TESTS:
n = B.nrows()
column_basis = tuple( b.column() for b in FreeModule(ZZ, n).basis() )
- super(BilinearFormEJA, self).__init__(column_basis,
- jordan_product,
- inner_product,
- **kwargs)
+
+ # TODO: I haven't actually checked this, but it seems legit.
+ associative = False
+ if n <= 2:
+ associative = True
+
+ super().__init__(column_basis,
+ jordan_product,
+ inner_product,
+ associative=associative,
+ **kwargs)
# The rank of this algebra is two, unless we're in a
# one-dimensional ambient space (because the rank is bounded
This multiplication table can be verified by hand::
sage: J = JordanSpinEJA(4)
- sage: e0,e1,e2,e3 = J.gens()
- sage: e0*e0
- e0
- sage: e0*e1
- e1
- sage: e0*e2
- e2
- sage: e0*e3
- e3
- sage: e1*e2
+ sage: b0,b1,b2,b3 = J.gens()
+ sage: b0*b0
+ b0
+ sage: b0*b1
+ b1
+ sage: b0*b2
+ b2
+ sage: b0*b3
+ b3
+ sage: b1*b2
0
- sage: e1*e3
+ sage: b1*b3
0
- sage: e2*e3
+ sage: b2*b3
0
We can change the generator prefix::
# But also don't pass check_field=False here, because the user
# can pass in a field!
- super(JordanSpinEJA, self).__init__(B, **kwargs)
+ super().__init__(B, **kwargs)
@staticmethod
def _max_random_instance_size():
if "orthonormalize" not in kwargs: kwargs["orthonormalize"] = False
if "check_axioms" not in kwargs: kwargs["check_axioms"] = False
- super(TrivialEJA, self).__init__(basis,
- jordan_product,
- inner_product,
- **kwargs)
+ super().__init__(basis,
+ jordan_product,
+ inner_product,
+ associative=True,
+ **kwargs)
+
# The rank is zero using my definition, namely the dimension of the
# largest subalgebra generated by any element.
self.rank.set_cache(0)
return cls(**kwargs)
-class CartesianProductEJA(CombinatorialFreeModule_CartesianProduct,
- FiniteDimensionalEJA):
+class CartesianProductEJA(FiniteDimensionalEJA):
r"""
The external (orthogonal) direct sum of two or more Euclidean
Jordan algebras. Every Euclidean Jordan algebra decomposes into an
sage: CP2.is_associative()
False
+ Cartesian products of Cartesian products work::
+
+ sage: J1 = JordanSpinEJA(1)
+ sage: J2 = JordanSpinEJA(1)
+ sage: J3 = JordanSpinEJA(1)
+ sage: J = cartesian_product([J1,cartesian_product([J2,J3])])
+ sage: J.multiplication_table()
+ +----++----+----+----+
+ | * || b0 | b1 | b2 |
+ +====++====+====+====+
+ | b0 || b0 | 0 | 0 |
+ +----++----+----+----+
+ | b1 || 0 | b1 | 0 |
+ +----++----+----+----+
+ | b2 || 0 | 0 | b2 |
+ +----++----+----+----+
+ sage: HadamardEJA(3).multiplication_table()
+ +----++----+----+----+
+ | * || b0 | b1 | b2 |
+ +====++====+====+====+
+ | b0 || b0 | 0 | 0 |
+ +----++----+----+----+
+ | b1 || 0 | b1 | 0 |
+ +----++----+----+----+
+ | b2 || 0 | 0 | b2 |
+ +----++----+----+----+
+
TESTS:
All factors must share the same base field::
Element = FiniteDimensionalEJAElement
- def __init__(self, algebras, **kwargs):
- CombinatorialFreeModule_CartesianProduct.__init__(self,
- algebras,
- **kwargs)
- field = algebras[0].base_ring()
- if not all( J.base_ring() == field for J in algebras ):
+ def __init__(self, factors, **kwargs):
+ m = len(factors)
+ if m == 0:
+ return TrivialEJA()
+
+ self._sets = factors
+
+ field = factors[0].base_ring()
+ if not all( J.base_ring() == field for J in factors ):
raise ValueError("all factors must share the same base field")
- associative = all( m.is_associative() for m in algebras )
+ associative = all( f.is_associative() for f in factors )
- # The definition of matrix_space() and self.basis() relies
- # only on the stuff in the CFM_CartesianProduct class, which
- # we've already initialized.
- Js = self.cartesian_factors()
- m = len(Js)
MS = self.matrix_space()
- basis = tuple(
- MS(tuple( self.cartesian_projection(i)(b).to_matrix()
- for i in range(m) ))
- for b in self.basis()
- )
+ basis = []
+ zero = MS.zero()
+ for i in range(m):
+ for b in factors[i].matrix_basis():
+ z = list(zero)
+ z[i] = b
+ basis.append(z)
+
+ basis = tuple( MS(b) for b in basis )
# Define jordan/inner products that operate on that matrix_basis.
def jordan_product(x,y):
return MS(tuple(
- (Js[i](x[i])*Js[i](y[i])).to_matrix() for i in range(m)
+ (factors[i](x[i])*factors[i](y[i])).to_matrix()
+ for i in range(m)
))
def inner_product(x, y):
return sum(
- Js[i](x[i]).inner_product(Js[i](y[i])) for i in range(m)
+ factors[i](x[i]).inner_product(factors[i](y[i]))
+ for i in range(m)
)
# There's no need to check the field since it already came
check_field=False,
check_axioms=False)
- ones = tuple(J.one() for J in algebras)
- self.one.set_cache(self._cartesian_product_of_elements(ones))
- self.rank.set_cache(sum(J.rank() for J in algebras))
+ ones = tuple(J.one().to_matrix() for J in factors)
+ self.one.set_cache(self(ones))
+ self.rank.set_cache(sum(J.rank() for J in factors))
+
+ def cartesian_factors(self):
+ # Copy/pasted from CombinatorialFreeModule_CartesianProduct.
+ return self._sets
+
+ def cartesian_factor(self, i):
+ r"""
+ Return the ``i``th factor of this algebra.
+ """
+ return self._sets[i]
+
+ def _repr_(self):
+ # Copy/pasted from CombinatorialFreeModule_CartesianProduct.
+ from sage.categories.cartesian_product import cartesian_product
+ return cartesian_product.symbol.join("%s" % factor
+ for factor in self._sets)
def matrix_space(self):
r"""
True
"""
- Ji = self.cartesian_factors()[i]
- # Requires the fix on Trac 31421/31422 to work!
- Pi = super().cartesian_projection(i)
+ offset = sum( self.cartesian_factor(k).dimension()
+ for k in range(i) )
+ Ji = self.cartesian_factor(i)
+ Pi = self._module_morphism(lambda j: Ji.monomial(j - offset),
+ codomain=Ji)
+
return FiniteDimensionalEJAOperator(self,Ji,Pi.matrix())
@cached_method
True
"""
- Ji = self.cartesian_factors()[i]
- # Requires the fix on Trac 31421/31422 to work!
- Ei = super().cartesian_embedding(i)
+ offset = sum( self.cartesian_factor(k).dimension()
+ for k in range(i) )
+ Ji = self.cartesian_factor(i)
+ Ei = Ji._module_morphism(lambda j: self.monomial(j + offset),
+ codomain=self)
return FiniteDimensionalEJAOperator(Ji,self,Ei.matrix())
RationalBasisEJA.CartesianProduct = RationalBasisCartesianProductEJA
-random_eja = ConcreteEJA.random_instance
+def random_eja(*args, **kwargs):
+ J1 = ConcreteEJA.random_instance(*args, **kwargs)
+
+ # This might make Cartesian products appear roughly as often as
+ # any other ConcreteEJA.
+ if ZZ.random_element(len(ConcreteEJA.__subclasses__()) + 1) == 0:
+ # Use random_eja() again so we can get more than two factors.
+ J2 = random_eja(*args, **kwargs)
+ J = cartesian_product([J1,J2])
+ return J
+ else:
+ return J1