X-Git-Url: http://gitweb.michael.orlitzky.com/?a=blobdiff_plain;f=mjo%2Feja%2Feuclidean_jordan_algebra.py;h=31717eeeb8e6b9daf37f8992eaa7f52fc1127470;hb=17c489c8fa1855074d6d363fd88b3f165a137953;hp=d459ebe97540abe88f1aef603e29ec994bd1cb74;hpb=aff15cd0c15bd9953531b1d54041f2d90f1d1cff;p=sage.d.git diff --git a/mjo/eja/euclidean_jordan_algebra.py b/mjo/eja/euclidean_jordan_algebra.py index d459ebe..31717ee 100644 --- a/mjo/eja/euclidean_jordan_algebra.py +++ b/mjo/eja/euclidean_jordan_algebra.py @@ -5,203 +5,237 @@ are used in optimization, and have some additional nice methods beyond what can be supported in a general Jordan Algebra. """ -from sage.categories.magmatic_algebras import MagmaticAlgebras +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 -class FiniteDimensionalEuclideanJordanAlgebraMorphism(FiniteDimensionalAlgebraMorphism): - """ - A linear map between two finite-dimensional EJAs. +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 - This is a very thin wrapper around FiniteDimensionalAlgebraMorphism - that does only a few things: + # 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) - 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. + def __call__(self, x): + """ + Allow this operator to be called only on elements of an EJA. - 3. Allows us to add, multiply (compose), and invert morphisms in - the obvious way. + EXAMPLES:: - 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/invert them. - """ + 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 - def __add__(self, other): """ - Add two EJA morphisms in the obvious way. + # 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()) + + + def _repr_(self): + r""" + + A text representation of this linear operator on a Euclidean + Jordan Algebra. 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: 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 + + """ + 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) - 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 __add__(self, other): """ - P = self.parent() - if not other in P: - raise ValueError("summands must live in the same space") + Add the ``other`` EJA operator to this one. - return FiniteDimensionalEuclideanJordanAlgebraMorphism( - P, - self.matrix() + other.matrix() ) + EXAMPLES: + When we add two EJA operators, we get another one back:: - def __init__(self, parent, f): - FiniteDimensionalAlgebraMorphism.__init__(self, - parent, - f.transpose(), - unitary=False, - check=False) + sage: J = RealSymmetricEJA(2) + 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] + [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 + + """ + 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)) def __invert__(self): """ + Invert this EJA operator. + 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:: - - sage: set_random_seed() - sage: J = random_eja() - sage: x = J.random_element() - sage: not x.is_invertible() or ( - ....: (~x.operator()).matrix() == x.operator_matrix().inverse() ) - True + 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 """ - A = self.matrix() - if not A.is_invertible(): - raise ValueError("morphism is not invertible") - - P = self.parent() - return FiniteDimensionalEuclideanJordanAlgebraMorphism(self.parent(), - A.inverse()) + return FiniteDimensionalEuclideanJordanAlgebraOperator( + self._codomain_eja, + self._domain_eja, + VectorSpaceMorphism.__invert__(self)) def __mul__(self, other): """ - Compose two EJA morphisms using multiplicative notation. + 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) - EXAMPLES:: + if not (self._domain_eja == other._codomain_eja): + raise ValueError("operator (co)domains must be compatible") - 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 - [0 0 0 0 0 0] - [0 0 0 0 0 0] - [0 0 0 0 0 0] - [0 0 0 0 0 0] - [0 0 0 0 0 0] - [0 0 0 0 0 0] - - TESTS:: + return FiniteDimensionalEuclideanJordanAlgebraOperator( + other._domain_eja, + self._codomain_eja, + VectorSpaceMorphism.__mul__(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 __neg__(self): """ - if not other.codomain() is self.domain(): - raise ValueError("(co)domains must agree for composition") + Negate this EJA operator. - return FiniteDimensionalEuclideanJordanAlgebraMorphism( - self.parent(), - self.matrix()*other.matrix() ) + EXAMPLES:: + 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 - def _repr_(self): """ - We override only the representation that is shown to the user, - because we want the matrix to be with respect to COLUMN vectors. + return FiniteDimensionalEuclideanJordanAlgebraOperator( + self._domain_eja, + self._codomain_eja, + VectorSpaceMorphism.__neg__(self)) + + + def __pow__(self, n): + """ + Raise this EJA operator to the power ``n``. TESTS: - Ensure that we see the transpose of the underlying matrix object: + Ensure that we get back another EJA operator that can be added, + subtracted, et cetera:: - 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] + sage: J = RealSymmetricEJA(2) + 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 "Morphism from {} to {} given by matrix\n{}".format( - self.domain(), self.codomain(), self.matrix()) + 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) - def matrix(self): + return FiniteDimensionalEuclideanJordanAlgebraOperator( + self._domain_eja, + self._codomain_eja, + mat) + + 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): @@ -222,7 +256,7 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra): raise ValueError("input is not a multiplication table") mult_table = tuple(mult_table) - cat = MagmaticAlgebras(field).FiniteDimensional().WithBasis() + cat = FiniteDimensionalAlgebrasWithBasis(field) cat.or_subcategory(category) if assume_associative: cat = cat.Associative() @@ -301,7 +335,7 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra): determinant). """ z = self._a_regular_element() - V = z.vector().parent().ambient_vector_space() + V = self.vector_space() V1 = V.span_of_basis( (z**k).vector() for k in range(self.rank()) ) b = (V1.basis() + V1.complement().basis()) return V.span_of_basis(b) @@ -526,6 +560,19 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra): else: return self._rank + def vector_space(self): + """ + Return the vector space that underlies this algebra. + + EXAMPLES:: + + sage: J = RealSymmetricEJA(2) + sage: J.vector_space() + Vector space of dimension 3 over Rational Field + + """ + return self.zero().vector().parent().ambient_vector_space() + class Element(FiniteDimensionalAlgebraElement): """ @@ -874,12 +921,12 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra): sage: n = ZZ.random_element(1,10) sage: J = JordanSpinEJA(n) sage: x = J.random_element() - sage: while x.is_zero(): + sage: while not x.is_invertible(): ....: x = J.random_element() sage: x_vec = x.vector() sage: x0 = x_vec[0] sage: x_bar = x_vec[1:] - sage: coeff = 1/(x0^2 - x_bar.inner_product(x_bar)) + sage: coeff = ~(x0^2 - x_bar.inner_product(x_bar)) sage: inv_vec = x_vec.parent()([x0] + (-x_bar).list()) sage: x_inverse = coeff*inv_vec sage: x.inverse() == J(x_inverse) @@ -922,8 +969,7 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra): if not self.is_invertible(): raise ValueError("element is not invertible") - P = self.parent() - return P(self.quadratic_representation().inverse()*self.vector()) + return (~self.quadratic_representation())(self) def is_invertible(self): @@ -1213,8 +1259,8 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra): """ P = self.parent() - return FiniteDimensionalEuclideanJordanAlgebraMorphism( - Hom(P,P), + return FiniteDimensionalEuclideanJordanAlgebraOperator( + P,P, self.operator_matrix() ) @@ -1280,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() @@ -1308,7 +1363,7 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra): sage: D = (x0^2 - x_bar.inner_product(x_bar))*D sage: D = D + 2*x_bar.tensor_product(x_bar) sage: Q = block_matrix(2,2,[A,B,C,D]) - sage: Q == x.quadratic_representation() + sage: Q == x.quadratic_representation().matrix() True Test all of the properties from Theorem 11.2 in Alizadeh:: @@ -1317,8 +1372,8 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra): sage: J = random_eja() sage: x = J.random_element() sage: y = J.random_element() - sage: Lx = x.operator_matrix() - sage: Lxx = (x*x).operator_matrix() + sage: Lx = x.operator() + sage: Lxx = (x*x).operator() sage: Qx = x.quadratic_representation() sage: Qy = y.quadratic_representation() sage: Qxy = x.quadratic_representation(y) @@ -1331,25 +1386,24 @@ 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: - sage: not x.is_invertible() or ( - ....: Qx*x.inverse().vector() == x.vector() ) + sage: not x.is_invertible() or ( Qx(x.inverse()) == x ) True sage: not x.is_invertible() or ( - ....: Qx.inverse() + ....: ~Qx ....: == ....: x.inverse().quadratic_representation() ) True - sage: Qxy*(J.one().vector()) == (x*y).vector() + sage: Qxy(J.one()) == x*y True Property 4: @@ -1362,15 +1416,15 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra): sage: not x.is_invertible() or ( ....: x.quadratic_representation(x.inverse())*Qx ....: == - ....: 2*x.operator_matrix()*Qex - Qx ) + ....: 2*x.operator()*Qex - Qx ) True - sage: 2*x.operator_matrix()*Qex - Qx == Lxx + sage: 2*x.operator()*Qex - Qx == Lxx True Property 5: - sage: J(Qy*x.vector()).quadratic_representation() == Qy*Qx*Qy + sage: Qy(x).quadratic_representation() == Qy*Qx*Qy True Property 6: @@ -1381,13 +1435,13 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra): Property 7: sage: not x.is_invertible() or ( - ....: Qx*x.inverse().operator_matrix() == Lx ) + ....: Qx*x.inverse().operator() == Lx ) True Property 8: sage: not x.operator_commutes_with(y) or ( - ....: J(Qx*y.vector())^n == J(Qxn*(y^n).vector()) ) + ....: Qx(y)^n == Qxn(y^n) ) True """ @@ -1396,9 +1450,9 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra): elif not other in self.parent(): raise TypeError("'other' must live in the same algebra") - L = self.operator_matrix() - M = other.operator_matrix() - return ( L*M + M*L - (self*other).operator_matrix() ) + L = self.operator() + M = other.operator() + return ( L*M + M*L - (self*other).operator() ) def span_of_powers(self): @@ -1412,7 +1466,7 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra): # # We do the extra ambient_vector_space() in case we're messing # with polynomials and the direct parent is a module. - V = self.vector().parent().ambient_vector_space() + V = self.parent().vector_space() return V.span( (self**d).vector() for d in xrange(V.dimension()) )