1 # -*- coding: utf-8 -*-
3 from itertools
import izip
5 from sage
.matrix
.constructor
import matrix
6 from sage
.modules
.free_module
import VectorSpace
7 from sage
.modules
.with_basis
.indexed_element
import IndexedFreeModuleElement
9 # TODO: make this unnecessary somehow.
10 from sage
.misc
.lazy_import
import lazy_import
11 lazy_import('mjo.eja.eja_algebra', 'FiniteDimensionalEuclideanJordanAlgebra')
12 lazy_import('mjo.eja.eja_element_subalgebra',
13 'FiniteDimensionalEuclideanJordanElementSubalgebra')
14 from mjo
.eja
.eja_operator
import FiniteDimensionalEuclideanJordanAlgebraOperator
15 from mjo
.eja
.eja_utils
import _mat2vec
17 class FiniteDimensionalEuclideanJordanAlgebraElement(IndexedFreeModuleElement
):
19 An element of a Euclidean Jordan algebra.
24 Oh man, I should not be doing this. This hides the "disabled"
25 methods ``left_matrix`` and ``matrix`` from introspection;
26 in particular it removes them from tab-completion.
28 return filter(lambda s
: s
not in ['left_matrix', 'matrix'],
36 Return ``self`` raised to the power ``n``.
38 Jordan algebras are always power-associative; see for
39 example Faraut and Korányi, Proposition II.1.2 (ii).
41 We have to override this because our superclass uses row
42 vectors instead of column vectors! We, on the other hand,
43 assume column vectors everywhere.
47 sage: from mjo.eja.eja_algebra import random_eja
51 The definition of `x^2` is the unambiguous `x*x`::
53 sage: set_random_seed()
54 sage: x = random_eja().random_element()
58 A few examples of power-associativity::
60 sage: set_random_seed()
61 sage: x = random_eja().random_element()
62 sage: x*(x*x)*(x*x) == x^5
64 sage: (x*x)*(x*x*x) == x^5
67 We also know that powers operator-commute (Koecher, Chapter
70 sage: set_random_seed()
71 sage: x = random_eja().random_element()
72 sage: m = ZZ.random_element(0,10)
73 sage: n = ZZ.random_element(0,10)
74 sage: Lxm = (x^m).operator()
75 sage: Lxn = (x^n).operator()
76 sage: Lxm*Lxn == Lxn*Lxm
81 return self
.parent().one()
85 return (self
**(n
-1))*self
88 def apply_univariate_polynomial(self
, p
):
90 Apply the univariate polynomial ``p`` to this element.
92 A priori, SageMath won't allow us to apply a univariate
93 polynomial to an element of an EJA, because we don't know
94 that EJAs are rings (they are usually not associative). Of
95 course, we know that EJAs are power-associative, so the
96 operation is ultimately kosher. This function sidesteps
97 the CAS to get the answer we want and expect.
101 sage: from mjo.eja.eja_algebra import (RealCartesianProductEJA,
106 sage: R = PolynomialRing(QQ, 't')
108 sage: p = t^4 - t^3 + 5*t - 2
109 sage: J = RealCartesianProductEJA(5)
110 sage: J.one().apply_univariate_polynomial(p) == 3*J.one()
115 We should always get back an element of the algebra::
117 sage: set_random_seed()
118 sage: p = PolynomialRing(QQ, 't').random_element()
119 sage: J = random_eja()
120 sage: x = J.random_element()
121 sage: x.apply_univariate_polynomial(p) in J
125 if len(p
.variables()) > 1:
126 raise ValueError("not a univariate polynomial")
129 # Convert the coeficcients to the parent's base ring,
130 # because a priori they might live in an (unnecessarily)
131 # larger ring for which P.sum() would fail below.
132 cs
= [ R(c
) for c
in p
.coefficients(sparse
=False) ]
133 return P
.sum( cs
[k
]*(self
**k
) for k
in range(len(cs
)) )
136 def characteristic_polynomial(self
):
138 Return the characteristic polynomial of this element.
142 sage: from mjo.eja.eja_algebra import RealCartesianProductEJA
146 The rank of `R^3` is three, and the minimal polynomial of
147 the identity element is `(t-1)` from which it follows that
148 the characteristic polynomial should be `(t-1)^3`::
150 sage: J = RealCartesianProductEJA(3)
151 sage: J.one().characteristic_polynomial()
152 t^3 - 3*t^2 + 3*t - 1
154 Likewise, the characteristic of the zero element in the
155 rank-three algebra `R^{n}` should be `t^{3}`::
157 sage: J = RealCartesianProductEJA(3)
158 sage: J.zero().characteristic_polynomial()
163 The characteristic polynomial of an element should evaluate
164 to zero on that element::
166 sage: set_random_seed()
167 sage: x = RealCartesianProductEJA(3).random_element()
168 sage: p = x.characteristic_polynomial()
169 sage: x.apply_univariate_polynomial(p)
172 The characteristic polynomials of the zero and unit elements
173 should be what we think they are in a subalgebra, too::
175 sage: J = RealCartesianProductEJA(3)
176 sage: p1 = J.one().characteristic_polynomial()
177 sage: q1 = J.zero().characteristic_polynomial()
178 sage: e0,e1,e2 = J.gens()
179 sage: A = (e0 + 2*e1 + 3*e2).subalgebra_generated_by() # dim 3
180 sage: p2 = A.one().characteristic_polynomial()
181 sage: q2 = A.zero().characteristic_polynomial()
188 p
= self
.parent().characteristic_polynomial()
189 return p(*self
.to_vector())
192 def inner_product(self
, other
):
194 Return the parent algebra's inner product of myself and ``other``.
198 sage: from mjo.eja.eja_algebra import (
199 ....: ComplexHermitianEJA,
201 ....: QuaternionHermitianEJA,
202 ....: RealSymmetricEJA,
207 The inner product in the Jordan spin algebra is the usual
208 inner product on `R^n` (this example only works because the
209 basis for the Jordan algebra is the standard basis in `R^n`)::
211 sage: J = JordanSpinEJA(3)
212 sage: x = vector(QQ,[1,2,3])
213 sage: y = vector(QQ,[4,5,6])
214 sage: x.inner_product(y)
216 sage: J.from_vector(x).inner_product(J.from_vector(y))
219 The inner product on `S^n` is `<X,Y> = trace(X*Y)`, where
220 multiplication is the usual matrix multiplication in `S^n`,
221 so the inner product of the identity matrix with itself
224 sage: J = RealSymmetricEJA(3)
225 sage: J.one().inner_product(J.one())
228 Likewise, the inner product on `C^n` is `<X,Y> =
229 Re(trace(X*Y))`, where we must necessarily take the real
230 part because the product of Hermitian matrices may not be
233 sage: J = ComplexHermitianEJA(3)
234 sage: J.one().inner_product(J.one())
237 Ditto for the quaternions::
239 sage: J = QuaternionHermitianEJA(3)
240 sage: J.one().inner_product(J.one())
245 Ensure that we can always compute an inner product, and that
246 it gives us back a real number::
248 sage: set_random_seed()
249 sage: J = random_eja()
250 sage: x,y = J.random_elements(2)
251 sage: x.inner_product(y) in RLF
257 raise TypeError("'other' must live in the same algebra")
259 return P
.inner_product(self
, other
)
262 def operator_commutes_with(self
, other
):
264 Return whether or not this element operator-commutes
269 sage: from mjo.eja.eja_algebra import random_eja
273 The definition of a Jordan algebra says that any element
274 operator-commutes with its square::
276 sage: set_random_seed()
277 sage: x = random_eja().random_element()
278 sage: x.operator_commutes_with(x^2)
283 Test Lemma 1 from Chapter III of Koecher::
285 sage: set_random_seed()
286 sage: u,v = random_eja().random_elements(2)
287 sage: lhs = u.operator_commutes_with(u*v)
288 sage: rhs = v.operator_commutes_with(u^2)
292 Test the first polarization identity from my notes, Koecher
293 Chapter III, or from Baes (2.3)::
295 sage: set_random_seed()
296 sage: x,y = random_eja().random_elements(2)
297 sage: Lx = x.operator()
298 sage: Ly = y.operator()
299 sage: Lxx = (x*x).operator()
300 sage: Lxy = (x*y).operator()
301 sage: bool(2*Lx*Lxy + Ly*Lxx == 2*Lxy*Lx + Lxx*Ly)
304 Test the second polarization identity from my notes or from
307 sage: set_random_seed()
308 sage: x,y,z = random_eja().random_elements(3)
309 sage: Lx = x.operator()
310 sage: Ly = y.operator()
311 sage: Lz = z.operator()
312 sage: Lzy = (z*y).operator()
313 sage: Lxy = (x*y).operator()
314 sage: Lxz = (x*z).operator()
315 sage: bool(Lx*Lzy + Lz*Lxy + Ly*Lxz == Lzy*Lx + Lxy*Lz + Lxz*Ly)
318 Test the third polarization identity from my notes or from
321 sage: set_random_seed()
322 sage: u,y,z = random_eja().random_elements(3)
323 sage: Lu = u.operator()
324 sage: Ly = y.operator()
325 sage: Lz = z.operator()
326 sage: Lzy = (z*y).operator()
327 sage: Luy = (u*y).operator()
328 sage: Luz = (u*z).operator()
329 sage: Luyz = (u*(y*z)).operator()
330 sage: lhs = Lu*Lzy + Lz*Luy + Ly*Luz
331 sage: rhs = Luyz + Ly*Lu*Lz + Lz*Lu*Ly
332 sage: bool(lhs == rhs)
336 if not other
in self
.parent():
337 raise TypeError("'other' must live in the same algebra")
346 Return my determinant, the product of my eigenvalues.
350 sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
355 sage: J = JordanSpinEJA(2)
356 sage: e0,e1 = J.gens()
357 sage: x = sum( J.gens() )
363 sage: J = JordanSpinEJA(3)
364 sage: e0,e1,e2 = J.gens()
365 sage: x = sum( J.gens() )
371 An element is invertible if and only if its determinant is
374 sage: set_random_seed()
375 sage: x = random_eja().random_element()
376 sage: x.is_invertible() == (x.det() != 0)
379 Ensure that the determinant is multiplicative on an associative
380 subalgebra as in Faraut and Korányi's Proposition II.2.2::
382 sage: set_random_seed()
383 sage: J = random_eja().random_element().subalgebra_generated_by()
384 sage: x,y = J.random_elements(2)
385 sage: (x*y).det() == x.det()*y.det()
391 p
= P
._charpoly
_coeff
(0)
392 # The _charpoly_coeff function already adds the factor of
393 # -1 to ensure that _charpoly_coeff(0) is really what
394 # appears in front of t^{0} in the charpoly. However,
395 # we want (-1)^r times THAT for the determinant.
396 return ((-1)**r
)*p(*self
.to_vector())
401 Return the Jordan-multiplicative inverse of this element.
405 We appeal to the quadratic representation as in Koecher's
406 Theorem 12 in Chapter III, Section 5.
410 sage: from mjo.eja.eja_algebra import (ComplexHermitianEJA,
416 The inverse in the spin factor algebra is given in Alizadeh's
419 sage: set_random_seed()
420 sage: J = JordanSpinEJA.random_instance()
421 sage: x = J.random_element()
422 sage: while not x.is_invertible():
423 ....: x = J.random_element()
424 sage: x_vec = x.to_vector()
426 sage: x_bar = x_vec[1:]
427 sage: coeff = ~(x0^2 - x_bar.inner_product(x_bar))
428 sage: inv_vec = x_vec.parent()([x0] + (-x_bar).list())
429 sage: x_inverse = coeff*inv_vec
430 sage: x.inverse() == J.from_vector(x_inverse)
433 Trying to invert a non-invertible element throws an error:
435 sage: JordanSpinEJA(3).zero().inverse()
436 Traceback (most recent call last):
438 ValueError: element is not invertible
442 The identity element is its own inverse::
444 sage: set_random_seed()
445 sage: J = random_eja()
446 sage: J.one().inverse() == J.one()
449 If an element has an inverse, it acts like one::
451 sage: set_random_seed()
452 sage: J = random_eja()
453 sage: x = J.random_element()
454 sage: (not x.is_invertible()) or (x.inverse()*x == J.one())
457 The inverse of the inverse is what we started with::
459 sage: set_random_seed()
460 sage: J = random_eja()
461 sage: x = J.random_element()
462 sage: (not x.is_invertible()) or (x.inverse().inverse() == x)
465 Proposition II.2.3 in Faraut and Korányi says that the inverse
466 of an element is the inverse of its left-multiplication operator
467 applied to the algebra's identity, when that inverse exists::
469 sage: set_random_seed()
470 sage: J = random_eja()
471 sage: x = J.random_element()
472 sage: (not x.operator().is_invertible()) or (
473 ....: x.operator().inverse()(J.one()) == x.inverse() )
476 Proposition II.2.4 in Faraut and Korányi gives a formula for
477 the inverse based on the characteristic polynomial and the
478 Cayley-Hamilton theorem for Euclidean Jordan algebras::
480 sage: set_random_seed()
481 sage: J = ComplexHermitianEJA(3)
482 sage: x = J.random_element()
483 sage: while not x.is_invertible():
484 ....: x = J.random_element()
486 sage: a = x.characteristic_polynomial().coefficients(sparse=False)
487 sage: expected = (-1)^(r+1)/x.det()
488 sage: expected *= sum( a[i+1]*x^i for i in range(r) )
489 sage: x.inverse() == expected
493 if not self
.is_invertible():
494 raise ValueError("element is not invertible")
496 return (~self
.quadratic_representation())(self
)
499 def is_invertible(self
):
501 Return whether or not this element is invertible.
505 The usual way to do this is to check if the determinant is
506 zero, but we need the characteristic polynomial for the
507 determinant. The minimal polynomial is a lot easier to get,
508 so we use Corollary 2 in Chapter V of Koecher to check
509 whether or not the paren't algebra's zero element is a root
510 of this element's minimal polynomial.
512 Beware that we can't use the superclass method, because it
513 relies on the algebra being associative.
517 sage: from mjo.eja.eja_algebra import random_eja
521 The identity element is always invertible::
523 sage: set_random_seed()
524 sage: J = random_eja()
525 sage: J.one().is_invertible()
528 The zero element is never invertible in a non-trivial algebra::
530 sage: set_random_seed()
531 sage: J = random_eja()
532 sage: (not J.is_trivial()) and J.zero().is_invertible()
537 if self
.parent().is_trivial():
542 # In fact, we only need to know if the constant term is non-zero,
543 # so we can pass in the field's zero element instead.
544 zero
= self
.base_ring().zero()
545 p
= self
.minimal_polynomial()
546 return not (p(zero
) == zero
)
549 def is_minimal_idempotent(self
):
551 Return whether or not this element is a minimal idempotent.
554 An element of a Euclidean Jordan algebra is a minimal idempotent
555 if it :meth:`is_idempotent` and if its Peirce subalgebra
556 corresponding to the eigenvalue ``1`` has dimension ``1`` (Baes,
561 sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
562 ....: RealSymmetricEJA,
567 This method is sloooooow.
571 The spectral decomposition of a non-regular element should always
572 contain at least one non-minimal idempotent::
574 sage: J = RealSymmetricEJA(3, AA)
575 sage: x = sum(J.gens())
578 sage: [ c.is_minimal_idempotent()
579 ....: for (l,c) in x.spectral_decomposition() ]
582 On the other hand, the spectral decomposition of a regular
583 element should always be in terms of minimal idempotents::
585 sage: J = JordanSpinEJA(4, AA)
586 sage: x = sum( i*J.gens()[i] for i in range(len(J.gens())) )
589 sage: [ c.is_minimal_idempotent()
590 ....: for (l,c) in x.spectral_decomposition() ]
595 The identity element is minimal only in an EJA of rank one::
597 sage: set_random_seed()
598 sage: J = random_eja()
599 sage: J.rank() == 1 or not J.one().is_minimal_idempotent()
602 A non-idempotent cannot be a minimal idempotent::
604 sage: set_random_seed()
605 sage: J = JordanSpinEJA(4)
606 sage: x = J.random_element()
607 sage: (not x.is_idempotent()) and x.is_minimal_idempotent()
610 Proposition 2.7.19 in Baes says that an element is a minimal
611 idempotent if and only if it's idempotent with trace equal to
614 sage: set_random_seed()
615 sage: J = JordanSpinEJA(4)
616 sage: x = J.random_element()
617 sage: expected = (x.is_idempotent() and x.trace() == 1)
618 sage: actual = x.is_minimal_idempotent()
619 sage: actual == expected
623 if not self
.is_idempotent():
626 (_
,_
,J1
) = self
.parent().peirce_decomposition(self
)
627 return (J1
.dimension() == 1)
630 def is_nilpotent(self
):
632 Return whether or not some power of this element is zero.
636 We use Theorem 5 in Chapter III of Koecher, which says that
637 an element ``x`` is nilpotent if and only if ``x.operator()``
638 is nilpotent. And it is a basic fact of linear algebra that
639 an operator on an `n`-dimensional space is nilpotent if and
640 only if, when raised to the `n`th power, it equals the zero
641 operator (for example, see Axler Corollary 8.8).
645 sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
650 sage: J = JordanSpinEJA(3)
651 sage: x = sum(J.gens())
652 sage: x.is_nilpotent()
657 The identity element is never nilpotent, except in a trivial EJA::
659 sage: set_random_seed()
660 sage: J = random_eja()
661 sage: J.one().is_nilpotent() and not J.is_trivial()
664 The additive identity is always nilpotent::
666 sage: set_random_seed()
667 sage: random_eja().zero().is_nilpotent()
672 zero_operator
= P
.zero().operator()
673 return self
.operator()**P
.dimension() == zero_operator
676 def is_regular(self
):
678 Return whether or not this is a regular element.
682 sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
687 The identity element always has degree one, but any element
688 linearly-independent from it is regular::
690 sage: J = JordanSpinEJA(5)
691 sage: J.one().is_regular()
693 sage: e0, e1, e2, e3, e4 = J.gens() # e0 is the identity
694 sage: for x in J.gens():
695 ....: (J.one() + x).is_regular()
704 The zero element should never be regular, unless the parent
705 algebra has dimension less than or equal to one::
707 sage: set_random_seed()
708 sage: J = random_eja()
709 sage: J.dimension() <= 1 or not J.zero().is_regular()
712 The unit element isn't regular unless the algebra happens to
713 consist of only its scalar multiples::
715 sage: set_random_seed()
716 sage: J = random_eja()
717 sage: J.dimension() <= 1 or not J.one().is_regular()
721 return self
.degree() == self
.parent().rank()
726 Return the degree of this element, which is defined to be
727 the degree of its minimal polynomial.
731 For now, we skip the messy minimal polynomial computation
732 and instead return the dimension of the vector space spanned
733 by the powers of this element. The latter is a bit more
734 straightforward to compute.
738 sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
743 sage: J = JordanSpinEJA(4)
744 sage: J.one().degree()
746 sage: e0,e1,e2,e3 = J.gens()
747 sage: (e0 - e1).degree()
750 In the spin factor algebra (of rank two), all elements that
751 aren't multiples of the identity are regular::
753 sage: set_random_seed()
754 sage: J = JordanSpinEJA.random_instance()
755 sage: x = J.random_element()
756 sage: x == x.coefficient(0)*J.one() or x.degree() == 2
761 The zero and unit elements are both of degree one in nontrivial
764 sage: set_random_seed()
765 sage: J = random_eja()
766 sage: d = J.zero().degree()
767 sage: (J.is_trivial() and d == 0) or d == 1
769 sage: d = J.one().degree()
770 sage: (J.is_trivial() and d == 0) or d == 1
773 Our implementation agrees with the definition::
775 sage: set_random_seed()
776 sage: x = random_eja().random_element()
777 sage: x.degree() == x.minimal_polynomial().degree()
781 if self
.is_zero() and not self
.parent().is_trivial():
782 # The minimal polynomial of zero in a nontrivial algebra
783 # is "t"; in a trivial algebra it's "1" by convention
784 # (it's an empty product).
786 return self
.subalgebra_generated_by().dimension()
789 def left_matrix(self
):
791 Our parent class defines ``left_matrix`` and ``matrix``
792 methods whose names are misleading. We don't want them.
794 raise NotImplementedError("use operator().matrix() instead")
799 def minimal_polynomial(self
):
801 Return the minimal polynomial of this element,
802 as a function of the variable `t`.
806 We restrict ourselves to the associative subalgebra
807 generated by this element, and then return the minimal
808 polynomial of this element's operator matrix (in that
809 subalgebra). This works by Baes Proposition 2.3.16.
813 sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
814 ....: RealSymmetricEJA,
820 Keeping in mind that the polynomial ``1`` evaluates the identity
821 element (also the zero element) of the trivial algebra, it is clear
822 that the polynomial ``1`` is the minimal polynomial of the only
823 element in a trivial algebra::
825 sage: J = TrivialEJA()
826 sage: J.one().minimal_polynomial()
828 sage: J.zero().minimal_polynomial()
833 The minimal polynomial of the identity and zero elements are
836 sage: set_random_seed()
837 sage: J = random_eja(nontrivial=True)
838 sage: J.one().minimal_polynomial()
840 sage: J.zero().minimal_polynomial()
843 The degree of an element is (by one definition) the degree
844 of its minimal polynomial::
846 sage: set_random_seed()
847 sage: x = random_eja().random_element()
848 sage: x.degree() == x.minimal_polynomial().degree()
851 The minimal polynomial and the characteristic polynomial coincide
852 and are known (see Alizadeh, Example 11.11) for all elements of
853 the spin factor algebra that aren't scalar multiples of the
854 identity. We require the dimension of the algebra to be at least
855 two here so that said elements actually exist::
857 sage: set_random_seed()
858 sage: n_max = max(2, JordanSpinEJA._max_test_case_size())
859 sage: n = ZZ.random_element(2, n_max)
860 sage: J = JordanSpinEJA(n)
861 sage: y = J.random_element()
862 sage: while y == y.coefficient(0)*J.one():
863 ....: y = J.random_element()
864 sage: y0 = y.to_vector()[0]
865 sage: y_bar = y.to_vector()[1:]
866 sage: actual = y.minimal_polynomial()
867 sage: t = PolynomialRing(J.base_ring(),'t').gen(0)
868 sage: expected = t^2 - 2*y0*t + (y0^2 - norm(y_bar)^2)
869 sage: bool(actual == expected)
872 The minimal polynomial should always kill its element::
874 sage: set_random_seed()
875 sage: x = random_eja().random_element()
876 sage: p = x.minimal_polynomial()
877 sage: x.apply_univariate_polynomial(p)
880 The minimal polynomial is invariant under a change of basis,
881 and in particular, a re-scaling of the basis::
883 sage: set_random_seed()
884 sage: n_max = RealSymmetricEJA._max_test_case_size()
885 sage: n = ZZ.random_element(1, n_max)
886 sage: J1 = RealSymmetricEJA(n,QQ)
887 sage: J2 = RealSymmetricEJA(n,QQ,normalize_basis=False)
888 sage: X = random_matrix(QQ,n)
889 sage: X = X*X.transpose()
892 sage: x1.minimal_polynomial() == x2.minimal_polynomial()
897 # We would generate a zero-dimensional subalgebra
898 # where the minimal polynomial would be constant.
899 # That might be correct, but only if *this* algebra
901 if not self
.parent().is_trivial():
902 # Pretty sure we know what the minimal polynomial of
903 # the zero operator is going to be. This ensures
904 # consistency of e.g. the polynomial variable returned
905 # in the "normal" case without us having to think about it.
906 return self
.operator().minimal_polynomial()
908 A
= self
.subalgebra_generated_by()
909 return A(self
).operator().minimal_polynomial()
913 def natural_representation(self
):
915 Return a more-natural representation of this element.
917 Every finite-dimensional Euclidean Jordan Algebra is a
918 direct sum of five simple algebras, four of which comprise
919 Hermitian matrices. This method returns the original
920 "natural" representation of this element as a Hermitian
921 matrix, if it has one. If not, you get the usual representation.
925 sage: from mjo.eja.eja_algebra import (ComplexHermitianEJA,
926 ....: QuaternionHermitianEJA)
930 sage: J = ComplexHermitianEJA(3)
933 sage: J.one().natural_representation()
943 sage: J = QuaternionHermitianEJA(3)
946 sage: J.one().natural_representation()
947 [1 0 0 0 0 0 0 0 0 0 0 0]
948 [0 1 0 0 0 0 0 0 0 0 0 0]
949 [0 0 1 0 0 0 0 0 0 0 0 0]
950 [0 0 0 1 0 0 0 0 0 0 0 0]
951 [0 0 0 0 1 0 0 0 0 0 0 0]
952 [0 0 0 0 0 1 0 0 0 0 0 0]
953 [0 0 0 0 0 0 1 0 0 0 0 0]
954 [0 0 0 0 0 0 0 1 0 0 0 0]
955 [0 0 0 0 0 0 0 0 1 0 0 0]
956 [0 0 0 0 0 0 0 0 0 1 0 0]
957 [0 0 0 0 0 0 0 0 0 0 1 0]
958 [0 0 0 0 0 0 0 0 0 0 0 1]
961 B
= self
.parent().natural_basis()
962 W
= self
.parent().natural_basis_space()
963 return W
.linear_combination(izip(B
,self
.to_vector()))
968 The norm of this element with respect to :meth:`inner_product`.
972 sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
973 ....: RealCartesianProductEJA)
977 sage: J = RealCartesianProductEJA(2)
978 sage: x = sum(J.gens())
984 sage: J = JordanSpinEJA(4)
985 sage: x = sum(J.gens())
990 return self
.inner_product(self
).sqrt()
995 Return the left-multiplication-by-this-element
996 operator on the ambient algebra.
1000 sage: from mjo.eja.eja_algebra import random_eja
1004 sage: set_random_seed()
1005 sage: J = random_eja()
1006 sage: x,y = J.random_elements(2)
1007 sage: x.operator()(y) == x*y
1009 sage: y.operator()(x) == x*y
1014 left_mult_by_self
= lambda y
: self
*y
1015 L
= P
.module_morphism(function
=left_mult_by_self
, codomain
=P
)
1016 return FiniteDimensionalEuclideanJordanAlgebraOperator(
1022 def quadratic_representation(self
, other
=None):
1024 Return the quadratic representation of this element.
1028 sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
1033 The explicit form in the spin factor algebra is given by
1034 Alizadeh's Example 11.12::
1036 sage: set_random_seed()
1037 sage: x = JordanSpinEJA.random_instance().random_element()
1038 sage: x_vec = x.to_vector()
1039 sage: n = x_vec.degree()
1041 sage: x_bar = x_vec[1:]
1042 sage: A = matrix(QQ, 1, [x_vec.inner_product(x_vec)])
1043 sage: B = 2*x0*x_bar.row()
1044 sage: C = 2*x0*x_bar.column()
1045 sage: D = matrix.identity(QQ, n-1)
1046 sage: D = (x0^2 - x_bar.inner_product(x_bar))*D
1047 sage: D = D + 2*x_bar.tensor_product(x_bar)
1048 sage: Q = matrix.block(2,2,[A,B,C,D])
1049 sage: Q == x.quadratic_representation().matrix()
1052 Test all of the properties from Theorem 11.2 in Alizadeh::
1054 sage: set_random_seed()
1055 sage: J = random_eja()
1056 sage: x,y = J.random_elements(2)
1057 sage: Lx = x.operator()
1058 sage: Lxx = (x*x).operator()
1059 sage: Qx = x.quadratic_representation()
1060 sage: Qy = y.quadratic_representation()
1061 sage: Qxy = x.quadratic_representation(y)
1062 sage: Qex = J.one().quadratic_representation(x)
1063 sage: n = ZZ.random_element(10)
1064 sage: Qxn = (x^n).quadratic_representation()
1068 sage: 2*Qxy == (x+y).quadratic_representation() - Qx - Qy
1071 Property 2 (multiply on the right for :trac:`28272`):
1073 sage: alpha = J.base_ring().random_element()
1074 sage: (alpha*x).quadratic_representation() == Qx*(alpha^2)
1079 sage: not x.is_invertible() or ( Qx(x.inverse()) == x )
1082 sage: not x.is_invertible() or (
1085 ....: x.inverse().quadratic_representation() )
1088 sage: Qxy(J.one()) == x*y
1093 sage: not x.is_invertible() or (
1094 ....: x.quadratic_representation(x.inverse())*Qx
1095 ....: == Qx*x.quadratic_representation(x.inverse()) )
1098 sage: not x.is_invertible() or (
1099 ....: x.quadratic_representation(x.inverse())*Qx
1101 ....: 2*Lx*Qex - Qx )
1104 sage: 2*Lx*Qex - Qx == Lxx
1109 sage: Qy(x).quadratic_representation() == Qy*Qx*Qy
1119 sage: not x.is_invertible() or (
1120 ....: Qx*x.inverse().operator() == Lx )
1125 sage: not x.operator_commutes_with(y) or (
1126 ....: Qx(y)^n == Qxn(y^n) )
1132 elif not other
in self
.parent():
1133 raise TypeError("'other' must live in the same algebra")
1136 M
= other
.operator()
1137 return ( L
*M
+ M
*L
- (self
*other
).operator() )
1141 def spectral_decomposition(self
):
1143 Return the unique spectral decomposition of this element.
1147 Following Faraut and Korányi's Theorem III.1.1, we restrict this
1148 element's left-multiplication-by operator to the subalgebra it
1149 generates. We then compute the spectral decomposition of that
1150 operator, and the spectral projectors we get back must be the
1151 left-multiplication-by operators for the idempotents we
1152 seek. Thus applying them to the identity element gives us those
1155 Since the eigenvalues are required to be distinct, we take
1156 the spectral decomposition of the zero element to be zero
1157 times the identity element of the algebra (which is idempotent,
1162 sage: from mjo.eja.eja_algebra import RealSymmetricEJA
1166 The spectral decomposition of the identity is ``1`` times itself,
1167 and the spectral decomposition of zero is ``0`` times the identity::
1169 sage: J = RealSymmetricEJA(3,AA)
1172 sage: J.one().spectral_decomposition()
1174 sage: J.zero().spectral_decomposition()
1179 sage: J = RealSymmetricEJA(4,AA)
1180 sage: x = sum(J.gens())
1181 sage: sd = x.spectral_decomposition()
1186 sage: c0.inner_product(c1) == 0
1188 sage: c0.is_idempotent()
1190 sage: c1.is_idempotent()
1192 sage: c0 + c1 == J.one()
1194 sage: l0*c0 + l1*c1 == x
1199 A
= self
.subalgebra_generated_by(orthonormalize_basis
=True)
1201 for (evalue
, proj
) in A(self
).operator().spectral_decomposition():
1202 result
.append( (evalue
, proj(A
.one()).superalgebra_element()) )
1205 def subalgebra_generated_by(self
, orthonormalize_basis
=False):
1207 Return the associative subalgebra of the parent EJA generated
1210 Since our parent algebra is unital, we want "subalgebra" to mean
1211 "unital subalgebra" as well; thus the subalgebra that an element
1212 generates will itself be a Euclidean Jordan algebra after
1213 restricting the algebra operations appropriately. This is the
1214 subalgebra that Faraut and Korányi work with in section II.2, for
1219 sage: from mjo.eja.eja_algebra import random_eja
1223 This subalgebra, being composed of only powers, is associative::
1225 sage: set_random_seed()
1226 sage: x0 = random_eja().random_element()
1227 sage: A = x0.subalgebra_generated_by()
1228 sage: x,y,z = A.random_elements(3)
1229 sage: (x*y)*z == x*(y*z)
1232 Squaring in the subalgebra should work the same as in
1235 sage: set_random_seed()
1236 sage: x = random_eja().random_element()
1237 sage: A = x.subalgebra_generated_by()
1238 sage: A(x^2) == A(x)*A(x)
1241 By definition, the subalgebra generated by the zero element is
1242 the one-dimensional algebra generated by the identity
1243 element... unless the original algebra was trivial, in which
1244 case the subalgebra is trivial too::
1246 sage: set_random_seed()
1247 sage: A = random_eja().zero().subalgebra_generated_by()
1248 sage: (A.is_trivial() and A.dimension() == 0) or A.dimension() == 1
1252 return FiniteDimensionalEuclideanJordanElementSubalgebra(self
, orthonormalize_basis
)
1255 def subalgebra_idempotent(self
):
1257 Find an idempotent in the associative subalgebra I generate
1258 using Proposition 2.3.5 in Baes.
1262 sage: from mjo.eja.eja_algebra import random_eja
1266 Ensure that we can find an idempotent in a non-trivial algebra
1267 where there are non-nilpotent elements::
1269 sage: set_random_seed()
1270 sage: J = random_eja(nontrivial=True)
1271 sage: x = J.random_element()
1272 sage: while x.is_nilpotent():
1273 ....: x = J.random_element()
1274 sage: c = x.subalgebra_idempotent()
1279 if self
.parent().is_trivial():
1282 if self
.is_nilpotent():
1283 raise ValueError("this only works with non-nilpotent elements!")
1285 J
= self
.subalgebra_generated_by()
1288 # The image of the matrix of left-u^m-multiplication
1289 # will be minimal for some natural number s...
1291 minimal_dim
= J
.dimension()
1292 for i
in xrange(1, minimal_dim
):
1293 this_dim
= (u
**i
).operator().matrix().image().dimension()
1294 if this_dim
< minimal_dim
:
1295 minimal_dim
= this_dim
1298 # Now minimal_matrix should correspond to the smallest
1299 # non-zero subspace in Baes's (or really, Koecher's)
1302 # However, we need to restrict the matrix to work on the
1303 # subspace... or do we? Can't we just solve, knowing that
1304 # A(c) = u^(s+1) should have a solution in the big space,
1307 # Beware, solve_right() means that we're using COLUMN vectors.
1308 # Our FiniteDimensionalAlgebraElement superclass uses rows.
1310 A
= u_next
.operator().matrix()
1311 c
= J
.from_vector(A
.solve_right(u_next
.to_vector()))
1313 # Now c is the idempotent we want, but it still lives in the subalgebra.
1314 return c
.superalgebra_element()
1319 Return my trace, the sum of my eigenvalues.
1321 In a trivial algebra, however you want to look at it, the trace is
1322 an empty sum for which we declare the result to be zero.
1326 sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
1327 ....: RealCartesianProductEJA,
1333 sage: J = TrivialEJA()
1334 sage: J.zero().trace()
1338 sage: J = JordanSpinEJA(3)
1339 sage: x = sum(J.gens())
1345 sage: J = RealCartesianProductEJA(5)
1346 sage: J.one().trace()
1351 The trace of an element is a real number::
1353 sage: set_random_seed()
1354 sage: J = random_eja()
1355 sage: J.random_element().trace() in RLF
1363 # Special case for the trivial algebra where
1364 # the trace is an empty sum.
1365 return P
.base_ring().zero()
1367 p
= P
._charpoly
_coeff
(r
-1)
1368 # The _charpoly_coeff function already adds the factor of
1369 # -1 to ensure that _charpoly_coeff(r-1) is really what
1370 # appears in front of t^{r-1} in the charpoly. However,
1371 # we want the negative of THAT for the trace.
1372 return -p(*self
.to_vector())
1375 def trace_inner_product(self
, other
):
1377 Return the trace inner product of myself and ``other``.
1381 sage: from mjo.eja.eja_algebra import random_eja
1385 The trace inner product is commutative, bilinear, and associative::
1387 sage: set_random_seed()
1388 sage: J = random_eja()
1389 sage: x,y,z = J.random_elements(3)
1391 sage: x.trace_inner_product(y) == y.trace_inner_product(x)
1394 sage: a = J.base_ring().random_element();
1395 sage: actual = (a*(x+z)).trace_inner_product(y)
1396 sage: expected = ( a*x.trace_inner_product(y) +
1397 ....: a*z.trace_inner_product(y) )
1398 sage: actual == expected
1400 sage: actual = x.trace_inner_product(a*(y+z))
1401 sage: expected = ( a*x.trace_inner_product(y) +
1402 ....: a*x.trace_inner_product(z) )
1403 sage: actual == expected
1406 sage: (x*y).trace_inner_product(z) == y.trace_inner_product(x*z)
1410 if not other
in self
.parent():
1411 raise TypeError("'other' must live in the same algebra")
1413 return (self
*other
).trace()
1416 def trace_norm(self
):
1418 The norm of this element with respect to :meth:`trace_inner_product`.
1422 sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
1423 ....: RealCartesianProductEJA)
1427 sage: J = RealCartesianProductEJA(2)
1428 sage: x = sum(J.gens())
1429 sage: x.trace_norm()
1434 sage: J = JordanSpinEJA(4)
1435 sage: x = sum(J.gens())
1436 sage: x.trace_norm()
1440 return self
.trace_inner_product(self
).sqrt()