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 span_of_powers(self
):
131 Return the vector space spanned by successive powers of
134 # The dimension of the subalgebra can't be greater than
135 # the big algebra, so just put everything into a list
136 # and let span() get rid of the excess.
137 V
= self
.vector().parent()
138 return V
.span( (self
**d
).vector() for d
in xrange(V
.dimension()) )
143 Compute the degree of this element the straightforward way
144 according to the definition; by appending powers to a list
145 and figuring out its dimension (that is, whether or not
146 they're linearly dependent).
151 sage: J.one().degree()
153 sage: e0,e1,e2,e3 = J.gens()
154 sage: (e0 - e1).degree()
157 In the spin factor algebra (of rank two), all elements that
158 aren't multiples of the identity are regular::
160 sage: set_random_seed()
161 sage: n = ZZ.random_element(1,10).abs()
163 sage: x = J.random_element()
164 sage: x == x.coefficient(0)*J.one() or x.degree() == 2
168 return self
.span_of_powers().dimension()
173 Return the matrix that represents left- (or right-)
174 multiplication by this element in the parent algebra.
176 We have to override this because the superclass method
177 returns a matrix that acts on row vectors (that is, on
180 fda_elt
= FiniteDimensionalAlgebraElement(self
.parent(), self
)
181 return fda_elt
.matrix().transpose()
184 def subalgebra_generated_by(self
):
186 Return the associative subalgebra of the parent EJA generated
191 sage: set_random_seed()
192 sage: n = ZZ.random_element(1,10).abs()
194 sage: x = J.random_element()
195 sage: x.subalgebra_generated_by().is_associative()
198 sage: x = J.random_element()
199 sage: x.subalgebra_generated_by().is_associative()
202 Squaring in the subalgebra should be the same thing as
203 squaring in the superalgebra::
206 sage: x = J.random_element()
207 sage: u = x.subalgebra_generated_by().random_element()
208 sage: u.matrix()*u.vector() == (u**2).vector()
212 # First get the subspace spanned by the powers of myself...
213 V
= self
.span_of_powers()
216 # Now figure out the entries of the right-multiplication
217 # matrix for the successive basis elements b0, b1,... of
220 for b_right
in V
.basis():
221 eja_b_right
= self
.parent()(b_right
)
223 # The first row of the right-multiplication matrix by
224 # b1 is what we get if we apply that matrix to b1. The
225 # second row of the right multiplication matrix by b1
226 # is what we get when we apply that matrix to b2...
228 # IMPORTANT: this assumes that all vectors are COLUMN
229 # vectors, unlike our superclass (which uses row vectors).
230 for b_left
in V
.basis():
231 eja_b_left
= self
.parent()(b_left
)
232 # Multiply in the original EJA, but then get the
233 # coordinates from the subalgebra in terms of its
235 this_row
= V
.coordinates((eja_b_left
*eja_b_right
).vector())
236 b_right_rows
.append(this_row
)
237 b_right_matrix
= matrix(F
, b_right_rows
)
238 mats
.append(b_right_matrix
)
240 # It's an algebra of polynomials in one element, and EJAs
241 # are power-associative.
243 # TODO: choose generator names intelligently.
244 return FiniteDimensionalEuclideanJordanAlgebra(F
, mats
, assume_associative
=True, names
='f')
247 def minimal_polynomial(self
):
251 sage: set_random_seed()
252 sage: n = ZZ.random_element(1,10).abs()
254 sage: x = J.random_element()
255 sage: x.degree() == x.minimal_polynomial().degree()
260 sage: set_random_seed()
261 sage: n = ZZ.random_element(1,10).abs()
263 sage: x = J.random_element()
264 sage: x.degree() == x.minimal_polynomial().degree()
267 The minimal polynomial and the characteristic polynomial coincide
268 and are known (see Alizadeh, Example 11.11) for all elements of
269 the spin factor algebra that aren't scalar multiples of the
272 sage: set_random_seed()
273 sage: n = ZZ.random_element(2,10).abs()
275 sage: y = J.random_element()
276 sage: while y == y.coefficient(0)*J.one():
277 ....: y = J.random_element()
278 sage: y0 = y.vector()[0]
279 sage: y_bar = y.vector()[1:]
280 sage: actual = y.minimal_polynomial()
281 sage: x = SR.symbol('x', domain='real')
282 sage: expected = x^2 - 2*y0*x + (y0^2 - norm(y_bar)^2)
283 sage: bool(actual == expected)
287 # The element we're going to call "minimal_polynomial()" on.
288 # Either myself, interpreted as an element of a finite-
289 # dimensional algebra, or an element of an associative
293 if self
.parent().is_associative():
294 elt
= FiniteDimensionalAlgebraElement(self
.parent(), self
)
296 V
= self
.span_of_powers()
297 assoc_subalg
= self
.subalgebra_generated_by()
298 # Mis-design warning: the basis used for span_of_powers()
299 # and subalgebra_generated_by() must be the same, and in
301 elt
= assoc_subalg(V
.coordinates(self
.vector()))
303 # Recursive call, but should work since elt lives in an
304 # associative algebra.
305 return elt
.minimal_polynomial()
308 def is_nilpotent(self
):
310 Return whether or not some power of this element is zero.
312 The superclass method won't work unless we're in an
313 associative algebra, and we aren't. However, we generate
314 an assocoative subalgebra and we're nilpotent there if and
315 only if we're nilpotent here (probably).
319 The identity element is never nilpotent::
321 sage: set_random_seed()
322 sage: n = ZZ.random_element(2,10).abs()
324 sage: J.one().is_nilpotent()
327 sage: J.one().is_nilpotent()
330 The additive identity is always nilpotent::
332 sage: set_random_seed()
333 sage: n = ZZ.random_element(2,10).abs()
335 sage: J.zero().is_nilpotent()
338 sage: J.zero().is_nilpotent()
342 # The element we're going to call "is_nilpotent()" on.
343 # Either myself, interpreted as an element of a finite-
344 # dimensional algebra, or an element of an associative
348 if self
.parent().is_associative():
349 elt
= FiniteDimensionalAlgebraElement(self
.parent(), self
)
351 V
= self
.span_of_powers()
352 assoc_subalg
= self
.subalgebra_generated_by()
353 # Mis-design warning: the basis used for span_of_powers()
354 # and subalgebra_generated_by() must be the same, and in
356 elt
= assoc_subalg(V
.coordinates(self
.vector()))
358 # Recursive call, but should work since elt lives in an
359 # associative algebra.
360 return elt
.is_nilpotent()
363 def subalgebra_idempotent(self
):
365 Find an idempotent in the associative subalgebra I generate
366 using Proposition 2.3.5 in Baes.
370 sage: set_random_seed()
372 sage: c = J.random_element().subalgebra_idempotent()
376 sage: c = J.random_element().subalgebra_idempotent()
381 if self
.is_nilpotent():
382 raise ValueError("this only works with non-nilpotent elements!")
384 V
= self
.span_of_powers()
385 J
= self
.subalgebra_generated_by()
386 # Mis-design warning: the basis used for span_of_powers()
387 # and subalgebra_generated_by() must be the same, and in
389 u
= J(V
.coordinates(self
.vector()))
391 # The image of the matrix of left-u^m-multiplication
392 # will be minimal for some natural number s...
394 minimal_dim
= V
.dimension()
395 for i
in xrange(1, V
.dimension()):
396 this_dim
= (u
**i
).matrix().image().dimension()
397 if this_dim
< minimal_dim
:
398 minimal_dim
= this_dim
401 # Now minimal_matrix should correspond to the smallest
402 # non-zero subspace in Baes's (or really, Koecher's)
405 # However, we need to restrict the matrix to work on the
406 # subspace... or do we? Can't we just solve, knowing that
407 # A(c) = u^(s+1) should have a solution in the big space,
410 # Beware, solve_right() means that we're using COLUMN vectors.
411 # Our FiniteDimensionalAlgebraElement superclass uses rows.
414 c_coordinates
= A
.solve_right(u_next
.vector())
416 # Now c_coordinates is the idempotent we want, but it's in
417 # the coordinate system of the subalgebra.
419 # We need the basis for J, but as elements of the parent algebra.
421 basis
= [self
.parent(v
) for v
in V
.basis()]
422 return self
.parent().linear_combination(zip(c_coordinates
, basis
))
426 def characteristic_polynomial(self
):
427 return self
.matrix().characteristic_polynomial()
430 def eja_rn(dimension
, field
=QQ
):
432 Return the Euclidean Jordan Algebra corresponding to the set
433 `R^n` under the Hadamard product.
437 This multiplication table can be verified by hand::
440 sage: e0,e1,e2 = J.gens()
455 # The FiniteDimensionalAlgebra constructor takes a list of
456 # matrices, the ith representing right multiplication by the ith
457 # basis element in the vector space. So if e_1 = (1,0,0), then
458 # right (Hadamard) multiplication of x by e_1 picks out the first
459 # component of x; and likewise for the ith basis element e_i.
460 Qs
= [ matrix(field
, dimension
, dimension
, lambda k
,j
: 1*(k
== j
== i
))
461 for i
in xrange(dimension
) ]
463 return FiniteDimensionalEuclideanJordanAlgebra(field
,Qs
,rank
=dimension
)
466 def eja_ln(dimension
, field
=QQ
):
468 Return the Jordan algebra corresponding to the Lorentz "ice cream"
469 cone of the given ``dimension``.
473 This multiplication table can be verified by hand::
476 sage: e0,e1,e2,e3 = J.gens()
492 In one dimension, this is the reals under multiplication::
501 id_matrix
= identity_matrix(field
,dimension
)
502 for i
in xrange(dimension
):
503 ei
= id_matrix
.column(i
)
504 Qi
= zero_matrix(field
,dimension
)
507 Qi
+= diagonal_matrix(dimension
, [ei
[0]]*dimension
)
508 # The addition of the diagonal matrix adds an extra ei[0] in the
509 # upper-left corner of the matrix.
510 Qi
[0,0] = Qi
[0,0] * ~
field(2)
513 # The rank of the spin factor algebra is two, UNLESS we're in a
514 # one-dimensional ambient space (the rank is bounded by the
515 # ambient dimension).
516 rank
= min(dimension
,2)
517 return FiniteDimensionalEuclideanJordanAlgebra(field
,Qs
,rank
=rank
)