From 9c86e23e2ef79fb9d7003f68952750dda0ca0e0b Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Sun, 28 Jul 2019 12:56:46 -0400 Subject: [PATCH] eja: separate out the operator class and rename both files. Let's start organizing. It's also time to get the test suite working. I've added stupid SETUP blocks to each docstring to load the stuff being tested. --- ...idean_jordan_algebra.py => eja_algebra.py} | 528 ++++++------------ mjo/eja/eja_operator.py | 416 ++++++++++++++ 2 files changed, 587 insertions(+), 357 deletions(-) rename mjo/eja/{euclidean_jordan_algebra.py => eja_algebra.py} (85%) create mode 100644 mjo/eja/eja_operator.py diff --git a/mjo/eja/euclidean_jordan_algebra.py b/mjo/eja/eja_algebra.py similarity index 85% rename from mjo/eja/euclidean_jordan_algebra.py rename to mjo/eja/eja_algebra.py index 70a7770..8ab6afa 100644 --- a/mjo/eja/euclidean_jordan_algebra.py +++ b/mjo/eja/eja_algebra.py @@ -5,367 +5,15 @@ are used in optimization, and have some additional nice methods beyond what can be supported in a general Jordan Algebra. """ -from sage.categories.finite_dimensional_algebras_with_basis import FiniteDimensionalAlgebrasWithBasis -from sage.categories.map import Map -from sage.structure.element import is_Matrix -from sage.structure.category_object import normalize_names +from sage.all import * from sage.algebras.finite_dimensional_algebras.finite_dimensional_algebra import FiniteDimensionalAlgebra from sage.algebras.finite_dimensional_algebras.finite_dimensional_algebra_element import FiniteDimensionalAlgebraElement +from sage.categories.finite_dimensional_algebras_with_basis import FiniteDimensionalAlgebrasWithBasis +from sage.structure.element import is_Matrix +from sage.structure.category_object import normalize_names - -class FiniteDimensionalEuclideanJordanAlgebraOperator(Map): - def __init__(self, domain_eja, codomain_eja, mat): - if not ( - isinstance(domain_eja, FiniteDimensionalEuclideanJordanAlgebra) and - isinstance(codomain_eja, FiniteDimensionalEuclideanJordanAlgebra) ): - raise ValueError('(co)domains must be finite-dimensional Euclidean ' - 'Jordan algebras') - - F = domain_eja.base_ring() - if not (F == codomain_eja.base_ring()): - raise ValueError("domain and codomain must have the same base ring") - - # We need to supply something here to avoid getting the - # default Homset of the parent FiniteDimensionalAlgebra class, - # which messes up e.g. equality testing. We use FreeModules(F) - # instead of VectorSpaces(F) because our characteristic polynomial - # algorithm will need to F to be a polynomial ring at some point. - # When F is a field, FreeModules(F) returns VectorSpaces(F) anyway. - parent = Hom(domain_eja, codomain_eja, FreeModules(F)) - - # The Map initializer will set our parent to a homset, which - # is explicitly NOT what we want, because these ain't algebra - # homomorphisms. - super(FiniteDimensionalEuclideanJordanAlgebraOperator,self).__init__(parent) - - # Keep a matrix around to do all of the real work. It would - # be nice if we could use a VectorSpaceMorphism instead, but - # those use row vectors that we don't want to accidentally - # expose to our users. - self._matrix = mat - - - def _call_(self, x): - """ - Allow this operator to be called only on elements of an EJA. - - EXAMPLES:: - - sage: J = JordanSpinEJA(3) - sage: x = J.linear_combination(zip(range(len(J.gens())), J.gens())) - sage: id = identity_matrix(J.base_ring(), J.dimension()) - sage: f = FiniteDimensionalEuclideanJordanAlgebraOperator(J,J,id) - sage: f(x) == x - True - - """ - return self.codomain()(self.matrix()*x.vector()) - - - def _add_(self, other): - """ - Add the ``other`` EJA operator to this one. - - EXAMPLES: - - When we add two EJA operators, we get another one back:: - - sage: J = RealSymmetricEJA(2) - sage: id = identity_matrix(J.base_ring(), J.dimension()) - sage: f = FiniteDimensionalEuclideanJordanAlgebraOperator(J,J,id) - sage: g = FiniteDimensionalEuclideanJordanAlgebraOperator(J,J,id) - sage: f + g - Linear operator between finite-dimensional Euclidean Jordan - algebras represented by the matrix: - [2 0 0] - [0 2 0] - [0 0 2] - Domain: Euclidean Jordan algebra of degree 3 over Rational Field - Codomain: Euclidean Jordan algebra of degree 3 over Rational Field - - If you try to add two identical vector space operators but on - different EJAs, that should blow up:: - - sage: J1 = RealSymmetricEJA(2) - sage: J2 = JordanSpinEJA(3) - sage: id = identity_matrix(QQ, 3) - sage: f = FiniteDimensionalEuclideanJordanAlgebraOperator(J1,J1,id) - sage: g = FiniteDimensionalEuclideanJordanAlgebraOperator(J2,J2,id) - sage: f + g - Traceback (most recent call last): - ... - TypeError: unsupported operand parent(s) for +: ... - - """ - return FiniteDimensionalEuclideanJordanAlgebraOperator( - self.domain(), - self.codomain(), - self.matrix() + other.matrix()) - - - def _composition_(self, other, homset): - """ - Compose two EJA operators to get another one (and NOT a formal - composite object) back. - - EXAMPLES:: - - sage: J1 = JordanSpinEJA(3) - sage: J2 = RealCartesianProductEJA(2) - sage: J3 = RealSymmetricEJA(1) - sage: mat1 = matrix(QQ, [[1,2,3], - ....: [4,5,6]]) - sage: mat2 = matrix(QQ, [[7,8]]) - sage: g = FiniteDimensionalEuclideanJordanAlgebraOperator(J1, - ....: J2, - ....: mat1) - sage: f = FiniteDimensionalEuclideanJordanAlgebraOperator(J2, - ....: J3, - ....: mat2) - sage: f*g - Linear operator between finite-dimensional Euclidean Jordan - algebras represented by the matrix: - [39 54 69] - Domain: Euclidean Jordan algebra of degree 3 over Rational Field - Codomain: Euclidean Jordan algebra of degree 1 over Rational Field - - """ - return FiniteDimensionalEuclideanJordanAlgebraOperator( - other.domain(), - self.codomain(), - self.matrix()*other.matrix()) - - - def __eq__(self, other): - if self.domain() != other.domain(): - return False - if self.codomain() != other.codomain(): - return False - if self.matrix() != other.matrix(): - return False - return True - - - def __invert__(self): - """ - Invert this EJA operator. - - EXAMPLES:: - - sage: J = RealSymmetricEJA(2) - sage: id = identity_matrix(J.base_ring(), J.dimension()) - sage: f = FiniteDimensionalEuclideanJordanAlgebraOperator(J,J,id) - sage: ~f - Linear operator between finite-dimensional Euclidean Jordan - algebras represented by the matrix: - [1 0 0] - [0 1 0] - [0 0 1] - Domain: Euclidean Jordan algebra of degree 3 over Rational Field - Codomain: Euclidean Jordan algebra of degree 3 over Rational Field - - """ - return FiniteDimensionalEuclideanJordanAlgebraOperator( - self.codomain(), - self.domain(), - ~self.matrix()) - - - def __mul__(self, other): - """ - Compose two EJA operators, or scale myself by an element of the - ambient vector space. - - We need to override the real ``__mul__`` function to prevent the - coercion framework from throwing an error when it fails to convert - a base ring element into a morphism. - - EXAMPLES: - - We can scale an operator on a rational algebra by a rational number:: - - sage: J = RealSymmetricEJA(2) - sage: e0,e1,e2 = J.gens() - sage: x = 2*e0 + 4*e1 + 16*e2 - sage: x.operator() - Linear operator between finite-dimensional Euclidean Jordan algebras - represented by the matrix: - [ 2 4 0] - [ 2 9 2] - [ 0 4 16] - Domain: Euclidean Jordan algebra of degree 3 over Rational Field - Codomain: Euclidean Jordan algebra of degree 3 over Rational Field - sage: x.operator()*(1/2) - Linear operator between finite-dimensional Euclidean Jordan algebras - represented by the matrix: - [ 1 2 0] - [ 1 9/2 1] - [ 0 2 8] - Domain: Euclidean Jordan algebra of degree 3 over Rational Field - Codomain: Euclidean Jordan algebra of degree 3 over Rational Field - - """ - if other in self.codomain().base_ring(): - return FiniteDimensionalEuclideanJordanAlgebraOperator( - self.domain(), - self.codomain(), - self.matrix()*other) - - # This should eventually delegate to _composition_ after performing - # some sanity checks for us. - mor = super(FiniteDimensionalEuclideanJordanAlgebraOperator,self) - return mor.__mul__(other) - - - def _neg_(self): - """ - Negate this EJA operator. - - EXAMPLES:: - - sage: J = RealSymmetricEJA(2) - sage: id = identity_matrix(J.base_ring(), J.dimension()) - sage: f = FiniteDimensionalEuclideanJordanAlgebraOperator(J,J,id) - sage: -f - Linear operator between finite-dimensional Euclidean Jordan - algebras represented by the matrix: - [-1 0 0] - [ 0 -1 0] - [ 0 0 -1] - Domain: Euclidean Jordan algebra of degree 3 over Rational Field - Codomain: Euclidean Jordan algebra of degree 3 over Rational Field - - """ - return FiniteDimensionalEuclideanJordanAlgebraOperator( - self.domain(), - self.codomain(), - -self.matrix()) - - - def __pow__(self, n): - """ - Raise this EJA operator to the power ``n``. - - TESTS: - - Ensure that we get back another EJA operator that can be added, - subtracted, et cetera:: - - sage: J = RealSymmetricEJA(2) - sage: id = identity_matrix(J.base_ring(), J.dimension()) - sage: f = FiniteDimensionalEuclideanJordanAlgebraOperator(J,J,id) - sage: f^0 + f^1 + f^2 - Linear operator between finite-dimensional Euclidean Jordan - algebras represented by the matrix: - [3 0 0] - [0 3 0] - [0 0 3] - Domain: Euclidean Jordan algebra of degree 3 over Rational Field - Codomain: Euclidean Jordan algebra of degree 3 over Rational Field - - """ - if (n == 1): - return self - elif (n == 0): - # Raising a vector space morphism to the zero power gives - # you back a special IdentityMorphism that is useless to us. - rows = self.codomain().dimension() - cols = self.domain().dimension() - mat = matrix.identity(self.base_ring(), rows, cols) - else: - mat = self.matrix()**n - - return FiniteDimensionalEuclideanJordanAlgebraOperator( - self.domain(), - self.codomain(), - mat) - - - def _repr_(self): - r""" - - A text representation of this linear operator on a Euclidean - Jordan Algebra. - - EXAMPLES:: - - sage: J = JordanSpinEJA(2) - sage: id = identity_matrix(J.base_ring(), J.dimension()) - sage: FiniteDimensionalEuclideanJordanAlgebraOperator(J,J,id) - Linear operator between finite-dimensional Euclidean Jordan - algebras represented by the matrix: - [1 0] - [0 1] - Domain: Euclidean Jordan algebra of degree 2 over Rational Field - Codomain: Euclidean Jordan algebra of degree 2 over Rational Field - - """ - msg = ("Linear operator between finite-dimensional Euclidean Jordan " - "algebras represented by the matrix:\n", - "{!r}\n", - "Domain: {}\n", - "Codomain: {}") - return ''.join(msg).format(self.matrix(), - self.domain(), - self.codomain()) - - - def _sub_(self, other): - """ - Subtract ``other`` from this EJA operator. - - EXAMPLES:: - - sage: J = RealSymmetricEJA(2) - sage: id = identity_matrix(J.base_ring(),J.dimension()) - sage: f = FiniteDimensionalEuclideanJordanAlgebraOperator(J,J,id) - sage: f - (f*2) - Linear operator between finite-dimensional Euclidean Jordan - algebras represented by the matrix: - [-1 0 0] - [ 0 -1 0] - [ 0 0 -1] - Domain: Euclidean Jordan algebra of degree 3 over Rational Field - Codomain: Euclidean Jordan algebra of degree 3 over Rational Field - - """ - return (self + (-other)) - - - def matrix(self): - """ - Return the matrix representation of this operator with respect - to the default bases of its (co)domain. - - EXAMPLES:: - - sage: J = RealSymmetricEJA(2) - sage: mat = matrix(J.base_ring(), J.dimension(), range(9)) - sage: f = FiniteDimensionalEuclideanJordanAlgebraOperator(J,J,mat) - sage: f.matrix() - [0 1 2] - [3 4 5] - [6 7 8] - - """ - return self._matrix - - - def minimal_polynomial(self): - """ - Return the minimal polynomial of this linear operator, - in the variable ``t``. - - EXAMPLES:: - - sage: J = RealSymmetricEJA(3) - sage: J.one().operator().minimal_polynomial() - t - 1 - - """ - # The matrix method returns a polynomial in 'x' but want one in 't'. - return self.matrix().minimal_polynomial().change_variable_name('t') +from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra): @@ -413,6 +61,10 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra): rank=None, natural_basis=None): """ + SETUP:: + + sage: from mjo.eja.eja_algebra import random_eja + EXAMPLES: By definition, Jordan multiplication commutes:: @@ -569,6 +221,10 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra): other words, we don't have to do a change of basis before e.g. computing the trace or determinant. + SETUP:: + + sage: from mjo.eja.eja_algebra import JordanSpinEJA + EXAMPLES: The characteristic polynomial in the spin algebra is given in @@ -620,6 +276,10 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra): subclasses if they are sure that the necessary properties are satisfied. + SETUP:: + + sage: from mjo.eja.eja_algebra import random_eja + EXAMPLES: The inner product must satisfy its axiom for this algebra to truly @@ -652,6 +312,11 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra): Note that this will always return a matrix. The standard basis in `R^n` will be returned as `n`-by-`1` column matrices. + SETUP:: + + sage: from mjo.eja.eja_algebra import (JordanSpinEJA, + ....: RealSymmetricEJA) + EXAMPLES:: sage: J = RealSymmetricEJA(2) @@ -690,10 +355,15 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra): else: return self._rank + def vector_space(self): """ Return the vector space that underlies this algebra. + SETUP:: + + sage: from mjo.eja.eja_algebra import RealSymmetricEJA + EXAMPLES:: sage: J = RealSymmetricEJA(2) @@ -721,6 +391,11 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra): def __init__(self, A, elt=None): """ + + SETUP:: + + sage: from mjo.eja.eja_algebra import RealSymmetricEJA + EXAMPLES: The identity in `S^n` is converted to the identity in the EJA:: @@ -777,6 +452,10 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra): instead of column vectors! We, on the other hand, assume column vectors everywhere. + SETUP:: + + sage: from mjo.eja.eja_algebra import random_eja + EXAMPLES:: sage: set_random_seed() @@ -825,6 +504,11 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra): operation is ultimately kosher. This function sidesteps the CAS to get the answer we want and expect. + SETUP:: + + sage: from mjo.eja.eja_algebra import (RealCartesianProductEJA, + ....: random_eja) + EXAMPLES:: sage: R = PolynomialRing(QQ, 't') @@ -861,6 +545,10 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra): """ Return the characteristic polynomial of this element. + SETUP:: + + sage: from mjo.eja.eja_algebra import RealCartesianProductEJA + EXAMPLES: The rank of `R^3` is three, and the minimal polynomial of @@ -896,6 +584,15 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra): """ Return the parent algebra's inner product of myself and ``other``. + SETUP:: + + sage: from mjo.eja.eja_algebra import ( + ....: ComplexHermitianEJA, + ....: JordanSpinEJA, + ....: QuaternionHermitianEJA, + ....: RealSymmetricEJA, + ....: random_eja) + EXAMPLES: The inner product in the Jordan spin algebra is the usual @@ -959,6 +656,10 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra): Return whether or not this element operator-commutes with ``other``. + SETUP:: + + sage: from mjo.eja.eja_algebra import random_eja + EXAMPLES: The definition of a Jordan algebra says that any element @@ -1046,6 +747,11 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra): """ Return my determinant, the product of my eigenvalues. + SETUP:: + + sage: from mjo.eja.eja_algebra import (JordanSpinEJA, + ....: random_eja) + EXAMPLES:: sage: J = JordanSpinEJA(2) @@ -1092,6 +798,11 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra): We appeal to the quadratic representation as in Koecher's Theorem 12 in Chapter III, Section 5. + SETUP:: + + sage: from mjo.eja.eja_algebra import (JordanSpinEJA, + ....: random_eja) + EXAMPLES: The inverse in the spin factor algebra is given in Alizadeh's @@ -1168,6 +879,10 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra): whether or not the paren't algebra's zero element is a root of this element's minimal polynomial. + SETUP:: + + sage: from mjo.eja.eja_algebra import random_eja + TESTS: The identity element is always invertible:: @@ -1199,6 +914,10 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra): an assocoative subalgebra and we're nilpotent there if and only if we're nilpotent here (probably). + SETUP:: + + sage: from mjo.eja.eja_algebra import random_eja + TESTS: The identity element is never nilpotent:: @@ -1239,6 +958,10 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra): """ Return whether or not this is a regular element. + SETUP:: + + sage: from mjo.eja.eja_algebra import JordanSpinEJA + EXAMPLES: The identity element always has degree one, but any element @@ -1267,6 +990,10 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra): and figuring out its dimension (that is, whether or not they're linearly dependent). + SETUP:: + + sage: from mjo.eja.eja_algebra import JordanSpinEJA + EXAMPLES:: sage: J = JordanSpinEJA(4) @@ -1312,6 +1039,11 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra): polynomial of this element's operator matrix (in that subalgebra). This works by Baes Proposition 2.3.16. + SETUP:: + + sage: from mjo.eja.eja_algebra import (JordanSpinEJA, + ....: random_eja) + TESTS: The minimal polynomial of the identity and zero elements are @@ -1380,6 +1112,11 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra): "natural" representation of this element as a Hermitian matrix, if it has one. If not, you get the usual representation. + SETUP:: + + sage: from mjo.eja.eja_algebra import (ComplexHermitianEJA, + ....: QuaternionHermitianEJA) + EXAMPLES:: sage: J = ComplexHermitianEJA(3) @@ -1423,6 +1160,10 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra): Return the left-multiplication-by-this-element operator on the ambient algebra. + SETUP:: + + sage: from mjo.eja.eja_algebra import random_eja + TESTS:: sage: set_random_seed() @@ -1447,6 +1188,11 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra): """ Return the quadratic representation of this element. + SETUP:: + + sage: from mjo.eja.eja_algebra import (JordanSpinEJA, + ....: random_eja) + EXAMPLES: The explicit form in the spin factor algebra is given by @@ -1578,6 +1324,10 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra): Return the associative subalgebra of the parent EJA generated by this element. + SETUP:: + + sage: from mjo.eja.eja_algebra import random_eja + TESTS:: sage: set_random_seed() @@ -1635,6 +1385,10 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra): Find an idempotent in the associative subalgebra I generate using Proposition 2.3.5 in Baes. + SETUP:: + + sage: from mjo.eja.eja_algebra import random_eja + TESTS:: sage: set_random_seed() @@ -1695,6 +1449,12 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra): """ Return my trace, the sum of my eigenvalues. + SETUP:: + + sage: from mjo.eja.eja_algebra import (JordanSpinEJA, + ....: RealCartesianProductEJA, + ....: random_eja) + EXAMPLES:: sage: J = JordanSpinEJA(3) @@ -1732,6 +1492,10 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra): """ Return the trace inner product of myself and ``other``. + SETUP:: + + sage: from mjo.eja.eja_algebra import random_eja + TESTS: The trace inner product is commutative:: @@ -1788,6 +1552,10 @@ class RealCartesianProductEJA(FiniteDimensionalEuclideanJordanAlgebra): copies of the spin algebra. Once Cartesian product algebras are implemented, this can go. + SETUP:: + + sage: from mjo.eja.eja_algebra import RealCartesianProductEJA + EXAMPLES: This multiplication table can be verified by hand:: @@ -1851,6 +1619,10 @@ def random_eja(): Later this might be extended to return Cartesian products of the EJAs above. + SETUP:: + + sage: from mjo.eja.eja_algebra import random_eja + TESTS:: sage: random_eja() @@ -1895,6 +1667,10 @@ def _complex_hermitian_basis(n, field=QQ): """ Returns a basis for the space of complex Hermitian n-by-n matrices. + SETUP:: + + sage: from mjo.eja.eja_algebra import _complex_hermitian_basis + TESTS:: sage: set_random_seed() @@ -1932,6 +1708,10 @@ def _quaternion_hermitian_basis(n, field=QQ): """ Returns a basis for the space of quaternion Hermitian n-by-n matrices. + SETUP:: + + sage: from mjo.eja.eja_algebra import _quaternion_hermitian_basis + TESTS:: sage: set_random_seed() @@ -2029,6 +1809,10 @@ def _embed_complex_matrix(M): matrices of size 2n-by-2n via the map the sends each entry `z = a + bi` to the block matrix ``[[a,b],[-b,a]]``. + SETUP:: + + sage: from mjo.eja.eja_algebra import _embed_complex_matrix + EXAMPLES:: sage: F = QuadraticField(-1,'i') @@ -2077,6 +1861,11 @@ def _unembed_complex_matrix(M): """ The inverse of _embed_complex_matrix(). + SETUP:: + + sage: from mjo.eja.eja_algebra import (_embed_complex_matrix, + ....: _unembed_complex_matrix) + EXAMPLES:: sage: A = matrix(QQ,[ [ 1, 2, 3, 4], @@ -2131,6 +1920,10 @@ def _embed_quaternion_matrix(M): ``[[a + bi, c+di],[-c + di, a-bi]]`, and then embedding those into a real matrix. + SETUP:: + + sage: from mjo.eja.eja_algebra import _embed_quaternion_matrix + EXAMPLES:: sage: Q = QuaternionAlgebra(QQ,-1,-1) @@ -2184,6 +1977,11 @@ def _unembed_quaternion_matrix(M): """ The inverse of _embed_quaternion_matrix(). + SETUP:: + + sage: from mjo.eja.eja_algebra import (_embed_quaternion_matrix, + ....: _unembed_quaternion_matrix) + EXAMPLES:: sage: M = matrix(QQ, [[ 1, 2, 3, 4], @@ -2250,6 +2048,10 @@ class RealSymmetricEJA(FiniteDimensionalEuclideanJordanAlgebra): matrices, the usual symmetric Jordan product, and the trace inner product. It has dimension `(n^2 + n)/2` over the reals. + SETUP:: + + sage: from mjo.eja.eja_algebra import RealSymmetricEJA + EXAMPLES:: sage: J = RealSymmetricEJA(2) @@ -2311,6 +2113,10 @@ class ComplexHermitianEJA(FiniteDimensionalEuclideanJordanAlgebra): and the real-part-of-trace inner product. It has dimension `n^2` over the reals. + SETUP:: + + sage: from mjo.eja.eja_algebra import ComplexHermitianEJA + TESTS: The degree of this algebra is `n^2`:: @@ -2368,6 +2174,10 @@ class QuaternionHermitianEJA(FiniteDimensionalEuclideanJordanAlgebra): real-part-of-trace inner product. It has dimension `2n^2 - n` over the reals. + SETUP:: + + sage: from mjo.eja.eja_algebra import QuaternionHermitianEJA + TESTS: The degree of this algebra is `n^2`:: @@ -2427,6 +2237,10 @@ class JordanSpinEJA(FiniteDimensionalEuclideanJordanAlgebra): (, x0*y_bar + y0*x_bar)``. It has dimension `n` over the reals. + SETUP:: + + sage: from mjo.eja.eja_algebra import JordanSpinEJA + EXAMPLES: This multiplication table can be verified by hand:: diff --git a/mjo/eja/eja_operator.py b/mjo/eja/eja_operator.py new file mode 100644 index 0000000..b225b29 --- /dev/null +++ b/mjo/eja/eja_operator.py @@ -0,0 +1,416 @@ +from sage.all import matrix +from sage.categories.all import FreeModules +from sage.categories.map import Map + +class FiniteDimensionalEuclideanJordanAlgebraOperator(Map): + def __init__(self, domain_eja, codomain_eja, mat): + # if not ( + # isinstance(domain_eja, FiniteDimensionalEuclideanJordanAlgebra) and + # isinstance(codomain_eja, FiniteDimensionalEuclideanJordanAlgebra) ): + # raise ValueError('(co)domains must be finite-dimensional Euclidean ' + # 'Jordan algebras') + + F = domain_eja.base_ring() + if not (F == codomain_eja.base_ring()): + raise ValueError("domain and codomain must have the same base ring") + + # We need to supply something here to avoid getting the + # default Homset of the parent FiniteDimensionalAlgebra class, + # which messes up e.g. equality testing. We use FreeModules(F) + # instead of VectorSpaces(F) because our characteristic polynomial + # algorithm will need to F to be a polynomial ring at some point. + # When F is a field, FreeModules(F) returns VectorSpaces(F) anyway. + parent = domain_eja.Hom(codomain_eja, FreeModules(F)) + + # The Map initializer will set our parent to a homset, which + # is explicitly NOT what we want, because these ain't algebra + # homomorphisms. + super(FiniteDimensionalEuclideanJordanAlgebraOperator,self).__init__(parent) + + # Keep a matrix around to do all of the real work. It would + # be nice if we could use a VectorSpaceMorphism instead, but + # those use row vectors that we don't want to accidentally + # expose to our users. + self._matrix = mat + + + def _call_(self, x): + """ + Allow this operator to be called only on elements of an EJA. + + SETUP:: + + sage: from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator + sage: from mjo.eja.eja_algebra import JordanSpinEJA + + EXAMPLES:: + + sage: J = JordanSpinEJA(3) + sage: x = J.linear_combination(zip(range(len(J.gens())), J.gens())) + sage: id = identity_matrix(J.base_ring(), J.dimension()) + sage: f = FiniteDimensionalEuclideanJordanAlgebraOperator(J,J,id) + sage: f(x) == x + True + + """ + return self.codomain()(self.matrix()*x.vector()) + + + def _add_(self, other): + """ + Add the ``other`` EJA operator to this one. + + SETUP:: + + sage: from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator + sage: from mjo.eja.eja_algebra import ( + ....: JordanSpinEJA, + ....: RealSymmetricEJA ) + + EXAMPLES: + + When we add two EJA operators, we get another one back:: + + sage: J = RealSymmetricEJA(2) + sage: id = identity_matrix(J.base_ring(), J.dimension()) + sage: f = FiniteDimensionalEuclideanJordanAlgebraOperator(J,J,id) + sage: g = FiniteDimensionalEuclideanJordanAlgebraOperator(J,J,id) + sage: f + g + Linear operator between finite-dimensional Euclidean Jordan + algebras represented by the matrix: + [2 0 0] + [0 2 0] + [0 0 2] + Domain: Euclidean Jordan algebra of degree 3 over Rational Field + Codomain: Euclidean Jordan algebra of degree 3 over Rational Field + + If you try to add two identical vector space operators but on + different EJAs, that should blow up:: + + sage: J1 = RealSymmetricEJA(2) + sage: J2 = JordanSpinEJA(3) + sage: id = identity_matrix(QQ, 3) + sage: f = FiniteDimensionalEuclideanJordanAlgebraOperator(J1,J1,id) + sage: g = FiniteDimensionalEuclideanJordanAlgebraOperator(J2,J2,id) + sage: f + g + Traceback (most recent call last): + ... + TypeError: unsupported operand parent(s) for +: ... + + """ + return FiniteDimensionalEuclideanJordanAlgebraOperator( + self.domain(), + self.codomain(), + self.matrix() + other.matrix()) + + + def _composition_(self, other, homset): + """ + Compose two EJA operators to get another one (and NOT a formal + composite object) back. + + SETUP:: + + sage: from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator + sage: from mjo.eja.eja_algebra import ( + ....: JordanSpinEJA, + ....: RealCartesianProductEJA, + ....: RealSymmetricEJA) + + EXAMPLES:: + + sage: J1 = JordanSpinEJA(3) + sage: J2 = RealCartesianProductEJA(2) + sage: J3 = RealSymmetricEJA(1) + sage: mat1 = matrix(QQ, [[1,2,3], + ....: [4,5,6]]) + sage: mat2 = matrix(QQ, [[7,8]]) + sage: g = FiniteDimensionalEuclideanJordanAlgebraOperator(J1, + ....: J2, + ....: mat1) + sage: f = FiniteDimensionalEuclideanJordanAlgebraOperator(J2, + ....: J3, + ....: mat2) + sage: f*g + Linear operator between finite-dimensional Euclidean Jordan + algebras represented by the matrix: + [39 54 69] + Domain: Euclidean Jordan algebra of degree 3 over Rational Field + Codomain: Euclidean Jordan algebra of degree 1 over Rational Field + + """ + return FiniteDimensionalEuclideanJordanAlgebraOperator( + other.domain(), + self.codomain(), + self.matrix()*other.matrix()) + + + def __eq__(self, other): + if self.domain() != other.domain(): + return False + if self.codomain() != other.codomain(): + return False + if self.matrix() != other.matrix(): + return False + return True + + + def __invert__(self): + """ + Invert this EJA operator. + + SETUP:: + + sage: from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator + sage: from mjo.eja.eja_algebra import RealSymmetricEJA + + EXAMPLES:: + + sage: J = RealSymmetricEJA(2) + sage: id = identity_matrix(J.base_ring(), J.dimension()) + sage: f = FiniteDimensionalEuclideanJordanAlgebraOperator(J,J,id) + sage: ~f + Linear operator between finite-dimensional Euclidean Jordan + algebras represented by the matrix: + [1 0 0] + [0 1 0] + [0 0 1] + Domain: Euclidean Jordan algebra of degree 3 over Rational Field + Codomain: Euclidean Jordan algebra of degree 3 over Rational Field + + """ + return FiniteDimensionalEuclideanJordanAlgebraOperator( + self.codomain(), + self.domain(), + ~self.matrix()) + + + def __mul__(self, other): + """ + Compose two EJA operators, or scale myself by an element of the + ambient vector space. + + We need to override the real ``__mul__`` function to prevent the + coercion framework from throwing an error when it fails to convert + a base ring element into a morphism. + + SETUP:: + + sage: from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator + sage: from mjo.eja.eja_algebra import RealSymmetricEJA + + EXAMPLES: + + We can scale an operator on a rational algebra by a rational number:: + + sage: J = RealSymmetricEJA(2) + sage: e0,e1,e2 = J.gens() + sage: x = 2*e0 + 4*e1 + 16*e2 + sage: x.operator() + Linear operator between finite-dimensional Euclidean Jordan algebras + represented by the matrix: + [ 2 4 0] + [ 2 9 2] + [ 0 4 16] + Domain: Euclidean Jordan algebra of degree 3 over Rational Field + Codomain: Euclidean Jordan algebra of degree 3 over Rational Field + sage: x.operator()*(1/2) + Linear operator between finite-dimensional Euclidean Jordan algebras + represented by the matrix: + [ 1 2 0] + [ 1 9/2 1] + [ 0 2 8] + Domain: Euclidean Jordan algebra of degree 3 over Rational Field + Codomain: Euclidean Jordan algebra of degree 3 over Rational Field + + """ + if other in self.codomain().base_ring(): + return FiniteDimensionalEuclideanJordanAlgebraOperator( + self.domain(), + self.codomain(), + self.matrix()*other) + + # This should eventually delegate to _composition_ after performing + # some sanity checks for us. + mor = super(FiniteDimensionalEuclideanJordanAlgebraOperator,self) + return mor.__mul__(other) + + + def _neg_(self): + """ + Negate this EJA operator. + + SETUP:: + + sage: from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator + sage: from mjo.eja.eja_algebra import RealSymmetricEJA + + EXAMPLES:: + + sage: J = RealSymmetricEJA(2) + sage: id = identity_matrix(J.base_ring(), J.dimension()) + sage: f = FiniteDimensionalEuclideanJordanAlgebraOperator(J,J,id) + sage: -f + Linear operator between finite-dimensional Euclidean Jordan + algebras represented by the matrix: + [-1 0 0] + [ 0 -1 0] + [ 0 0 -1] + Domain: Euclidean Jordan algebra of degree 3 over Rational Field + Codomain: Euclidean Jordan algebra of degree 3 over Rational Field + + """ + return FiniteDimensionalEuclideanJordanAlgebraOperator( + self.domain(), + self.codomain(), + -self.matrix()) + + + def __pow__(self, n): + """ + Raise this EJA operator to the power ``n``. + + SETUP:: + + sage: from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator + sage: from mjo.eja.eja_algebra import RealSymmetricEJA + + TESTS: + + Ensure that we get back another EJA operator that can be added, + subtracted, et cetera:: + + sage: J = RealSymmetricEJA(2) + sage: id = identity_matrix(J.base_ring(), J.dimension()) + sage: f = FiniteDimensionalEuclideanJordanAlgebraOperator(J,J,id) + sage: f^0 + f^1 + f^2 + Linear operator between finite-dimensional Euclidean Jordan + algebras represented by the matrix: + [3 0 0] + [0 3 0] + [0 0 3] + Domain: Euclidean Jordan algebra of degree 3 over Rational Field + Codomain: Euclidean Jordan algebra of degree 3 over Rational Field + + """ + if (n == 1): + return self + elif (n == 0): + # Raising a vector space morphism to the zero power gives + # you back a special IdentityMorphism that is useless to us. + rows = self.codomain().dimension() + cols = self.domain().dimension() + mat = matrix.identity(self.base_ring(), rows, cols) + else: + mat = self.matrix()**n + + return FiniteDimensionalEuclideanJordanAlgebraOperator( + self.domain(), + self.codomain(), + mat) + + + def _repr_(self): + r""" + + A text representation of this linear operator on a Euclidean + Jordan Algebra. + + SETUP:: + + sage: from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator + sage: from mjo.eja.eja_algebra import JordanSpinEJA + + EXAMPLES:: + + sage: J = JordanSpinEJA(2) + sage: id = identity_matrix(J.base_ring(), J.dimension()) + sage: FiniteDimensionalEuclideanJordanAlgebraOperator(J,J,id) + Linear operator between finite-dimensional Euclidean Jordan + algebras represented by the matrix: + [1 0] + [0 1] + Domain: Euclidean Jordan algebra of degree 2 over Rational Field + Codomain: Euclidean Jordan algebra of degree 2 over Rational Field + + """ + msg = ("Linear operator between finite-dimensional Euclidean Jordan " + "algebras represented by the matrix:\n", + "{!r}\n", + "Domain: {}\n", + "Codomain: {}") + return ''.join(msg).format(self.matrix(), + self.domain(), + self.codomain()) + + + def _sub_(self, other): + """ + Subtract ``other`` from this EJA operator. + + SETUP:: + + sage: from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator + sage: from mjo.eja.eja_algebra import RealSymmetricEJA + + EXAMPLES:: + + sage: J = RealSymmetricEJA(2) + sage: id = identity_matrix(J.base_ring(),J.dimension()) + sage: f = FiniteDimensionalEuclideanJordanAlgebraOperator(J,J,id) + sage: f - (f*2) + Linear operator between finite-dimensional Euclidean Jordan + algebras represented by the matrix: + [-1 0 0] + [ 0 -1 0] + [ 0 0 -1] + Domain: Euclidean Jordan algebra of degree 3 over Rational Field + Codomain: Euclidean Jordan algebra of degree 3 over Rational Field + + """ + return (self + (-other)) + + + def matrix(self): + """ + Return the matrix representation of this operator with respect + to the default bases of its (co)domain. + + SETUP:: + + sage: from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator + sage: from mjo.eja.eja_algebra import RealSymmetricEJA + + EXAMPLES:: + + sage: J = RealSymmetricEJA(2) + sage: mat = matrix(J.base_ring(), J.dimension(), range(9)) + sage: f = FiniteDimensionalEuclideanJordanAlgebraOperator(J,J,mat) + sage: f.matrix() + [0 1 2] + [3 4 5] + [6 7 8] + + """ + return self._matrix + + + def minimal_polynomial(self): + """ + Return the minimal polynomial of this linear operator, + in the variable ``t``. + + SETUP:: + + sage: from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator + sage: from mjo.eja.eja_algebra import RealSymmetricEJA + + EXAMPLES:: + + sage: J = RealSymmetricEJA(3) + sage: J.one().operator().minimal_polynomial() + t - 1 + + """ + # The matrix method returns a polynomial in 'x' but want one in 't'. + return self.matrix().minimal_polynomial().change_variable_name('t') -- 2.43.2