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)
169 p
= self
.parent().characteristic_polynomial()
170 return p(*self
.to_vector())
173 def inner_product(self
, other
):
175 Return the parent algebra's inner product of myself and ``other``.
179 sage: from mjo.eja.eja_algebra import (
180 ....: ComplexHermitianEJA,
182 ....: QuaternionHermitianEJA,
183 ....: RealSymmetricEJA,
188 The inner product in the Jordan spin algebra is the usual
189 inner product on `R^n` (this example only works because the
190 basis for the Jordan algebra is the standard basis in `R^n`)::
192 sage: J = JordanSpinEJA(3)
193 sage: x = vector(QQ,[1,2,3])
194 sage: y = vector(QQ,[4,5,6])
195 sage: x.inner_product(y)
197 sage: J.from_vector(x).inner_product(J.from_vector(y))
200 The inner product on `S^n` is `<X,Y> = trace(X*Y)`, where
201 multiplication is the usual matrix multiplication in `S^n`,
202 so the inner product of the identity matrix with itself
205 sage: J = RealSymmetricEJA(3)
206 sage: J.one().inner_product(J.one())
209 Likewise, the inner product on `C^n` is `<X,Y> =
210 Re(trace(X*Y))`, where we must necessarily take the real
211 part because the product of Hermitian matrices may not be
214 sage: J = ComplexHermitianEJA(3)
215 sage: J.one().inner_product(J.one())
218 Ditto for the quaternions::
220 sage: J = QuaternionHermitianEJA(3)
221 sage: J.one().inner_product(J.one())
226 Ensure that we can always compute an inner product, and that
227 it gives us back a real number::
229 sage: set_random_seed()
230 sage: J = random_eja()
231 sage: x = J.random_element()
232 sage: y = J.random_element()
233 sage: x.inner_product(y) in RR
239 raise TypeError("'other' must live in the same algebra")
241 return P
.inner_product(self
, other
)
244 def operator_commutes_with(self
, other
):
246 Return whether or not this element operator-commutes
251 sage: from mjo.eja.eja_algebra import random_eja
255 The definition of a Jordan algebra says that any element
256 operator-commutes with its square::
258 sage: set_random_seed()
259 sage: x = random_eja().random_element()
260 sage: x.operator_commutes_with(x^2)
265 Test Lemma 1 from Chapter III of Koecher::
267 sage: set_random_seed()
268 sage: J = random_eja()
269 sage: u = J.random_element()
270 sage: v = J.random_element()
271 sage: lhs = u.operator_commutes_with(u*v)
272 sage: rhs = v.operator_commutes_with(u^2)
276 Test the first polarization identity from my notes, Koecher
277 Chapter III, or from Baes (2.3)::
279 sage: set_random_seed()
280 sage: J = random_eja()
281 sage: x = J.random_element()
282 sage: y = J.random_element()
283 sage: Lx = x.operator()
284 sage: Ly = y.operator()
285 sage: Lxx = (x*x).operator()
286 sage: Lxy = (x*y).operator()
287 sage: bool(2*Lx*Lxy + Ly*Lxx == 2*Lxy*Lx + Lxx*Ly)
290 Test the second polarization identity from my notes or from
293 sage: set_random_seed()
294 sage: J = random_eja()
295 sage: x = J.random_element()
296 sage: y = J.random_element()
297 sage: z = J.random_element()
298 sage: Lx = x.operator()
299 sage: Ly = y.operator()
300 sage: Lz = z.operator()
301 sage: Lzy = (z*y).operator()
302 sage: Lxy = (x*y).operator()
303 sage: Lxz = (x*z).operator()
304 sage: bool(Lx*Lzy + Lz*Lxy + Ly*Lxz == Lzy*Lx + Lxy*Lz + Lxz*Ly)
307 Test the third polarization identity from my notes or from
310 sage: set_random_seed()
311 sage: J = random_eja()
312 sage: u = J.random_element()
313 sage: y = J.random_element()
314 sage: z = J.random_element()
315 sage: Lu = u.operator()
316 sage: Ly = y.operator()
317 sage: Lz = z.operator()
318 sage: Lzy = (z*y).operator()
319 sage: Luy = (u*y).operator()
320 sage: Luz = (u*z).operator()
321 sage: Luyz = (u*(y*z)).operator()
322 sage: lhs = Lu*Lzy + Lz*Luy + Ly*Luz
323 sage: rhs = Luyz + Ly*Lu*Lz + Lz*Lu*Ly
324 sage: bool(lhs == rhs)
328 if not other
in self
.parent():
329 raise TypeError("'other' must live in the same algebra")
338 Return my determinant, the product of my eigenvalues.
342 sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
347 sage: J = JordanSpinEJA(2)
348 sage: e0,e1 = J.gens()
349 sage: x = sum( J.gens() )
355 sage: J = JordanSpinEJA(3)
356 sage: e0,e1,e2 = J.gens()
357 sage: x = sum( J.gens() )
363 An element is invertible if and only if its determinant is
366 sage: set_random_seed()
367 sage: x = random_eja().random_element()
368 sage: x.is_invertible() == (x.det() != 0)
374 p
= P
._charpoly
_coeff
(0)
375 # The _charpoly_coeff function already adds the factor of
376 # -1 to ensure that _charpoly_coeff(0) is really what
377 # appears in front of t^{0} in the charpoly. However,
378 # we want (-1)^r times THAT for the determinant.
379 return ((-1)**r
)*p(*self
.to_vector())
384 Return the Jordan-multiplicative inverse of this element.
388 We appeal to the quadratic representation as in Koecher's
389 Theorem 12 in Chapter III, Section 5.
393 sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
398 The inverse in the spin factor algebra is given in Alizadeh's
401 sage: set_random_seed()
402 sage: n = ZZ.random_element(1,10)
403 sage: J = JordanSpinEJA(n)
404 sage: x = J.random_element()
405 sage: while not x.is_invertible():
406 ....: x = J.random_element()
407 sage: x_vec = x.to_vector()
409 sage: x_bar = x_vec[1:]
410 sage: coeff = ~(x0^2 - x_bar.inner_product(x_bar))
411 sage: inv_vec = x_vec.parent()([x0] + (-x_bar).list())
412 sage: x_inverse = coeff*inv_vec
413 sage: x.inverse() == J.from_vector(x_inverse)
418 The identity element is its own inverse::
420 sage: set_random_seed()
421 sage: J = random_eja()
422 sage: J.one().inverse() == J.one()
425 If an element has an inverse, it acts like one::
427 sage: set_random_seed()
428 sage: J = random_eja()
429 sage: x = J.random_element()
430 sage: (not x.is_invertible()) or (x.inverse()*x == J.one())
433 The inverse of the inverse is what we started with::
435 sage: set_random_seed()
436 sage: J = random_eja()
437 sage: x = J.random_element()
438 sage: (not x.is_invertible()) or (x.inverse().inverse() == x)
441 The zero element is never invertible::
443 sage: set_random_seed()
444 sage: J = random_eja().zero().inverse()
445 Traceback (most recent call last):
447 ValueError: element is not invertible
450 if not self
.is_invertible():
451 raise ValueError("element is not invertible")
453 return (~self
.quadratic_representation())(self
)
456 def is_invertible(self
):
458 Return whether or not this element is invertible.
462 The usual way to do this is to check if the determinant is
463 zero, but we need the characteristic polynomial for the
464 determinant. The minimal polynomial is a lot easier to get,
465 so we use Corollary 2 in Chapter V of Koecher to check
466 whether or not the paren't algebra's zero element is a root
467 of this element's minimal polynomial.
469 Beware that we can't use the superclass method, because it
470 relies on the algebra being associative.
474 sage: from mjo.eja.eja_algebra import random_eja
478 The identity element is always invertible::
480 sage: set_random_seed()
481 sage: J = random_eja()
482 sage: J.one().is_invertible()
485 The zero element is never invertible in a non-trivial algebra::
487 sage: set_random_seed()
488 sage: J = random_eja()
489 sage: (not J.is_trivial()) and J.zero().is_invertible()
494 if self
.parent().is_trivial():
499 # In fact, we only need to know if the constant term is non-zero,
500 # so we can pass in the field's zero element instead.
501 zero
= self
.base_ring().zero()
502 p
= self
.minimal_polynomial()
503 return not (p(zero
) == zero
)
506 def is_nilpotent(self
):
508 Return whether or not some power of this element is zero.
512 We use Theorem 5 in Chapter III of Koecher, which says that
513 an element ``x`` is nilpotent if and only if ``x.operator()``
514 is nilpotent. And it is a basic fact of linear algebra that
515 an operator on an `n`-dimensional space is nilpotent if and
516 only if, when raised to the `n`th power, it equals the zero
517 operator (for example, see Axler Corollary 8.8).
521 sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
526 sage: J = JordanSpinEJA(3)
527 sage: x = sum(J.gens())
528 sage: x.is_nilpotent()
533 The identity element is never nilpotent::
535 sage: set_random_seed()
536 sage: random_eja().one().is_nilpotent()
539 The additive identity is always nilpotent::
541 sage: set_random_seed()
542 sage: random_eja().zero().is_nilpotent()
547 zero_operator
= P
.zero().operator()
548 return self
.operator()**P
.dimension() == zero_operator
551 def is_regular(self
):
553 Return whether or not this is a regular element.
557 sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
562 The identity element always has degree one, but any element
563 linearly-independent from it is regular::
565 sage: J = JordanSpinEJA(5)
566 sage: J.one().is_regular()
568 sage: e0, e1, e2, e3, e4 = J.gens() # e0 is the identity
569 sage: for x in J.gens():
570 ....: (J.one() + x).is_regular()
579 The zero element should never be regular, unless the parent
580 algebra has dimension one::
582 sage: set_random_seed()
583 sage: J = random_eja()
584 sage: J.dimension() == 1 or not J.zero().is_regular()
587 The unit element isn't regular unless the algebra happens to
588 consist of only its scalar multiples::
590 sage: set_random_seed()
591 sage: J = random_eja()
592 sage: J.dimension() == 1 or not J.one().is_regular()
596 return self
.degree() == self
.parent().rank()
601 Return the degree of this element, which is defined to be
602 the degree of its minimal polynomial.
606 For now, we skip the messy minimal polynomial computation
607 and instead return the dimension of the vector space spanned
608 by the powers of this element. The latter is a bit more
609 straightforward to compute.
613 sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
618 sage: J = JordanSpinEJA(4)
619 sage: J.one().degree()
621 sage: e0,e1,e2,e3 = J.gens()
622 sage: (e0 - e1).degree()
625 In the spin factor algebra (of rank two), all elements that
626 aren't multiples of the identity are regular::
628 sage: set_random_seed()
629 sage: n = ZZ.random_element(1,10)
630 sage: J = JordanSpinEJA(n)
631 sage: x = J.random_element()
632 sage: x == x.coefficient(0)*J.one() or x.degree() == 2
637 The zero and unit elements are both of degree one::
639 sage: set_random_seed()
640 sage: J = random_eja()
641 sage: J.zero().degree()
643 sage: J.one().degree()
646 Our implementation agrees with the definition::
648 sage: set_random_seed()
649 sage: x = random_eja().random_element()
650 sage: x.degree() == x.minimal_polynomial().degree()
654 if self
.is_zero() and not self
.parent().is_trivial():
655 # The minimal polynomial of zero in a nontrivial algebra
656 # is "t"; in a trivial algebra it's "1" by convention
657 # (it's an empty product).
659 return self
.subalgebra_generated_by().dimension()
662 def left_matrix(self
):
664 Our parent class defines ``left_matrix`` and ``matrix``
665 methods whose names are misleading. We don't want them.
667 raise NotImplementedError("use operator().matrix() instead")
672 def minimal_polynomial(self
):
674 Return the minimal polynomial of this element,
675 as a function of the variable `t`.
679 We restrict ourselves to the associative subalgebra
680 generated by this element, and then return the minimal
681 polynomial of this element's operator matrix (in that
682 subalgebra). This works by Baes Proposition 2.3.16.
686 sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
691 The minimal polynomial of the identity and zero elements are
694 sage: set_random_seed()
695 sage: J = random_eja()
696 sage: J.one().minimal_polynomial()
698 sage: J.zero().minimal_polynomial()
701 The degree of an element is (by one definition) the degree
702 of its minimal polynomial::
704 sage: set_random_seed()
705 sage: x = random_eja().random_element()
706 sage: x.degree() == x.minimal_polynomial().degree()
709 The minimal polynomial and the characteristic polynomial coincide
710 and are known (see Alizadeh, Example 11.11) for all elements of
711 the spin factor algebra that aren't scalar multiples of the
714 sage: set_random_seed()
715 sage: n = ZZ.random_element(2,10)
716 sage: J = JordanSpinEJA(n)
717 sage: y = J.random_element()
718 sage: while y == y.coefficient(0)*J.one():
719 ....: y = J.random_element()
720 sage: y0 = y.to_vector()[0]
721 sage: y_bar = y.to_vector()[1:]
722 sage: actual = y.minimal_polynomial()
723 sage: t = PolynomialRing(J.base_ring(),'t').gen(0)
724 sage: expected = t^2 - 2*y0*t + (y0^2 - norm(y_bar)^2)
725 sage: bool(actual == expected)
728 The minimal polynomial should always kill its element::
730 sage: set_random_seed()
731 sage: x = random_eja().random_element()
732 sage: p = x.minimal_polynomial()
733 sage: x.apply_univariate_polynomial(p)
738 # We would generate a zero-dimensional subalgebra
739 # where the minimal polynomial would be constant.
740 # That might be correct, but only if *this* algebra
742 if not self
.parent().is_trivial():
743 # Pretty sure we know what the minimal polynomial of
744 # the zero operator is going to be. This ensures
745 # consistency of e.g. the polynomial variable returned
746 # in the "normal" case without us having to think about it.
747 return self
.operator().minimal_polynomial()
749 A
= self
.subalgebra_generated_by()
750 return A(self
).operator().minimal_polynomial()
754 def natural_representation(self
):
756 Return a more-natural representation of this element.
758 Every finite-dimensional Euclidean Jordan Algebra is a
759 direct sum of five simple algebras, four of which comprise
760 Hermitian matrices. This method returns the original
761 "natural" representation of this element as a Hermitian
762 matrix, if it has one. If not, you get the usual representation.
766 sage: from mjo.eja.eja_algebra import (ComplexHermitianEJA,
767 ....: QuaternionHermitianEJA)
771 sage: J = ComplexHermitianEJA(3)
774 sage: J.one().natural_representation()
784 sage: J = QuaternionHermitianEJA(3)
787 sage: J.one().natural_representation()
788 [1 0 0 0 0 0 0 0 0 0 0 0]
789 [0 1 0 0 0 0 0 0 0 0 0 0]
790 [0 0 1 0 0 0 0 0 0 0 0 0]
791 [0 0 0 1 0 0 0 0 0 0 0 0]
792 [0 0 0 0 1 0 0 0 0 0 0 0]
793 [0 0 0 0 0 1 0 0 0 0 0 0]
794 [0 0 0 0 0 0 1 0 0 0 0 0]
795 [0 0 0 0 0 0 0 1 0 0 0 0]
796 [0 0 0 0 0 0 0 0 1 0 0 0]
797 [0 0 0 0 0 0 0 0 0 1 0 0]
798 [0 0 0 0 0 0 0 0 0 0 1 0]
799 [0 0 0 0 0 0 0 0 0 0 0 1]
802 B
= self
.parent().natural_basis()
803 W
= B
[0].matrix_space()
804 return W
.linear_combination(zip(B
,self
.to_vector()))
809 Return the left-multiplication-by-this-element
810 operator on the ambient algebra.
814 sage: from mjo.eja.eja_algebra import random_eja
818 sage: set_random_seed()
819 sage: J = random_eja()
820 sage: x = J.random_element()
821 sage: y = J.random_element()
822 sage: x.operator()(y) == x*y
824 sage: y.operator()(x) == x*y
829 left_mult_by_self
= lambda y
: self
*y
830 L
= P
.module_morphism(function
=left_mult_by_self
, codomain
=P
)
831 return FiniteDimensionalEuclideanJordanAlgebraOperator(
837 def quadratic_representation(self
, other
=None):
839 Return the quadratic representation of this element.
843 sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
848 The explicit form in the spin factor algebra is given by
849 Alizadeh's Example 11.12::
851 sage: set_random_seed()
852 sage: n = ZZ.random_element(1,10)
853 sage: J = JordanSpinEJA(n)
854 sage: x = J.random_element()
855 sage: x_vec = x.to_vector()
857 sage: x_bar = x_vec[1:]
858 sage: A = matrix(QQ, 1, [x_vec.inner_product(x_vec)])
859 sage: B = 2*x0*x_bar.row()
860 sage: C = 2*x0*x_bar.column()
861 sage: D = matrix.identity(QQ, n-1)
862 sage: D = (x0^2 - x_bar.inner_product(x_bar))*D
863 sage: D = D + 2*x_bar.tensor_product(x_bar)
864 sage: Q = matrix.block(2,2,[A,B,C,D])
865 sage: Q == x.quadratic_representation().matrix()
868 Test all of the properties from Theorem 11.2 in Alizadeh::
870 sage: set_random_seed()
871 sage: J = random_eja()
872 sage: x = J.random_element()
873 sage: y = J.random_element()
874 sage: Lx = x.operator()
875 sage: Lxx = (x*x).operator()
876 sage: Qx = x.quadratic_representation()
877 sage: Qy = y.quadratic_representation()
878 sage: Qxy = x.quadratic_representation(y)
879 sage: Qex = J.one().quadratic_representation(x)
880 sage: n = ZZ.random_element(10)
881 sage: Qxn = (x^n).quadratic_representation()
885 sage: 2*Qxy == (x+y).quadratic_representation() - Qx - Qy
888 Property 2 (multiply on the right for :trac:`28272`):
890 sage: alpha = QQ.random_element()
891 sage: (alpha*x).quadratic_representation() == Qx*(alpha^2)
896 sage: not x.is_invertible() or ( Qx(x.inverse()) == x )
899 sage: not x.is_invertible() or (
902 ....: x.inverse().quadratic_representation() )
905 sage: Qxy(J.one()) == x*y
910 sage: not x.is_invertible() or (
911 ....: x.quadratic_representation(x.inverse())*Qx
912 ....: == Qx*x.quadratic_representation(x.inverse()) )
915 sage: not x.is_invertible() or (
916 ....: x.quadratic_representation(x.inverse())*Qx
918 ....: 2*x.operator()*Qex - Qx )
921 sage: 2*x.operator()*Qex - Qx == Lxx
926 sage: Qy(x).quadratic_representation() == Qy*Qx*Qy
936 sage: not x.is_invertible() or (
937 ....: Qx*x.inverse().operator() == Lx )
942 sage: not x.operator_commutes_with(y) or (
943 ....: Qx(y)^n == Qxn(y^n) )
949 elif not other
in self
.parent():
950 raise TypeError("'other' must live in the same algebra")
954 return ( L
*M
+ M
*L
- (self
*other
).operator() )
959 def subalgebra_generated_by(self
):
961 Return the associative subalgebra of the parent EJA generated
966 sage: from mjo.eja.eja_algebra import random_eja
970 This subalgebra, being composed of only powers, is associative::
972 sage: set_random_seed()
973 sage: x0 = random_eja().random_element()
974 sage: A = x0.subalgebra_generated_by()
975 sage: x = A.random_element()
976 sage: y = A.random_element()
977 sage: z = A.random_element()
978 sage: (x*y)*z == x*(y*z)
981 Squaring in the subalgebra should work the same as in
984 sage: set_random_seed()
985 sage: x = random_eja().random_element()
986 sage: A = x.subalgebra_generated_by()
987 sage: A(x^2) == A(x)*A(x)
990 The subalgebra generated by the zero element is trivial::
992 sage: set_random_seed()
993 sage: A = random_eja().zero().subalgebra_generated_by()
995 Euclidean Jordan algebra of dimension 0 over Rational Field
1000 return FiniteDimensionalEuclideanJordanElementSubalgebra(self
)
1003 def subalgebra_idempotent(self
):
1005 Find an idempotent in the associative subalgebra I generate
1006 using Proposition 2.3.5 in Baes.
1010 sage: from mjo.eja.eja_algebra import random_eja
1014 sage: set_random_seed()
1015 sage: J = random_eja()
1016 sage: x = J.random_element()
1017 sage: while x.is_nilpotent():
1018 ....: x = J.random_element()
1019 sage: c = x.subalgebra_idempotent()
1024 if self
.is_nilpotent():
1025 raise ValueError("this only works with non-nilpotent elements!")
1027 J
= self
.subalgebra_generated_by()
1030 # The image of the matrix of left-u^m-multiplication
1031 # will be minimal for some natural number s...
1033 minimal_dim
= J
.dimension()
1034 for i
in xrange(1, minimal_dim
):
1035 this_dim
= (u
**i
).operator().matrix().image().dimension()
1036 if this_dim
< minimal_dim
:
1037 minimal_dim
= this_dim
1040 # Now minimal_matrix should correspond to the smallest
1041 # non-zero subspace in Baes's (or really, Koecher's)
1044 # However, we need to restrict the matrix to work on the
1045 # subspace... or do we? Can't we just solve, knowing that
1046 # A(c) = u^(s+1) should have a solution in the big space,
1049 # Beware, solve_right() means that we're using COLUMN vectors.
1050 # Our FiniteDimensionalAlgebraElement superclass uses rows.
1052 A
= u_next
.operator().matrix()
1053 c
= J
.from_vector(A
.solve_right(u_next
.to_vector()))
1055 # Now c is the idempotent we want, but it still lives in the subalgebra.
1056 return c
.superalgebra_element()
1061 Return my trace, the sum of my eigenvalues.
1065 sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
1066 ....: RealCartesianProductEJA,
1071 sage: J = JordanSpinEJA(3)
1072 sage: x = sum(J.gens())
1078 sage: J = RealCartesianProductEJA(5)
1079 sage: J.one().trace()
1084 The trace of an element is a real number::
1086 sage: set_random_seed()
1087 sage: J = random_eja()
1088 sage: J.random_element().trace() in J.base_ring()
1094 p
= P
._charpoly
_coeff
(r
-1)
1095 # The _charpoly_coeff function already adds the factor of
1096 # -1 to ensure that _charpoly_coeff(r-1) is really what
1097 # appears in front of t^{r-1} in the charpoly. However,
1098 # we want the negative of THAT for the trace.
1099 return -p(*self
.to_vector())
1102 def trace_inner_product(self
, other
):
1104 Return the trace inner product of myself and ``other``.
1108 sage: from mjo.eja.eja_algebra import random_eja
1112 The trace inner product is commutative::
1114 sage: set_random_seed()
1115 sage: J = random_eja()
1116 sage: x = J.random_element(); y = J.random_element()
1117 sage: x.trace_inner_product(y) == y.trace_inner_product(x)
1120 The trace inner product is bilinear::
1122 sage: set_random_seed()
1123 sage: J = random_eja()
1124 sage: x = J.random_element()
1125 sage: y = J.random_element()
1126 sage: z = J.random_element()
1127 sage: a = QQ.random_element();
1128 sage: actual = (a*(x+z)).trace_inner_product(y)
1129 sage: expected = ( a*x.trace_inner_product(y) +
1130 ....: a*z.trace_inner_product(y) )
1131 sage: actual == expected
1133 sage: actual = x.trace_inner_product(a*(y+z))
1134 sage: expected = ( a*x.trace_inner_product(y) +
1135 ....: a*x.trace_inner_product(z) )
1136 sage: actual == expected
1139 The trace inner product satisfies the compatibility
1140 condition in the definition of a Euclidean Jordan algebra::
1142 sage: set_random_seed()
1143 sage: J = random_eja()
1144 sage: x = J.random_element()
1145 sage: y = J.random_element()
1146 sage: z = J.random_element()
1147 sage: (x*y).trace_inner_product(z) == y.trace_inner_product(x*z)
1151 if not other
in self
.parent():
1152 raise TypeError("'other' must live in the same algebra")
1154 return (self
*other
).trace()