what can be supported in a general Jordan Algebra.
"""
-from itertools import izip, repeat
+from itertools import repeat
from sage.algebras.quatalg.quaternion_algebra import QuaternionAlgebra
from sage.categories.magmatic_algebras import MagmaticAlgebras
# long run to have the multiplication table be in terms of
# algebra elements. We do this after calling the superclass
# constructor so that from_vector() knows what to do.
- self._multiplication_table = [ map(lambda x: self.from_vector(x), ls)
- for ls in mult_table ]
+ self._multiplication_table = [
+ list(map(lambda x: self.from_vector(x), ls))
+ for ls in mult_table
+ ]
def _element_constructor_(self, elt):
SETUP::
sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
- ....: RealCartesianProductEJA,
+ ....: HadamardEJA,
....: RealSymmetricEJA)
EXAMPLES:
vector representations) back and forth faithfully::
sage: set_random_seed()
- sage: J = RealCartesianProductEJA.random_instance()
+ sage: J = HadamardEJA.random_instance()
sage: x = J.random_element()
sage: J(x.to_vector().column()) == x
True
S = PolynomialRing(S, R.variable_names())
t = S(t)
- return sum( a[k]*(t**k) for k in xrange(len(a)) )
+ return sum( a[k]*(t**k) for k in range(len(a)) )
def inner_product(self, x, y):
"""
M = list(self._multiplication_table) # copy
- for i in xrange(len(M)):
+ for i in range(len(M)):
# M had better be "square"
M[i] = [self.monomial(i)] + M[i]
M = [["*"] + list(self.gens())] + M
SETUP::
- sage: from mjo.eja.eja_algebra import (RealCartesianProductEJA,
+ sage: from mjo.eja.eja_algebra import (HadamardEJA,
....: random_eja)
EXAMPLES::
- sage: J = RealCartesianProductEJA(5)
+ sage: J = HadamardEJA(5)
sage: J.one()
e0 + e1 + e2 + e3 + e4
In the future, this can be extended to a complete system of
orthogonal idempotents.
+
+ INPUT:
+
+ - ``c`` -- an idempotent of this algebra.
+
+ OUTPUT:
+
+ A triple (J0, J5, J1) containing two subalgebras and one subspace
+ of this algebra,
+
+ - ``J0`` -- the algebra on the eigenspace of ``c.operator()``
+ corresponding to the eigenvalue zero.
+
+ - ``J5`` -- the eigenspace (NOT a subalgebra) of ``c.operator()``
+ corresponding to the eigenvalue one-half.
+
+ - ``J1`` -- the algebra on the eigenspace of ``c.operator()``
+ corresponding to the eigenvalue one.
+
+ These are the only possible eigenspaces for that operator, and this
+ algebra is a direct sum of them. The spaces ``J0`` and ``J1`` are
+ orthogonal, and are subalgebras of this algebra with the appropriate
+ restrictions.
+
+ SETUP::
+
+ sage: from mjo.eja.eja_algebra import random_eja, RealSymmetricEJA
+
+ EXAMPLES:
+
+ The canonical example comes from the symmetric matrices, which
+ decompose into diagonal and off-diagonal parts::
+
+ sage: J = RealSymmetricEJA(3)
+ sage: C = matrix(QQ, [ [1,0,0],
+ ....: [0,1,0],
+ ....: [0,0,0] ])
+ sage: c = J(C)
+ sage: J0,J5,J1 = J.peirce_decomposition(c)
+ sage: J0
+ Euclidean Jordan algebra of dimension 1...
+ sage: J5
+ Vector space of degree 6 and dimension 2...
+ sage: J1
+ Euclidean Jordan algebra of dimension 3...
+
+ TESTS:
+
+ Every algebra decomposes trivially with respect to its identity
+ element::
+
+ sage: set_random_seed()
+ sage: J = random_eja()
+ sage: J0,J5,J1 = J.peirce_decomposition(J.one())
+ sage: J0.dimension() == 0 and J5.dimension() == 0
+ True
+ sage: J1.superalgebra() == J and J1.dimension() == J.dimension()
+ True
+
+ The identity elements in the two subalgebras are the
+ projections onto their respective subspaces of the
+ superalgebra's identity element::
+
+ sage: set_random_seed()
+ sage: J = random_eja()
+ sage: x = J.random_element()
+ sage: if not J.is_trivial():
+ ....: while x.is_nilpotent():
+ ....: x = J.random_element()
+ sage: c = x.subalgebra_idempotent()
+ sage: J0,J5,J1 = J.peirce_decomposition(c)
+ sage: J1(c) == J1.one()
+ True
+ sage: J0(J.one() - c) == J0.one()
+ True
+
"""
if not c.is_idempotent():
raise ValueError("element is not idempotent: %s" % c)
# eigenspace corresponding to lambda=1 if you take the
# decomposition relative to the identity element).
trivial = FiniteDimensionalEuclideanJordanSubalgebra(self, ())
- J0 = trivial # eigenvalue zero
- J2 = trivial # eigenvalue one-half
- J1 = trivial # eigenvalue one
+ J0 = trivial # eigenvalue zero
+ J5 = VectorSpace(self.base_ring(), 0) # eigenvalue one-half
+ J1 = trivial # eigenvalue one
for (eigval, eigspace) in c.operator().matrix().left_eigenspaces():
- gens = tuple( self.from_vector(b) for b in eigspace.basis() )
- subalg = FiniteDimensionalEuclideanJordanSubalgebra(self, gens)
- if eigval == 0:
- J0 = subalg
- elif eigval == ~(self.base_ring()(2)):
- J2 = subalg
- elif eigval == 1:
- J1 = subalg
+ if eigval == ~(self.base_ring()(2)):
+ J5 = eigspace
else:
- raise ValueError("unexpected eigenvalue: %s" % eigval)
+ gens = tuple( self.from_vector(b) for b in eigspace.basis() )
+ subalg = FiniteDimensionalEuclideanJordanSubalgebra(self, gens)
+ if eigval == 0:
+ J0 = subalg
+ elif eigval == 1:
+ J1 = subalg
+ else:
+ raise ValueError("unexpected eigenvalue: %s" % eigval)
- return (J0, J2, J1)
+ return (J0, J5, J1)
def random_elements(self, count):
True
"""
- return tuple( self.random_element() for idx in xrange(count) )
+ return tuple( self.random_element() for idx in range(count) )
def rank(self):
return cls(n, field, **kwargs)
-class RealCartesianProductEJA(FiniteDimensionalEuclideanJordanAlgebra,
- KnownRankEJA):
+class HadamardEJA(FiniteDimensionalEuclideanJordanAlgebra, KnownRankEJA):
"""
Return the Euclidean Jordan Algebra corresponding to the set
`R^n` under the Hadamard product.
SETUP::
- sage: from mjo.eja.eja_algebra import RealCartesianProductEJA
+ sage: from mjo.eja.eja_algebra import HadamardEJA
EXAMPLES:
This multiplication table can be verified by hand::
- sage: J = RealCartesianProductEJA(3)
+ sage: J = HadamardEJA(3)
sage: e0,e1,e2 = J.gens()
sage: e0*e0
e0
We can change the generator prefix::
- sage: RealCartesianProductEJA(3, prefix='r').gens()
+ sage: HadamardEJA(3, prefix='r').gens()
(r0, r1, r2)
"""
def __init__(self, n, field=QQ, **kwargs):
V = VectorSpace(field, n)
- mult_table = [ [ V.gen(i)*(i == j) for j in xrange(n) ]
- for i in xrange(n) ]
+ mult_table = [ [ V.gen(i)*(i == j) for j in range(n) ]
+ for i in range(n) ]
- fdeja = super(RealCartesianProductEJA, self)
+ fdeja = super(HadamardEJA, self)
return fdeja.__init__(field, mult_table, rank=n, **kwargs)
def inner_product(self, x, y):
SETUP::
- sage: from mjo.eja.eja_algebra import RealCartesianProductEJA
+ sage: from mjo.eja.eja_algebra import HadamardEJA
TESTS:
over `R^n`::
sage: set_random_seed()
- sage: J = RealCartesianProductEJA.random_instance()
+ sage: J = HadamardEJA.random_instance()
sage: x,y = J.random_elements(2)
sage: X = x.natural_representation()
sage: Y = y.natural_representation()
basis = tuple( s.change_ring(field) for s in basis )
self._basis_normalizers = tuple(
~(self.natural_inner_product(s,s).sqrt()) for s in basis )
- basis = tuple(s*c for (s,c) in izip(basis,self._basis_normalizers))
+ basis = tuple(s*c for (s,c) in zip(basis,self._basis_normalizers))
Qs = self.multiplication_table_from_matrix_basis(basis)
# with had entries in a nice field.
return super(MatrixEuclideanJordanAlgebra, self)._charpoly_coeff(i)
else:
- basis = ( (b/n) for (b,n) in izip(self.natural_basis(),
- self._basis_normalizers) )
+ basis = ( (b/n) for (b,n) in zip(self.natural_basis(),
+ self._basis_normalizers) )
# Do this over the rationals and convert back at the end.
J = MatrixEuclideanJordanAlgebra(QQ,
(_,x,_,_) = J._charpoly_matrix_system()
p = J._charpoly_coeff(i)
# p might be missing some vars, have to substitute "optionally"
- pairs = izip(x.base_ring().gens(), self._basis_normalizers)
+ pairs = zip(x.base_ring().gens(), self._basis_normalizers)
substitutions = { v: v*c for (v,c) in pairs }
result = p.subs(substitutions)
V = VectorSpace(field, dimension**2)
W = V.span_of_basis( _mat2vec(s) for s in basis )
n = len(basis)
- mult_table = [[W.zero() for j in xrange(n)] for i in xrange(n)]
- for i in xrange(n):
- for j in xrange(n):
+ mult_table = [[W.zero() for j in range(n)] for i in range(n)]
+ for i in range(n):
+ for j in range(n):
mat_entry = (basis[i]*basis[j] + basis[j]*basis[i])/2
mult_table[i][j] = W.coordinate_vector(_mat2vec(mat_entry))
# The basis of symmetric matrices, as matrices, in their R^(n-by-n)
# coordinates.
S = []
- for i in xrange(n):
- for j in xrange(i+1):
+ for i in range(n):
+ for j in range(i+1):
Eij = matrix(field, n, lambda k,l: k==i and l==j)
if i == j:
Sij = Eij
# Go top-left to bottom-right (reading order), converting every
# 2-by-2 block we see to a single complex element.
elements = []
- for k in xrange(n/2):
- for j in xrange(n/2):
+ for k in range(n/2):
+ for j in range(n/2):
submat = M[2*k:2*k+2,2*j:2*j+2]
if submat[0,0] != submat[1,1]:
raise ValueError('bad on-diagonal submatrix')
# * The diagonal will (as a result) be real.
#
S = []
- for i in xrange(n):
- for j in xrange(i+1):
+ for i in range(n):
+ for j in range(i+1):
Eij = matrix(F, n, lambda k,l: k==i and l==j)
if i == j:
Sij = cls.real_embed(Eij)
# 4-by-4 block we see to a 2-by-2 complex block, to a 1-by-1
# quaternion block.
elements = []
- for l in xrange(n/4):
- for m in xrange(n/4):
+ for l in range(n/4):
+ for m in range(n/4):
submat = ComplexMatrixEuclideanJordanAlgebra.real_unembed(
M[4*l:4*l+4,4*m:4*m+4] )
if submat[0,0] != submat[1,1].conjugate():
# * The diagonal will (as a result) be real.
#
S = []
- for i in xrange(n):
- for j in xrange(i+1):
+ for i in range(n):
+ for j in range(i+1):
Eij = matrix(Q, n, lambda k,l: k==i and l==j)
if i == j:
Sij = cls.real_embed(Eij)
super(QuaternionHermitianEJA,self).__init__(field, basis, n, **kwargs)
+class BilinearFormEJA(FiniteDimensionalEuclideanJordanAlgebra, KnownRankEJA):
+ r"""
+ The rank-2 simple EJA consisting of real vectors ``x=(x0, x_bar)``
+ with the half-trace inner product and jordan product ``x*y =
+ (x0*y0 + <B*x_bar,y_bar>, x0*y_bar + y0*x_bar)`` where ``B`` is a
+ symmetric positive-definite "bilinear form" matrix. It has
+ dimension `n` over the reals, and reduces to the ``JordanSpinEJA``
+ when ``B`` is the identity matrix of order ``n-1``.
+
+ SETUP::
+
+ sage: from mjo.eja.eja_algebra import BilinearFormEJA
+
+ EXAMPLES:
+
+ When no bilinear form is specified, the identity matrix is used,
+ and the resulting algebra is the Jordan spin algebra::
+
+ sage: J0 = BilinearFormEJA(3)
+ sage: J1 = JordanSpinEJA(3)
+ sage: J0.multiplication_table() == J0.multiplication_table()
+ True
+
+ TESTS:
+
+ We can create a zero-dimensional algebra::
+
+ sage: J = BilinearFormEJA(0)
+ sage: J.basis()
+ Finite family {}
+
+ We can check the multiplication condition given in the Jordan, von
+ Neumann, and Wigner paper (and also discussed on my "On the
+ symmetry..." paper). Note that this relies heavily on the standard
+ choice of basis, as does anything utilizing the bilinear form matrix::
+
+ sage: set_random_seed()
+ sage: n = ZZ.random_element(5)
+ sage: M = matrix.random(QQ, max(0,n-1), algorithm='unimodular')
+ sage: B = M.transpose()*M
+ sage: J = BilinearFormEJA(n, B=B)
+ sage: eis = VectorSpace(M.base_ring(), M.ncols()).basis()
+ sage: V = J.vector_space()
+ sage: sis = [ J.from_vector(V([0] + (M.inverse()*ei).list()))
+ ....: for ei in eis ]
+ sage: actual = [ sis[i]*sis[j]
+ ....: for i in range(n-1)
+ ....: for j in range(n-1) ]
+ sage: expected = [ J.one() if i == j else J.zero()
+ ....: for i in range(n-1)
+ ....: for j in range(n-1) ]
+ sage: actual == expected
+ True
+ """
+ def __init__(self, n, field=QQ, B=None, **kwargs):
+ if B is None:
+ self._B = matrix.identity(field, max(0,n-1))
+ else:
+ self._B = B
+
+ V = VectorSpace(field, n)
+ mult_table = [[V.zero() for j in range(n)] for i in range(n)]
+ for i in range(n):
+ for j in range(n):
+ x = V.gen(i)
+ y = V.gen(j)
+ x0 = x[0]
+ xbar = x[1:]
+ y0 = y[0]
+ ybar = y[1:]
+ z0 = x0*y0 + (self._B*xbar).inner_product(ybar)
+ zbar = y0*xbar + x0*ybar
+ z = V([z0] + zbar.list())
+ mult_table[i][j] = z
+
+ # The rank of this algebra is two, unless we're in a
+ # one-dimensional ambient space (because the rank is bounded
+ # by the ambient dimension).
+ fdeja = super(BilinearFormEJA, self)
+ return fdeja.__init__(field, mult_table, rank=min(n,2), **kwargs)
+
+ def inner_product(self, x, y):
+ r"""
+ Half of the trace inner product.
+
+ This is defined so that the special case of the Jordan spin
+ algebra gets the usual inner product.
+
+ SETUP::
+
+ sage: from mjo.eja.eja_algebra import BilinearFormEJA
+
+ TESTS:
+
+ Ensure that this is one-half of the trace inner-product::
+
+ sage: set_random_seed()
+ sage: n = ZZ.random_element(5)
+ sage: M = matrix.random(QQ, n-1, algorithm='unimodular')
+ sage: B = M.transpose()*M
+ sage: J = BilinearFormEJA(n, B=B)
+ sage: eis = VectorSpace(M.base_ring(), M.ncols()).basis()
+ sage: V = J.vector_space()
+ sage: sis = [ J.from_vector(V([0] + (M.inverse()*ei).list()))
+ ....: for ei in eis ]
+ sage: actual = [ sis[i]*sis[j]
+ ....: for i in range(n-1)
+ ....: for j in range(n-1) ]
+ sage: expected = [ J.one() if i == j else J.zero()
+ ....: for i in range(n-1)
+ ....: for j in range(n-1) ]
+
+ """
+ xvec = x.to_vector()
+ xbar = xvec[1:]
+ yvec = y.to_vector()
+ ybar = yvec[1:]
+ return x[0]*y[0] + (self._B*xbar).inner_product(ybar)
+
class JordanSpinEJA(FiniteDimensionalEuclideanJordanAlgebra, KnownRankEJA):
"""
The rank-2 simple EJA consisting of real vectors ``x=(x0, x_bar)``
with the usual inner product and jordan product ``x*y =
- (<x_bar,y_bar>, x0*y_bar + y0*x_bar)``. It has dimension `n` over
+ (<x,y>, x0*y_bar + y0*x_bar)``. It has dimension `n` over
the reals.
SETUP::
"""
def __init__(self, n, field=QQ, **kwargs):
V = VectorSpace(field, n)
- mult_table = [[V.zero() for j in xrange(n)] for i in xrange(n)]
- for i in xrange(n):
- for j in xrange(n):
+ mult_table = [[V.zero() for j in range(n)] for i in range(n)]
+ for i in range(n):
+ for j in range(n):
x = V.gen(i)
y = V.gen(j)
x0 = x[0]