X-Git-Url: http://gitweb.michael.orlitzky.com/?a=blobdiff_plain;f=mjo%2Feja%2Feuclidean_jordan_algebra.py;h=31717eeeb8e6b9daf37f8992eaa7f52fc1127470;hb=17c489c8fa1855074d6d363fd88b3f165a137953;hp=2fa4800b09f4f26ca784532e154cde1665ed7b84;hpb=87644b67c69d96150793929470507522a8fa2b0b;p=sage.d.git diff --git a/mjo/eja/euclidean_jordan_algebra.py b/mjo/eja/euclidean_jordan_algebra.py index 2fa4800..31717ee 100644 --- a/mjo/eja/euclidean_jordan_algebra.py +++ b/mjo/eja/euclidean_jordan_algebra.py @@ -5,13 +5,239 @@ 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 + +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 + + # 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): + """ + Allow this operator to be called only on elements of an EJA. + + EXAMPLES:: + + 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 + + """ + # 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 = 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) + + + def __add__(self, other): + """ + Add the ``other`` EJA operator to this one. + + EXAMPLES: + + When we add two EJA operators, we get another one back:: + + 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: 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 __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) + + if not (self._domain_eja == other._codomain_eja): + raise ValueError("operator (co)domains must be compatible") + + return FiniteDimensionalEuclideanJordanAlgebraOperator( + other._domain_eja, + self._codomain_eja, + VectorSpaceMorphism.__mul__(self,other)) + + + def __neg__(self): + """ + Negate this EJA operator. + + 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 + + """ + 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 get back another EJA operator that can be added, + subtracted, et cetera:: + + 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 + + """ + 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 __sub__(self, other): + """ + Subtract ``other`` from this EJA operator. + """ + return (self + (-other)) + + class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra): @staticmethod def __classcall_private__(cls, @@ -30,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() @@ -109,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) @@ -334,12 +560,35 @@ 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): """ An element of a Euclidean Jordan algebra. """ + def __dir__(self): + """ + Oh man, I should not be doing this. This hides the "disabled" + methods ``left_matrix`` and ``matrix`` from introspection; + in particular it removes them from tab-completion. + """ + return filter(lambda s: s not in ['left_matrix', 'matrix'], + dir(self.__class__) ) + + def __init__(self, A, elt=None): """ EXAMPLES: @@ -658,8 +907,10 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra): """ Return the Jordan-multiplicative inverse of this element. - We can't use the superclass method because it relies on the - algebra being associative. + ALGORITHM: + + We appeal to the quadratic representation as in Koecher's + Theorem 12 in Chapter III, Section 5. EXAMPLES: @@ -670,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) @@ -698,35 +949,27 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra): sage: (not x.is_invertible()) or (x.inverse()*x == J.one()) True - """ - if self.parent().is_associative(): - elt = FiniteDimensionalAlgebraElement(self.parent(), self) - return elt.inverse() + The inverse of the inverse is what we started with:: - # TODO: we can do better once the call to is_invertible() - # doesn't crash on irregular elements. - #if not self.is_invertible(): - # raise ValueError('element is not invertible') + sage: set_random_seed() + sage: J = random_eja() + sage: x = J.random_element() + sage: (not x.is_invertible()) or (x.inverse().inverse() == x) + True - # We do this a little different than the usual recursive - # call to a finite-dimensional algebra element, because we - # wind up with an inverse that lives in the subalgebra and - # we need information about the parent to convert it back. - V = self.span_of_powers() - assoc_subalg = self.subalgebra_generated_by() - # Mis-design warning: the basis used for span_of_powers() - # and subalgebra_generated_by() must be the same, and in - # the same order! - elt = assoc_subalg(V.coordinates(self.vector())) + The zero element is never invertible:: - # This will be in the subalgebra's coordinates... - fda_elt = FiniteDimensionalAlgebraElement(assoc_subalg, elt) - subalg_inverse = fda_elt.inverse() + sage: set_random_seed() + sage: J = random_eja().zero().inverse() + Traceback (most recent call last): + ... + ValueError: element is not invertible - # So we have to convert back... - basis = [ self.parent(v) for v in V.basis() ] - pairs = zip(subalg_inverse.vector(), basis) - return self.parent().linear_combination(pairs) + """ + if not self.is_invertible(): + raise ValueError("element is not invertible") + + return (~self.quadratic_representation())(self) def is_invertible(self): @@ -867,6 +1110,16 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra): return self.span_of_powers().dimension() + def left_matrix(self): + """ + Our parent class defines ``left_matrix`` and ``matrix`` + methods whose names are misleading. We don't want them. + """ + raise NotImplementedError("use operator_matrix() instead") + + matrix = left_matrix + + def minimal_polynomial(self): """ Return the minimal polynomial of this element, @@ -988,14 +1241,37 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra): return W.linear_combination(zip(self.vector(), B)) + def operator(self): + """ + Return the left-multiplication-by-this-element + operator on the ambient algebra. + + TESTS:: + + sage: set_random_seed() + sage: J = random_eja() + sage: x = J.random_element() + sage: y = J.random_element() + sage: x.operator()(y) == x*y + True + sage: y.operator()(x) == x*y + True + + """ + P = self.parent() + return FiniteDimensionalEuclideanJordanAlgebraOperator( + P,P, + self.operator_matrix() ) + + + def operator_matrix(self): """ Return the matrix that represents left- (or right-) multiplication by this element in the parent algebra. - We have to override this because the superclass method - returns a matrix that acts on row vectors (that is, on - the right). + We implement this ourselves to work around the fact that + our parent class represents everything with row vectors. EXAMPLES: @@ -1050,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() @@ -1078,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:: @@ -1087,38 +1372,76 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra): sage: J = random_eja() sage: x = J.random_element() sage: y = J.random_element() + 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) + sage: Qex = J.one().quadratic_representation(x) + sage: n = ZZ.random_element(10) + sage: Qxn = (x^n).quadratic_representation() Property 1: - sage: actual = x.quadratic_representation(y) - sage: expected = ( (x+y).quadratic_representation() - ....: -x.quadratic_representation() - ....: -y.quadratic_representation() ) / 2 - sage: actual == expected + 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: actual = (alpha*x).quadratic_representation() - sage: expected = (alpha^2)*x.quadratic_representation() - sage: actual == expected + sage: (alpha*x).quadratic_representation() == Qx*(alpha^2) + True + + Property 3: + + sage: not x.is_invertible() or ( Qx(x.inverse()) == x ) + True + + sage: not x.is_invertible() or ( + ....: ~Qx + ....: == + ....: x.inverse().quadratic_representation() ) + True + + sage: Qxy(J.one()) == x*y + True + + Property 4: + + sage: not x.is_invertible() or ( + ....: x.quadratic_representation(x.inverse())*Qx + ....: == Qx*x.quadratic_representation(x.inverse()) ) + True + + sage: not x.is_invertible() or ( + ....: x.quadratic_representation(x.inverse())*Qx + ....: == + ....: 2*x.operator()*Qex - Qx ) + True + + sage: 2*x.operator()*Qex - Qx == Lxx True Property 5: - sage: Qy = y.quadratic_representation() - sage: actual = J(Qy*x.vector()).quadratic_representation() - sage: expected = Qy*x.quadratic_representation()*Qy - sage: actual == expected + sage: Qy(x).quadratic_representation() == Qy*Qx*Qy True Property 6: - sage: k = ZZ.random_element(1,10) - sage: actual = (x^k).quadratic_representation() - sage: expected = (x.quadratic_representation())^k - sage: actual == expected + sage: Qxn == (Qx)^n + True + + Property 7: + + sage: not x.is_invertible() or ( + ....: Qx*x.inverse().operator() == Lx ) + True + + Property 8: + + sage: not x.operator_commutes_with(y) or ( + ....: Qx(y)^n == Qxn(y^n) ) True """ @@ -1127,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): @@ -1143,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()) ) @@ -1212,12 +1535,11 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra): TESTS:: sage: set_random_seed() - sage: J = RealCartesianProductEJA(5) - sage: c = J.random_element().subalgebra_idempotent() - sage: c^2 == c - True - sage: J = JordanSpinEJA(5) - sage: c = J.random_element().subalgebra_idempotent() + sage: J = random_eja() + sage: x = J.random_element() + sage: while x.is_nilpotent(): + ....: x = J.random_element() + sage: c = x.subalgebra_idempotent() sage: c^2 == c True