- 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):
- """
-
- SETUP::
-
- sage: from mjo.eja.eja_algebra import RealSymmetricEJA
-
- EXAMPLES:
-
- The identity in `S^n` is converted to the identity in the EJA::
-
- sage: J = RealSymmetricEJA(3)
- sage: I = matrix.identity(QQ,3)
- sage: J(I) == J.one()
- True
-
- This skew-symmetric matrix can't be represented in the EJA::
-
- sage: J = RealSymmetricEJA(3)
- sage: A = matrix(QQ,3, lambda i,j: i-j)
- sage: J(A)
- Traceback (most recent call last):
- ...
- ArithmeticError: vector is not in free module
-
- """
- # Goal: if we're given a matrix, and if it lives in our
- # parent algebra's "natural ambient space," convert it
- # into an algebra element.
- #
- # The catch is, we make a recursive call after converting
- # the given matrix into a vector that lives in the algebra.
- # This we need to try the parent class initializer first,
- # to avoid recursing forever if we're given something that
- # already fits into the algebra, but also happens to live
- # in the parent's "natural ambient space" (this happens with
- # vectors in R^n).
- try:
- FiniteDimensionalAlgebraElement.__init__(self, A, elt)
- except ValueError:
- natural_basis = A.natural_basis()
- if elt in natural_basis[0].matrix_space():
- # Thanks for nothing! Matrix spaces aren't vector
- # spaces in Sage, so we have to figure out its
- # natural-basis coordinates ourselves.
- V = VectorSpace(elt.base_ring(), elt.nrows()**2)
- W = V.span( _mat2vec(s) for s in natural_basis )
- coords = W.coordinates(_mat2vec(elt))
- FiniteDimensionalAlgebraElement.__init__(self, A, coords)
-
- def __pow__(self, n):
- """
- Return ``self`` raised to the power ``n``.
-
- Jordan algebras are always power-associative; see for
- example Faraut and Koranyi, Proposition II.1.2 (ii).
-
- .. WARNING:
-
- We have to override this because our superclass uses row vectors
- 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()
- sage: x = random_eja().random_element()
- sage: x.operator()(x) == (x^2)
- True
-
- A few examples of power-associativity::
-
- sage: set_random_seed()
- sage: x = random_eja().random_element()
- sage: x*(x*x)*(x*x) == x^5
- True
- sage: (x*x)*(x*x*x) == x^5
- True
-
- We also know that powers operator-commute (Koecher, Chapter
- III, Corollary 1)::
-
- sage: set_random_seed()
- 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()
- sage: Lxn = (x^n).operator()
- sage: Lxm*Lxn == Lxn*Lxm
- True
-
- """
- if n == 0:
- return self.parent().one()
- elif n == 1:
- return self
- else:
- return (self.operator()**(n-1))(self)
-
-
- def apply_univariate_polynomial(self, p):
- """
- Apply the univariate polynomial ``p`` to this element.
-
- A priori, SageMath won't allow us to apply a univariate
- polynomial to an element of an EJA, because we don't know
- that EJAs are rings (they are usually not associative). Of
- course, we know that EJAs are power-associative, so the
- 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')
- sage: t = R.gen(0)
- sage: p = t^4 - t^3 + 5*t - 2
- sage: J = RealCartesianProductEJA(5)
- sage: J.one().apply_univariate_polynomial(p) == 3*J.one()
- True
-
- TESTS:
-
- We should always get back an element of the algebra::
-
- sage: set_random_seed()
- sage: p = PolynomialRing(QQ, 't').random_element()
- sage: J = random_eja()
- sage: x = J.random_element()
- sage: x.apply_univariate_polynomial(p) in J
- True
-
- """
- if len(p.variables()) > 1:
- raise ValueError("not a univariate polynomial")
- P = self.parent()
- R = P.base_ring()
- # Convert the coeficcients to the parent's base ring,
- # because a priori they might live in an (unnecessarily)
- # larger ring for which P.sum() would fail below.
- cs = [ R(c) for c in p.coefficients(sparse=False) ]
- return P.sum( cs[k]*(self**k) for k in range(len(cs)) )
-
-
- def characteristic_polynomial(self):
- """
- 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
- the identity element is `(t-1)` from which it follows that
- the characteristic polynomial should be `(t-1)^3`::
-
- sage: J = RealCartesianProductEJA(3)
- sage: J.one().characteristic_polynomial()
- t^3 - 3*t^2 + 3*t - 1
-
- Likewise, the characteristic of the zero element in the
- rank-three algebra `R^{n}` should be `t^{3}`::
-
- sage: J = RealCartesianProductEJA(3)
- sage: J.zero().characteristic_polynomial()
- t^3
-
- The characteristic polynomial of an element should evaluate
- to zero on that element::
-
- sage: set_random_seed()
- sage: x = RealCartesianProductEJA(3).random_element()
- sage: p = x.characteristic_polynomial()
- sage: x.apply_univariate_polynomial(p)
- 0
-
- """
- p = self.parent().characteristic_polynomial()
- return p(*self.vector())
-
-
- def inner_product(self, other):
- """
- 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
- inner product on `R^n` (this example only works because the
- basis for the Jordan algebra is the standard basis in `R^n`)::
-
- sage: J = JordanSpinEJA(3)
- sage: x = vector(QQ,[1,2,3])
- sage: y = vector(QQ,[4,5,6])
- sage: x.inner_product(y)
- 32
- sage: J(x).inner_product(J(y))
- 32
-
- The inner product on `S^n` is `<X,Y> = trace(X*Y)`, where
- multiplication is the usual matrix multiplication in `S^n`,
- so the inner product of the identity matrix with itself
- should be the `n`::
-
- sage: J = RealSymmetricEJA(3)
- sage: J.one().inner_product(J.one())
- 3
-
- Likewise, the inner product on `C^n` is `<X,Y> =
- Re(trace(X*Y))`, where we must necessarily take the real
- part because the product of Hermitian matrices may not be
- Hermitian::
-
- sage: J = ComplexHermitianEJA(3)
- sage: J.one().inner_product(J.one())
- 3
-
- Ditto for the quaternions::
-
- sage: J = QuaternionHermitianEJA(3)
- sage: J.one().inner_product(J.one())
- 3
-
- TESTS:
-
- Ensure that we can always compute an inner product, and that
- it gives us back a real number::
-
- sage: set_random_seed()
- sage: J = random_eja()
- sage: x = J.random_element()
- sage: y = J.random_element()
- sage: x.inner_product(y) in RR
- True
-
- """
- P = self.parent()
- if not other in P:
- raise TypeError("'other' must live in the same algebra")
-
- return P.inner_product(self, other)
-
-
- def operator_commutes_with(self, other):
- """
- 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
- operator-commutes with its square::
-
- sage: set_random_seed()
- sage: x = random_eja().random_element()
- sage: x.operator_commutes_with(x^2)
- True
-
- TESTS:
-
- Test Lemma 1 from Chapter III of Koecher::
-
- sage: set_random_seed()
- sage: J = random_eja()
- sage: u = J.random_element()
- sage: v = J.random_element()
- sage: lhs = u.operator_commutes_with(u*v)
- sage: rhs = v.operator_commutes_with(u^2)
- 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()
- B = other.operator()
- return (A*B == B*A)
-
-
- def det(self):
- """
- Return my determinant, the product of my eigenvalues.
-
- SETUP::
-
- sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
- ....: random_eja)
-
- EXAMPLES::
-
- sage: J = JordanSpinEJA(2)
- sage: e0,e1 = J.gens()
- sage: x = sum( J.gens() )
- sage: x.det()
- 0
-
- ::
-
- sage: J = JordanSpinEJA(3)
- sage: e0,e1,e2 = J.gens()
- sage: x = sum( J.gens() )
- sage: x.det()
- -1
-
- TESTS:
-
- An element is invertible if and only if its determinant is
- non-zero::
-
- sage: set_random_seed()
- sage: x = random_eja().random_element()
- sage: x.is_invertible() == (x.det() != 0)
- True
-
- """
- P = self.parent()
- r = P.rank()
- p = P._charpoly_coeff(0)
- # The _charpoly_coeff function already adds the factor of
- # -1 to ensure that _charpoly_coeff(0) is really what
- # appears in front of t^{0} in the charpoly. However,
- # we want (-1)^r times THAT for the determinant.
- return ((-1)**r)*p(*self.vector())
-
-
- def inverse(self):
- """
- Return the Jordan-multiplicative inverse of this element.
-
- ALGORITHM:
-
- 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
- Example 11.11::
-
- sage: set_random_seed()
- sage: n = ZZ.random_element(1,10)
- sage: J = JordanSpinEJA(n)
- sage: x = J.random_element()
- 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 = ~(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)
- True
-
- TESTS:
-
- The identity element is its own inverse::
-
- sage: set_random_seed()
- sage: J = random_eja()
- sage: J.one().inverse() == J.one()
- True
-
- If an element has an inverse, it acts like one::
-
- sage: set_random_seed()
- sage: J = random_eja()
- sage: x = J.random_element()
- sage: (not x.is_invertible()) or (x.inverse()*x == J.one())
- True
-
- 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
-
- The zero element is never invertible::
-
- sage: set_random_seed()
- sage: J = random_eja().zero().inverse()
- Traceback (most recent call last):
- ...
- ValueError: element is not invertible