1 from sage
.matrix
.constructor
import matrix
2 from sage
.categories
.all
import FreeModules
3 from sage
.categories
.map import Map
5 class FiniteDimensionalEuclideanJordanAlgebraOperator(Map
):
7 An operator between two finite-dimensional Euclidean Jordan algebras.
11 sage: from mjo.eja.eja_algebra import HadamardEJA
12 sage: from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator
16 The domain and codomain must be finite-dimensional Euclidean
17 Jordan algebras; if either is not, then an error is raised::
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):
25 TypeError: domain must be a finite-dimensional Euclidean
27 sage: FiniteDimensionalEuclideanJordanAlgebraOperator(J,V,M)
28 Traceback (most recent call last):
30 TypeError: codomain must be a finite-dimensional Euclidean
35 def __init__(self
, domain_eja
, codomain_eja
, mat
):
36 from mjo
.eja
.eja_algebra
import FiniteDimensionalEuclideanJordanAlgebra
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')
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")
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
))
63 # The Map initializer will set our parent to a homset, which
64 # is explicitly NOT what we want, because these ain't algebra
66 super(FiniteDimensionalEuclideanJordanAlgebraOperator
,self
).__init
__(parent
)
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.
77 Allow this operator to be called only on elements of an EJA.
81 sage: from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator
82 sage: from mjo.eja.eja_algebra import JordanSpinEJA
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)
94 return self
.codomain().from_vector(self
.matrix()*x
.to_vector())
97 def _add_(self
, other
):
99 Add the ``other`` EJA operator to this one.
103 sage: from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator
104 sage: from mjo.eja.eja_algebra import (
106 ....: RealSymmetricEJA )
110 When we add two EJA operators, we get another one back::
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)
117 Linear operator between finite-dimensional Euclidean Jordan
118 algebras represented by the matrix:
122 Domain: Euclidean Jordan algebra of dimension 3 over...
123 Codomain: Euclidean Jordan algebra of dimension 3 over...
125 If you try to add two identical vector space operators but on
126 different EJAs, that should blow up::
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)
135 Traceback (most recent call last):
137 TypeError: unsupported operand parent(s) for +: ...
140 return FiniteDimensionalEuclideanJordanAlgebraOperator(
143 self
.matrix() + other
.matrix())
146 def _composition_(self
, other
, homset
):
148 Compose two EJA operators to get another one (and NOT a formal
149 composite object) back.
153 sage: from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator
154 sage: from mjo.eja.eja_algebra import (
157 ....: RealSymmetricEJA)
161 sage: J1 = JordanSpinEJA(3)
162 sage: J2 = HadamardEJA(2)
163 sage: J3 = RealSymmetricEJA(1)
164 sage: mat1 = matrix(AA, [[1,2,3],
166 sage: mat2 = matrix(AA, [[7,8]])
167 sage: g = FiniteDimensionalEuclideanJordanAlgebraOperator(J1,
170 sage: f = FiniteDimensionalEuclideanJordanAlgebraOperator(J2,
174 Linear operator between finite-dimensional Euclidean Jordan
175 algebras represented by the matrix:
177 Domain: Euclidean Jordan algebra of dimension 3 over
179 Codomain: Euclidean Jordan algebra of dimension 1 over
183 return FiniteDimensionalEuclideanJordanAlgebraOperator(
186 self
.matrix()*other
.matrix())
189 def __eq__(self
, other
):
190 if self
.domain() != other
.domain():
192 if self
.codomain() != other
.codomain():
194 if self
.matrix() != other
.matrix():
199 def __invert__(self
):
201 Invert this EJA operator.
205 sage: from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator
206 sage: from mjo.eja.eja_algebra import RealSymmetricEJA
210 sage: J = RealSymmetricEJA(2)
211 sage: id = identity_matrix(J.base_ring(), J.dimension())
212 sage: f = FiniteDimensionalEuclideanJordanAlgebraOperator(J,J,id)
214 Linear operator between finite-dimensional Euclidean Jordan
215 algebras represented by the matrix:
219 Domain: Euclidean Jordan algebra of dimension 3 over...
220 Codomain: Euclidean Jordan algebra of dimension 3 over...
223 return FiniteDimensionalEuclideanJordanAlgebraOperator(
229 def __mul__(self
, other
):
231 Compose two EJA operators, or scale myself by an element of the
232 ambient vector space.
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.
240 sage: from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator
241 sage: from mjo.eja.eja_algebra import RealSymmetricEJA
245 We can scale an operator on a rational algebra by a rational number::
247 sage: J = RealSymmetricEJA(2)
248 sage: e0,e1,e2 = J.gens()
249 sage: x = 2*e0 + 4*e1 + 16*e2
251 Linear operator between finite-dimensional Euclidean Jordan algebras
252 represented by the matrix:
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:
264 Domain: Euclidean Jordan algebra of dimension 3 over...
265 Codomain: Euclidean Jordan algebra of dimension 3 over...
269 if other
in self
.codomain().base_ring():
270 return FiniteDimensionalEuclideanJordanAlgebraOperator(
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.
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
)
287 Negate this EJA operator.
291 sage: from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator
292 sage: from mjo.eja.eja_algebra import RealSymmetricEJA
296 sage: J = RealSymmetricEJA(2)
297 sage: id = identity_matrix(J.base_ring(), J.dimension())
298 sage: f = FiniteDimensionalEuclideanJordanAlgebraOperator(J,J,id)
300 Linear operator between finite-dimensional Euclidean Jordan
301 algebras represented by the matrix:
305 Domain: Euclidean Jordan algebra of dimension 3 over...
306 Codomain: Euclidean Jordan algebra of dimension 3 over...
309 return FiniteDimensionalEuclideanJordanAlgebraOperator(
315 def __pow__(self
, n
):
317 Raise this EJA operator to the power ``n``.
321 sage: from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator
322 sage: from mjo.eja.eja_algebra import RealSymmetricEJA
326 Ensure that we get back another EJA operator that can be added,
327 subtracted, et cetera::
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:
338 Domain: Euclidean Jordan algebra of dimension 3 over...
339 Codomain: Euclidean Jordan algebra of dimension 3 over...
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
)
351 mat
= self
.matrix()**n
353 return FiniteDimensionalEuclideanJordanAlgebraOperator(
362 A text representation of this linear operator on a Euclidean
367 sage: from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator
368 sage: from mjo.eja.eja_algebra import JordanSpinEJA
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:
379 Domain: Euclidean Jordan algebra of dimension 2 over
381 Codomain: Euclidean Jordan algebra of dimension 2 over
385 msg
= ("Linear operator between finite-dimensional Euclidean Jordan "
386 "algebras represented by the matrix:\n",
390 return ''.join(msg
).format(self
.matrix(),
395 def _sub_(self
, other
):
397 Subtract ``other`` from this EJA operator.
401 sage: from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator
402 sage: from mjo.eja.eja_algebra import RealSymmetricEJA
406 sage: J = RealSymmetricEJA(2)
407 sage: id = identity_matrix(J.base_ring(),J.dimension())
408 sage: f = FiniteDimensionalEuclideanJordanAlgebraOperator(J,J,id)
410 Linear operator between finite-dimensional Euclidean Jordan
411 algebras represented by the matrix:
415 Domain: Euclidean Jordan algebra of dimension 3 over...
416 Codomain: Euclidean Jordan algebra of dimension 3 over...
419 return (self
+ (-other
))
424 Return whether or not this map is the zero operator.
428 sage: from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator
429 sage: from mjo.eja.eja_algebra import (random_eja,
431 ....: RealSymmetricEJA)
435 sage: J1 = JordanSpinEJA(2)
436 sage: J2 = RealSymmetricEJA(2)
437 sage: R = J1.base_ring()
438 sage: M = matrix(R, [ [0, 0],
441 sage: L = FiniteDimensionalEuclideanJordanAlgebraOperator(J1,J2,M)
444 sage: M = matrix(R, [ [0, 0],
447 sage: L = FiniteDimensionalEuclideanJordanAlgebraOperator(J1,J2,M)
453 The left-multiplication-by-zero operation on a given algebra
456 sage: set_random_seed()
457 sage: J = random_eja()
458 sage: J.zero().operator().is_zero()
462 return self
.matrix().is_zero()
467 Return the inverse of this operator, if it exists.
469 The reason this method is not simply an alias for the built-in
470 :meth:`__invert__` is that the built-in inversion is a bit magic
471 since it's intended to be a unary operator. If we alias ``inverse``
472 to ``__invert__``, then we wind up having to call e.g. ``A.inverse``
477 sage: from mjo.eja.eja_algebra import RealSymmetricEJA, random_eja
481 sage: J = RealSymmetricEJA(2)
482 sage: x = sum(J.gens())
483 sage: x.operator().inverse().matrix()
487 sage: x.operator().matrix().inverse()
494 The identity operator is its own inverse::
496 sage: set_random_seed()
497 sage: J = random_eja()
498 sage: idJ = J.one().operator()
499 sage: idJ.inverse() == idJ
502 The inverse of the inverse is the operator we started with::
504 sage: set_random_seed()
505 sage: x = random_eja().random_element()
506 sage: L = x.operator()
507 sage: not L.is_invertible() or (L.inverse().inverse() == L)
514 def is_invertible(self
):
516 Return whether or not this operator is invertible.
520 sage: from mjo.eja.eja_algebra import (RealSymmetricEJA,
526 sage: J = RealSymmetricEJA(2)
527 sage: x = sum(J.gens())
528 sage: x.operator().matrix()
532 sage: x.operator().matrix().is_invertible()
534 sage: x.operator().is_invertible()
537 The zero operator is invertible in a trivial algebra::
539 sage: J = TrivialEJA()
540 sage: J.zero().operator().is_invertible()
545 The identity operator is always invertible::
547 sage: set_random_seed()
548 sage: J = random_eja()
549 sage: J.one().operator().is_invertible()
552 The zero operator is never invertible in a nontrivial algebra::
554 sage: set_random_seed()
555 sage: J = random_eja()
556 sage: not J.is_trivial() and J.zero().operator().is_invertible()
560 return self
.matrix().is_invertible()
565 Return the matrix representation of this operator with respect
566 to the default bases of its (co)domain.
570 sage: from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator
571 sage: from mjo.eja.eja_algebra import RealSymmetricEJA
575 sage: J = RealSymmetricEJA(2)
576 sage: mat = matrix(J.base_ring(), J.dimension(), range(9))
577 sage: f = FiniteDimensionalEuclideanJordanAlgebraOperator(J,J,mat)
587 def minimal_polynomial(self
):
589 Return the minimal polynomial of this linear operator,
590 in the variable ``t``.
594 sage: from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator
595 sage: from mjo.eja.eja_algebra import RealSymmetricEJA
599 sage: J = RealSymmetricEJA(3)
600 sage: J.one().operator().minimal_polynomial()
604 # The matrix method returns a polynomial in 'x' but want one in 't'.
605 return self
.matrix().minimal_polynomial().change_variable_name('t')
608 def spectral_decomposition(self
):
610 Return the spectral decomposition of this operator as a list of
611 (eigenvalue, orthogonal projector) pairs.
613 This is the unique spectral decomposition, up to the order of
614 the projection operators, with distinct eigenvalues. So, the
615 projections are generally onto subspaces of dimension greater
620 sage: from mjo.eja.eja_algebra import RealSymmetricEJA
624 sage: J = RealSymmetricEJA(4)
625 sage: x = sum(J.gens())
626 sage: A = x.subalgebra_generated_by(orthonormalize_basis=True)
627 sage: L0x = A(x).operator()
628 sage: sd = L0x.spectral_decomposition()
633 sage: P0*l0 + P1*l1 == L0x
635 sage: P0 + P1 == P0^0 # the identity
641 sage: P0*P1 == A.zero().operator()
643 sage: P1*P0 == A.zero().operator()
647 if not self
.matrix().is_symmetric():
648 raise ValueError('algebra basis is not orthonormal')
650 D
,P
= self
.matrix().jordan_form(subdivide
=False,transformation
=True)
651 eigenvalues
= D
.diagonal()
654 for i
in range(len(us
)):
655 # they won't be normalized, but they have to be
656 # for the spectral theorem to work.
657 us
[i
] = us
[i
]/us
[i
].norm()
658 mat
= us
[i
].column()*us
[i
].row()
659 Pi
= FiniteDimensionalEuclideanJordanAlgebraOperator(
663 projectors
.append(Pi
)
664 return list(zip(eigenvalues
, projectors
))