]>
gitweb.michael.orlitzky.com - sage.d.git/blob - 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: J = random_eja()
474 sage: J.zero().operator().is_zero()
478 return self
.matrix().is_zero()
483 Return the inverse of this operator, if it exists.
485 The reason this method is not simply an alias for the built-in
486 :meth:`__invert__` is that the built-in inversion is a bit magic
487 since it's intended to be a unary operator. If we alias ``inverse``
488 to ``__invert__``, then we wind up having to call e.g. ``A.inverse``
493 sage: from mjo.eja.eja_algebra import RealSymmetricEJA, random_eja
497 sage: J = RealSymmetricEJA(2)
498 sage: x = sum(J.gens())
499 sage: x.operator().inverse().matrix()
503 sage: x.operator().matrix().inverse()
510 The identity operator is its own inverse::
512 sage: J = random_eja()
513 sage: idJ = J.one().operator()
514 sage: idJ.inverse() == idJ
517 The inverse of the inverse is the operator we started with::
519 sage: x = random_eja().random_element()
520 sage: L = x.operator()
521 sage: not L.is_invertible() or (L.inverse().inverse() == L)
528 def is_invertible(self
):
530 Return whether or not this operator is invertible.
534 sage: from mjo.eja.eja_algebra import (RealSymmetricEJA,
540 sage: J = RealSymmetricEJA(2)
541 sage: x = sum(J.gens())
542 sage: x.operator().matrix()
546 sage: x.operator().matrix().is_invertible()
548 sage: x.operator().is_invertible()
551 The zero operator is invertible in a trivial algebra::
553 sage: J = TrivialEJA()
554 sage: J.zero().operator().is_invertible()
559 The identity operator is always invertible::
561 sage: J = random_eja()
562 sage: J.one().operator().is_invertible()
565 The zero operator is never invertible in a nontrivial algebra::
567 sage: J = random_eja()
568 sage: not J.is_trivial() and J.zero().operator().is_invertible()
572 return self
.matrix().is_invertible()
577 Return the matrix representation of this operator with respect
578 to the default bases of its (co)domain.
582 sage: from mjo.eja.eja_operator import FiniteDimensionalEJAOperator
583 sage: from mjo.eja.eja_algebra import RealSymmetricEJA
587 sage: J = RealSymmetricEJA(2)
588 sage: mat = matrix(J.base_ring(), J.dimension(), range(9))
589 sage: f = FiniteDimensionalEJAOperator(J,J,mat)
599 def minimal_polynomial(self
):
601 Return the minimal polynomial of this linear operator,
602 in the variable ``t``.
606 sage: from mjo.eja.eja_operator import FiniteDimensionalEJAOperator
607 sage: from mjo.eja.eja_algebra import RealSymmetricEJA
611 sage: J = RealSymmetricEJA(3)
612 sage: J.one().operator().minimal_polynomial()
616 # The matrix method returns a polynomial in 'x' but want one in 't'.
617 return self
.matrix().minimal_polynomial().change_variable_name('t')
620 def spectral_decomposition(self
):
622 Return the spectral decomposition of this operator as a list of
623 (eigenvalue, orthogonal projector) pairs.
625 This is the unique spectral decomposition, up to the order of
626 the projection operators, with distinct eigenvalues. So, the
627 projections are generally onto subspaces of dimension greater
632 sage: from mjo.eja.eja_algebra import RealSymmetricEJA
636 sage: J = RealSymmetricEJA(4)
637 sage: x = sum(J.gens())
638 sage: A = x.subalgebra_generated_by()
639 sage: L0x = A(x).operator()
640 sage: sd = L0x.spectral_decomposition()
645 sage: P0*l0 + P1*l1 == L0x
647 sage: P0 + P1 == P0^0 # the identity
653 sage: P0*P1 == A.zero().operator()
655 sage: P1*P0 == A.zero().operator()
659 if not self
.matrix().is_symmetric():
660 raise ValueError('algebra basis is not orthonormal')
662 D
,P
= self
.matrix().jordan_form(subdivide
=False,transformation
=True)
663 eigenvalues
= D
.diagonal()
666 for i
in range(len(us
)):
667 # they won't be normalized, but they have to be
668 # for the spectral theorem to work.
669 us
[i
] = us
[i
]/us
[i
].norm()
670 mat
= us
[i
].column()*us
[i
].row()
671 Pi
= FiniteDimensionalEJAOperator(
675 projectors
.append(Pi
)
676 return list(zip(eigenvalues
, projectors
))