]> gitweb.michael.orlitzky.com - sage.d.git/blob - mjo/eja/eja_operator.py
eja: add is_self_adjoint() for operators.
[sage.d.git] / mjo / eja / eja_operator.py
1 from sage.matrix.constructor import matrix
2 from sage.categories.all import FreeModules
3 from sage.categories.map import Map
4
5 class FiniteDimensionalEuclideanJordanAlgebraOperator(Map):
6 r"""
7 An operator between two finite-dimensional Euclidean Jordan algebras.
8
9 SETUP::
10
11 sage: from mjo.eja.eja_algebra import HadamardEJA
12 sage: from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator
13
14 EXAMPLES:
15
16 The domain and codomain must be finite-dimensional Euclidean
17 Jordan algebras; if either is not, then an error is raised::
18
19 sage: J = HadamardEJA(3)
20 sage: V = VectorSpace(J.base_ring(), 3)
21 sage: M = matrix.identity(J.base_ring(), 3)
22 sage: FiniteDimensionalEuclideanJordanAlgebraOperator(V,J,M)
23 Traceback (most recent call last):
24 ...
25 TypeError: domain must be a finite-dimensional Euclidean
26 Jordan algebra
27 sage: FiniteDimensionalEuclideanJordanAlgebraOperator(J,V,M)
28 Traceback (most recent call last):
29 ...
30 TypeError: codomain must be a finite-dimensional Euclidean
31 Jordan algebra
32
33 """
34
35 def __init__(self, domain_eja, codomain_eja, mat):
36 from mjo.eja.eja_algebra import FiniteDimensionalEuclideanJordanAlgebra
37
38 # I guess we should check this, because otherwise you could
39 # pass in pretty much anything algebraish.
40 if not isinstance(domain_eja,
41 FiniteDimensionalEuclideanJordanAlgebra):
42 raise TypeError('domain must be a finite-dimensional '
43 'Euclidean Jordan algebra')
44 if not isinstance(codomain_eja,
45 FiniteDimensionalEuclideanJordanAlgebra):
46 raise TypeError('codomain must be a finite-dimensional '
47 'Euclidean Jordan algebra')
48
49 F = domain_eja.base_ring()
50 if not (F == codomain_eja.base_ring()):
51 raise ValueError("domain and codomain must have the same base ring")
52 if not (F == mat.base_ring()):
53 raise ValueError("domain and matrix must have the same base ring")
54
55 # We need to supply something here to avoid getting the
56 # default Homset of the parent FiniteDimensionalAlgebra class,
57 # which messes up e.g. equality testing. We use FreeModules(F)
58 # instead of VectorSpaces(F) because our characteristic polynomial
59 # algorithm will need to F to be a polynomial ring at some point.
60 # When F is a field, FreeModules(F) returns VectorSpaces(F) anyway.
61 parent = domain_eja.Hom(codomain_eja, FreeModules(F))
62
63 # The Map initializer will set our parent to a homset, which
64 # is explicitly NOT what we want, because these ain't algebra
65 # homomorphisms.
66 super(FiniteDimensionalEuclideanJordanAlgebraOperator,self).__init__(parent)
67
68 # Keep a matrix around to do all of the real work. It would
69 # be nice if we could use a VectorSpaceMorphism instead, but
70 # those use row vectors that we don't want to accidentally
71 # expose to our users.
72 self._matrix = mat
73
74
75 def _call_(self, x):
76 """
77 Allow this operator to be called only on elements of an EJA.
78
79 SETUP::
80
81 sage: from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator
82 sage: from mjo.eja.eja_algebra import JordanSpinEJA
83
84 EXAMPLES::
85
86 sage: J = JordanSpinEJA(3)
87 sage: x = J.linear_combination(zip(J.gens(),range(len(J.gens()))))
88 sage: id = identity_matrix(J.base_ring(), J.dimension())
89 sage: f = FiniteDimensionalEuclideanJordanAlgebraOperator(J,J,id)
90 sage: f(x) == x
91 True
92
93 """
94 return self.codomain().from_vector(self.matrix()*x.to_vector())
95
96
97 def _add_(self, other):
98 """
99 Add the ``other`` EJA operator to this one.
100
101 SETUP::
102
103 sage: from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator
104 sage: from mjo.eja.eja_algebra import (
105 ....: JordanSpinEJA,
106 ....: RealSymmetricEJA )
107
108 EXAMPLES:
109
110 When we add two EJA operators, we get another one back::
111
112 sage: J = RealSymmetricEJA(2)
113 sage: id = identity_matrix(J.base_ring(), J.dimension())
114 sage: f = FiniteDimensionalEuclideanJordanAlgebraOperator(J,J,id)
115 sage: g = FiniteDimensionalEuclideanJordanAlgebraOperator(J,J,id)
116 sage: f + g
117 Linear operator between finite-dimensional Euclidean Jordan
118 algebras represented by the matrix:
119 [2 0 0]
120 [0 2 0]
121 [0 0 2]
122 Domain: Euclidean Jordan algebra of dimension 3 over...
123 Codomain: Euclidean Jordan algebra of dimension 3 over...
124
125 If you try to add two identical vector space operators but on
126 different EJAs, that should blow up::
127
128 sage: J1 = RealSymmetricEJA(2)
129 sage: id1 = identity_matrix(J1.base_ring(), 3)
130 sage: J2 = JordanSpinEJA(3)
131 sage: id2 = identity_matrix(J2.base_ring(), 3)
132 sage: f = FiniteDimensionalEuclideanJordanAlgebraOperator(J1,J1,id1)
133 sage: g = FiniteDimensionalEuclideanJordanAlgebraOperator(J2,J2,id2)
134 sage: f + g
135 Traceback (most recent call last):
136 ...
137 TypeError: unsupported operand parent(s) for +: ...
138
139 """
140 return FiniteDimensionalEuclideanJordanAlgebraOperator(
141 self.domain(),
142 self.codomain(),
143 self.matrix() + other.matrix())
144
145
146 def _composition_(self, other, homset):
147 """
148 Compose two EJA operators to get another one (and NOT a formal
149 composite object) back.
150
151 SETUP::
152
153 sage: from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator
154 sage: from mjo.eja.eja_algebra import (
155 ....: JordanSpinEJA,
156 ....: HadamardEJA,
157 ....: RealSymmetricEJA)
158
159 EXAMPLES::
160
161 sage: J1 = JordanSpinEJA(3)
162 sage: J2 = HadamardEJA(2)
163 sage: J3 = RealSymmetricEJA(1)
164 sage: mat1 = matrix(AA, [[1,2,3],
165 ....: [4,5,6]])
166 sage: mat2 = matrix(AA, [[7,8]])
167 sage: g = FiniteDimensionalEuclideanJordanAlgebraOperator(J1,
168 ....: J2,
169 ....: mat1)
170 sage: f = FiniteDimensionalEuclideanJordanAlgebraOperator(J2,
171 ....: J3,
172 ....: mat2)
173 sage: f*g
174 Linear operator between finite-dimensional Euclidean Jordan
175 algebras represented by the matrix:
176 [39 54 69]
177 Domain: Euclidean Jordan algebra of dimension 3 over
178 Algebraic Real Field
179 Codomain: Euclidean Jordan algebra of dimension 1 over
180 Algebraic Real Field
181
182 """
183 return FiniteDimensionalEuclideanJordanAlgebraOperator(
184 other.domain(),
185 self.codomain(),
186 self.matrix()*other.matrix())
187
188
189 def __eq__(self, other):
190 if self.domain() != other.domain():
191 return False
192 if self.codomain() != other.codomain():
193 return False
194 if self.matrix() != other.matrix():
195 return False
196 return True
197
198
199 def __invert__(self):
200 """
201 Invert this EJA operator.
202
203 SETUP::
204
205 sage: from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator
206 sage: from mjo.eja.eja_algebra import RealSymmetricEJA
207
208 EXAMPLES::
209
210 sage: J = RealSymmetricEJA(2)
211 sage: id = identity_matrix(J.base_ring(), J.dimension())
212 sage: f = FiniteDimensionalEuclideanJordanAlgebraOperator(J,J,id)
213 sage: ~f
214 Linear operator between finite-dimensional Euclidean Jordan
215 algebras represented by the matrix:
216 [1 0 0]
217 [0 1 0]
218 [0 0 1]
219 Domain: Euclidean Jordan algebra of dimension 3 over...
220 Codomain: Euclidean Jordan algebra of dimension 3 over...
221
222 """
223 return FiniteDimensionalEuclideanJordanAlgebraOperator(
224 self.codomain(),
225 self.domain(),
226 ~self.matrix())
227
228
229 def __mul__(self, other):
230 """
231 Compose two EJA operators, or scale myself by an element of the
232 ambient vector space.
233
234 We need to override the real ``__mul__`` function to prevent the
235 coercion framework from throwing an error when it fails to convert
236 a base ring element into a morphism.
237
238 SETUP::
239
240 sage: from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator
241 sage: from mjo.eja.eja_algebra import RealSymmetricEJA
242
243 EXAMPLES:
244
245 We can scale an operator on a rational algebra by a rational number::
246
247 sage: J = RealSymmetricEJA(2)
248 sage: e0,e1,e2 = J.gens()
249 sage: x = 2*e0 + 4*e1 + 16*e2
250 sage: x.operator()
251 Linear operator between finite-dimensional Euclidean Jordan algebras
252 represented by the matrix:
253 [ 2 2 0]
254 [ 2 9 2]
255 [ 0 2 16]
256 Domain: Euclidean Jordan algebra of dimension 3 over...
257 Codomain: Euclidean Jordan algebra of dimension 3 over...
258 sage: x.operator()*(1/2)
259 Linear operator between finite-dimensional Euclidean Jordan algebras
260 represented by the matrix:
261 [ 1 1 0]
262 [ 1 9/2 1]
263 [ 0 1 8]
264 Domain: Euclidean Jordan algebra of dimension 3 over...
265 Codomain: Euclidean Jordan algebra of dimension 3 over...
266
267 """
268 try:
269 if other in self.codomain().base_ring():
270 return FiniteDimensionalEuclideanJordanAlgebraOperator(
271 self.domain(),
272 self.codomain(),
273 self.matrix()*other)
274 except NotImplementedError:
275 # This can happen with certain arguments if the base_ring()
276 # is weird and doesn't know how to test membership.
277 pass
278
279 # This should eventually delegate to _composition_ after performing
280 # some sanity checks for us.
281 mor = super(FiniteDimensionalEuclideanJordanAlgebraOperator,self)
282 return mor.__mul__(other)
283
284
285 def _neg_(self):
286 """
287 Negate this EJA operator.
288
289 SETUP::
290
291 sage: from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator
292 sage: from mjo.eja.eja_algebra import RealSymmetricEJA
293
294 EXAMPLES::
295
296 sage: J = RealSymmetricEJA(2)
297 sage: id = identity_matrix(J.base_ring(), J.dimension())
298 sage: f = FiniteDimensionalEuclideanJordanAlgebraOperator(J,J,id)
299 sage: -f
300 Linear operator between finite-dimensional Euclidean Jordan
301 algebras represented by the matrix:
302 [-1 0 0]
303 [ 0 -1 0]
304 [ 0 0 -1]
305 Domain: Euclidean Jordan algebra of dimension 3 over...
306 Codomain: Euclidean Jordan algebra of dimension 3 over...
307
308 """
309 return FiniteDimensionalEuclideanJordanAlgebraOperator(
310 self.domain(),
311 self.codomain(),
312 -self.matrix())
313
314
315 def __pow__(self, n):
316 """
317 Raise this EJA operator to the power ``n``.
318
319 SETUP::
320
321 sage: from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator
322 sage: from mjo.eja.eja_algebra import RealSymmetricEJA
323
324 TESTS:
325
326 Ensure that we get back another EJA operator that can be added,
327 subtracted, et cetera::
328
329 sage: J = RealSymmetricEJA(2)
330 sage: id = identity_matrix(J.base_ring(), J.dimension())
331 sage: f = FiniteDimensionalEuclideanJordanAlgebraOperator(J,J,id)
332 sage: f^0 + f^1 + f^2
333 Linear operator between finite-dimensional Euclidean Jordan
334 algebras represented by the matrix:
335 [3 0 0]
336 [0 3 0]
337 [0 0 3]
338 Domain: Euclidean Jordan algebra of dimension 3 over...
339 Codomain: Euclidean Jordan algebra of dimension 3 over...
340
341 """
342 if (n == 1):
343 return self
344 elif (n == 0):
345 # Raising a vector space morphism to the zero power gives
346 # you back a special IdentityMorphism that is useless to us.
347 rows = self.codomain().dimension()
348 cols = self.domain().dimension()
349 mat = matrix.identity(self.base_ring(), rows, cols)
350 else:
351 mat = self.matrix()**n
352
353 return FiniteDimensionalEuclideanJordanAlgebraOperator(
354 self.domain(),
355 self.codomain(),
356 mat)
357
358
359 def _repr_(self):
360 r"""
361
362 A text representation of this linear operator on a Euclidean
363 Jordan Algebra.
364
365 SETUP::
366
367 sage: from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator
368 sage: from mjo.eja.eja_algebra import JordanSpinEJA
369
370 EXAMPLES::
371
372 sage: J = JordanSpinEJA(2)
373 sage: id = identity_matrix(J.base_ring(), J.dimension())
374 sage: FiniteDimensionalEuclideanJordanAlgebraOperator(J,J,id)
375 Linear operator between finite-dimensional Euclidean Jordan
376 algebras represented by the matrix:
377 [1 0]
378 [0 1]
379 Domain: Euclidean Jordan algebra of dimension 2 over
380 Algebraic Real Field
381 Codomain: Euclidean Jordan algebra of dimension 2 over
382 Algebraic Real Field
383
384 """
385 msg = ("Linear operator between finite-dimensional Euclidean Jordan "
386 "algebras represented by the matrix:\n",
387 "{!r}\n",
388 "Domain: {}\n",
389 "Codomain: {}")
390 return ''.join(msg).format(self.matrix(),
391 self.domain(),
392 self.codomain())
393
394
395 def _sub_(self, other):
396 """
397 Subtract ``other`` from this EJA operator.
398
399 SETUP::
400
401 sage: from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator
402 sage: from mjo.eja.eja_algebra import RealSymmetricEJA
403
404 EXAMPLES::
405
406 sage: J = RealSymmetricEJA(2)
407 sage: id = identity_matrix(J.base_ring(),J.dimension())
408 sage: f = FiniteDimensionalEuclideanJordanAlgebraOperator(J,J,id)
409 sage: f - (f*2)
410 Linear operator between finite-dimensional Euclidean Jordan
411 algebras represented by the matrix:
412 [-1 0 0]
413 [ 0 -1 0]
414 [ 0 0 -1]
415 Domain: Euclidean Jordan algebra of dimension 3 over...
416 Codomain: Euclidean Jordan algebra of dimension 3 over...
417
418 """
419 return (self + (-other))
420
421
422 def is_self_adjoint(self):
423 r"""
424 Return whether or not this operator is self-adjoint.
425
426 At least in Sage, the fact that the base field is real means
427 that the algebra elements have to be real as well (this is why
428 we embed the complex numbers and quaternions). As a result, the
429 matrix of this operator will contain only real entries, and it
430 suffices to check only symmetry, not conjugate symmetry.
431
432 SETUP::
433
434 sage: from mjo.eja.eja_algebra import (JordanSpinEJA)
435
436 EXAMPLES::
437
438 sage: J = JordanSpinEJA(4)
439 sage: J.one().operator().is_self_adjoint()
440 True
441
442 """
443 return self.matrix().is_symmetric()
444
445
446 def is_zero(self):
447 r"""
448 Return whether or not this map is the zero operator.
449
450 SETUP::
451
452 sage: from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator
453 sage: from mjo.eja.eja_algebra import (random_eja,
454 ....: JordanSpinEJA,
455 ....: RealSymmetricEJA)
456
457 EXAMPLES::
458
459 sage: J1 = JordanSpinEJA(2)
460 sage: J2 = RealSymmetricEJA(2)
461 sage: R = J1.base_ring()
462 sage: M = matrix(R, [ [0, 0],
463 ....: [0, 0],
464 ....: [0, 0] ])
465 sage: L = FiniteDimensionalEuclideanJordanAlgebraOperator(J1,J2,M)
466 sage: L.is_zero()
467 True
468 sage: M = matrix(R, [ [0, 0],
469 ....: [0, 1],
470 ....: [0, 0] ])
471 sage: L = FiniteDimensionalEuclideanJordanAlgebraOperator(J1,J2,M)
472 sage: L.is_zero()
473 False
474
475 TESTS:
476
477 The left-multiplication-by-zero operation on a given algebra
478 is its zero map::
479
480 sage: set_random_seed()
481 sage: J = random_eja()
482 sage: J.zero().operator().is_zero()
483 True
484
485 """
486 return self.matrix().is_zero()
487
488
489 def inverse(self):
490 """
491 Return the inverse of this operator, if it exists.
492
493 The reason this method is not simply an alias for the built-in
494 :meth:`__invert__` is that the built-in inversion is a bit magic
495 since it's intended to be a unary operator. If we alias ``inverse``
496 to ``__invert__``, then we wind up having to call e.g. ``A.inverse``
497 without parentheses.
498
499 SETUP::
500
501 sage: from mjo.eja.eja_algebra import RealSymmetricEJA, random_eja
502
503 EXAMPLES::
504
505 sage: J = RealSymmetricEJA(2)
506 sage: x = sum(J.gens())
507 sage: x.operator().inverse().matrix()
508 [3/2 -1 1/2]
509 [ -1 2 -1]
510 [1/2 -1 3/2]
511 sage: x.operator().matrix().inverse()
512 [3/2 -1 1/2]
513 [ -1 2 -1]
514 [1/2 -1 3/2]
515
516 TESTS:
517
518 The identity operator is its own inverse::
519
520 sage: set_random_seed()
521 sage: J = random_eja()
522 sage: idJ = J.one().operator()
523 sage: idJ.inverse() == idJ
524 True
525
526 The inverse of the inverse is the operator we started with::
527
528 sage: set_random_seed()
529 sage: x = random_eja().random_element()
530 sage: L = x.operator()
531 sage: not L.is_invertible() or (L.inverse().inverse() == L)
532 True
533
534 """
535 return ~self
536
537
538 def is_invertible(self):
539 """
540 Return whether or not this operator is invertible.
541
542 SETUP::
543
544 sage: from mjo.eja.eja_algebra import (RealSymmetricEJA,
545 ....: TrivialEJA,
546 ....: random_eja)
547
548 EXAMPLES::
549
550 sage: J = RealSymmetricEJA(2)
551 sage: x = sum(J.gens())
552 sage: x.operator().matrix()
553 [ 1 1/2 0]
554 [1/2 1 1/2]
555 [ 0 1/2 1]
556 sage: x.operator().matrix().is_invertible()
557 True
558 sage: x.operator().is_invertible()
559 True
560
561 The zero operator is invertible in a trivial algebra::
562
563 sage: J = TrivialEJA()
564 sage: J.zero().operator().is_invertible()
565 True
566
567 TESTS:
568
569 The identity operator is always invertible::
570
571 sage: set_random_seed()
572 sage: J = random_eja()
573 sage: J.one().operator().is_invertible()
574 True
575
576 The zero operator is never invertible in a nontrivial algebra::
577
578 sage: set_random_seed()
579 sage: J = random_eja()
580 sage: not J.is_trivial() and J.zero().operator().is_invertible()
581 False
582
583 """
584 return self.matrix().is_invertible()
585
586
587 def matrix(self):
588 """
589 Return the matrix representation of this operator with respect
590 to the default bases of its (co)domain.
591
592 SETUP::
593
594 sage: from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator
595 sage: from mjo.eja.eja_algebra import RealSymmetricEJA
596
597 EXAMPLES::
598
599 sage: J = RealSymmetricEJA(2)
600 sage: mat = matrix(J.base_ring(), J.dimension(), range(9))
601 sage: f = FiniteDimensionalEuclideanJordanAlgebraOperator(J,J,mat)
602 sage: f.matrix()
603 [0 1 2]
604 [3 4 5]
605 [6 7 8]
606
607 """
608 return self._matrix
609
610
611 def minimal_polynomial(self):
612 """
613 Return the minimal polynomial of this linear operator,
614 in the variable ``t``.
615
616 SETUP::
617
618 sage: from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator
619 sage: from mjo.eja.eja_algebra import RealSymmetricEJA
620
621 EXAMPLES::
622
623 sage: J = RealSymmetricEJA(3)
624 sage: J.one().operator().minimal_polynomial()
625 t - 1
626
627 """
628 # The matrix method returns a polynomial in 'x' but want one in 't'.
629 return self.matrix().minimal_polynomial().change_variable_name('t')
630
631
632 def spectral_decomposition(self):
633 """
634 Return the spectral decomposition of this operator as a list of
635 (eigenvalue, orthogonal projector) pairs.
636
637 This is the unique spectral decomposition, up to the order of
638 the projection operators, with distinct eigenvalues. So, the
639 projections are generally onto subspaces of dimension greater
640 than one.
641
642 SETUP::
643
644 sage: from mjo.eja.eja_algebra import RealSymmetricEJA
645
646 EXAMPLES::
647
648 sage: J = RealSymmetricEJA(4)
649 sage: x = sum(J.gens())
650 sage: A = x.subalgebra_generated_by(orthonormalize_basis=True)
651 sage: L0x = A(x).operator()
652 sage: sd = L0x.spectral_decomposition()
653 sage: l0 = sd[0][0]
654 sage: l1 = sd[1][0]
655 sage: P0 = sd[0][1]
656 sage: P1 = sd[1][1]
657 sage: P0*l0 + P1*l1 == L0x
658 True
659 sage: P0 + P1 == P0^0 # the identity
660 True
661 sage: P0^2 == P0
662 True
663 sage: P1^2 == P1
664 True
665 sage: P0*P1 == A.zero().operator()
666 True
667 sage: P1*P0 == A.zero().operator()
668 True
669
670 """
671 if not self.matrix().is_symmetric():
672 raise ValueError('algebra basis is not orthonormal')
673
674 D,P = self.matrix().jordan_form(subdivide=False,transformation=True)
675 eigenvalues = D.diagonal()
676 us = P.columns()
677 projectors = []
678 for i in range(len(us)):
679 # they won't be normalized, but they have to be
680 # for the spectral theorem to work.
681 us[i] = us[i]/us[i].norm()
682 mat = us[i].column()*us[i].row()
683 Pi = FiniteDimensionalEuclideanJordanAlgebraOperator(
684 self.domain(),
685 self.codomain(),
686 mat)
687 projectors.append(Pi)
688 return list(zip(eigenvalues, projectors))