]> gitweb.michael.orlitzky.com - sage.d.git/blob - mjo/eja/euclidean_jordan_algebra.py
eja: add is_nilpotent() for elements.
[sage.d.git] / mjo / eja / euclidean_jordan_algebra.py
1 """
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.
6 """
7
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
11
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
14
15 class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra):
16 @staticmethod
17 def __classcall_private__(cls,
18 field,
19 mult_table,
20 names='e',
21 assume_associative=False,
22 category=None,
23 rank=None):
24 n = len(mult_table)
25 mult_table = [b.base_extend(field) for b in mult_table]
26 for b in mult_table:
27 b.set_immutable()
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)
31
32 cat = MagmaticAlgebras(field).FiniteDimensional().WithBasis()
33 cat.or_subcategory(category)
34 if assume_associative:
35 cat = cat.Associative()
36
37 names = normalize_names(n, names)
38
39 fda = super(FiniteDimensionalEuclideanJordanAlgebra, cls)
40 return fda.__classcall__(cls,
41 field,
42 mult_table,
43 assume_associative=assume_associative,
44 names=names,
45 category=cat,
46 rank=rank)
47
48
49 def __init__(self, field,
50 mult_table,
51 names='e',
52 assume_associative=False,
53 category=None,
54 rank=None):
55 self._rank = rank
56 fda = super(FiniteDimensionalEuclideanJordanAlgebra, self)
57 fda.__init__(field,
58 mult_table,
59 names=names,
60 category=category)
61
62
63 def _repr_(self):
64 """
65 Return a string representation of ``self``.
66 """
67 fmt = "Euclidean Jordan algebra of degree {} over {}"
68 return fmt.format(self.degree(), self.base_ring())
69
70 def rank(self):
71 """
72 Return the rank of this EJA.
73 """
74 if self._rank is None:
75 raise ValueError("no rank specified at genesis")
76 else:
77 return self._rank
78
79
80 class Element(FiniteDimensionalAlgebraElement):
81 """
82 An element of a Euclidean Jordan algebra.
83
84 Since EJAs are commutative, the "right multiplication" matrix is
85 also the left multiplication matrix and must be symmetric::
86
87 sage: set_random_seed()
88 sage: n = ZZ.random_element(1,10).abs()
89 sage: J = eja_rn(5)
90 sage: J.random_element().matrix().is_symmetric()
91 True
92 sage: J = eja_ln(5)
93 sage: J.random_element().matrix().is_symmetric()
94 True
95
96 """
97
98 def __pow__(self, n):
99 """
100 Return ``self`` raised to the power ``n``.
101
102 Jordan algebras are always power-associative; see for
103 example Faraut and Koranyi, Proposition II.1.2 (ii).
104 """
105 A = self.parent()
106 if n == 0:
107 return A.one()
108 elif n == 1:
109 return self
110 else:
111 return A.element_class(A, self.vector()*(self.matrix()**(n-1)))
112
113
114 def span_of_powers(self):
115 """
116 Return the vector space spanned by successive powers of
117 this element.
118 """
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()) )
124
125
126 def degree(self):
127 """
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).
132
133 EXAMPLES::
134
135 sage: J = eja_ln(4)
136 sage: J.one().degree()
137 1
138 sage: e0,e1,e2,e3 = J.gens()
139 sage: (e0 - e1).degree()
140 2
141
142 In the spin factor algebra (of rank two), all elements that
143 aren't multiples of the identity are regular::
144
145 sage: set_random_seed()
146 sage: n = ZZ.random_element(1,10).abs()
147 sage: J = eja_ln(n)
148 sage: x = J.random_element()
149 sage: x == x.coefficient(0)*J.one() or x.degree() == 2
150 True
151
152 """
153 return self.span_of_powers().dimension()
154
155
156 def subalgebra_generated_by(self):
157 """
158 Return the associative subalgebra of the parent EJA generated
159 by this element.
160
161 TESTS::
162
163 sage: set_random_seed()
164 sage: n = ZZ.random_element(1,10).abs()
165 sage: J = eja_rn(n)
166 sage: x = J.random_element()
167 sage: x.subalgebra_generated_by().is_associative()
168 True
169 sage: J = eja_ln(n)
170 sage: x = J.random_element()
171 sage: x.subalgebra_generated_by().is_associative()
172 True
173
174 """
175 # First get the subspace spanned by the powers of myself...
176 V = self.span_of_powers()
177 F = self.base_ring()
178
179 # Now figure out the entries of the right-multiplication
180 # matrix for the successive basis elements b0, b1,... of
181 # that subspace.
182 mats = []
183 for b_right in V.basis():
184 eja_b_right = self.parent()(b_right)
185 b_right_rows = []
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
194 # basis.
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)
199
200 # It's an algebra of polynomials in one element, and EJAs
201 # are power-associative.
202 return FiniteDimensionalEuclideanJordanAlgebra(F, mats, assume_associative=True)
203
204
205 def minimal_polynomial(self):
206 """
207 EXAMPLES::
208
209 sage: set_random_seed()
210 sage: n = ZZ.random_element(1,10).abs()
211 sage: J = eja_rn(n)
212 sage: x = J.random_element()
213 sage: x.degree() == x.minimal_polynomial().degree()
214 True
215
216 ::
217
218 sage: set_random_seed()
219 sage: n = ZZ.random_element(1,10).abs()
220 sage: J = eja_ln(n)
221 sage: x = J.random_element()
222 sage: x.degree() == x.minimal_polynomial().degree()
223 True
224
225 The minimal polynomial and the characteristic polynomial coincide
226 and are known (see Alizadeh, Example 11.11) for all elements of
227 the spin factor algebra that aren't scalar multiples of the
228 identity::
229
230 sage: set_random_seed()
231 sage: n = ZZ.random_element(2,10).abs()
232 sage: J = eja_ln(n)
233 sage: y = J.random_element()
234 sage: while y == y.coefficient(0)*J.one():
235 ....: y = J.random_element()
236 sage: y0 = y.vector()[0]
237 sage: y_bar = y.vector()[1:]
238 sage: actual = y.minimal_polynomial()
239 sage: x = SR.symbol('x', domain='real')
240 sage: expected = x^2 - 2*y0*x + (y0^2 - norm(y_bar)^2)
241 sage: bool(actual == expected)
242 True
243
244 """
245 # The element we're going to call "minimal_polynomial()" on.
246 # Either myself, interpreted as an element of a finite-
247 # dimensional algebra, or an element of an associative
248 # subalgebra.
249 elt = None
250
251 if self.parent().is_associative():
252 elt = FiniteDimensionalAlgebraElement(self.parent(), self)
253 else:
254 V = self.span_of_powers()
255 assoc_subalg = self.subalgebra_generated_by()
256 # Mis-design warning: the basis used for span_of_powers()
257 # and subalgebra_generated_by() must be the same, and in
258 # the same order!
259 elt = assoc_subalg(V.coordinates(self.vector()))
260
261 # Recursive call, but should work since elt lives in an
262 # associative algebra.
263 return elt.minimal_polynomial()
264
265
266 def is_nilpotent(self):
267 """
268 Return whether or not some power of this element is zero.
269
270 The superclass method won't work unless we're in an
271 associative algebra, and we aren't. However, we generate
272 an assocoative subalgebra and we're nilpotent there if and
273 only if we're nilpotent here (probably).
274
275 TESTS:
276
277 The identity element is never nilpotent::
278
279 sage: set_random_seed()
280 sage: n = ZZ.random_element(2,10).abs()
281 sage: J = eja_rn(n)
282 sage: J.one().is_nilpotent()
283 False
284 sage: J = eja_ln(n)
285 sage: J.one().is_nilpotent()
286 False
287
288 The additive identity is always nilpotent::
289
290 sage: set_random_seed()
291 sage: n = ZZ.random_element(2,10).abs()
292 sage: J = eja_rn(n)
293 sage: J.zero().is_nilpotent()
294 True
295 sage: J = eja_ln(n)
296 sage: J.zero().is_nilpotent()
297 True
298
299 """
300 # The element we're going to call "is_nilpotent()" on.
301 # Either myself, interpreted as an element of a finite-
302 # dimensional algebra, or an element of an associative
303 # subalgebra.
304 elt = None
305
306 if self.parent().is_associative():
307 elt = FiniteDimensionalAlgebraElement(self.parent(), self)
308 else:
309 V = self.span_of_powers()
310 assoc_subalg = self.subalgebra_generated_by()
311 # Mis-design warning: the basis used for span_of_powers()
312 # and subalgebra_generated_by() must be the same, and in
313 # the same order!
314 elt = assoc_subalg(V.coordinates(self.vector()))
315
316 # Recursive call, but should work since elt lives in an
317 # associative algebra.
318 return elt.is_nilpotent()
319
320
321 def characteristic_polynomial(self):
322 return self.matrix().characteristic_polynomial()
323
324
325 def eja_rn(dimension, field=QQ):
326 """
327 Return the Euclidean Jordan Algebra corresponding to the set
328 `R^n` under the Hadamard product.
329
330 EXAMPLES:
331
332 This multiplication table can be verified by hand::
333
334 sage: J = eja_rn(3)
335 sage: e0,e1,e2 = J.gens()
336 sage: e0*e0
337 e0
338 sage: e0*e1
339 0
340 sage: e0*e2
341 0
342 sage: e1*e1
343 e1
344 sage: e1*e2
345 0
346 sage: e2*e2
347 e2
348
349 """
350 # The FiniteDimensionalAlgebra constructor takes a list of
351 # matrices, the ith representing right multiplication by the ith
352 # basis element in the vector space. So if e_1 = (1,0,0), then
353 # right (Hadamard) multiplication of x by e_1 picks out the first
354 # component of x; and likewise for the ith basis element e_i.
355 Qs = [ matrix(field, dimension, dimension, lambda k,j: 1*(k == j == i))
356 for i in xrange(dimension) ]
357
358 return FiniteDimensionalEuclideanJordanAlgebra(field,Qs,rank=dimension)
359
360
361 def eja_ln(dimension, field=QQ):
362 """
363 Return the Jordan algebra corresponding to the Lorentz "ice cream"
364 cone of the given ``dimension``.
365
366 EXAMPLES:
367
368 This multiplication table can be verified by hand::
369
370 sage: J = eja_ln(4)
371 sage: e0,e1,e2,e3 = J.gens()
372 sage: e0*e0
373 e0
374 sage: e0*e1
375 e1
376 sage: e0*e2
377 e2
378 sage: e0*e3
379 e3
380 sage: e1*e2
381 0
382 sage: e1*e3
383 0
384 sage: e2*e3
385 0
386
387 In one dimension, this is the reals under multiplication::
388
389 sage: J1 = eja_ln(1)
390 sage: J2 = eja_rn(1)
391 sage: J1 == J2
392 True
393
394 """
395 Qs = []
396 id_matrix = identity_matrix(field,dimension)
397 for i in xrange(dimension):
398 ei = id_matrix.column(i)
399 Qi = zero_matrix(field,dimension)
400 Qi.set_row(0, ei)
401 Qi.set_column(0, ei)
402 Qi += diagonal_matrix(dimension, [ei[0]]*dimension)
403 # The addition of the diagonal matrix adds an extra ei[0] in the
404 # upper-left corner of the matrix.
405 Qi[0,0] = Qi[0,0] * ~field(2)
406 Qs.append(Qi)
407
408 # The rank of the spin factor algebra is two, UNLESS we're in a
409 # one-dimensional ambient space (the rank is bounded by the
410 # ambient dimension).
411 rank = min(dimension,2)
412 return FiniteDimensionalEuclideanJordanAlgebra(field,Qs,rank=rank)