From 17c489c8fa1855074d6d363fd88b3f165a137953 Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Sat, 27 Jul 2019 11:05:42 -0400 Subject: [PATCH] eja: replace the Hom stuff with a custom EJA operator class. Implementing homsets as linear operators was always a hack, since linear operators will in general not be algebra homomorphisms. We wound up re-implementing addition, subtraction, etc. of operators anyway, so in hindsight, there is no extra difficulty in creating our own EJA operator class as a subclass of VectorSpaceMorphism. This commit throws out the EJA morphism stuff, and replaces it with an equivalent EJA operator class to remain mathematically sound. --- mjo/eja/euclidean_jordan_algebra.py | 513 ++++++++++------------------ 1 file changed, 171 insertions(+), 342 deletions(-) diff --git a/mjo/eja/euclidean_jordan_algebra.py b/mjo/eja/euclidean_jordan_algebra.py index 24ea731..31717ee 100644 --- a/mjo/eja/euclidean_jordan_algebra.py +++ b/mjo/eja/euclidean_jordan_algebra.py @@ -7,406 +7,235 @@ what can be supported in a general Jordan Algebra. from sage.categories.finite_dimensional_algebras_with_basis import FiniteDimensionalAlgebrasWithBasis from sage.categories.morphism import SetMorphism +from sage.modules.vector_space_morphism import VectorSpaceMorphism from sage.structure.element import is_Matrix from sage.structure.category_object import normalize_names from sage.algebras.finite_dimensional_algebras.finite_dimensional_algebra import FiniteDimensionalAlgebra from sage.algebras.finite_dimensional_algebras.finite_dimensional_algebra_element import FiniteDimensionalAlgebraElement -from sage.algebras.finite_dimensional_algebras.finite_dimensional_algebra_morphism import FiniteDimensionalAlgebraMorphism, FiniteDimensionalAlgebraHomset -class FiniteDimensionalEuclideanJordanAlgebraHomset(FiniteDimensionalAlgebraHomset): +class FiniteDimensionalEuclideanJordanAlgebraOperator(VectorSpaceMorphism): + def __init__(self, domain_eja, codomain_eja, mat): + # We save these so that we can output them as part of our + # text representation. Overriding the domain/codomain methods + # doesn't work because the EJAs aren't (directly) vector spaces. + self._domain_eja = domain_eja + self._codomain_eja = codomain_eja - def has_coerce_map_from(self, S): - """ - EXAMPLES:: - - sage: J = RealSymmetricEJA(2) - sage: H = J.Hom(J) - sage: H.has_coerce_map_from(QQ) - True - - """ - try: - # The Homset classes override has_coerce_map_from() with - # something that crashes when it's given e.g. QQ. - if S.is_subring(self.codomain().base_ring()): - return True - except: - pclass = super(FiniteDimensionalEuclideanJordanAlgebraHomset, self) - return pclass.has_coerce_map_from(S) - - - def _coerce_map_from_(self, S): - """ - EXAMPLES:: - - sage: J = RealSymmetricEJA(2) - sage: H = J.Hom(J) - sage: H.coerce(2) - Morphism from Euclidean Jordan algebra of degree 3 over Rational - Field to Euclidean Jordan algebra of degree 3 over Rational Field - given by matrix - [2 0 0] - [0 2 0] - [0 0 2] - - """ - C = self.codomain() - R = C.base_ring() - if S.is_subring(R): - h = S.hom(self.codomain()) - return SetMorphism(Hom(S,C), lambda x: h(x).operator()) + # Otherwise, we just feed everything to the vector space morphism + # constructor. + V = domain_eja.vector_space() + W = codomain_eja.vector_space() + homspace = V.Hom(W) + VectorSpaceMorphism.__init__(self, homspace, mat) def __call__(self, x): """ - EXAMPLES:: - - sage: J = RealSymmetricEJA(2) - sage: H = J.Hom(J) - sage: H(2) - Morphism from Euclidean Jordan algebra of degree 3 over Rational - Field to Euclidean Jordan algebra of degree 3 over Rational Field - given by matrix - [2 0 0] - [0 2 0] - [0 0 2] - - """ - if x in self.base_ring(): - cols = self.domain().dimension() - rows = self.codomain().dimension() - x = x*identity_matrix(self.codomain().base_ring(), rows, cols) - return FiniteDimensionalEuclideanJordanAlgebraMorphism(self, x) - - - def one(self): - """ - Return the identity morphism, but as a member of the right - space (so that we can add it, multiply it, etc.) - """ - cols = self.domain().dimension() - rows = self.codomain().dimension() - mat = identity_matrix(self.base_ring(), rows, cols) - return FiniteDimensionalEuclideanJordanAlgebraMorphism(self, mat) - - - -class FiniteDimensionalEuclideanJordanAlgebraMorphism(FiniteDimensionalAlgebraMorphism): - """ - A linear map between two finite-dimensional EJAs. - - This is a very thin wrapper around FiniteDimensionalAlgebraMorphism - that does only a few things: - - 1. Avoids the ``unitary`` and ``check`` arguments to the constructor - that will always be ``False``. This is necessary because these - are homomorphisms with respect to ADDITION, but the SageMath - machinery wants to check that they're homomorphisms with respect - to (Jordan) MULTIPLICATION. That obviously doesn't work. - - 2. Inputs and outputs the underlying matrix with respect to COLUMN - vectors, unlike the parent class. - - 3. Allows us to add, subtract, negate, multiply (compose), and - invert morphisms in the obvious way. - - If this seems a bit heavyweight, it is. I would have been happy to - use a the ring morphism that underlies the finite-dimensional - algebra morphism, but they don't seem to be callable on elements of - our EJA, and you can't add/multiply/etc. them. - """ - def _add_(self, other): - """ - Add two EJA morphisms in the obvious way. + Allow this operator to be called only on elements of an EJA. EXAMPLES:: - sage: J = RealSymmetricEJA(3) - sage: x = J.zero() - sage: y = J.one() - sage: x.operator() + y.operator() - Morphism from Euclidean Jordan algebra of degree 6 over Rational - Field to Euclidean Jordan algebra of degree 6 over Rational Field - given by matrix - [1 0 0 0 0 0] - [0 1 0 0 0 0] - [0 0 1 0 0 0] - [0 0 0 1 0 0] - [0 0 0 0 1 0] - [0 0 0 0 0 1] - - TESTS:: - - sage: set_random_seed() - sage: J = random_eja() - sage: x = J.random_element() - sage: y = J.random_element() - sage: (x.operator() + y.operator()) in J.Hom(J) + sage: J = JordanSpinEJA(3) + sage: x = J.linear_combination(zip(range(len(J.gens())), J.gens())) + sage: id = identity_matrix(J.base_ring(), J.dimension()) + sage: f = FiniteDimensionalEuclideanJordanAlgebraOperator(J,J,id) + sage: f(x) == x True """ - P = self.parent() - if not other in P: - raise ValueError("summands must live in the same space") + # Overriding the single-underscore _call_ didn't work? + if x not in self._domain_eja: + raise ValueError("argument does not live in the operator's domain") + return self._codomain_eja(self.matrix()*x.vector()) - return FiniteDimensionalEuclideanJordanAlgebraMorphism( - P, - self.matrix() + other.matrix() ) + def _repr_(self): + r""" - def __init__(self, parent, f): - FiniteDimensionalAlgebraMorphism.__init__(self, - parent, - f.transpose(), - unitary=False, - check=False) - + A text representation of this linear operator on a Euclidean + Jordan Algebra. - def __invert__(self): - """ EXAMPLES:: - sage: J = RealSymmetricEJA(2) - sage: x = J.linear_combination(zip(range(len(J.gens())), J.gens())) - sage: x.is_invertible() - True - sage: ~x.operator() - Morphism from Euclidean Jordan algebra of degree 3 over Rational - Field to Euclidean Jordan algebra of degree 3 over Rational Field - given by matrix - [-3/2 2 -1/2] - [ 1 0 0] - [-1/2 0 1/2] - sage: x.operator_matrix().inverse() - [-3/2 2 -1/2] - [ 1 0 0] - [-1/2 0 1/2] - - TESTS: - - Beware, ``x`` being invertible isn't sufficient for its operator - to be invertible below:: - - sage: set_random_seed() - sage: J = random_eja() - sage: x = J.random_element() - sage: id = J.Hom(J).one() - sage: not x.operator_matrix().is_invertible() or ( - ....: ~x.operator()*x.operator() == id ) - True + sage: J = JordanSpinEJA(2) + sage: id = identity_matrix(J.base_ring(), J.dimension()) + sage: FiniteDimensionalEuclideanJordanAlgebraOperator(J,J,id) + Linear operator between finite-dimensional Euclidean Jordan + algebras represented by the matrix: + [1 0] + [0 1] + Domain: Euclidean Jordan algebra of degree 2 over Rational Field + Codomain: Euclidean Jordan algebra of degree 2 over Rational Field """ - A = self.matrix() - if not A.is_invertible(): - raise ValueError("morphism is not invertible") + msg = ("Linear operator between finite-dimensional Euclidean Jordan " + "algebras represented by the matrix:\n", + "{!r}\n", + "Domain: {}\n", + "Codomain: {}") + return ''.join(msg).format(self.matrix(), + self._domain_eja, + self._codomain_eja) - P = self.parent() - return FiniteDimensionalEuclideanJordanAlgebraMorphism(self.parent(), - A.inverse()) - def _lmul_(self, right): + def __add__(self, other): """ - Compose two EJA morphisms using multiplicative notation. + Add the ``other`` EJA operator to this one. - EXAMPLES:: - - sage: J = RealSymmetricEJA(2) - sage: x = J.zero() - sage: y = J.one() - sage: x.operator() * y.operator() - Morphism from Euclidean Jordan algebra of degree 3 over Rational - Field to Euclidean Jordan algebra of degree 3 over Rational Field - given by matrix - [0 0 0] - [0 0 0] - [0 0 0] + EXAMPLES: - :: + When we add two EJA operators, we get another one back:: sage: J = RealSymmetricEJA(2) - sage: x = J.linear_combination(zip(range(len(J.gens())), J.gens())) - sage: x.operator() - Morphism from Euclidean Jordan algebra of degree 3 over Rational - Field to Euclidean Jordan algebra of degree 3 over Rational Field - given by matrix - [ 0 1 0] - [1/2 1 1/2] - [ 0 1 2] - sage: 2*x.operator() - Morphism from Euclidean Jordan algebra of degree 3 over Rational - Field to Euclidean Jordan algebra of degree 3 over Rational Field - given by matrix - [0 2 0] - [1 2 1] - [0 2 4] - sage: x.operator()*2 - Morphism from Euclidean Jordan algebra of degree 3 over Rational - Field to Euclidean Jordan algebra of degree 3 over Rational Field - given by matrix + sage: id = identity_matrix(J.base_ring(), J.dimension()) + sage: f = FiniteDimensionalEuclideanJordanAlgebraOperator(J,J,id) + sage: g = FiniteDimensionalEuclideanJordanAlgebraOperator(J,J,id) + sage: f + g + Linear operator between finite-dimensional Euclidean Jordan + algebras represented by the matrix: + [2 0 0] [0 2 0] - [1 2 1] - [0 2 4] + [0 0 2] + Domain: Euclidean Jordan algebra of degree 3 over Rational Field + Codomain: Euclidean Jordan algebra of degree 3 over Rational Field + + If you try to add two identical vector space operators but on + different EJAs, that should blow up:: + + sage: J1 = RealSymmetricEJA(2) + sage: J2 = JordanSpinEJA(3) + sage: id = identity_matrix(QQ, 3) + sage: f = FiniteDimensionalEuclideanJordanAlgebraOperator(J1,J1,id) + sage: g = FiniteDimensionalEuclideanJordanAlgebraOperator(J2,J2,id) + sage: f + g + Traceback (most recent call last): + ... + ValueError: operator (co)domains must match - TESTS:: + """ + if not (self._domain_eja == other._domain_eja and + self._codomain_eja == other._codomain_eja): + raise ValueError("operator (co)domains must match") + return FiniteDimensionalEuclideanJordanAlgebraOperator( + self._domain_eja, + self._codomain_eja, + VectorSpaceMorphism.__add__(self,other)) - sage: set_random_seed() - sage: J = random_eja() - sage: x = J.random_element() - sage: y = J.random_element() - sage: (x.operator() * y.operator()) in J.Hom(J) - True + def __invert__(self): """ - try: - # I think the morphism classes break the coercion framework - # somewhere along the way, so we have to do this ourselves. - right = self.parent().coerce(right) - except: - pass - - if not right.codomain() is self.domain(): - raise ValueError("(co)domains must agree for composition") + Invert this EJA operator. - return FiniteDimensionalEuclideanJordanAlgebraMorphism( - self.parent(), - self.matrix()*right.matrix() ) + EXAMPLES:: - __mul__ = _lmul_ + sage: J = RealSymmetricEJA(2) + sage: id = identity_matrix(J.base_ring(), J.dimension()) + sage: f = FiniteDimensionalEuclideanJordanAlgebraOperator(J,J,id) + sage: ~f + Linear operator between finite-dimensional Euclidean Jordan + algebras represented by the matrix: + [1 0 0] + [0 1 0] + [0 0 1] + Domain: Euclidean Jordan algebra of degree 3 over Rational Field + Codomain: Euclidean Jordan algebra of degree 3 over Rational Field + """ + return FiniteDimensionalEuclideanJordanAlgebraOperator( + self._codomain_eja, + self._domain_eja, + VectorSpaceMorphism.__invert__(self)) - def __pow__(self, n): + def __mul__(self, other): """ + Compose this EJA operator with the ``other`` one, or scale it by + an element of its base ring. + """ + if other in self._codomain_eja.base_ring(): + return FiniteDimensionalEuclideanJordanAlgebraOperator( + self._domain_eja, + self._codomain_eja, + self._matrix*other) - TESTS:: - - sage: J = JordanSpinEJA(4) - sage: e0,e1,e2,e3 = J.gens() - sage: x = -5/2*e0 + 1/2*e2 + 20*e3 - sage: Qx = x.quadratic_representation() - sage: Qx^0 - Morphism from Euclidean Jordan algebra of degree 4 over Rational - Field to Euclidean Jordan algebra of degree 4 over Rational Field - given by matrix - [1 0 0 0] - [0 1 0 0] - [0 0 1 0] - [0 0 0 1] - sage: (x^0).quadratic_representation() == Qx^0 - True + if not (self._domain_eja == other._codomain_eja): + raise ValueError("operator (co)domains must be compatible") - """ - if n == 0: - # We get back the stupid identity morphism which doesn't - # live in the right space. - return self.parent().one() - elif n == 1: - return self - else: - return FiniteDimensionalAlgebraMorphism.__pow__(self,n) + return FiniteDimensionalEuclideanJordanAlgebraOperator( + other._domain_eja, + self._codomain_eja, + VectorSpaceMorphism.__mul__(self,other)) - def _neg_(self): + def __neg__(self): """ - Negate this morphism. + Negate this EJA operator. EXAMPLES:: sage: J = RealSymmetricEJA(2) - sage: x = J.one() - sage: -x.operator() - Morphism from Euclidean Jordan algebra of degree 3 over Rational - Field to Euclidean Jordan algebra of degree 3 over Rational Field - given by matrix + sage: id = identity_matrix(J.base_ring(), J.dimension()) + sage: f = FiniteDimensionalEuclideanJordanAlgebraOperator(J,J,id) + sage: -f + Linear operator between finite-dimensional Euclidean Jordan + algebras represented by the matrix: [-1 0 0] [ 0 -1 0] [ 0 0 -1] - - TESTS:: - - sage: set_random_seed() - sage: J = random_eja() - sage: x = J.random_element() - sage: -x.operator() in J.Hom(J) - True + Domain: Euclidean Jordan algebra of degree 3 over Rational Field + Codomain: Euclidean Jordan algebra of degree 3 over Rational Field """ - return FiniteDimensionalEuclideanJordanAlgebraMorphism( - self.parent(), - -self.matrix() ) + return FiniteDimensionalEuclideanJordanAlgebraOperator( + self._domain_eja, + self._codomain_eja, + VectorSpaceMorphism.__neg__(self)) - def _repr_(self): + def __pow__(self, n): """ - We override only the representation that is shown to the user, - because we want the matrix to be with respect to COLUMN vectors. + Raise this EJA operator to the power ``n``. TESTS: - Ensure that we see the transpose of the underlying matrix object: - - sage: J = RealSymmetricEJA(3) - sage: x = J.linear_combination(zip(range(len(J.gens())), J.gens())) - sage: L = x.operator() - sage: L - Morphism from Euclidean Jordan algebra of degree 6 over Rational - Field to Euclidean Jordan algebra of degree 6 over Rational Field - given by matrix - [ 0 1 2 0 0 0] - [1/2 3/2 2 1/2 1 0] - [ 1 2 5/2 0 1/2 1] - [ 0 1 0 3 4 0] - [ 0 1 1/2 2 4 2] - [ 0 0 2 0 4 5] - sage: L._matrix - [ 0 1/2 1 0 0 0] - [ 1 3/2 2 1 1 0] - [ 2 2 5/2 0 1/2 2] - [ 0 1/2 0 3 2 0] - [ 0 1 1/2 4 4 4] - [ 0 0 1 0 2 5] - - """ - return "Morphism from {} to {} given by matrix\n{}".format( - self.domain(), self.codomain(), self.matrix()) - - - def __sub__(self, other): - """ - Subtract one morphism from another using addition and negation. - - EXAMPLES:: + Ensure that we get back another EJA operator that can be added, + subtracted, et cetera:: sage: J = RealSymmetricEJA(2) - sage: L1 = J.one().operator() - sage: L1 - L1 - Morphism from Euclidean Jordan algebra of degree 3 over Rational - Field to Euclidean Jordan algebra of degree 3 over Rational - Field given by matrix - [0 0 0] - [0 0 0] - [0 0 0] - - TESTS:: - - sage: set_random_seed() - sage: J = random_eja() - sage: x = J.random_element() - sage: y = J.random_element() - sage: x.operator() - y.operator() in J.Hom(J) - True + sage: id = identity_matrix(J.base_ring(), J.dimension()) + sage: f = FiniteDimensionalEuclideanJordanAlgebraOperator(J,J,id) + sage: f^0 + f^1 + f^2 + Linear operator between finite-dimensional Euclidean Jordan + algebras represented by the matrix: + [3 0 0] + [0 3 0] + [0 0 3] + Domain: Euclidean Jordan algebra of degree 3 over Rational Field + Codomain: Euclidean Jordan algebra of degree 3 over Rational Field """ - return self + (-other) + if (n == 1): + return self + elif (n == 0): + # Raising a vector space morphism to the zero power gives + # you back a special IdentityMorphism that is useless to us. + rows = self.codomain().dimension() + cols = self.domain().dimension() + mat = matrix.identity(self.base_ring(), rows, cols) + else: + mat = VectorSpaceMorphism.__pow__(self,n) + return FiniteDimensionalEuclideanJordanAlgebraOperator( + self._domain_eja, + self._codomain_eja, + mat) - def matrix(self): + def __sub__(self, other): """ - Return the matrix of this morphism with respect to a left-action - on column vectors. + Subtract ``other`` from this EJA operator. """ - return FiniteDimensionalAlgebraMorphism.matrix(self).transpose() + return (self + (-other)) class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra): @@ -445,15 +274,6 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra): natural_basis=natural_basis) - def _Hom_(self, B, cat): - """ - Construct a homset of ``self`` and ``B``. - """ - return FiniteDimensionalEuclideanJordanAlgebraHomset(self, - B, - category=cat) - - def __init__(self, field, mult_table, @@ -1439,8 +1259,8 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra): """ P = self.parent() - return FiniteDimensionalEuclideanJordanAlgebraMorphism( - Hom(P,P), + return FiniteDimensionalEuclideanJordanAlgebraOperator( + P,P, self.operator_matrix() ) @@ -1506,6 +1326,15 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra): sage: bool(lhs == rhs) True + Ensure that our operator's ``matrix`` method agrees with + this implementation:: + + sage: set_random_seed() + sage: J = random_eja() + sage: x = J.random_element() + sage: x.operator().matrix() == x.operator_matrix() + True + """ fda_elt = FiniteDimensionalAlgebraElement(self.parent(), self) return fda_elt.matrix().transpose() @@ -1557,10 +1386,10 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra): sage: 2*Qxy == (x+y).quadratic_representation() - Qx - Qy True - Property 2: + Property 2 (multiply on the right for :trac:`28272`): sage: alpha = QQ.random_element() - sage: (alpha*x).quadratic_representation() == (alpha^2)*Qx + sage: (alpha*x).quadratic_representation() == Qx*(alpha^2) True Property 3: -- 2.44.2