from sage.modules.free_module import VectorSpace
from sage.modules.with_basis.indexed_element import IndexedFreeModuleElement
-# TODO: make this unnecessary somehow.
-from sage.misc.lazy_import import lazy_import
-lazy_import('mjo.eja.eja_algebra', 'FiniteDimensionalEuclideanJordanAlgebra')
-lazy_import('mjo.eja.eja_subalgebra',
- 'FiniteDimensionalEuclideanJordanElementSubalgebra')
from mjo.eja.eja_operator import FiniteDimensionalEuclideanJordanAlgebraOperator
from mjo.eja.eja_utils import _mat2vec
Return ``self`` raised to the power ``n``.
Jordan algebras are always power-associative; see for
- example Faraut and Koranyi, Proposition II.1.2 (ii).
+ example Faraut and Korányi, Proposition II.1.2 (ii).
We have to override this because our superclass uses row
vectors instead of column vectors! We, on the other hand,
elif n == 1:
return self
else:
- return (self.operator()**(n-1))(self)
+ return (self**(n-1))*self
def apply_univariate_polynomial(self, p):
SETUP::
- sage: from mjo.eja.eja_algebra import (RealCartesianProductEJA,
+ sage: from mjo.eja.eja_algebra import (HadamardEJA,
....: 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 = HadamardEJA(5)
sage: J.one().apply_univariate_polynomial(p) == 3*J.one()
True
We should always get back an element of the algebra::
sage: set_random_seed()
- sage: p = PolynomialRing(QQ, 't').random_element()
+ sage: p = PolynomialRing(AA, 't').random_element()
sage: J = random_eja()
sage: x = J.random_element()
sage: x.apply_univariate_polynomial(p) in J
SETUP::
- sage: from mjo.eja.eja_algebra import RealCartesianProductEJA
+ sage: from mjo.eja.eja_algebra import HadamardEJA
EXAMPLES:
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 = HadamardEJA(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 = HadamardEJA(3)
sage: J.zero().characteristic_polynomial()
t^3
to zero on that element::
sage: set_random_seed()
- sage: x = RealCartesianProductEJA(3).random_element()
+ sage: x = HadamardEJA(3).random_element()
sage: p = x.characteristic_polynomial()
sage: x.apply_univariate_polynomial(p)
0
+ The characteristic polynomials of the zero and unit elements
+ should be what we think they are in a subalgebra, too::
+
+ sage: J = HadamardEJA(3)
+ sage: p1 = J.one().characteristic_polynomial()
+ sage: q1 = J.zero().characteristic_polynomial()
+ sage: e0,e1,e2 = J.gens()
+ sage: A = (e0 + 2*e1 + 3*e2).subalgebra_generated_by() # dim 3
+ sage: p2 = A.one().characteristic_polynomial()
+ sage: q2 = A.zero().characteristic_polynomial()
+ sage: p1 == p2
+ True
+ sage: q1 == q2
+ True
+
"""
- p = self.parent().characteristic_polynomial()
+ p = self.parent().characteristic_polynomial_of()
return p(*self.to_vector())
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
+ sage: x,y = J.random_elements(2)
+ sage: x.inner_product(y) in RLF
True
"""
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: u,v = random_eja().random_elements(2)
sage: lhs = u.operator_commutes_with(u*v)
sage: rhs = v.operator_commutes_with(u^2)
sage: lhs == rhs
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: x,y = random_eja().random_elements(2)
sage: Lx = x.operator()
sage: Ly = y.operator()
sage: Lxx = (x*x).operator()
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: x,y,z = random_eja().random_elements(3)
sage: Lx = x.operator()
sage: Ly = y.operator()
sage: Lz = z.operator()
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: u,y,z = random_eja().random_elements(3)
sage: Lu = u.operator()
sage: Ly = y.operator()
sage: Lz = z.operator()
SETUP::
sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
+ ....: TrivialEJA,
....: random_eja)
EXAMPLES::
sage: x.det()
-1
+ The determinant of the sole element in the rank-zero trivial
+ algebra is ``1``, by three paths of reasoning. First, its
+ characteristic polynomial is a constant ``1``, so the constant
+ term in that polynomial is ``1``. Second, the characteristic
+ polynomial evaluated at zero is again ``1``. And finally, the
+ (empty) product of its eigenvalues is likewise just unity::
+
+ sage: J = TrivialEJA()
+ sage: J.zero().det()
+ 1
+
TESTS:
An element is invertible if and only if its determinant is
sage: x.is_invertible() == (x.det() != 0)
True
+ Ensure that the determinant is multiplicative on an associative
+ subalgebra as in Faraut and Korányi's Proposition II.2.2::
+
+ sage: set_random_seed()
+ sage: J = random_eja().random_element().subalgebra_generated_by()
+ sage: x,y = J.random_elements(2)
+ sage: (x*y).det() == x.det()*y.det()
+ 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.
+
+ if r == 0:
+ # Special case, since we don't get the a0=1
+ # coefficient when the rank of the algebra
+ # is zero.
+ return P.base_ring().one()
+
+ p = P._charpoly_coefficients()[0]
+ # The _charpoly_coeff function already adds the factor of -1
+ # to ensure that _charpoly_coefficients()[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.to_vector())
SETUP::
- sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
+ sage: from mjo.eja.eja_algebra import (ComplexHermitianEJA,
+ ....: JordanSpinEJA,
....: random_eja)
EXAMPLES:
Example 11.11::
sage: set_random_seed()
- sage: n = ZZ.random_element(1,10)
- sage: J = JordanSpinEJA(n)
+ sage: J = JordanSpinEJA.random_instance()
sage: x = J.random_element()
sage: while not x.is_invertible():
....: x = J.random_element()
sage: x_vec = x.to_vector()
- sage: x0 = x_vec[0]
+ sage: x0 = x_vec[:1]
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: coeff = x0.inner_product(x0) - x_bar.inner_product(x_bar)
+ sage: x_inverse = x_vec.parent()(x0.list() + (-x_bar).list())
+ sage: if not coeff.is_zero(): x_inverse = x_inverse/coeff
sage: x.inverse() == J.from_vector(x_inverse)
True
+ Trying to invert a non-invertible element throws an error:
+
+ sage: JordanSpinEJA(3).zero().inverse()
+ Traceback (most recent call last):
+ ...
+ ValueError: element is not invertible
+
TESTS:
The identity element is its own inverse::
sage: (not x.is_invertible()) or (x.inverse().inverse() == x)
True
- The zero element is never invertible::
+ Proposition II.2.3 in Faraut and Korányi says that the inverse
+ of an element is the inverse of its left-multiplication operator
+ applied to the algebra's identity, when that inverse exists::
sage: set_random_seed()
- sage: J = random_eja().zero().inverse()
- Traceback (most recent call last):
- ...
- ValueError: element is not invertible
+ sage: J = random_eja()
+ sage: x = J.random_element()
+ sage: (not x.operator().is_invertible()) or (
+ ....: x.operator().inverse()(J.one()) == x.inverse() )
+ True
+
+ Proposition II.2.4 in Faraut and Korányi gives a formula for
+ the inverse based on the characteristic polynomial and the
+ Cayley-Hamilton theorem for Euclidean Jordan algebras::
+
+ sage: set_random_seed()
+ sage: J = ComplexHermitianEJA(3)
+ sage: x = J.random_element()
+ sage: while not x.is_invertible():
+ ....: x = J.random_element()
+ sage: r = J.rank()
+ sage: a = x.characteristic_polynomial().coefficients(sparse=False)
+ sage: expected = (-1)^(r+1)/x.det()
+ sage: expected *= sum( a[i+1]*x^i for i in range(r) )
+ sage: x.inverse() == expected
+ True
"""
if not self.is_invertible():
raise ValueError("element is not invertible")
+ if self.parent()._charpoly_coefficients.is_in_cache():
+ # We can invert using our charpoly if it will be fast to
+ # compute. If the coefficients are cached, our rank had
+ # better be too!
+ r = self.parent().rank()
+ a = self.characteristic_polynomial().coefficients(sparse=False)
+ return (-1)**(r+1)*sum(a[i+1]*self**i for i in range(r))/self.det()
+
return (~self.quadratic_representation())(self)
whether or not the paren't algebra's zero element is a root
of this element's minimal polynomial.
+ That is... unless the coefficients of our algebra's
+ "characteristic polynomial of" function are already cached!
+ In that case, we just use the determinant (which will be fast
+ as a result).
+
Beware that we can't use the superclass method, because it
relies on the algebra being associative.
sage: J.one().is_invertible()
True
- The zero element is never invertible::
+ The zero element is never invertible in a non-trivial algebra::
sage: set_random_seed()
sage: J = random_eja()
- sage: J.zero().is_invertible()
+ sage: (not J.is_trivial()) and J.zero().is_invertible()
False
"""
+ if self.is_zero():
+ if self.parent().is_trivial():
+ return True
+ else:
+ return False
+
+ if self.parent()._charpoly_coefficients.is_in_cache():
+ # The determinant will be quicker than computing the minimal
+ # polynomial from scratch, most likely.
+ return (not self.det().is_zero())
+
# In fact, we only need to know if the constant term is non-zero,
# so we can pass in the field's zero element instead.
zero = self.base_ring().zero()
return not (p(zero) == zero)
+ def is_primitive_idempotent(self):
+ """
+ Return whether or not this element is a primitive (or minimal)
+ idempotent.
+
+ A primitive idempotent is a non-zero idempotent that is not
+ the sum of two other non-zero idempotents. Remark 2.7.15 in
+ Baes shows that this is what he refers to as a "minimal
+ idempotent."
+
+ An element of a Euclidean Jordan algebra is a minimal idempotent
+ if it :meth:`is_idempotent` and if its Peirce subalgebra
+ corresponding to the eigenvalue ``1`` has dimension ``1`` (Baes,
+ Proposition 2.7.17).
+
+ SETUP::
+
+ sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
+ ....: RealSymmetricEJA,
+ ....: TrivialEJA,
+ ....: random_eja)
+
+ WARNING::
+
+ This method is sloooooow.
+
+ EXAMPLES:
+
+ The spectral decomposition of a non-regular element should always
+ contain at least one non-minimal idempotent::
+
+ sage: J = RealSymmetricEJA(3)
+ sage: x = sum(J.gens())
+ sage: x.is_regular()
+ False
+ sage: [ c.is_primitive_idempotent()
+ ....: for (l,c) in x.spectral_decomposition() ]
+ [False, True]
+
+ On the other hand, the spectral decomposition of a regular
+ element should always be in terms of minimal idempotents::
+
+ sage: J = JordanSpinEJA(4)
+ sage: x = sum( i*J.gens()[i] for i in range(len(J.gens())) )
+ sage: x.is_regular()
+ True
+ sage: [ c.is_primitive_idempotent()
+ ....: for (l,c) in x.spectral_decomposition() ]
+ [True, True]
+
+ TESTS:
+
+ The identity element is minimal only in an EJA of rank one::
+
+ sage: set_random_seed()
+ sage: J = random_eja()
+ sage: J.rank() == 1 or not J.one().is_primitive_idempotent()
+ True
+
+ A non-idempotent cannot be a minimal idempotent::
+
+ sage: set_random_seed()
+ sage: J = JordanSpinEJA(4)
+ sage: x = J.random_element()
+ sage: (not x.is_idempotent()) and x.is_primitive_idempotent()
+ False
+
+ Proposition 2.7.19 in Baes says that an element is a minimal
+ idempotent if and only if it's idempotent with trace equal to
+ unity::
+
+ sage: set_random_seed()
+ sage: J = JordanSpinEJA(4)
+ sage: x = J.random_element()
+ sage: expected = (x.is_idempotent() and x.trace() == 1)
+ sage: actual = x.is_primitive_idempotent()
+ sage: actual == expected
+ True
+
+ Primitive idempotents must be non-zero::
+
+ sage: set_random_seed()
+ sage: J = random_eja()
+ sage: J.zero().is_idempotent()
+ True
+ sage: J.zero().is_primitive_idempotent()
+ False
+
+ As a consequence of the fact that primitive idempotents must
+ be non-zero, there are no primitive idempotents in a trivial
+ Euclidean Jordan algebra::
+
+ sage: J = TrivialEJA()
+ sage: J.one().is_idempotent()
+ True
+ sage: J.one().is_primitive_idempotent()
+ False
+
+ """
+ if not self.is_idempotent():
+ return False
+
+ if self.is_zero():
+ return False
+
+ (_,_,J1) = self.parent().peirce_decomposition(self)
+ return (J1.dimension() == 1)
+
+
def is_nilpotent(self):
"""
Return whether or not some power of this element is zero.
TESTS:
- The identity element is never nilpotent::
+ The identity element is never nilpotent, except in a trivial EJA::
sage: set_random_seed()
- sage: random_eja().one().is_nilpotent()
+ sage: J = random_eja()
+ sage: J.one().is_nilpotent() and not J.is_trivial()
False
The additive identity is always nilpotent::
TESTS:
The zero element should never be regular, unless the parent
- algebra has dimension one::
+ algebra has dimension less than or equal to one::
sage: set_random_seed()
sage: J = random_eja()
- sage: J.dimension() == 1 or not J.zero().is_regular()
+ sage: J.dimension() <= 1 or not J.zero().is_regular()
True
The unit element isn't regular unless the algebra happens to
sage: set_random_seed()
sage: J = random_eja()
- sage: J.dimension() == 1 or not J.one().is_regular()
+ sage: J.dimension() <= 1 or not J.one().is_regular()
True
"""
aren't multiples of the identity are regular::
sage: set_random_seed()
- sage: n = ZZ.random_element(1,10)
- sage: J = JordanSpinEJA(n)
+ sage: J = JordanSpinEJA.random_instance()
+ sage: n = J.dimension()
sage: x = J.random_element()
- sage: x == x.coefficient(0)*J.one() or x.degree() == 2
+ sage: x.degree() == min(n,2) or (x == x.coefficient(0)*J.one())
True
TESTS:
- The zero and unit elements are both of degree one::
+ The zero and unit elements are both of degree one in nontrivial
+ algebras::
sage: set_random_seed()
sage: J = random_eja()
- sage: J.zero().degree()
- 1
- sage: J.one().degree()
- 1
+ sage: d = J.zero().degree()
+ sage: (J.is_trivial() and d == 0) or d == 1
+ True
+ sage: d = J.one().degree()
+ sage: (J.is_trivial() and d == 0) or d == 1
+ True
Our implementation agrees with the definition::
True
"""
+ if self.is_zero() and not self.parent().is_trivial():
+ # The minimal polynomial of zero in a nontrivial algebra
+ # is "t"; in a trivial algebra it's "1" by convention
+ # (it's an empty product).
+ return 1
return self.subalgebra_generated_by().dimension()
SETUP::
sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
+ ....: RealSymmetricEJA,
+ ....: TrivialEJA,
....: random_eja)
+ EXAMPLES:
+
+ Keeping in mind that the polynomial ``1`` evaluates the identity
+ element (also the zero element) of the trivial algebra, it is clear
+ that the polynomial ``1`` is the minimal polynomial of the only
+ element in a trivial algebra::
+
+ sage: J = TrivialEJA()
+ sage: J.one().minimal_polynomial()
+ 1
+ sage: J.zero().minimal_polynomial()
+ 1
+
TESTS:
The minimal polynomial of the identity and zero elements are
- always the same::
+ always the same, except in trivial algebras where the minimal
+ polynomial of the unit/zero element is ``1``::
sage: set_random_seed()
sage: J = random_eja()
- sage: J.one().minimal_polynomial()
+ sage: mu = J.one().minimal_polynomial()
+ sage: t = mu.parent().gen()
+ sage: mu + int(J.is_trivial())*(t-2)
t - 1
- sage: J.zero().minimal_polynomial()
+ sage: mu = J.zero().minimal_polynomial()
+ sage: t = mu.parent().gen()
+ sage: mu + int(J.is_trivial())*(t-1)
t
The degree of an element is (by one definition) the degree
The minimal polynomial and the characteristic polynomial coincide
and are known (see Alizadeh, Example 11.11) for all elements of
the spin factor algebra that aren't scalar multiples of the
- identity::
+ identity. We require the dimension of the algebra to be at least
+ two here so that said elements actually exist::
sage: set_random_seed()
- sage: n = ZZ.random_element(2,10)
+ sage: n_max = max(2, JordanSpinEJA._max_random_instance_size())
+ sage: n = ZZ.random_element(2, n_max)
sage: J = JordanSpinEJA(n)
sage: y = J.random_element()
sage: while y == y.coefficient(0)*J.one():
sage: x.apply_univariate_polynomial(p)
0
+ The minimal polynomial is invariant under a change of basis,
+ and in particular, a re-scaling of the basis::
+
+ sage: set_random_seed()
+ sage: n_max = RealSymmetricEJA._max_random_instance_size()
+ sage: n = ZZ.random_element(1, n_max)
+ sage: J1 = RealSymmetricEJA(n)
+ sage: J2 = RealSymmetricEJA(n,normalize_basis=False)
+ sage: X = random_matrix(AA,n)
+ sage: X = X*X.transpose()
+ sage: x1 = J1(X)
+ sage: x2 = J2(X)
+ sage: x1.minimal_polynomial() == x2.minimal_polynomial()
+ True
+
"""
- A = self.subalgebra_generated_by()
+ if self.is_zero():
+ # We would generate a zero-dimensional subalgebra
+ # where the minimal polynomial would be constant.
+ # That might be correct, but only if *this* algebra
+ # is trivial too.
+ if not self.parent().is_trivial():
+ # Pretty sure we know what the minimal polynomial of
+ # the zero operator is going to be. This ensures
+ # consistency of e.g. the polynomial variable returned
+ # in the "normal" case without us having to think about it.
+ return self.operator().minimal_polynomial()
+
+ A = self.subalgebra_generated_by(orthonormalize_basis=False)
return A(self).operator().minimal_polynomial()
- def natural_representation(self):
+ def to_matrix(self):
"""
- Return a more-natural representation of this element.
+ Return an (often more natural) representation of this element as a
+ matrix.
- Every finite-dimensional Euclidean Jordan Algebra is a
- direct sum of five simple algebras, four of which comprise
- Hermitian matrices. This method returns the original
- "natural" representation of this element as a Hermitian
- matrix, if it has one. If not, you get the usual representation.
+ Every finite-dimensional Euclidean Jordan Algebra is a direct
+ sum of five simple algebras, four of which comprise Hermitian
+ matrices. This method returns a "natural" matrix
+ representation of this element as either a Hermitian matrix or
+ column vector.
SETUP::
sage: J = ComplexHermitianEJA(3)
sage: J.one()
e0 + e3 + e8
- sage: J.one().natural_representation()
+ sage: J.one().to_matrix()
[1 0 0 0 0 0]
[0 1 0 0 0 0]
[0 0 1 0 0 0]
sage: J = QuaternionHermitianEJA(3)
sage: J.one()
e0 + e5 + e14
- sage: J.one().natural_representation()
+ sage: J.one().to_matrix()
[1 0 0 0 0 0 0 0 0 0 0 0]
[0 1 0 0 0 0 0 0 0 0 0 0]
[0 0 1 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 1 0 0]
[0 0 0 0 0 0 0 0 0 0 1 0]
[0 0 0 0 0 0 0 0 0 0 0 1]
+ """
+ B = self.parent().matrix_basis()
+ W = self.parent().matrix_space()
+ # This is just a manual "from_vector()", but of course
+ # matrix spaces aren't vector spaces in sage, so they
+ # don't have a from_vector() method.
+ return W.linear_combination( zip(B, self.to_vector()) )
+
+
+ def norm(self):
"""
- B = self.parent().natural_basis()
- W = B[0].matrix_space()
- return W.linear_combination(zip(B,self.to_vector()))
+ The norm of this element with respect to :meth:`inner_product`.
+
+ SETUP::
+
+ sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
+ ....: HadamardEJA)
+
+ EXAMPLES::
+
+ sage: J = HadamardEJA(2)
+ sage: x = sum(J.gens())
+ sage: x.norm()
+ 1.414213562373095?
+
+ ::
+
+ sage: J = JordanSpinEJA(4)
+ sage: x = sum(J.gens())
+ sage: x.norm()
+ 2
+
+ """
+ return self.inner_product(self).sqrt()
def operator(self):
sage: set_random_seed()
sage: J = random_eja()
- sage: x = J.random_element()
- sage: y = J.random_element()
+ sage: x,y = J.random_elements(2)
sage: x.operator()(y) == x*y
True
sage: y.operator()(x) == x*y
Alizadeh's Example 11.12::
sage: set_random_seed()
- sage: n = ZZ.random_element(1,10)
- sage: J = JordanSpinEJA(n)
- sage: x = J.random_element()
+ sage: x = JordanSpinEJA.random_instance().random_element()
sage: x_vec = x.to_vector()
- sage: x0 = x_vec[0]
- sage: x_bar = x_vec[1:]
- sage: A = matrix(QQ, 1, [x_vec.inner_product(x_vec)])
- sage: B = 2*x0*x_bar.row()
- sage: C = 2*x0*x_bar.column()
- sage: D = matrix.identity(QQ, n-1)
- sage: D = (x0^2 - x_bar.inner_product(x_bar))*D
- sage: D = D + 2*x_bar.tensor_product(x_bar)
- sage: Q = matrix.block(2,2,[A,B,C,D])
+ sage: Q = matrix.identity(x.base_ring(), 0)
+ sage: n = x_vec.degree()
+ sage: if n > 0:
+ ....: x0 = x_vec[0]
+ ....: x_bar = x_vec[1:]
+ ....: A = matrix(x.base_ring(), 1, [x_vec.inner_product(x_vec)])
+ ....: B = 2*x0*x_bar.row()
+ ....: C = 2*x0*x_bar.column()
+ ....: D = matrix.identity(x.base_ring(), n-1)
+ ....: D = (x0^2 - x_bar.inner_product(x_bar))*D
+ ....: D = D + 2*x_bar.tensor_product(x_bar)
+ ....: Q = matrix.block(2,2,[A,B,C,D])
sage: Q == x.quadratic_representation().matrix()
True
sage: set_random_seed()
sage: J = random_eja()
- sage: x = J.random_element()
- sage: y = J.random_element()
+ sage: x,y = J.random_elements(2)
sage: Lx = x.operator()
sage: Lxx = (x*x).operator()
sage: Qx = x.quadratic_representation()
Property 2 (multiply on the right for :trac:`28272`):
- sage: alpha = QQ.random_element()
+ sage: alpha = J.base_ring().random_element()
sage: (alpha*x).quadratic_representation() == Qx*(alpha^2)
True
sage: not x.is_invertible() or (
....: x.quadratic_representation(x.inverse())*Qx
....: ==
- ....: 2*x.operator()*Qex - Qx )
+ ....: 2*Lx*Qex - Qx )
True
- sage: 2*x.operator()*Qex - Qx == Lxx
+ sage: 2*Lx*Qex - Qx == Lxx
True
Property 5:
+ def spectral_decomposition(self):
+ """
+ Return the unique spectral decomposition of this element.
+
+ ALGORITHM:
+
+ Following Faraut and Korányi's Theorem III.1.1, we restrict this
+ element's left-multiplication-by operator to the subalgebra it
+ generates. We then compute the spectral decomposition of that
+ operator, and the spectral projectors we get back must be the
+ left-multiplication-by operators for the idempotents we
+ seek. Thus applying them to the identity element gives us those
+ idempotents.
+
+ Since the eigenvalues are required to be distinct, we take
+ the spectral decomposition of the zero element to be zero
+ times the identity element of the algebra (which is idempotent,
+ obviously).
+
+ SETUP::
+
+ sage: from mjo.eja.eja_algebra import RealSymmetricEJA
+
+ EXAMPLES:
+
+ The spectral decomposition of the identity is ``1`` times itself,
+ and the spectral decomposition of zero is ``0`` times the identity::
+
+ sage: J = RealSymmetricEJA(3)
+ sage: J.one()
+ e0 + e2 + e5
+ sage: J.one().spectral_decomposition()
+ [(1, e0 + e2 + e5)]
+ sage: J.zero().spectral_decomposition()
+ [(0, e0 + e2 + e5)]
- def subalgebra_generated_by(self):
+ TESTS::
+
+ sage: J = RealSymmetricEJA(4)
+ sage: x = sum(J.gens())
+ sage: sd = x.spectral_decomposition()
+ sage: l0 = sd[0][0]
+ sage: l1 = sd[1][0]
+ sage: c0 = sd[0][1]
+ sage: c1 = sd[1][1]
+ sage: c0.inner_product(c1) == 0
+ True
+ sage: c0.is_idempotent()
+ True
+ sage: c1.is_idempotent()
+ True
+ sage: c0 + c1 == J.one()
+ True
+ sage: l0*c0 + l1*c1 == x
+ True
+
+ The spectral decomposition should work in subalgebras, too::
+
+ sage: J = RealSymmetricEJA(4)
+ sage: (e0, e1, e2, e3, e4, e5, e6, e7, e8, e9) = J.gens()
+ sage: A = 2*e5 - 2*e8
+ sage: (lambda1, c1) = A.spectral_decomposition()[1]
+ sage: (J0, J5, J1) = J.peirce_decomposition(c1)
+ sage: (f0, f1, f2) = J1.gens()
+ sage: f0.spectral_decomposition()
+ [(0, f2), (1, f0)]
+
+ """
+ A = self.subalgebra_generated_by(orthonormalize_basis=True)
+ result = []
+ for (evalue, proj) in A(self).operator().spectral_decomposition():
+ result.append( (evalue, proj(A.one()).superalgebra_element()) )
+ return result
+
+ def subalgebra_generated_by(self, orthonormalize_basis=False):
"""
Return the associative subalgebra of the parent EJA generated
by this element.
+ Since our parent algebra is unital, we want "subalgebra" to mean
+ "unital subalgebra" as well; thus the subalgebra that an element
+ generates will itself be a Euclidean Jordan algebra after
+ restricting the algebra operations appropriately. This is the
+ subalgebra that Faraut and Korányi work with in section II.2, for
+ example.
+
SETUP::
sage: from mjo.eja.eja_algebra import random_eja
sage: set_random_seed()
sage: x0 = random_eja().random_element()
sage: A = x0.subalgebra_generated_by()
- sage: x = A.random_element()
- sage: y = A.random_element()
- sage: z = A.random_element()
+ sage: x,y,z = A.random_elements(3)
sage: (x*y)*z == x*(y*z)
True
sage: A(x^2) == A(x)*A(x)
True
+ By definition, the subalgebra generated by the zero element is
+ the one-dimensional algebra generated by the identity
+ element... unless the original algebra was trivial, in which
+ case the subalgebra is trivial too::
+
+ sage: set_random_seed()
+ sage: A = random_eja().zero().subalgebra_generated_by()
+ sage: (A.is_trivial() and A.dimension() == 0) or A.dimension() == 1
+ True
+
"""
- return FiniteDimensionalEuclideanJordanElementSubalgebra(self)
+ from mjo.eja.eja_element_subalgebra import FiniteDimensionalEuclideanJordanElementSubalgebra
+ return FiniteDimensionalEuclideanJordanElementSubalgebra(self, orthonormalize_basis)
def subalgebra_idempotent(self):
sage: from mjo.eja.eja_algebra import random_eja
- TESTS::
+ TESTS:
+
+ Ensure that we can find an idempotent in a non-trivial algebra
+ where there are non-nilpotent elements, or that we get the dumb
+ solution in the trivial algebra::
sage: set_random_seed()
sage: J = random_eja()
sage: x = J.random_element()
- sage: while x.is_nilpotent():
+ sage: while x.is_nilpotent() and not J.is_trivial():
....: x = J.random_element()
sage: c = x.subalgebra_idempotent()
sage: c^2 == c
True
"""
+ if self.parent().is_trivial():
+ return self
+
if self.is_nilpotent():
raise ValueError("this only works with non-nilpotent elements!")
# will be minimal for some natural number s...
s = 0
minimal_dim = J.dimension()
- for i in xrange(1, minimal_dim):
+ for i in range(1, minimal_dim):
this_dim = (u**i).operator().matrix().image().dimension()
if this_dim < minimal_dim:
minimal_dim = this_dim
"""
Return my trace, the sum of my eigenvalues.
+ In a trivial algebra, however you want to look at it, the trace is
+ an empty sum for which we declare the result to be zero.
+
SETUP::
sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
- ....: RealCartesianProductEJA,
+ ....: HadamardEJA,
+ ....: TrivialEJA,
....: random_eja)
EXAMPLES::
+ sage: J = TrivialEJA()
+ sage: J.zero().trace()
+ 0
+
+ ::
sage: J = JordanSpinEJA(3)
sage: x = sum(J.gens())
sage: x.trace()
::
- sage: J = RealCartesianProductEJA(5)
+ sage: J = HadamardEJA(5)
sage: J.one().trace()
5
sage: set_random_seed()
sage: J = random_eja()
- sage: J.random_element().trace() in J.base_ring()
+ sage: J.random_element().trace() in RLF
True
"""
P = self.parent()
r = P.rank()
- p = P._charpoly_coeff(r-1)
+
+ if r == 0:
+ # Special case for the trivial algebra where
+ # the trace is an empty sum.
+ return P.base_ring().zero()
+
+ p = P._charpoly_coefficients()[r-1]
# The _charpoly_coeff function already adds the factor of
# -1 to ensure that _charpoly_coeff(r-1) is really what
# appears in front of t^{r-1} in the charpoly. However,
TESTS:
- The trace inner product is commutative::
+ The trace inner product is commutative, bilinear, and associative::
sage: set_random_seed()
sage: J = random_eja()
- sage: x = J.random_element(); y = J.random_element()
+ sage: x,y,z = J.random_elements(3)
+ sage: # commutative
sage: x.trace_inner_product(y) == y.trace_inner_product(x)
True
-
- The trace inner product is bilinear::
-
- sage: set_random_seed()
- sage: J = random_eja()
- sage: x = J.random_element()
- sage: y = J.random_element()
- sage: z = J.random_element()
- sage: a = QQ.random_element();
+ sage: # bilinear
+ sage: a = J.base_ring().random_element();
sage: actual = (a*(x+z)).trace_inner_product(y)
sage: expected = ( a*x.trace_inner_product(y) +
....: a*z.trace_inner_product(y) )
....: a*x.trace_inner_product(z) )
sage: actual == expected
True
-
- The trace inner product satisfies the compatibility
- condition in the definition of a Euclidean Jordan algebra::
-
- sage: set_random_seed()
- sage: J = random_eja()
- sage: x = J.random_element()
- sage: y = J.random_element()
- sage: z = J.random_element()
+ sage: # associative
sage: (x*y).trace_inner_product(z) == y.trace_inner_product(x*z)
True
raise TypeError("'other' must live in the same algebra")
return (self*other).trace()
+
+
+ def trace_norm(self):
+ """
+ The norm of this element with respect to :meth:`trace_inner_product`.
+
+ SETUP::
+
+ sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
+ ....: HadamardEJA)
+
+ EXAMPLES::
+
+ sage: J = HadamardEJA(2)
+ sage: x = sum(J.gens())
+ sage: x.trace_norm()
+ 1.414213562373095?
+
+ ::
+
+ sage: J = JordanSpinEJA(4)
+ sage: x = sum(J.gens())
+ sage: x.trace_norm()
+ 2.828427124746190?
+
+ """
+ return self.trace_inner_product(self).sqrt()