]> gitweb.michael.orlitzky.com - sage.d.git/blob - mjo/eja/eja_subalgebra.py
eja: fix one() for subalgebras with orthonormal bases.
[sage.d.git] / mjo / eja / eja_subalgebra.py
1 from sage.matrix.constructor import matrix
2
3 from mjo.eja.eja_algebra import FiniteDimensionalEuclideanJordanAlgebra
4 from mjo.eja.eja_element import FiniteDimensionalEuclideanJordanAlgebraElement
5 from mjo.eja.eja_utils import gram_schmidt
6
7 class FiniteDimensionalEuclideanJordanElementSubalgebraElement(FiniteDimensionalEuclideanJordanAlgebraElement):
8 """
9 SETUP::
10
11 sage: from mjo.eja.eja_algebra import random_eja
12
13 TESTS::
14
15 The natural representation of an element in the subalgebra is
16 the same as its natural representation in the superalgebra::
17
18 sage: set_random_seed()
19 sage: A = random_eja().random_element().subalgebra_generated_by()
20 sage: y = A.random_element()
21 sage: actual = y.natural_representation()
22 sage: expected = y.superalgebra_element().natural_representation()
23 sage: actual == expected
24 True
25
26 The left-multiplication-by operator for elements in the subalgebra
27 works like it does in the superalgebra, even if we orthonormalize
28 our basis::
29
30 sage: set_random_seed()
31 sage: x = random_eja(AA).random_element()
32 sage: A = x.subalgebra_generated_by(orthonormalize_basis=True)
33 sage: y = A.random_element()
34 sage: y.operator()(A.one()) == y
35 True
36
37 """
38
39 def superalgebra_element(self):
40 """
41 Return the object in our algebra's superalgebra that corresponds
42 to myself.
43
44 SETUP::
45
46 sage: from mjo.eja.eja_algebra import (RealSymmetricEJA,
47 ....: random_eja)
48
49 EXAMPLES::
50
51 sage: J = RealSymmetricEJA(3)
52 sage: x = sum(J.gens())
53 sage: x
54 e0 + e1 + e2 + e3 + e4 + e5
55 sage: A = x.subalgebra_generated_by()
56 sage: A(x)
57 f1
58 sage: A(x).superalgebra_element()
59 e0 + e1 + e2 + e3 + e4 + e5
60
61 TESTS:
62
63 We can convert back and forth faithfully::
64
65 sage: set_random_seed()
66 sage: J = random_eja()
67 sage: x = J.random_element()
68 sage: A = x.subalgebra_generated_by()
69 sage: A(x).superalgebra_element() == x
70 True
71 sage: y = A.random_element()
72 sage: A(y.superalgebra_element()) == y
73 True
74
75 """
76 return self.parent().superalgebra().linear_combination(
77 zip(self.parent()._superalgebra_basis, self.to_vector()) )
78
79
80
81
82 class FiniteDimensionalEuclideanJordanElementSubalgebra(FiniteDimensionalEuclideanJordanAlgebra):
83 """
84 The subalgebra of an EJA generated by a single element.
85
86 SETUP::
87
88 sage: from mjo.eja.eja_algebra import (ComplexHermitianEJA,
89 ....: JordanSpinEJA)
90
91 TESTS:
92
93 Ensure that our generator names don't conflict with the superalgebra::
94
95 sage: J = JordanSpinEJA(3)
96 sage: J.one().subalgebra_generated_by().gens()
97 (f0,)
98 sage: J = JordanSpinEJA(3, prefix='f')
99 sage: J.one().subalgebra_generated_by().gens()
100 (g0,)
101 sage: J = JordanSpinEJA(3, prefix='b')
102 sage: J.one().subalgebra_generated_by().gens()
103 (c0,)
104
105 Ensure that we can find subalgebras of subalgebras::
106
107 sage: A = ComplexHermitianEJA(3).one().subalgebra_generated_by()
108 sage: B = A.one().subalgebra_generated_by()
109 sage: B.dimension()
110 1
111
112 """
113 def __init__(self, elt, orthonormalize_basis):
114 self._superalgebra = elt.parent()
115 category = self._superalgebra.category().Associative()
116 V = self._superalgebra.vector_space()
117 field = self._superalgebra.base_ring()
118
119 # A half-assed attempt to ensure that we don't collide with
120 # the superalgebra's prefix (ignoring the fact that there
121 # could be super-superelgrbas in scope). If possible, we
122 # try to "increment" the parent algebra's prefix, although
123 # this idea goes out the window fast because some prefixen
124 # are off-limits.
125 prefixen = [ 'f', 'g', 'h', 'a', 'b', 'c', 'd' ]
126 try:
127 prefix = prefixen[prefixen.index(self._superalgebra.prefix()) + 1]
128 except ValueError:
129 prefix = prefixen[0]
130
131 if elt.is_zero():
132 # Short circuit because 0^0 == 1 is going to make us
133 # think we have a one-dimensional algebra otherwise.
134 natural_basis = tuple()
135 mult_table = tuple()
136 rank = 0
137 self._vector_space = V.zero_subspace()
138 self._superalgebra_basis = []
139 fdeja = super(FiniteDimensionalEuclideanJordanElementSubalgebra,
140 self)
141 return fdeja.__init__(field,
142 mult_table,
143 rank,
144 prefix=prefix,
145 category=category,
146 natural_basis=natural_basis)
147
148
149 # This list is guaranteed to contain all independent powers,
150 # because it's the maximal set of powers that could possibly
151 # be independent (by a dimension argument).
152 powers = [ elt**k for k in range(V.dimension()) ]
153
154 if orthonormalize_basis == False:
155 # In this case, we just need to figure out which elements
156 # of the "powers" list are redundant... First compute the
157 # vector subspace spanned by the powers of the given
158 # element.
159 power_vectors = [ p.to_vector() for p in powers ]
160
161 # Figure out which powers form a linearly-independent set.
162 ind_rows = matrix(field, power_vectors).pivot_rows()
163
164 # Pick those out of the list of all powers.
165 superalgebra_basis = tuple(map(powers.__getitem__, ind_rows))
166
167 # If our superalgebra is a subalgebra of something else, then
168 # these vectors won't have the right coordinates for
169 # V.span_of_basis() unless we use V.from_vector() on them.
170 basis_vectors = map(power_vectors.__getitem__, ind_rows)
171 else:
172 # If we're going to orthonormalize the basis anyway, we
173 # might as well just do Gram-Schmidt on the whole list of
174 # powers. The redundant ones will get zero'd out.
175 superalgebra_basis = gram_schmidt(powers)
176 basis_vectors = [ b.to_vector() for b in superalgebra_basis ]
177
178 W = V.span_of_basis( V.from_vector(v) for v in basis_vectors )
179 n = len(superalgebra_basis)
180 mult_table = [[W.zero() for i in range(n)] for j in range(n)]
181 for i in range(n):
182 for j in range(n):
183 product = superalgebra_basis[i]*superalgebra_basis[j]
184 # product.to_vector() might live in a vector subspace
185 # if our parent algebra is already a subalgebra. We
186 # use V.from_vector() to make it "the right size" in
187 # that case.
188 product_vector = V.from_vector(product.to_vector())
189 mult_table[i][j] = W.coordinate_vector(product_vector)
190
191 # The rank is the highest possible degree of a minimal
192 # polynomial, and is bounded above by the dimension. We know
193 # in this case that there's an element whose minimal
194 # polynomial has the same degree as the space's dimension
195 # (remember how we constructed the space?), so that must be
196 # its rank too.
197 rank = W.dimension()
198
199 natural_basis = tuple( b.natural_representation()
200 for b in superalgebra_basis )
201
202
203 self._vector_space = W
204 self._superalgebra_basis = superalgebra_basis
205
206
207 fdeja = super(FiniteDimensionalEuclideanJordanElementSubalgebra, self)
208 return fdeja.__init__(field,
209 mult_table,
210 rank,
211 prefix=prefix,
212 category=category,
213 natural_basis=natural_basis)
214
215
216 def _a_regular_element(self):
217 """
218 Override the superalgebra method to return the one
219 regular element that is sure to exist in this
220 subalgebra, namely the element that generated it.
221
222 SETUP::
223
224 sage: from mjo.eja.eja_algebra import random_eja
225
226 TESTS::
227
228 sage: set_random_seed()
229 sage: J = random_eja().random_element().subalgebra_generated_by()
230 sage: J._a_regular_element().is_regular()
231 True
232
233 """
234 if self.dimension() == 0:
235 return self.zero()
236 else:
237 return self.monomial(1)
238
239
240 def _element_constructor_(self, elt):
241 """
242 Construct an element of this subalgebra from the given one.
243 The only valid arguments are elements of the parent algebra
244 that happen to live in this subalgebra.
245
246 SETUP::
247
248 sage: from mjo.eja.eja_algebra import RealSymmetricEJA
249 sage: from mjo.eja.eja_subalgebra import FiniteDimensionalEuclideanJordanElementSubalgebra
250
251 EXAMPLES::
252
253 sage: J = RealSymmetricEJA(3)
254 sage: x = sum( i*J.gens()[i] for i in range(6) )
255 sage: K = FiniteDimensionalEuclideanJordanElementSubalgebra(x,False)
256 sage: [ K(x^k) for k in range(J.rank()) ]
257 [f0, f1, f2]
258
259 ::
260
261 """
262 if elt == 0:
263 # Just as in the superalgebra class, we need to hack
264 # this special case to ensure that random_element() can
265 # coerce a ring zero into the algebra.
266 return self.zero()
267
268 if elt in self.superalgebra():
269 coords = self.vector_space().coordinate_vector(elt.to_vector())
270 return self.from_vector(coords)
271
272
273
274 def one(self):
275 """
276 Return the multiplicative identity element of this algebra.
277
278 The superclass method computes the identity element, which is
279 beyond overkill in this case: the superalgebra identity
280 restricted to this algebra is its identity. Note that we can't
281 count on the first basis element being the identity -- it migth
282 have been scaled if we orthonormalized the basis.
283
284 SETUP::
285
286 sage: from mjo.eja.eja_algebra import (RealCartesianProductEJA,
287 ....: random_eja)
288
289 EXAMPLES::
290
291 sage: J = RealCartesianProductEJA(5)
292 sage: J.one()
293 e0 + e1 + e2 + e3 + e4
294 sage: x = sum(J.gens())
295 sage: A = x.subalgebra_generated_by()
296 sage: A.one()
297 f0
298 sage: A.one().superalgebra_element()
299 e0 + e1 + e2 + e3 + e4
300
301 TESTS:
302
303 The identity element acts like the identity over the rationals::
304
305 sage: set_random_seed()
306 sage: x = random_eja().random_element()
307 sage: A = x.subalgebra_generated_by()
308 sage: x = A.random_element()
309 sage: A.one()*x == x and x*A.one() == x
310 True
311
312 The identity element acts like the identity over the algebraic
313 reals with an orthonormal basis::
314
315 sage: set_random_seed()
316 sage: x = random_eja(AA).random_element()
317 sage: A = x.subalgebra_generated_by(orthonormalize_basis=True)
318 sage: x = A.random_element()
319 sage: A.one()*x == x and x*A.one() == x
320 True
321
322 The matrix of the unit element's operator is the identity over
323 the rationals::
324
325 sage: set_random_seed()
326 sage: x = random_eja().random_element()
327 sage: A = x.subalgebra_generated_by()
328 sage: actual = A.one().operator().matrix()
329 sage: expected = matrix.identity(A.base_ring(), A.dimension())
330 sage: actual == expected
331 True
332
333 The matrix of the unit element's operator is the identity over
334 the algebraic reals with an orthonormal basis::
335
336 sage: set_random_seed()
337 sage: x = random_eja(AA).random_element()
338 sage: A = x.subalgebra_generated_by(orthonormalize_basis=True)
339 sage: actual = A.one().operator().matrix()
340 sage: expected = matrix.identity(A.base_ring(), A.dimension())
341 sage: actual == expected
342 True
343
344 """
345 if self.dimension() == 0:
346 return self.zero()
347 else:
348 sa_one = self.superalgebra().one().to_vector()
349 sa_coords = self.vector_space().coordinate_vector(sa_one)
350 return self.from_vector(sa_coords)
351
352
353 def natural_basis_space(self):
354 """
355 Return the natural basis space of this algebra, which is identical
356 to that of its superalgebra.
357
358 This is correct "by definition," and avoids a mismatch when the
359 subalgebra is trivial (with no natural basis to infer anything
360 from) and the parent is not.
361 """
362 return self.superalgebra().natural_basis_space()
363
364
365 def superalgebra(self):
366 """
367 Return the superalgebra that this algebra was generated from.
368 """
369 return self._superalgebra
370
371
372 def vector_space(self):
373 """
374 SETUP::
375
376 sage: from mjo.eja.eja_algebra import RealSymmetricEJA
377 sage: from mjo.eja.eja_subalgebra import FiniteDimensionalEuclideanJordanElementSubalgebra
378
379 EXAMPLES::
380
381 sage: J = RealSymmetricEJA(3)
382 sage: x = J.monomial(0) + 2*J.monomial(2) + 5*J.monomial(5)
383 sage: K = FiniteDimensionalEuclideanJordanElementSubalgebra(x,False)
384 sage: K.vector_space()
385 Vector space of degree 6 and dimension 3 over...
386 User basis matrix:
387 [ 1 0 1 0 0 1]
388 [ 1 0 2 0 0 5]
389 [ 1 0 4 0 0 25]
390 sage: (x^0).to_vector()
391 (1, 0, 1, 0, 0, 1)
392 sage: (x^1).to_vector()
393 (1, 0, 2, 0, 0, 5)
394 sage: (x^2).to_vector()
395 (1, 0, 4, 0, 0, 25)
396
397 """
398 return self._vector_space
399
400
401 Element = FiniteDimensionalEuclideanJordanElementSubalgebraElement