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,
56 fda
= super(FiniteDimensionalEuclideanJordanAlgebra
, self
)
65 Return a string representation of ``self``.
67 fmt
= "Euclidean Jordan algebra of degree {} over {}"
68 return fmt
.format(self
.degree(), self
.base_ring())
72 Return the rank of this EJA.
74 if self
._rank
is None:
75 raise ValueError("no rank specified at genesis")
80 class Element(FiniteDimensionalAlgebraElement
):
82 An element of a Euclidean Jordan algebra.
87 Return ``self`` raised to the power ``n``.
89 Jordan algebras are always power-associative; see for
90 example Faraut and Koranyi, Proposition II.1.2 (ii).
94 We have to override this because our superclass uses row vectors
95 instead of column vectors! We, on the other hand, assume column
100 sage: set_random_seed()
101 sage: x = random_eja().random_element()
102 sage: x.matrix()*x.vector() == (x**2).vector()
112 return A
.element_class(A
, (self
.matrix()**(n
-1))*self
.vector())
115 def characteristic_polynomial(self
):
117 Return my characteristic polynomial (if I'm a regular
120 Eventually this should be implemented in terms of the parent
121 algebra's characteristic polynomial that works for ALL
124 if self
.is_regular():
125 return self
.minimal_polynomial()
127 raise NotImplementedError('irregular element')
132 Return my determinant, the product of my eigenvalues.
136 sage: J = JordanSpinSimpleEJA(2)
137 sage: e0,e1 = J.gens()
141 sage: J = JordanSpinSimpleEJA(3)
142 sage: e0,e1,e2 = J.gens()
143 sage: x = e0 + e1 + e2
148 cs
= self
.characteristic_polynomial().coefficients(sparse
=False)
151 return cs
[0] * (-1)**r
153 raise ValueError('charpoly had no coefficients')
158 Return the Jordan-multiplicative inverse of this element.
160 We can't use the superclass method because it relies on the
161 algebra being associative.
165 The inverse in the spin factor algebra is given in Alizadeh's
168 sage: set_random_seed()
169 sage: n = ZZ.random_element(1,10).abs()
170 sage: J = JordanSpinSimpleEJA(n)
171 sage: x = J.random_element()
172 sage: while x.is_zero():
173 ....: x = J.random_element()
174 sage: x_vec = x.vector()
176 sage: x_bar = x_vec[1:]
177 sage: coeff = 1/(x0^2 - x_bar.inner_product(x_bar))
178 sage: inv_vec = x_vec.parent()([x0] + (-x_bar).list())
179 sage: x_inverse = coeff*inv_vec
180 sage: x.inverse() == J(x_inverse)
185 The identity element is its own inverse::
187 sage: set_random_seed()
188 sage: J = random_eja()
189 sage: J.one().inverse() == J.one()
192 If an element has an inverse, it acts like one. TODO: this
193 can be a lot less ugly once ``is_invertible`` doesn't crash
194 on irregular elements::
196 sage: set_random_seed()
197 sage: J = random_eja()
198 sage: x = J.random_element()
200 ....: x.inverse()*x == J.one()
206 if self
.parent().is_associative():
207 elt
= FiniteDimensionalAlgebraElement(self
.parent(), self
)
210 # TODO: we can do better once the call to is_invertible()
211 # doesn't crash on irregular elements.
212 #if not self.is_invertible():
213 # raise ArgumentError('element is not invertible')
215 # We do this a little different than the usual recursive
216 # call to a finite-dimensional algebra element, because we
217 # wind up with an inverse that lives in the subalgebra and
218 # we need information about the parent to convert it back.
219 V
= self
.span_of_powers()
220 assoc_subalg
= self
.subalgebra_generated_by()
221 # Mis-design warning: the basis used for span_of_powers()
222 # and subalgebra_generated_by() must be the same, and in
224 elt
= assoc_subalg(V
.coordinates(self
.vector()))
226 # This will be in the subalgebra's coordinates...
227 fda_elt
= FiniteDimensionalAlgebraElement(assoc_subalg
, elt
)
228 subalg_inverse
= fda_elt
.inverse()
230 # So we have to convert back...
231 basis
= [ self
.parent(v
) for v
in V
.basis() ]
232 pairs
= zip(subalg_inverse
.vector(), basis
)
233 return self
.parent().linear_combination(pairs
)
236 def is_invertible(self
):
238 Return whether or not this element is invertible.
240 We can't use the superclass method because it relies on
241 the algebra being associative.
243 return not self
.det().is_zero()
246 def is_nilpotent(self
):
248 Return whether or not some power of this element is zero.
250 The superclass method won't work unless we're in an
251 associative algebra, and we aren't. However, we generate
252 an assocoative subalgebra and we're nilpotent there if and
253 only if we're nilpotent here (probably).
257 The identity element is never nilpotent::
259 sage: set_random_seed()
260 sage: random_eja().one().is_nilpotent()
263 The additive identity is always nilpotent::
265 sage: set_random_seed()
266 sage: random_eja().zero().is_nilpotent()
270 # The element we're going to call "is_nilpotent()" on.
271 # Either myself, interpreted as an element of a finite-
272 # dimensional algebra, or an element of an associative
276 if self
.parent().is_associative():
277 elt
= FiniteDimensionalAlgebraElement(self
.parent(), self
)
279 V
= self
.span_of_powers()
280 assoc_subalg
= self
.subalgebra_generated_by()
281 # Mis-design warning: the basis used for span_of_powers()
282 # and subalgebra_generated_by() must be the same, and in
284 elt
= assoc_subalg(V
.coordinates(self
.vector()))
286 # Recursive call, but should work since elt lives in an
287 # associative algebra.
288 return elt
.is_nilpotent()
291 def is_regular(self
):
293 Return whether or not this is a regular element.
297 The identity element always has degree one, but any element
298 linearly-independent from it is regular::
300 sage: J = JordanSpinSimpleEJA(5)
301 sage: J.one().is_regular()
303 sage: e0, e1, e2, e3, e4 = J.gens() # e0 is the identity
304 sage: for x in J.gens():
305 ....: (J.one() + x).is_regular()
313 return self
.degree() == self
.parent().rank()
318 Compute the degree of this element the straightforward way
319 according to the definition; by appending powers to a list
320 and figuring out its dimension (that is, whether or not
321 they're linearly dependent).
325 sage: J = JordanSpinSimpleEJA(4)
326 sage: J.one().degree()
328 sage: e0,e1,e2,e3 = J.gens()
329 sage: (e0 - e1).degree()
332 In the spin factor algebra (of rank two), all elements that
333 aren't multiples of the identity are regular::
335 sage: set_random_seed()
336 sage: n = ZZ.random_element(1,10).abs()
337 sage: J = JordanSpinSimpleEJA(n)
338 sage: x = J.random_element()
339 sage: x == x.coefficient(0)*J.one() or x.degree() == 2
343 return self
.span_of_powers().dimension()
348 Return the matrix that represents left- (or right-)
349 multiplication by this element in the parent algebra.
351 We have to override this because the superclass method
352 returns a matrix that acts on row vectors (that is, on
355 fda_elt
= FiniteDimensionalAlgebraElement(self
.parent(), self
)
356 return fda_elt
.matrix().transpose()
359 def minimal_polynomial(self
):
363 sage: set_random_seed()
364 sage: x = random_eja().random_element()
365 sage: x.degree() == x.minimal_polynomial().degree()
370 sage: set_random_seed()
371 sage: x = random_eja().random_element()
372 sage: x.degree() == x.minimal_polynomial().degree()
375 The minimal polynomial and the characteristic polynomial coincide
376 and are known (see Alizadeh, Example 11.11) for all elements of
377 the spin factor algebra that aren't scalar multiples of the
380 sage: set_random_seed()
381 sage: n = ZZ.random_element(2,10).abs()
382 sage: J = JordanSpinSimpleEJA(n)
383 sage: y = J.random_element()
384 sage: while y == y.coefficient(0)*J.one():
385 ....: y = J.random_element()
386 sage: y0 = y.vector()[0]
387 sage: y_bar = y.vector()[1:]
388 sage: actual = y.minimal_polynomial()
389 sage: x = SR.symbol('x', domain='real')
390 sage: expected = x^2 - 2*y0*x + (y0^2 - norm(y_bar)^2)
391 sage: bool(actual == expected)
395 # The element we're going to call "minimal_polynomial()" on.
396 # Either myself, interpreted as an element of a finite-
397 # dimensional algebra, or an element of an associative
401 if self
.parent().is_associative():
402 elt
= FiniteDimensionalAlgebraElement(self
.parent(), self
)
404 V
= self
.span_of_powers()
405 assoc_subalg
= self
.subalgebra_generated_by()
406 # Mis-design warning: the basis used for span_of_powers()
407 # and subalgebra_generated_by() must be the same, and in
409 elt
= assoc_subalg(V
.coordinates(self
.vector()))
411 # Recursive call, but should work since elt lives in an
412 # associative algebra.
413 return elt
.minimal_polynomial()
416 def quadratic_representation(self
, other
=None):
418 Return the quadratic representation of this element.
422 The explicit form in the spin factor algebra is given by
423 Alizadeh's Example 11.12::
425 sage: set_random_seed()
426 sage: n = ZZ.random_element(1,10).abs()
427 sage: J = JordanSpinSimpleEJA(n)
428 sage: x = J.random_element()
429 sage: x_vec = x.vector()
431 sage: x_bar = x_vec[1:]
432 sage: A = matrix(QQ, 1, [x_vec.inner_product(x_vec)])
433 sage: B = 2*x0*x_bar.row()
434 sage: C = 2*x0*x_bar.column()
435 sage: D = identity_matrix(QQ, n-1)
436 sage: D = (x0^2 - x_bar.inner_product(x_bar))*D
437 sage: D = D + 2*x_bar.tensor_product(x_bar)
438 sage: Q = block_matrix(2,2,[A,B,C,D])
439 sage: Q == x.quadratic_representation()
442 Test all of the properties from Theorem 11.2 in Alizadeh::
444 sage: set_random_seed()
445 sage: J = random_eja()
446 sage: x = J.random_element()
447 sage: y = J.random_element()
451 sage: actual = x.quadratic_representation(y)
452 sage: expected = ( (x+y).quadratic_representation()
453 ....: -x.quadratic_representation()
454 ....: -y.quadratic_representation() ) / 2
455 sage: actual == expected
460 sage: alpha = QQ.random_element()
461 sage: actual = (alpha*x).quadratic_representation()
462 sage: expected = (alpha^2)*x.quadratic_representation()
463 sage: actual == expected
468 sage: Qy = y.quadratic_representation()
469 sage: actual = J(Qy*x.vector()).quadratic_representation()
470 sage: expected = Qy*x.quadratic_representation()*Qy
471 sage: actual == expected
476 sage: k = ZZ.random_element(1,10).abs()
477 sage: actual = (x^k).quadratic_representation()
478 sage: expected = (x.quadratic_representation())^k
479 sage: actual == expected
485 elif not other
in self
.parent():
486 raise ArgumentError("'other' must live in the same algebra")
488 return ( self
.matrix()*other
.matrix()
489 + other
.matrix()*self
.matrix()
490 - (self
*other
).matrix() )
493 def span_of_powers(self
):
495 Return the vector space spanned by successive powers of
498 # The dimension of the subalgebra can't be greater than
499 # the big algebra, so just put everything into a list
500 # and let span() get rid of the excess.
501 V
= self
.vector().parent()
502 return V
.span( (self
**d
).vector() for d
in xrange(V
.dimension()) )
505 def subalgebra_generated_by(self
):
507 Return the associative subalgebra of the parent EJA generated
512 sage: set_random_seed()
513 sage: x = random_eja().random_element()
514 sage: x.subalgebra_generated_by().is_associative()
517 Squaring in the subalgebra should be the same thing as
518 squaring in the superalgebra::
520 sage: set_random_seed()
521 sage: x = random_eja().random_element()
522 sage: u = x.subalgebra_generated_by().random_element()
523 sage: u.matrix()*u.vector() == (u**2).vector()
527 # First get the subspace spanned by the powers of myself...
528 V
= self
.span_of_powers()
531 # Now figure out the entries of the right-multiplication
532 # matrix for the successive basis elements b0, b1,... of
535 for b_right
in V
.basis():
536 eja_b_right
= self
.parent()(b_right
)
538 # The first row of the right-multiplication matrix by
539 # b1 is what we get if we apply that matrix to b1. The
540 # second row of the right multiplication matrix by b1
541 # is what we get when we apply that matrix to b2...
543 # IMPORTANT: this assumes that all vectors are COLUMN
544 # vectors, unlike our superclass (which uses row vectors).
545 for b_left
in V
.basis():
546 eja_b_left
= self
.parent()(b_left
)
547 # Multiply in the original EJA, but then get the
548 # coordinates from the subalgebra in terms of its
550 this_row
= V
.coordinates((eja_b_left
*eja_b_right
).vector())
551 b_right_rows
.append(this_row
)
552 b_right_matrix
= matrix(F
, b_right_rows
)
553 mats
.append(b_right_matrix
)
555 # It's an algebra of polynomials in one element, and EJAs
556 # are power-associative.
558 # TODO: choose generator names intelligently.
559 return FiniteDimensionalEuclideanJordanAlgebra(F
, mats
, assume_associative
=True, names
='f')
562 def subalgebra_idempotent(self
):
564 Find an idempotent in the associative subalgebra I generate
565 using Proposition 2.3.5 in Baes.
569 sage: set_random_seed()
571 sage: c = J.random_element().subalgebra_idempotent()
574 sage: J = JordanSpinSimpleEJA(5)
575 sage: c = J.random_element().subalgebra_idempotent()
580 if self
.is_nilpotent():
581 raise ValueError("this only works with non-nilpotent elements!")
583 V
= self
.span_of_powers()
584 J
= self
.subalgebra_generated_by()
585 # Mis-design warning: the basis used for span_of_powers()
586 # and subalgebra_generated_by() must be the same, and in
588 u
= J(V
.coordinates(self
.vector()))
590 # The image of the matrix of left-u^m-multiplication
591 # will be minimal for some natural number s...
593 minimal_dim
= V
.dimension()
594 for i
in xrange(1, V
.dimension()):
595 this_dim
= (u
**i
).matrix().image().dimension()
596 if this_dim
< minimal_dim
:
597 minimal_dim
= this_dim
600 # Now minimal_matrix should correspond to the smallest
601 # non-zero subspace in Baes's (or really, Koecher's)
604 # However, we need to restrict the matrix to work on the
605 # subspace... or do we? Can't we just solve, knowing that
606 # A(c) = u^(s+1) should have a solution in the big space,
609 # Beware, solve_right() means that we're using COLUMN vectors.
610 # Our FiniteDimensionalAlgebraElement superclass uses rows.
613 c_coordinates
= A
.solve_right(u_next
.vector())
615 # Now c_coordinates is the idempotent we want, but it's in
616 # the coordinate system of the subalgebra.
618 # We need the basis for J, but as elements of the parent algebra.
620 basis
= [self
.parent(v
) for v
in V
.basis()]
621 return self
.parent().linear_combination(zip(c_coordinates
, basis
))
626 Return my trace, the sum of my eigenvalues.
630 sage: J = JordanSpinSimpleEJA(3)
631 sage: e0,e1,e2 = J.gens()
632 sage: x = e0 + e1 + e2
637 cs
= self
.characteristic_polynomial().coefficients(sparse
=False)
641 raise ValueError('charpoly had fewer than 2 coefficients')
644 def trace_inner_product(self
, other
):
646 Return the trace inner product of myself and ``other``.
648 if not other
in self
.parent():
649 raise ArgumentError("'other' must live in the same algebra")
651 return (self
*other
).trace()
654 def eja_rn(dimension
, field
=QQ
):
656 Return the Euclidean Jordan Algebra corresponding to the set
657 `R^n` under the Hadamard product.
661 This multiplication table can be verified by hand::
664 sage: e0,e1,e2 = J.gens()
679 # The FiniteDimensionalAlgebra constructor takes a list of
680 # matrices, the ith representing right multiplication by the ith
681 # basis element in the vector space. So if e_1 = (1,0,0), then
682 # right (Hadamard) multiplication of x by e_1 picks out the first
683 # component of x; and likewise for the ith basis element e_i.
684 Qs
= [ matrix(field
, dimension
, dimension
, lambda k
,j
: 1*(k
== j
== i
))
685 for i
in xrange(dimension
) ]
687 return FiniteDimensionalEuclideanJordanAlgebra(field
,Qs
,rank
=dimension
)
693 Return a "random" finite-dimensional Euclidean Jordan Algebra.
697 For now, we choose a random natural number ``n`` (greater than zero)
698 and then give you back one of the following:
700 * The cartesian product of the rational numbers ``n`` times; this is
701 ``QQ^n`` with the Hadamard product.
703 * The Jordan spin algebra on ``QQ^n``.
705 * The ``n``-by-``n`` rational symmetric matrices with the symmetric
708 Later this might be extended to return Cartesian products of the
714 Euclidean Jordan algebra of degree...
717 n
= ZZ
.random_element(1,5).abs()
718 constructor
= choice([eja_rn
,
720 RealSymmetricSimpleEJA
,
721 ComplexHermitianSimpleEJA
])
722 return constructor(n
, field
=QQ
)
726 def _real_symmetric_basis(n
, field
=QQ
):
728 Return a basis for the space of real symmetric n-by-n matrices.
730 # The basis of symmetric matrices, as matrices, in their R^(n-by-n)
734 for j
in xrange(i
+1):
735 Eij
= matrix(field
, n
, lambda k
,l
: k
==i
and l
==j
)
739 # Beware, orthogonal but not normalized!
740 Sij
= Eij
+ Eij
.transpose()
745 def _complex_hermitian_basis(n
, field
=QQ
):
747 Returns a basis for the space of complex Hermitian n-by-n matrices.
751 sage: set_random_seed()
752 sage: n = ZZ.random_element(1,5).abs()
753 sage: all( M.is_symmetric() for M in _complex_hermitian_basis(n) )
757 F
= QuadraticField(-1, 'I')
760 # This is like the symmetric case, but we need to be careful:
762 # * We want conjugate-symmetry, not just symmetry.
763 # * The diagonal will (as a result) be real.
767 for j
in xrange(i
+1):
768 Eij
= matrix(field
, n
, lambda k
,l
: k
==i
and l
==j
)
770 Sij
= _embed_complex_matrix(Eij
)
773 # Beware, orthogonal but not normalized! The second one
774 # has a minus because it's conjugated.
775 Sij_real
= _embed_complex_matrix(Eij
+ Eij
.transpose())
777 Sij_imag
= _embed_complex_matrix(I
*Eij
- I
*Eij
.transpose())
782 def _multiplication_table_from_matrix_basis(basis
):
784 At least three of the five simple Euclidean Jordan algebras have the
785 symmetric multiplication (A,B) |-> (AB + BA)/2, where the
786 multiplication on the right is matrix multiplication. Given a basis
787 for the underlying matrix space, this function returns a
788 multiplication table (obtained by looping through the basis
789 elements) for an algebra of those matrices.
791 # In S^2, for example, we nominally have four coordinates even
792 # though the space is of dimension three only. The vector space V
793 # is supposed to hold the entire long vector, and the subspace W
794 # of V will be spanned by the vectors that arise from symmetric
795 # matrices. Thus for S^2, dim(V) == 4 and dim(W) == 3.
796 field
= basis
[0].base_ring()
797 dimension
= basis
[0].nrows()
800 return vector(field
, m
.list())
803 return matrix(field
, dimension
, v
.list())
805 V
= VectorSpace(field
, dimension
**2)
806 W
= V
.span( mat2vec(s
) for s
in basis
)
808 # Taking the span above reorders our basis (thanks, jerk!) so we
809 # need to put our "matrix basis" in the same order as the
810 # (reordered) vector basis.
811 S
= [ vec2mat(b
) for b
in W
.basis() ]
815 # Brute force the multiplication-by-s matrix by looping
816 # through all elements of the basis and doing the computation
817 # to find out what the corresponding row should be. BEWARE:
818 # these multiplication tables won't be symmetric! It therefore
819 # becomes REALLY IMPORTANT that the underlying algebra
820 # constructor uses ROW vectors and not COLUMN vectors. That's
821 # why we're computing rows here and not columns.
824 this_row
= mat2vec((s
*t
+ t
*s
)/2)
825 Q_rows
.append(W
.coordinates(this_row
))
826 Q
= matrix(field
, W
.dimension(), Q_rows
)
832 def _embed_complex_matrix(M
):
834 Embed the n-by-n complex matrix ``M`` into the space of real
835 matrices of size 2n-by-2n via the map the sends each entry `z = a +
836 bi` to the block matrix ``[[a,b],[-b,a]]``.
840 sage: F = QuadraticField(-1,'i')
841 sage: x1 = F(4 - 2*i)
842 sage: x2 = F(1 + 2*i)
845 sage: M = matrix(F,2,[x1,x2,x3,x4])
846 sage: _embed_complex_matrix(M)
856 raise ArgumentError("the matrix 'M' must be square")
857 field
= M
.base_ring()
862 blocks
.append(matrix(field
, 2, [[a
,-b
],[b
,a
]]))
864 # We can drop the imaginaries here.
865 return block_matrix(field
.base_ring(), n
, blocks
)
868 def _unembed_complex_matrix(M
):
870 The inverse of _embed_complex_matrix().
874 sage: A = matrix(QQ,[ [ 1, 2, 3, 4],
875 ....: [-2, 1, -4, 3],
876 ....: [ 9, 10, 11, 12],
877 ....: [-10, 9, -12, 11] ])
878 sage: _unembed_complex_matrix(A)
880 [ -10*i + 9 -12*i + 11]
884 raise ArgumentError("the matrix 'M' must be square")
885 if not n
.mod(2).is_zero():
886 raise ArgumentError("the matrix 'M' must be a complex embedding")
888 F
= QuadraticField(-1, 'i')
891 # Go top-left to bottom-right (reading order), converting every
892 # 2-by-2 block we see to a single complex element.
894 for k
in xrange(n
/2):
895 for j
in xrange(n
/2):
896 submat
= M
[2*k
:2*k
+2,2*j
:2*j
+2]
897 if submat
[0,0] != submat
[1,1]:
898 raise ArgumentError('bad real submatrix')
899 if submat
[0,1] != -submat
[1,0]:
900 raise ArgumentError('bad imag submatrix')
901 z
= submat
[0,0] + submat
[1,0]*i
904 return matrix(F
, n
/2, elements
)
907 def RealSymmetricSimpleEJA(n
, field
=QQ
):
909 The rank-n simple EJA consisting of real symmetric n-by-n
910 matrices, the usual symmetric Jordan product, and the trace inner
911 product. It has dimension `(n^2 + n)/2` over the reals.
915 sage: J = RealSymmetricSimpleEJA(2)
916 sage: e0, e1, e2 = J.gens()
926 The degree of this algebra is `(n^2 + n) / 2`::
928 sage: set_random_seed()
929 sage: n = ZZ.random_element(1,5).abs()
930 sage: J = RealSymmetricSimpleEJA(n)
931 sage: J.degree() == (n^2 + n)/2
935 S
= _real_symmetric_basis(n
, field
=field
)
936 Qs
= _multiplication_table_from_matrix_basis(S
)
938 return FiniteDimensionalEuclideanJordanAlgebra(field
,Qs
,rank
=n
)
941 def ComplexHermitianSimpleEJA(n
, field
=QQ
):
943 The rank-n simple EJA consisting of complex Hermitian n-by-n
944 matrices over the real numbers, the usual symmetric Jordan product,
945 and the real-part-of-trace inner product. It has dimension `n^2` over
950 The degree of this algebra is `n^2`::
952 sage: set_random_seed()
953 sage: n = ZZ.random_element(1,5).abs()
954 sage: J = ComplexHermitianSimpleEJA(n)
955 sage: J.degree() == n^2
959 S
= _complex_hermitian_basis(n
)
960 Qs
= _multiplication_table_from_matrix_basis(S
)
961 return FiniteDimensionalEuclideanJordanAlgebra(field
, Qs
, rank
=n
)
964 def QuaternionHermitianSimpleEJA(n
):
966 The rank-n simple EJA consisting of self-adjoint n-by-n quaternion
967 matrices, the usual symmetric Jordan product, and the
968 real-part-of-trace inner product. It has dimension `2n^2 - n` over
973 def OctonionHermitianSimpleEJA(n
):
975 This shit be crazy. It has dimension 27 over the reals.
980 def JordanSpinSimpleEJA(n
, field
=QQ
):
982 The rank-2 simple EJA consisting of real vectors ``x=(x0, x_bar)``
983 with the usual inner product and jordan product ``x*y =
984 (<x_bar,y_bar>, x0*y_bar + y0*x_bar)``. It has dimension `n` over
989 This multiplication table can be verified by hand::
991 sage: J = JordanSpinSimpleEJA(4)
992 sage: e0,e1,e2,e3 = J.gens()
1008 In one dimension, this is the reals under multiplication::
1010 sage: J1 = JordanSpinSimpleEJA(1)
1011 sage: J2 = eja_rn(1)
1017 id_matrix
= identity_matrix(field
, n
)
1019 ei
= id_matrix
.column(i
)
1020 Qi
= zero_matrix(field
, n
)
1022 Qi
.set_column(0, ei
)
1023 Qi
+= diagonal_matrix(n
, [ei
[0]]*n
)
1024 # The addition of the diagonal matrix adds an extra ei[0] in the
1025 # upper-left corner of the matrix.
1026 Qi
[0,0] = Qi
[0,0] * ~
field(2)
1029 # The rank of the spin factor algebra is two, UNLESS we're in a
1030 # one-dimensional ambient space (the rank is bounded by the
1031 # ambient dimension).
1032 return FiniteDimensionalEuclideanJordanAlgebra(field
, Qs
, rank
=min(n
,2))