]>
gitweb.michael.orlitzky.com - sage.d.git/blob - 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
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: b0,b1,b2 = J.gens()
243 sage: x = 2*b0 + 4*b1 + 16*b2
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 return super().__mul
__(other
)
280 Negate this EJA operator.
284 sage: from mjo.eja.eja_operator import FiniteDimensionalEJAOperator
285 sage: from mjo.eja.eja_algebra import RealSymmetricEJA
289 sage: J = RealSymmetricEJA(2)
290 sage: id = identity_matrix(J.base_ring(), J.dimension())
291 sage: f = FiniteDimensionalEJAOperator(J,J,id)
293 Linear operator between finite-dimensional Euclidean Jordan
294 algebras represented by the matrix:
298 Domain: Euclidean Jordan algebra of dimension 3 over...
299 Codomain: Euclidean Jordan algebra of dimension 3 over...
302 return FiniteDimensionalEJAOperator(
308 def __pow__(self
, n
):
310 Raise this EJA operator to the power ``n``.
314 sage: from mjo.eja.eja_operator import FiniteDimensionalEJAOperator
315 sage: from mjo.eja.eja_algebra import RealSymmetricEJA
319 Ensure that we get back another EJA operator that can be added,
320 subtracted, et cetera::
322 sage: J = RealSymmetricEJA(2)
323 sage: id = identity_matrix(J.base_ring(), J.dimension())
324 sage: f = FiniteDimensionalEJAOperator(J,J,id)
325 sage: f^0 + f^1 + f^2
326 Linear operator between finite-dimensional Euclidean Jordan
327 algebras represented by the matrix:
331 Domain: Euclidean Jordan algebra of dimension 3 over...
332 Codomain: Euclidean Jordan algebra of dimension 3 over...
338 # Raising a vector space morphism to the zero power gives
339 # you back a special IdentityMorphism that is useless to us.
340 rows
= self
.codomain().dimension()
341 cols
= self
.domain().dimension()
342 mat
= matrix
.identity(self
.base_ring(), rows
, cols
)
344 mat
= self
.matrix()**n
346 return FiniteDimensionalEJAOperator(
355 A text representation of this linear operator on a Euclidean
360 sage: from mjo.eja.eja_operator import FiniteDimensionalEJAOperator
361 sage: from mjo.eja.eja_algebra import JordanSpinEJA
365 sage: J = JordanSpinEJA(2)
366 sage: id = identity_matrix(J.base_ring(), J.dimension())
367 sage: FiniteDimensionalEJAOperator(J,J,id)
368 Linear operator between finite-dimensional Euclidean Jordan
369 algebras represented by the matrix:
372 Domain: Euclidean Jordan algebra of dimension 2 over
374 Codomain: Euclidean Jordan algebra of dimension 2 over
378 msg
= ("Linear operator between finite-dimensional Euclidean Jordan "
379 "algebras represented by the matrix:\n",
383 return ''.join(msg
).format(self
.matrix(),
388 def _sub_(self
, other
):
390 Subtract ``other`` from this EJA operator.
394 sage: from mjo.eja.eja_operator import FiniteDimensionalEJAOperator
395 sage: from mjo.eja.eja_algebra import RealSymmetricEJA
399 sage: J = RealSymmetricEJA(2)
400 sage: id = identity_matrix(J.base_ring(),J.dimension())
401 sage: f = FiniteDimensionalEJAOperator(J,J,id)
403 Linear operator between finite-dimensional Euclidean Jordan
404 algebras represented by the matrix:
408 Domain: Euclidean Jordan algebra of dimension 3 over...
409 Codomain: Euclidean Jordan algebra of dimension 3 over...
412 return (self
+ (-other
))
415 def is_self_adjoint(self
):
417 Return whether or not this operator is self-adjoint.
419 At least in Sage, the fact that the base field is real means
420 that the algebra elements have to be real as well (this is why
421 we embed the complex numbers and quaternions). As a result, the
422 matrix of this operator will contain only real entries, and it
423 suffices to check only symmetry, not conjugate symmetry.
427 sage: from mjo.eja.eja_algebra import (JordanSpinEJA)
431 sage: J = JordanSpinEJA(4)
432 sage: J.one().operator().is_self_adjoint()
436 return self
.matrix().is_symmetric()
441 Return whether or not this map is the zero operator.
445 sage: from mjo.eja.eja_operator import FiniteDimensionalEJAOperator
446 sage: from mjo.eja.eja_algebra import (random_eja,
448 ....: RealSymmetricEJA)
452 sage: J1 = JordanSpinEJA(2)
453 sage: J2 = RealSymmetricEJA(2)
454 sage: R = J1.base_ring()
455 sage: M = matrix(R, [ [0, 0],
458 sage: L = FiniteDimensionalEJAOperator(J1,J2,M)
461 sage: M = matrix(R, [ [0, 0],
464 sage: L = FiniteDimensionalEJAOperator(J1,J2,M)
470 The left-multiplication-by-zero operation on a given algebra
473 sage: set_random_seed()
474 sage: J = random_eja()
475 sage: J.zero().operator().is_zero()
479 return self
.matrix().is_zero()
484 Return the inverse of this operator, if it exists.
486 The reason this method is not simply an alias for the built-in
487 :meth:`__invert__` is that the built-in inversion is a bit magic
488 since it's intended to be a unary operator. If we alias ``inverse``
489 to ``__invert__``, then we wind up having to call e.g. ``A.inverse``
494 sage: from mjo.eja.eja_algebra import RealSymmetricEJA, random_eja
498 sage: J = RealSymmetricEJA(2)
499 sage: x = sum(J.gens())
500 sage: x.operator().inverse().matrix()
504 sage: x.operator().matrix().inverse()
511 The identity operator is its own inverse::
513 sage: set_random_seed()
514 sage: J = random_eja()
515 sage: idJ = J.one().operator()
516 sage: idJ.inverse() == idJ
519 The inverse of the inverse is the operator we started with::
521 sage: set_random_seed()
522 sage: x = random_eja().random_element()
523 sage: L = x.operator()
524 sage: not L.is_invertible() or (L.inverse().inverse() == L)
531 def is_invertible(self
):
533 Return whether or not this operator is invertible.
537 sage: from mjo.eja.eja_algebra import (RealSymmetricEJA,
543 sage: J = RealSymmetricEJA(2)
544 sage: x = sum(J.gens())
545 sage: x.operator().matrix()
549 sage: x.operator().matrix().is_invertible()
551 sage: x.operator().is_invertible()
554 The zero operator is invertible in a trivial algebra::
556 sage: J = TrivialEJA()
557 sage: J.zero().operator().is_invertible()
562 The identity operator is always invertible::
564 sage: set_random_seed()
565 sage: J = random_eja()
566 sage: J.one().operator().is_invertible()
569 The zero operator is never invertible in a nontrivial algebra::
571 sage: set_random_seed()
572 sage: J = random_eja()
573 sage: not J.is_trivial() and J.zero().operator().is_invertible()
577 return self
.matrix().is_invertible()
582 Return the matrix representation of this operator with respect
583 to the default bases of its (co)domain.
587 sage: from mjo.eja.eja_operator import FiniteDimensionalEJAOperator
588 sage: from mjo.eja.eja_algebra import RealSymmetricEJA
592 sage: J = RealSymmetricEJA(2)
593 sage: mat = matrix(J.base_ring(), J.dimension(), range(9))
594 sage: f = FiniteDimensionalEJAOperator(J,J,mat)
604 def minimal_polynomial(self
):
606 Return the minimal polynomial of this linear operator,
607 in the variable ``t``.
611 sage: from mjo.eja.eja_operator import FiniteDimensionalEJAOperator
612 sage: from mjo.eja.eja_algebra import RealSymmetricEJA
616 sage: J = RealSymmetricEJA(3)
617 sage: J.one().operator().minimal_polynomial()
621 # The matrix method returns a polynomial in 'x' but want one in 't'.
622 return self
.matrix().minimal_polynomial().change_variable_name('t')
625 def spectral_decomposition(self
):
627 Return the spectral decomposition of this operator as a list of
628 (eigenvalue, orthogonal projector) pairs.
630 This is the unique spectral decomposition, up to the order of
631 the projection operators, with distinct eigenvalues. So, the
632 projections are generally onto subspaces of dimension greater
637 sage: from mjo.eja.eja_algebra import RealSymmetricEJA
641 sage: J = RealSymmetricEJA(4)
642 sage: x = sum(J.gens())
643 sage: A = x.subalgebra_generated_by()
644 sage: L0x = A(x).operator()
645 sage: sd = L0x.spectral_decomposition()
650 sage: P0*l0 + P1*l1 == L0x
652 sage: P0 + P1 == P0^0 # the identity
658 sage: P0*P1 == A.zero().operator()
660 sage: P1*P0 == A.zero().operator()
664 if not self
.matrix().is_symmetric():
665 raise ValueError('algebra basis is not orthonormal')
667 D
,P
= self
.matrix().jordan_form(subdivide
=False,transformation
=True)
668 eigenvalues
= D
.diagonal()
671 for i
in range(len(us
)):
672 # they won't be normalized, but they have to be
673 # for the spectral theorem to work.
674 us
[i
] = us
[i
]/us
[i
].norm()
675 mat
= us
[i
].column()*us
[i
].row()
676 Pi
= FiniteDimensionalEJAOperator(
680 projectors
.append(Pi
)
681 return list(zip(eigenvalues
, projectors
))