what can be supported in a general Jordan Algebra.
"""
-from sage.categories.magmatic_algebras import MagmaticAlgebras
+from sage.categories.finite_dimensional_algebras_with_basis import FiniteDimensionalAlgebrasWithBasis
+from sage.categories.morphism import SetMorphism
+from sage.modules.vector_space_morphism import VectorSpaceMorphism
from sage.structure.element import is_Matrix
from sage.structure.category_object import normalize_names
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.algebras.finite_dimensional_algebras.finite_dimensional_algebra_morphism import FiniteDimensionalAlgebraMorphism
-class FiniteDimensionalEuclideanJordanAlgebraMorphism(FiniteDimensionalAlgebraMorphism):
- """
- A linear map between two finite-dimensional EJAs.
-
- This is a very thin wrapper around FiniteDimensionalAlgebraMorphism
- that does only a few things:
+class FiniteDimensionalEuclideanJordanAlgebraOperator(VectorSpaceMorphism):
+ def __init__(self, domain_eja, codomain_eja, mat):
+ # We save these so that we can output them as part of our
+ # text representation. Overriding the domain/codomain methods
+ # doesn't work because the EJAs aren't (directly) vector spaces.
+ self._domain_eja = domain_eja
+ self._codomain_eja = codomain_eja
- 1. Avoids the ``unitary`` and ``check`` arguments to the constructor
- that will always be ``False``. This is necessary because these
- are homomorphisms with respect to ADDITION, but the SageMath
- machinery wants to check that they're homomorphisms with respect
- to (Jordan) MULTIPLICATION. That obviously doesn't work.
+ # Otherwise, we just feed everything to the vector space morphism
+ # constructor.
+ V = domain_eja.vector_space()
+ W = codomain_eja.vector_space()
+ homspace = V.Hom(W)
+ VectorSpaceMorphism.__init__(self, homspace, mat)
- 2. Inputs and outputs the underlying matrix with respect to COLUMN
- vectors, unlike the parent class.
- 3. Allows us to add morphisms in the obvious way.
+ def __call__(self, x):
+ """
+ Allow this operator to be called only on elements of an EJA.
- 4. Allows us to invert morphisms.
+ EXAMPLES::
- If this seems a bit heavyweight, it is. I would have been happy to
- use a the ring morphism that underlies the finite-dimensional
- algebra morphism, but they don't seem to be callable on elements of
- our EJA, and you can't add/invert them.
- """
+ 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
- def __add__(self, other):
"""
- Add two EJA morphisms in the obvious way.
+ # Overriding the single-underscore _call_ didn't work?
+ if x not in self._domain_eja:
+ raise ValueError("argument does not live in the operator's domain")
+ return self._codomain_eja(self.matrix()*x.vector())
+
+
+ def _repr_(self):
+ r"""
+
+ A text representation of this linear operator on a Euclidean
+ Jordan Algebra.
EXAMPLES::
- sage: J = RealSymmetricEJA(3)
- sage: x = J.zero()
- sage: y = J.one()
- sage: x.operator() + y.operator()
- Morphism from Euclidean Jordan algebra of degree 6 over Rational
- Field to Euclidean Jordan algebra of degree 6 over Rational Field
- given by matrix
- [1 0 0 0 0 0]
- [0 1 0 0 0 0]
- [0 0 1 0 0 0]
- [0 0 0 1 0 0]
- [0 0 0 0 1 0]
- [0 0 0 0 0 1]
-
- TESTS::
+ 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_eja,
+ self._codomain_eja)
- sage: set_random_seed()
- sage: J = random_eja()
- sage: x = J.random_element()
- sage: y = J.random_element()
- sage: (x.operator() + y.operator()) in J.Hom(J)
- True
+ def __add__(self, other):
"""
- P = self.parent()
- if not other in P:
- raise ValueError("summands must live in the same space")
+ Add the ``other`` EJA operator to this one.
- return FiniteDimensionalEuclideanJordanAlgebraMorphism(
- P,
- self.matrix() + other.matrix() )
+ EXAMPLES:
+ When we add two EJA operators, we get another one back::
- def __init__(self, parent, f):
- FiniteDimensionalAlgebraMorphism.__init__(self,
- parent,
- f.transpose(),
- unitary=False,
- check=False)
+ 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):
+ ...
+ ValueError: operator (co)domains must match
+
+ """
+ if not (self._domain_eja == other._domain_eja and
+ self._codomain_eja == other._codomain_eja):
+ raise ValueError("operator (co)domains must match")
+ return FiniteDimensionalEuclideanJordanAlgebraOperator(
+ self._domain_eja,
+ self._codomain_eja,
+ VectorSpaceMorphism.__add__(self,other))
def __invert__(self):
"""
+ Invert this EJA operator.
+
EXAMPLES::
sage: J = RealSymmetricEJA(2)
- sage: x = J.linear_combination(zip(range(len(J.gens())), J.gens()))
- sage: x.is_invertible()
- True
- sage: ~x.operator()
- Morphism from Euclidean Jordan algebra of degree 3 over Rational
- Field to Euclidean Jordan algebra of degree 3 over Rational Field
- given by matrix
- [-3/2 2 -1/2]
- [ 1 0 0]
- [-1/2 0 1/2]
- sage: x.operator_matrix().inverse()
- [-3/2 2 -1/2]
- [ 1 0 0]
- [-1/2 0 1/2]
-
- TESTS::
+ 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
- sage: set_random_seed()
- sage: J = random_eja()
- sage: x = J.random_element()
- sage: not x.is_invertible() or (
- ....: (~x.operator()).matrix() == x.operator_matrix().inverse() )
- True
+ """
+ return FiniteDimensionalEuclideanJordanAlgebraOperator(
+ self._codomain_eja,
+ self._domain_eja,
+ VectorSpaceMorphism.__invert__(self))
+ def __mul__(self, other):
+ """
+ Compose this EJA operator with the ``other`` one, or scale it by
+ an element of its base ring.
"""
- A = self.matrix()
- if not A.is_invertible():
- raise ValueError("morphism is not invertible")
+ if other in self._codomain_eja.base_ring():
+ return FiniteDimensionalEuclideanJordanAlgebraOperator(
+ self._domain_eja,
+ self._codomain_eja,
+ self._matrix*other)
- P = self.parent()
- return FiniteDimensionalEuclideanJordanAlgebraMorphism(self.parent(),
- A.inverse())
+ if not (self._domain_eja == other._codomain_eja):
+ raise ValueError("operator (co)domains must be compatible")
+
+ return FiniteDimensionalEuclideanJordanAlgebraOperator(
+ other._domain_eja,
+ self._codomain_eja,
+ VectorSpaceMorphism.__mul__(self,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
- def _repr_(self):
"""
- We override only the representation that is shown to the user,
- because we want the matrix to be with respect to COLUMN vectors.
+ return FiniteDimensionalEuclideanJordanAlgebraOperator(
+ self._domain_eja,
+ self._codomain_eja,
+ VectorSpaceMorphism.__neg__(self))
+
+
+ def __pow__(self, n):
+ """
+ Raise this EJA operator to the power ``n``.
TESTS:
- Ensure that we see the transpose of the underlying matrix object:
+ Ensure that we get back another EJA operator that can be added,
+ subtracted, et cetera::
- sage: J = RealSymmetricEJA(3)
- sage: x = J.linear_combination(zip(range(len(J.gens())), J.gens()))
- sage: L = x.operator()
- sage: L
- Morphism from Euclidean Jordan algebra of degree 6 over Rational
- Field to Euclidean Jordan algebra of degree 6 over Rational Field
- given by matrix
- [ 0 1 2 0 0 0]
- [1/2 3/2 2 1/2 1 0]
- [ 1 2 5/2 0 1/2 1]
- [ 0 1 0 3 4 0]
- [ 0 1 1/2 2 4 2]
- [ 0 0 2 0 4 5]
- sage: L._matrix
- [ 0 1/2 1 0 0 0]
- [ 1 3/2 2 1 1 0]
- [ 2 2 5/2 0 1/2 2]
- [ 0 1/2 0 3 2 0]
- [ 0 1 1/2 4 4 4]
- [ 0 0 1 0 2 5]
+ 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
"""
- return "Morphism from {} to {} given by matrix\n{}".format(
- self.domain(), self.codomain(), self.matrix())
+ 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 = VectorSpaceMorphism.__pow__(self,n)
- def matrix(self):
+ return FiniteDimensionalEuclideanJordanAlgebraOperator(
+ self._domain_eja,
+ self._codomain_eja,
+ mat)
+
+ def __sub__(self, other):
"""
- Return the matrix of this morphism with respect to a left-action
- on column vectors.
+ Subtract ``other`` from this EJA operator.
"""
- return FiniteDimensionalAlgebraMorphism.matrix(self).transpose()
+ return (self + (-other))
class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra):
raise ValueError("input is not a multiplication table")
mult_table = tuple(mult_table)
- cat = MagmaticAlgebras(field).FiniteDimensional().WithBasis()
+ cat = FiniteDimensionalAlgebrasWithBasis(field)
cat.or_subcategory(category)
if assume_associative:
cat = cat.Associative()
determinant).
"""
z = self._a_regular_element()
- V = z.vector().parent().ambient_vector_space()
+ V = self.vector_space()
V1 = V.span_of_basis( (z**k).vector() for k in range(self.rank()) )
b = (V1.basis() + V1.complement().basis())
return V.span_of_basis(b)
else:
return self._rank
+ def vector_space(self):
+ """
+ Return the vector space that underlies this algebra.
+
+ EXAMPLES::
+
+ sage: J = RealSymmetricEJA(2)
+ sage: J.vector_space()
+ Vector space of dimension 3 over Rational Field
+
+ """
+ return self.zero().vector().parent().ambient_vector_space()
+
class Element(FiniteDimensionalAlgebraElement):
"""
sage: n = ZZ.random_element(1,10)
sage: J = JordanSpinEJA(n)
sage: x = J.random_element()
- sage: while x.is_zero():
+ sage: while not x.is_invertible():
....: x = J.random_element()
sage: x_vec = x.vector()
sage: x0 = x_vec[0]
sage: x_bar = x_vec[1:]
- sage: coeff = 1/(x0^2 - x_bar.inner_product(x_bar))
+ sage: coeff = ~(x0^2 - x_bar.inner_product(x_bar))
sage: inv_vec = x_vec.parent()([x0] + (-x_bar).list())
sage: x_inverse = coeff*inv_vec
sage: x.inverse() == J(x_inverse)
if not self.is_invertible():
raise ValueError("element is not invertible")
- P = self.parent()
- return P(self.quadratic_representation().inverse()*self.vector())
+ return (~self.quadratic_representation())(self)
def is_invertible(self):
"""
P = self.parent()
- return FiniteDimensionalEuclideanJordanAlgebraMorphism(
- Hom(P,P),
+ return FiniteDimensionalEuclideanJordanAlgebraOperator(
+ P,P,
self.operator_matrix() )
sage: bool(lhs == rhs)
True
+ Ensure that our operator's ``matrix`` method agrees with
+ this implementation::
+
+ sage: set_random_seed()
+ sage: J = random_eja()
+ sage: x = J.random_element()
+ sage: x.operator().matrix() == x.operator_matrix()
+ True
+
"""
fda_elt = FiniteDimensionalAlgebraElement(self.parent(), self)
return fda_elt.matrix().transpose()
sage: D = (x0^2 - x_bar.inner_product(x_bar))*D
sage: D = D + 2*x_bar.tensor_product(x_bar)
sage: Q = block_matrix(2,2,[A,B,C,D])
- sage: Q == x.quadratic_representation()
+ sage: Q == x.quadratic_representation().matrix()
True
Test all of the properties from Theorem 11.2 in Alizadeh::
sage: J = random_eja()
sage: x = J.random_element()
sage: y = J.random_element()
- sage: Lx = x.operator_matrix()
- sage: Lxx = (x*x).operator_matrix()
+ sage: Lx = x.operator()
+ sage: Lxx = (x*x).operator()
sage: Qx = x.quadratic_representation()
sage: Qy = y.quadratic_representation()
sage: Qxy = x.quadratic_representation(y)
sage: 2*Qxy == (x+y).quadratic_representation() - Qx - Qy
True
- Property 2:
+ Property 2 (multiply on the right for :trac:`28272`):
sage: alpha = QQ.random_element()
- sage: (alpha*x).quadratic_representation() == (alpha^2)*Qx
+ sage: (alpha*x).quadratic_representation() == Qx*(alpha^2)
True
Property 3:
- sage: not x.is_invertible() or (
- ....: Qx*x.inverse().vector() == x.vector() )
+ sage: not x.is_invertible() or ( Qx(x.inverse()) == x )
True
sage: not x.is_invertible() or (
- ....: Qx.inverse()
+ ....: ~Qx
....: ==
....: x.inverse().quadratic_representation() )
True
- sage: Qxy*(J.one().vector()) == (x*y).vector()
+ sage: Qxy(J.one()) == x*y
True
Property 4:
sage: not x.is_invertible() or (
....: x.quadratic_representation(x.inverse())*Qx
....: ==
- ....: 2*x.operator_matrix()*Qex - Qx )
+ ....: 2*x.operator()*Qex - Qx )
True
- sage: 2*x.operator_matrix()*Qex - Qx == Lxx
+ sage: 2*x.operator()*Qex - Qx == Lxx
True
Property 5:
- sage: J(Qy*x.vector()).quadratic_representation() == Qy*Qx*Qy
+ sage: Qy(x).quadratic_representation() == Qy*Qx*Qy
True
Property 6:
Property 7:
sage: not x.is_invertible() or (
- ....: Qx*x.inverse().operator_matrix() == Lx )
+ ....: Qx*x.inverse().operator() == Lx )
True
Property 8:
sage: not x.operator_commutes_with(y) or (
- ....: J(Qx*y.vector())^n == J(Qxn*(y^n).vector()) )
+ ....: Qx(y)^n == Qxn(y^n) )
True
"""
elif not other in self.parent():
raise TypeError("'other' must live in the same algebra")
- L = self.operator_matrix()
- M = other.operator_matrix()
- return ( L*M + M*L - (self*other).operator_matrix() )
+ L = self.operator()
+ M = other.operator()
+ return ( L*M + M*L - (self*other).operator() )
def span_of_powers(self):
#
# We do the extra ambient_vector_space() in case we're messing
# with polynomials and the direct parent is a module.
- V = self.vector().parent().ambient_vector_space()
+ V = self.parent().vector_space()
return V.span( (self**d).vector() for d in xrange(V.dimension()) )