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()
175 # First get the subspace spanned by the powers of myself...
176 V
= self
.span_of_powers()
179 # Now figure out the entries of the right-multiplication
180 # matrix for the successive basis elements b0, b1,... of
183 for b_right
in V
.basis():
184 eja_b_right
= self
.parent()(b_right
)
186 # The first row of the right-multiplication matrix by
187 # b1 is what we get if we apply that matrix to b1. The
188 # second row of the right multiplication matrix by b1
189 # is what we get when we apply that matrix to b2...
190 for b_left
in V
.basis():
191 eja_b_left
= self
.parent()(b_left
)
192 # Multiply in the original EJA, but then get the
193 # coordinates from the subalgebra in terms of its
195 this_row
= V
.coordinates((eja_b_left
*eja_b_right
).vector())
196 b_right_rows
.append(this_row
)
197 b_right_matrix
= matrix(F
, b_right_rows
)
198 mats
.append(b_right_matrix
)
200 # It's an algebra of polynomials in one element, and EJAs
201 # are power-associative.
203 # TODO: choose generator names intelligently.
204 return FiniteDimensionalEuclideanJordanAlgebra(F
, mats
, assume_associative
=True, names
='f')
207 def minimal_polynomial(self
):
211 sage: set_random_seed()
212 sage: n = ZZ.random_element(1,10).abs()
214 sage: x = J.random_element()
215 sage: x.degree() == x.minimal_polynomial().degree()
220 sage: set_random_seed()
221 sage: n = ZZ.random_element(1,10).abs()
223 sage: x = J.random_element()
224 sage: x.degree() == x.minimal_polynomial().degree()
227 The minimal polynomial and the characteristic polynomial coincide
228 and are known (see Alizadeh, Example 11.11) for all elements of
229 the spin factor algebra that aren't scalar multiples of the
232 sage: set_random_seed()
233 sage: n = ZZ.random_element(2,10).abs()
235 sage: y = J.random_element()
236 sage: while y == y.coefficient(0)*J.one():
237 ....: y = J.random_element()
238 sage: y0 = y.vector()[0]
239 sage: y_bar = y.vector()[1:]
240 sage: actual = y.minimal_polynomial()
241 sage: x = SR.symbol('x', domain='real')
242 sage: expected = x^2 - 2*y0*x + (y0^2 - norm(y_bar)^2)
243 sage: bool(actual == expected)
247 # The element we're going to call "minimal_polynomial()" on.
248 # Either myself, interpreted as an element of a finite-
249 # dimensional algebra, or an element of an associative
253 if self
.parent().is_associative():
254 elt
= FiniteDimensionalAlgebraElement(self
.parent(), self
)
256 V
= self
.span_of_powers()
257 assoc_subalg
= self
.subalgebra_generated_by()
258 # Mis-design warning: the basis used for span_of_powers()
259 # and subalgebra_generated_by() must be the same, and in
261 elt
= assoc_subalg(V
.coordinates(self
.vector()))
263 # Recursive call, but should work since elt lives in an
264 # associative algebra.
265 return elt
.minimal_polynomial()
268 def is_nilpotent(self
):
270 Return whether or not some power of this element is zero.
272 The superclass method won't work unless we're in an
273 associative algebra, and we aren't. However, we generate
274 an assocoative subalgebra and we're nilpotent there if and
275 only if we're nilpotent here (probably).
279 The identity element is never nilpotent::
281 sage: set_random_seed()
282 sage: n = ZZ.random_element(2,10).abs()
284 sage: J.one().is_nilpotent()
287 sage: J.one().is_nilpotent()
290 The additive identity is always nilpotent::
292 sage: set_random_seed()
293 sage: n = ZZ.random_element(2,10).abs()
295 sage: J.zero().is_nilpotent()
298 sage: J.zero().is_nilpotent()
302 # The element we're going to call "is_nilpotent()" on.
303 # Either myself, interpreted as an element of a finite-
304 # dimensional algebra, or an element of an associative
308 if self
.parent().is_associative():
309 elt
= FiniteDimensionalAlgebraElement(self
.parent(), self
)
311 V
= self
.span_of_powers()
312 assoc_subalg
= self
.subalgebra_generated_by()
313 # Mis-design warning: the basis used for span_of_powers()
314 # and subalgebra_generated_by() must be the same, and in
316 elt
= assoc_subalg(V
.coordinates(self
.vector()))
318 # Recursive call, but should work since elt lives in an
319 # associative algebra.
320 return elt
.is_nilpotent()
323 def characteristic_polynomial(self
):
324 return self
.matrix().characteristic_polynomial()
327 def eja_rn(dimension
, field
=QQ
):
329 Return the Euclidean Jordan Algebra corresponding to the set
330 `R^n` under the Hadamard product.
334 This multiplication table can be verified by hand::
337 sage: e0,e1,e2 = J.gens()
352 # The FiniteDimensionalAlgebra constructor takes a list of
353 # matrices, the ith representing right multiplication by the ith
354 # basis element in the vector space. So if e_1 = (1,0,0), then
355 # right (Hadamard) multiplication of x by e_1 picks out the first
356 # component of x; and likewise for the ith basis element e_i.
357 Qs
= [ matrix(field
, dimension
, dimension
, lambda k
,j
: 1*(k
== j
== i
))
358 for i
in xrange(dimension
) ]
360 return FiniteDimensionalEuclideanJordanAlgebra(field
,Qs
,rank
=dimension
)
363 def eja_ln(dimension
, field
=QQ
):
365 Return the Jordan algebra corresponding to the Lorentz "ice cream"
366 cone of the given ``dimension``.
370 This multiplication table can be verified by hand::
373 sage: e0,e1,e2,e3 = J.gens()
389 In one dimension, this is the reals under multiplication::
398 id_matrix
= identity_matrix(field
,dimension
)
399 for i
in xrange(dimension
):
400 ei
= id_matrix
.column(i
)
401 Qi
= zero_matrix(field
,dimension
)
404 Qi
+= diagonal_matrix(dimension
, [ei
[0]]*dimension
)
405 # The addition of the diagonal matrix adds an extra ei[0] in the
406 # upper-left corner of the matrix.
407 Qi
[0,0] = Qi
[0,0] * ~
field(2)
410 # The rank of the spin factor algebra is two, UNLESS we're in a
411 # one-dimensional ambient space (the rank is bounded by the
412 # ambient dimension).
413 rank
= min(dimension
,2)
414 return FiniteDimensionalEuclideanJordanAlgebra(field
,Qs
,rank
=rank
)