X-Git-Url: http://gitweb.michael.orlitzky.com/?a=blobdiff_plain;f=mjo%2Feja%2Feja_element.py;h=575c2a4754ae261f284c53de4d423011c9bf5418;hb=c16c11d5b4cd015b6d7516a175a92f46e03bd2d7;hp=a8594ca02688493355d9de5f867b3c0cfc1faf07;hpb=fb62f0efeab4e93afcbdd5c1828f28d527ef3ddf;p=sage.d.git diff --git a/mjo/eja/eja_element.py b/mjo/eja/eja_element.py index a8594ca..575c2a4 100644 --- a/mjo/eja/eja_element.py +++ b/mjo/eja/eja_element.py @@ -1,14 +1,20 @@ -from sage.algebras.finite_dimensional_algebras.finite_dimensional_algebra_element import FiniteDimensionalAlgebraElement +# -*- coding: utf-8 -*- + +from itertools import izip + from sage.matrix.constructor import matrix from sage.modules.free_module import VectorSpace +from sage.modules.with_basis.indexed_element import IndexedFreeModuleElement # TODO: make this unnecessary somehow. from sage.misc.lazy_import import lazy_import lazy_import('mjo.eja.eja_algebra', 'FiniteDimensionalEuclideanJordanAlgebra') +lazy_import('mjo.eja.eja_subalgebra', + 'FiniteDimensionalEuclideanJordanElementSubalgebra') from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator from mjo.eja.eja_utils import _mat2vec -class FiniteDimensionalEuclideanJordanAlgebraElement(FiniteDimensionalAlgebraElement): +class FiniteDimensionalEuclideanJordanAlgebraElement(IndexedFreeModuleElement): """ An element of a Euclidean Jordan algebra. """ @@ -23,75 +29,14 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(FiniteDimensionalAlgebraEle dir(self.__class__) ) - def __init__(self, A, elt=None): - """ - - SETUP:: - - sage: from mjo.eja.eja_algebra import (RealSymmetricEJA, - ....: random_eja) - - EXAMPLES: - - The identity in `S^n` is converted to the identity in the EJA:: - - sage: J = RealSymmetricEJA(3) - sage: I = matrix.identity(QQ,3) - sage: J(I) == J.one() - True - - This skew-symmetric matrix can't be represented in the EJA:: - - sage: J = RealSymmetricEJA(3) - sage: A = matrix(QQ,3, lambda i,j: i-j) - sage: J(A) - Traceback (most recent call last): - ... - ArithmeticError: vector is not in free module - - TESTS: - Ensure that we can convert any element of the parent's - underlying vector space back into an algebra element whose - vector representation is what we started with:: - - sage: set_random_seed() - sage: J = random_eja() - sage: v = J.vector_space().random_element() - sage: J(v).vector() == v - True - - """ - # Goal: if we're given a matrix, and if it lives in our - # parent algebra's "natural ambient space," convert it - # into an algebra element. - # - # The catch is, we make a recursive call after converting - # the given matrix into a vector that lives in the algebra. - # This we need to try the parent class initializer first, - # to avoid recursing forever if we're given something that - # already fits into the algebra, but also happens to live - # in the parent's "natural ambient space" (this happens with - # vectors in R^n). - try: - FiniteDimensionalAlgebraElement.__init__(self, A, elt) - except ValueError: - natural_basis = A.natural_basis() - if elt in natural_basis[0].matrix_space(): - # Thanks for nothing! Matrix spaces aren't vector - # spaces in Sage, so we have to figure out its - # natural-basis coordinates ourselves. - V = VectorSpace(elt.base_ring(), elt.nrows()**2) - W = V.span( _mat2vec(s) for s in natural_basis ) - coords = W.coordinates(_mat2vec(elt)) - FiniteDimensionalAlgebraElement.__init__(self, A, coords) def __pow__(self, n): """ Return ``self`` raised to the power ``n``. Jordan algebras are always power-associative; see for - example Faraut and Koranyi, Proposition II.1.2 (ii). + example Faraut and Korányi, Proposition II.1.2 (ii). We have to override this because our superclass uses row vectors instead of column vectors! We, on the other hand, @@ -137,7 +82,7 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(FiniteDimensionalAlgebraEle elif n == 1: return self else: - return (self.operator()**(n-1))(self) + return (self**(n-1))*self def apply_univariate_polynomial(self, p): @@ -224,9 +169,24 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(FiniteDimensionalAlgebraEle sage: x.apply_univariate_polynomial(p) 0 + The characteristic polynomials of the zero and unit elements + should be what we think they are in a subalgebra, too:: + + sage: J = RealCartesianProductEJA(3) + sage: p1 = J.one().characteristic_polynomial() + sage: q1 = J.zero().characteristic_polynomial() + sage: e0,e1,e2 = J.gens() + sage: A = (e0 + 2*e1 + 3*e2).subalgebra_generated_by() # dim 3 + sage: p2 = A.one().characteristic_polynomial() + sage: q2 = A.zero().characteristic_polynomial() + sage: p1 == p2 + True + sage: q1 == q2 + True + """ p = self.parent().characteristic_polynomial() - return p(*self.vector()) + return p(*self.to_vector()) def inner_product(self, other): @@ -253,7 +213,7 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(FiniteDimensionalAlgebraEle sage: y = vector(QQ,[4,5,6]) sage: x.inner_product(y) 32 - sage: J(x).inner_product(J(y)) + sage: J.from_vector(x).inner_product(J.from_vector(y)) 32 The inner product on `S^n` is ` = trace(X*Y)`, where @@ -287,9 +247,8 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(FiniteDimensionalAlgebraEle sage: set_random_seed() sage: J = random_eja() - sage: x = J.random_element() - sage: y = J.random_element() - sage: x.inner_product(y) in RR + sage: x,y = J.random_elements(2) + sage: x.inner_product(y) in RLF True """ @@ -324,9 +283,7 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(FiniteDimensionalAlgebraEle Test Lemma 1 from Chapter III of Koecher:: sage: set_random_seed() - sage: J = random_eja() - sage: u = J.random_element() - sage: v = J.random_element() + sage: u,v = random_eja().random_elements(2) sage: lhs = u.operator_commutes_with(u*v) sage: rhs = v.operator_commutes_with(u^2) sage: lhs == rhs @@ -336,9 +293,7 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(FiniteDimensionalAlgebraEle Chapter III, or from Baes (2.3):: sage: set_random_seed() - sage: J = random_eja() - sage: x = J.random_element() - sage: y = J.random_element() + sage: x,y = random_eja().random_elements(2) sage: Lx = x.operator() sage: Ly = y.operator() sage: Lxx = (x*x).operator() @@ -350,10 +305,7 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(FiniteDimensionalAlgebraEle Baes (2.4):: sage: set_random_seed() - sage: J = random_eja() - sage: x = J.random_element() - sage: y = J.random_element() - sage: z = J.random_element() + sage: x,y,z = random_eja().random_elements(3) sage: Lx = x.operator() sage: Ly = y.operator() sage: Lz = z.operator() @@ -367,10 +319,7 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(FiniteDimensionalAlgebraEle Baes (2.5):: sage: set_random_seed() - sage: J = random_eja() - sage: u = J.random_element() - sage: y = J.random_element() - sage: z = J.random_element() + sage: u,y,z = random_eja().random_elements(3) sage: Lu = u.operator() sage: Ly = y.operator() sage: Lz = z.operator() @@ -427,6 +376,15 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(FiniteDimensionalAlgebraEle sage: x.is_invertible() == (x.det() != 0) True + Ensure that the determinant is multiplicative on an associative + subalgebra as in Faraut and Korányi's Proposition II.2.2:: + + sage: set_random_seed() + sage: J = random_eja().random_element().subalgebra_generated_by() + sage: x,y = J.random_elements(2) + sage: (x*y).det() == x.det()*y.det() + True + """ P = self.parent() r = P.rank() @@ -435,7 +393,7 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(FiniteDimensionalAlgebraEle # -1 to ensure that _charpoly_coeff(0) is really what # appears in front of t^{0} in the charpoly. However, # we want (-1)^r times THAT for the determinant. - return ((-1)**r)*p(*self.vector()) + return ((-1)**r)*p(*self.to_vector()) def inverse(self): @@ -449,7 +407,8 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(FiniteDimensionalAlgebraEle SETUP:: - sage: from mjo.eja.eja_algebra import (JordanSpinEJA, + sage: from mjo.eja.eja_algebra import (ComplexHermitianEJA, + ....: JordanSpinEJA, ....: random_eja) EXAMPLES: @@ -458,20 +417,26 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(FiniteDimensionalAlgebraEle Example 11.11:: sage: set_random_seed() - sage: n = ZZ.random_element(1,10) - sage: J = JordanSpinEJA(n) + sage: J = JordanSpinEJA.random_instance() sage: x = J.random_element() sage: while not x.is_invertible(): ....: x = J.random_element() - sage: x_vec = x.vector() + sage: x_vec = x.to_vector() sage: x0 = x_vec[0] sage: x_bar = x_vec[1:] 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) + sage: x.inverse() == J.from_vector(x_inverse) True + Trying to invert a non-invertible element throws an error: + + sage: JordanSpinEJA(3).zero().inverse() + Traceback (most recent call last): + ... + ValueError: element is not invertible + TESTS: The identity element is its own inverse:: @@ -497,13 +462,32 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(FiniteDimensionalAlgebraEle sage: (not x.is_invertible()) or (x.inverse().inverse() == x) True - The zero element is never invertible:: + Proposition II.2.3 in Faraut and Korányi says that the inverse + of an element is the inverse of its left-multiplication operator + applied to the algebra's identity, when that inverse exists:: sage: set_random_seed() - sage: J = random_eja().zero().inverse() - Traceback (most recent call last): - ... - ValueError: element is not invertible + sage: J = random_eja() + sage: x = J.random_element() + sage: (not x.operator().is_invertible()) or ( + ....: x.operator().inverse()(J.one()) == x.inverse() ) + True + + Proposition II.2.4 in Faraut and Korányi gives a formula for + the inverse based on the characteristic polynomial and the + Cayley-Hamilton theorem for Euclidean Jordan algebras:: + + sage: set_random_seed() + sage: J = ComplexHermitianEJA(3) + sage: x = J.random_element() + sage: while not x.is_invertible(): + ....: x = J.random_element() + sage: r = J.rank() + sage: a = x.characteristic_polynomial().coefficients(sparse=False) + sage: expected = (-1)^(r+1)/x.det() + sage: expected *= sum( a[i+1]*x^i for i in range(r) ) + sage: x.inverse() == expected + True """ if not self.is_invertible(): @@ -541,19 +525,117 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(FiniteDimensionalAlgebraEle sage: J.one().is_invertible() True - The zero element is never invertible:: + The zero element is never invertible in a non-trivial algebra:: sage: set_random_seed() sage: J = random_eja() - sage: J.zero().is_invertible() + sage: (not J.is_trivial()) and J.zero().is_invertible() False """ - zero = self.parent().zero() + if self.is_zero(): + if self.parent().is_trivial(): + return True + else: + return False + + # In fact, we only need to know if the constant term is non-zero, + # so we can pass in the field's zero element instead. + zero = self.base_ring().zero() p = self.minimal_polynomial() return not (p(zero) == zero) + def is_minimal_idempotent(self): + """ + Return whether or not this element is a minimal idempotent. + + + An element of a Euclidean Jordan algebra is a minimal idempotent + if it :meth:`is_idempotent` and if its Peirce subalgebra + corresponding to the eigenvalue ``1`` has dimension ``1`` (Baes, + Proposition 2.7.17). + + SETUP:: + + sage: from mjo.eja.eja_algebra import (JordanSpinEJA, + ....: RealSymmetricEJA, + ....: random_eja) + + WARNING:: + + This method is sloooooow. + + EXAMPLES: + + The spectral decomposition of a non-regular element should always + contain at least one non-minimal idempotent:: + + sage: J = RealSymmetricEJA(3, AA) + sage: x = sum(J.gens()) + sage: x.is_regular() + False + sage: [ c.is_minimal_idempotent() + ....: for (l,c) in x.spectral_decomposition() ] + [False, True] + + On the other hand, the spectral decomposition of a regular + element should always be in terms of minimal idempotents:: + + sage: J = JordanSpinEJA(4, AA) + sage: x = sum( i*J.gens()[i] for i in range(len(J.gens())) ) + sage: x.is_regular() + True + sage: [ c.is_minimal_idempotent() + ....: for (l,c) in x.spectral_decomposition() ] + [True, True] + + TESTS: + + The identity element is minimal only in an EJA of rank one:: + + sage: set_random_seed() + sage: J = random_eja() + sage: J.rank() == 1 or not J.one().is_minimal_idempotent() + True + + A non-idempotent cannot be a minimal idempotent:: + + sage: set_random_seed() + sage: J = JordanSpinEJA(4) + sage: x = J.random_element() + sage: (not x.is_idempotent()) and x.is_minimal_idempotent() + False + + Proposition 2.7.19 in Baes says that an element is a minimal + idempotent if and only if it's idempotent with trace equal to + unity:: + + sage: set_random_seed() + sage: J = JordanSpinEJA(4) + sage: x = J.random_element() + sage: expected = (x.is_idempotent() and x.trace() == 1) + sage: actual = x.is_minimal_idempotent() + sage: actual == expected + True + + """ + # TODO: when the Peirce decomposition is implemented for real, + # we can use that instead of finding this eigenspace manually. + # + # Trivial eigenspaces don't appear in the list, so we default to the + # trivial one and override it if there's a nontrivial space in the + # list. + if not self.is_idempotent(): + return False + + J1 = VectorSpace(self.parent().base_ring(), 0) + for (eigval, eigspace) in self.operator().matrix().left_eigenspaces(): + if eigval == 1: + J1 = eigspace + return (J1.dimension() == 1) + + def is_nilpotent(self): """ Return whether or not some power of this element is zero. @@ -581,10 +663,11 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(FiniteDimensionalAlgebraEle TESTS: - The identity element is never nilpotent:: + The identity element is never nilpotent, except in a trivial EJA:: sage: set_random_seed() - sage: random_eja().one().is_nilpotent() + sage: J = random_eja() + sage: J.one().is_nilpotent() and not J.is_trivial() False The additive identity is always nilpotent:: @@ -628,11 +711,11 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(FiniteDimensionalAlgebraEle TESTS: The zero element should never be regular, unless the parent - algebra has dimension one:: + algebra has dimension less than or equal to one:: sage: set_random_seed() sage: J = random_eja() - sage: J.dimension() == 1 or not J.zero().is_regular() + sage: J.dimension() <= 1 or not J.zero().is_regular() True The unit element isn't regular unless the algebra happens to @@ -640,7 +723,7 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(FiniteDimensionalAlgebraEle sage: set_random_seed() sage: J = random_eja() - sage: J.dimension() == 1 or not J.one().is_regular() + sage: J.dimension() <= 1 or not J.one().is_regular() True """ @@ -677,22 +760,24 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(FiniteDimensionalAlgebraEle aren't multiples of the identity are regular:: sage: set_random_seed() - sage: n = ZZ.random_element(1,10) - sage: J = JordanSpinEJA(n) + sage: J = JordanSpinEJA.random_instance() sage: x = J.random_element() sage: x == x.coefficient(0)*J.one() or x.degree() == 2 True TESTS: - The zero and unit elements are both of degree one:: + The zero and unit elements are both of degree one in nontrivial + algebras:: sage: set_random_seed() sage: J = random_eja() - sage: J.zero().degree() - 1 - sage: J.one().degree() - 1 + sage: d = J.zero().degree() + sage: (J.is_trivial() and d == 0) or d == 1 + True + sage: d = J.one().degree() + sage: (J.is_trivial() and d == 0) or d == 1 + True Our implementation agrees with the definition:: @@ -702,7 +787,12 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(FiniteDimensionalAlgebraEle True """ - return self.span_of_powers().dimension() + if self.is_zero() and not self.parent().is_trivial(): + # The minimal polynomial of zero in a nontrivial algebra + # is "t"; in a trivial algebra it's "1" by convention + # (it's an empty product). + return 1 + return self.subalgebra_generated_by().dimension() def left_matrix(self): @@ -730,15 +820,30 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(FiniteDimensionalAlgebraEle SETUP:: sage: from mjo.eja.eja_algebra import (JordanSpinEJA, + ....: RealSymmetricEJA, + ....: TrivialEJA, ....: random_eja) + EXAMPLES: + + Keeping in mind that the polynomial ``1`` evaluates the identity + element (also the zero element) of the trivial algebra, it is clear + that the polynomial ``1`` is the minimal polynomial of the only + element in a trivial algebra:: + + sage: J = TrivialEJA() + sage: J.one().minimal_polynomial() + 1 + sage: J.zero().minimal_polynomial() + 1 + TESTS: The minimal polynomial of the identity and zero elements are always the same:: sage: set_random_seed() - sage: J = random_eja() + sage: J = random_eja(nontrivial=True) sage: J.one().minimal_polynomial() t - 1 sage: J.zero().minimal_polynomial() @@ -755,16 +860,18 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(FiniteDimensionalAlgebraEle The minimal polynomial and the characteristic polynomial coincide and are known (see Alizadeh, Example 11.11) for all elements of the spin factor algebra that aren't scalar multiples of the - identity:: + identity. We require the dimension of the algebra to be at least + two here so that said elements actually exist:: sage: set_random_seed() - sage: n = ZZ.random_element(2,10) + sage: n_max = max(2, JordanSpinEJA._max_test_case_size()) + sage: n = ZZ.random_element(2, n_max) sage: J = JordanSpinEJA(n) sage: y = J.random_element() sage: while y == y.coefficient(0)*J.one(): ....: y = J.random_element() - sage: y0 = y.vector()[0] - sage: y_bar = y.vector()[1:] + sage: y0 = y.to_vector()[0] + sage: y_bar = y.to_vector()[1:] sage: actual = y.minimal_polynomial() sage: t = PolynomialRing(J.base_ring(),'t').gen(0) sage: expected = t^2 - 2*y0*t + (y0^2 - norm(y_bar)^2) @@ -779,14 +886,36 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(FiniteDimensionalAlgebraEle sage: x.apply_univariate_polynomial(p) 0 + The minimal polynomial is invariant under a change of basis, + and in particular, a re-scaling of the basis:: + + sage: set_random_seed() + sage: n_max = RealSymmetricEJA._max_test_case_size() + sage: n = ZZ.random_element(1, n_max) + sage: J1 = RealSymmetricEJA(n,QQ) + sage: J2 = RealSymmetricEJA(n,QQ,normalize_basis=False) + sage: X = random_matrix(QQ,n) + sage: X = X*X.transpose() + sage: x1 = J1(X) + sage: x2 = J2(X) + sage: x1.minimal_polynomial() == x2.minimal_polynomial() + True + """ - 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())) - return elt.operator().minimal_polynomial() + if self.is_zero(): + # We would generate a zero-dimensional subalgebra + # where the minimal polynomial would be constant. + # That might be correct, but only if *this* algebra + # is trivial too. + if not self.parent().is_trivial(): + # Pretty sure we know what the minimal polynomial of + # the zero operator is going to be. This ensures + # consistency of e.g. the polynomial variable returned + # in the "normal" case without us having to think about it. + return self.operator().minimal_polynomial() + + A = self.subalgebra_generated_by() + return A(self).operator().minimal_polynomial() @@ -809,7 +938,7 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(FiniteDimensionalAlgebraEle sage: J = ComplexHermitianEJA(3) sage: J.one() - e0 + e5 + e8 + e0 + e3 + e8 sage: J.one().natural_representation() [1 0 0 0 0 0] [0 1 0 0 0 0] @@ -822,7 +951,7 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(FiniteDimensionalAlgebraEle sage: J = QuaternionHermitianEJA(3) sage: J.one() - e0 + e9 + e14 + e0 + e5 + e14 sage: J.one().natural_representation() [1 0 0 0 0 0 0 0 0 0 0 0] [0 1 0 0 0 0 0 0 0 0 0 0] @@ -839,8 +968,35 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(FiniteDimensionalAlgebraEle """ B = self.parent().natural_basis() - W = B[0].matrix_space() - return W.linear_combination(zip(self.vector(), B)) + W = self.parent().natural_basis_space() + return W.linear_combination(izip(B,self.to_vector())) + + + def norm(self): + """ + The norm of this element with respect to :meth:`inner_product`. + + SETUP:: + + sage: from mjo.eja.eja_algebra import (JordanSpinEJA, + ....: RealCartesianProductEJA) + + EXAMPLES:: + + sage: J = RealCartesianProductEJA(2) + sage: x = sum(J.gens()) + sage: x.norm() + sqrt(2) + + :: + + sage: J = JordanSpinEJA(4) + sage: x = sum(J.gens()) + sage: x.norm() + 2 + + """ + return self.inner_product(self).sqrt() def operator(self): @@ -856,8 +1012,7 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(FiniteDimensionalAlgebraEle sage: set_random_seed() sage: J = random_eja() - sage: x = J.random_element() - sage: y = J.random_element() + sage: x,y = J.random_elements(2) sage: x.operator()(y) == x*y True sage: y.operator()(x) == x*y @@ -865,11 +1020,12 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(FiniteDimensionalAlgebraEle """ P = self.parent() - fda_elt = FiniteDimensionalAlgebraElement(P, self) + left_mult_by_self = lambda y: self*y + L = P.module_morphism(function=left_mult_by_self, codomain=P) return FiniteDimensionalEuclideanJordanAlgebraOperator( P, P, - fda_elt.matrix().transpose() ) + L.matrix() ) def quadratic_representation(self, other=None): @@ -887,10 +1043,9 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(FiniteDimensionalAlgebraEle Alizadeh's Example 11.12:: sage: set_random_seed() - sage: n = ZZ.random_element(1,10) - sage: J = JordanSpinEJA(n) - sage: x = J.random_element() - sage: x_vec = x.vector() + sage: x = JordanSpinEJA.random_instance().random_element() + sage: x_vec = x.to_vector() + sage: n = x_vec.degree() sage: x0 = x_vec[0] sage: x_bar = x_vec[1:] sage: A = matrix(QQ, 1, [x_vec.inner_product(x_vec)]) @@ -907,8 +1062,7 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(FiniteDimensionalAlgebraEle sage: set_random_seed() sage: J = random_eja() - sage: x = J.random_element() - sage: y = J.random_element() + sage: x,y = J.random_elements(2) sage: Lx = x.operator() sage: Lxx = (x*x).operator() sage: Qx = x.quadratic_representation() @@ -925,7 +1079,7 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(FiniteDimensionalAlgebraEle Property 2 (multiply on the right for :trac:`28272`): - sage: alpha = QQ.random_element() + sage: alpha = J.base_ring().random_element() sage: (alpha*x).quadratic_representation() == Qx*(alpha^2) True @@ -953,10 +1107,10 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(FiniteDimensionalAlgebraEle sage: not x.is_invertible() or ( ....: x.quadratic_representation(x.inverse())*Qx ....: == - ....: 2*x.operator()*Qex - Qx ) + ....: 2*Lx*Qex - Qx ) True - sage: 2*x.operator()*Qex - Qx == Lxx + sage: 2*Lx*Qex - Qx == Lxx True Property 5: @@ -992,35 +1146,96 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(FiniteDimensionalAlgebraEle return ( L*M + M*L - (self*other).operator() ) - def span_of_powers(self): - """ - Return the vector space spanned by successive powers of - this element. + + def spectral_decomposition(self): """ - # The dimension of the subalgebra can't be greater than - # the big algebra, so just put everything into a list - # and let span() get rid of the excess. - # - # We do the extra ambient_vector_space() in case we're messing - # with polynomials and the direct parent is a module. - V = self.parent().vector_space() - return V.span( (self**d).vector() for d in xrange(V.dimension()) ) + Return the unique spectral decomposition of this element. + + ALGORITHM: + + Following Faraut and Korányi's Theorem III.1.1, we restrict this + element's left-multiplication-by operator to the subalgebra it + generates. We then compute the spectral decomposition of that + operator, and the spectral projectors we get back must be the + left-multiplication-by operators for the idempotents we + seek. Thus applying them to the identity element gives us those + idempotents. + + Since the eigenvalues are required to be distinct, we take + the spectral decomposition of the zero element to be zero + times the identity element of the algebra (which is idempotent, + obviously). + + SETUP:: + + sage: from mjo.eja.eja_algebra import RealSymmetricEJA + + EXAMPLES: + + The spectral decomposition of the identity is ``1`` times itself, + and the spectral decomposition of zero is ``0`` times the identity:: + + sage: J = RealSymmetricEJA(3,AA) + sage: J.one() + e0 + e2 + e5 + sage: J.one().spectral_decomposition() + [(1, e0 + e2 + e5)] + sage: J.zero().spectral_decomposition() + [(0, e0 + e2 + e5)] + + TESTS:: + sage: J = RealSymmetricEJA(4,AA) + sage: x = sum(J.gens()) + sage: sd = x.spectral_decomposition() + sage: l0 = sd[0][0] + sage: l1 = sd[1][0] + sage: c0 = sd[0][1] + sage: c1 = sd[1][1] + sage: c0.inner_product(c1) == 0 + True + sage: c0.is_idempotent() + True + sage: c1.is_idempotent() + True + sage: c0 + c1 == J.one() + True + sage: l0*c0 + l1*c1 == x + True - def subalgebra_generated_by(self): + """ + P = self.parent() + A = self.subalgebra_generated_by(orthonormalize_basis=True) + result = [] + for (evalue, proj) in A(self).operator().spectral_decomposition(): + result.append( (evalue, proj(A.one()).superalgebra_element()) ) + return result + + def subalgebra_generated_by(self, orthonormalize_basis=False): """ Return the associative subalgebra of the parent EJA generated by this element. + Since our parent algebra is unital, we want "subalgebra" to mean + "unital subalgebra" as well; thus the subalgebra that an element + generates will itself be a Euclidean Jordan algebra after + restricting the algebra operations appropriately. This is the + subalgebra that Faraut and Korányi work with in section II.2, for + example. + SETUP:: sage: from mjo.eja.eja_algebra import random_eja - TESTS:: + TESTS: + + This subalgebra, being composed of only powers, is associative:: sage: set_random_seed() - sage: x = random_eja().random_element() - sage: x.subalgebra_generated_by().is_associative() + sage: x0 = random_eja().random_element() + sage: A = x0.subalgebra_generated_by() + sage: x,y,z = A.random_elements(3) + sage: (x*y)*z == x*(y*z) True Squaring in the subalgebra should work the same as in @@ -1028,54 +1243,22 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(FiniteDimensionalAlgebraEle sage: set_random_seed() sage: x = random_eja().random_element() - sage: u = x.subalgebra_generated_by().random_element() - sage: u.operator()(u) == u^2 + sage: A = x.subalgebra_generated_by() + sage: A(x^2) == A(x)*A(x) + True + + By definition, the subalgebra generated by the zero element is + the one-dimensional algebra generated by the identity + element... unless the original algebra was trivial, in which + case the subalgebra is trivial too:: + + sage: set_random_seed() + sage: A = random_eja().zero().subalgebra_generated_by() + sage: (A.is_trivial() and A.dimension() == 0) or A.dimension() == 1 True """ - # First get the subspace spanned by the powers of myself... - V = self.span_of_powers() - F = self.base_ring() - - # Now figure out the entries of the right-multiplication - # matrix for the successive basis elements b0, b1,... of - # that subspace. - mats = [] - for b_right in V.basis(): - eja_b_right = self.parent()(b_right) - b_right_rows = [] - # The first row of the right-multiplication matrix by - # b1 is what we get if we apply that matrix to b1. The - # second row of the right multiplication matrix by b1 - # is what we get when we apply that matrix to b2... - # - # IMPORTANT: this assumes that all vectors are COLUMN - # vectors, unlike our superclass (which uses row vectors). - for b_left in V.basis(): - eja_b_left = self.parent()(b_left) - # Multiply in the original EJA, but then get the - # coordinates from the subalgebra in terms of its - # basis. - this_row = V.coordinates((eja_b_left*eja_b_right).vector()) - b_right_rows.append(this_row) - b_right_matrix = matrix(F, b_right_rows) - mats.append(b_right_matrix) - - # It's an algebra of polynomials in one element, and EJAs - # are power-associative. - # - # TODO: choose generator names intelligently. - # - # The rank is the highest possible degree of a minimal polynomial, - # and is bounded above by the dimension. We know in this case that - # there's an element whose minimal polynomial has the same degree - # as the space's dimension, so that must be its rank too. - return FiniteDimensionalEuclideanJordanAlgebra( - F, - mats, - V.dimension(), - assume_associative=True, - names='f') + return FiniteDimensionalEuclideanJordanElementSubalgebra(self, orthonormalize_basis) def subalgebra_idempotent(self): @@ -1102,18 +1285,14 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(FiniteDimensionalAlgebraEle if self.is_nilpotent(): raise ValueError("this only works with non-nilpotent elements!") - V = self.span_of_powers() J = 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! - u = J(V.coordinates(self.vector())) + u = J(self) # The image of the matrix of left-u^m-multiplication # will be minimal for some natural number s... s = 0 - minimal_dim = V.dimension() - for i in xrange(1, V.dimension()): + minimal_dim = J.dimension() + for i in xrange(1, minimal_dim): this_dim = (u**i).operator().matrix().image().dimension() if this_dim < minimal_dim: minimal_dim = this_dim @@ -1132,29 +1311,33 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(FiniteDimensionalAlgebraEle # Our FiniteDimensionalAlgebraElement superclass uses rows. u_next = u**(s+1) A = u_next.operator().matrix() - c_coordinates = A.solve_right(u_next.vector()) + c = J.from_vector(A.solve_right(u_next.to_vector())) - # Now c_coordinates is the idempotent we want, but it's in - # the coordinate system of the subalgebra. - # - # We need the basis for J, but as elements of the parent algebra. - # - basis = [self.parent(v) for v in V.basis()] - return self.parent().linear_combination(zip(c_coordinates, basis)) + # Now c is the idempotent we want, but it still lives in the subalgebra. + return c.superalgebra_element() def trace(self): """ Return my trace, the sum of my eigenvalues. + In a trivial algebra, however you want to look at it, the trace is + an empty sum for which we declare the result to be zero. + SETUP:: sage: from mjo.eja.eja_algebra import (JordanSpinEJA, ....: RealCartesianProductEJA, + ....: TrivialEJA, ....: random_eja) EXAMPLES:: + sage: J = TrivialEJA() + sage: J.zero().trace() + 0 + + :: sage: J = JordanSpinEJA(3) sage: x = sum(J.gens()) sage: x.trace() @@ -1172,18 +1355,24 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(FiniteDimensionalAlgebraEle sage: set_random_seed() sage: J = random_eja() - sage: J.random_element().trace() in J.base_ring() + sage: J.random_element().trace() in RLF True """ P = self.parent() r = P.rank() + + if r == 0: + # Special case for the trivial algebra where + # the trace is an empty sum. + return P.base_ring().zero() + p = P._charpoly_coeff(r-1) # The _charpoly_coeff function already adds the factor of # -1 to ensure that _charpoly_coeff(r-1) is really what # appears in front of t^{r-1} in the charpoly. However, # we want the negative of THAT for the trace. - return -p(*self.vector()) + return -p(*self.to_vector()) def trace_inner_product(self, other): @@ -1196,22 +1385,16 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(FiniteDimensionalAlgebraEle TESTS: - The trace inner product is commutative:: + The trace inner product is commutative, bilinear, and associative:: sage: set_random_seed() sage: J = random_eja() - sage: x = J.random_element(); y = J.random_element() + sage: x,y,z = J.random_elements(3) + sage: # commutative sage: x.trace_inner_product(y) == y.trace_inner_product(x) True - - The trace inner product is bilinear:: - - sage: set_random_seed() - sage: J = random_eja() - sage: x = J.random_element() - sage: y = J.random_element() - sage: z = J.random_element() - sage: a = QQ.random_element(); + sage: # bilinear + sage: a = J.base_ring().random_element(); sage: actual = (a*(x+z)).trace_inner_product(y) sage: expected = ( a*x.trace_inner_product(y) + ....: a*z.trace_inner_product(y) ) @@ -1222,15 +1405,7 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(FiniteDimensionalAlgebraEle ....: a*x.trace_inner_product(z) ) sage: actual == expected True - - The trace inner product satisfies the compatibility - condition in the definition of a Euclidean Jordan algebra:: - - sage: set_random_seed() - sage: J = random_eja() - sage: x = J.random_element() - sage: y = J.random_element() - sage: z = J.random_element() + sage: # associative sage: (x*y).trace_inner_product(z) == y.trace_inner_product(x*z) True @@ -1239,3 +1414,30 @@ class FiniteDimensionalEuclideanJordanAlgebraElement(FiniteDimensionalAlgebraEle raise TypeError("'other' must live in the same algebra") return (self*other).trace() + + + def trace_norm(self): + """ + The norm of this element with respect to :meth:`trace_inner_product`. + + SETUP:: + + sage: from mjo.eja.eja_algebra import (JordanSpinEJA, + ....: RealCartesianProductEJA) + + EXAMPLES:: + + sage: J = RealCartesianProductEJA(2) + sage: x = sum(J.gens()) + sage: x.trace_norm() + sqrt(2) + + :: + + sage: J = JordanSpinEJA(4) + sage: x = sum(J.gens()) + sage: x.trace_norm() + 2*sqrt(2) + + """ + return self.trace_inner_product(self).sqrt()