from sage.misc.prandom import choice
from sage.misc.table import table
from sage.modules.free_module import FreeModule, VectorSpace
-from sage.rings.all import (ZZ, QQ, RR, RLF, CLF,
+from sage.rings.all import (ZZ, QQ, AA, QQbar, RR, RLF, CLF,
PolynomialRing,
QuadraticField)
from mjo.eja.eja_element import FiniteDimensionalEuclideanJordanAlgebraElement
from mjo.eja.eja_utils import _mat2vec
class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule):
- # This is an ugly hack needed to prevent the category framework
- # from implementing a coercion from our base ring (e.g. the
- # rationals) into the algebra. First of all -- such a coercion is
- # nonsense to begin with. But more importantly, it tries to do so
- # in the category of rings, and since our algebras aren't
- # associative they generally won't be rings.
- _no_generic_basering_coercion = True
+
+ def _coerce_map_from_base_ring(self):
+ """
+ Disable the map from the base ring into the algebra.
+
+ Performing a nonsense conversion like this automatically
+ is counterpedagogical. The fallback is to try the usual
+ element constructor, which should also fail.
+
+ SETUP::
+
+ sage: from mjo.eja.eja_algebra import random_eja
+
+ TESTS::
+
+ sage: set_random_seed()
+ sage: J = random_eja()
+ sage: J(1)
+ Traceback (most recent call last):
+ ...
+ ValueError: not a naturally-represented algebra element
+
+ """
+ return None
def __init__(self,
field,
True
"""
+ msg = "not a naturally-represented algebra element"
if elt == 0:
# The superclass implementation of random_element()
# needs to be able to coerce "0" into the algebra.
return self.zero()
+ elif elt in self.base_ring():
+ # Ensure that no base ring -> algebra coercion is performed
+ # by this method. There's some stupidity in sage that would
+ # otherwise propagate to this method; for example, sage thinks
+ # that the integer 3 belongs to the space of 2-by-2 matrices.
+ raise ValueError(msg)
natural_basis = self.natural_basis()
basis_space = natural_basis[0].matrix_space()
if elt not in basis_space:
- raise ValueError("not a naturally-represented algebra element")
+ raise ValueError(msg)
# Thanks for nothing! Matrix spaces aren't vector spaces in
# Sage, so we have to figure out its natural-basis coordinates
Ensure that it says what we think it says::
- sage: JordanSpinEJA(2, field=QQ)
- Euclidean Jordan algebra of dimension 2 over Rational Field
+ sage: JordanSpinEJA(2, field=AA)
+ Euclidean Jordan algebra of dimension 2 over Algebraic Real Field
sage: JordanSpinEJA(3, field=RDF)
Euclidean Jordan algebra of dimension 3 over Real Double Field
Finite family {0: e0, 1: e1, 2: e2}
sage: J.natural_basis()
(
- [1 0] [ 0 1/2*sqrt2] [0 0]
- [0 0], [1/2*sqrt2 0], [0 1]
+ [1 0] [ 0 0.7071067811865475?] [0 0]
+ [0 0], [0.7071067811865475? 0], [0 1]
)
::
J5 = VectorSpace(self.base_ring(), 0) # eigenvalue one-half
J1 = trivial # eigenvalue one
- for (eigval, eigspace) in c.operator().matrix().left_eigenspaces():
+ for (eigval, eigspace) in c.operator().matrix().right_eigenspaces():
if eigval == ~(self.base_ring()(2)):
J5 = eigspace
else:
return tuple( self.random_element() for idx in range(count) )
+ def _rank_computation(self):
+ r"""
+ Compute the rank of this algebra using highly suspicious voodoo.
+
+ ALGORITHM:
+
+ We first compute the basis representation of the operator L_x
+ using polynomial indeterminates are placeholders for the
+ coordinates of "x", which is arbitrary. We then use that
+ matrix to compute the (polynomial) entries of x^0, x^1, ...,
+ x^d,... for increasing values of "d", starting at zero. The
+ idea is that. If we also add "coefficient variables" a_0,
+ a_1,... to the ring, we can form the linear combination
+ a_0*x^0 + ... + a_d*x^d = 0, and ask what dimension the
+ solution space has as an affine variety. When "d" is smaller
+ than the rank, we expect that dimension to be the number of
+ coordinates of "x", since we can set *those* to whatever we
+ want, but linear independence forces the coefficients a_i to
+ be zero. Eventually, when "d" passes the rank, the dimension
+ of the solution space begins to grow, because we can *still*
+ set the coordinates of "x" arbitrarily, but now there are some
+ coefficients that make the sum zero as well. So, when the
+ dimension of the variety jumps, we return the corresponding
+ "d" as the rank of the algebra. This appears to work.
+
+ SETUP::
+
+ sage: from mjo.eja.eja_algebra import (HadamardEJA,
+ ....: JordanSpinEJA,
+ ....: RealSymmetricEJA,
+ ....: ComplexHermitianEJA,
+ ....: QuaternionHermitianEJA)
+
+ EXAMPLES::
+
+ sage: J = HadamardEJA(5)
+ sage: J._rank_computation() == J.rank()
+ True
+ sage: J = JordanSpinEJA(5)
+ sage: J._rank_computation() == J.rank()
+ True
+ sage: J = RealSymmetricEJA(4)
+ sage: J._rank_computation() == J.rank()
+ True
+ sage: J = ComplexHermitianEJA(3)
+ sage: J._rank_computation() == J.rank()
+ True
+ sage: J = QuaternionHermitianEJA(2)
+ sage: J._rank_computation() == J.rank()
+ True
+
+ """
+ n = self.dimension()
+ var_names = [ "X" + str(z) for z in range(1,n+1) ]
+ d = 0
+ ideal_dim = len(var_names)
+ def L_x_i_j(i,j):
+ # From a result in my book, these are the entries of the
+ # basis representation of L_x.
+ return sum( vars[d+k]*self.monomial(k).operator().matrix()[i,j]
+ for k in range(n) )
+
+ while ideal_dim == len(var_names):
+ coeff_names = [ "a" + str(z) for z in range(d) ]
+ R = PolynomialRing(self.base_ring(), coeff_names + var_names)
+ vars = R.gens()
+ L_x = matrix(R, n, n, L_x_i_j)
+ x_powers = [ vars[k]*(L_x**k)*self.one().to_vector()
+ for k in range(d) ]
+ eqs = [ sum(x_powers[k][j] for k in range(d)) for j in range(n) ]
+ ideal_dim = R.ideal(eqs).dimension()
+ d += 1
+
+ # Subtract one because we increment one too many times, and
+ # subtract another one because "d" is one greater than the
+ # answer anyway; when d=3, we go up to x^2.
+ return d-2
+
def rank(self):
"""
Return the rank of this EJA.
return 5
@classmethod
- def random_instance(cls, field=QQ, **kwargs):
+ def random_instance(cls, field=AA, **kwargs):
"""
Return a random instance of this type of algebra.
(r0, r1, r2)
"""
- def __init__(self, n, field=QQ, **kwargs):
+ def __init__(self, n, field=AA, **kwargs):
V = VectorSpace(field, n)
mult_table = [ [ V.gen(i)*(i == j) for j in range(n) ]
for i in range(n) ]
return x.to_vector().inner_product(y.to_vector())
-def random_eja(field=QQ, nontrivial=False):
+def random_eja(field=AA, nontrivial=False):
"""
Return a "random" finite-dimensional Euclidean Jordan Algebra.
In theory, our "field" can be any subfield of the reals::
- sage: RealSymmetricEJA(2, AA)
- Euclidean Jordan algebra of dimension 3 over Algebraic Real Field
+ sage: RealSymmetricEJA(2, RDF)
+ Euclidean Jordan algebra of dimension 3 over Real Double Field
sage: RealSymmetricEJA(2, RR)
Euclidean Jordan algebra of dimension 3 over Real Field with
53 bits of precision
return 4 # Dimension 10
- def __init__(self, n, field=QQ, **kwargs):
+ def __init__(self, n, field=AA, **kwargs):
basis = self._denormalized_basis(n, field)
super(RealSymmetricEJA, self).__init__(field, basis, n, **kwargs)
EXAMPLES::
- sage: F = QuadraticField(-1, 'i')
+ sage: F = QuadraticField(-1, 'I')
sage: x1 = F(4 - 2*i)
sage: x2 = F(1 + 2*i)
sage: x3 = F(-i)
sage: set_random_seed()
sage: n_max = ComplexMatrixEuclideanJordanAlgebra._max_test_case_size()
sage: n = ZZ.random_element(n_max)
- sage: F = QuadraticField(-1, 'i')
+ sage: F = QuadraticField(-1, 'I')
sage: X = random_matrix(F, n)
sage: Y = random_matrix(F, n)
sage: Xe = ComplexMatrixEuclideanJordanAlgebra.real_embed(X)
....: [ 9, 10, 11, 12],
....: [-10, 9, -12, 11] ])
sage: ComplexMatrixEuclideanJordanAlgebra.real_unembed(A)
- [ 2*i + 1 4*i + 3]
- [ 10*i + 9 12*i + 11]
+ [ 2*I + 1 4*I + 3]
+ [ 10*I + 9 12*I + 11]
TESTS:
Unembedding is the inverse of embedding::
sage: set_random_seed()
- sage: F = QuadraticField(-1, 'i')
+ sage: F = QuadraticField(-1, 'I')
sage: M = random_matrix(F, 3)
sage: Me = ComplexMatrixEuclideanJordanAlgebra.real_embed(M)
sage: ComplexMatrixEuclideanJordanAlgebra.real_unembed(Me) == M
field = M.base_ring()
R = PolynomialRing(field, 'z')
z = R.gen()
- F = field.extension(z**2 + 1, 'i', embedding=CLF(-1).sqrt())
+ if field is AA:
+ # Sage doesn't know how to embed AA into QQbar, i.e. how
+ # to adjoin sqrt(-1) to AA.
+ F = QQbar
+ else:
+ F = field.extension(z**2 + 1, 'I', embedding=CLF(-1).sqrt())
i = F.gen()
# Go top-left to bottom-right (reading order), converting every
sage: Ye = y.natural_representation()
sage: X = ComplexHermitianEJA.real_unembed(Xe)
sage: Y = ComplexHermitianEJA.real_unembed(Ye)
- sage: expected = (X*Y).trace().vector()[0]
+ sage: expected = (X*Y).trace().real()
sage: actual = ComplexHermitianEJA.natural_inner_product(Xe,Ye)
sage: actual == expected
True
In theory, our "field" can be any subfield of the reals::
- sage: ComplexHermitianEJA(2, AA)
- Euclidean Jordan algebra of dimension 4 over Algebraic Real Field
+ sage: ComplexHermitianEJA(2, RDF)
+ Euclidean Jordan algebra of dimension 4 over Real Double Field
sage: ComplexHermitianEJA(2, RR)
Euclidean Jordan algebra of dimension 4 over Real Field with
53 bits of precision
return ( s.change_ring(field) for s in S )
- def __init__(self, n, field=QQ, **kwargs):
+ def __init__(self, n, field=AA, **kwargs):
basis = self._denormalized_basis(n,field)
super(ComplexHermitianEJA,self).__init__(field, basis, n, **kwargs)
if M.ncols() != n:
raise ValueError("the matrix 'M' must be square")
- F = QuadraticField(-1, 'i')
+ F = QuadraticField(-1, 'I')
i = F.gen()
blocks = []
raise ValueError('bad on-diagonal submatrix')
if submat[0,1] != -submat[1,0].conjugate():
raise ValueError('bad off-diagonal submatrix')
- z = submat[0,0].vector()[0] # real part
- z += submat[0,0].vector()[1]*i # imag part
- z += submat[0,1].vector()[0]*j # real part
- z += submat[0,1].vector()[1]*k # imag part
+ z = submat[0,0].real()
+ z += submat[0,0].imag()*i
+ z += submat[0,1].real()*j
+ z += submat[0,1].imag()*k
elements.append(z)
return matrix(Q, n/4, elements)
In theory, our "field" can be any subfield of the reals::
- sage: QuaternionHermitianEJA(2, AA)
- Euclidean Jordan algebra of dimension 6 over Algebraic Real Field
+ sage: QuaternionHermitianEJA(2, RDF)
+ Euclidean Jordan algebra of dimension 6 over Real Double Field
sage: QuaternionHermitianEJA(2, RR)
Euclidean Jordan algebra of dimension 6 over Real Field with
53 bits of precision
return ( s.change_ring(field) for s in S )
- def __init__(self, n, field=QQ, **kwargs):
+ def __init__(self, n, field=AA, **kwargs):
basis = self._denormalized_basis(n,field)
super(QuaternionHermitianEJA,self).__init__(field, basis, n, **kwargs)
-class JordanSpinEJA(FiniteDimensionalEuclideanJordanAlgebra, KnownRankEJA):
+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,
+ ....: JordanSpinEJA)
+
+ 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=AA, 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 when
+ the algebra isn't just the reals (when ``n`` isn't one). This
+ is in Faraut and Koranyi, and also my "On the symmetry..."
+ paper::
+
+ sage: set_random_seed()
+ sage: n = ZZ.random_element(2,5)
+ sage: M = matrix.random(QQ, max(0,n-1), algorithm='unimodular')
+ sage: B = M.transpose()*M
+ sage: J = BilinearFormEJA(n, B=B)
+ sage: x = J.random_element()
+ sage: y = J.random_element()
+ sage: x.inner_product(y) == (x*y).trace()/2
+ True
+
+ """
+ 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(BilinearFormEJA):
"""
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::
sage: JordanSpinEJA(2, prefix='B').gens()
(B0, B1)
- """
- def __init__(self, n, field=QQ, **kwargs):
- 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:]
- # z = x*y
- z0 = x.inner_product(y)
- zbar = y0*xbar + x0*ybar
- z = V([z0] + zbar.list())
- mult_table[i][j] = z
-
- # 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, self)
- return fdeja.__init__(field, mult_table, rank=min(n,2), **kwargs)
-
- def inner_product(self, x, y):
- """
- Faster to reimplement than to use natural representations.
-
- SETUP::
-
- sage: from mjo.eja.eja_algebra import JordanSpinEJA
-
- TESTS:
+ TESTS:
- Ensure that this is the usual inner product for the algebras
- over `R^n`::
+ Ensure that we have the usual inner product on `R^n`::
sage: set_random_seed()
sage: J = JordanSpinEJA.random_instance()
sage: x.inner_product(y) == J.natural_inner_product(X,Y)
True
- """
- return x.to_vector().inner_product(y.to_vector())
+ """
+ def __init__(self, n, field=AA, **kwargs):
+ # This is a special case of the BilinearFormEJA with the identity
+ # matrix as its bilinear form.
+ return super(JordanSpinEJA, self).__init__(n, field, **kwargs)
class TrivialEJA(FiniteDimensionalEuclideanJordanAlgebra, KnownRankEJA):
sage: J.one().norm()
0
sage: J.one().subalgebra_generated_by()
- Euclidean Jordan algebra of dimension 0 over Rational Field
+ Euclidean Jordan algebra of dimension 0 over Algebraic Real Field
sage: J.rank()
0
"""
- def __init__(self, field=QQ, **kwargs):
+ def __init__(self, field=AA, **kwargs):
mult_table = []
fdeja = super(TrivialEJA, self)
# The rank is zero using my definition, namely the dimension of the