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
):
6 def __init__(self
, domain_eja
, codomain_eja
, mat
):
8 # isinstance(domain_eja, FiniteDimensionalEuclideanJordanAlgebra) and
9 # isinstance(codomain_eja, FiniteDimensionalEuclideanJordanAlgebra) ):
10 # raise ValueError('(co)domains must be finite-dimensional Euclidean '
13 F
= domain_eja
.base_ring()
14 if not (F
== codomain_eja
.base_ring()):
15 raise ValueError("domain and codomain must have the same base ring")
16 if not (F
== mat
.base_ring()):
17 raise ValueError("domain and matrix must have the same base ring")
19 # We need to supply something here to avoid getting the
20 # default Homset of the parent FiniteDimensionalAlgebra class,
21 # which messes up e.g. equality testing. We use FreeModules(F)
22 # instead of VectorSpaces(F) because our characteristic polynomial
23 # algorithm will need to F to be a polynomial ring at some point.
24 # When F is a field, FreeModules(F) returns VectorSpaces(F) anyway.
25 parent
= domain_eja
.Hom(codomain_eja
, FreeModules(F
))
27 # The Map initializer will set our parent to a homset, which
28 # is explicitly NOT what we want, because these ain't algebra
30 super(FiniteDimensionalEuclideanJordanAlgebraOperator
,self
).__init
__(parent
)
32 # Keep a matrix around to do all of the real work. It would
33 # be nice if we could use a VectorSpaceMorphism instead, but
34 # those use row vectors that we don't want to accidentally
35 # expose to our users.
41 Allow this operator to be called only on elements of an EJA.
45 sage: from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator
46 sage: from mjo.eja.eja_algebra import JordanSpinEJA
50 sage: J = JordanSpinEJA(3)
51 sage: x = J.linear_combination(zip(J.gens(),range(len(J.gens()))))
52 sage: id = identity_matrix(J.base_ring(), J.dimension())
53 sage: f = FiniteDimensionalEuclideanJordanAlgebraOperator(J,J,id)
58 return self
.codomain().from_vector(self
.matrix()*x
.to_vector())
61 def _add_(self
, other
):
63 Add the ``other`` EJA operator to this one.
67 sage: from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator
68 sage: from mjo.eja.eja_algebra import (
70 ....: RealSymmetricEJA )
74 When we add two EJA operators, we get another one back::
76 sage: J = RealSymmetricEJA(2)
77 sage: id = identity_matrix(J.base_ring(), J.dimension())
78 sage: f = FiniteDimensionalEuclideanJordanAlgebraOperator(J,J,id)
79 sage: g = FiniteDimensionalEuclideanJordanAlgebraOperator(J,J,id)
81 Linear operator between finite-dimensional Euclidean Jordan
82 algebras represented by the matrix:
86 Domain: Euclidean Jordan algebra of dimension 3 over...
87 Codomain: Euclidean Jordan algebra of dimension 3 over...
89 If you try to add two identical vector space operators but on
90 different EJAs, that should blow up::
92 sage: J1 = RealSymmetricEJA(2)
93 sage: id1 = identity_matrix(J1.base_ring(), 3)
94 sage: J2 = JordanSpinEJA(3)
95 sage: id2 = identity_matrix(J2.base_ring(), 3)
96 sage: f = FiniteDimensionalEuclideanJordanAlgebraOperator(J1,J1,id1)
97 sage: g = FiniteDimensionalEuclideanJordanAlgebraOperator(J2,J2,id2)
99 Traceback (most recent call last):
101 TypeError: unsupported operand parent(s) for +: ...
104 return FiniteDimensionalEuclideanJordanAlgebraOperator(
107 self
.matrix() + other
.matrix())
110 def _composition_(self
, other
, homset
):
112 Compose two EJA operators to get another one (and NOT a formal
113 composite object) back.
117 sage: from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator
118 sage: from mjo.eja.eja_algebra import (
121 ....: RealSymmetricEJA)
125 sage: J1 = JordanSpinEJA(3)
126 sage: J2 = HadamardEJA(2)
127 sage: J3 = RealSymmetricEJA(1)
128 sage: mat1 = matrix(QQ, [[1,2,3],
130 sage: mat2 = matrix(QQ, [[7,8]])
131 sage: g = FiniteDimensionalEuclideanJordanAlgebraOperator(J1,
134 sage: f = FiniteDimensionalEuclideanJordanAlgebraOperator(J2,
138 Linear operator between finite-dimensional Euclidean Jordan
139 algebras represented by the matrix:
141 Domain: Euclidean Jordan algebra of dimension 3 over
143 Codomain: Euclidean Jordan algebra of dimension 1 over
147 return FiniteDimensionalEuclideanJordanAlgebraOperator(
150 self
.matrix()*other
.matrix())
153 def __eq__(self
, other
):
154 if self
.domain() != other
.domain():
156 if self
.codomain() != other
.codomain():
158 if self
.matrix() != other
.matrix():
163 def __invert__(self
):
165 Invert this EJA operator.
169 sage: from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator
170 sage: from mjo.eja.eja_algebra import RealSymmetricEJA
174 sage: J = RealSymmetricEJA(2)
175 sage: id = identity_matrix(J.base_ring(), J.dimension())
176 sage: f = FiniteDimensionalEuclideanJordanAlgebraOperator(J,J,id)
178 Linear operator between finite-dimensional Euclidean Jordan
179 algebras represented by the matrix:
183 Domain: Euclidean Jordan algebra of dimension 3 over...
184 Codomain: Euclidean Jordan algebra of dimension 3 over...
187 return FiniteDimensionalEuclideanJordanAlgebraOperator(
193 def __mul__(self
, other
):
195 Compose two EJA operators, or scale myself by an element of the
196 ambient vector space.
198 We need to override the real ``__mul__`` function to prevent the
199 coercion framework from throwing an error when it fails to convert
200 a base ring element into a morphism.
204 sage: from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator
205 sage: from mjo.eja.eja_algebra import RealSymmetricEJA
209 We can scale an operator on a rational algebra by a rational number::
211 sage: J = RealSymmetricEJA(2)
212 sage: e0,e1,e2 = J.gens()
213 sage: x = 2*e0 + 4*e1 + 16*e2
215 Linear operator between finite-dimensional Euclidean Jordan algebras
216 represented by the matrix:
220 Domain: Euclidean Jordan algebra of dimension 3 over...
221 Codomain: Euclidean Jordan algebra of dimension 3 over...
222 sage: x.operator()*(1/2)
223 Linear operator between finite-dimensional Euclidean Jordan algebras
224 represented by the matrix:
228 Domain: Euclidean Jordan algebra of dimension 3 over...
229 Codomain: Euclidean Jordan algebra of dimension 3 over...
233 if other
in self
.codomain().base_ring():
234 return FiniteDimensionalEuclideanJordanAlgebraOperator(
238 except NotImplementedError:
239 # This can happen with certain arguments if the base_ring()
240 # is weird and doesn't know how to test membership.
243 # This should eventually delegate to _composition_ after performing
244 # some sanity checks for us.
245 mor
= super(FiniteDimensionalEuclideanJordanAlgebraOperator
,self
)
246 return mor
.__mul
__(other
)
251 Negate this EJA operator.
255 sage: from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator
256 sage: from mjo.eja.eja_algebra import RealSymmetricEJA
260 sage: J = RealSymmetricEJA(2)
261 sage: id = identity_matrix(J.base_ring(), J.dimension())
262 sage: f = FiniteDimensionalEuclideanJordanAlgebraOperator(J,J,id)
264 Linear operator between finite-dimensional Euclidean Jordan
265 algebras represented by the matrix:
269 Domain: Euclidean Jordan algebra of dimension 3 over...
270 Codomain: Euclidean Jordan algebra of dimension 3 over...
273 return FiniteDimensionalEuclideanJordanAlgebraOperator(
279 def __pow__(self
, n
):
281 Raise this EJA operator to the power ``n``.
285 sage: from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator
286 sage: from mjo.eja.eja_algebra import RealSymmetricEJA
290 Ensure that we get back another EJA operator that can be added,
291 subtracted, et cetera::
293 sage: J = RealSymmetricEJA(2)
294 sage: id = identity_matrix(J.base_ring(), J.dimension())
295 sage: f = FiniteDimensionalEuclideanJordanAlgebraOperator(J,J,id)
296 sage: f^0 + f^1 + f^2
297 Linear operator between finite-dimensional Euclidean Jordan
298 algebras represented by the matrix:
302 Domain: Euclidean Jordan algebra of dimension 3 over...
303 Codomain: Euclidean Jordan algebra of dimension 3 over...
309 # Raising a vector space morphism to the zero power gives
310 # you back a special IdentityMorphism that is useless to us.
311 rows
= self
.codomain().dimension()
312 cols
= self
.domain().dimension()
313 mat
= matrix
.identity(self
.base_ring(), rows
, cols
)
315 mat
= self
.matrix()**n
317 return FiniteDimensionalEuclideanJordanAlgebraOperator(
326 A text representation of this linear operator on a Euclidean
331 sage: from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator
332 sage: from mjo.eja.eja_algebra import JordanSpinEJA
336 sage: J = JordanSpinEJA(2)
337 sage: id = identity_matrix(J.base_ring(), J.dimension())
338 sage: FiniteDimensionalEuclideanJordanAlgebraOperator(J,J,id)
339 Linear operator between finite-dimensional Euclidean Jordan
340 algebras represented by the matrix:
343 Domain: Euclidean Jordan algebra of dimension 2 over
345 Codomain: Euclidean Jordan algebra of dimension 2 over
349 msg
= ("Linear operator between finite-dimensional Euclidean Jordan "
350 "algebras represented by the matrix:\n",
354 return ''.join(msg
).format(self
.matrix(),
359 def _sub_(self
, other
):
361 Subtract ``other`` from this EJA operator.
365 sage: from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator
366 sage: from mjo.eja.eja_algebra import RealSymmetricEJA
370 sage: J = RealSymmetricEJA(2)
371 sage: id = identity_matrix(J.base_ring(),J.dimension())
372 sage: f = FiniteDimensionalEuclideanJordanAlgebraOperator(J,J,id)
374 Linear operator between finite-dimensional Euclidean Jordan
375 algebras represented by the matrix:
379 Domain: Euclidean Jordan algebra of dimension 3 over...
380 Codomain: Euclidean Jordan algebra of dimension 3 over...
383 return (self
+ (-other
))
388 Return the inverse of this operator, if it exists.
390 The reason this method is not simply an alias for the built-in
391 :meth:`__invert__` is that the built-in inversion is a bit magic
392 since it's intended to be a unary operator. If we alias ``inverse``
393 to ``__invert__``, then we wind up having to call e.g. ``A.inverse``
398 sage: from mjo.eja.eja_algebra import RealSymmetricEJA, random_eja
402 sage: J = RealSymmetricEJA(2)
403 sage: x = sum(J.gens())
404 sage: x.operator().inverse().matrix()
408 sage: x.operator().matrix().inverse()
415 The identity operator is its own inverse::
417 sage: set_random_seed()
418 sage: J = random_eja()
419 sage: idJ = J.one().operator()
420 sage: idJ.inverse() == idJ
423 The inverse of the inverse is the operator we started with::
425 sage: set_random_seed()
426 sage: x = random_eja().random_element()
427 sage: L = x.operator()
428 sage: not L.is_invertible() or (L.inverse().inverse() == L)
435 def is_invertible(self
):
437 Return whether or not this operator is invertible.
441 sage: from mjo.eja.eja_algebra import (RealSymmetricEJA,
447 sage: J = RealSymmetricEJA(2)
448 sage: x = sum(J.gens())
449 sage: x.operator().matrix()
453 sage: x.operator().matrix().is_invertible()
455 sage: x.operator().is_invertible()
458 The zero operator is invertible in a trivial algebra::
460 sage: J = TrivialEJA()
461 sage: J.zero().operator().is_invertible()
466 The identity operator is always invertible::
468 sage: set_random_seed()
469 sage: J = random_eja()
470 sage: J.one().operator().is_invertible()
473 The zero operator is never invertible in a nontrivial algebra::
475 sage: set_random_seed()
476 sage: J = random_eja()
477 sage: not J.is_trivial() and J.zero().operator().is_invertible()
481 return self
.matrix().is_invertible()
486 Return the matrix representation of this operator with respect
487 to the default bases of its (co)domain.
491 sage: from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator
492 sage: from mjo.eja.eja_algebra import RealSymmetricEJA
496 sage: J = RealSymmetricEJA(2)
497 sage: mat = matrix(J.base_ring(), J.dimension(), range(9))
498 sage: f = FiniteDimensionalEuclideanJordanAlgebraOperator(J,J,mat)
508 def minimal_polynomial(self
):
510 Return the minimal polynomial of this linear operator,
511 in the variable ``t``.
515 sage: from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator
516 sage: from mjo.eja.eja_algebra import RealSymmetricEJA
520 sage: J = RealSymmetricEJA(3)
521 sage: J.one().operator().minimal_polynomial()
525 # The matrix method returns a polynomial in 'x' but want one in 't'.
526 return self
.matrix().minimal_polynomial().change_variable_name('t')
529 def spectral_decomposition(self
):
531 Return the spectral decomposition of this operator as a list of
532 (eigenvalue, orthogonal projector) pairs.
534 This is the unique spectral decomposition, up to the order of
535 the projection operators, with distinct eigenvalues. So, the
536 projections are generally onto subspaces of dimension greater
541 sage: from mjo.eja.eja_algebra import RealSymmetricEJA
545 sage: J = RealSymmetricEJA(4,AA)
546 sage: x = sum(J.gens())
547 sage: A = x.subalgebra_generated_by(orthonormalize_basis=True)
548 sage: L0x = A(x).operator()
549 sage: sd = L0x.spectral_decomposition()
554 sage: P0*l0 + P1*l1 == L0x
556 sage: P0 + P1 == P0^0 # the identity
562 sage: P0*P1 == A.zero().operator()
564 sage: P1*P0 == A.zero().operator()
568 if not self
.matrix().is_symmetric():
569 raise ValueError('algebra basis is not orthonormal')
571 D
,P
= self
.matrix().jordan_form(subdivide
=False,transformation
=True)
572 eigenvalues
= D
.diagonal()
575 for i
in range(len(us
)):
576 # they won't be normalized, but they have to be
577 # for the spectral theorem to work.
578 us
[i
] = us
[i
]/us
[i
].norm()
579 mat
= us
[i
].column()*us
[i
].row()
580 Pi
= FiniteDimensionalEuclideanJordanAlgebraOperator(
584 projectors
.append(Pi
)
585 return list(zip(eigenvalues
, projectors
))