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 (
120 ....: RealCartesianProductEJA,
121 ....: RealSymmetricEJA)
125 sage: J1 = JordanSpinEJA(3)
126 sage: J2 = RealCartesianProductEJA(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 zero operator is never invertible::
425 sage: set_random_seed()
426 sage: J = random_eja()
427 sage: J.zero().operator().inverse()
428 Traceback (most recent call last):
430 ZeroDivisionError: input matrix must be nonsingular
436 def is_invertible(self
):
438 Return whether or not this operator is invertible.
442 sage: from mjo.eja.eja_algebra import RealSymmetricEJA, random_eja
446 sage: J = RealSymmetricEJA(2)
447 sage: x = sum(J.gens())
448 sage: x.operator().matrix()
452 sage: x.operator().matrix().is_invertible()
454 sage: x.operator().is_invertible()
459 The identity operator is always invertible::
461 sage: set_random_seed()
462 sage: J = random_eja()
463 sage: J.one().operator().is_invertible()
466 The zero operator is never invertible::
468 sage: set_random_seed()
469 sage: J = random_eja()
470 sage: J.zero().operator().is_invertible()
474 return self
.matrix().is_invertible()
479 Return the matrix representation of this operator with respect
480 to the default bases of its (co)domain.
484 sage: from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator
485 sage: from mjo.eja.eja_algebra import RealSymmetricEJA
489 sage: J = RealSymmetricEJA(2)
490 sage: mat = matrix(J.base_ring(), J.dimension(), range(9))
491 sage: f = FiniteDimensionalEuclideanJordanAlgebraOperator(J,J,mat)
501 def minimal_polynomial(self
):
503 Return the minimal polynomial of this linear operator,
504 in the variable ``t``.
508 sage: from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator
509 sage: from mjo.eja.eja_algebra import RealSymmetricEJA
513 sage: J = RealSymmetricEJA(3)
514 sage: J.one().operator().minimal_polynomial()
518 # The matrix method returns a polynomial in 'x' but want one in 't'.
519 return self
.matrix().minimal_polynomial().change_variable_name('t')
522 def spectral_decomposition(self
):
524 Return the spectral decomposition of this operator as a list of
525 (eigenvalue, orthogonal projector) pairs.
527 This is the unique spectral decomposition, up to the order of
528 the projection operators, with distinct eigenvalues. So, the
529 projections are generally onto subspaces of dimension greater
534 sage: from mjo.eja.eja_algebra import RealSymmetricEJA
538 sage: J = RealSymmetricEJA(4,AA)
539 sage: x = sum(J.gens())
540 sage: A = x.subalgebra_generated_by(orthonormalize_basis=True)
541 sage: L0x = A(x).operator()
542 sage: sd = L0x.spectral_decomposition()
547 sage: P0*l0 + P1*l1 == L0x
549 sage: P0 + P1 == P0^0 # the identity
555 sage: P0*P1 == A.zero().operator()
557 sage: P1*P0 == A.zero().operator()
561 if not self
.matrix().is_symmetric():
562 raise ValueError('algebra basis is not orthonormal')
564 D
,P
= self
.matrix().jordan_form(subdivide
=False,transformation
=True)
565 eigenvalues
= D
.diagonal()
568 for i
in range(len(us
)):
569 # they won't be normalized, but they have to be
570 # for the spectral theorem to work.
571 us
[i
] = us
[i
]/us
[i
].norm()
572 mat
= us
[i
].column()*us
[i
].row()
573 Pi
= FiniteDimensionalEuclideanJordanAlgebraOperator(
577 projectors
.append(Pi
)
578 return zip(eigenvalues
, projectors
)