]> gitweb.michael.orlitzky.com - sage.d.git/blob - mjo/eja/eja_element.py
eja: declare a utf-8 encoding and use it to write Korányi.
[sage.d.git] / mjo / eja / eja_element.py
1 # -*- coding: utf-8 -*-
2
3 from itertools import izip
4
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
8
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_subalgebra',
13 'FiniteDimensionalEuclideanJordanElementSubalgebra')
14 from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator
15 from mjo.eja.eja_utils import _mat2vec
16
17 class FiniteDimensionalEuclideanJordanAlgebraElement(IndexedFreeModuleElement):
18 """
19 An element of a Euclidean Jordan algebra.
20 """
21
22 def __dir__(self):
23 """
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.
27 """
28 return filter(lambda s: s not in ['left_matrix', 'matrix'],
29 dir(self.__class__) )
30
31
32
33
34 def __pow__(self, n):
35 """
36 Return ``self`` raised to the power ``n``.
37
38 Jordan algebras are always power-associative; see for
39 example Faraut and Korányi, Proposition II.1.2 (ii).
40
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.
44
45 SETUP::
46
47 sage: from mjo.eja.eja_algebra import random_eja
48
49 TESTS:
50
51 The definition of `x^2` is the unambiguous `x*x`::
52
53 sage: set_random_seed()
54 sage: x = random_eja().random_element()
55 sage: x*x == (x^2)
56 True
57
58 A few examples of power-associativity::
59
60 sage: set_random_seed()
61 sage: x = random_eja().random_element()
62 sage: x*(x*x)*(x*x) == x^5
63 True
64 sage: (x*x)*(x*x*x) == x^5
65 True
66
67 We also know that powers operator-commute (Koecher, Chapter
68 III, Corollary 1)::
69
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
77 True
78
79 """
80 if n == 0:
81 return self.parent().one()
82 elif n == 1:
83 return self
84 else:
85 return (self**(n-1))*self
86
87
88 def apply_univariate_polynomial(self, p):
89 """
90 Apply the univariate polynomial ``p`` to this element.
91
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.
98
99 SETUP::
100
101 sage: from mjo.eja.eja_algebra import (RealCartesianProductEJA,
102 ....: random_eja)
103
104 EXAMPLES::
105
106 sage: R = PolynomialRing(QQ, 't')
107 sage: t = R.gen(0)
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()
111 True
112
113 TESTS:
114
115 We should always get back an element of the algebra::
116
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
122 True
123
124 """
125 if len(p.variables()) > 1:
126 raise ValueError("not a univariate polynomial")
127 P = self.parent()
128 R = P.base_ring()
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)) )
134
135
136 def characteristic_polynomial(self):
137 """
138 Return the characteristic polynomial of this element.
139
140 SETUP::
141
142 sage: from mjo.eja.eja_algebra import RealCartesianProductEJA
143
144 EXAMPLES:
145
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`::
149
150 sage: J = RealCartesianProductEJA(3)
151 sage: J.one().characteristic_polynomial()
152 t^3 - 3*t^2 + 3*t - 1
153
154 Likewise, the characteristic of the zero element in the
155 rank-three algebra `R^{n}` should be `t^{3}`::
156
157 sage: J = RealCartesianProductEJA(3)
158 sage: J.zero().characteristic_polynomial()
159 t^3
160
161 TESTS:
162
163 The characteristic polynomial of an element should evaluate
164 to zero on that element::
165
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)
170 0
171
172 The characteristic polynomials of the zero and unit elements
173 should be what we think they are in a subalgebra, too::
174
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()
182 sage: p1 == p2
183 True
184 sage: q1 == q2
185 True
186
187 """
188 p = self.parent().characteristic_polynomial()
189 return p(*self.to_vector())
190
191
192 def inner_product(self, other):
193 """
194 Return the parent algebra's inner product of myself and ``other``.
195
196 SETUP::
197
198 sage: from mjo.eja.eja_algebra import (
199 ....: ComplexHermitianEJA,
200 ....: JordanSpinEJA,
201 ....: QuaternionHermitianEJA,
202 ....: RealSymmetricEJA,
203 ....: random_eja)
204
205 EXAMPLES:
206
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`)::
210
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)
215 32
216 sage: J.from_vector(x).inner_product(J.from_vector(y))
217 32
218
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
222 should be the `n`::
223
224 sage: J = RealSymmetricEJA(3)
225 sage: J.one().inner_product(J.one())
226 3
227
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
231 Hermitian::
232
233 sage: J = ComplexHermitianEJA(3)
234 sage: J.one().inner_product(J.one())
235 3
236
237 Ditto for the quaternions::
238
239 sage: J = QuaternionHermitianEJA(3)
240 sage: J.one().inner_product(J.one())
241 3
242
243 TESTS:
244
245 Ensure that we can always compute an inner product, and that
246 it gives us back a real number::
247
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
252 True
253
254 """
255 P = self.parent()
256 if not other in P:
257 raise TypeError("'other' must live in the same algebra")
258
259 return P.inner_product(self, other)
260
261
262 def operator_commutes_with(self, other):
263 """
264 Return whether or not this element operator-commutes
265 with ``other``.
266
267 SETUP::
268
269 sage: from mjo.eja.eja_algebra import random_eja
270
271 EXAMPLES:
272
273 The definition of a Jordan algebra says that any element
274 operator-commutes with its square::
275
276 sage: set_random_seed()
277 sage: x = random_eja().random_element()
278 sage: x.operator_commutes_with(x^2)
279 True
280
281 TESTS:
282
283 Test Lemma 1 from Chapter III of Koecher::
284
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)
289 sage: lhs == rhs
290 True
291
292 Test the first polarization identity from my notes, Koecher
293 Chapter III, or from Baes (2.3)::
294
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)
302 True
303
304 Test the second polarization identity from my notes or from
305 Baes (2.4)::
306
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)
316 True
317
318 Test the third polarization identity from my notes or from
319 Baes (2.5)::
320
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)
333 True
334
335 """
336 if not other in self.parent():
337 raise TypeError("'other' must live in the same algebra")
338
339 A = self.operator()
340 B = other.operator()
341 return (A*B == B*A)
342
343
344 def det(self):
345 """
346 Return my determinant, the product of my eigenvalues.
347
348 SETUP::
349
350 sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
351 ....: random_eja)
352
353 EXAMPLES::
354
355 sage: J = JordanSpinEJA(2)
356 sage: e0,e1 = J.gens()
357 sage: x = sum( J.gens() )
358 sage: x.det()
359 0
360
361 ::
362
363 sage: J = JordanSpinEJA(3)
364 sage: e0,e1,e2 = J.gens()
365 sage: x = sum( J.gens() )
366 sage: x.det()
367 -1
368
369 TESTS:
370
371 An element is invertible if and only if its determinant is
372 non-zero::
373
374 sage: set_random_seed()
375 sage: x = random_eja().random_element()
376 sage: x.is_invertible() == (x.det() != 0)
377 True
378
379 Ensure that the determinant is multiplicative on an associative
380 subalgebra as in Faraut and Korányi's Proposition II.2.2::
381
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()
386 True
387
388 """
389 P = self.parent()
390 r = P.rank()
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())
397
398
399 def inverse(self):
400 """
401 Return the Jordan-multiplicative inverse of this element.
402
403 ALGORITHM:
404
405 We appeal to the quadratic representation as in Koecher's
406 Theorem 12 in Chapter III, Section 5.
407
408 SETUP::
409
410 sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
411 ....: random_eja)
412
413 EXAMPLES:
414
415 The inverse in the spin factor algebra is given in Alizadeh's
416 Example 11.11::
417
418 sage: set_random_seed()
419 sage: J = JordanSpinEJA.random_instance()
420 sage: x = J.random_element()
421 sage: while not x.is_invertible():
422 ....: x = J.random_element()
423 sage: x_vec = x.to_vector()
424 sage: x0 = x_vec[0]
425 sage: x_bar = x_vec[1:]
426 sage: coeff = ~(x0^2 - x_bar.inner_product(x_bar))
427 sage: inv_vec = x_vec.parent()([x0] + (-x_bar).list())
428 sage: x_inverse = coeff*inv_vec
429 sage: x.inverse() == J.from_vector(x_inverse)
430 True
431
432 TESTS:
433
434 The identity element is its own inverse::
435
436 sage: set_random_seed()
437 sage: J = random_eja()
438 sage: J.one().inverse() == J.one()
439 True
440
441 If an element has an inverse, it acts like one::
442
443 sage: set_random_seed()
444 sage: J = random_eja()
445 sage: x = J.random_element()
446 sage: (not x.is_invertible()) or (x.inverse()*x == J.one())
447 True
448
449 The inverse of the inverse is what we started with::
450
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().inverse() == x)
455 True
456
457 The zero element is never invertible::
458
459 sage: set_random_seed()
460 sage: J = random_eja().zero().inverse()
461 Traceback (most recent call last):
462 ...
463 ValueError: element is not invertible
464
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::
468
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() )
474 True
475
476 """
477 if not self.is_invertible():
478 raise ValueError("element is not invertible")
479
480 return (~self.quadratic_representation())(self)
481
482
483 def is_invertible(self):
484 """
485 Return whether or not this element is invertible.
486
487 ALGORITHM:
488
489 The usual way to do this is to check if the determinant is
490 zero, but we need the characteristic polynomial for the
491 determinant. The minimal polynomial is a lot easier to get,
492 so we use Corollary 2 in Chapter V of Koecher to check
493 whether or not the paren't algebra's zero element is a root
494 of this element's minimal polynomial.
495
496 Beware that we can't use the superclass method, because it
497 relies on the algebra being associative.
498
499 SETUP::
500
501 sage: from mjo.eja.eja_algebra import random_eja
502
503 TESTS:
504
505 The identity element is always invertible::
506
507 sage: set_random_seed()
508 sage: J = random_eja()
509 sage: J.one().is_invertible()
510 True
511
512 The zero element is never invertible in a non-trivial algebra::
513
514 sage: set_random_seed()
515 sage: J = random_eja()
516 sage: (not J.is_trivial()) and J.zero().is_invertible()
517 False
518
519 """
520 if self.is_zero():
521 if self.parent().is_trivial():
522 return True
523 else:
524 return False
525
526 # In fact, we only need to know if the constant term is non-zero,
527 # so we can pass in the field's zero element instead.
528 zero = self.base_ring().zero()
529 p = self.minimal_polynomial()
530 return not (p(zero) == zero)
531
532
533 def is_nilpotent(self):
534 """
535 Return whether or not some power of this element is zero.
536
537 ALGORITHM:
538
539 We use Theorem 5 in Chapter III of Koecher, which says that
540 an element ``x`` is nilpotent if and only if ``x.operator()``
541 is nilpotent. And it is a basic fact of linear algebra that
542 an operator on an `n`-dimensional space is nilpotent if and
543 only if, when raised to the `n`th power, it equals the zero
544 operator (for example, see Axler Corollary 8.8).
545
546 SETUP::
547
548 sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
549 ....: random_eja)
550
551 EXAMPLES::
552
553 sage: J = JordanSpinEJA(3)
554 sage: x = sum(J.gens())
555 sage: x.is_nilpotent()
556 False
557
558 TESTS:
559
560 The identity element is never nilpotent::
561
562 sage: set_random_seed()
563 sage: random_eja().one().is_nilpotent()
564 False
565
566 The additive identity is always nilpotent::
567
568 sage: set_random_seed()
569 sage: random_eja().zero().is_nilpotent()
570 True
571
572 """
573 P = self.parent()
574 zero_operator = P.zero().operator()
575 return self.operator()**P.dimension() == zero_operator
576
577
578 def is_regular(self):
579 """
580 Return whether or not this is a regular element.
581
582 SETUP::
583
584 sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
585 ....: random_eja)
586
587 EXAMPLES:
588
589 The identity element always has degree one, but any element
590 linearly-independent from it is regular::
591
592 sage: J = JordanSpinEJA(5)
593 sage: J.one().is_regular()
594 False
595 sage: e0, e1, e2, e3, e4 = J.gens() # e0 is the identity
596 sage: for x in J.gens():
597 ....: (J.one() + x).is_regular()
598 False
599 True
600 True
601 True
602 True
603
604 TESTS:
605
606 The zero element should never be regular, unless the parent
607 algebra has dimension one::
608
609 sage: set_random_seed()
610 sage: J = random_eja()
611 sage: J.dimension() == 1 or not J.zero().is_regular()
612 True
613
614 The unit element isn't regular unless the algebra happens to
615 consist of only its scalar multiples::
616
617 sage: set_random_seed()
618 sage: J = random_eja()
619 sage: J.dimension() == 1 or not J.one().is_regular()
620 True
621
622 """
623 return self.degree() == self.parent().rank()
624
625
626 def degree(self):
627 """
628 Return the degree of this element, which is defined to be
629 the degree of its minimal polynomial.
630
631 ALGORITHM:
632
633 For now, we skip the messy minimal polynomial computation
634 and instead return the dimension of the vector space spanned
635 by the powers of this element. The latter is a bit more
636 straightforward to compute.
637
638 SETUP::
639
640 sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
641 ....: random_eja)
642
643 EXAMPLES::
644
645 sage: J = JordanSpinEJA(4)
646 sage: J.one().degree()
647 1
648 sage: e0,e1,e2,e3 = J.gens()
649 sage: (e0 - e1).degree()
650 2
651
652 In the spin factor algebra (of rank two), all elements that
653 aren't multiples of the identity are regular::
654
655 sage: set_random_seed()
656 sage: J = JordanSpinEJA.random_instance()
657 sage: x = J.random_element()
658 sage: x == x.coefficient(0)*J.one() or x.degree() == 2
659 True
660
661 TESTS:
662
663 The zero and unit elements are both of degree one::
664
665 sage: set_random_seed()
666 sage: J = random_eja()
667 sage: J.zero().degree()
668 1
669 sage: J.one().degree()
670 1
671
672 Our implementation agrees with the definition::
673
674 sage: set_random_seed()
675 sage: x = random_eja().random_element()
676 sage: x.degree() == x.minimal_polynomial().degree()
677 True
678
679 """
680 if self.is_zero() and not self.parent().is_trivial():
681 # The minimal polynomial of zero in a nontrivial algebra
682 # is "t"; in a trivial algebra it's "1" by convention
683 # (it's an empty product).
684 return 1
685 return self.subalgebra_generated_by().dimension()
686
687
688 def left_matrix(self):
689 """
690 Our parent class defines ``left_matrix`` and ``matrix``
691 methods whose names are misleading. We don't want them.
692 """
693 raise NotImplementedError("use operator().matrix() instead")
694
695 matrix = left_matrix
696
697
698 def minimal_polynomial(self):
699 """
700 Return the minimal polynomial of this element,
701 as a function of the variable `t`.
702
703 ALGORITHM:
704
705 We restrict ourselves to the associative subalgebra
706 generated by this element, and then return the minimal
707 polynomial of this element's operator matrix (in that
708 subalgebra). This works by Baes Proposition 2.3.16.
709
710 SETUP::
711
712 sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
713 ....: RealSymmetricEJA,
714 ....: random_eja)
715
716 TESTS:
717
718 The minimal polynomial of the identity and zero elements are
719 always the same::
720
721 sage: set_random_seed()
722 sage: J = random_eja()
723 sage: J.one().minimal_polynomial()
724 t - 1
725 sage: J.zero().minimal_polynomial()
726 t
727
728 The degree of an element is (by one definition) the degree
729 of its minimal polynomial::
730
731 sage: set_random_seed()
732 sage: x = random_eja().random_element()
733 sage: x.degree() == x.minimal_polynomial().degree()
734 True
735
736 The minimal polynomial and the characteristic polynomial coincide
737 and are known (see Alizadeh, Example 11.11) for all elements of
738 the spin factor algebra that aren't scalar multiples of the
739 identity. We require the dimension of the algebra to be at least
740 two here so that said elements actually exist::
741
742 sage: set_random_seed()
743 sage: n_max = max(2, JordanSpinEJA._max_test_case_size())
744 sage: n = ZZ.random_element(2, n_max)
745 sage: J = JordanSpinEJA(n)
746 sage: y = J.random_element()
747 sage: while y == y.coefficient(0)*J.one():
748 ....: y = J.random_element()
749 sage: y0 = y.to_vector()[0]
750 sage: y_bar = y.to_vector()[1:]
751 sage: actual = y.minimal_polynomial()
752 sage: t = PolynomialRing(J.base_ring(),'t').gen(0)
753 sage: expected = t^2 - 2*y0*t + (y0^2 - norm(y_bar)^2)
754 sage: bool(actual == expected)
755 True
756
757 The minimal polynomial should always kill its element::
758
759 sage: set_random_seed()
760 sage: x = random_eja().random_element()
761 sage: p = x.minimal_polynomial()
762 sage: x.apply_univariate_polynomial(p)
763 0
764
765 The minimal polynomial is invariant under a change of basis,
766 and in particular, a re-scaling of the basis::
767
768 sage: set_random_seed()
769 sage: n_max = RealSymmetricEJA._max_test_case_size()
770 sage: n = ZZ.random_element(1, n_max)
771 sage: J1 = RealSymmetricEJA(n,QQ)
772 sage: J2 = RealSymmetricEJA(n,QQ,normalize_basis=False)
773 sage: X = random_matrix(QQ,n)
774 sage: X = X*X.transpose()
775 sage: x1 = J1(X)
776 sage: x2 = J2(X)
777 sage: x1.minimal_polynomial() == x2.minimal_polynomial()
778 True
779
780 """
781 if self.is_zero():
782 # We would generate a zero-dimensional subalgebra
783 # where the minimal polynomial would be constant.
784 # That might be correct, but only if *this* algebra
785 # is trivial too.
786 if not self.parent().is_trivial():
787 # Pretty sure we know what the minimal polynomial of
788 # the zero operator is going to be. This ensures
789 # consistency of e.g. the polynomial variable returned
790 # in the "normal" case without us having to think about it.
791 return self.operator().minimal_polynomial()
792
793 A = self.subalgebra_generated_by()
794 return A(self).operator().minimal_polynomial()
795
796
797
798 def natural_representation(self):
799 """
800 Return a more-natural representation of this element.
801
802 Every finite-dimensional Euclidean Jordan Algebra is a
803 direct sum of five simple algebras, four of which comprise
804 Hermitian matrices. This method returns the original
805 "natural" representation of this element as a Hermitian
806 matrix, if it has one. If not, you get the usual representation.
807
808 SETUP::
809
810 sage: from mjo.eja.eja_algebra import (ComplexHermitianEJA,
811 ....: QuaternionHermitianEJA)
812
813 EXAMPLES::
814
815 sage: J = ComplexHermitianEJA(3)
816 sage: J.one()
817 e0 + e3 + e8
818 sage: J.one().natural_representation()
819 [1 0 0 0 0 0]
820 [0 1 0 0 0 0]
821 [0 0 1 0 0 0]
822 [0 0 0 1 0 0]
823 [0 0 0 0 1 0]
824 [0 0 0 0 0 1]
825
826 ::
827
828 sage: J = QuaternionHermitianEJA(3)
829 sage: J.one()
830 e0 + e5 + e14
831 sage: J.one().natural_representation()
832 [1 0 0 0 0 0 0 0 0 0 0 0]
833 [0 1 0 0 0 0 0 0 0 0 0 0]
834 [0 0 1 0 0 0 0 0 0 0 0 0]
835 [0 0 0 1 0 0 0 0 0 0 0 0]
836 [0 0 0 0 1 0 0 0 0 0 0 0]
837 [0 0 0 0 0 1 0 0 0 0 0 0]
838 [0 0 0 0 0 0 1 0 0 0 0 0]
839 [0 0 0 0 0 0 0 1 0 0 0 0]
840 [0 0 0 0 0 0 0 0 1 0 0 0]
841 [0 0 0 0 0 0 0 0 0 1 0 0]
842 [0 0 0 0 0 0 0 0 0 0 1 0]
843 [0 0 0 0 0 0 0 0 0 0 0 1]
844
845 """
846 B = self.parent().natural_basis()
847 W = self.parent().natural_basis_space()
848 return W.linear_combination(izip(B,self.to_vector()))
849
850
851 def norm(self):
852 """
853 The norm of this element with respect to :meth:`inner_product`.
854
855 SETUP::
856
857 sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
858 ....: RealCartesianProductEJA)
859
860 EXAMPLES::
861
862 sage: J = RealCartesianProductEJA(2)
863 sage: x = sum(J.gens())
864 sage: x.norm()
865 sqrt(2)
866
867 ::
868
869 sage: J = JordanSpinEJA(4)
870 sage: x = sum(J.gens())
871 sage: x.norm()
872 2
873
874 """
875 return self.inner_product(self).sqrt()
876
877
878 def operator(self):
879 """
880 Return the left-multiplication-by-this-element
881 operator on the ambient algebra.
882
883 SETUP::
884
885 sage: from mjo.eja.eja_algebra import random_eja
886
887 TESTS::
888
889 sage: set_random_seed()
890 sage: J = random_eja()
891 sage: x,y = J.random_elements(2)
892 sage: x.operator()(y) == x*y
893 True
894 sage: y.operator()(x) == x*y
895 True
896
897 """
898 P = self.parent()
899 left_mult_by_self = lambda y: self*y
900 L = P.module_morphism(function=left_mult_by_self, codomain=P)
901 return FiniteDimensionalEuclideanJordanAlgebraOperator(
902 P,
903 P,
904 L.matrix() )
905
906
907 def quadratic_representation(self, other=None):
908 """
909 Return the quadratic representation of this element.
910
911 SETUP::
912
913 sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
914 ....: random_eja)
915
916 EXAMPLES:
917
918 The explicit form in the spin factor algebra is given by
919 Alizadeh's Example 11.12::
920
921 sage: set_random_seed()
922 sage: x = JordanSpinEJA.random_instance().random_element()
923 sage: x_vec = x.to_vector()
924 sage: n = x_vec.degree()
925 sage: x0 = x_vec[0]
926 sage: x_bar = x_vec[1:]
927 sage: A = matrix(QQ, 1, [x_vec.inner_product(x_vec)])
928 sage: B = 2*x0*x_bar.row()
929 sage: C = 2*x0*x_bar.column()
930 sage: D = matrix.identity(QQ, n-1)
931 sage: D = (x0^2 - x_bar.inner_product(x_bar))*D
932 sage: D = D + 2*x_bar.tensor_product(x_bar)
933 sage: Q = matrix.block(2,2,[A,B,C,D])
934 sage: Q == x.quadratic_representation().matrix()
935 True
936
937 Test all of the properties from Theorem 11.2 in Alizadeh::
938
939 sage: set_random_seed()
940 sage: J = random_eja()
941 sage: x,y = J.random_elements(2)
942 sage: Lx = x.operator()
943 sage: Lxx = (x*x).operator()
944 sage: Qx = x.quadratic_representation()
945 sage: Qy = y.quadratic_representation()
946 sage: Qxy = x.quadratic_representation(y)
947 sage: Qex = J.one().quadratic_representation(x)
948 sage: n = ZZ.random_element(10)
949 sage: Qxn = (x^n).quadratic_representation()
950
951 Property 1:
952
953 sage: 2*Qxy == (x+y).quadratic_representation() - Qx - Qy
954 True
955
956 Property 2 (multiply on the right for :trac:`28272`):
957
958 sage: alpha = J.base_ring().random_element()
959 sage: (alpha*x).quadratic_representation() == Qx*(alpha^2)
960 True
961
962 Property 3:
963
964 sage: not x.is_invertible() or ( Qx(x.inverse()) == x )
965 True
966
967 sage: not x.is_invertible() or (
968 ....: ~Qx
969 ....: ==
970 ....: x.inverse().quadratic_representation() )
971 True
972
973 sage: Qxy(J.one()) == x*y
974 True
975
976 Property 4:
977
978 sage: not x.is_invertible() or (
979 ....: x.quadratic_representation(x.inverse())*Qx
980 ....: == Qx*x.quadratic_representation(x.inverse()) )
981 True
982
983 sage: not x.is_invertible() or (
984 ....: x.quadratic_representation(x.inverse())*Qx
985 ....: ==
986 ....: 2*Lx*Qex - Qx )
987 True
988
989 sage: 2*Lx*Qex - Qx == Lxx
990 True
991
992 Property 5:
993
994 sage: Qy(x).quadratic_representation() == Qy*Qx*Qy
995 True
996
997 Property 6:
998
999 sage: Qxn == (Qx)^n
1000 True
1001
1002 Property 7:
1003
1004 sage: not x.is_invertible() or (
1005 ....: Qx*x.inverse().operator() == Lx )
1006 True
1007
1008 Property 8:
1009
1010 sage: not x.operator_commutes_with(y) or (
1011 ....: Qx(y)^n == Qxn(y^n) )
1012 True
1013
1014 """
1015 if other is None:
1016 other=self
1017 elif not other in self.parent():
1018 raise TypeError("'other' must live in the same algebra")
1019
1020 L = self.operator()
1021 M = other.operator()
1022 return ( L*M + M*L - (self*other).operator() )
1023
1024
1025
1026
1027 def subalgebra_generated_by(self, orthonormalize_basis=False):
1028 """
1029 Return the associative subalgebra of the parent EJA generated
1030 by this element.
1031
1032 SETUP::
1033
1034 sage: from mjo.eja.eja_algebra import random_eja
1035
1036 TESTS:
1037
1038 This subalgebra, being composed of only powers, is associative::
1039
1040 sage: set_random_seed()
1041 sage: x0 = random_eja().random_element()
1042 sage: A = x0.subalgebra_generated_by()
1043 sage: x,y,z = A.random_elements(3)
1044 sage: (x*y)*z == x*(y*z)
1045 True
1046
1047 Squaring in the subalgebra should work the same as in
1048 the superalgebra::
1049
1050 sage: set_random_seed()
1051 sage: x = random_eja().random_element()
1052 sage: A = x.subalgebra_generated_by()
1053 sage: A(x^2) == A(x)*A(x)
1054 True
1055
1056 The subalgebra generated by the zero element is trivial::
1057
1058 sage: set_random_seed()
1059 sage: A = random_eja().zero().subalgebra_generated_by()
1060 sage: A
1061 Euclidean Jordan algebra of dimension 0 over...
1062 sage: A.one()
1063 0
1064
1065 """
1066 return FiniteDimensionalEuclideanJordanElementSubalgebra(self, orthonormalize_basis)
1067
1068
1069 def subalgebra_idempotent(self):
1070 """
1071 Find an idempotent in the associative subalgebra I generate
1072 using Proposition 2.3.5 in Baes.
1073
1074 SETUP::
1075
1076 sage: from mjo.eja.eja_algebra import random_eja
1077
1078 TESTS::
1079
1080 sage: set_random_seed()
1081 sage: J = random_eja()
1082 sage: x = J.random_element()
1083 sage: while x.is_nilpotent():
1084 ....: x = J.random_element()
1085 sage: c = x.subalgebra_idempotent()
1086 sage: c^2 == c
1087 True
1088
1089 """
1090 if self.is_nilpotent():
1091 raise ValueError("this only works with non-nilpotent elements!")
1092
1093 J = self.subalgebra_generated_by()
1094 u = J(self)
1095
1096 # The image of the matrix of left-u^m-multiplication
1097 # will be minimal for some natural number s...
1098 s = 0
1099 minimal_dim = J.dimension()
1100 for i in xrange(1, minimal_dim):
1101 this_dim = (u**i).operator().matrix().image().dimension()
1102 if this_dim < minimal_dim:
1103 minimal_dim = this_dim
1104 s = i
1105
1106 # Now minimal_matrix should correspond to the smallest
1107 # non-zero subspace in Baes's (or really, Koecher's)
1108 # proposition.
1109 #
1110 # However, we need to restrict the matrix to work on the
1111 # subspace... or do we? Can't we just solve, knowing that
1112 # A(c) = u^(s+1) should have a solution in the big space,
1113 # too?
1114 #
1115 # Beware, solve_right() means that we're using COLUMN vectors.
1116 # Our FiniteDimensionalAlgebraElement superclass uses rows.
1117 u_next = u**(s+1)
1118 A = u_next.operator().matrix()
1119 c = J.from_vector(A.solve_right(u_next.to_vector()))
1120
1121 # Now c is the idempotent we want, but it still lives in the subalgebra.
1122 return c.superalgebra_element()
1123
1124
1125 def trace(self):
1126 """
1127 Return my trace, the sum of my eigenvalues.
1128
1129 SETUP::
1130
1131 sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
1132 ....: RealCartesianProductEJA,
1133 ....: random_eja)
1134
1135 EXAMPLES::
1136
1137 sage: J = JordanSpinEJA(3)
1138 sage: x = sum(J.gens())
1139 sage: x.trace()
1140 2
1141
1142 ::
1143
1144 sage: J = RealCartesianProductEJA(5)
1145 sage: J.one().trace()
1146 5
1147
1148 TESTS:
1149
1150 The trace of an element is a real number::
1151
1152 sage: set_random_seed()
1153 sage: J = random_eja()
1154 sage: J.random_element().trace() in RLF
1155 True
1156
1157 """
1158 P = self.parent()
1159 r = P.rank()
1160 p = P._charpoly_coeff(r-1)
1161 # The _charpoly_coeff function already adds the factor of
1162 # -1 to ensure that _charpoly_coeff(r-1) is really what
1163 # appears in front of t^{r-1} in the charpoly. However,
1164 # we want the negative of THAT for the trace.
1165 return -p(*self.to_vector())
1166
1167
1168 def trace_inner_product(self, other):
1169 """
1170 Return the trace inner product of myself and ``other``.
1171
1172 SETUP::
1173
1174 sage: from mjo.eja.eja_algebra import random_eja
1175
1176 TESTS:
1177
1178 The trace inner product is commutative, bilinear, and associative::
1179
1180 sage: set_random_seed()
1181 sage: J = random_eja()
1182 sage: x,y,z = J.random_elements(3)
1183 sage: # commutative
1184 sage: x.trace_inner_product(y) == y.trace_inner_product(x)
1185 True
1186 sage: # bilinear
1187 sage: a = J.base_ring().random_element();
1188 sage: actual = (a*(x+z)).trace_inner_product(y)
1189 sage: expected = ( a*x.trace_inner_product(y) +
1190 ....: a*z.trace_inner_product(y) )
1191 sage: actual == expected
1192 True
1193 sage: actual = x.trace_inner_product(a*(y+z))
1194 sage: expected = ( a*x.trace_inner_product(y) +
1195 ....: a*x.trace_inner_product(z) )
1196 sage: actual == expected
1197 True
1198 sage: # associative
1199 sage: (x*y).trace_inner_product(z) == y.trace_inner_product(x*z)
1200 True
1201
1202 """
1203 if not other in self.parent():
1204 raise TypeError("'other' must live in the same algebra")
1205
1206 return (self*other).trace()
1207
1208
1209 def trace_norm(self):
1210 """
1211 The norm of this element with respect to :meth:`trace_inner_product`.
1212
1213 SETUP::
1214
1215 sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
1216 ....: RealCartesianProductEJA)
1217
1218 EXAMPLES::
1219
1220 sage: J = RealCartesianProductEJA(2)
1221 sage: x = sum(J.gens())
1222 sage: x.trace_norm()
1223 sqrt(2)
1224
1225 ::
1226
1227 sage: J = JordanSpinEJA(4)
1228 sage: x = sum(J.gens())
1229 sage: x.trace_norm()
1230 2*sqrt(2)
1231
1232 """
1233 return self.trace_inner_product(self).sqrt()