1 # -*- coding: utf-8 -*-
3 from sage
.matrix
.constructor
import matrix
4 from sage
.modules
.free_module
import VectorSpace
5 from sage
.modules
.with_basis
.indexed_element
import IndexedFreeModuleElement
7 # TODO: make this unnecessary somehow.
8 from sage
.misc
.lazy_import
import lazy_import
9 lazy_import('mjo.eja.eja_algebra', 'FiniteDimensionalEuclideanJordanAlgebra')
10 lazy_import('mjo.eja.eja_element_subalgebra',
11 'FiniteDimensionalEuclideanJordanElementSubalgebra')
12 from mjo
.eja
.eja_operator
import FiniteDimensionalEuclideanJordanAlgebraOperator
13 from mjo
.eja
.eja_utils
import _mat2vec
15 class FiniteDimensionalEuclideanJordanAlgebraElement(IndexedFreeModuleElement
):
17 An element of a Euclidean Jordan algebra.
22 Oh man, I should not be doing this. This hides the "disabled"
23 methods ``left_matrix`` and ``matrix`` from introspection;
24 in particular it removes them from tab-completion.
26 return filter(lambda s
: s
not in ['left_matrix', 'matrix'],
34 Return ``self`` raised to the power ``n``.
36 Jordan algebras are always power-associative; see for
37 example Faraut and Korányi, Proposition II.1.2 (ii).
39 We have to override this because our superclass uses row
40 vectors instead of column vectors! We, on the other hand,
41 assume column vectors everywhere.
45 sage: from mjo.eja.eja_algebra import random_eja
49 The definition of `x^2` is the unambiguous `x*x`::
51 sage: set_random_seed()
52 sage: x = random_eja().random_element()
56 A few examples of power-associativity::
58 sage: set_random_seed()
59 sage: x = random_eja().random_element()
60 sage: x*(x*x)*(x*x) == x^5
62 sage: (x*x)*(x*x*x) == x^5
65 We also know that powers operator-commute (Koecher, Chapter
68 sage: set_random_seed()
69 sage: x = random_eja().random_element()
70 sage: m = ZZ.random_element(0,10)
71 sage: n = ZZ.random_element(0,10)
72 sage: Lxm = (x^m).operator()
73 sage: Lxn = (x^n).operator()
74 sage: Lxm*Lxn == Lxn*Lxm
79 return self
.parent().one()
83 return (self
**(n
-1))*self
86 def apply_univariate_polynomial(self
, p
):
88 Apply the univariate polynomial ``p`` to this element.
90 A priori, SageMath won't allow us to apply a univariate
91 polynomial to an element of an EJA, because we don't know
92 that EJAs are rings (they are usually not associative). Of
93 course, we know that EJAs are power-associative, so the
94 operation is ultimately kosher. This function sidesteps
95 the CAS to get the answer we want and expect.
99 sage: from mjo.eja.eja_algebra import (HadamardEJA,
104 sage: R = PolynomialRing(QQ, 't')
106 sage: p = t^4 - t^3 + 5*t - 2
107 sage: J = HadamardEJA(5)
108 sage: J.one().apply_univariate_polynomial(p) == 3*J.one()
113 We should always get back an element of the algebra::
115 sage: set_random_seed()
116 sage: p = PolynomialRing(AA, 't').random_element()
117 sage: J = random_eja()
118 sage: x = J.random_element()
119 sage: x.apply_univariate_polynomial(p) in J
123 if len(p
.variables()) > 1:
124 raise ValueError("not a univariate polynomial")
127 # Convert the coeficcients to the parent's base ring,
128 # because a priori they might live in an (unnecessarily)
129 # larger ring for which P.sum() would fail below.
130 cs
= [ R(c
) for c
in p
.coefficients(sparse
=False) ]
131 return P
.sum( cs
[k
]*(self
**k
) for k
in range(len(cs
)) )
134 def characteristic_polynomial(self
):
136 Return the characteristic polynomial of this element.
140 sage: from mjo.eja.eja_algebra import HadamardEJA
144 The rank of `R^3` is three, and the minimal polynomial of
145 the identity element is `(t-1)` from which it follows that
146 the characteristic polynomial should be `(t-1)^3`::
148 sage: J = HadamardEJA(3)
149 sage: J.one().characteristic_polynomial()
150 t^3 - 3*t^2 + 3*t - 1
152 Likewise, the characteristic of the zero element in the
153 rank-three algebra `R^{n}` should be `t^{3}`::
155 sage: J = HadamardEJA(3)
156 sage: J.zero().characteristic_polynomial()
161 The characteristic polynomial of an element should evaluate
162 to zero on that element::
164 sage: set_random_seed()
165 sage: x = HadamardEJA(3).random_element()
166 sage: p = x.characteristic_polynomial()
167 sage: x.apply_univariate_polynomial(p)
170 The characteristic polynomials of the zero and unit elements
171 should be what we think they are in a subalgebra, too::
173 sage: J = HadamardEJA(3)
174 sage: p1 = J.one().characteristic_polynomial()
175 sage: q1 = J.zero().characteristic_polynomial()
176 sage: e0,e1,e2 = J.gens()
177 sage: A = (e0 + 2*e1 + 3*e2).subalgebra_generated_by() # dim 3
178 sage: p2 = A.one().characteristic_polynomial()
179 sage: q2 = A.zero().characteristic_polynomial()
186 p
= self
.parent().characteristic_polynomial()
187 return p(*self
.to_vector())
190 def inner_product(self
, other
):
192 Return the parent algebra's inner product of myself and ``other``.
196 sage: from mjo.eja.eja_algebra import (
197 ....: ComplexHermitianEJA,
199 ....: QuaternionHermitianEJA,
200 ....: RealSymmetricEJA,
205 The inner product in the Jordan spin algebra is the usual
206 inner product on `R^n` (this example only works because the
207 basis for the Jordan algebra is the standard basis in `R^n`)::
209 sage: J = JordanSpinEJA(3)
210 sage: x = vector(QQ,[1,2,3])
211 sage: y = vector(QQ,[4,5,6])
212 sage: x.inner_product(y)
214 sage: J.from_vector(x).inner_product(J.from_vector(y))
217 The inner product on `S^n` is `<X,Y> = trace(X*Y)`, where
218 multiplication is the usual matrix multiplication in `S^n`,
219 so the inner product of the identity matrix with itself
222 sage: J = RealSymmetricEJA(3)
223 sage: J.one().inner_product(J.one())
226 Likewise, the inner product on `C^n` is `<X,Y> =
227 Re(trace(X*Y))`, where we must necessarily take the real
228 part because the product of Hermitian matrices may not be
231 sage: J = ComplexHermitianEJA(3)
232 sage: J.one().inner_product(J.one())
235 Ditto for the quaternions::
237 sage: J = QuaternionHermitianEJA(3)
238 sage: J.one().inner_product(J.one())
243 Ensure that we can always compute an inner product, and that
244 it gives us back a real number::
246 sage: set_random_seed()
247 sage: J = random_eja()
248 sage: x,y = J.random_elements(2)
249 sage: x.inner_product(y) in RLF
255 raise TypeError("'other' must live in the same algebra")
257 return P
.inner_product(self
, other
)
260 def operator_commutes_with(self
, other
):
262 Return whether or not this element operator-commutes
267 sage: from mjo.eja.eja_algebra import random_eja
271 The definition of a Jordan algebra says that any element
272 operator-commutes with its square::
274 sage: set_random_seed()
275 sage: x = random_eja().random_element()
276 sage: x.operator_commutes_with(x^2)
281 Test Lemma 1 from Chapter III of Koecher::
283 sage: set_random_seed()
284 sage: u,v = random_eja().random_elements(2)
285 sage: lhs = u.operator_commutes_with(u*v)
286 sage: rhs = v.operator_commutes_with(u^2)
290 Test the first polarization identity from my notes, Koecher
291 Chapter III, or from Baes (2.3)::
293 sage: set_random_seed()
294 sage: x,y = random_eja().random_elements(2)
295 sage: Lx = x.operator()
296 sage: Ly = y.operator()
297 sage: Lxx = (x*x).operator()
298 sage: Lxy = (x*y).operator()
299 sage: bool(2*Lx*Lxy + Ly*Lxx == 2*Lxy*Lx + Lxx*Ly)
302 Test the second polarization identity from my notes or from
305 sage: set_random_seed()
306 sage: x,y,z = random_eja().random_elements(3)
307 sage: Lx = x.operator()
308 sage: Ly = y.operator()
309 sage: Lz = z.operator()
310 sage: Lzy = (z*y).operator()
311 sage: Lxy = (x*y).operator()
312 sage: Lxz = (x*z).operator()
313 sage: bool(Lx*Lzy + Lz*Lxy + Ly*Lxz == Lzy*Lx + Lxy*Lz + Lxz*Ly)
316 Test the third polarization identity from my notes or from
319 sage: set_random_seed()
320 sage: u,y,z = random_eja().random_elements(3)
321 sage: Lu = u.operator()
322 sage: Ly = y.operator()
323 sage: Lz = z.operator()
324 sage: Lzy = (z*y).operator()
325 sage: Luy = (u*y).operator()
326 sage: Luz = (u*z).operator()
327 sage: Luyz = (u*(y*z)).operator()
328 sage: lhs = Lu*Lzy + Lz*Luy + Ly*Luz
329 sage: rhs = Luyz + Ly*Lu*Lz + Lz*Lu*Ly
330 sage: bool(lhs == rhs)
334 if not other
in self
.parent():
335 raise TypeError("'other' must live in the same algebra")
344 Return my determinant, the product of my eigenvalues.
348 sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
354 sage: J = JordanSpinEJA(2)
355 sage: e0,e1 = J.gens()
356 sage: x = sum( J.gens() )
362 sage: J = JordanSpinEJA(3)
363 sage: e0,e1,e2 = J.gens()
364 sage: x = sum( J.gens() )
368 The determinant of the sole element in the rank-zero trivial
369 algebra is ``1``, by three paths of reasoning. First, its
370 characteristic polynomial is a constant ``1``, so the constant
371 term in that polynomial is ``1``. Second, the characteristic
372 polynomial evaluated at zero is again ``1``. And finally, the
373 (empty) product of its eigenvalues is likewise just unity::
375 sage: J = TrivialEJA()
381 An element is invertible if and only if its determinant is
384 sage: set_random_seed()
385 sage: x = random_eja().random_element()
386 sage: x.is_invertible() == (x.det() != 0)
389 Ensure that the determinant is multiplicative on an associative
390 subalgebra as in Faraut and Korányi's Proposition II.2.2::
392 sage: set_random_seed()
393 sage: J = random_eja().random_element().subalgebra_generated_by()
394 sage: x,y = J.random_elements(2)
395 sage: (x*y).det() == x.det()*y.det()
402 # Special case, since we don't get the a0=1
403 # coefficient when the rank of the algebra
405 return P
.base_ring().one()
407 p
= P
._charpoly
_coefficients
()[0]
408 # The _charpoly_coeff function already adds the factor of -1
409 # to ensure that _charpoly_coefficients()[0] is really what
410 # appears in front of t^{0} in the charpoly. However, we want
411 # (-1)^r times THAT for the determinant.
412 return ((-1)**r
)*p(*self
.to_vector())
417 Return the Jordan-multiplicative inverse of this element.
421 We appeal to the quadratic representation as in Koecher's
422 Theorem 12 in Chapter III, Section 5.
426 sage: from mjo.eja.eja_algebra import (ComplexHermitianEJA,
432 The inverse in the spin factor algebra is given in Alizadeh's
435 sage: set_random_seed()
436 sage: J = JordanSpinEJA.random_instance()
437 sage: x = J.random_element()
438 sage: while not x.is_invertible():
439 ....: x = J.random_element()
440 sage: x_vec = x.to_vector()
442 sage: x_bar = x_vec[1:]
443 sage: coeff = ~(x0^2 - x_bar.inner_product(x_bar))
444 sage: inv_vec = x_vec.parent()([x0] + (-x_bar).list())
445 sage: x_inverse = coeff*inv_vec
446 sage: x.inverse() == J.from_vector(x_inverse)
449 Trying to invert a non-invertible element throws an error:
451 sage: JordanSpinEJA(3).zero().inverse()
452 Traceback (most recent call last):
454 ValueError: element is not invertible
458 The identity element is its own inverse::
460 sage: set_random_seed()
461 sage: J = random_eja()
462 sage: J.one().inverse() == J.one()
465 If an element has an inverse, it acts like one::
467 sage: set_random_seed()
468 sage: J = random_eja()
469 sage: x = J.random_element()
470 sage: (not x.is_invertible()) or (x.inverse()*x == J.one())
473 The inverse of the inverse is what we started with::
475 sage: set_random_seed()
476 sage: J = random_eja()
477 sage: x = J.random_element()
478 sage: (not x.is_invertible()) or (x.inverse().inverse() == x)
481 Proposition II.2.3 in Faraut and Korányi says that the inverse
482 of an element is the inverse of its left-multiplication operator
483 applied to the algebra's identity, when that inverse exists::
485 sage: set_random_seed()
486 sage: J = random_eja()
487 sage: x = J.random_element()
488 sage: (not x.operator().is_invertible()) or (
489 ....: x.operator().inverse()(J.one()) == x.inverse() )
492 Proposition II.2.4 in Faraut and Korányi gives a formula for
493 the inverse based on the characteristic polynomial and the
494 Cayley-Hamilton theorem for Euclidean Jordan algebras::
496 sage: set_random_seed()
497 sage: J = ComplexHermitianEJA(3)
498 sage: x = J.random_element()
499 sage: while not x.is_invertible():
500 ....: x = J.random_element()
502 sage: a = x.characteristic_polynomial().coefficients(sparse=False)
503 sage: expected = (-1)^(r+1)/x.det()
504 sage: expected *= sum( a[i+1]*x^i for i in range(r) )
505 sage: x.inverse() == expected
509 if not self
.is_invertible():
510 raise ValueError("element is not invertible")
512 return (~self
.quadratic_representation())(self
)
515 def is_invertible(self
):
517 Return whether or not this element is invertible.
521 The usual way to do this is to check if the determinant is
522 zero, but we need the characteristic polynomial for the
523 determinant. The minimal polynomial is a lot easier to get,
524 so we use Corollary 2 in Chapter V of Koecher to check
525 whether or not the paren't algebra's zero element is a root
526 of this element's minimal polynomial.
528 Beware that we can't use the superclass method, because it
529 relies on the algebra being associative.
533 sage: from mjo.eja.eja_algebra import random_eja
537 The identity element is always invertible::
539 sage: set_random_seed()
540 sage: J = random_eja()
541 sage: J.one().is_invertible()
544 The zero element is never invertible in a non-trivial algebra::
546 sage: set_random_seed()
547 sage: J = random_eja()
548 sage: (not J.is_trivial()) and J.zero().is_invertible()
553 if self
.parent().is_trivial():
558 # In fact, we only need to know if the constant term is non-zero,
559 # so we can pass in the field's zero element instead.
560 zero
= self
.base_ring().zero()
561 p
= self
.minimal_polynomial()
562 return not (p(zero
) == zero
)
565 def is_primitive_idempotent(self
):
567 Return whether or not this element is a primitive (or minimal)
570 A primitive idempotent is a non-zero idempotent that is not
571 the sum of two other non-zero idempotents. Remark 2.7.15 in
572 Baes shows that this is what he refers to as a "minimal
575 An element of a Euclidean Jordan algebra is a minimal idempotent
576 if it :meth:`is_idempotent` and if its Peirce subalgebra
577 corresponding to the eigenvalue ``1`` has dimension ``1`` (Baes,
582 sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
583 ....: RealSymmetricEJA,
589 This method is sloooooow.
593 The spectral decomposition of a non-regular element should always
594 contain at least one non-minimal idempotent::
596 sage: J = RealSymmetricEJA(3)
597 sage: x = sum(J.gens())
600 sage: [ c.is_primitive_idempotent()
601 ....: for (l,c) in x.spectral_decomposition() ]
604 On the other hand, the spectral decomposition of a regular
605 element should always be in terms of minimal idempotents::
607 sage: J = JordanSpinEJA(4)
608 sage: x = sum( i*J.gens()[i] for i in range(len(J.gens())) )
611 sage: [ c.is_primitive_idempotent()
612 ....: for (l,c) in x.spectral_decomposition() ]
617 The identity element is minimal only in an EJA of rank one::
619 sage: set_random_seed()
620 sage: J = random_eja()
621 sage: J.rank() == 1 or not J.one().is_primitive_idempotent()
624 A non-idempotent cannot be a minimal idempotent::
626 sage: set_random_seed()
627 sage: J = JordanSpinEJA(4)
628 sage: x = J.random_element()
629 sage: (not x.is_idempotent()) and x.is_primitive_idempotent()
632 Proposition 2.7.19 in Baes says that an element is a minimal
633 idempotent if and only if it's idempotent with trace equal to
636 sage: set_random_seed()
637 sage: J = JordanSpinEJA(4)
638 sage: x = J.random_element()
639 sage: expected = (x.is_idempotent() and x.trace() == 1)
640 sage: actual = x.is_primitive_idempotent()
641 sage: actual == expected
644 Primitive idempotents must be non-zero::
646 sage: set_random_seed()
647 sage: J = random_eja()
648 sage: J.zero().is_idempotent()
650 sage: J.zero().is_primitive_idempotent()
653 As a consequence of the fact that primitive idempotents must
654 be non-zero, there are no primitive idempotents in a trivial
655 Euclidean Jordan algebra::
657 sage: J = TrivialEJA()
658 sage: J.one().is_idempotent()
660 sage: J.one().is_primitive_idempotent()
664 if not self
.is_idempotent():
670 (_
,_
,J1
) = self
.parent().peirce_decomposition(self
)
671 return (J1
.dimension() == 1)
674 def is_nilpotent(self
):
676 Return whether or not some power of this element is zero.
680 We use Theorem 5 in Chapter III of Koecher, which says that
681 an element ``x`` is nilpotent if and only if ``x.operator()``
682 is nilpotent. And it is a basic fact of linear algebra that
683 an operator on an `n`-dimensional space is nilpotent if and
684 only if, when raised to the `n`th power, it equals the zero
685 operator (for example, see Axler Corollary 8.8).
689 sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
694 sage: J = JordanSpinEJA(3)
695 sage: x = sum(J.gens())
696 sage: x.is_nilpotent()
701 The identity element is never nilpotent, except in a trivial EJA::
703 sage: set_random_seed()
704 sage: J = random_eja()
705 sage: J.one().is_nilpotent() and not J.is_trivial()
708 The additive identity is always nilpotent::
710 sage: set_random_seed()
711 sage: random_eja().zero().is_nilpotent()
716 zero_operator
= P
.zero().operator()
717 return self
.operator()**P
.dimension() == zero_operator
720 def is_regular(self
):
722 Return whether or not this is a regular element.
726 sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
731 The identity element always has degree one, but any element
732 linearly-independent from it is regular::
734 sage: J = JordanSpinEJA(5)
735 sage: J.one().is_regular()
737 sage: e0, e1, e2, e3, e4 = J.gens() # e0 is the identity
738 sage: for x in J.gens():
739 ....: (J.one() + x).is_regular()
748 The zero element should never be regular, unless the parent
749 algebra has dimension less than or equal to one::
751 sage: set_random_seed()
752 sage: J = random_eja()
753 sage: J.dimension() <= 1 or not J.zero().is_regular()
756 The unit element isn't regular unless the algebra happens to
757 consist of only its scalar multiples::
759 sage: set_random_seed()
760 sage: J = random_eja()
761 sage: J.dimension() <= 1 or not J.one().is_regular()
765 return self
.degree() == self
.parent().rank()
770 Return the degree of this element, which is defined to be
771 the degree of its minimal polynomial.
775 For now, we skip the messy minimal polynomial computation
776 and instead return the dimension of the vector space spanned
777 by the powers of this element. The latter is a bit more
778 straightforward to compute.
782 sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
787 sage: J = JordanSpinEJA(4)
788 sage: J.one().degree()
790 sage: e0,e1,e2,e3 = J.gens()
791 sage: (e0 - e1).degree()
794 In the spin factor algebra (of rank two), all elements that
795 aren't multiples of the identity are regular::
797 sage: set_random_seed()
798 sage: J = JordanSpinEJA.random_instance()
799 sage: x = J.random_element()
800 sage: x == x.coefficient(0)*J.one() or x.degree() == 2
805 The zero and unit elements are both of degree one in nontrivial
808 sage: set_random_seed()
809 sage: J = random_eja()
810 sage: d = J.zero().degree()
811 sage: (J.is_trivial() and d == 0) or d == 1
813 sage: d = J.one().degree()
814 sage: (J.is_trivial() and d == 0) or d == 1
817 Our implementation agrees with the definition::
819 sage: set_random_seed()
820 sage: x = random_eja().random_element()
821 sage: x.degree() == x.minimal_polynomial().degree()
825 if self
.is_zero() and not self
.parent().is_trivial():
826 # The minimal polynomial of zero in a nontrivial algebra
827 # is "t"; in a trivial algebra it's "1" by convention
828 # (it's an empty product).
830 return self
.subalgebra_generated_by().dimension()
833 def left_matrix(self
):
835 Our parent class defines ``left_matrix`` and ``matrix``
836 methods whose names are misleading. We don't want them.
838 raise NotImplementedError("use operator().matrix() instead")
843 def minimal_polynomial(self
):
845 Return the minimal polynomial of this element,
846 as a function of the variable `t`.
850 We restrict ourselves to the associative subalgebra
851 generated by this element, and then return the minimal
852 polynomial of this element's operator matrix (in that
853 subalgebra). This works by Baes Proposition 2.3.16.
857 sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
858 ....: RealSymmetricEJA,
864 Keeping in mind that the polynomial ``1`` evaluates the identity
865 element (also the zero element) of the trivial algebra, it is clear
866 that the polynomial ``1`` is the minimal polynomial of the only
867 element in a trivial algebra::
869 sage: J = TrivialEJA()
870 sage: J.one().minimal_polynomial()
872 sage: J.zero().minimal_polynomial()
877 The minimal polynomial of the identity and zero elements are
878 always the same, except in trivial algebras where the minimal
879 polynomial of the unit/zero element is ``1``::
881 sage: set_random_seed()
882 sage: J = random_eja()
883 sage: mu = J.one().minimal_polynomial()
884 sage: t = mu.parent().gen()
885 sage: mu + int(J.is_trivial())*(t-2)
887 sage: mu = J.zero().minimal_polynomial()
888 sage: t = mu.parent().gen()
889 sage: mu + int(J.is_trivial())*(t-1)
892 The degree of an element is (by one definition) the degree
893 of its minimal polynomial::
895 sage: set_random_seed()
896 sage: x = random_eja().random_element()
897 sage: x.degree() == x.minimal_polynomial().degree()
900 The minimal polynomial and the characteristic polynomial coincide
901 and are known (see Alizadeh, Example 11.11) for all elements of
902 the spin factor algebra that aren't scalar multiples of the
903 identity. We require the dimension of the algebra to be at least
904 two here so that said elements actually exist::
906 sage: set_random_seed()
907 sage: n_max = max(2, JordanSpinEJA._max_test_case_size())
908 sage: n = ZZ.random_element(2, n_max)
909 sage: J = JordanSpinEJA(n)
910 sage: y = J.random_element()
911 sage: while y == y.coefficient(0)*J.one():
912 ....: y = J.random_element()
913 sage: y0 = y.to_vector()[0]
914 sage: y_bar = y.to_vector()[1:]
915 sage: actual = y.minimal_polynomial()
916 sage: t = PolynomialRing(J.base_ring(),'t').gen(0)
917 sage: expected = t^2 - 2*y0*t + (y0^2 - norm(y_bar)^2)
918 sage: bool(actual == expected)
921 The minimal polynomial should always kill its element::
923 sage: set_random_seed()
924 sage: x = random_eja().random_element()
925 sage: p = x.minimal_polynomial()
926 sage: x.apply_univariate_polynomial(p)
929 The minimal polynomial is invariant under a change of basis,
930 and in particular, a re-scaling of the basis::
932 sage: set_random_seed()
933 sage: n_max = RealSymmetricEJA._max_test_case_size()
934 sage: n = ZZ.random_element(1, n_max)
935 sage: J1 = RealSymmetricEJA(n)
936 sage: J2 = RealSymmetricEJA(n,normalize_basis=False)
937 sage: X = random_matrix(AA,n)
938 sage: X = X*X.transpose()
941 sage: x1.minimal_polynomial() == x2.minimal_polynomial()
946 # We would generate a zero-dimensional subalgebra
947 # where the minimal polynomial would be constant.
948 # That might be correct, but only if *this* algebra
950 if not self
.parent().is_trivial():
951 # Pretty sure we know what the minimal polynomial of
952 # the zero operator is going to be. This ensures
953 # consistency of e.g. the polynomial variable returned
954 # in the "normal" case without us having to think about it.
955 return self
.operator().minimal_polynomial()
957 A
= self
.subalgebra_generated_by()
958 return A(self
).operator().minimal_polynomial()
962 def natural_representation(self
):
964 Return a more-natural representation of this element.
966 Every finite-dimensional Euclidean Jordan Algebra is a
967 direct sum of five simple algebras, four of which comprise
968 Hermitian matrices. This method returns the original
969 "natural" representation of this element as a Hermitian
970 matrix, if it has one. If not, you get the usual representation.
974 sage: from mjo.eja.eja_algebra import (ComplexHermitianEJA,
975 ....: QuaternionHermitianEJA)
979 sage: J = ComplexHermitianEJA(3)
982 sage: J.one().natural_representation()
992 sage: J = QuaternionHermitianEJA(3)
995 sage: J.one().natural_representation()
996 [1 0 0 0 0 0 0 0 0 0 0 0]
997 [0 1 0 0 0 0 0 0 0 0 0 0]
998 [0 0 1 0 0 0 0 0 0 0 0 0]
999 [0 0 0 1 0 0 0 0 0 0 0 0]
1000 [0 0 0 0 1 0 0 0 0 0 0 0]
1001 [0 0 0 0 0 1 0 0 0 0 0 0]
1002 [0 0 0 0 0 0 1 0 0 0 0 0]
1003 [0 0 0 0 0 0 0 1 0 0 0 0]
1004 [0 0 0 0 0 0 0 0 1 0 0 0]
1005 [0 0 0 0 0 0 0 0 0 1 0 0]
1006 [0 0 0 0 0 0 0 0 0 0 1 0]
1007 [0 0 0 0 0 0 0 0 0 0 0 1]
1010 B
= self
.parent().natural_basis()
1011 W
= self
.parent().natural_basis_space()
1012 return W
.linear_combination(zip(B
,self
.to_vector()))
1017 The norm of this element with respect to :meth:`inner_product`.
1021 sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
1026 sage: J = HadamardEJA(2)
1027 sage: x = sum(J.gens())
1033 sage: J = JordanSpinEJA(4)
1034 sage: x = sum(J.gens())
1039 return self
.inner_product(self
).sqrt()
1044 Return the left-multiplication-by-this-element
1045 operator on the ambient algebra.
1049 sage: from mjo.eja.eja_algebra import random_eja
1053 sage: set_random_seed()
1054 sage: J = random_eja()
1055 sage: x,y = J.random_elements(2)
1056 sage: x.operator()(y) == x*y
1058 sage: y.operator()(x) == x*y
1063 left_mult_by_self
= lambda y
: self
*y
1064 L
= P
.module_morphism(function
=left_mult_by_self
, codomain
=P
)
1065 return FiniteDimensionalEuclideanJordanAlgebraOperator(
1071 def quadratic_representation(self
, other
=None):
1073 Return the quadratic representation of this element.
1077 sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
1082 The explicit form in the spin factor algebra is given by
1083 Alizadeh's Example 11.12::
1085 sage: set_random_seed()
1086 sage: x = JordanSpinEJA.random_instance().random_element()
1087 sage: x_vec = x.to_vector()
1088 sage: n = x_vec.degree()
1090 sage: x_bar = x_vec[1:]
1091 sage: A = matrix(AA, 1, [x_vec.inner_product(x_vec)])
1092 sage: B = 2*x0*x_bar.row()
1093 sage: C = 2*x0*x_bar.column()
1094 sage: D = matrix.identity(AA, n-1)
1095 sage: D = (x0^2 - x_bar.inner_product(x_bar))*D
1096 sage: D = D + 2*x_bar.tensor_product(x_bar)
1097 sage: Q = matrix.block(2,2,[A,B,C,D])
1098 sage: Q == x.quadratic_representation().matrix()
1101 Test all of the properties from Theorem 11.2 in Alizadeh::
1103 sage: set_random_seed()
1104 sage: J = random_eja()
1105 sage: x,y = J.random_elements(2)
1106 sage: Lx = x.operator()
1107 sage: Lxx = (x*x).operator()
1108 sage: Qx = x.quadratic_representation()
1109 sage: Qy = y.quadratic_representation()
1110 sage: Qxy = x.quadratic_representation(y)
1111 sage: Qex = J.one().quadratic_representation(x)
1112 sage: n = ZZ.random_element(10)
1113 sage: Qxn = (x^n).quadratic_representation()
1117 sage: 2*Qxy == (x+y).quadratic_representation() - Qx - Qy
1120 Property 2 (multiply on the right for :trac:`28272`):
1122 sage: alpha = J.base_ring().random_element()
1123 sage: (alpha*x).quadratic_representation() == Qx*(alpha^2)
1128 sage: not x.is_invertible() or ( Qx(x.inverse()) == x )
1131 sage: not x.is_invertible() or (
1134 ....: x.inverse().quadratic_representation() )
1137 sage: Qxy(J.one()) == x*y
1142 sage: not x.is_invertible() or (
1143 ....: x.quadratic_representation(x.inverse())*Qx
1144 ....: == Qx*x.quadratic_representation(x.inverse()) )
1147 sage: not x.is_invertible() or (
1148 ....: x.quadratic_representation(x.inverse())*Qx
1150 ....: 2*Lx*Qex - Qx )
1153 sage: 2*Lx*Qex - Qx == Lxx
1158 sage: Qy(x).quadratic_representation() == Qy*Qx*Qy
1168 sage: not x.is_invertible() or (
1169 ....: Qx*x.inverse().operator() == Lx )
1174 sage: not x.operator_commutes_with(y) or (
1175 ....: Qx(y)^n == Qxn(y^n) )
1181 elif not other
in self
.parent():
1182 raise TypeError("'other' must live in the same algebra")
1185 M
= other
.operator()
1186 return ( L
*M
+ M
*L
- (self
*other
).operator() )
1190 def spectral_decomposition(self
):
1192 Return the unique spectral decomposition of this element.
1196 Following Faraut and Korányi's Theorem III.1.1, we restrict this
1197 element's left-multiplication-by operator to the subalgebra it
1198 generates. We then compute the spectral decomposition of that
1199 operator, and the spectral projectors we get back must be the
1200 left-multiplication-by operators for the idempotents we
1201 seek. Thus applying them to the identity element gives us those
1204 Since the eigenvalues are required to be distinct, we take
1205 the spectral decomposition of the zero element to be zero
1206 times the identity element of the algebra (which is idempotent,
1211 sage: from mjo.eja.eja_algebra import RealSymmetricEJA
1215 The spectral decomposition of the identity is ``1`` times itself,
1216 and the spectral decomposition of zero is ``0`` times the identity::
1218 sage: J = RealSymmetricEJA(3)
1221 sage: J.one().spectral_decomposition()
1223 sage: J.zero().spectral_decomposition()
1228 sage: J = RealSymmetricEJA(4)
1229 sage: x = sum(J.gens())
1230 sage: sd = x.spectral_decomposition()
1235 sage: c0.inner_product(c1) == 0
1237 sage: c0.is_idempotent()
1239 sage: c1.is_idempotent()
1241 sage: c0 + c1 == J.one()
1243 sage: l0*c0 + l1*c1 == x
1246 The spectral decomposition should work in subalgebras, too::
1248 sage: J = RealSymmetricEJA(4)
1249 sage: (e0, e1, e2, e3, e4, e5, e6, e7, e8, e9) = J.gens()
1250 sage: A = 2*e5 - 2*e8
1251 sage: (lambda1, c1) = A.spectral_decomposition()[1]
1252 sage: (J0, J5, J1) = J.peirce_decomposition(c1)
1253 sage: (f0, f1, f2) = J1.gens()
1254 sage: f0.spectral_decomposition()
1255 [(0, 1.000000000000000?*f2), (1, 1.000000000000000?*f0)]
1258 A
= self
.subalgebra_generated_by(orthonormalize_basis
=True)
1260 for (evalue
, proj
) in A(self
).operator().spectral_decomposition():
1261 result
.append( (evalue
, proj(A
.one()).superalgebra_element()) )
1264 def subalgebra_generated_by(self
, orthonormalize_basis
=False):
1266 Return the associative subalgebra of the parent EJA generated
1269 Since our parent algebra is unital, we want "subalgebra" to mean
1270 "unital subalgebra" as well; thus the subalgebra that an element
1271 generates will itself be a Euclidean Jordan algebra after
1272 restricting the algebra operations appropriately. This is the
1273 subalgebra that Faraut and Korányi work with in section II.2, for
1278 sage: from mjo.eja.eja_algebra import random_eja
1282 This subalgebra, being composed of only powers, is associative::
1284 sage: set_random_seed()
1285 sage: x0 = random_eja().random_element()
1286 sage: A = x0.subalgebra_generated_by()
1287 sage: x,y,z = A.random_elements(3)
1288 sage: (x*y)*z == x*(y*z)
1291 Squaring in the subalgebra should work the same as in
1294 sage: set_random_seed()
1295 sage: x = random_eja().random_element()
1296 sage: A = x.subalgebra_generated_by()
1297 sage: A(x^2) == A(x)*A(x)
1300 By definition, the subalgebra generated by the zero element is
1301 the one-dimensional algebra generated by the identity
1302 element... unless the original algebra was trivial, in which
1303 case the subalgebra is trivial too::
1305 sage: set_random_seed()
1306 sage: A = random_eja().zero().subalgebra_generated_by()
1307 sage: (A.is_trivial() and A.dimension() == 0) or A.dimension() == 1
1311 return FiniteDimensionalEuclideanJordanElementSubalgebra(self
, orthonormalize_basis
)
1314 def subalgebra_idempotent(self
):
1316 Find an idempotent in the associative subalgebra I generate
1317 using Proposition 2.3.5 in Baes.
1321 sage: from mjo.eja.eja_algebra import random_eja
1325 Ensure that we can find an idempotent in a non-trivial algebra
1326 where there are non-nilpotent elements, or that we get the dumb
1327 solution in the trivial algebra::
1329 sage: set_random_seed()
1330 sage: J = random_eja()
1331 sage: x = J.random_element()
1332 sage: while x.is_nilpotent() and not J.is_trivial():
1333 ....: x = J.random_element()
1334 sage: c = x.subalgebra_idempotent()
1339 if self
.parent().is_trivial():
1342 if self
.is_nilpotent():
1343 raise ValueError("this only works with non-nilpotent elements!")
1345 J
= self
.subalgebra_generated_by()
1348 # The image of the matrix of left-u^m-multiplication
1349 # will be minimal for some natural number s...
1351 minimal_dim
= J
.dimension()
1352 for i
in range(1, minimal_dim
):
1353 this_dim
= (u
**i
).operator().matrix().image().dimension()
1354 if this_dim
< minimal_dim
:
1355 minimal_dim
= this_dim
1358 # Now minimal_matrix should correspond to the smallest
1359 # non-zero subspace in Baes's (or really, Koecher's)
1362 # However, we need to restrict the matrix to work on the
1363 # subspace... or do we? Can't we just solve, knowing that
1364 # A(c) = u^(s+1) should have a solution in the big space,
1367 # Beware, solve_right() means that we're using COLUMN vectors.
1368 # Our FiniteDimensionalAlgebraElement superclass uses rows.
1370 A
= u_next
.operator().matrix()
1371 c
= J
.from_vector(A
.solve_right(u_next
.to_vector()))
1373 # Now c is the idempotent we want, but it still lives in the subalgebra.
1374 return c
.superalgebra_element()
1379 Return my trace, the sum of my eigenvalues.
1381 In a trivial algebra, however you want to look at it, the trace is
1382 an empty sum for which we declare the result to be zero.
1386 sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
1393 sage: J = TrivialEJA()
1394 sage: J.zero().trace()
1398 sage: J = JordanSpinEJA(3)
1399 sage: x = sum(J.gens())
1405 sage: J = HadamardEJA(5)
1406 sage: J.one().trace()
1411 The trace of an element is a real number::
1413 sage: set_random_seed()
1414 sage: J = random_eja()
1415 sage: J.random_element().trace() in RLF
1423 # Special case for the trivial algebra where
1424 # the trace is an empty sum.
1425 return P
.base_ring().zero()
1427 p
= P
._charpoly
_coefficients
()[r
-1]
1428 # The _charpoly_coeff function already adds the factor of
1429 # -1 to ensure that _charpoly_coeff(r-1) is really what
1430 # appears in front of t^{r-1} in the charpoly. However,
1431 # we want the negative of THAT for the trace.
1432 return -p(*self
.to_vector())
1435 def trace_inner_product(self
, other
):
1437 Return the trace inner product of myself and ``other``.
1441 sage: from mjo.eja.eja_algebra import random_eja
1445 The trace inner product is commutative, bilinear, and associative::
1447 sage: set_random_seed()
1448 sage: J = random_eja()
1449 sage: x,y,z = J.random_elements(3)
1451 sage: x.trace_inner_product(y) == y.trace_inner_product(x)
1454 sage: a = J.base_ring().random_element();
1455 sage: actual = (a*(x+z)).trace_inner_product(y)
1456 sage: expected = ( a*x.trace_inner_product(y) +
1457 ....: a*z.trace_inner_product(y) )
1458 sage: actual == expected
1460 sage: actual = x.trace_inner_product(a*(y+z))
1461 sage: expected = ( a*x.trace_inner_product(y) +
1462 ....: a*x.trace_inner_product(z) )
1463 sage: actual == expected
1466 sage: (x*y).trace_inner_product(z) == y.trace_inner_product(x*z)
1470 if not other
in self
.parent():
1471 raise TypeError("'other' must live in the same algebra")
1473 return (self
*other
).trace()
1476 def trace_norm(self
):
1478 The norm of this element with respect to :meth:`trace_inner_product`.
1482 sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
1487 sage: J = HadamardEJA(2)
1488 sage: x = sum(J.gens())
1489 sage: x.trace_norm()
1494 sage: J = JordanSpinEJA(4)
1495 sage: x = sum(J.gens())
1496 sage: x.trace_norm()
1500 return self
.trace_inner_product(self
).sqrt()