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.
84 Since EJAs are commutative, the "right multiplication" matrix is
85 also the left multiplication matrix and must be symmetric::
87 sage: set_random_seed()
88 sage: n = ZZ.random_element(1,10).abs()
90 sage: J.random_element().matrix().is_symmetric()
93 sage: J.random_element().matrix().is_symmetric()
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()
115 sage: x = J.random_element()
116 sage: x.matrix()*x.vector() == (x**2).vector()
126 return A
.element_class(A
, (self
.matrix()**(n
-1))*self
.vector())
129 def characteristic_polynomial(self
):
131 Return my characteristic polynomial (if I'm a regular
134 Eventually this should be implemented in terms of the parent
135 algebra's characteristic polynomial that works for ALL
138 if self
.is_regular():
139 return self
.minimal_polynomial()
141 return NotImplementedError('irregular element')
144 def is_nilpotent(self
):
146 Return whether or not some power of this element is zero.
148 The superclass method won't work unless we're in an
149 associative algebra, and we aren't. However, we generate
150 an assocoative subalgebra and we're nilpotent there if and
151 only if we're nilpotent here (probably).
155 The identity element is never nilpotent::
157 sage: set_random_seed()
158 sage: n = ZZ.random_element(2,10).abs()
160 sage: J.one().is_nilpotent()
163 sage: J.one().is_nilpotent()
166 The additive identity is always nilpotent::
168 sage: set_random_seed()
169 sage: n = ZZ.random_element(2,10).abs()
171 sage: J.zero().is_nilpotent()
174 sage: J.zero().is_nilpotent()
178 # The element we're going to call "is_nilpotent()" on.
179 # Either myself, interpreted as an element of a finite-
180 # dimensional algebra, or an element of an associative
184 if self
.parent().is_associative():
185 elt
= FiniteDimensionalAlgebraElement(self
.parent(), self
)
187 V
= self
.span_of_powers()
188 assoc_subalg
= self
.subalgebra_generated_by()
189 # Mis-design warning: the basis used for span_of_powers()
190 # and subalgebra_generated_by() must be the same, and in
192 elt
= assoc_subalg(V
.coordinates(self
.vector()))
194 # Recursive call, but should work since elt lives in an
195 # associative algebra.
196 return elt
.is_nilpotent()
199 def is_regular(self
):
201 Return whether or not this is a regular element.
205 The identity element always has degree one, but any element
206 linearly-independent from it is regular::
209 sage: J.one().is_regular()
211 sage: e0, e1, e2, e3, e4 = J.gens() # e0 is the identity
212 sage: for x in J.gens():
213 ....: (J.one() + x).is_regular()
221 return self
.degree() == self
.parent().rank()
226 Compute the degree of this element the straightforward way
227 according to the definition; by appending powers to a list
228 and figuring out its dimension (that is, whether or not
229 they're linearly dependent).
234 sage: J.one().degree()
236 sage: e0,e1,e2,e3 = J.gens()
237 sage: (e0 - e1).degree()
240 In the spin factor algebra (of rank two), all elements that
241 aren't multiples of the identity are regular::
243 sage: set_random_seed()
244 sage: n = ZZ.random_element(1,10).abs()
246 sage: x = J.random_element()
247 sage: x == x.coefficient(0)*J.one() or x.degree() == 2
251 return self
.span_of_powers().dimension()
256 Return the matrix that represents left- (or right-)
257 multiplication by this element in the parent algebra.
259 We have to override this because the superclass method
260 returns a matrix that acts on row vectors (that is, on
263 fda_elt
= FiniteDimensionalAlgebraElement(self
.parent(), self
)
264 return fda_elt
.matrix().transpose()
267 def minimal_polynomial(self
):
271 sage: set_random_seed()
272 sage: n = ZZ.random_element(1,10).abs()
274 sage: x = J.random_element()
275 sage: x.degree() == x.minimal_polynomial().degree()
280 sage: set_random_seed()
281 sage: n = ZZ.random_element(1,10).abs()
283 sage: x = J.random_element()
284 sage: x.degree() == x.minimal_polynomial().degree()
287 The minimal polynomial and the characteristic polynomial coincide
288 and are known (see Alizadeh, Example 11.11) for all elements of
289 the spin factor algebra that aren't scalar multiples of the
292 sage: set_random_seed()
293 sage: n = ZZ.random_element(2,10).abs()
295 sage: y = J.random_element()
296 sage: while y == y.coefficient(0)*J.one():
297 ....: y = J.random_element()
298 sage: y0 = y.vector()[0]
299 sage: y_bar = y.vector()[1:]
300 sage: actual = y.minimal_polynomial()
301 sage: x = SR.symbol('x', domain='real')
302 sage: expected = x^2 - 2*y0*x + (y0^2 - norm(y_bar)^2)
303 sage: bool(actual == expected)
307 # The element we're going to call "minimal_polynomial()" on.
308 # Either myself, interpreted as an element of a finite-
309 # dimensional algebra, or an element of an associative
313 if self
.parent().is_associative():
314 elt
= FiniteDimensionalAlgebraElement(self
.parent(), self
)
316 V
= self
.span_of_powers()
317 assoc_subalg
= self
.subalgebra_generated_by()
318 # Mis-design warning: the basis used for span_of_powers()
319 # and subalgebra_generated_by() must be the same, and in
321 elt
= assoc_subalg(V
.coordinates(self
.vector()))
323 # Recursive call, but should work since elt lives in an
324 # associative algebra.
325 return elt
.minimal_polynomial()
328 def span_of_powers(self
):
330 Return the vector space spanned by successive powers of
333 # The dimension of the subalgebra can't be greater than
334 # the big algebra, so just put everything into a list
335 # and let span() get rid of the excess.
336 V
= self
.vector().parent()
337 return V
.span( (self
**d
).vector() for d
in xrange(V
.dimension()) )
340 def subalgebra_generated_by(self
):
342 Return the associative subalgebra of the parent EJA generated
347 sage: set_random_seed()
348 sage: n = ZZ.random_element(1,10).abs()
350 sage: x = J.random_element()
351 sage: x.subalgebra_generated_by().is_associative()
354 sage: x = J.random_element()
355 sage: x.subalgebra_generated_by().is_associative()
358 Squaring in the subalgebra should be the same thing as
359 squaring in the superalgebra::
362 sage: x = J.random_element()
363 sage: u = x.subalgebra_generated_by().random_element()
364 sage: u.matrix()*u.vector() == (u**2).vector()
368 # First get the subspace spanned by the powers of myself...
369 V
= self
.span_of_powers()
372 # Now figure out the entries of the right-multiplication
373 # matrix for the successive basis elements b0, b1,... of
376 for b_right
in V
.basis():
377 eja_b_right
= self
.parent()(b_right
)
379 # The first row of the right-multiplication matrix by
380 # b1 is what we get if we apply that matrix to b1. The
381 # second row of the right multiplication matrix by b1
382 # is what we get when we apply that matrix to b2...
384 # IMPORTANT: this assumes that all vectors are COLUMN
385 # vectors, unlike our superclass (which uses row vectors).
386 for b_left
in V
.basis():
387 eja_b_left
= self
.parent()(b_left
)
388 # Multiply in the original EJA, but then get the
389 # coordinates from the subalgebra in terms of its
391 this_row
= V
.coordinates((eja_b_left
*eja_b_right
).vector())
392 b_right_rows
.append(this_row
)
393 b_right_matrix
= matrix(F
, b_right_rows
)
394 mats
.append(b_right_matrix
)
396 # It's an algebra of polynomials in one element, and EJAs
397 # are power-associative.
399 # TODO: choose generator names intelligently.
400 return FiniteDimensionalEuclideanJordanAlgebra(F
, mats
, assume_associative
=True, names
='f')
403 def subalgebra_idempotent(self
):
405 Find an idempotent in the associative subalgebra I generate
406 using Proposition 2.3.5 in Baes.
410 sage: set_random_seed()
412 sage: c = J.random_element().subalgebra_idempotent()
416 sage: c = J.random_element().subalgebra_idempotent()
421 if self
.is_nilpotent():
422 raise ValueError("this only works with non-nilpotent elements!")
424 V
= self
.span_of_powers()
425 J
= self
.subalgebra_generated_by()
426 # Mis-design warning: the basis used for span_of_powers()
427 # and subalgebra_generated_by() must be the same, and in
429 u
= J(V
.coordinates(self
.vector()))
431 # The image of the matrix of left-u^m-multiplication
432 # will be minimal for some natural number s...
434 minimal_dim
= V
.dimension()
435 for i
in xrange(1, V
.dimension()):
436 this_dim
= (u
**i
).matrix().image().dimension()
437 if this_dim
< minimal_dim
:
438 minimal_dim
= this_dim
441 # Now minimal_matrix should correspond to the smallest
442 # non-zero subspace in Baes's (or really, Koecher's)
445 # However, we need to restrict the matrix to work on the
446 # subspace... or do we? Can't we just solve, knowing that
447 # A(c) = u^(s+1) should have a solution in the big space,
450 # Beware, solve_right() means that we're using COLUMN vectors.
451 # Our FiniteDimensionalAlgebraElement superclass uses rows.
454 c_coordinates
= A
.solve_right(u_next
.vector())
456 # Now c_coordinates is the idempotent we want, but it's in
457 # the coordinate system of the subalgebra.
459 # We need the basis for J, but as elements of the parent algebra.
461 basis
= [self
.parent(v
) for v
in V
.basis()]
462 return self
.parent().linear_combination(zip(c_coordinates
, basis
))
466 def eja_rn(dimension
, field
=QQ
):
468 Return the Euclidean Jordan Algebra corresponding to the set
469 `R^n` under the Hadamard product.
473 This multiplication table can be verified by hand::
476 sage: e0,e1,e2 = J.gens()
491 # The FiniteDimensionalAlgebra constructor takes a list of
492 # matrices, the ith representing right multiplication by the ith
493 # basis element in the vector space. So if e_1 = (1,0,0), then
494 # right (Hadamard) multiplication of x by e_1 picks out the first
495 # component of x; and likewise for the ith basis element e_i.
496 Qs
= [ matrix(field
, dimension
, dimension
, lambda k
,j
: 1*(k
== j
== i
))
497 for i
in xrange(dimension
) ]
499 return FiniteDimensionalEuclideanJordanAlgebra(field
,Qs
,rank
=dimension
)
502 def eja_ln(dimension
, field
=QQ
):
504 Return the Jordan algebra corresponding to the Lorentz "ice cream"
505 cone of the given ``dimension``.
509 This multiplication table can be verified by hand::
512 sage: e0,e1,e2,e3 = J.gens()
528 In one dimension, this is the reals under multiplication::
537 id_matrix
= identity_matrix(field
,dimension
)
538 for i
in xrange(dimension
):
539 ei
= id_matrix
.column(i
)
540 Qi
= zero_matrix(field
,dimension
)
543 Qi
+= diagonal_matrix(dimension
, [ei
[0]]*dimension
)
544 # The addition of the diagonal matrix adds an extra ei[0] in the
545 # upper-left corner of the matrix.
546 Qi
[0,0] = Qi
[0,0] * ~
field(2)
549 # The rank of the spin factor algebra is two, UNLESS we're in a
550 # one-dimensional ambient space (the rank is bounded by the
551 # ambient dimension).
552 rank
= min(dimension
,2)
553 return FiniteDimensionalEuclideanJordanAlgebra(field
,Qs
,rank
=rank
)