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:
+
+ 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.
+
+ 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.
+
+ 4. Allows us to invert morphisms.
+
+ 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.
+ """
+
+ def __add__(self, other):
+ """
+ Add two EJA morphisms in the obvious way.
+
+ 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: 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
+
+ """
+ P = self.parent()
+ if not other in P:
+ raise ValueError("summands must live in the same space")
+
+ return FiniteDimensionalEuclideanJordanAlgebraMorphism(
+ P,
+ self.matrix() + other.matrix() )
+
+
+ def __init__(self, parent, f):
+ FiniteDimensionalAlgebraMorphism.__init__(self,
+ parent,
+ f.transpose(),
+ unitary=False,
+ check=False)
+
+
+ def __invert__(self):
+ """
+ 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: 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
+
+ """
+ A = self.matrix()
+ if not A.is_invertible():
+ raise ValueError("morphism is not invertible")
+
+ P = self.parent()
+ return FiniteDimensionalEuclideanJordanAlgebraMorphism(self.parent(),
+ A.inverse())
+
+ 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.
+
+ TESTS:
+
+ Ensure that we see the transpose of the underlying matrix object:
+
+ 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]
+
+ """
+ return "Morphism from {} to {} given by matrix\n{}".format(
+ self.domain(), self.codomain(), self.matrix())
+
+ def matrix(self):
+ """
+ Return the matrix of this morphism with respect to a left-action
+ on column vectors.
+ """
+ return FiniteDimensionalAlgebraMorphism.matrix(self).transpose()
+
class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra):
@staticmethod
An element of a Euclidean Jordan algebra.
"""
+ def __dir__(self):
+ """
+ Oh man, I should not be doing this. This hides the "disabled"
+ methods ``left_matrix`` and ``matrix`` from introspection;
+ in particular it removes them from tab-completion.
+ """
+ return filter(lambda s: s not in ['left_matrix', 'matrix'],
+ dir(self.__class__) )
+
+
def __init__(self, A, elt=None):
"""
EXAMPLES:
"""
Return the Jordan-multiplicative inverse of this element.
- We can't use the superclass method because it relies on the
- algebra being associative.
+ ALGORITHM:
+
+ We appeal to the quadratic representation as in Koecher's
+ Theorem 12 in Chapter III, Section 5.
EXAMPLES:
sage: (not x.is_invertible()) or (x.inverse()*x == J.one())
True
- """
- if self.parent().is_associative():
- elt = FiniteDimensionalAlgebraElement(self.parent(), self)
- return elt.inverse()
+ The inverse of the inverse is what we started with::
- # TODO: we can do better once the call to is_invertible()
- # doesn't crash on irregular elements.
- #if not self.is_invertible():
- # raise ValueError('element is not invertible')
+ sage: set_random_seed()
+ sage: J = random_eja()
+ sage: x = J.random_element()
+ sage: (not x.is_invertible()) or (x.inverse().inverse() == x)
+ True
- # We do this a little different than the usual recursive
- # call to a finite-dimensional algebra element, because we
- # wind up with an inverse that lives in the subalgebra and
- # we need information about the parent to convert it back.
- V = self.span_of_powers()
- assoc_subalg = self.subalgebra_generated_by()
- # Mis-design warning: the basis used for span_of_powers()
- # and subalgebra_generated_by() must be the same, and in
- # the same order!
- elt = assoc_subalg(V.coordinates(self.vector()))
+ The zero element is never invertible::
- # This will be in the subalgebra's coordinates...
- fda_elt = FiniteDimensionalAlgebraElement(assoc_subalg, elt)
- subalg_inverse = fda_elt.inverse()
+ sage: set_random_seed()
+ sage: J = random_eja().zero().inverse()
+ Traceback (most recent call last):
+ ...
+ ValueError: element is not invertible
- # So we have to convert back...
- basis = [ self.parent(v) for v in V.basis() ]
- pairs = zip(subalg_inverse.vector(), basis)
- return self.parent().linear_combination(pairs)
+ """
+ if not self.is_invertible():
+ raise ValueError("element is not invertible")
+
+ P = self.parent()
+ return P(self.quadratic_representation().inverse()*self.vector())
def is_invertible(self):
return self.span_of_powers().dimension()
+ def left_matrix(self):
+ """
+ Our parent class defines ``left_matrix`` and ``matrix``
+ methods whose names are misleading. We don't want them.
+ """
+ raise NotImplementedError("use operator_matrix() instead")
+
+ matrix = left_matrix
+
+
def minimal_polynomial(self):
"""
Return the minimal polynomial of this element,
return W.linear_combination(zip(self.vector(), B))
+ def operator(self):
+ """
+ Return the left-multiplication-by-this-element
+ operator on the ambient algebra.
+
+ TESTS::
+
+ sage: set_random_seed()
+ sage: J = random_eja()
+ sage: x = J.random_element()
+ sage: y = J.random_element()
+ sage: x.operator()(y) == x*y
+ True
+ sage: y.operator()(x) == x*y
+ True
+
+ """
+ P = self.parent()
+ return FiniteDimensionalEuclideanJordanAlgebraMorphism(
+ Hom(P,P),
+ self.operator_matrix() )
+
+
+
def operator_matrix(self):
"""
Return the matrix that represents left- (or right-)
multiplication by this element in the parent algebra.
- We have to override this because the superclass method
- returns a matrix that acts on row vectors (that is, on
- the right).
+ We implement this ourselves to work around the fact that
+ our parent class represents everything with row vectors.
EXAMPLES:
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: Qx = x.quadratic_representation()
+ sage: Qy = y.quadratic_representation()
+ sage: Qxy = x.quadratic_representation(y)
+ sage: Qex = J.one().quadratic_representation(x)
+ sage: n = ZZ.random_element(10)
+ sage: Qxn = (x^n).quadratic_representation()
Property 1:
- sage: actual = x.quadratic_representation(y)
- sage: expected = ( (x+y).quadratic_representation()
- ....: -x.quadratic_representation()
- ....: -y.quadratic_representation() ) / 2
- sage: actual == expected
+ sage: 2*Qxy == (x+y).quadratic_representation() - Qx - Qy
True
Property 2:
sage: alpha = QQ.random_element()
- sage: actual = (alpha*x).quadratic_representation()
- sage: expected = (alpha^2)*x.quadratic_representation()
- sage: actual == expected
+ sage: (alpha*x).quadratic_representation() == (alpha^2)*Qx
+ True
+
+ Property 3:
+
+ sage: not x.is_invertible() or (
+ ....: Qx*x.inverse().vector() == x.vector() )
+ True
+
+ sage: not x.is_invertible() or (
+ ....: Qx.inverse()
+ ....: ==
+ ....: x.inverse().quadratic_representation() )
+ True
+
+ sage: Qxy*(J.one().vector()) == (x*y).vector()
+ True
+
+ Property 4:
+
+ sage: not x.is_invertible() or (
+ ....: x.quadratic_representation(x.inverse())*Qx
+ ....: == Qx*x.quadratic_representation(x.inverse()) )
+ True
+
+ sage: not x.is_invertible() or (
+ ....: x.quadratic_representation(x.inverse())*Qx
+ ....: ==
+ ....: 2*x.operator_matrix()*Qex - Qx )
+ True
+
+ sage: 2*x.operator_matrix()*Qex - Qx == Lxx
True
Property 5:
- sage: Qy = y.quadratic_representation()
- sage: actual = J(Qy*x.vector()).quadratic_representation()
- sage: expected = Qy*x.quadratic_representation()*Qy
- sage: actual == expected
+ sage: J(Qy*x.vector()).quadratic_representation() == Qy*Qx*Qy
True
Property 6:
- sage: k = ZZ.random_element(1,10)
- sage: actual = (x^k).quadratic_representation()
- sage: expected = (x.quadratic_representation())^k
- sage: actual == expected
+ sage: Qxn == (Qx)^n
+ True
+
+ Property 7:
+
+ sage: not x.is_invertible() or (
+ ....: Qx*x.inverse().operator_matrix() == Lx )
+ True
+
+ Property 8:
+
+ sage: not x.operator_commutes_with(y) or (
+ ....: J(Qx*y.vector())^n == J(Qxn*(y^n).vector()) )
True
"""
TESTS::
sage: set_random_seed()
- sage: J = RealCartesianProductEJA(5)
- sage: c = J.random_element().subalgebra_idempotent()
- sage: c^2 == c
- True
- sage: J = JordanSpinEJA(5)
- sage: c = J.random_element().subalgebra_idempotent()
+ sage: J = random_eja()
+ sage: x = J.random_element()
+ sage: while x.is_nilpotent():
+ ....: x = J.random_element()
+ sage: c = x.subalgebra_idempotent()
sage: c^2 == c
True