2 Euclidean Jordan Algebras. These are formally-real Jordan Algebras;
3 specifically those where u^2 + v^2 = 0 implies that u = v = 0. They
4 are used in optimization, and have some additional nice methods beyond
5 what can be supported in a general Jordan Algebra.
8 from sage
.categories
.magmatic_algebras
import MagmaticAlgebras
9 from sage
.structure
.element
import is_Matrix
10 from sage
.structure
.category_object
import normalize_names
12 from sage
.algebras
.finite_dimensional_algebras
.finite_dimensional_algebra
import FiniteDimensionalAlgebra
13 from sage
.algebras
.finite_dimensional_algebras
.finite_dimensional_algebra_element
import FiniteDimensionalAlgebraElement
15 class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra
):
17 def __classcall_private__(cls
,
21 assume_associative
=False,
25 mult_table
= [b
.base_extend(field
) for b
in mult_table
]
28 if not (is_Matrix(b
) and b
.dimensions() == (n
, n
)):
29 raise ValueError("input is not a multiplication table")
30 mult_table
= tuple(mult_table
)
32 cat
= MagmaticAlgebras(field
).FiniteDimensional().WithBasis()
33 cat
.or_subcategory(category
)
34 if assume_associative
:
35 cat
= cat
.Associative()
37 names
= normalize_names(n
, names
)
39 fda
= super(FiniteDimensionalEuclideanJordanAlgebra
, cls
)
40 return fda
.__classcall
__(cls
,
43 assume_associative
=assume_associative
,
49 def __init__(self
, field
,
52 assume_associative
=False,
58 By definition, Jordan multiplication commutes::
60 sage: set_random_seed()
61 sage: J = random_eja()
62 sage: x = J.random_element()
63 sage: y = J.random_element()
69 fda
= super(FiniteDimensionalEuclideanJordanAlgebra
, self
)
78 Return a string representation of ``self``.
80 fmt
= "Euclidean Jordan algebra of degree {} over {}"
81 return fmt
.format(self
.degree(), self
.base_ring())
85 Return the rank of this EJA.
87 if self
._rank
is None:
88 raise ValueError("no rank specified at genesis")
93 class Element(FiniteDimensionalAlgebraElement
):
95 An element of a Euclidean Jordan algebra.
100 Return ``self`` raised to the power ``n``.
102 Jordan algebras are always power-associative; see for
103 example Faraut and Koranyi, Proposition II.1.2 (ii).
107 We have to override this because our superclass uses row vectors
108 instead of column vectors! We, on the other hand, assume column
113 sage: set_random_seed()
114 sage: x = random_eja().random_element()
115 sage: x.matrix()*x.vector() == (x^2).vector()
118 A few examples of power-associativity::
120 sage: set_random_seed()
121 sage: x = random_eja().random_element()
122 sage: x*(x*x)*(x*x) == x^5
124 sage: (x*x)*(x*x*x) == x^5
134 return A
.element_class(A
, (self
.matrix()**(n
-1))*self
.vector())
137 def characteristic_polynomial(self
):
139 Return my characteristic polynomial (if I'm a regular
142 Eventually this should be implemented in terms of the parent
143 algebra's characteristic polynomial that works for ALL
146 if self
.is_regular():
147 return self
.minimal_polynomial()
149 raise NotImplementedError('irregular element')
154 Return my determinant, the product of my eigenvalues.
158 sage: J = JordanSpinSimpleEJA(2)
159 sage: e0,e1 = J.gens()
163 sage: J = JordanSpinSimpleEJA(3)
164 sage: e0,e1,e2 = J.gens()
165 sage: x = e0 + e1 + e2
170 cs
= self
.characteristic_polynomial().coefficients(sparse
=False)
173 return cs
[0] * (-1)**r
175 raise ValueError('charpoly had no coefficients')
180 Return the Jordan-multiplicative inverse of this element.
182 We can't use the superclass method because it relies on the
183 algebra being associative.
187 The inverse in the spin factor algebra is given in Alizadeh's
190 sage: set_random_seed()
191 sage: n = ZZ.random_element(1,10).abs()
192 sage: J = JordanSpinSimpleEJA(n)
193 sage: x = J.random_element()
194 sage: while x.is_zero():
195 ....: x = J.random_element()
196 sage: x_vec = x.vector()
198 sage: x_bar = x_vec[1:]
199 sage: coeff = 1/(x0^2 - x_bar.inner_product(x_bar))
200 sage: inv_vec = x_vec.parent()([x0] + (-x_bar).list())
201 sage: x_inverse = coeff*inv_vec
202 sage: x.inverse() == J(x_inverse)
207 The identity element is its own inverse::
209 sage: set_random_seed()
210 sage: J = random_eja()
211 sage: J.one().inverse() == J.one()
214 If an element has an inverse, it acts like one. TODO: this
215 can be a lot less ugly once ``is_invertible`` doesn't crash
216 on irregular elements::
218 sage: set_random_seed()
219 sage: J = random_eja()
220 sage: x = J.random_element()
222 ....: x.inverse()*x == J.one()
228 if self
.parent().is_associative():
229 elt
= FiniteDimensionalAlgebraElement(self
.parent(), self
)
232 # TODO: we can do better once the call to is_invertible()
233 # doesn't crash on irregular elements.
234 #if not self.is_invertible():
235 # raise ArgumentError('element is not invertible')
237 # We do this a little different than the usual recursive
238 # call to a finite-dimensional algebra element, because we
239 # wind up with an inverse that lives in the subalgebra and
240 # we need information about the parent to convert it back.
241 V
= self
.span_of_powers()
242 assoc_subalg
= self
.subalgebra_generated_by()
243 # Mis-design warning: the basis used for span_of_powers()
244 # and subalgebra_generated_by() must be the same, and in
246 elt
= assoc_subalg(V
.coordinates(self
.vector()))
248 # This will be in the subalgebra's coordinates...
249 fda_elt
= FiniteDimensionalAlgebraElement(assoc_subalg
, elt
)
250 subalg_inverse
= fda_elt
.inverse()
252 # So we have to convert back...
253 basis
= [ self
.parent(v
) for v
in V
.basis() ]
254 pairs
= zip(subalg_inverse
.vector(), basis
)
255 return self
.parent().linear_combination(pairs
)
258 def is_invertible(self
):
260 Return whether or not this element is invertible.
262 We can't use the superclass method because it relies on
263 the algebra being associative.
265 return not self
.det().is_zero()
268 def is_nilpotent(self
):
270 Return whether or not some power of this element is zero.
272 The superclass method won't work unless we're in an
273 associative algebra, and we aren't. However, we generate
274 an assocoative subalgebra and we're nilpotent there if and
275 only if we're nilpotent here (probably).
279 The identity element is never nilpotent::
281 sage: set_random_seed()
282 sage: random_eja().one().is_nilpotent()
285 The additive identity is always nilpotent::
287 sage: set_random_seed()
288 sage: random_eja().zero().is_nilpotent()
292 # The element we're going to call "is_nilpotent()" on.
293 # Either myself, interpreted as an element of a finite-
294 # dimensional algebra, or an element of an associative
298 if self
.parent().is_associative():
299 elt
= FiniteDimensionalAlgebraElement(self
.parent(), self
)
301 V
= self
.span_of_powers()
302 assoc_subalg
= self
.subalgebra_generated_by()
303 # Mis-design warning: the basis used for span_of_powers()
304 # and subalgebra_generated_by() must be the same, and in
306 elt
= assoc_subalg(V
.coordinates(self
.vector()))
308 # Recursive call, but should work since elt lives in an
309 # associative algebra.
310 return elt
.is_nilpotent()
313 def is_regular(self
):
315 Return whether or not this is a regular element.
319 The identity element always has degree one, but any element
320 linearly-independent from it is regular::
322 sage: J = JordanSpinSimpleEJA(5)
323 sage: J.one().is_regular()
325 sage: e0, e1, e2, e3, e4 = J.gens() # e0 is the identity
326 sage: for x in J.gens():
327 ....: (J.one() + x).is_regular()
335 return self
.degree() == self
.parent().rank()
340 Compute the degree of this element the straightforward way
341 according to the definition; by appending powers to a list
342 and figuring out its dimension (that is, whether or not
343 they're linearly dependent).
347 sage: J = JordanSpinSimpleEJA(4)
348 sage: J.one().degree()
350 sage: e0,e1,e2,e3 = J.gens()
351 sage: (e0 - e1).degree()
354 In the spin factor algebra (of rank two), all elements that
355 aren't multiples of the identity are regular::
357 sage: set_random_seed()
358 sage: n = ZZ.random_element(1,10).abs()
359 sage: J = JordanSpinSimpleEJA(n)
360 sage: x = J.random_element()
361 sage: x == x.coefficient(0)*J.one() or x.degree() == 2
365 return self
.span_of_powers().dimension()
370 Return the matrix that represents left- (or right-)
371 multiplication by this element in the parent algebra.
373 We have to override this because the superclass method
374 returns a matrix that acts on row vectors (that is, on
379 Test the first polarization identity from my notes, Koecher Chapter
380 III, or from Baes (2.3)::
382 sage: set_random_seed()
383 sage: J = random_eja()
384 sage: x = J.random_element()
385 sage: y = J.random_element()
386 sage: Lx = x.matrix()
387 sage: Ly = y.matrix()
388 sage: Lxx = (x*x).matrix()
389 sage: Lxy = (x*y).matrix()
390 sage: bool(2*Lx*Lxy + Ly*Lxx == 2*Lxy*Lx + Lxx*Ly)
393 Test the second polarization identity from my notes or from
396 sage: set_random_seed()
397 sage: J = random_eja()
398 sage: x = J.random_element()
399 sage: y = J.random_element()
400 sage: z = J.random_element()
401 sage: Lx = x.matrix()
402 sage: Ly = y.matrix()
403 sage: Lz = z.matrix()
404 sage: Lzy = (z*y).matrix()
405 sage: Lxy = (x*y).matrix()
406 sage: Lxz = (x*z).matrix()
407 sage: bool(Lx*Lzy + Lz*Lxy + Ly*Lxz == Lzy*Lx + Lxy*Lz + Lxz*Ly)
410 Test the third polarization identity from my notes or from
413 sage: set_random_seed()
414 sage: J = random_eja()
415 sage: u = J.random_element()
416 sage: y = J.random_element()
417 sage: z = J.random_element()
418 sage: Lu = u.matrix()
419 sage: Ly = y.matrix()
420 sage: Lz = z.matrix()
421 sage: Lzy = (z*y).matrix()
422 sage: Luy = (u*y).matrix()
423 sage: Luz = (u*z).matrix()
424 sage: Luyz = (u*(y*z)).matrix()
425 sage: lhs = Lu*Lzy + Lz*Luy + Ly*Luz
426 sage: rhs = Luyz + Ly*Lu*Lz + Lz*Lu*Ly
427 sage: bool(lhs == rhs)
431 fda_elt
= FiniteDimensionalAlgebraElement(self
.parent(), self
)
432 return fda_elt
.matrix().transpose()
435 def minimal_polynomial(self
):
439 sage: set_random_seed()
440 sage: x = random_eja().random_element()
441 sage: x.degree() == x.minimal_polynomial().degree()
446 sage: set_random_seed()
447 sage: x = random_eja().random_element()
448 sage: x.degree() == x.minimal_polynomial().degree()
451 The minimal polynomial and the characteristic polynomial coincide
452 and are known (see Alizadeh, Example 11.11) for all elements of
453 the spin factor algebra that aren't scalar multiples of the
456 sage: set_random_seed()
457 sage: n = ZZ.random_element(2,10).abs()
458 sage: J = JordanSpinSimpleEJA(n)
459 sage: y = J.random_element()
460 sage: while y == y.coefficient(0)*J.one():
461 ....: y = J.random_element()
462 sage: y0 = y.vector()[0]
463 sage: y_bar = y.vector()[1:]
464 sage: actual = y.minimal_polynomial()
465 sage: x = SR.symbol('x', domain='real')
466 sage: expected = x^2 - 2*y0*x + (y0^2 - norm(y_bar)^2)
467 sage: bool(actual == expected)
471 # The element we're going to call "minimal_polynomial()" on.
472 # Either myself, interpreted as an element of a finite-
473 # dimensional algebra, or an element of an associative
477 if self
.parent().is_associative():
478 elt
= FiniteDimensionalAlgebraElement(self
.parent(), self
)
480 V
= self
.span_of_powers()
481 assoc_subalg
= self
.subalgebra_generated_by()
482 # Mis-design warning: the basis used for span_of_powers()
483 # and subalgebra_generated_by() must be the same, and in
485 elt
= assoc_subalg(V
.coordinates(self
.vector()))
487 # Recursive call, but should work since elt lives in an
488 # associative algebra.
489 return elt
.minimal_polynomial()
492 def quadratic_representation(self
, other
=None):
494 Return the quadratic representation of this element.
498 The explicit form in the spin factor algebra is given by
499 Alizadeh's Example 11.12::
501 sage: set_random_seed()
502 sage: n = ZZ.random_element(1,10).abs()
503 sage: J = JordanSpinSimpleEJA(n)
504 sage: x = J.random_element()
505 sage: x_vec = x.vector()
507 sage: x_bar = x_vec[1:]
508 sage: A = matrix(QQ, 1, [x_vec.inner_product(x_vec)])
509 sage: B = 2*x0*x_bar.row()
510 sage: C = 2*x0*x_bar.column()
511 sage: D = identity_matrix(QQ, n-1)
512 sage: D = (x0^2 - x_bar.inner_product(x_bar))*D
513 sage: D = D + 2*x_bar.tensor_product(x_bar)
514 sage: Q = block_matrix(2,2,[A,B,C,D])
515 sage: Q == x.quadratic_representation()
518 Test all of the properties from Theorem 11.2 in Alizadeh::
520 sage: set_random_seed()
521 sage: J = random_eja()
522 sage: x = J.random_element()
523 sage: y = J.random_element()
527 sage: actual = x.quadratic_representation(y)
528 sage: expected = ( (x+y).quadratic_representation()
529 ....: -x.quadratic_representation()
530 ....: -y.quadratic_representation() ) / 2
531 sage: actual == expected
536 sage: alpha = QQ.random_element()
537 sage: actual = (alpha*x).quadratic_representation()
538 sage: expected = (alpha^2)*x.quadratic_representation()
539 sage: actual == expected
544 sage: Qy = y.quadratic_representation()
545 sage: actual = J(Qy*x.vector()).quadratic_representation()
546 sage: expected = Qy*x.quadratic_representation()*Qy
547 sage: actual == expected
552 sage: k = ZZ.random_element(1,10).abs()
553 sage: actual = (x^k).quadratic_representation()
554 sage: expected = (x.quadratic_representation())^k
555 sage: actual == expected
561 elif not other
in self
.parent():
562 raise ArgumentError("'other' must live in the same algebra")
564 return ( self
.matrix()*other
.matrix()
565 + other
.matrix()*self
.matrix()
566 - (self
*other
).matrix() )
569 def span_of_powers(self
):
571 Return the vector space spanned by successive powers of
574 # The dimension of the subalgebra can't be greater than
575 # the big algebra, so just put everything into a list
576 # and let span() get rid of the excess.
577 V
= self
.vector().parent()
578 return V
.span( (self
**d
).vector() for d
in xrange(V
.dimension()) )
581 def subalgebra_generated_by(self
):
583 Return the associative subalgebra of the parent EJA generated
588 sage: set_random_seed()
589 sage: x = random_eja().random_element()
590 sage: x.subalgebra_generated_by().is_associative()
593 Squaring in the subalgebra should be the same thing as
594 squaring in the superalgebra::
596 sage: set_random_seed()
597 sage: x = random_eja().random_element()
598 sage: u = x.subalgebra_generated_by().random_element()
599 sage: u.matrix()*u.vector() == (u**2).vector()
603 # First get the subspace spanned by the powers of myself...
604 V
= self
.span_of_powers()
607 # Now figure out the entries of the right-multiplication
608 # matrix for the successive basis elements b0, b1,... of
611 for b_right
in V
.basis():
612 eja_b_right
= self
.parent()(b_right
)
614 # The first row of the right-multiplication matrix by
615 # b1 is what we get if we apply that matrix to b1. The
616 # second row of the right multiplication matrix by b1
617 # is what we get when we apply that matrix to b2...
619 # IMPORTANT: this assumes that all vectors are COLUMN
620 # vectors, unlike our superclass (which uses row vectors).
621 for b_left
in V
.basis():
622 eja_b_left
= self
.parent()(b_left
)
623 # Multiply in the original EJA, but then get the
624 # coordinates from the subalgebra in terms of its
626 this_row
= V
.coordinates((eja_b_left
*eja_b_right
).vector())
627 b_right_rows
.append(this_row
)
628 b_right_matrix
= matrix(F
, b_right_rows
)
629 mats
.append(b_right_matrix
)
631 # It's an algebra of polynomials in one element, and EJAs
632 # are power-associative.
634 # TODO: choose generator names intelligently.
635 return FiniteDimensionalEuclideanJordanAlgebra(F
, mats
, assume_associative
=True, names
='f')
638 def subalgebra_idempotent(self
):
640 Find an idempotent in the associative subalgebra I generate
641 using Proposition 2.3.5 in Baes.
645 sage: set_random_seed()
647 sage: c = J.random_element().subalgebra_idempotent()
650 sage: J = JordanSpinSimpleEJA(5)
651 sage: c = J.random_element().subalgebra_idempotent()
656 if self
.is_nilpotent():
657 raise ValueError("this only works with non-nilpotent elements!")
659 V
= self
.span_of_powers()
660 J
= self
.subalgebra_generated_by()
661 # Mis-design warning: the basis used for span_of_powers()
662 # and subalgebra_generated_by() must be the same, and in
664 u
= J(V
.coordinates(self
.vector()))
666 # The image of the matrix of left-u^m-multiplication
667 # will be minimal for some natural number s...
669 minimal_dim
= V
.dimension()
670 for i
in xrange(1, V
.dimension()):
671 this_dim
= (u
**i
).matrix().image().dimension()
672 if this_dim
< minimal_dim
:
673 minimal_dim
= this_dim
676 # Now minimal_matrix should correspond to the smallest
677 # non-zero subspace in Baes's (or really, Koecher's)
680 # However, we need to restrict the matrix to work on the
681 # subspace... or do we? Can't we just solve, knowing that
682 # A(c) = u^(s+1) should have a solution in the big space,
685 # Beware, solve_right() means that we're using COLUMN vectors.
686 # Our FiniteDimensionalAlgebraElement superclass uses rows.
689 c_coordinates
= A
.solve_right(u_next
.vector())
691 # Now c_coordinates is the idempotent we want, but it's in
692 # the coordinate system of the subalgebra.
694 # We need the basis for J, but as elements of the parent algebra.
696 basis
= [self
.parent(v
) for v
in V
.basis()]
697 return self
.parent().linear_combination(zip(c_coordinates
, basis
))
702 Return my trace, the sum of my eigenvalues.
706 sage: J = JordanSpinSimpleEJA(3)
707 sage: e0,e1,e2 = J.gens()
708 sage: x = e0 + e1 + e2
713 cs
= self
.characteristic_polynomial().coefficients(sparse
=False)
717 raise ValueError('charpoly had fewer than 2 coefficients')
720 def trace_inner_product(self
, other
):
722 Return the trace inner product of myself and ``other``.
724 if not other
in self
.parent():
725 raise ArgumentError("'other' must live in the same algebra")
727 return (self
*other
).trace()
730 def eja_rn(dimension
, field
=QQ
):
732 Return the Euclidean Jordan Algebra corresponding to the set
733 `R^n` under the Hadamard product.
737 This multiplication table can be verified by hand::
740 sage: e0,e1,e2 = J.gens()
755 # The FiniteDimensionalAlgebra constructor takes a list of
756 # matrices, the ith representing right multiplication by the ith
757 # basis element in the vector space. So if e_1 = (1,0,0), then
758 # right (Hadamard) multiplication of x by e_1 picks out the first
759 # component of x; and likewise for the ith basis element e_i.
760 Qs
= [ matrix(field
, dimension
, dimension
, lambda k
,j
: 1*(k
== j
== i
))
761 for i
in xrange(dimension
) ]
763 return FiniteDimensionalEuclideanJordanAlgebra(field
,Qs
,rank
=dimension
)
769 Return a "random" finite-dimensional Euclidean Jordan Algebra.
773 For now, we choose a random natural number ``n`` (greater than zero)
774 and then give you back one of the following:
776 * The cartesian product of the rational numbers ``n`` times; this is
777 ``QQ^n`` with the Hadamard product.
779 * The Jordan spin algebra on ``QQ^n``.
781 * The ``n``-by-``n`` rational symmetric matrices with the symmetric
784 Later this might be extended to return Cartesian products of the
790 Euclidean Jordan algebra of degree...
793 n
= ZZ
.random_element(1,5).abs()
794 constructor
= choice([eja_rn
,
796 RealSymmetricSimpleEJA
,
797 ComplexHermitianSimpleEJA
])
798 return constructor(n
, field
=QQ
)
802 def _real_symmetric_basis(n
, field
=QQ
):
804 Return a basis for the space of real symmetric n-by-n matrices.
806 # The basis of symmetric matrices, as matrices, in their R^(n-by-n)
810 for j
in xrange(i
+1):
811 Eij
= matrix(field
, n
, lambda k
,l
: k
==i
and l
==j
)
815 # Beware, orthogonal but not normalized!
816 Sij
= Eij
+ Eij
.transpose()
821 def _complex_hermitian_basis(n
, field
=QQ
):
823 Returns a basis for the space of complex Hermitian n-by-n matrices.
827 sage: set_random_seed()
828 sage: n = ZZ.random_element(1,5).abs()
829 sage: all( M.is_symmetric() for M in _complex_hermitian_basis(n) )
833 F
= QuadraticField(-1, 'I')
836 # This is like the symmetric case, but we need to be careful:
838 # * We want conjugate-symmetry, not just symmetry.
839 # * The diagonal will (as a result) be real.
843 for j
in xrange(i
+1):
844 Eij
= matrix(field
, n
, lambda k
,l
: k
==i
and l
==j
)
846 Sij
= _embed_complex_matrix(Eij
)
849 # Beware, orthogonal but not normalized! The second one
850 # has a minus because it's conjugated.
851 Sij_real
= _embed_complex_matrix(Eij
+ Eij
.transpose())
853 Sij_imag
= _embed_complex_matrix(I
*Eij
- I
*Eij
.transpose())
858 def _multiplication_table_from_matrix_basis(basis
):
860 At least three of the five simple Euclidean Jordan algebras have the
861 symmetric multiplication (A,B) |-> (AB + BA)/2, where the
862 multiplication on the right is matrix multiplication. Given a basis
863 for the underlying matrix space, this function returns a
864 multiplication table (obtained by looping through the basis
865 elements) for an algebra of those matrices.
867 # In S^2, for example, we nominally have four coordinates even
868 # though the space is of dimension three only. The vector space V
869 # is supposed to hold the entire long vector, and the subspace W
870 # of V will be spanned by the vectors that arise from symmetric
871 # matrices. Thus for S^2, dim(V) == 4 and dim(W) == 3.
872 field
= basis
[0].base_ring()
873 dimension
= basis
[0].nrows()
876 return vector(field
, m
.list())
879 return matrix(field
, dimension
, v
.list())
881 V
= VectorSpace(field
, dimension
**2)
882 W
= V
.span( mat2vec(s
) for s
in basis
)
884 # Taking the span above reorders our basis (thanks, jerk!) so we
885 # need to put our "matrix basis" in the same order as the
886 # (reordered) vector basis.
887 S
= [ vec2mat(b
) for b
in W
.basis() ]
891 # Brute force the multiplication-by-s matrix by looping
892 # through all elements of the basis and doing the computation
893 # to find out what the corresponding row should be. BEWARE:
894 # these multiplication tables won't be symmetric! It therefore
895 # becomes REALLY IMPORTANT that the underlying algebra
896 # constructor uses ROW vectors and not COLUMN vectors. That's
897 # why we're computing rows here and not columns.
900 this_row
= mat2vec((s
*t
+ t
*s
)/2)
901 Q_rows
.append(W
.coordinates(this_row
))
902 Q
= matrix(field
, W
.dimension(), Q_rows
)
908 def _embed_complex_matrix(M
):
910 Embed the n-by-n complex matrix ``M`` into the space of real
911 matrices of size 2n-by-2n via the map the sends each entry `z = a +
912 bi` to the block matrix ``[[a,b],[-b,a]]``.
916 sage: F = QuadraticField(-1,'i')
917 sage: x1 = F(4 - 2*i)
918 sage: x2 = F(1 + 2*i)
921 sage: M = matrix(F,2,[x1,x2,x3,x4])
922 sage: _embed_complex_matrix(M)
932 raise ArgumentError("the matrix 'M' must be square")
933 field
= M
.base_ring()
938 blocks
.append(matrix(field
, 2, [[a
,-b
],[b
,a
]]))
940 # We can drop the imaginaries here.
941 return block_matrix(field
.base_ring(), n
, blocks
)
944 def _unembed_complex_matrix(M
):
946 The inverse of _embed_complex_matrix().
950 sage: A = matrix(QQ,[ [ 1, 2, 3, 4],
951 ....: [-2, 1, -4, 3],
952 ....: [ 9, 10, 11, 12],
953 ....: [-10, 9, -12, 11] ])
954 sage: _unembed_complex_matrix(A)
956 [ -10*i + 9 -12*i + 11]
960 raise ArgumentError("the matrix 'M' must be square")
961 if not n
.mod(2).is_zero():
962 raise ArgumentError("the matrix 'M' must be a complex embedding")
964 F
= QuadraticField(-1, 'i')
967 # Go top-left to bottom-right (reading order), converting every
968 # 2-by-2 block we see to a single complex element.
970 for k
in xrange(n
/2):
971 for j
in xrange(n
/2):
972 submat
= M
[2*k
:2*k
+2,2*j
:2*j
+2]
973 if submat
[0,0] != submat
[1,1]:
974 raise ArgumentError('bad real submatrix')
975 if submat
[0,1] != -submat
[1,0]:
976 raise ArgumentError('bad imag submatrix')
977 z
= submat
[0,0] + submat
[1,0]*i
980 return matrix(F
, n
/2, elements
)
983 def RealSymmetricSimpleEJA(n
, field
=QQ
):
985 The rank-n simple EJA consisting of real symmetric n-by-n
986 matrices, the usual symmetric Jordan product, and the trace inner
987 product. It has dimension `(n^2 + n)/2` over the reals.
991 sage: J = RealSymmetricSimpleEJA(2)
992 sage: e0, e1, e2 = J.gens()
1002 The degree of this algebra is `(n^2 + n) / 2`::
1004 sage: set_random_seed()
1005 sage: n = ZZ.random_element(1,5).abs()
1006 sage: J = RealSymmetricSimpleEJA(n)
1007 sage: J.degree() == (n^2 + n)/2
1011 S
= _real_symmetric_basis(n
, field
=field
)
1012 Qs
= _multiplication_table_from_matrix_basis(S
)
1014 return FiniteDimensionalEuclideanJordanAlgebra(field
,Qs
,rank
=n
)
1017 def ComplexHermitianSimpleEJA(n
, field
=QQ
):
1019 The rank-n simple EJA consisting of complex Hermitian n-by-n
1020 matrices over the real numbers, the usual symmetric Jordan product,
1021 and the real-part-of-trace inner product. It has dimension `n^2` over
1026 The degree of this algebra is `n^2`::
1028 sage: set_random_seed()
1029 sage: n = ZZ.random_element(1,5).abs()
1030 sage: J = ComplexHermitianSimpleEJA(n)
1031 sage: J.degree() == n^2
1035 S
= _complex_hermitian_basis(n
)
1036 Qs
= _multiplication_table_from_matrix_basis(S
)
1037 return FiniteDimensionalEuclideanJordanAlgebra(field
, Qs
, rank
=n
)
1040 def QuaternionHermitianSimpleEJA(n
):
1042 The rank-n simple EJA consisting of self-adjoint n-by-n quaternion
1043 matrices, the usual symmetric Jordan product, and the
1044 real-part-of-trace inner product. It has dimension `2n^2 - n` over
1049 def OctonionHermitianSimpleEJA(n
):
1051 This shit be crazy. It has dimension 27 over the reals.
1056 def JordanSpinSimpleEJA(n
, field
=QQ
):
1058 The rank-2 simple EJA consisting of real vectors ``x=(x0, x_bar)``
1059 with the usual inner product and jordan product ``x*y =
1060 (<x_bar,y_bar>, x0*y_bar + y0*x_bar)``. It has dimension `n` over
1065 This multiplication table can be verified by hand::
1067 sage: J = JordanSpinSimpleEJA(4)
1068 sage: e0,e1,e2,e3 = J.gens()
1084 In one dimension, this is the reals under multiplication::
1086 sage: J1 = JordanSpinSimpleEJA(1)
1087 sage: J2 = eja_rn(1)
1093 id_matrix
= identity_matrix(field
, n
)
1095 ei
= id_matrix
.column(i
)
1096 Qi
= zero_matrix(field
, n
)
1098 Qi
.set_column(0, ei
)
1099 Qi
+= diagonal_matrix(n
, [ei
[0]]*n
)
1100 # The addition of the diagonal matrix adds an extra ei[0] in the
1101 # upper-left corner of the matrix.
1102 Qi
[0,0] = Qi
[0,0] * ~
field(2)
1105 # The rank of the spin factor algebra is two, UNLESS we're in a
1106 # one-dimensional ambient space (the rank is bounded by the
1107 # ambient dimension).
1108 return FiniteDimensionalEuclideanJordanAlgebra(field
, Qs
, rank
=min(n
,2))