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.map import Map
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
+
+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')
+
+
class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra):
@staticmethod
def __classcall_private__(cls,
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()
return fmt.format(self.degree(), self.base_ring())
+ def _a_regular_element(self):
+ """
+ Guess a regular element. Needed to compute the basis for our
+ characteristic polynomial coefficients.
+ """
+ gs = self.gens()
+ z = self.sum( (i+1)*gs[i] for i in range(len(gs)) )
+ if not z.is_regular():
+ raise ValueError("don't know a regular element")
+ return z
+
+
+ @cached_method
+ def _charpoly_basis_space(self):
+ """
+ Return the vector space spanned by the basis used in our
+ characteristic polynomial coefficients. This is used not only to
+ compute those coefficients, but also any time we need to
+ evaluate the coefficients (like when we compute the trace or
+ determinant).
+ """
+ z = self._a_regular_element()
+ 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)
+
@cached_method
def _charpoly_coeff(self, i):
store the trace/determinant (a_{r-1} and a_{0} respectively)
separate from the entire characteristic polynomial.
"""
- (A_of_x, x) = self._charpoly_matrix()
+ (A_of_x, x, xr, detA) = self._charpoly_matrix_system()
R = A_of_x.base_ring()
- A_cols = A_of_x.columns()
- A_cols[i] = (x**self.rank()).vector()
- numerator = column_matrix(A_of_x.base_ring(), A_cols).det()
- denominator = A_of_x.det()
+ if i >= self.rank():
+ # Guaranteed by theory
+ return R.zero()
+
+ # Danger: the in-place modification is done for performance
+ # reasons (reconstructing a matrix with huge polynomial
+ # entries is slow), but I don't know how cached_method works,
+ # so it's highly possible that we're modifying some global
+ # list variable by reference, here. In other words, you
+ # probably shouldn't call this method twice on the same
+ # algebra, at the same time, in two threads
+ Ai_orig = A_of_x.column(i)
+ A_of_x.set_column(i,xr)
+ numerator = A_of_x.det()
+ A_of_x.set_column(i,Ai_orig)
# We're relying on the theory here to ensure that each a_i is
# indeed back in R, and the added negative signs are to make
# the whole charpoly expression sum to zero.
- return R(-numerator/denominator)
+ return R(-numerator/detA)
@cached_method
- def _charpoly_matrix(self):
+ def _charpoly_matrix_system(self):
"""
Compute the matrix whose entries A_ij are polynomials in
- X1,...,XN. This same matrix is used in more than one method and
- it's not so fast to construct.
+ X1,...,XN, the vector ``x`` of variables X1,...,XN, the vector
+ corresponding to `x^r` and the determinent of the matrix A =
+ [A_ij]. In other words, all of the fixed (cachable) data needed
+ to compute the coefficients of the characteristic polynomial.
"""
r = self.rank()
n = self.dimension()
idmat = identity_matrix(J.base_ring(), n)
+ W = self._charpoly_basis_space()
+ W = W.change_ring(R.fraction_field())
+
+ # Starting with the standard coordinates x = (X1,X2,...,Xn)
+ # and then converting the entries to W-coordinates allows us
+ # to pass in the standard coordinates to the charpoly and get
+ # back the right answer. Specifically, with x = (X1,X2,...,Xn),
+ # we have
+ #
+ # W.coordinates(x^2) eval'd at (standard z-coords)
+ # =
+ # W-coords of (z^2)
+ # =
+ # W-coords of (standard coords of x^2 eval'd at std-coords of z)
+ #
+ # We want the middle equivalent thing in our matrix, but use
+ # the first equivalent thing instead so that we can pass in
+ # standard coordinates.
x = J(vector(R, R.gens()))
- l1 = [column_matrix((x**k).vector()) for k in range(r)]
+ l1 = [column_matrix(W.coordinates((x**k).vector())) for k in range(r)]
l2 = [idmat.column(k-1).column() for k in range(r+1, n+1)]
A_of_x = block_matrix(R, 1, n, (l1 + l2))
- return (A_of_x, x)
+ xr = W.coordinates((x**r).vector())
+ return (A_of_x, x, xr, A_of_x.det())
@cached_method
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):
"""
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:
sage: set_random_seed()
sage: x = random_eja().random_element()
- sage: x.operator_matrix()*x.vector() == (x^2).vector()
+ sage: x.operator()(x) == (x^2)
True
A few examples of power-associativity::
sage: x = random_eja().random_element()
sage: m = ZZ.random_element(0,10)
sage: n = ZZ.random_element(0,10)
- sage: Lxm = (x^m).operator_matrix()
- sage: Lxn = (x^n).operator_matrix()
+ sage: Lxm = (x^m).operator()
+ sage: Lxn = (x^n).operator()
sage: Lxm*Lxn == Lxn*Lxm
True
"""
- A = self.parent()
if n == 0:
- return A.one()
+ return self.parent().one()
elif n == 1:
return self
else:
- return A( (self.operator_matrix()**(n-1))*self.vector() )
+ return (self.operator()**(n-1))(self)
def apply_univariate_polynomial(self, p):
sage: lhs == rhs
True
+ Test the first polarization identity from my notes, Koecher Chapter
+ III, or from Baes (2.3)::
+
+ sage: set_random_seed()
+ sage: J = random_eja()
+ sage: x = J.random_element()
+ sage: y = J.random_element()
+ sage: Lx = x.operator()
+ sage: Ly = y.operator()
+ sage: Lxx = (x*x).operator()
+ sage: Lxy = (x*y).operator()
+ sage: bool(2*Lx*Lxy + Ly*Lxx == 2*Lxy*Lx + Lxx*Ly)
+ True
+
+ Test the second polarization identity from my notes or from
+ Baes (2.4)::
+
+ sage: set_random_seed()
+ sage: J = random_eja()
+ sage: x = J.random_element()
+ sage: y = J.random_element()
+ sage: z = J.random_element()
+ sage: Lx = x.operator()
+ sage: Ly = y.operator()
+ sage: Lz = z.operator()
+ sage: Lzy = (z*y).operator()
+ sage: Lxy = (x*y).operator()
+ sage: Lxz = (x*z).operator()
+ sage: bool(Lx*Lzy + Lz*Lxy + Ly*Lxz == Lzy*Lx + Lxy*Lz + Lxz*Ly)
+ True
+
+ Test the third polarization identity from my notes or from
+ Baes (2.5)::
+
+ sage: set_random_seed()
+ sage: J = random_eja()
+ sage: u = J.random_element()
+ sage: y = J.random_element()
+ sage: z = J.random_element()
+ sage: Lu = u.operator()
+ sage: Ly = y.operator()
+ sage: Lz = z.operator()
+ sage: Lzy = (z*y).operator()
+ sage: Luy = (u*y).operator()
+ sage: Luz = (u*z).operator()
+ sage: Luyz = (u*(y*z)).operator()
+ sage: lhs = Lu*Lzy + Lz*Luy + Ly*Luz
+ sage: rhs = Luyz + Ly*Lu*Lz + Lz*Lu*Ly
+ sage: bool(lhs == rhs)
+ True
+
"""
if not other in self.parent():
raise TypeError("'other' must live in the same algebra")
- A = self.operator_matrix()
- B = other.operator_matrix()
+ A = self.operator()
+ B = other.operator()
return (A*B == B*A)
"""
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: 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)
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::
+
+ sage: set_random_seed()
+ sage: J = random_eja()
+ sage: x = J.random_element()
+ sage: (not x.is_invertible()) or (x.inverse().inverse() == x)
+ True
- # 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')
+ The zero element is never invertible::
- # 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()))
+ sage: set_random_seed()
+ sage: J = random_eja().zero().inverse()
+ Traceback (most recent call last):
+ ...
+ ValueError: element is not invertible
- # This will be in the subalgebra's coordinates...
- fda_elt = FiniteDimensionalAlgebraElement(assoc_subalg, elt)
- subalg_inverse = fda_elt.inverse()
+ """
+ if not self.is_invertible():
+ raise 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)
+ return (~self.quadratic_representation())(self)
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,
# and subalgebra_generated_by() must be the same, and in
# the same order!
elt = assoc_subalg(V.coordinates(self.vector()))
+ return elt.operator().minimal_polynomial()
- # We get back a symbolic polynomial in 'x' but want a real
- # polynomial in 't'.
- p_of_x = elt.operator_matrix().minimal_polynomial()
- return p_of_x.change_variable_name('t')
def natural_representation(self):
return W.linear_combination(zip(self.vector(), B))
- def operator_matrix(self):
+ def operator(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).
+ Return the left-multiplication-by-this-element
+ operator on the ambient algebra.
- EXAMPLES:
-
- Test the first polarization identity from my notes, Koecher Chapter
- III, or from Baes (2.3)::
+ TESTS::
sage: set_random_seed()
sage: J = random_eja()
sage: x = J.random_element()
sage: y = J.random_element()
- sage: Lx = x.operator_matrix()
- sage: Ly = y.operator_matrix()
- sage: Lxx = (x*x).operator_matrix()
- sage: Lxy = (x*y).operator_matrix()
- sage: bool(2*Lx*Lxy + Ly*Lxx == 2*Lxy*Lx + Lxx*Ly)
+ sage: x.operator()(y) == x*y
True
-
- Test the second polarization identity from my notes or from
- Baes (2.4)::
-
- sage: set_random_seed()
- sage: J = random_eja()
- sage: x = J.random_element()
- sage: y = J.random_element()
- sage: z = J.random_element()
- sage: Lx = x.operator_matrix()
- sage: Ly = y.operator_matrix()
- sage: Lz = z.operator_matrix()
- sage: Lzy = (z*y).operator_matrix()
- sage: Lxy = (x*y).operator_matrix()
- sage: Lxz = (x*z).operator_matrix()
- sage: bool(Lx*Lzy + Lz*Lxy + Ly*Lxz == Lzy*Lx + Lxy*Lz + Lxz*Ly)
- True
-
- Test the third polarization identity from my notes or from
- Baes (2.5)::
-
- sage: set_random_seed()
- sage: J = random_eja()
- sage: u = J.random_element()
- sage: y = J.random_element()
- sage: z = J.random_element()
- sage: Lu = u.operator_matrix()
- sage: Ly = y.operator_matrix()
- sage: Lz = z.operator_matrix()
- sage: Lzy = (z*y).operator_matrix()
- sage: Luy = (u*y).operator_matrix()
- sage: Luz = (u*z).operator_matrix()
- sage: Luyz = (u*(y*z)).operator_matrix()
- sage: lhs = Lu*Lzy + Lz*Luy + Ly*Luz
- sage: rhs = Luyz + Ly*Lu*Lz + Lz*Lu*Ly
- sage: bool(lhs == rhs)
+ sage: y.operator()(x) == x*y
True
"""
- fda_elt = FiniteDimensionalAlgebraElement(self.parent(), self)
- return fda_elt.matrix().transpose()
+ P = self.parent()
+ fda_elt = FiniteDimensionalAlgebraElement(P, self)
+ return FiniteDimensionalEuclideanJordanAlgebraOperator(
+ P,
+ P,
+ fda_elt.matrix().transpose() )
def quadratic_representation(self, other=None):
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()
+ sage: Lxx = (x*x).operator()
+ 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:
+ Property 2 (multiply on the right for :trac:`28272`):
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() == Qx*(alpha^2)
+ True
+
+ Property 3:
+
+ sage: not x.is_invertible() or ( Qx(x.inverse()) == x )
+ True
+
+ sage: not x.is_invertible() or (
+ ....: ~Qx
+ ....: ==
+ ....: x.inverse().quadratic_representation() )
+ True
+
+ sage: Qxy(J.one()) == x*y
+ 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()*Qex - Qx )
+ True
+
+ sage: 2*x.operator()*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: Qy(x).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() == Lx )
+ True
+
+ Property 8:
+
+ sage: not x.operator_commutes_with(y) or (
+ ....: 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()) )
sage: x.subalgebra_generated_by().is_associative()
True
- Squaring in the subalgebra should be the same thing as
- squaring in the superalgebra::
+ Squaring in the subalgebra should work the same as in
+ the superalgebra::
sage: set_random_seed()
sage: x = random_eja().random_element()
sage: u = x.subalgebra_generated_by().random_element()
- sage: u.operator_matrix()*u.vector() == (u**2).vector()
+ sage: u.operator()(u) == u^2
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
s = 0
minimal_dim = V.dimension()
for i in xrange(1, V.dimension()):
- this_dim = (u**i).operator_matrix().image().dimension()
+ this_dim = (u**i).operator().matrix().image().dimension()
if this_dim < minimal_dim:
minimal_dim = this_dim
s = i
# Beware, solve_right() means that we're using COLUMN vectors.
# Our FiniteDimensionalAlgebraElement superclass uses rows.
u_next = u**(s+1)
- A = u_next.operator_matrix()
+ A = u_next.operator().matrix()
c_coordinates = A.solve_right(u_next.vector())
# Now c_coordinates is the idempotent we want, but it's in