1 from sage
.matrix
.constructor
import matrix
2 from sage
.categories
.all
import FreeModules
3 from sage
.categories
.map import Map
5 class FiniteDimensionalEJAOperator(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 FiniteDimensionalEJAOperator
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: FiniteDimensionalEJAOperator(V,J,M)
23 Traceback (most recent call last):
25 TypeError: domain must be a finite-dimensional Euclidean
27 sage: FiniteDimensionalEJAOperator(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 FiniteDimensionalEJA
38 # I guess we should check this, because otherwise you could
39 # pass in pretty much anything algebraish.
40 if not isinstance(domain_eja
, FiniteDimensionalEJA
):
41 raise TypeError('domain must be a finite-dimensional '
42 'Euclidean Jordan algebra')
43 if not isinstance(codomain_eja
, FiniteDimensionalEJA
):
44 raise TypeError('codomain must be a finite-dimensional '
45 'Euclidean Jordan algebra')
47 F
= domain_eja
.base_ring()
48 if not (F
== codomain_eja
.base_ring()):
49 raise ValueError("domain and codomain must have the same base ring")
50 if not (F
== mat
.base_ring()):
51 raise ValueError("domain and matrix must have the same base ring")
53 # We need to supply something here to avoid getting the
54 # default Homset of the parent FiniteDimensionalAlgebra class,
55 # which messes up e.g. equality testing. We use FreeModules(F)
56 # instead of VectorSpaces(F) because our characteristic polynomial
57 # algorithm will need to F to be a polynomial ring at some point.
58 # When F is a field, FreeModules(F) returns VectorSpaces(F) anyway.
59 parent
= domain_eja
.Hom(codomain_eja
, FreeModules(F
))
61 # The Map initializer will set our parent to a homset, which
62 # is explicitly NOT what we want, because these ain't algebra
64 super().__init
__(parent
)
66 # Keep a matrix around to do all of the real work. It would
67 # be nice if we could use a VectorSpaceMorphism instead, but
68 # those use row vectors that we don't want to accidentally
69 # expose to our users.
75 Allow this operator to be called only on elements of an EJA.
79 sage: from mjo.eja.eja_operator import FiniteDimensionalEJAOperator
80 sage: from mjo.eja.eja_algebra import JordanSpinEJA
84 sage: J = JordanSpinEJA(3)
85 sage: x = J.linear_combination(zip(J.gens(),range(len(J.gens()))))
86 sage: id = identity_matrix(J.base_ring(), J.dimension())
87 sage: f = FiniteDimensionalEJAOperator(J,J,id)
92 return self
.codomain().from_vector(self
.matrix()*x
.to_vector())
95 def _add_(self
, other
):
97 Add the ``other`` EJA operator to this one.
101 sage: from mjo.eja.eja_operator import FiniteDimensionalEJAOperator
102 sage: from mjo.eja.eja_algebra import (
104 ....: RealSymmetricEJA )
108 When we add two EJA operators, we get another one back::
110 sage: J = RealSymmetricEJA(2)
111 sage: id = identity_matrix(J.base_ring(), J.dimension())
112 sage: f = FiniteDimensionalEJAOperator(J,J,id)
113 sage: g = FiniteDimensionalEJAOperator(J,J,id)
115 Linear operator between finite-dimensional Euclidean Jordan
116 algebras represented by the matrix:
120 Domain: Euclidean Jordan algebra of dimension 3 over...
121 Codomain: Euclidean Jordan algebra of dimension 3 over...
123 If you try to add two identical vector space operators but on
124 different EJAs, that should blow up::
126 sage: J1 = RealSymmetricEJA(2)
127 sage: id1 = identity_matrix(J1.base_ring(), 3)
128 sage: J2 = JordanSpinEJA(3)
129 sage: id2 = identity_matrix(J2.base_ring(), 3)
130 sage: f = FiniteDimensionalEJAOperator(J1,J1,id1)
131 sage: g = FiniteDimensionalEJAOperator(J2,J2,id2)
133 Traceback (most recent call last):
135 TypeError: unsupported operand parent(s) for +: ...
138 return FiniteDimensionalEJAOperator(
141 self
.matrix() + other
.matrix())
144 def _composition_(self
, other
, homset
):
146 Compose two EJA operators to get another one (and NOT a formal
147 composite object) back.
151 sage: from mjo.eja.eja_operator import FiniteDimensionalEJAOperator
152 sage: from mjo.eja.eja_algebra import (
155 ....: RealSymmetricEJA)
159 sage: J1 = JordanSpinEJA(3)
160 sage: J2 = HadamardEJA(2)
161 sage: J3 = RealSymmetricEJA(1)
162 sage: mat1 = matrix(AA, [[1,2,3],
164 sage: mat2 = matrix(AA, [[7,8]])
165 sage: g = FiniteDimensionalEJAOperator(J1, J2, mat1)
166 sage: f = FiniteDimensionalEJAOperator(J2, J3, mat2)
168 Linear operator between finite-dimensional Euclidean Jordan
169 algebras represented by the matrix:
171 Domain: Euclidean Jordan algebra of dimension 3 over
173 Codomain: Euclidean Jordan algebra of dimension 1 over
177 return FiniteDimensionalEJAOperator(
180 self
.matrix()*other
.matrix())
183 def __eq__(self
, other
):
184 if self
.domain() != other
.domain():
186 if self
.codomain() != other
.codomain():
188 if self
.matrix() != other
.matrix():
193 def __invert__(self
):
195 Invert this EJA operator.
199 sage: from mjo.eja.eja_operator import FiniteDimensionalEJAOperator
200 sage: from mjo.eja.eja_algebra import RealSymmetricEJA
204 sage: J = RealSymmetricEJA(2)
205 sage: id = identity_matrix(J.base_ring(), J.dimension())
206 sage: f = FiniteDimensionalEJAOperator(J,J,id)
208 Linear operator between finite-dimensional Euclidean Jordan
209 algebras represented by the matrix:
213 Domain: Euclidean Jordan algebra of dimension 3 over...
214 Codomain: Euclidean Jordan algebra of dimension 3 over...
217 return FiniteDimensionalEJAOperator(
223 def __mul__(self
, other
):
225 Compose two EJA operators, or scale myself by an element of the
226 ambient vector space.
228 We need to override the real ``__mul__`` function to prevent the
229 coercion framework from throwing an error when it fails to convert
230 a base ring element into a morphism.
234 sage: from mjo.eja.eja_operator import FiniteDimensionalEJAOperator
235 sage: from mjo.eja.eja_algebra import RealSymmetricEJA
239 We can scale an operator on a rational algebra by a rational number::
241 sage: J = RealSymmetricEJA(2)
242 sage: e0,e1,e2 = J.gens()
243 sage: x = 2*e0 + 4*e1 + 16*e2
245 Linear operator between finite-dimensional Euclidean Jordan algebras
246 represented by the matrix:
250 Domain: Euclidean Jordan algebra of dimension 3 over...
251 Codomain: Euclidean Jordan algebra of dimension 3 over...
252 sage: x.operator()*(1/2)
253 Linear operator between finite-dimensional Euclidean Jordan algebras
254 represented by the matrix:
258 Domain: Euclidean Jordan algebra of dimension 3 over...
259 Codomain: Euclidean Jordan algebra of dimension 3 over...
263 if other
in self
.codomain().base_ring():
264 return FiniteDimensionalEJAOperator(
268 except NotImplementedError:
269 # This can happen with certain arguments if the base_ring()
270 # is weird and doesn't know how to test membership.
273 # This should eventually delegate to _composition_ after performing
274 # some sanity checks for us.
275 mor
= super(FiniteDimensionalEJAOperator
,self
)
276 return mor
.__mul
__(other
)
281 Negate this EJA operator.
285 sage: from mjo.eja.eja_operator import FiniteDimensionalEJAOperator
286 sage: from mjo.eja.eja_algebra import RealSymmetricEJA
290 sage: J = RealSymmetricEJA(2)
291 sage: id = identity_matrix(J.base_ring(), J.dimension())
292 sage: f = FiniteDimensionalEJAOperator(J,J,id)
294 Linear operator between finite-dimensional Euclidean Jordan
295 algebras represented by the matrix:
299 Domain: Euclidean Jordan algebra of dimension 3 over...
300 Codomain: Euclidean Jordan algebra of dimension 3 over...
303 return FiniteDimensionalEJAOperator(
309 def __pow__(self
, n
):
311 Raise this EJA operator to the power ``n``.
315 sage: from mjo.eja.eja_operator import FiniteDimensionalEJAOperator
316 sage: from mjo.eja.eja_algebra import RealSymmetricEJA
320 Ensure that we get back another EJA operator that can be added,
321 subtracted, et cetera::
323 sage: J = RealSymmetricEJA(2)
324 sage: id = identity_matrix(J.base_ring(), J.dimension())
325 sage: f = FiniteDimensionalEJAOperator(J,J,id)
326 sage: f^0 + f^1 + f^2
327 Linear operator between finite-dimensional Euclidean Jordan
328 algebras represented by the matrix:
332 Domain: Euclidean Jordan algebra of dimension 3 over...
333 Codomain: Euclidean Jordan algebra of dimension 3 over...
339 # Raising a vector space morphism to the zero power gives
340 # you back a special IdentityMorphism that is useless to us.
341 rows
= self
.codomain().dimension()
342 cols
= self
.domain().dimension()
343 mat
= matrix
.identity(self
.base_ring(), rows
, cols
)
345 mat
= self
.matrix()**n
347 return FiniteDimensionalEJAOperator(
356 A text representation of this linear operator on a Euclidean
361 sage: from mjo.eja.eja_operator import FiniteDimensionalEJAOperator
362 sage: from mjo.eja.eja_algebra import JordanSpinEJA
366 sage: J = JordanSpinEJA(2)
367 sage: id = identity_matrix(J.base_ring(), J.dimension())
368 sage: FiniteDimensionalEJAOperator(J,J,id)
369 Linear operator between finite-dimensional Euclidean Jordan
370 algebras represented by the matrix:
373 Domain: Euclidean Jordan algebra of dimension 2 over
375 Codomain: Euclidean Jordan algebra of dimension 2 over
379 msg
= ("Linear operator between finite-dimensional Euclidean Jordan "
380 "algebras represented by the matrix:\n",
384 return ''.join(msg
).format(self
.matrix(),
389 def _sub_(self
, other
):
391 Subtract ``other`` from this EJA operator.
395 sage: from mjo.eja.eja_operator import FiniteDimensionalEJAOperator
396 sage: from mjo.eja.eja_algebra import RealSymmetricEJA
400 sage: J = RealSymmetricEJA(2)
401 sage: id = identity_matrix(J.base_ring(),J.dimension())
402 sage: f = FiniteDimensionalEJAOperator(J,J,id)
404 Linear operator between finite-dimensional Euclidean Jordan
405 algebras represented by the matrix:
409 Domain: Euclidean Jordan algebra of dimension 3 over...
410 Codomain: Euclidean Jordan algebra of dimension 3 over...
413 return (self
+ (-other
))
416 def is_self_adjoint(self
):
418 Return whether or not this operator is self-adjoint.
420 At least in Sage, the fact that the base field is real means
421 that the algebra elements have to be real as well (this is why
422 we embed the complex numbers and quaternions). As a result, the
423 matrix of this operator will contain only real entries, and it
424 suffices to check only symmetry, not conjugate symmetry.
428 sage: from mjo.eja.eja_algebra import (JordanSpinEJA)
432 sage: J = JordanSpinEJA(4)
433 sage: J.one().operator().is_self_adjoint()
437 return self
.matrix().is_symmetric()
442 Return whether or not this map is the zero operator.
446 sage: from mjo.eja.eja_operator import FiniteDimensionalEJAOperator
447 sage: from mjo.eja.eja_algebra import (random_eja,
449 ....: RealSymmetricEJA)
453 sage: J1 = JordanSpinEJA(2)
454 sage: J2 = RealSymmetricEJA(2)
455 sage: R = J1.base_ring()
456 sage: M = matrix(R, [ [0, 0],
459 sage: L = FiniteDimensionalEJAOperator(J1,J2,M)
462 sage: M = matrix(R, [ [0, 0],
465 sage: L = FiniteDimensionalEJAOperator(J1,J2,M)
471 The left-multiplication-by-zero operation on a given algebra
474 sage: set_random_seed()
475 sage: J = random_eja()
476 sage: J.zero().operator().is_zero()
480 return self
.matrix().is_zero()
485 Return the inverse of this operator, if it exists.
487 The reason this method is not simply an alias for the built-in
488 :meth:`__invert__` is that the built-in inversion is a bit magic
489 since it's intended to be a unary operator. If we alias ``inverse``
490 to ``__invert__``, then we wind up having to call e.g. ``A.inverse``
495 sage: from mjo.eja.eja_algebra import RealSymmetricEJA, random_eja
499 sage: J = RealSymmetricEJA(2)
500 sage: x = sum(J.gens())
501 sage: x.operator().inverse().matrix()
505 sage: x.operator().matrix().inverse()
512 The identity operator is its own inverse::
514 sage: set_random_seed()
515 sage: J = random_eja()
516 sage: idJ = J.one().operator()
517 sage: idJ.inverse() == idJ
520 The inverse of the inverse is the operator we started with::
522 sage: set_random_seed()
523 sage: x = random_eja().random_element()
524 sage: L = x.operator()
525 sage: not L.is_invertible() or (L.inverse().inverse() == L)
532 def is_invertible(self
):
534 Return whether or not this operator is invertible.
538 sage: from mjo.eja.eja_algebra import (RealSymmetricEJA,
544 sage: J = RealSymmetricEJA(2)
545 sage: x = sum(J.gens())
546 sage: x.operator().matrix()
550 sage: x.operator().matrix().is_invertible()
552 sage: x.operator().is_invertible()
555 The zero operator is invertible in a trivial algebra::
557 sage: J = TrivialEJA()
558 sage: J.zero().operator().is_invertible()
563 The identity operator is always invertible::
565 sage: set_random_seed()
566 sage: J = random_eja()
567 sage: J.one().operator().is_invertible()
570 The zero operator is never invertible in a nontrivial algebra::
572 sage: set_random_seed()
573 sage: J = random_eja()
574 sage: not J.is_trivial() and J.zero().operator().is_invertible()
578 return self
.matrix().is_invertible()
583 Return the matrix representation of this operator with respect
584 to the default bases of its (co)domain.
588 sage: from mjo.eja.eja_operator import FiniteDimensionalEJAOperator
589 sage: from mjo.eja.eja_algebra import RealSymmetricEJA
593 sage: J = RealSymmetricEJA(2)
594 sage: mat = matrix(J.base_ring(), J.dimension(), range(9))
595 sage: f = FiniteDimensionalEJAOperator(J,J,mat)
605 def minimal_polynomial(self
):
607 Return the minimal polynomial of this linear operator,
608 in the variable ``t``.
612 sage: from mjo.eja.eja_operator import FiniteDimensionalEJAOperator
613 sage: from mjo.eja.eja_algebra import RealSymmetricEJA
617 sage: J = RealSymmetricEJA(3)
618 sage: J.one().operator().minimal_polynomial()
622 # The matrix method returns a polynomial in 'x' but want one in 't'.
623 return self
.matrix().minimal_polynomial().change_variable_name('t')
626 def spectral_decomposition(self
):
628 Return the spectral decomposition of this operator as a list of
629 (eigenvalue, orthogonal projector) pairs.
631 This is the unique spectral decomposition, up to the order of
632 the projection operators, with distinct eigenvalues. So, the
633 projections are generally onto subspaces of dimension greater
638 sage: from mjo.eja.eja_algebra import RealSymmetricEJA
642 sage: J = RealSymmetricEJA(4)
643 sage: x = sum(J.gens())
644 sage: A = x.subalgebra_generated_by()
645 sage: L0x = A(x).operator()
646 sage: sd = L0x.spectral_decomposition()
651 sage: P0*l0 + P1*l1 == L0x
653 sage: P0 + P1 == P0^0 # the identity
659 sage: P0*P1 == A.zero().operator()
661 sage: P1*P0 == A.zero().operator()
665 if not self
.matrix().is_symmetric():
666 raise ValueError('algebra basis is not orthonormal')
668 D
,P
= self
.matrix().jordan_form(subdivide
=False,transformation
=True)
669 eigenvalues
= D
.diagonal()
672 for i
in range(len(us
)):
673 # they won't be normalized, but they have to be
674 # for the spectral theorem to work.
675 us
[i
] = us
[i
]/us
[i
].norm()
676 mat
= us
[i
].column()*us
[i
].row()
677 Pi
= FiniteDimensionalEJAOperator(
681 projectors
.append(Pi
)
682 return list(zip(eigenvalues
, projectors
))