1 from sage
.matrix
.constructor
import matrix
2 from sage
.modules
.free_module
import VectorSpace
3 from sage
.modules
.with_basis
.indexed_element
import IndexedFreeModuleElement
5 # TODO: make this unnecessary somehow.
6 from sage
.misc
.lazy_import
import lazy_import
7 lazy_import('mjo.eja.eja_algebra', 'FiniteDimensionalEuclideanJordanAlgebra')
8 lazy_import('mjo.eja.eja_subalgebra',
9 'FiniteDimensionalEuclideanJordanElementSubalgebra')
10 from mjo
.eja
.eja_operator
import FiniteDimensionalEuclideanJordanAlgebraOperator
11 from mjo
.eja
.eja_utils
import _mat2vec
13 class FiniteDimensionalEuclideanJordanAlgebraElement(IndexedFreeModuleElement
):
15 An element of a Euclidean Jordan algebra.
20 Oh man, I should not be doing this. This hides the "disabled"
21 methods ``left_matrix`` and ``matrix`` from introspection;
22 in particular it removes them from tab-completion.
24 return filter(lambda s
: s
not in ['left_matrix', 'matrix'],
32 Return ``self`` raised to the power ``n``.
34 Jordan algebras are always power-associative; see for
35 example Faraut and Koranyi, Proposition II.1.2 (ii).
37 We have to override this because our superclass uses row
38 vectors instead of column vectors! We, on the other hand,
39 assume column vectors everywhere.
43 sage: from mjo.eja.eja_algebra import random_eja
47 The definition of `x^2` is the unambiguous `x*x`::
49 sage: set_random_seed()
50 sage: x = random_eja().random_element()
54 A few examples of power-associativity::
56 sage: set_random_seed()
57 sage: x = random_eja().random_element()
58 sage: x*(x*x)*(x*x) == x^5
60 sage: (x*x)*(x*x*x) == x^5
63 We also know that powers operator-commute (Koecher, Chapter
66 sage: set_random_seed()
67 sage: x = random_eja().random_element()
68 sage: m = ZZ.random_element(0,10)
69 sage: n = ZZ.random_element(0,10)
70 sage: Lxm = (x^m).operator()
71 sage: Lxn = (x^n).operator()
72 sage: Lxm*Lxn == Lxn*Lxm
77 return self
.parent().one()
81 return (self
.operator()**(n
-1))(self
)
84 def apply_univariate_polynomial(self
, p
):
86 Apply the univariate polynomial ``p`` to this element.
88 A priori, SageMath won't allow us to apply a univariate
89 polynomial to an element of an EJA, because we don't know
90 that EJAs are rings (they are usually not associative). Of
91 course, we know that EJAs are power-associative, so the
92 operation is ultimately kosher. This function sidesteps
93 the CAS to get the answer we want and expect.
97 sage: from mjo.eja.eja_algebra import (RealCartesianProductEJA,
102 sage: R = PolynomialRing(QQ, 't')
104 sage: p = t^4 - t^3 + 5*t - 2
105 sage: J = RealCartesianProductEJA(5)
106 sage: J.one().apply_univariate_polynomial(p) == 3*J.one()
111 We should always get back an element of the algebra::
113 sage: set_random_seed()
114 sage: p = PolynomialRing(QQ, 't').random_element()
115 sage: J = random_eja()
116 sage: x = J.random_element()
117 sage: x.apply_univariate_polynomial(p) in J
121 if len(p
.variables()) > 1:
122 raise ValueError("not a univariate polynomial")
125 # Convert the coeficcients to the parent's base ring,
126 # because a priori they might live in an (unnecessarily)
127 # larger ring for which P.sum() would fail below.
128 cs
= [ R(c
) for c
in p
.coefficients(sparse
=False) ]
129 return P
.sum( cs
[k
]*(self
**k
) for k
in range(len(cs
)) )
132 def characteristic_polynomial(self
):
134 Return the characteristic polynomial of this element.
138 sage: from mjo.eja.eja_algebra import RealCartesianProductEJA
142 The rank of `R^3` is three, and the minimal polynomial of
143 the identity element is `(t-1)` from which it follows that
144 the characteristic polynomial should be `(t-1)^3`::
146 sage: J = RealCartesianProductEJA(3)
147 sage: J.one().characteristic_polynomial()
148 t^3 - 3*t^2 + 3*t - 1
150 Likewise, the characteristic of the zero element in the
151 rank-three algebra `R^{n}` should be `t^{3}`::
153 sage: J = RealCartesianProductEJA(3)
154 sage: J.zero().characteristic_polynomial()
159 The characteristic polynomial of an element should evaluate
160 to zero on that element::
162 sage: set_random_seed()
163 sage: x = RealCartesianProductEJA(3).random_element()
164 sage: p = x.characteristic_polynomial()
165 sage: x.apply_univariate_polynomial(p)
168 The characteristic polynomials of the zero and unit elements
169 should be what we think they are in a subalgebra, too::
171 sage: J = RealCartesianProductEJA(3)
172 sage: p1 = J.one().characteristic_polynomial()
173 sage: q1 = J.zero().characteristic_polynomial()
174 sage: e0,e1,e2 = J.gens()
175 sage: A = (e0 + 2*e1 + 3*e2).subalgebra_generated_by() # dim 3
176 sage: p2 = A.one().characteristic_polynomial()
177 sage: q2 = A.zero().characteristic_polynomial()
184 p
= self
.parent().characteristic_polynomial()
185 return p(*self
.to_vector())
188 def inner_product(self
, other
):
190 Return the parent algebra's inner product of myself and ``other``.
194 sage: from mjo.eja.eja_algebra import (
195 ....: ComplexHermitianEJA,
197 ....: QuaternionHermitianEJA,
198 ....: RealSymmetricEJA,
203 The inner product in the Jordan spin algebra is the usual
204 inner product on `R^n` (this example only works because the
205 basis for the Jordan algebra is the standard basis in `R^n`)::
207 sage: J = JordanSpinEJA(3)
208 sage: x = vector(QQ,[1,2,3])
209 sage: y = vector(QQ,[4,5,6])
210 sage: x.inner_product(y)
212 sage: J.from_vector(x).inner_product(J.from_vector(y))
215 The inner product on `S^n` is `<X,Y> = trace(X*Y)`, where
216 multiplication is the usual matrix multiplication in `S^n`,
217 so the inner product of the identity matrix with itself
220 sage: J = RealSymmetricEJA(3)
221 sage: J.one().inner_product(J.one())
224 Likewise, the inner product on `C^n` is `<X,Y> =
225 Re(trace(X*Y))`, where we must necessarily take the real
226 part because the product of Hermitian matrices may not be
229 sage: J = ComplexHermitianEJA(3)
230 sage: J.one().inner_product(J.one())
233 Ditto for the quaternions::
235 sage: J = QuaternionHermitianEJA(3)
236 sage: J.one().inner_product(J.one())
241 Ensure that we can always compute an inner product, and that
242 it gives us back a real number::
244 sage: set_random_seed()
245 sage: J = random_eja()
246 sage: x = J.random_element()
247 sage: y = J.random_element()
248 sage: x.inner_product(y) in RR
254 raise TypeError("'other' must live in the same algebra")
256 return P
.inner_product(self
, other
)
259 def operator_commutes_with(self
, other
):
261 Return whether or not this element operator-commutes
266 sage: from mjo.eja.eja_algebra import random_eja
270 The definition of a Jordan algebra says that any element
271 operator-commutes with its square::
273 sage: set_random_seed()
274 sage: x = random_eja().random_element()
275 sage: x.operator_commutes_with(x^2)
280 Test Lemma 1 from Chapter III of Koecher::
282 sage: set_random_seed()
283 sage: J = random_eja()
284 sage: u = J.random_element()
285 sage: v = J.random_element()
286 sage: lhs = u.operator_commutes_with(u*v)
287 sage: rhs = v.operator_commutes_with(u^2)
291 Test the first polarization identity from my notes, Koecher
292 Chapter III, or from Baes (2.3)::
294 sage: set_random_seed()
295 sage: J = random_eja()
296 sage: x = J.random_element()
297 sage: y = J.random_element()
298 sage: Lx = x.operator()
299 sage: Ly = y.operator()
300 sage: Lxx = (x*x).operator()
301 sage: Lxy = (x*y).operator()
302 sage: bool(2*Lx*Lxy + Ly*Lxx == 2*Lxy*Lx + Lxx*Ly)
305 Test the second polarization identity from my notes or from
308 sage: set_random_seed()
309 sage: J = random_eja()
310 sage: x = J.random_element()
311 sage: y = J.random_element()
312 sage: z = J.random_element()
313 sage: Lx = x.operator()
314 sage: Ly = y.operator()
315 sage: Lz = z.operator()
316 sage: Lzy = (z*y).operator()
317 sage: Lxy = (x*y).operator()
318 sage: Lxz = (x*z).operator()
319 sage: bool(Lx*Lzy + Lz*Lxy + Ly*Lxz == Lzy*Lx + Lxy*Lz + Lxz*Ly)
322 Test the third polarization identity from my notes or from
325 sage: set_random_seed()
326 sage: J = random_eja()
327 sage: u = J.random_element()
328 sage: y = J.random_element()
329 sage: z = J.random_element()
330 sage: Lu = u.operator()
331 sage: Ly = y.operator()
332 sage: Lz = z.operator()
333 sage: Lzy = (z*y).operator()
334 sage: Luy = (u*y).operator()
335 sage: Luz = (u*z).operator()
336 sage: Luyz = (u*(y*z)).operator()
337 sage: lhs = Lu*Lzy + Lz*Luy + Ly*Luz
338 sage: rhs = Luyz + Ly*Lu*Lz + Lz*Lu*Ly
339 sage: bool(lhs == rhs)
343 if not other
in self
.parent():
344 raise TypeError("'other' must live in the same algebra")
353 Return my determinant, the product of my eigenvalues.
357 sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
362 sage: J = JordanSpinEJA(2)
363 sage: e0,e1 = J.gens()
364 sage: x = sum( J.gens() )
370 sage: J = JordanSpinEJA(3)
371 sage: e0,e1,e2 = J.gens()
372 sage: x = sum( J.gens() )
378 An element is invertible if and only if its determinant is
381 sage: set_random_seed()
382 sage: x = random_eja().random_element()
383 sage: x.is_invertible() == (x.det() != 0)
386 Ensure that the determinant is multiplicative on an associative
387 subalgebra as in Faraut and Koranyi's Proposition II.2.2::
389 sage: set_random_seed()
390 sage: J = random_eja().random_element().subalgebra_generated_by()
391 sage: x = J.random_element()
392 sage: y = J.random_element()
393 sage: (x*y).det() == x.det()*y.det()
399 p
= P
._charpoly
_coeff
(0)
400 # The _charpoly_coeff function already adds the factor of
401 # -1 to ensure that _charpoly_coeff(0) is really what
402 # appears in front of t^{0} in the charpoly. However,
403 # we want (-1)^r times THAT for the determinant.
404 return ((-1)**r
)*p(*self
.to_vector())
409 Return the Jordan-multiplicative inverse of this element.
413 We appeal to the quadratic representation as in Koecher's
414 Theorem 12 in Chapter III, Section 5.
418 sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
423 The inverse in the spin factor algebra is given in Alizadeh's
426 sage: set_random_seed()
427 sage: n = ZZ.random_element(1,10)
428 sage: J = JordanSpinEJA(n)
429 sage: x = J.random_element()
430 sage: while not x.is_invertible():
431 ....: x = J.random_element()
432 sage: x_vec = x.to_vector()
434 sage: x_bar = x_vec[1:]
435 sage: coeff = ~(x0^2 - x_bar.inner_product(x_bar))
436 sage: inv_vec = x_vec.parent()([x0] + (-x_bar).list())
437 sage: x_inverse = coeff*inv_vec
438 sage: x.inverse() == J.from_vector(x_inverse)
443 The identity element is its own inverse::
445 sage: set_random_seed()
446 sage: J = random_eja()
447 sage: J.one().inverse() == J.one()
450 If an element has an inverse, it acts like one::
452 sage: set_random_seed()
453 sage: J = random_eja()
454 sage: x = J.random_element()
455 sage: (not x.is_invertible()) or (x.inverse()*x == J.one())
458 The inverse of the inverse is what we started with::
460 sage: set_random_seed()
461 sage: J = random_eja()
462 sage: x = J.random_element()
463 sage: (not x.is_invertible()) or (x.inverse().inverse() == x)
466 The zero element is never invertible::
468 sage: set_random_seed()
469 sage: J = random_eja().zero().inverse()
470 Traceback (most recent call last):
472 ValueError: element is not invertible
475 if not self
.is_invertible():
476 raise ValueError("element is not invertible")
478 return (~self
.quadratic_representation())(self
)
481 def is_invertible(self
):
483 Return whether or not this element is invertible.
487 The usual way to do this is to check if the determinant is
488 zero, but we need the characteristic polynomial for the
489 determinant. The minimal polynomial is a lot easier to get,
490 so we use Corollary 2 in Chapter V of Koecher to check
491 whether or not the paren't algebra's zero element is a root
492 of this element's minimal polynomial.
494 Beware that we can't use the superclass method, because it
495 relies on the algebra being associative.
499 sage: from mjo.eja.eja_algebra import random_eja
503 The identity element is always invertible::
505 sage: set_random_seed()
506 sage: J = random_eja()
507 sage: J.one().is_invertible()
510 The zero element is never invertible in a non-trivial algebra::
512 sage: set_random_seed()
513 sage: J = random_eja()
514 sage: (not J.is_trivial()) and J.zero().is_invertible()
519 if self
.parent().is_trivial():
524 # In fact, we only need to know if the constant term is non-zero,
525 # so we can pass in the field's zero element instead.
526 zero
= self
.base_ring().zero()
527 p
= self
.minimal_polynomial()
528 return not (p(zero
) == zero
)
531 def is_nilpotent(self
):
533 Return whether or not some power of this element is zero.
537 We use Theorem 5 in Chapter III of Koecher, which says that
538 an element ``x`` is nilpotent if and only if ``x.operator()``
539 is nilpotent. And it is a basic fact of linear algebra that
540 an operator on an `n`-dimensional space is nilpotent if and
541 only if, when raised to the `n`th power, it equals the zero
542 operator (for example, see Axler Corollary 8.8).
546 sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
551 sage: J = JordanSpinEJA(3)
552 sage: x = sum(J.gens())
553 sage: x.is_nilpotent()
558 The identity element is never nilpotent::
560 sage: set_random_seed()
561 sage: random_eja().one().is_nilpotent()
564 The additive identity is always nilpotent::
566 sage: set_random_seed()
567 sage: random_eja().zero().is_nilpotent()
572 zero_operator
= P
.zero().operator()
573 return self
.operator()**P
.dimension() == zero_operator
576 def is_regular(self
):
578 Return whether or not this is a regular element.
582 sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
587 The identity element always has degree one, but any element
588 linearly-independent from it is regular::
590 sage: J = JordanSpinEJA(5)
591 sage: J.one().is_regular()
593 sage: e0, e1, e2, e3, e4 = J.gens() # e0 is the identity
594 sage: for x in J.gens():
595 ....: (J.one() + x).is_regular()
604 The zero element should never be regular, unless the parent
605 algebra has dimension one::
607 sage: set_random_seed()
608 sage: J = random_eja()
609 sage: J.dimension() == 1 or not J.zero().is_regular()
612 The unit element isn't regular unless the algebra happens to
613 consist of only its scalar multiples::
615 sage: set_random_seed()
616 sage: J = random_eja()
617 sage: J.dimension() == 1 or not J.one().is_regular()
621 return self
.degree() == self
.parent().rank()
626 Return the degree of this element, which is defined to be
627 the degree of its minimal polynomial.
631 For now, we skip the messy minimal polynomial computation
632 and instead return the dimension of the vector space spanned
633 by the powers of this element. The latter is a bit more
634 straightforward to compute.
638 sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
643 sage: J = JordanSpinEJA(4)
644 sage: J.one().degree()
646 sage: e0,e1,e2,e3 = J.gens()
647 sage: (e0 - e1).degree()
650 In the spin factor algebra (of rank two), all elements that
651 aren't multiples of the identity are regular::
653 sage: set_random_seed()
654 sage: n = ZZ.random_element(1,10)
655 sage: J = JordanSpinEJA(n)
656 sage: x = J.random_element()
657 sage: x == x.coefficient(0)*J.one() or x.degree() == 2
662 The zero and unit elements are both of degree one::
664 sage: set_random_seed()
665 sage: J = random_eja()
666 sage: J.zero().degree()
668 sage: J.one().degree()
671 Our implementation agrees with the definition::
673 sage: set_random_seed()
674 sage: x = random_eja().random_element()
675 sage: x.degree() == x.minimal_polynomial().degree()
679 if self
.is_zero() and not self
.parent().is_trivial():
680 # The minimal polynomial of zero in a nontrivial algebra
681 # is "t"; in a trivial algebra it's "1" by convention
682 # (it's an empty product).
684 return self
.subalgebra_generated_by().dimension()
687 def left_matrix(self
):
689 Our parent class defines ``left_matrix`` and ``matrix``
690 methods whose names are misleading. We don't want them.
692 raise NotImplementedError("use operator().matrix() instead")
697 def minimal_polynomial(self
):
699 Return the minimal polynomial of this element,
700 as a function of the variable `t`.
704 We restrict ourselves to the associative subalgebra
705 generated by this element, and then return the minimal
706 polynomial of this element's operator matrix (in that
707 subalgebra). This works by Baes Proposition 2.3.16.
711 sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
712 ....: RealSymmetricEJA,
717 The minimal polynomial of the identity and zero elements are
720 sage: set_random_seed()
721 sage: J = random_eja()
722 sage: J.one().minimal_polynomial()
724 sage: J.zero().minimal_polynomial()
727 The degree of an element is (by one definition) the degree
728 of its minimal polynomial::
730 sage: set_random_seed()
731 sage: x = random_eja().random_element()
732 sage: x.degree() == x.minimal_polynomial().degree()
735 The minimal polynomial and the characteristic polynomial coincide
736 and are known (see Alizadeh, Example 11.11) for all elements of
737 the spin factor algebra that aren't scalar multiples of the
740 sage: set_random_seed()
741 sage: n = ZZ.random_element(2,10)
742 sage: J = JordanSpinEJA(n)
743 sage: y = J.random_element()
744 sage: while y == y.coefficient(0)*J.one():
745 ....: y = J.random_element()
746 sage: y0 = y.to_vector()[0]
747 sage: y_bar = y.to_vector()[1:]
748 sage: actual = y.minimal_polynomial()
749 sage: t = PolynomialRing(J.base_ring(),'t').gen(0)
750 sage: expected = t^2 - 2*y0*t + (y0^2 - norm(y_bar)^2)
751 sage: bool(actual == expected)
754 The minimal polynomial should always kill its element::
756 sage: set_random_seed()
757 sage: x = random_eja().random_element()
758 sage: p = x.minimal_polynomial()
759 sage: x.apply_univariate_polynomial(p)
762 The minimal polynomial is invariant under a change of basis,
763 and in particular, a re-scaling of the basis::
765 sage: set_random_seed()
766 sage: n = ZZ.random_element(1,5)
767 sage: J1 = RealSymmetricEJA(n)
768 sage: J2 = RealSymmetricEJA(n,QQ,False)
769 sage: X = random_matrix(QQ,n)
770 sage: X = X*X.transpose()
773 sage: x1.minimal_polynomial() == x2.minimal_polynomial()
778 # We would generate a zero-dimensional subalgebra
779 # where the minimal polynomial would be constant.
780 # That might be correct, but only if *this* algebra
782 if not self
.parent().is_trivial():
783 # Pretty sure we know what the minimal polynomial of
784 # the zero operator is going to be. This ensures
785 # consistency of e.g. the polynomial variable returned
786 # in the "normal" case without us having to think about it.
787 return self
.operator().minimal_polynomial()
789 A
= self
.subalgebra_generated_by()
790 return A(self
).operator().minimal_polynomial()
794 def natural_representation(self
):
796 Return a more-natural representation of this element.
798 Every finite-dimensional Euclidean Jordan Algebra is a
799 direct sum of five simple algebras, four of which comprise
800 Hermitian matrices. This method returns the original
801 "natural" representation of this element as a Hermitian
802 matrix, if it has one. If not, you get the usual representation.
806 sage: from mjo.eja.eja_algebra import (ComplexHermitianEJA,
807 ....: QuaternionHermitianEJA)
811 sage: J = ComplexHermitianEJA(3)
814 sage: J.one().natural_representation()
824 sage: J = QuaternionHermitianEJA(3)
827 sage: J.one().natural_representation()
828 [1 0 0 0 0 0 0 0 0 0 0 0]
829 [0 1 0 0 0 0 0 0 0 0 0 0]
830 [0 0 1 0 0 0 0 0 0 0 0 0]
831 [0 0 0 1 0 0 0 0 0 0 0 0]
832 [0 0 0 0 1 0 0 0 0 0 0 0]
833 [0 0 0 0 0 1 0 0 0 0 0 0]
834 [0 0 0 0 0 0 1 0 0 0 0 0]
835 [0 0 0 0 0 0 0 1 0 0 0 0]
836 [0 0 0 0 0 0 0 0 1 0 0 0]
837 [0 0 0 0 0 0 0 0 0 1 0 0]
838 [0 0 0 0 0 0 0 0 0 0 1 0]
839 [0 0 0 0 0 0 0 0 0 0 0 1]
842 B
= self
.parent().natural_basis()
843 W
= self
.parent().natural_basis_space()
844 return W
.linear_combination(zip(B
,self
.to_vector()))
849 The norm of this element with respect to :meth:`inner_product`.
853 sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
854 ....: RealCartesianProductEJA)
858 sage: J = RealCartesianProductEJA(2)
859 sage: x = sum(J.gens())
865 sage: J = JordanSpinEJA(4)
866 sage: x = sum(J.gens())
871 return self
.inner_product(self
).sqrt()
876 Return the left-multiplication-by-this-element
877 operator on the ambient algebra.
881 sage: from mjo.eja.eja_algebra import random_eja
885 sage: set_random_seed()
886 sage: J = random_eja()
887 sage: x = J.random_element()
888 sage: y = J.random_element()
889 sage: x.operator()(y) == x*y
891 sage: y.operator()(x) == x*y
896 left_mult_by_self
= lambda y
: self
*y
897 L
= P
.module_morphism(function
=left_mult_by_self
, codomain
=P
)
898 return FiniteDimensionalEuclideanJordanAlgebraOperator(
904 def quadratic_representation(self
, other
=None):
906 Return the quadratic representation of this element.
910 sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
915 The explicit form in the spin factor algebra is given by
916 Alizadeh's Example 11.12::
918 sage: set_random_seed()
919 sage: n = ZZ.random_element(1,10)
920 sage: J = JordanSpinEJA(n)
921 sage: x = J.random_element()
922 sage: x_vec = x.to_vector()
924 sage: x_bar = x_vec[1:]
925 sage: A = matrix(QQ, 1, [x_vec.inner_product(x_vec)])
926 sage: B = 2*x0*x_bar.row()
927 sage: C = 2*x0*x_bar.column()
928 sage: D = matrix.identity(QQ, n-1)
929 sage: D = (x0^2 - x_bar.inner_product(x_bar))*D
930 sage: D = D + 2*x_bar.tensor_product(x_bar)
931 sage: Q = matrix.block(2,2,[A,B,C,D])
932 sage: Q == x.quadratic_representation().matrix()
935 Test all of the properties from Theorem 11.2 in Alizadeh::
937 sage: set_random_seed()
938 sage: J = random_eja()
939 sage: x = J.random_element()
940 sage: y = J.random_element()
941 sage: Lx = x.operator()
942 sage: Lxx = (x*x).operator()
943 sage: Qx = x.quadratic_representation()
944 sage: Qy = y.quadratic_representation()
945 sage: Qxy = x.quadratic_representation(y)
946 sage: Qex = J.one().quadratic_representation(x)
947 sage: n = ZZ.random_element(10)
948 sage: Qxn = (x^n).quadratic_representation()
952 sage: 2*Qxy == (x+y).quadratic_representation() - Qx - Qy
955 Property 2 (multiply on the right for :trac:`28272`):
957 sage: alpha = J.base_ring().random_element()
958 sage: (alpha*x).quadratic_representation() == Qx*(alpha^2)
963 sage: not x.is_invertible() or ( Qx(x.inverse()) == x )
966 sage: not x.is_invertible() or (
969 ....: x.inverse().quadratic_representation() )
972 sage: Qxy(J.one()) == x*y
977 sage: not x.is_invertible() or (
978 ....: x.quadratic_representation(x.inverse())*Qx
979 ....: == Qx*x.quadratic_representation(x.inverse()) )
982 sage: not x.is_invertible() or (
983 ....: x.quadratic_representation(x.inverse())*Qx
985 ....: 2*x.operator()*Qex - Qx )
988 sage: 2*x.operator()*Qex - Qx == Lxx
993 sage: Qy(x).quadratic_representation() == Qy*Qx*Qy
1003 sage: not x.is_invertible() or (
1004 ....: Qx*x.inverse().operator() == Lx )
1009 sage: not x.operator_commutes_with(y) or (
1010 ....: Qx(y)^n == Qxn(y^n) )
1016 elif not other
in self
.parent():
1017 raise TypeError("'other' must live in the same algebra")
1020 M
= other
.operator()
1021 return ( L
*M
+ M
*L
- (self
*other
).operator() )
1026 def subalgebra_generated_by(self
):
1028 Return the associative subalgebra of the parent EJA generated
1033 sage: from mjo.eja.eja_algebra import random_eja
1037 This subalgebra, being composed of only powers, is associative::
1039 sage: set_random_seed()
1040 sage: x0 = random_eja().random_element()
1041 sage: A = x0.subalgebra_generated_by()
1042 sage: x = A.random_element()
1043 sage: y = A.random_element()
1044 sage: z = A.random_element()
1045 sage: (x*y)*z == x*(y*z)
1048 Squaring in the subalgebra should work the same as in
1051 sage: set_random_seed()
1052 sage: x = random_eja().random_element()
1053 sage: A = x.subalgebra_generated_by()
1054 sage: A(x^2) == A(x)*A(x)
1057 The subalgebra generated by the zero element is trivial::
1059 sage: set_random_seed()
1060 sage: A = random_eja().zero().subalgebra_generated_by()
1062 Euclidean Jordan algebra of dimension 0 over...
1067 return FiniteDimensionalEuclideanJordanElementSubalgebra(self
)
1070 def subalgebra_idempotent(self
):
1072 Find an idempotent in the associative subalgebra I generate
1073 using Proposition 2.3.5 in Baes.
1077 sage: from mjo.eja.eja_algebra import random_eja
1081 sage: set_random_seed()
1082 sage: J = random_eja()
1083 sage: x = J.random_element()
1084 sage: while x.is_nilpotent():
1085 ....: x = J.random_element()
1086 sage: c = x.subalgebra_idempotent()
1091 if self
.is_nilpotent():
1092 raise ValueError("this only works with non-nilpotent elements!")
1094 J
= self
.subalgebra_generated_by()
1097 # The image of the matrix of left-u^m-multiplication
1098 # will be minimal for some natural number s...
1100 minimal_dim
= J
.dimension()
1101 for i
in xrange(1, minimal_dim
):
1102 this_dim
= (u
**i
).operator().matrix().image().dimension()
1103 if this_dim
< minimal_dim
:
1104 minimal_dim
= this_dim
1107 # Now minimal_matrix should correspond to the smallest
1108 # non-zero subspace in Baes's (or really, Koecher's)
1111 # However, we need to restrict the matrix to work on the
1112 # subspace... or do we? Can't we just solve, knowing that
1113 # A(c) = u^(s+1) should have a solution in the big space,
1116 # Beware, solve_right() means that we're using COLUMN vectors.
1117 # Our FiniteDimensionalAlgebraElement superclass uses rows.
1119 A
= u_next
.operator().matrix()
1120 c
= J
.from_vector(A
.solve_right(u_next
.to_vector()))
1122 # Now c is the idempotent we want, but it still lives in the subalgebra.
1123 return c
.superalgebra_element()
1128 Return my trace, the sum of my eigenvalues.
1132 sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
1133 ....: RealCartesianProductEJA,
1138 sage: J = JordanSpinEJA(3)
1139 sage: x = sum(J.gens())
1145 sage: J = RealCartesianProductEJA(5)
1146 sage: J.one().trace()
1151 The trace of an element is a real number::
1153 sage: set_random_seed()
1154 sage: J = random_eja()
1155 sage: J.random_element().trace() in J.base_ring()
1161 p
= P
._charpoly
_coeff
(r
-1)
1162 # The _charpoly_coeff function already adds the factor of
1163 # -1 to ensure that _charpoly_coeff(r-1) is really what
1164 # appears in front of t^{r-1} in the charpoly. However,
1165 # we want the negative of THAT for the trace.
1166 return -p(*self
.to_vector())
1169 def trace_inner_product(self
, other
):
1171 Return the trace inner product of myself and ``other``.
1175 sage: from mjo.eja.eja_algebra import random_eja
1179 The trace inner product is commutative, bilinear, and satisfies
1182 sage: set_random_seed()
1183 sage: J = random_eja()
1184 sage: x = J.random_element();
1185 sage: y = J.random_element()
1186 sage: z = J.random_element()
1188 sage: x.trace_inner_product(y) == y.trace_inner_product(x)
1191 sage: a = J.base_ring().random_element();
1192 sage: actual = (a*(x+z)).trace_inner_product(y)
1193 sage: expected = ( a*x.trace_inner_product(y) +
1194 ....: a*z.trace_inner_product(y) )
1195 sage: actual == expected
1197 sage: actual = x.trace_inner_product(a*(y+z))
1198 sage: expected = ( a*x.trace_inner_product(y) +
1199 ....: a*x.trace_inner_product(z) )
1200 sage: actual == expected
1202 sage: # jordan axiom
1203 sage: (x*y).trace_inner_product(z) == y.trace_inner_product(x*z)
1207 if not other
in self
.parent():
1208 raise TypeError("'other' must live in the same algebra")
1210 return (self
*other
).trace()
1213 def trace_norm(self
):
1215 The norm of this element with respect to :meth:`trace_inner_product`.
1219 sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
1220 ....: RealCartesianProductEJA)
1224 sage: J = RealCartesianProductEJA(2)
1225 sage: x = sum(J.gens())
1226 sage: x.trace_norm()
1231 sage: J = JordanSpinEJA(4)
1232 sage: x = sum(J.gens())
1233 sage: x.trace_norm()
1237 return self
.trace_inner_product(self
).sqrt()