assume_associative=False,
category=None,
rank=None,
- natural_basis=None,
- inner_product=None):
+ natural_basis=None):
n = len(mult_table)
mult_table = [b.base_extend(field) for b in mult_table]
for b in mult_table:
names=names,
category=cat,
rank=rank,
- natural_basis=natural_basis,
- inner_product=inner_product)
+ natural_basis=natural_basis)
def __init__(self,
assume_associative=False,
category=None,
rank=None,
- natural_basis=None,
- inner_product=None):
+ natural_basis=None):
"""
EXAMPLES:
"""
self._rank = rank
self._natural_basis = natural_basis
- self._inner_product = inner_product
+ self._multiplication_table = mult_table
fda = super(FiniteDimensionalEuclideanJordanAlgebra, self)
fda.__init__(field,
mult_table,
return fmt.format(self.degree(), self.base_ring())
+ def characteristic_polynomial(self):
+ r = self.rank()
+ n = self.dimension()
+
+ names = ['X' + str(i) for i in range(1,n+1)]
+ R = PolynomialRing(self.base_ring(), names)
+ J = FiniteDimensionalEuclideanJordanAlgebra(R,
+ self._multiplication_table,
+ rank=r)
+ x0 = J.zero()
+ c = 1
+ for g in J.gens():
+ x0 += c*g
+ c +=1
+ if not x0.is_regular():
+ raise ValueError("don't know a regular element")
+
+ # Get the vector space (as opposed to module) so that
+ # span_of_basis() works.
+ V = x0.vector().parent().ambient_vector_space()
+ V1 = V.span_of_basis( (x0**k).vector() for k in range(r) )
+ B = V1.basis() + V1.complement().basis()
+ W = V.span_of_basis(B)
+
+ def e(k):
+ # The coordinates of e_k with respect to the basis B.
+ # But, the e_k are elements of B...
+ return identity_matrix(J.base_ring(), n).column(k-1).column()
+
+ # A matrix implementation 1
+ x = J(vector(R, R.gens()))
+ l1 = [column_matrix(W.coordinates((x**k).vector())) for k in range(r)]
+ l2 = [e(k) for k in range(r+1, n+1)]
+ A_of_x = block_matrix(1, n, (l1 + l2))
+ xr = W.coordinates((x**r).vector())
+ a = []
+ for i in range(n):
+ A_cols = A.columns()
+ A_cols[i] = xr
+ numerator = column_matrix(A.base_ring(), A_cols).det()
+ denominator = A.det()
+ ai = numerator/denominator
+ a.append(ai)
+
+ # Note: all entries past the rth should be zero.
+ return a
+
+
def inner_product(self, x, y):
"""
The inner product associated with this Euclidean Jordan algebra.
- Will default to the trace inner product if nothing else.
+ Defaults to the trace inner product, but can be overridden by
+ subclasses if they are sure that the necessary properties are
+ satisfied.
EXAMPLES:
"""
if (not x in self) or (not y in self):
raise TypeError("arguments must live in this algebra")
- if self._inner_product is None:
- return x.trace_inner_product(y)
- else:
- return self._inner_product(x,y)
+ return x.trace_inner_product(y)
def natural_basis(self):
EXAMPLES::
- sage: J = RealSymmetricSimpleEJA(2)
+ sage: J = RealSymmetricEJA(2)
sage: J.basis()
Family (e0, e1, e2)
sage: J.natural_basis()
The identity in `S^n` is converted to the identity in the EJA::
- sage: J = RealSymmetricSimpleEJA(3)
+ sage: J = RealSymmetricEJA(3)
sage: I = identity_matrix(QQ,3)
sage: J(I) == J.one()
True
This skew-symmetric matrix can't be represented in the EJA::
- sage: J = RealSymmetricSimpleEJA(3)
+ sage: J = RealSymmetricEJA(3)
sage: A = matrix(QQ,3, lambda i,j: i-j)
sage: J(A)
Traceback (most recent call last):
so the inner product of the identity matrix with itself
should be the `n`::
- sage: J = RealSymmetricSimpleEJA(3)
+ sage: J = RealSymmetricEJA(3)
sage: J.one().inner_product(J.one())
3
part because the product of Hermitian matrices may not be
Hermitian::
- sage: J = ComplexHermitianSimpleEJA(3)
+ sage: J = ComplexHermitianEJA(3)
sage: J.one().inner_product(J.one())
3
Ditto for the quaternions::
- sage: J = QuaternionHermitianSimpleEJA(3)
+ sage: J = QuaternionHermitianEJA(3)
sage: J.one().inner_product(J.one())
3
We can't use the superclass method because it relies on
the algebra being associative.
+
+ ALGORITHM:
+
+ The usual way to do this is to check if the determinant is
+ zero, but we need the characteristic polynomial for the
+ determinant. The minimal polynomial is a lot easier to get,
+ so we use Corollary 2 in Chapter V of Koecher to check
+ whether or not the paren't algebra's zero element is a root
+ of this element's minimal polynomial.
+
+ TESTS:
+
+ The identity element is always invertible::
+
+ sage: set_random_seed()
+ sage: J = random_eja()
+ sage: J.one().is_invertible()
+ True
+
+ The zero element is never invertible::
+
+ sage: set_random_seed()
+ sage: J = random_eja()
+ sage: J.zero().is_invertible()
+ False
+
"""
- return not self.det().is_zero()
+ zero = self.parent().zero()
+ p = self.minimal_polynomial()
+ return not (p(zero) == zero)
def is_nilpotent(self):
def minimal_polynomial(self):
"""
- EXAMPLES::
+ Return the minimal polynomial of this element,
+ as a function of the variable `t`.
+
+ ALGORITHM:
+
+ We restrict ourselves to the associative subalgebra
+ generated by this element, and then return the minimal
+ polynomial of this element's operator matrix (in that
+ subalgebra). This works by Baes Proposition 2.3.16.
+
+ TESTS:
+
+ The minimal polynomial of the identity and zero elements are
+ always the same::
sage: set_random_seed()
- sage: x = random_eja().random_element()
- sage: x.degree() == x.minimal_polynomial().degree()
- True
+ sage: J = random_eja()
+ sage: J.one().minimal_polynomial()
+ t - 1
+ sage: J.zero().minimal_polynomial()
+ t
- ::
+ The degree of an element is (by one definition) the degree
+ of its minimal polynomial::
sage: set_random_seed()
sage: x = random_eja().random_element()
sage: y0 = y.vector()[0]
sage: y_bar = y.vector()[1:]
sage: actual = y.minimal_polynomial()
- sage: x = SR.symbol('x', domain='real')
- sage: expected = x^2 - 2*y0*x + (y0^2 - norm(y_bar)^2)
+ sage: t = PolynomialRing(J.base_ring(),'t').gen(0)
+ sage: expected = t^2 - 2*y0*t + (y0^2 - norm(y_bar)^2)
sage: bool(actual == expected)
True
"""
- # The element we're going to call "minimal_polynomial()" on.
- # Either myself, interpreted as an element of a finite-
- # dimensional algebra, or an element of an associative
- # subalgebra.
- elt = None
-
- if self.parent().is_associative():
- elt = FiniteDimensionalAlgebraElement(self.parent(), self)
- else:
- 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()))
+ 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()))
- # Recursive call, but should work since elt lives in an
- # associative algebra.
- return elt.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):
EXAMPLES::
- sage: J = ComplexHermitianSimpleEJA(3)
+ sage: J = ComplexHermitianEJA(3)
sage: J.one()
e0 + e5 + e8
sage: J.one().natural_representation()
::
- sage: J = QuaternionHermitianSimpleEJA(3)
+ sage: J = QuaternionHermitianEJA(3)
sage: J.one()
e0 + e9 + e14
sage: J.one().natural_representation()
# The dimension of the subalgebra can't be greater than
# the big algebra, so just put everything into a list
# and let span() get rid of the excess.
- V = self.vector().parent()
+ #
+ # 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()
return V.span( (self**d).vector() for d in xrange(V.dimension()) )
TESTS::
sage: set_random_seed()
- sage: J = eja_rn(5)
+ sage: J = RealCartesianProductEJA(5)
sage: c = J.random_element().subalgebra_idempotent()
sage: c^2 == c
True
return (self*other).trace()
-def eja_rn(dimension, field=QQ):
+class RealCartesianProductEJA(FiniteDimensionalEuclideanJordanAlgebra):
"""
Return the Euclidean Jordan Algebra corresponding to the set
`R^n` under the Hadamard product.
+ Note: this is nothing more than the Cartesian product of ``n``
+ copies of the spin algebra. Once Cartesian product algebras
+ are implemented, this can go.
+
EXAMPLES:
This multiplication table can be verified by hand::
- sage: J = eja_rn(3)
+ sage: J = RealCartesianProductEJA(3)
sage: e0,e1,e2 = J.gens()
sage: e0*e0
e0
e2
"""
- # The FiniteDimensionalAlgebra constructor takes a list of
- # matrices, the ith representing right multiplication by the ith
- # basis element in the vector space. So if e_1 = (1,0,0), then
- # right (Hadamard) multiplication of x by e_1 picks out the first
- # component of x; and likewise for the ith basis element e_i.
- Qs = [ matrix(field, dimension, dimension, lambda k,j: 1*(k == j == i))
- for i in xrange(dimension) ]
+ @staticmethod
+ def __classcall_private__(cls, n, field=QQ):
+ # The FiniteDimensionalAlgebra constructor takes a list of
+ # matrices, the ith representing right multiplication by the ith
+ # basis element in the vector space. So if e_1 = (1,0,0), then
+ # right (Hadamard) multiplication of x by e_1 picks out the first
+ # component of x; and likewise for the ith basis element e_i.
+ Qs = [ matrix(field, n, n, lambda k,j: 1*(k == j == i))
+ for i in xrange(n) ]
- return FiniteDimensionalEuclideanJordanAlgebra(field,
- Qs,
- rank=dimension,
- inner_product=_usual_ip)
+ fdeja = super(RealCartesianProductEJA, cls)
+ return fdeja.__classcall_private__(cls, field, Qs, rank=n)
+ def inner_product(self, x, y):
+ return _usual_ip(x,y)
def random_eja():
"""
n = ZZ.random_element(1,5)
- constructor = choice([eja_rn,
+ constructor = choice([RealCartesianProductEJA,
JordanSpinEJA,
- RealSymmetricSimpleEJA,
- ComplexHermitianSimpleEJA,
- QuaternionHermitianSimpleEJA])
+ RealSymmetricEJA,
+ ComplexHermitianEJA,
+ QuaternionHermitianEJA])
return constructor(n, field=QQ)
return (X_mat*Y_mat).trace()
-def RealSymmetricSimpleEJA(n, field=QQ):
+class RealSymmetricEJA(FiniteDimensionalEuclideanJordanAlgebra):
"""
The rank-n simple EJA consisting of real symmetric n-by-n
matrices, the usual symmetric Jordan product, and the trace inner
EXAMPLES::
- sage: J = RealSymmetricSimpleEJA(2)
+ sage: J = RealSymmetricEJA(2)
sage: e0, e1, e2 = J.gens()
sage: e0*e0
e0
sage: set_random_seed()
sage: n = ZZ.random_element(1,5)
- sage: J = RealSymmetricSimpleEJA(n)
+ sage: J = RealSymmetricEJA(n)
sage: J.degree() == (n^2 + n)/2
True
sage: set_random_seed()
sage: n = ZZ.random_element(1,5)
- sage: J = RealSymmetricSimpleEJA(n)
+ sage: J = RealSymmetricEJA(n)
sage: x = J.random_element()
sage: y = J.random_element()
sage: actual = (x*y).natural_representation()
True
"""
- S = _real_symmetric_basis(n, field=field)
- (Qs, T) = _multiplication_table_from_matrix_basis(S)
+ @staticmethod
+ def __classcall_private__(cls, n, field=QQ):
+ S = _real_symmetric_basis(n, field=field)
+ (Qs, T) = _multiplication_table_from_matrix_basis(S)
- return FiniteDimensionalEuclideanJordanAlgebra(field,
- Qs,
- rank=n,
- natural_basis=T,
- inner_product=_matrix_ip)
+ fdeja = super(RealSymmetricEJA, cls)
+ return fdeja.__classcall_private__(cls,
+ field,
+ Qs,
+ rank=n,
+ natural_basis=T)
+ def inner_product(self, x, y):
+ return _matrix_ip(x,y)
-def ComplexHermitianSimpleEJA(n, field=QQ):
+
+class ComplexHermitianEJA(FiniteDimensionalEuclideanJordanAlgebra):
"""
The rank-n simple EJA consisting of complex Hermitian n-by-n
matrices over the real numbers, the usual symmetric Jordan product,
sage: set_random_seed()
sage: n = ZZ.random_element(1,5)
- sage: J = ComplexHermitianSimpleEJA(n)
+ sage: J = ComplexHermitianEJA(n)
sage: J.degree() == n^2
True
sage: set_random_seed()
sage: n = ZZ.random_element(1,5)
- sage: J = ComplexHermitianSimpleEJA(n)
+ sage: J = ComplexHermitianEJA(n)
sage: x = J.random_element()
sage: y = J.random_element()
sage: actual = (x*y).natural_representation()
True
"""
- S = _complex_hermitian_basis(n)
- (Qs, T) = _multiplication_table_from_matrix_basis(S)
+ @staticmethod
+ def __classcall_private__(cls, n, field=QQ):
+ S = _complex_hermitian_basis(n)
+ (Qs, T) = _multiplication_table_from_matrix_basis(S)
- # Since a+bi on the diagonal is represented as
- #
- # a + bi = [ a b ]
- # [ -b a ],
- #
- # we'll double-count the "a" entries if we take the trace of
- # the embedding.
- ip = lambda X,Y: _matrix_ip(X,Y)/2
+ fdeja = super(ComplexHermitianEJA, cls)
+ return fdeja.__classcall_private__(cls,
+ field,
+ Qs,
+ rank=n,
+ natural_basis=T)
- return FiniteDimensionalEuclideanJordanAlgebra(field,
- Qs,
- rank=n,
- natural_basis=T,
- inner_product=ip)
+ def inner_product(self, x, y):
+ # Since a+bi on the diagonal is represented as
+ #
+ # a + bi = [ a b ]
+ # [ -b a ],
+ #
+ # we'll double-count the "a" entries if we take the trace of
+ # the embedding.
+ return _matrix_ip(x,y)/2
-def QuaternionHermitianSimpleEJA(n, field=QQ):
+class QuaternionHermitianEJA(FiniteDimensionalEuclideanJordanAlgebra):
"""
The rank-n simple EJA consisting of self-adjoint n-by-n quaternion
matrices, the usual symmetric Jordan product, and the
sage: set_random_seed()
sage: n = ZZ.random_element(1,5)
- sage: J = QuaternionHermitianSimpleEJA(n)
+ sage: J = QuaternionHermitianEJA(n)
sage: J.degree() == 2*(n^2) - n
True
sage: set_random_seed()
sage: n = ZZ.random_element(1,5)
- sage: J = QuaternionHermitianSimpleEJA(n)
+ sage: J = QuaternionHermitianEJA(n)
sage: x = J.random_element()
sage: y = J.random_element()
sage: actual = (x*y).natural_representation()
True
"""
- S = _quaternion_hermitian_basis(n)
- (Qs, T) = _multiplication_table_from_matrix_basis(S)
-
- # Since a+bi+cj+dk on the diagonal is represented as
- #
- # a + bi +cj + dk = [ a b c d]
- # [ -b a -d c]
- # [ -c d a -b]
- # [ -d -c b a],
- #
- # we'll quadruple-count the "a" entries if we take the trace of
- # the embedding.
- ip = lambda X,Y: _matrix_ip(X,Y)/4
+ @staticmethod
+ def __classcall_private__(cls, n, field=QQ):
+ S = _quaternion_hermitian_basis(n)
+ (Qs, T) = _multiplication_table_from_matrix_basis(S)
- return FiniteDimensionalEuclideanJordanAlgebra(field,
- Qs,
- rank=n,
- natural_basis=T,
- inner_product=ip)
+ fdeja = super(QuaternionHermitianEJA, cls)
+ return fdeja.__classcall_private__(cls,
+ field,
+ Qs,
+ rank=n,
+ natural_basis=T)
+ def inner_product(self, x, y):
+ # Since a+bi+cj+dk on the diagonal is represented as
+ #
+ # a + bi +cj + dk = [ a b c d]
+ # [ -b a -d c]
+ # [ -c d a -b]
+ # [ -d -c b a],
+ #
+ # we'll quadruple-count the "a" entries if we take the trace of
+ # the embedding.
+ return _matrix_ip(x,y)/4
-def OctonionHermitianSimpleEJA(n):
- """
- This shit be crazy. It has dimension 27 over the reals.
- """
- n = 3
- pass
class JordanSpinEJA(FiniteDimensionalEuclideanJordanAlgebra):
"""
Qi[0,0] = Qi[0,0] * ~field(2)
Qs.append(Qi)
+ # The rank of the spin algebra is two, unless we're in a
+ # one-dimensional ambient space (because the rank is bounded by
+ # the ambient dimension).
fdeja = super(JordanSpinEJA, cls)
- return fdeja.__classcall_private__(cls, field, Qs)
-
- def rank(self):
- """
- Return the rank of this Jordan Spin Algebra.
-
- The rank of the spin algebra is two, unless we're in a
- one-dimensional ambient space (because the rank is bounded by
- the ambient dimension).
- """
- return min(self.dimension(),2)
+ return fdeja.__classcall_private__(cls, field, Qs, rank=min(n,2))
def inner_product(self, x, y):
return _usual_ip(x,y)