1 from sage
.matrix
.constructor
import matrix
2 from sage
.modules
.free_module
import VectorSpace
3 from sage
.modules
.with_basis
.indexed_element
import IndexedFreeModuleElement
5 # TODO: make this unnecessary somehow.
6 from sage
.misc
.lazy_import
import lazy_import
7 lazy_import('mjo.eja.eja_algebra', 'FiniteDimensionalEuclideanJordanAlgebra')
8 lazy_import('mjo.eja.eja_subalgebra',
9 'FiniteDimensionalEuclideanJordanElementSubalgebra')
10 from mjo
.eja
.eja_operator
import FiniteDimensionalEuclideanJordanAlgebraOperator
11 from mjo
.eja
.eja_utils
import _mat2vec
13 class FiniteDimensionalEuclideanJordanAlgebraElement(IndexedFreeModuleElement
):
15 An element of a Euclidean Jordan algebra.
20 Oh man, I should not be doing this. This hides the "disabled"
21 methods ``left_matrix`` and ``matrix`` from introspection;
22 in particular it removes them from tab-completion.
24 return filter(lambda s
: s
not in ['left_matrix', 'matrix'],
32 Return ``self`` raised to the power ``n``.
34 Jordan algebras are always power-associative; see for
35 example Faraut and Koranyi, Proposition II.1.2 (ii).
37 We have to override this because our superclass uses row
38 vectors instead of column vectors! We, on the other hand,
39 assume column vectors everywhere.
43 sage: from mjo.eja.eja_algebra import random_eja
47 The definition of `x^2` is the unambiguous `x*x`::
49 sage: set_random_seed()
50 sage: x = random_eja().random_element()
54 A few examples of power-associativity::
56 sage: set_random_seed()
57 sage: x = random_eja().random_element()
58 sage: x*(x*x)*(x*x) == x^5
60 sage: (x*x)*(x*x*x) == x^5
63 We also know that powers operator-commute (Koecher, Chapter
66 sage: set_random_seed()
67 sage: x = random_eja().random_element()
68 sage: m = ZZ.random_element(0,10)
69 sage: n = ZZ.random_element(0,10)
70 sage: Lxm = (x^m).operator()
71 sage: Lxn = (x^n).operator()
72 sage: Lxm*Lxn == Lxn*Lxm
77 return self
.parent().one()
81 return (self
.operator()**(n
-1))(self
)
84 def apply_univariate_polynomial(self
, p
):
86 Apply the univariate polynomial ``p`` to this element.
88 A priori, SageMath won't allow us to apply a univariate
89 polynomial to an element of an EJA, because we don't know
90 that EJAs are rings (they are usually not associative). Of
91 course, we know that EJAs are power-associative, so the
92 operation is ultimately kosher. This function sidesteps
93 the CAS to get the answer we want and expect.
97 sage: from mjo.eja.eja_algebra import (RealCartesianProductEJA,
102 sage: R = PolynomialRing(QQ, 't')
104 sage: p = t^4 - t^3 + 5*t - 2
105 sage: J = RealCartesianProductEJA(5)
106 sage: J.one().apply_univariate_polynomial(p) == 3*J.one()
111 We should always get back an element of the algebra::
113 sage: set_random_seed()
114 sage: p = PolynomialRing(QQ, 't').random_element()
115 sage: J = random_eja()
116 sage: x = J.random_element()
117 sage: x.apply_univariate_polynomial(p) in J
121 if len(p
.variables()) > 1:
122 raise ValueError("not a univariate polynomial")
125 # Convert the coeficcients to the parent's base ring,
126 # because a priori they might live in an (unnecessarily)
127 # larger ring for which P.sum() would fail below.
128 cs
= [ R(c
) for c
in p
.coefficients(sparse
=False) ]
129 return P
.sum( cs
[k
]*(self
**k
) for k
in range(len(cs
)) )
132 def characteristic_polynomial(self
):
134 Return the characteristic polynomial of this element.
138 sage: from mjo.eja.eja_algebra import RealCartesianProductEJA
142 The rank of `R^3` is three, and the minimal polynomial of
143 the identity element is `(t-1)` from which it follows that
144 the characteristic polynomial should be `(t-1)^3`::
146 sage: J = RealCartesianProductEJA(3)
147 sage: J.one().characteristic_polynomial()
148 t^3 - 3*t^2 + 3*t - 1
150 Likewise, the characteristic of the zero element in the
151 rank-three algebra `R^{n}` should be `t^{3}`::
153 sage: J = RealCartesianProductEJA(3)
154 sage: J.zero().characteristic_polynomial()
159 The characteristic polynomial of an element should evaluate
160 to zero on that element::
162 sage: set_random_seed()
163 sage: x = RealCartesianProductEJA(3).random_element()
164 sage: p = x.characteristic_polynomial()
165 sage: x.apply_univariate_polynomial(p)
168 The characteristic polynomials of the zero and unit elements
169 should be what we think they are in a subalgebra, too::
171 sage: J = RealCartesianProductEJA(3)
172 sage: p1 = J.one().characteristic_polynomial()
173 sage: q1 = J.zero().characteristic_polynomial()
174 sage: e0,e1,e2 = J.gens()
175 sage: A = (e0 + 2*e1 + 3*e2).subalgebra_generated_by() # dim 3
176 sage: p2 = A.one().characteristic_polynomial()
177 sage: q2 = A.zero().characteristic_polynomial()
184 p
= self
.parent().characteristic_polynomial()
185 return p(*self
.to_vector())
188 def inner_product(self
, other
):
190 Return the parent algebra's inner product of myself and ``other``.
194 sage: from mjo.eja.eja_algebra import (
195 ....: ComplexHermitianEJA,
197 ....: QuaternionHermitianEJA,
198 ....: RealSymmetricEJA,
203 The inner product in the Jordan spin algebra is the usual
204 inner product on `R^n` (this example only works because the
205 basis for the Jordan algebra is the standard basis in `R^n`)::
207 sage: J = JordanSpinEJA(3)
208 sage: x = vector(QQ,[1,2,3])
209 sage: y = vector(QQ,[4,5,6])
210 sage: x.inner_product(y)
212 sage: J.from_vector(x).inner_product(J.from_vector(y))
215 The inner product on `S^n` is `<X,Y> = trace(X*Y)`, where
216 multiplication is the usual matrix multiplication in `S^n`,
217 so the inner product of the identity matrix with itself
220 sage: J = RealSymmetricEJA(3)
221 sage: J.one().inner_product(J.one())
224 Likewise, the inner product on `C^n` is `<X,Y> =
225 Re(trace(X*Y))`, where we must necessarily take the real
226 part because the product of Hermitian matrices may not be
229 sage: J = ComplexHermitianEJA(3)
230 sage: J.one().inner_product(J.one())
233 Ditto for the quaternions::
235 sage: J = QuaternionHermitianEJA(3)
236 sage: J.one().inner_product(J.one())
241 Ensure that we can always compute an inner product, and that
242 it gives us back a real number::
244 sage: set_random_seed()
245 sage: J = random_eja()
246 sage: x,y = J.random_elements(2)
247 sage: x.inner_product(y) in RLF
253 raise TypeError("'other' must live in the same algebra")
255 return P
.inner_product(self
, other
)
258 def operator_commutes_with(self
, other
):
260 Return whether or not this element operator-commutes
265 sage: from mjo.eja.eja_algebra import random_eja
269 The definition of a Jordan algebra says that any element
270 operator-commutes with its square::
272 sage: set_random_seed()
273 sage: x = random_eja().random_element()
274 sage: x.operator_commutes_with(x^2)
279 Test Lemma 1 from Chapter III of Koecher::
281 sage: set_random_seed()
282 sage: u,v = random_eja().random_elements(2)
283 sage: lhs = u.operator_commutes_with(u*v)
284 sage: rhs = v.operator_commutes_with(u^2)
288 Test the first polarization identity from my notes, Koecher
289 Chapter III, or from Baes (2.3)::
291 sage: set_random_seed()
292 sage: x,y = random_eja().random_elements(2)
293 sage: Lx = x.operator()
294 sage: Ly = y.operator()
295 sage: Lxx = (x*x).operator()
296 sage: Lxy = (x*y).operator()
297 sage: bool(2*Lx*Lxy + Ly*Lxx == 2*Lxy*Lx + Lxx*Ly)
300 Test the second polarization identity from my notes or from
303 sage: set_random_seed()
304 sage: x,y,z = random_eja().random_elements(3)
305 sage: Lx = x.operator()
306 sage: Ly = y.operator()
307 sage: Lz = z.operator()
308 sage: Lzy = (z*y).operator()
309 sage: Lxy = (x*y).operator()
310 sage: Lxz = (x*z).operator()
311 sage: bool(Lx*Lzy + Lz*Lxy + Ly*Lxz == Lzy*Lx + Lxy*Lz + Lxz*Ly)
314 Test the third polarization identity from my notes or from
317 sage: set_random_seed()
318 sage: u,y,z = random_eja().random_elements(3)
319 sage: Lu = u.operator()
320 sage: Ly = y.operator()
321 sage: Lz = z.operator()
322 sage: Lzy = (z*y).operator()
323 sage: Luy = (u*y).operator()
324 sage: Luz = (u*z).operator()
325 sage: Luyz = (u*(y*z)).operator()
326 sage: lhs = Lu*Lzy + Lz*Luy + Ly*Luz
327 sage: rhs = Luyz + Ly*Lu*Lz + Lz*Lu*Ly
328 sage: bool(lhs == rhs)
332 if not other
in self
.parent():
333 raise TypeError("'other' must live in the same algebra")
342 Return my determinant, the product of my eigenvalues.
346 sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
351 sage: J = JordanSpinEJA(2)
352 sage: e0,e1 = J.gens()
353 sage: x = sum( J.gens() )
359 sage: J = JordanSpinEJA(3)
360 sage: e0,e1,e2 = J.gens()
361 sage: x = sum( J.gens() )
367 An element is invertible if and only if its determinant is
370 sage: set_random_seed()
371 sage: x = random_eja().random_element()
372 sage: x.is_invertible() == (x.det() != 0)
375 Ensure that the determinant is multiplicative on an associative
376 subalgebra as in Faraut and Koranyi's Proposition II.2.2::
378 sage: set_random_seed()
379 sage: J = random_eja().random_element().subalgebra_generated_by()
380 sage: x,y = J.random_elements(2)
381 sage: (x*y).det() == x.det()*y.det()
387 p
= P
._charpoly
_coeff
(0)
388 # The _charpoly_coeff function already adds the factor of
389 # -1 to ensure that _charpoly_coeff(0) is really what
390 # appears in front of t^{0} in the charpoly. However,
391 # we want (-1)^r times THAT for the determinant.
392 return ((-1)**r
)*p(*self
.to_vector())
397 Return the Jordan-multiplicative inverse of this element.
401 We appeal to the quadratic representation as in Koecher's
402 Theorem 12 in Chapter III, Section 5.
406 sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
411 The inverse in the spin factor algebra is given in Alizadeh's
414 sage: set_random_seed()
415 sage: J = JordanSpinEJA.random_instance()
416 sage: x = J.random_element()
417 sage: while not x.is_invertible():
418 ....: x = J.random_element()
419 sage: x_vec = x.to_vector()
421 sage: x_bar = x_vec[1:]
422 sage: coeff = ~(x0^2 - x_bar.inner_product(x_bar))
423 sage: inv_vec = x_vec.parent()([x0] + (-x_bar).list())
424 sage: x_inverse = coeff*inv_vec
425 sage: x.inverse() == J.from_vector(x_inverse)
430 The identity element is its own inverse::
432 sage: set_random_seed()
433 sage: J = random_eja()
434 sage: J.one().inverse() == J.one()
437 If an element has an inverse, it acts like one::
439 sage: set_random_seed()
440 sage: J = random_eja()
441 sage: x = J.random_element()
442 sage: (not x.is_invertible()) or (x.inverse()*x == J.one())
445 The inverse of the inverse is what we started with::
447 sage: set_random_seed()
448 sage: J = random_eja()
449 sage: x = J.random_element()
450 sage: (not x.is_invertible()) or (x.inverse().inverse() == x)
453 The zero element is never invertible::
455 sage: set_random_seed()
456 sage: J = random_eja().zero().inverse()
457 Traceback (most recent call last):
459 ValueError: element is not invertible
462 if not self
.is_invertible():
463 raise ValueError("element is not invertible")
465 return (~self
.quadratic_representation())(self
)
468 def is_invertible(self
):
470 Return whether or not this element is invertible.
474 The usual way to do this is to check if the determinant is
475 zero, but we need the characteristic polynomial for the
476 determinant. The minimal polynomial is a lot easier to get,
477 so we use Corollary 2 in Chapter V of Koecher to check
478 whether or not the paren't algebra's zero element is a root
479 of this element's minimal polynomial.
481 Beware that we can't use the superclass method, because it
482 relies on the algebra being associative.
486 sage: from mjo.eja.eja_algebra import random_eja
490 The identity element is always invertible::
492 sage: set_random_seed()
493 sage: J = random_eja()
494 sage: J.one().is_invertible()
497 The zero element is never invertible in a non-trivial algebra::
499 sage: set_random_seed()
500 sage: J = random_eja()
501 sage: (not J.is_trivial()) and J.zero().is_invertible()
506 if self
.parent().is_trivial():
511 # In fact, we only need to know if the constant term is non-zero,
512 # so we can pass in the field's zero element instead.
513 zero
= self
.base_ring().zero()
514 p
= self
.minimal_polynomial()
515 return not (p(zero
) == zero
)
518 def is_nilpotent(self
):
520 Return whether or not some power of this element is zero.
524 We use Theorem 5 in Chapter III of Koecher, which says that
525 an element ``x`` is nilpotent if and only if ``x.operator()``
526 is nilpotent. And it is a basic fact of linear algebra that
527 an operator on an `n`-dimensional space is nilpotent if and
528 only if, when raised to the `n`th power, it equals the zero
529 operator (for example, see Axler Corollary 8.8).
533 sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
538 sage: J = JordanSpinEJA(3)
539 sage: x = sum(J.gens())
540 sage: x.is_nilpotent()
545 The identity element is never nilpotent::
547 sage: set_random_seed()
548 sage: random_eja().one().is_nilpotent()
551 The additive identity is always nilpotent::
553 sage: set_random_seed()
554 sage: random_eja().zero().is_nilpotent()
559 zero_operator
= P
.zero().operator()
560 return self
.operator()**P
.dimension() == zero_operator
563 def is_regular(self
):
565 Return whether or not this is a regular element.
569 sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
574 The identity element always has degree one, but any element
575 linearly-independent from it is regular::
577 sage: J = JordanSpinEJA(5)
578 sage: J.one().is_regular()
580 sage: e0, e1, e2, e3, e4 = J.gens() # e0 is the identity
581 sage: for x in J.gens():
582 ....: (J.one() + x).is_regular()
591 The zero element should never be regular, unless the parent
592 algebra has dimension one::
594 sage: set_random_seed()
595 sage: J = random_eja()
596 sage: J.dimension() == 1 or not J.zero().is_regular()
599 The unit element isn't regular unless the algebra happens to
600 consist of only its scalar multiples::
602 sage: set_random_seed()
603 sage: J = random_eja()
604 sage: J.dimension() == 1 or not J.one().is_regular()
608 return self
.degree() == self
.parent().rank()
613 Return the degree of this element, which is defined to be
614 the degree of its minimal polynomial.
618 For now, we skip the messy minimal polynomial computation
619 and instead return the dimension of the vector space spanned
620 by the powers of this element. The latter is a bit more
621 straightforward to compute.
625 sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
630 sage: J = JordanSpinEJA(4)
631 sage: J.one().degree()
633 sage: e0,e1,e2,e3 = J.gens()
634 sage: (e0 - e1).degree()
637 In the spin factor algebra (of rank two), all elements that
638 aren't multiples of the identity are regular::
640 sage: set_random_seed()
641 sage: J = JordanSpinEJA.random_instance()
642 sage: x = J.random_element()
643 sage: x == x.coefficient(0)*J.one() or x.degree() == 2
648 The zero and unit elements are both of degree one::
650 sage: set_random_seed()
651 sage: J = random_eja()
652 sage: J.zero().degree()
654 sage: J.one().degree()
657 Our implementation agrees with the definition::
659 sage: set_random_seed()
660 sage: x = random_eja().random_element()
661 sage: x.degree() == x.minimal_polynomial().degree()
665 if self
.is_zero() and not self
.parent().is_trivial():
666 # The minimal polynomial of zero in a nontrivial algebra
667 # is "t"; in a trivial algebra it's "1" by convention
668 # (it's an empty product).
670 return self
.subalgebra_generated_by().dimension()
673 def left_matrix(self
):
675 Our parent class defines ``left_matrix`` and ``matrix``
676 methods whose names are misleading. We don't want them.
678 raise NotImplementedError("use operator().matrix() instead")
683 def minimal_polynomial(self
):
685 Return the minimal polynomial of this element,
686 as a function of the variable `t`.
690 We restrict ourselves to the associative subalgebra
691 generated by this element, and then return the minimal
692 polynomial of this element's operator matrix (in that
693 subalgebra). This works by Baes Proposition 2.3.16.
697 sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
698 ....: RealSymmetricEJA,
703 The minimal polynomial of the identity and zero elements are
706 sage: set_random_seed()
707 sage: J = random_eja()
708 sage: J.one().minimal_polynomial()
710 sage: J.zero().minimal_polynomial()
713 The degree of an element is (by one definition) the degree
714 of its minimal polynomial::
716 sage: set_random_seed()
717 sage: x = random_eja().random_element()
718 sage: x.degree() == x.minimal_polynomial().degree()
721 The minimal polynomial and the characteristic polynomial coincide
722 and are known (see Alizadeh, Example 11.11) for all elements of
723 the spin factor algebra that aren't scalar multiples of the
724 identity. We require the dimension of the algebra to be at least
725 two here so that said elements actually exist::
727 sage: set_random_seed()
728 sage: n_max = max(2, JordanSpinEJA._max_test_case_size())
729 sage: n = ZZ.random_element(2, n_max)
730 sage: J = JordanSpinEJA(n)
731 sage: y = J.random_element()
732 sage: while y == y.coefficient(0)*J.one():
733 ....: y = J.random_element()
734 sage: y0 = y.to_vector()[0]
735 sage: y_bar = y.to_vector()[1:]
736 sage: actual = y.minimal_polynomial()
737 sage: t = PolynomialRing(J.base_ring(),'t').gen(0)
738 sage: expected = t^2 - 2*y0*t + (y0^2 - norm(y_bar)^2)
739 sage: bool(actual == expected)
742 The minimal polynomial should always kill its element::
744 sage: set_random_seed()
745 sage: x = random_eja().random_element()
746 sage: p = x.minimal_polynomial()
747 sage: x.apply_univariate_polynomial(p)
750 The minimal polynomial is invariant under a change of basis,
751 and in particular, a re-scaling of the basis::
753 sage: set_random_seed()
754 sage: n_max = RealSymmetricEJA._max_test_case_size()
755 sage: n = ZZ.random_element(1, n_max)
756 sage: J1 = RealSymmetricEJA(n,QQ)
757 sage: J2 = RealSymmetricEJA(n,QQ,False)
758 sage: X = random_matrix(QQ,n)
759 sage: X = X*X.transpose()
762 sage: x1.minimal_polynomial() == x2.minimal_polynomial()
767 # We would generate a zero-dimensional subalgebra
768 # where the minimal polynomial would be constant.
769 # That might be correct, but only if *this* algebra
771 if not self
.parent().is_trivial():
772 # Pretty sure we know what the minimal polynomial of
773 # the zero operator is going to be. This ensures
774 # consistency of e.g. the polynomial variable returned
775 # in the "normal" case without us having to think about it.
776 return self
.operator().minimal_polynomial()
778 A
= self
.subalgebra_generated_by()
779 return A(self
).operator().minimal_polynomial()
783 def natural_representation(self
):
785 Return a more-natural representation of this element.
787 Every finite-dimensional Euclidean Jordan Algebra is a
788 direct sum of five simple algebras, four of which comprise
789 Hermitian matrices. This method returns the original
790 "natural" representation of this element as a Hermitian
791 matrix, if it has one. If not, you get the usual representation.
795 sage: from mjo.eja.eja_algebra import (ComplexHermitianEJA,
796 ....: QuaternionHermitianEJA)
800 sage: J = ComplexHermitianEJA(3)
803 sage: J.one().natural_representation()
813 sage: J = QuaternionHermitianEJA(3)
816 sage: J.one().natural_representation()
817 [1 0 0 0 0 0 0 0 0 0 0 0]
818 [0 1 0 0 0 0 0 0 0 0 0 0]
819 [0 0 1 0 0 0 0 0 0 0 0 0]
820 [0 0 0 1 0 0 0 0 0 0 0 0]
821 [0 0 0 0 1 0 0 0 0 0 0 0]
822 [0 0 0 0 0 1 0 0 0 0 0 0]
823 [0 0 0 0 0 0 1 0 0 0 0 0]
824 [0 0 0 0 0 0 0 1 0 0 0 0]
825 [0 0 0 0 0 0 0 0 1 0 0 0]
826 [0 0 0 0 0 0 0 0 0 1 0 0]
827 [0 0 0 0 0 0 0 0 0 0 1 0]
828 [0 0 0 0 0 0 0 0 0 0 0 1]
831 B
= self
.parent().natural_basis()
832 W
= self
.parent().natural_basis_space()
833 return W
.linear_combination(zip(B
,self
.to_vector()))
838 The norm of this element with respect to :meth:`inner_product`.
842 sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
843 ....: RealCartesianProductEJA)
847 sage: J = RealCartesianProductEJA(2)
848 sage: x = sum(J.gens())
854 sage: J = JordanSpinEJA(4)
855 sage: x = sum(J.gens())
860 return self
.inner_product(self
).sqrt()
865 Return the left-multiplication-by-this-element
866 operator on the ambient algebra.
870 sage: from mjo.eja.eja_algebra import random_eja
874 sage: set_random_seed()
875 sage: J = random_eja()
876 sage: x,y = J.random_elements(2)
877 sage: x.operator()(y) == x*y
879 sage: y.operator()(x) == x*y
884 left_mult_by_self
= lambda y
: self
*y
885 L
= P
.module_morphism(function
=left_mult_by_self
, codomain
=P
)
886 return FiniteDimensionalEuclideanJordanAlgebraOperator(
892 def quadratic_representation(self
, other
=None):
894 Return the quadratic representation of this element.
898 sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
903 The explicit form in the spin factor algebra is given by
904 Alizadeh's Example 11.12::
906 sage: set_random_seed()
907 sage: x = JordanSpinEJA.random_instance().random_element()
908 sage: x_vec = x.to_vector()
909 sage: n = x_vec.degree()
911 sage: x_bar = x_vec[1:]
912 sage: A = matrix(QQ, 1, [x_vec.inner_product(x_vec)])
913 sage: B = 2*x0*x_bar.row()
914 sage: C = 2*x0*x_bar.column()
915 sage: D = matrix.identity(QQ, n-1)
916 sage: D = (x0^2 - x_bar.inner_product(x_bar))*D
917 sage: D = D + 2*x_bar.tensor_product(x_bar)
918 sage: Q = matrix.block(2,2,[A,B,C,D])
919 sage: Q == x.quadratic_representation().matrix()
922 Test all of the properties from Theorem 11.2 in Alizadeh::
924 sage: set_random_seed()
925 sage: J = random_eja()
926 sage: x,y = J.random_elements(2)
927 sage: Lx = x.operator()
928 sage: Lxx = (x*x).operator()
929 sage: Qx = x.quadratic_representation()
930 sage: Qy = y.quadratic_representation()
931 sage: Qxy = x.quadratic_representation(y)
932 sage: Qex = J.one().quadratic_representation(x)
933 sage: n = ZZ.random_element(10)
934 sage: Qxn = (x^n).quadratic_representation()
938 sage: 2*Qxy == (x+y).quadratic_representation() - Qx - Qy
941 Property 2 (multiply on the right for :trac:`28272`):
943 sage: alpha = J.base_ring().random_element()
944 sage: (alpha*x).quadratic_representation() == Qx*(alpha^2)
949 sage: not x.is_invertible() or ( Qx(x.inverse()) == x )
952 sage: not x.is_invertible() or (
955 ....: x.inverse().quadratic_representation() )
958 sage: Qxy(J.one()) == x*y
963 sage: not x.is_invertible() or (
964 ....: x.quadratic_representation(x.inverse())*Qx
965 ....: == Qx*x.quadratic_representation(x.inverse()) )
968 sage: not x.is_invertible() or (
969 ....: x.quadratic_representation(x.inverse())*Qx
971 ....: 2*x.operator()*Qex - Qx )
974 sage: 2*x.operator()*Qex - Qx == Lxx
979 sage: Qy(x).quadratic_representation() == Qy*Qx*Qy
989 sage: not x.is_invertible() or (
990 ....: Qx*x.inverse().operator() == Lx )
995 sage: not x.operator_commutes_with(y) or (
996 ....: Qx(y)^n == Qxn(y^n) )
1002 elif not other
in self
.parent():
1003 raise TypeError("'other' must live in the same algebra")
1006 M
= other
.operator()
1007 return ( L
*M
+ M
*L
- (self
*other
).operator() )
1012 def subalgebra_generated_by(self
):
1014 Return the associative subalgebra of the parent EJA generated
1019 sage: from mjo.eja.eja_algebra import random_eja
1023 This subalgebra, being composed of only powers, is associative::
1025 sage: set_random_seed()
1026 sage: x0 = random_eja().random_element()
1027 sage: A = x0.subalgebra_generated_by()
1028 sage: x,y,z = A.random_elements(3)
1029 sage: (x*y)*z == x*(y*z)
1032 Squaring in the subalgebra should work the same as in
1035 sage: set_random_seed()
1036 sage: x = random_eja().random_element()
1037 sage: A = x.subalgebra_generated_by()
1038 sage: A(x^2) == A(x)*A(x)
1041 The subalgebra generated by the zero element is trivial::
1043 sage: set_random_seed()
1044 sage: A = random_eja().zero().subalgebra_generated_by()
1046 Euclidean Jordan algebra of dimension 0 over...
1051 return FiniteDimensionalEuclideanJordanElementSubalgebra(self
)
1054 def subalgebra_idempotent(self
):
1056 Find an idempotent in the associative subalgebra I generate
1057 using Proposition 2.3.5 in Baes.
1061 sage: from mjo.eja.eja_algebra import random_eja
1065 sage: set_random_seed()
1066 sage: J = random_eja()
1067 sage: x = J.random_element()
1068 sage: while x.is_nilpotent():
1069 ....: x = J.random_element()
1070 sage: c = x.subalgebra_idempotent()
1075 if self
.is_nilpotent():
1076 raise ValueError("this only works with non-nilpotent elements!")
1078 J
= self
.subalgebra_generated_by()
1081 # The image of the matrix of left-u^m-multiplication
1082 # will be minimal for some natural number s...
1084 minimal_dim
= J
.dimension()
1085 for i
in xrange(1, minimal_dim
):
1086 this_dim
= (u
**i
).operator().matrix().image().dimension()
1087 if this_dim
< minimal_dim
:
1088 minimal_dim
= this_dim
1091 # Now minimal_matrix should correspond to the smallest
1092 # non-zero subspace in Baes's (or really, Koecher's)
1095 # However, we need to restrict the matrix to work on the
1096 # subspace... or do we? Can't we just solve, knowing that
1097 # A(c) = u^(s+1) should have a solution in the big space,
1100 # Beware, solve_right() means that we're using COLUMN vectors.
1101 # Our FiniteDimensionalAlgebraElement superclass uses rows.
1103 A
= u_next
.operator().matrix()
1104 c
= J
.from_vector(A
.solve_right(u_next
.to_vector()))
1106 # Now c is the idempotent we want, but it still lives in the subalgebra.
1107 return c
.superalgebra_element()
1112 Return my trace, the sum of my eigenvalues.
1116 sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
1117 ....: RealCartesianProductEJA,
1122 sage: J = JordanSpinEJA(3)
1123 sage: x = sum(J.gens())
1129 sage: J = RealCartesianProductEJA(5)
1130 sage: J.one().trace()
1135 The trace of an element is a real number::
1137 sage: set_random_seed()
1138 sage: J = random_eja()
1139 sage: J.random_element().trace() in RLF
1145 p
= P
._charpoly
_coeff
(r
-1)
1146 # The _charpoly_coeff function already adds the factor of
1147 # -1 to ensure that _charpoly_coeff(r-1) is really what
1148 # appears in front of t^{r-1} in the charpoly. However,
1149 # we want the negative of THAT for the trace.
1150 return -p(*self
.to_vector())
1153 def trace_inner_product(self
, other
):
1155 Return the trace inner product of myself and ``other``.
1159 sage: from mjo.eja.eja_algebra import random_eja
1163 The trace inner product is commutative, bilinear, and satisfies
1166 sage: set_random_seed()
1167 sage: J = random_eja()
1168 sage: x,y,z = J.random_elements(3)
1170 sage: x.trace_inner_product(y) == y.trace_inner_product(x)
1173 sage: a = J.base_ring().random_element();
1174 sage: actual = (a*(x+z)).trace_inner_product(y)
1175 sage: expected = ( a*x.trace_inner_product(y) +
1176 ....: a*z.trace_inner_product(y) )
1177 sage: actual == expected
1179 sage: actual = x.trace_inner_product(a*(y+z))
1180 sage: expected = ( a*x.trace_inner_product(y) +
1181 ....: a*x.trace_inner_product(z) )
1182 sage: actual == expected
1184 sage: # jordan axiom
1185 sage: (x*y).trace_inner_product(z) == y.trace_inner_product(x*z)
1189 if not other
in self
.parent():
1190 raise TypeError("'other' must live in the same algebra")
1192 return (self
*other
).trace()
1195 def trace_norm(self
):
1197 The norm of this element with respect to :meth:`trace_inner_product`.
1201 sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
1202 ....: RealCartesianProductEJA)
1206 sage: J = RealCartesianProductEJA(2)
1207 sage: x = sum(J.gens())
1208 sage: x.trace_norm()
1213 sage: J = JordanSpinEJA(4)
1214 sage: x = sum(J.gens())
1215 sage: x.trace_norm()
1219 return self
.trace_inner_product(self
).sqrt()