from sage.matrix.constructor import matrix from sage.misc.cachefunc import cached_method from mjo.eja.eja_algebra import EJA from mjo.eja.eja_element import (EJAElement, CartesianProductParentEJAElement) class EJASubalgebraElement(EJAElement): """ SETUP:: sage: from mjo.eja.eja_algebra import random_eja TESTS:: The matrix representation of an element in the subalgebra is the same as its matrix representation in the superalgebra:: sage: x = random_eja(field=QQ,orthonormalize=False).random_element() sage: A = x.subalgebra_generated_by(orthonormalize=False) sage: y = A.random_element() sage: actual = y.to_matrix() sage: expected = y.superalgebra_element().to_matrix() sage: actual == expected True The left-multiplication-by operator for elements in the subalgebra works like it does in the superalgebra, even if we orthonormalize our basis:: sage: x = random_eja(field=AA).random_element() # long time sage: A = x.subalgebra_generated_by(orthonormalize=True) # long time sage: y = A.random_element() # long time sage: y.operator()(A.one()) == y # long time True """ def superalgebra_element(self): """ Return the object in our algebra's superalgebra that corresponds to myself. SETUP:: sage: from mjo.eja.eja_algebra import (RealSymmetricEJA, ....: random_eja) EXAMPLES:: sage: J = RealSymmetricEJA(3) sage: x = sum(J.gens()) sage: x b0 + b1 + b2 + b3 + b4 + b5 sage: A = x.subalgebra_generated_by(orthonormalize=False) sage: A(x) c1 sage: A(x).superalgebra_element() b0 + b1 + b2 + b3 + b4 + b5 sage: y = sum(A.gens()) sage: y c0 + c1 sage: B = y.subalgebra_generated_by(orthonormalize=False) sage: B(y) d1 sage: B(y).superalgebra_element() c0 + c1 TESTS: We can convert back and forth faithfully:: sage: J = random_eja(field=QQ, orthonormalize=False) sage: x = J.random_element() sage: A = x.subalgebra_generated_by(orthonormalize=False) sage: A(x).superalgebra_element() == x True sage: y = A.random_element() sage: A(y.superalgebra_element()) == y True sage: B = y.subalgebra_generated_by(orthonormalize=False) sage: B(y).superalgebra_element() == y True """ return self.parent().superalgebra_embedding()(self) class EJASubalgebra(EJA): """ A subalgebra of an EJA with a given basis. SETUP:: sage: from mjo.eja.eja_algebra import (ComplexHermitianEJA, ....: JordanSpinEJA, ....: RealSymmetricEJA) sage: from mjo.eja.eja_subalgebra import EJASubalgebra EXAMPLES: The following Peirce subalgebras of the 2-by-2 real symmetric matrices do not contain the superalgebra's identity element:: sage: J = RealSymmetricEJA(2) sage: E11 = matrix(AA, [ [1,0], ....: [0,0] ]) sage: E22 = matrix(AA, [ [0,0], ....: [0,1] ]) sage: K1 = EJASubalgebra(J, (J(E11),), associative=True) sage: K1.one().to_matrix() [1 0] [0 0] sage: K2 = EJASubalgebra(J, (J(E22),), associative=True) sage: K2.one().to_matrix() [0 0] [0 1] TESTS: Ensure that our generator names don't conflict with the superalgebra:: sage: J = JordanSpinEJA(3) sage: J.one().subalgebra_generated_by().gens() (c0,) sage: J = JordanSpinEJA(3, prefix='f') sage: J.one().subalgebra_generated_by().gens() (g0,) sage: J = JordanSpinEJA(3, prefix='a') sage: J.one().subalgebra_generated_by().gens() (b0,) Ensure that we can find subalgebras of subalgebras:: sage: A = ComplexHermitianEJA(3).one().subalgebra_generated_by() sage: B = A.one().subalgebra_generated_by() sage: B.dimension() 1 """ def __init__(self, superalgebra, basis, **kwargs): self._superalgebra = superalgebra V = self._superalgebra.vector_space() field = self._superalgebra.base_ring() # A half-assed attempt to ensure that we don't collide with # the superalgebra's prefix (ignoring the fact that there # could be super-superelgrbas in scope). If possible, we # try to "increment" the parent algebra's prefix, although # this idea goes out the window fast because some prefixen # are off-limits. prefixen = ["b","c","d","e","f","g","h","l","m"] try: prefix = prefixen[prefixen.index(self._superalgebra.prefix()) + 1] except ValueError: prefix = prefixen[0] # The superalgebra constructor expects these to be in original matrix # form, not algebra-element form. matrix_basis = tuple( b.to_matrix() for b in basis ) def jordan_product(x,y): return (self._superalgebra(x)*self._superalgebra(y)).to_matrix() def inner_product(x,y): return self._superalgebra(x).inner_product(self._superalgebra(y)) super().__init__(matrix_basis, jordan_product, inner_product, field=field, matrix_space=superalgebra.matrix_space(), prefix=prefix, **kwargs) def _element_constructor_(self, elt): """ Construct an element of this subalgebra from the given one. The only valid arguments are elements of the parent algebra that happen to live in this subalgebra. SETUP:: sage: from mjo.eja.eja_algebra import RealSymmetricEJA sage: from mjo.eja.eja_subalgebra import EJASubalgebra EXAMPLES:: sage: J = RealSymmetricEJA(3) sage: X = matrix(AA, [ [0,0,1], ....: [0,1,0], ....: [1,0,0] ]) sage: x = J(X) sage: basis = ( x, x^2 ) # x^2 is the identity matrix sage: K = EJASubalgebra(J, ....: basis, ....: associative=True, ....: orthonormalize=False) sage: K(J.one()) c1 sage: K(J.one() + x) c0 + c1 :: """ if elt in self.superalgebra(): # If the subalgebra is trivial, its _matrix_span will be empty # but we still want to be able convert the superalgebra's zero() # element into the subalgebra's zero() element. There's no great # workaround for this because sage checks that your basis is # linearly-independent everywhere, so we can't just give it a # basis consisting of the zero element. m = elt.to_matrix() if self.is_trivial() and m.is_zero(): return self.zero() else: return super()._element_constructor_(m) else: return super()._element_constructor_(elt) def superalgebra(self): """ Return the superalgebra that this algebra was generated from. """ return self._superalgebra @cached_method def superalgebra_embedding(self): r""" Return the embedding from this subalgebra into the superalgebra. SETUP:: sage: from mjo.eja.eja_algebra import HadamardEJA EXAMPLES:: sage: J = HadamardEJA(4) sage: A = J.one().subalgebra_generated_by() sage: iota = A.superalgebra_embedding() sage: iota Linear operator between finite-dimensional Euclidean Jordan algebras represented by the matrix: [1/2] [1/2] [1/2] [1/2] Domain: Euclidean Jordan algebra of dimension 1 over Algebraic Real Field Codomain: Euclidean Jordan algebra of dimension 4 over Algebraic Real Field sage: iota(A.one()) == J.one() True """ from mjo.eja.eja_operator import EJAOperator mm = self._module_morphism(lambda j: self.superalgebra()(self.monomial(j).to_matrix()), codomain=self.superalgebra()) return EJAOperator(self, self.superalgebra(), mm.matrix()) Element = EJASubalgebraElement class CartesianProductEJASubalgebraElement(EJASubalgebraElement, CartesianProductParentEJAElement): r""" The class for elements that both belong to a subalgebra and have a Cartesian product algebra as their parent. By inheriting :class:`CartesianProductParentEJAElement` in addition to :class:`EJASubalgebraElement`, we allow the ``to_matrix()`` method to be overridden with the version that works on Cartesian products. SETUP:: sage: from mjo.eja.eja_algebra import (HadamardEJA, ....: RealSymmetricEJA) TESTS: This used to fail when ``subalgebra_idempotent()`` tried to embed the subalgebra element back into the original EJA:: sage: J1 = HadamardEJA(0, field=QQ, orthonormalize=False) sage: J2 = RealSymmetricEJA(2, field=QQ, orthonormalize=False) sage: J = cartesian_product([J1,J2]) sage: J.one().subalgebra_idempotent() == J.one() True """ pass class CartesianProductEJASubalgebra(EJASubalgebra): r""" Subalgebras whose parents are Cartesian products. Exists only to specify a special element class that will (in addition) inherit from ``CartesianProductParentEJAElement``. """ Element = CartesianProductEJASubalgebraElement