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).
111 return A
.element_class(A
, self
.vector()*(self
.matrix()**(n
-1)))
114 def span_of_powers(self
):
116 Return the vector space spanned by successive powers of
119 # The dimension of the subalgebra can't be greater than
120 # the big algebra, so just put everything into a list
121 # and let span() get rid of the excess.
122 V
= self
.vector().parent()
123 return V
.span( (self
**d
).vector() for d
in xrange(V
.dimension()) )
128 Compute the degree of this element the straightforward way
129 according to the definition; by appending powers to a list
130 and figuring out its dimension (that is, whether or not
131 they're linearly dependent).
136 sage: J.one().degree()
138 sage: e0,e1,e2,e3 = J.gens()
139 sage: (e0 - e1).degree()
142 In the spin factor algebra (of rank two), all elements that
143 aren't multiples of the identity are regular::
145 sage: set_random_seed()
146 sage: n = ZZ.random_element(1,10).abs()
148 sage: x = J.random_element()
149 sage: x == x.coefficient(0)*J.one() or x.degree() == 2
153 return self
.span_of_powers().dimension()
156 def subalgebra_generated_by(self
):
158 Return the associative subalgebra of the parent EJA generated
163 sage: set_random_seed()
164 sage: n = ZZ.random_element(1,10).abs()
166 sage: x = J.random_element()
167 sage: x.subalgebra_generated_by().is_associative()
170 sage: x = J.random_element()
171 sage: x.subalgebra_generated_by().is_associative()
174 This is buggy right now::
177 sage: x = J.random_element()
178 sage: x.matrix()*x.vector() == (x**2).vector() # works
180 sage: u = x.subalgebra_generated_by().random_element()
181 sage: u.matrix()*u.vector() == (u**2).vector() # busted
185 # First get the subspace spanned by the powers of myself...
186 V
= self
.span_of_powers()
189 # Now figure out the entries of the right-multiplication
190 # matrix for the successive basis elements b0, b1,... of
193 for b_right
in V
.basis():
194 eja_b_right
= self
.parent()(b_right
)
196 # The first row of the right-multiplication matrix by
197 # b1 is what we get if we apply that matrix to b1. The
198 # second row of the right multiplication matrix by b1
199 # is what we get when we apply that matrix to b2...
200 for b_left
in V
.basis():
201 eja_b_left
= self
.parent()(b_left
)
202 # Multiply in the original EJA, but then get the
203 # coordinates from the subalgebra in terms of its
205 this_row
= V
.coordinates((eja_b_left
*eja_b_right
).vector())
206 b_right_rows
.append(this_row
)
207 b_right_matrix
= matrix(F
, b_right_rows
)
208 mats
.append(b_right_matrix
)
210 # It's an algebra of polynomials in one element, and EJAs
211 # are power-associative.
213 # TODO: choose generator names intelligently.
214 return FiniteDimensionalEuclideanJordanAlgebra(F
, mats
, assume_associative
=True, names
='f')
217 def minimal_polynomial(self
):
221 sage: set_random_seed()
222 sage: n = ZZ.random_element(1,10).abs()
224 sage: x = J.random_element()
225 sage: x.degree() == x.minimal_polynomial().degree()
230 sage: set_random_seed()
231 sage: n = ZZ.random_element(1,10).abs()
233 sage: x = J.random_element()
234 sage: x.degree() == x.minimal_polynomial().degree()
237 The minimal polynomial and the characteristic polynomial coincide
238 and are known (see Alizadeh, Example 11.11) for all elements of
239 the spin factor algebra that aren't scalar multiples of the
242 sage: set_random_seed()
243 sage: n = ZZ.random_element(2,10).abs()
245 sage: y = J.random_element()
246 sage: while y == y.coefficient(0)*J.one():
247 ....: y = J.random_element()
248 sage: y0 = y.vector()[0]
249 sage: y_bar = y.vector()[1:]
250 sage: actual = y.minimal_polynomial()
251 sage: x = SR.symbol('x', domain='real')
252 sage: expected = x^2 - 2*y0*x + (y0^2 - norm(y_bar)^2)
253 sage: bool(actual == expected)
257 # The element we're going to call "minimal_polynomial()" on.
258 # Either myself, interpreted as an element of a finite-
259 # dimensional algebra, or an element of an associative
263 if self
.parent().is_associative():
264 elt
= FiniteDimensionalAlgebraElement(self
.parent(), self
)
266 V
= self
.span_of_powers()
267 assoc_subalg
= self
.subalgebra_generated_by()
268 # Mis-design warning: the basis used for span_of_powers()
269 # and subalgebra_generated_by() must be the same, and in
271 elt
= assoc_subalg(V
.coordinates(self
.vector()))
273 # Recursive call, but should work since elt lives in an
274 # associative algebra.
275 return elt
.minimal_polynomial()
278 def is_nilpotent(self
):
280 Return whether or not some power of this element is zero.
282 The superclass method won't work unless we're in an
283 associative algebra, and we aren't. However, we generate
284 an assocoative subalgebra and we're nilpotent there if and
285 only if we're nilpotent here (probably).
289 The identity element is never nilpotent::
291 sage: set_random_seed()
292 sage: n = ZZ.random_element(2,10).abs()
294 sage: J.one().is_nilpotent()
297 sage: J.one().is_nilpotent()
300 The additive identity is always nilpotent::
302 sage: set_random_seed()
303 sage: n = ZZ.random_element(2,10).abs()
305 sage: J.zero().is_nilpotent()
308 sage: J.zero().is_nilpotent()
312 # The element we're going to call "is_nilpotent()" on.
313 # Either myself, interpreted as an element of a finite-
314 # dimensional algebra, or an element of an associative
318 if self
.parent().is_associative():
319 elt
= FiniteDimensionalAlgebraElement(self
.parent(), self
)
321 V
= self
.span_of_powers()
322 assoc_subalg
= self
.subalgebra_generated_by()
323 # Mis-design warning: the basis used for span_of_powers()
324 # and subalgebra_generated_by() must be the same, and in
326 elt
= assoc_subalg(V
.coordinates(self
.vector()))
328 # Recursive call, but should work since elt lives in an
329 # associative algebra.
330 return elt
.is_nilpotent()
333 def subalgebra_idempotent(self
):
335 Find an idempotent in the associative subalgebra I generate
336 using Proposition 2.3.5 in Baes.
338 if self
.is_nilpotent():
339 raise ValueError("this only works with non-nilpotent elements!")
341 V
= self
.span_of_powers()
342 J
= self
.subalgebra_generated_by()
343 # Mis-design warning: the basis used for span_of_powers()
344 # and subalgebra_generated_by() must be the same, and in
346 u
= J(V
.coordinates(self
.vector()))
348 # The image of the matrix of left-u^m-multiplication
349 # will be minimal for some natural number s...
351 minimal_dim
= V
.dimension()
352 for i
in xrange(1, V
.dimension()):
353 this_dim
= (u
**i
).matrix().image().dimension()
354 if this_dim
< minimal_dim
:
355 minimal_dim
= this_dim
358 # Now minimal_matrix should correspond to the smallest
359 # non-zero subspace in Baes's (or really, Koecher's)
362 # However, we need to restrict the matrix to work on the
363 # subspace... or do we? Can't we just solve, knowing that
364 # A(c) = u^(s+1) should have a solution in the big space,
368 c_coordinates
= A
.solve_right(u_next
.vector())
370 # Now c_coordinates is the idempotent we want, but it's in
371 # the coordinate system of the subalgebra.
373 # We need the basis for J, but as elements of the parent algebra.
376 # TODO: this is buggy, but it's probably because the
377 # multiplication table for the subalgebra is wrong! The
378 # matrices should be symmetric I bet.
379 basis
= [self
.parent(v
) for v
in V
.basis()]
380 return self
.parent().linear_combination(zip(c_coordinates
, basis
))
384 def characteristic_polynomial(self
):
385 return self
.matrix().characteristic_polynomial()
388 def eja_rn(dimension
, field
=QQ
):
390 Return the Euclidean Jordan Algebra corresponding to the set
391 `R^n` under the Hadamard product.
395 This multiplication table can be verified by hand::
398 sage: e0,e1,e2 = J.gens()
413 # The FiniteDimensionalAlgebra constructor takes a list of
414 # matrices, the ith representing right multiplication by the ith
415 # basis element in the vector space. So if e_1 = (1,0,0), then
416 # right (Hadamard) multiplication of x by e_1 picks out the first
417 # component of x; and likewise for the ith basis element e_i.
418 Qs
= [ matrix(field
, dimension
, dimension
, lambda k
,j
: 1*(k
== j
== i
))
419 for i
in xrange(dimension
) ]
421 return FiniteDimensionalEuclideanJordanAlgebra(field
,Qs
,rank
=dimension
)
424 def eja_ln(dimension
, field
=QQ
):
426 Return the Jordan algebra corresponding to the Lorentz "ice cream"
427 cone of the given ``dimension``.
431 This multiplication table can be verified by hand::
434 sage: e0,e1,e2,e3 = J.gens()
450 In one dimension, this is the reals under multiplication::
459 id_matrix
= identity_matrix(field
,dimension
)
460 for i
in xrange(dimension
):
461 ei
= id_matrix
.column(i
)
462 Qi
= zero_matrix(field
,dimension
)
465 Qi
+= diagonal_matrix(dimension
, [ei
[0]]*dimension
)
466 # The addition of the diagonal matrix adds an extra ei[0] in the
467 # upper-left corner of the matrix.
468 Qi
[0,0] = Qi
[0,0] * ~
field(2)
471 # The rank of the spin factor algebra is two, UNLESS we're in a
472 # one-dimensional ambient space (the rank is bounded by the
473 # ambient dimension).
474 rank
= min(dimension
,2)
475 return FiniteDimensionalEuclideanJordanAlgebra(field
,Qs
,rank
=rank
)