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
127 We also know that powers operator-commute (Koecher, Chapter
130 sage: set_random_seed()
131 sage: x = random_eja().random_element()
132 sage: m = ZZ.random_element(0,10)
133 sage: n = ZZ.random_element(0,10)
134 sage: Lxm = (x^m).matrix()
135 sage: Lxn = (x^n).matrix()
136 sage: Lxm*Lxn == Lxn*Lxm
146 return A
.element_class(A
, (self
.matrix()**(n
-1))*self
.vector())
149 def characteristic_polynomial(self
):
151 Return my characteristic polynomial (if I'm a regular
154 Eventually this should be implemented in terms of the parent
155 algebra's characteristic polynomial that works for ALL
158 if self
.is_regular():
159 return self
.minimal_polynomial()
161 raise NotImplementedError('irregular element')
166 Return my determinant, the product of my eigenvalues.
170 sage: J = JordanSpinSimpleEJA(2)
171 sage: e0,e1 = J.gens()
175 sage: J = JordanSpinSimpleEJA(3)
176 sage: e0,e1,e2 = J.gens()
177 sage: x = e0 + e1 + e2
182 cs
= self
.characteristic_polynomial().coefficients(sparse
=False)
185 return cs
[0] * (-1)**r
187 raise ValueError('charpoly had no coefficients')
192 Return the Jordan-multiplicative inverse of this element.
194 We can't use the superclass method because it relies on the
195 algebra being associative.
199 The inverse in the spin factor algebra is given in Alizadeh's
202 sage: set_random_seed()
203 sage: n = ZZ.random_element(1,10)
204 sage: J = JordanSpinSimpleEJA(n)
205 sage: x = J.random_element()
206 sage: while x.is_zero():
207 ....: x = J.random_element()
208 sage: x_vec = x.vector()
210 sage: x_bar = x_vec[1:]
211 sage: coeff = 1/(x0^2 - x_bar.inner_product(x_bar))
212 sage: inv_vec = x_vec.parent()([x0] + (-x_bar).list())
213 sage: x_inverse = coeff*inv_vec
214 sage: x.inverse() == J(x_inverse)
219 The identity element is its own inverse::
221 sage: set_random_seed()
222 sage: J = random_eja()
223 sage: J.one().inverse() == J.one()
226 If an element has an inverse, it acts like one. TODO: this
227 can be a lot less ugly once ``is_invertible`` doesn't crash
228 on irregular elements::
230 sage: set_random_seed()
231 sage: J = random_eja()
232 sage: x = J.random_element()
234 ....: x.inverse()*x == J.one()
240 if self
.parent().is_associative():
241 elt
= FiniteDimensionalAlgebraElement(self
.parent(), self
)
244 # TODO: we can do better once the call to is_invertible()
245 # doesn't crash on irregular elements.
246 #if not self.is_invertible():
247 # raise ArgumentError('element is not invertible')
249 # We do this a little different than the usual recursive
250 # call to a finite-dimensional algebra element, because we
251 # wind up with an inverse that lives in the subalgebra and
252 # we need information about the parent to convert it back.
253 V
= self
.span_of_powers()
254 assoc_subalg
= self
.subalgebra_generated_by()
255 # Mis-design warning: the basis used for span_of_powers()
256 # and subalgebra_generated_by() must be the same, and in
258 elt
= assoc_subalg(V
.coordinates(self
.vector()))
260 # This will be in the subalgebra's coordinates...
261 fda_elt
= FiniteDimensionalAlgebraElement(assoc_subalg
, elt
)
262 subalg_inverse
= fda_elt
.inverse()
264 # So we have to convert back...
265 basis
= [ self
.parent(v
) for v
in V
.basis() ]
266 pairs
= zip(subalg_inverse
.vector(), basis
)
267 return self
.parent().linear_combination(pairs
)
270 def is_invertible(self
):
272 Return whether or not this element is invertible.
274 We can't use the superclass method because it relies on
275 the algebra being associative.
277 return not self
.det().is_zero()
280 def is_nilpotent(self
):
282 Return whether or not some power of this element is zero.
284 The superclass method won't work unless we're in an
285 associative algebra, and we aren't. However, we generate
286 an assocoative subalgebra and we're nilpotent there if and
287 only if we're nilpotent here (probably).
291 The identity element is never nilpotent::
293 sage: set_random_seed()
294 sage: random_eja().one().is_nilpotent()
297 The additive identity is always nilpotent::
299 sage: set_random_seed()
300 sage: random_eja().zero().is_nilpotent()
304 # The element we're going to call "is_nilpotent()" on.
305 # Either myself, interpreted as an element of a finite-
306 # dimensional algebra, or an element of an associative
310 if self
.parent().is_associative():
311 elt
= FiniteDimensionalAlgebraElement(self
.parent(), self
)
313 V
= self
.span_of_powers()
314 assoc_subalg
= self
.subalgebra_generated_by()
315 # Mis-design warning: the basis used for span_of_powers()
316 # and subalgebra_generated_by() must be the same, and in
318 elt
= assoc_subalg(V
.coordinates(self
.vector()))
320 # Recursive call, but should work since elt lives in an
321 # associative algebra.
322 return elt
.is_nilpotent()
325 def is_regular(self
):
327 Return whether or not this is a regular element.
331 The identity element always has degree one, but any element
332 linearly-independent from it is regular::
334 sage: J = JordanSpinSimpleEJA(5)
335 sage: J.one().is_regular()
337 sage: e0, e1, e2, e3, e4 = J.gens() # e0 is the identity
338 sage: for x in J.gens():
339 ....: (J.one() + x).is_regular()
347 return self
.degree() == self
.parent().rank()
352 Compute the degree of this element the straightforward way
353 according to the definition; by appending powers to a list
354 and figuring out its dimension (that is, whether or not
355 they're linearly dependent).
359 sage: J = JordanSpinSimpleEJA(4)
360 sage: J.one().degree()
362 sage: e0,e1,e2,e3 = J.gens()
363 sage: (e0 - e1).degree()
366 In the spin factor algebra (of rank two), all elements that
367 aren't multiples of the identity are regular::
369 sage: set_random_seed()
370 sage: n = ZZ.random_element(1,10)
371 sage: J = JordanSpinSimpleEJA(n)
372 sage: x = J.random_element()
373 sage: x == x.coefficient(0)*J.one() or x.degree() == 2
377 return self
.span_of_powers().dimension()
382 Return the matrix that represents left- (or right-)
383 multiplication by this element in the parent algebra.
385 We have to override this because the superclass method
386 returns a matrix that acts on row vectors (that is, on
391 Test the first polarization identity from my notes, Koecher Chapter
392 III, or from Baes (2.3)::
394 sage: set_random_seed()
395 sage: J = random_eja()
396 sage: x = J.random_element()
397 sage: y = J.random_element()
398 sage: Lx = x.matrix()
399 sage: Ly = y.matrix()
400 sage: Lxx = (x*x).matrix()
401 sage: Lxy = (x*y).matrix()
402 sage: bool(2*Lx*Lxy + Ly*Lxx == 2*Lxy*Lx + Lxx*Ly)
405 Test the second polarization identity from my notes or from
408 sage: set_random_seed()
409 sage: J = random_eja()
410 sage: x = J.random_element()
411 sage: y = J.random_element()
412 sage: z = J.random_element()
413 sage: Lx = x.matrix()
414 sage: Ly = y.matrix()
415 sage: Lz = z.matrix()
416 sage: Lzy = (z*y).matrix()
417 sage: Lxy = (x*y).matrix()
418 sage: Lxz = (x*z).matrix()
419 sage: bool(Lx*Lzy + Lz*Lxy + Ly*Lxz == Lzy*Lx + Lxy*Lz + Lxz*Ly)
422 Test the third polarization identity from my notes or from
425 sage: set_random_seed()
426 sage: J = random_eja()
427 sage: u = J.random_element()
428 sage: y = J.random_element()
429 sage: z = J.random_element()
430 sage: Lu = u.matrix()
431 sage: Ly = y.matrix()
432 sage: Lz = z.matrix()
433 sage: Lzy = (z*y).matrix()
434 sage: Luy = (u*y).matrix()
435 sage: Luz = (u*z).matrix()
436 sage: Luyz = (u*(y*z)).matrix()
437 sage: lhs = Lu*Lzy + Lz*Luy + Ly*Luz
438 sage: rhs = Luyz + Ly*Lu*Lz + Lz*Lu*Ly
439 sage: bool(lhs == rhs)
443 fda_elt
= FiniteDimensionalAlgebraElement(self
.parent(), self
)
444 return fda_elt
.matrix().transpose()
447 def minimal_polynomial(self
):
451 sage: set_random_seed()
452 sage: x = random_eja().random_element()
453 sage: x.degree() == x.minimal_polynomial().degree()
458 sage: set_random_seed()
459 sage: x = random_eja().random_element()
460 sage: x.degree() == x.minimal_polynomial().degree()
463 The minimal polynomial and the characteristic polynomial coincide
464 and are known (see Alizadeh, Example 11.11) for all elements of
465 the spin factor algebra that aren't scalar multiples of the
468 sage: set_random_seed()
469 sage: n = ZZ.random_element(2,10)
470 sage: J = JordanSpinSimpleEJA(n)
471 sage: y = J.random_element()
472 sage: while y == y.coefficient(0)*J.one():
473 ....: y = J.random_element()
474 sage: y0 = y.vector()[0]
475 sage: y_bar = y.vector()[1:]
476 sage: actual = y.minimal_polynomial()
477 sage: x = SR.symbol('x', domain='real')
478 sage: expected = x^2 - 2*y0*x + (y0^2 - norm(y_bar)^2)
479 sage: bool(actual == expected)
483 # The element we're going to call "minimal_polynomial()" on.
484 # Either myself, interpreted as an element of a finite-
485 # dimensional algebra, or an element of an associative
489 if self
.parent().is_associative():
490 elt
= FiniteDimensionalAlgebraElement(self
.parent(), self
)
492 V
= self
.span_of_powers()
493 assoc_subalg
= self
.subalgebra_generated_by()
494 # Mis-design warning: the basis used for span_of_powers()
495 # and subalgebra_generated_by() must be the same, and in
497 elt
= assoc_subalg(V
.coordinates(self
.vector()))
499 # Recursive call, but should work since elt lives in an
500 # associative algebra.
501 return elt
.minimal_polynomial()
504 def quadratic_representation(self
, other
=None):
506 Return the quadratic representation of this element.
510 The explicit form in the spin factor algebra is given by
511 Alizadeh's Example 11.12::
513 sage: set_random_seed()
514 sage: n = ZZ.random_element(1,10)
515 sage: J = JordanSpinSimpleEJA(n)
516 sage: x = J.random_element()
517 sage: x_vec = x.vector()
519 sage: x_bar = x_vec[1:]
520 sage: A = matrix(QQ, 1, [x_vec.inner_product(x_vec)])
521 sage: B = 2*x0*x_bar.row()
522 sage: C = 2*x0*x_bar.column()
523 sage: D = identity_matrix(QQ, n-1)
524 sage: D = (x0^2 - x_bar.inner_product(x_bar))*D
525 sage: D = D + 2*x_bar.tensor_product(x_bar)
526 sage: Q = block_matrix(2,2,[A,B,C,D])
527 sage: Q == x.quadratic_representation()
530 Test all of the properties from Theorem 11.2 in Alizadeh::
532 sage: set_random_seed()
533 sage: J = random_eja()
534 sage: x = J.random_element()
535 sage: y = J.random_element()
539 sage: actual = x.quadratic_representation(y)
540 sage: expected = ( (x+y).quadratic_representation()
541 ....: -x.quadratic_representation()
542 ....: -y.quadratic_representation() ) / 2
543 sage: actual == expected
548 sage: alpha = QQ.random_element()
549 sage: actual = (alpha*x).quadratic_representation()
550 sage: expected = (alpha^2)*x.quadratic_representation()
551 sage: actual == expected
556 sage: Qy = y.quadratic_representation()
557 sage: actual = J(Qy*x.vector()).quadratic_representation()
558 sage: expected = Qy*x.quadratic_representation()*Qy
559 sage: actual == expected
564 sage: k = ZZ.random_element(1,10)
565 sage: actual = (x^k).quadratic_representation()
566 sage: expected = (x.quadratic_representation())^k
567 sage: actual == expected
573 elif not other
in self
.parent():
574 raise ArgumentError("'other' must live in the same algebra")
576 return ( self
.matrix()*other
.matrix()
577 + other
.matrix()*self
.matrix()
578 - (self
*other
).matrix() )
581 def span_of_powers(self
):
583 Return the vector space spanned by successive powers of
586 # The dimension of the subalgebra can't be greater than
587 # the big algebra, so just put everything into a list
588 # and let span() get rid of the excess.
589 V
= self
.vector().parent()
590 return V
.span( (self
**d
).vector() for d
in xrange(V
.dimension()) )
593 def subalgebra_generated_by(self
):
595 Return the associative subalgebra of the parent EJA generated
600 sage: set_random_seed()
601 sage: x = random_eja().random_element()
602 sage: x.subalgebra_generated_by().is_associative()
605 Squaring in the subalgebra should be the same thing as
606 squaring in the superalgebra::
608 sage: set_random_seed()
609 sage: x = random_eja().random_element()
610 sage: u = x.subalgebra_generated_by().random_element()
611 sage: u.matrix()*u.vector() == (u**2).vector()
615 # First get the subspace spanned by the powers of myself...
616 V
= self
.span_of_powers()
619 # Now figure out the entries of the right-multiplication
620 # matrix for the successive basis elements b0, b1,... of
623 for b_right
in V
.basis():
624 eja_b_right
= self
.parent()(b_right
)
626 # The first row of the right-multiplication matrix by
627 # b1 is what we get if we apply that matrix to b1. The
628 # second row of the right multiplication matrix by b1
629 # is what we get when we apply that matrix to b2...
631 # IMPORTANT: this assumes that all vectors are COLUMN
632 # vectors, unlike our superclass (which uses row vectors).
633 for b_left
in V
.basis():
634 eja_b_left
= self
.parent()(b_left
)
635 # Multiply in the original EJA, but then get the
636 # coordinates from the subalgebra in terms of its
638 this_row
= V
.coordinates((eja_b_left
*eja_b_right
).vector())
639 b_right_rows
.append(this_row
)
640 b_right_matrix
= matrix(F
, b_right_rows
)
641 mats
.append(b_right_matrix
)
643 # It's an algebra of polynomials in one element, and EJAs
644 # are power-associative.
646 # TODO: choose generator names intelligently.
647 return FiniteDimensionalEuclideanJordanAlgebra(F
, mats
, assume_associative
=True, names
='f')
650 def subalgebra_idempotent(self
):
652 Find an idempotent in the associative subalgebra I generate
653 using Proposition 2.3.5 in Baes.
657 sage: set_random_seed()
659 sage: c = J.random_element().subalgebra_idempotent()
662 sage: J = JordanSpinSimpleEJA(5)
663 sage: c = J.random_element().subalgebra_idempotent()
668 if self
.is_nilpotent():
669 raise ValueError("this only works with non-nilpotent elements!")
671 V
= self
.span_of_powers()
672 J
= self
.subalgebra_generated_by()
673 # Mis-design warning: the basis used for span_of_powers()
674 # and subalgebra_generated_by() must be the same, and in
676 u
= J(V
.coordinates(self
.vector()))
678 # The image of the matrix of left-u^m-multiplication
679 # will be minimal for some natural number s...
681 minimal_dim
= V
.dimension()
682 for i
in xrange(1, V
.dimension()):
683 this_dim
= (u
**i
).matrix().image().dimension()
684 if this_dim
< minimal_dim
:
685 minimal_dim
= this_dim
688 # Now minimal_matrix should correspond to the smallest
689 # non-zero subspace in Baes's (or really, Koecher's)
692 # However, we need to restrict the matrix to work on the
693 # subspace... or do we? Can't we just solve, knowing that
694 # A(c) = u^(s+1) should have a solution in the big space,
697 # Beware, solve_right() means that we're using COLUMN vectors.
698 # Our FiniteDimensionalAlgebraElement superclass uses rows.
701 c_coordinates
= A
.solve_right(u_next
.vector())
703 # Now c_coordinates is the idempotent we want, but it's in
704 # the coordinate system of the subalgebra.
706 # We need the basis for J, but as elements of the parent algebra.
708 basis
= [self
.parent(v
) for v
in V
.basis()]
709 return self
.parent().linear_combination(zip(c_coordinates
, basis
))
714 Return my trace, the sum of my eigenvalues.
718 sage: J = JordanSpinSimpleEJA(3)
719 sage: e0,e1,e2 = J.gens()
720 sage: x = e0 + e1 + e2
725 cs
= self
.characteristic_polynomial().coefficients(sparse
=False)
729 raise ValueError('charpoly had fewer than 2 coefficients')
732 def trace_inner_product(self
, other
):
734 Return the trace inner product of myself and ``other``.
736 if not other
in self
.parent():
737 raise ArgumentError("'other' must live in the same algebra")
739 return (self
*other
).trace()
742 def eja_rn(dimension
, field
=QQ
):
744 Return the Euclidean Jordan Algebra corresponding to the set
745 `R^n` under the Hadamard product.
749 This multiplication table can be verified by hand::
752 sage: e0,e1,e2 = J.gens()
767 # The FiniteDimensionalAlgebra constructor takes a list of
768 # matrices, the ith representing right multiplication by the ith
769 # basis element in the vector space. So if e_1 = (1,0,0), then
770 # right (Hadamard) multiplication of x by e_1 picks out the first
771 # component of x; and likewise for the ith basis element e_i.
772 Qs
= [ matrix(field
, dimension
, dimension
, lambda k
,j
: 1*(k
== j
== i
))
773 for i
in xrange(dimension
) ]
775 return FiniteDimensionalEuclideanJordanAlgebra(field
,Qs
,rank
=dimension
)
781 Return a "random" finite-dimensional Euclidean Jordan Algebra.
785 For now, we choose a random natural number ``n`` (greater than zero)
786 and then give you back one of the following:
788 * The cartesian product of the rational numbers ``n`` times; this is
789 ``QQ^n`` with the Hadamard product.
791 * The Jordan spin algebra on ``QQ^n``.
793 * The ``n``-by-``n`` rational symmetric matrices with the symmetric
796 Later this might be extended to return Cartesian products of the
802 Euclidean Jordan algebra of degree...
805 n
= ZZ
.random_element(1,5)
806 constructor
= choice([eja_rn
,
808 RealSymmetricSimpleEJA
,
809 ComplexHermitianSimpleEJA
])
810 return constructor(n
, field
=QQ
)
814 def _real_symmetric_basis(n
, field
=QQ
):
816 Return a basis for the space of real symmetric n-by-n matrices.
818 # The basis of symmetric matrices, as matrices, in their R^(n-by-n)
822 for j
in xrange(i
+1):
823 Eij
= matrix(field
, n
, lambda k
,l
: k
==i
and l
==j
)
827 # Beware, orthogonal but not normalized!
828 Sij
= Eij
+ Eij
.transpose()
833 def _complex_hermitian_basis(n
, field
=QQ
):
835 Returns a basis for the space of complex Hermitian n-by-n matrices.
839 sage: set_random_seed()
840 sage: n = ZZ.random_element(1,5)
841 sage: all( M.is_symmetric() for M in _complex_hermitian_basis(n) )
845 F
= QuadraticField(-1, 'I')
848 # This is like the symmetric case, but we need to be careful:
850 # * We want conjugate-symmetry, not just symmetry.
851 # * The diagonal will (as a result) be real.
855 for j
in xrange(i
+1):
856 Eij
= matrix(field
, n
, lambda k
,l
: k
==i
and l
==j
)
858 Sij
= _embed_complex_matrix(Eij
)
861 # Beware, orthogonal but not normalized! The second one
862 # has a minus because it's conjugated.
863 Sij_real
= _embed_complex_matrix(Eij
+ Eij
.transpose())
865 Sij_imag
= _embed_complex_matrix(I
*Eij
- I
*Eij
.transpose())
870 def _multiplication_table_from_matrix_basis(basis
):
872 At least three of the five simple Euclidean Jordan algebras have the
873 symmetric multiplication (A,B) |-> (AB + BA)/2, where the
874 multiplication on the right is matrix multiplication. Given a basis
875 for the underlying matrix space, this function returns a
876 multiplication table (obtained by looping through the basis
877 elements) for an algebra of those matrices.
879 # In S^2, for example, we nominally have four coordinates even
880 # though the space is of dimension three only. The vector space V
881 # is supposed to hold the entire long vector, and the subspace W
882 # of V will be spanned by the vectors that arise from symmetric
883 # matrices. Thus for S^2, dim(V) == 4 and dim(W) == 3.
884 field
= basis
[0].base_ring()
885 dimension
= basis
[0].nrows()
888 return vector(field
, m
.list())
891 return matrix(field
, dimension
, v
.list())
893 V
= VectorSpace(field
, dimension
**2)
894 W
= V
.span( mat2vec(s
) for s
in basis
)
896 # Taking the span above reorders our basis (thanks, jerk!) so we
897 # need to put our "matrix basis" in the same order as the
898 # (reordered) vector basis.
899 S
= [ vec2mat(b
) for b
in W
.basis() ]
903 # Brute force the multiplication-by-s matrix by looping
904 # through all elements of the basis and doing the computation
905 # to find out what the corresponding row should be. BEWARE:
906 # these multiplication tables won't be symmetric! It therefore
907 # becomes REALLY IMPORTANT that the underlying algebra
908 # constructor uses ROW vectors and not COLUMN vectors. That's
909 # why we're computing rows here and not columns.
912 this_row
= mat2vec((s
*t
+ t
*s
)/2)
913 Q_rows
.append(W
.coordinates(this_row
))
914 Q
= matrix(field
, W
.dimension(), Q_rows
)
920 def _embed_complex_matrix(M
):
922 Embed the n-by-n complex matrix ``M`` into the space of real
923 matrices of size 2n-by-2n via the map the sends each entry `z = a +
924 bi` to the block matrix ``[[a,b],[-b,a]]``.
928 sage: F = QuadraticField(-1,'i')
929 sage: x1 = F(4 - 2*i)
930 sage: x2 = F(1 + 2*i)
933 sage: M = matrix(F,2,[x1,x2,x3,x4])
934 sage: _embed_complex_matrix(M)
944 raise ArgumentError("the matrix 'M' must be square")
945 field
= M
.base_ring()
950 blocks
.append(matrix(field
, 2, [[a
,-b
],[b
,a
]]))
952 # We can drop the imaginaries here.
953 return block_matrix(field
.base_ring(), n
, blocks
)
956 def _unembed_complex_matrix(M
):
958 The inverse of _embed_complex_matrix().
962 sage: A = matrix(QQ,[ [ 1, 2, 3, 4],
963 ....: [-2, 1, -4, 3],
964 ....: [ 9, 10, 11, 12],
965 ....: [-10, 9, -12, 11] ])
966 sage: _unembed_complex_matrix(A)
968 [ -10*i + 9 -12*i + 11]
972 raise ArgumentError("the matrix 'M' must be square")
973 if not n
.mod(2).is_zero():
974 raise ArgumentError("the matrix 'M' must be a complex embedding")
976 F
= QuadraticField(-1, 'i')
979 # Go top-left to bottom-right (reading order), converting every
980 # 2-by-2 block we see to a single complex element.
982 for k
in xrange(n
/2):
983 for j
in xrange(n
/2):
984 submat
= M
[2*k
:2*k
+2,2*j
:2*j
+2]
985 if submat
[0,0] != submat
[1,1]:
986 raise ArgumentError('bad real submatrix')
987 if submat
[0,1] != -submat
[1,0]:
988 raise ArgumentError('bad imag submatrix')
989 z
= submat
[0,0] + submat
[1,0]*i
992 return matrix(F
, n
/2, elements
)
995 def RealSymmetricSimpleEJA(n
, field
=QQ
):
997 The rank-n simple EJA consisting of real symmetric n-by-n
998 matrices, the usual symmetric Jordan product, and the trace inner
999 product. It has dimension `(n^2 + n)/2` over the reals.
1003 sage: J = RealSymmetricSimpleEJA(2)
1004 sage: e0, e1, e2 = J.gens()
1014 The degree of this algebra is `(n^2 + n) / 2`::
1016 sage: set_random_seed()
1017 sage: n = ZZ.random_element(1,5)
1018 sage: J = RealSymmetricSimpleEJA(n)
1019 sage: J.degree() == (n^2 + n)/2
1023 S
= _real_symmetric_basis(n
, field
=field
)
1024 Qs
= _multiplication_table_from_matrix_basis(S
)
1026 return FiniteDimensionalEuclideanJordanAlgebra(field
,Qs
,rank
=n
)
1029 def ComplexHermitianSimpleEJA(n
, field
=QQ
):
1031 The rank-n simple EJA consisting of complex Hermitian n-by-n
1032 matrices over the real numbers, the usual symmetric Jordan product,
1033 and the real-part-of-trace inner product. It has dimension `n^2` over
1038 The degree of this algebra is `n^2`::
1040 sage: set_random_seed()
1041 sage: n = ZZ.random_element(1,5)
1042 sage: J = ComplexHermitianSimpleEJA(n)
1043 sage: J.degree() == n^2
1047 S
= _complex_hermitian_basis(n
)
1048 Qs
= _multiplication_table_from_matrix_basis(S
)
1049 return FiniteDimensionalEuclideanJordanAlgebra(field
, Qs
, rank
=n
)
1052 def QuaternionHermitianSimpleEJA(n
):
1054 The rank-n simple EJA consisting of self-adjoint n-by-n quaternion
1055 matrices, the usual symmetric Jordan product, and the
1056 real-part-of-trace inner product. It has dimension `2n^2 - n` over
1061 def OctonionHermitianSimpleEJA(n
):
1063 This shit be crazy. It has dimension 27 over the reals.
1068 def JordanSpinSimpleEJA(n
, field
=QQ
):
1070 The rank-2 simple EJA consisting of real vectors ``x=(x0, x_bar)``
1071 with the usual inner product and jordan product ``x*y =
1072 (<x_bar,y_bar>, x0*y_bar + y0*x_bar)``. It has dimension `n` over
1077 This multiplication table can be verified by hand::
1079 sage: J = JordanSpinSimpleEJA(4)
1080 sage: e0,e1,e2,e3 = J.gens()
1096 In one dimension, this is the reals under multiplication::
1098 sage: J1 = JordanSpinSimpleEJA(1)
1099 sage: J2 = eja_rn(1)
1105 id_matrix
= identity_matrix(field
, n
)
1107 ei
= id_matrix
.column(i
)
1108 Qi
= zero_matrix(field
, n
)
1110 Qi
.set_column(0, ei
)
1111 Qi
+= diagonal_matrix(n
, [ei
[0]]*n
)
1112 # The addition of the diagonal matrix adds an extra ei[0] in the
1113 # upper-left corner of the matrix.
1114 Qi
[0,0] = Qi
[0,0] * ~
field(2)
1117 # The rank of the spin factor algebra is two, UNLESS we're in a
1118 # one-dimensional ambient space (the rank is bounded by the
1119 # ambient dimension).
1120 return FiniteDimensionalEuclideanJordanAlgebra(field
, Qs
, rank
=min(n
,2))