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()
171 def subalgebra_generated_by(self
):
173 Return the associative subalgebra of the parent EJA generated
178 sage: set_random_seed()
179 sage: n = ZZ.random_element(1,10).abs()
181 sage: x = J.random_element()
182 sage: x.subalgebra_generated_by().is_associative()
185 sage: x = J.random_element()
186 sage: x.subalgebra_generated_by().is_associative()
189 Squaring in the subalgebra should be the same thing as
190 squaring in the superalgebra::
193 sage: x = J.random_element()
194 sage: u = x.subalgebra_generated_by().random_element()
195 sage: u.matrix()*u.vector() == (u**2).vector()
199 # First get the subspace spanned by the powers of myself...
200 V
= self
.span_of_powers()
203 # Now figure out the entries of the right-multiplication
204 # matrix for the successive basis elements b0, b1,... of
207 for b_right
in V
.basis():
208 eja_b_right
= self
.parent()(b_right
)
210 # The first row of the right-multiplication matrix by
211 # b1 is what we get if we apply that matrix to b1. The
212 # second row of the right multiplication matrix by b1
213 # is what we get when we apply that matrix to b2...
215 # IMPORTANT: this assumes that all vectors are COLUMN
216 # vectors, unlike our superclass (which uses row vectors).
217 for b_left
in V
.basis():
218 eja_b_left
= self
.parent()(b_left
)
219 # Multiply in the original EJA, but then get the
220 # coordinates from the subalgebra in terms of its
222 this_row
= V
.coordinates((eja_b_left
*eja_b_right
).vector())
223 b_right_rows
.append(this_row
)
224 b_right_matrix
= matrix(F
, b_right_rows
)
225 mats
.append(b_right_matrix
)
227 # It's an algebra of polynomials in one element, and EJAs
228 # are power-associative.
230 # TODO: choose generator names intelligently.
231 return FiniteDimensionalEuclideanJordanAlgebra(F
, mats
, assume_associative
=True, names
='f')
234 def minimal_polynomial(self
):
238 sage: set_random_seed()
239 sage: n = ZZ.random_element(1,10).abs()
241 sage: x = J.random_element()
242 sage: x.degree() == x.minimal_polynomial().degree()
247 sage: set_random_seed()
248 sage: n = ZZ.random_element(1,10).abs()
250 sage: x = J.random_element()
251 sage: x.degree() == x.minimal_polynomial().degree()
254 The minimal polynomial and the characteristic polynomial coincide
255 and are known (see Alizadeh, Example 11.11) for all elements of
256 the spin factor algebra that aren't scalar multiples of the
259 sage: set_random_seed()
260 sage: n = ZZ.random_element(2,10).abs()
262 sage: y = J.random_element()
263 sage: while y == y.coefficient(0)*J.one():
264 ....: y = J.random_element()
265 sage: y0 = y.vector()[0]
266 sage: y_bar = y.vector()[1:]
267 sage: actual = y.minimal_polynomial()
268 sage: x = SR.symbol('x', domain='real')
269 sage: expected = x^2 - 2*y0*x + (y0^2 - norm(y_bar)^2)
270 sage: bool(actual == expected)
274 # The element we're going to call "minimal_polynomial()" on.
275 # Either myself, interpreted as an element of a finite-
276 # dimensional algebra, or an element of an associative
280 if self
.parent().is_associative():
281 elt
= FiniteDimensionalAlgebraElement(self
.parent(), self
)
283 V
= self
.span_of_powers()
284 assoc_subalg
= self
.subalgebra_generated_by()
285 # Mis-design warning: the basis used for span_of_powers()
286 # and subalgebra_generated_by() must be the same, and in
288 elt
= assoc_subalg(V
.coordinates(self
.vector()))
290 # Recursive call, but should work since elt lives in an
291 # associative algebra.
292 return elt
.minimal_polynomial()
295 def is_nilpotent(self
):
297 Return whether or not some power of this element is zero.
299 The superclass method won't work unless we're in an
300 associative algebra, and we aren't. However, we generate
301 an assocoative subalgebra and we're nilpotent there if and
302 only if we're nilpotent here (probably).
306 The identity element is never nilpotent::
308 sage: set_random_seed()
309 sage: n = ZZ.random_element(2,10).abs()
311 sage: J.one().is_nilpotent()
314 sage: J.one().is_nilpotent()
317 The additive identity is always nilpotent::
319 sage: set_random_seed()
320 sage: n = ZZ.random_element(2,10).abs()
322 sage: J.zero().is_nilpotent()
325 sage: J.zero().is_nilpotent()
329 # The element we're going to call "is_nilpotent()" on.
330 # Either myself, interpreted as an element of a finite-
331 # dimensional algebra, or an element of an associative
335 if self
.parent().is_associative():
336 elt
= FiniteDimensionalAlgebraElement(self
.parent(), self
)
338 V
= self
.span_of_powers()
339 assoc_subalg
= self
.subalgebra_generated_by()
340 # Mis-design warning: the basis used for span_of_powers()
341 # and subalgebra_generated_by() must be the same, and in
343 elt
= assoc_subalg(V
.coordinates(self
.vector()))
345 # Recursive call, but should work since elt lives in an
346 # associative algebra.
347 return elt
.is_nilpotent()
350 def subalgebra_idempotent(self
):
352 Find an idempotent in the associative subalgebra I generate
353 using Proposition 2.3.5 in Baes.
355 if self
.is_nilpotent():
356 raise ValueError("this only works with non-nilpotent elements!")
358 V
= self
.span_of_powers()
359 J
= self
.subalgebra_generated_by()
360 # Mis-design warning: the basis used for span_of_powers()
361 # and subalgebra_generated_by() must be the same, and in
363 u
= J(V
.coordinates(self
.vector()))
365 # The image of the matrix of left-u^m-multiplication
366 # will be minimal for some natural number s...
368 minimal_dim
= V
.dimension()
369 for i
in xrange(1, V
.dimension()):
370 this_dim
= (u
**i
).matrix().image().dimension()
371 if this_dim
< minimal_dim
:
372 minimal_dim
= this_dim
375 # Now minimal_matrix should correspond to the smallest
376 # non-zero subspace in Baes's (or really, Koecher's)
379 # However, we need to restrict the matrix to work on the
380 # subspace... or do we? Can't we just solve, knowing that
381 # A(c) = u^(s+1) should have a solution in the big space,
384 # Beware, solve_right() means that we're using COLUMN vectors.
385 # Our FiniteDimensionalAlgebraElement superclass uses rows.
388 c_coordinates
= A
.solve_right(u_next
.vector())
390 # Now c_coordinates is the idempotent we want, but it's in
391 # the coordinate system of the subalgebra.
393 # We need the basis for J, but as elements of the parent algebra.
395 basis
= [self
.parent(v
) for v
in V
.basis()]
396 return self
.parent().linear_combination(zip(c_coordinates
, basis
))
400 def characteristic_polynomial(self
):
401 return self
.matrix().characteristic_polynomial()
404 def eja_rn(dimension
, field
=QQ
):
406 Return the Euclidean Jordan Algebra corresponding to the set
407 `R^n` under the Hadamard product.
411 This multiplication table can be verified by hand::
414 sage: e0,e1,e2 = J.gens()
429 # The FiniteDimensionalAlgebra constructor takes a list of
430 # matrices, the ith representing right multiplication by the ith
431 # basis element in the vector space. So if e_1 = (1,0,0), then
432 # right (Hadamard) multiplication of x by e_1 picks out the first
433 # component of x; and likewise for the ith basis element e_i.
434 Qs
= [ matrix(field
, dimension
, dimension
, lambda k
,j
: 1*(k
== j
== i
))
435 for i
in xrange(dimension
) ]
437 return FiniteDimensionalEuclideanJordanAlgebra(field
,Qs
,rank
=dimension
)
440 def eja_ln(dimension
, field
=QQ
):
442 Return the Jordan algebra corresponding to the Lorentz "ice cream"
443 cone of the given ``dimension``.
447 This multiplication table can be verified by hand::
450 sage: e0,e1,e2,e3 = J.gens()
466 In one dimension, this is the reals under multiplication::
475 id_matrix
= identity_matrix(field
,dimension
)
476 for i
in xrange(dimension
):
477 ei
= id_matrix
.column(i
)
478 Qi
= zero_matrix(field
,dimension
)
481 Qi
+= diagonal_matrix(dimension
, [ei
[0]]*dimension
)
482 # The addition of the diagonal matrix adds an extra ei[0] in the
483 # upper-left corner of the matrix.
484 Qi
[0,0] = Qi
[0,0] * ~
field(2)
487 # The rank of the spin factor algebra is two, UNLESS we're in a
488 # one-dimensional ambient space (the rank is bounded by the
489 # ambient dimension).
490 rank
= min(dimension
,2)
491 return FiniteDimensionalEuclideanJordanAlgebra(field
,Qs
,rank
=rank
)