+ S = []
+ 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)
+ S.append(Sij)
+ else:
+ # The second, third, and fourth ones have a minus
+ # because they're conjugated.
+ Sij_real = cls.real_embed(Eij + Eij.transpose())
+ S.append(Sij_real)
+ Sij_I = cls.real_embed(I*Eij - I*Eij.transpose())
+ S.append(Sij_I)
+ Sij_J = cls.real_embed(J*Eij - J*Eij.transpose())
+ S.append(Sij_J)
+ Sij_K = cls.real_embed(K*Eij - K*Eij.transpose())
+ S.append(Sij_K)
+
+ # Since we embedded these, we can drop back to the "field" that we
+ # started with instead of the quaternion algebra "Q".
+ return tuple( s.change_ring(field) for s in S )
+
+
+ def __init__(self, n, field=AA, **kwargs):
+ basis = self._denormalized_basis(n,field)
+ super(QuaternionHermitianEJA, self).__init__(field,
+ basis,
+ self.jordan_product,
+ self.trace_inner_product,
+ **kwargs)
+ self.rank.set_cache(n)
+ # TODO: cache one()!
+
+ @staticmethod
+ def _max_random_instance_size():
+ r"""
+ The maximum rank of a random QuaternionHermitianEJA.
+ """
+ return 2 # Dimension 6
+
+ @classmethod
+ def random_instance(cls, field=AA, **kwargs):
+ """
+ Return a random instance of this type of algebra.
+ """
+ n = ZZ.random_element(cls._max_random_instance_size() + 1)
+ return cls(n, field, **kwargs)
+
+
+class HadamardEJA(ConcreteEuclideanJordanAlgebra):
+ """
+ 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.
+
+ SETUP::
+
+ sage: from mjo.eja.eja_algebra import HadamardEJA
+
+ EXAMPLES:
+
+ This multiplication table can be verified by hand::
+
+ sage: J = HadamardEJA(3)
+ sage: e0,e1,e2 = J.gens()
+ sage: e0*e0
+ e0
+ sage: e0*e1
+ 0
+ sage: e0*e2
+ 0
+ sage: e1*e1
+ e1
+ sage: e1*e2
+ 0
+ sage: e2*e2
+ e2
+
+ TESTS:
+
+ We can change the generator prefix::
+
+ sage: HadamardEJA(3, prefix='r').gens()
+ (r0, r1, r2)
+
+ """
+ def __init__(self, n, field=AA, **kwargs):
+ V = VectorSpace(field, n)
+ basis = V.basis()
+
+ def jordan_product(x,y):
+ return V([ xi*yi for (xi,yi) in zip(x,y) ])
+ def inner_product(x,y):
+ return x.inner_product(y)
+
+ super(HadamardEJA, self).__init__(field,
+ basis,
+ jordan_product,
+ inner_product,
+ **kwargs)
+ self.rank.set_cache(n)
+
+ if n == 0:
+ self.one.set_cache( self.zero() )
+ else:
+ self.one.set_cache( sum(self.gens()) )
+
+ @staticmethod
+ def _max_random_instance_size():
+ r"""
+ The maximum dimension of a random HadamardEJA.
+ """
+ return 5
+
+ @classmethod
+ def random_instance(cls, field=AA, **kwargs):
+ """
+ Return a random instance of this type of algebra.
+ """
+ n = ZZ.random_element(cls._max_random_instance_size() + 1)
+ return cls(n, field, **kwargs)
+
+
+class BilinearFormEJA(ConcreteEuclideanJordanAlgebra):
+ 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 =
+ (<Bx,y>,y_bar>, x0*y_bar + y0*x_bar)`` where `B = 1 \times B22` is
+ a symmetric positive-definite "bilinear form" matrix. Its
+ dimension is the size of `B`, and it has rank two in dimensions
+ larger than two. It reduces to the ``JordanSpinEJA`` when `B` is
+ the identity matrix of order ``n``.
+
+ We insist that the one-by-one upper-left identity block of `B` be
+ passed in as well so that we can be passed a matrix of size zero
+ to construct a trivial algebra.
+
+ 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: B = matrix.identity(AA,3)
+ sage: J0 = BilinearFormEJA(B)
+ sage: J1 = JordanSpinEJA(3)
+ sage: J0.multiplication_table() == J0.multiplication_table()
+ True
+
+ An error is raised if the matrix `B` does not correspond to a
+ positive-definite bilinear form::
+
+ sage: B = matrix.random(QQ,2,3)
+ sage: J = BilinearFormEJA(B)
+ Traceback (most recent call last):
+ ...
+ ValueError: bilinear form is not positive-definite
+ sage: B = matrix.zero(QQ,3)
+ sage: J = BilinearFormEJA(B)
+ Traceback (most recent call last):
+ ...
+ ValueError: bilinear form is not positive-definite
+
+ TESTS:
+
+ We can create a zero-dimensional algebra::
+
+ sage: B = matrix.identity(AA,0)
+ sage: J = BilinearFormEJA(B)
+ 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. We opt not to orthonormalize the basis, because if we
+ did, we would have to normalize the `s_{i}` in a similar manner::
+
+ sage: set_random_seed()
+ sage: n = ZZ.random_element(5)
+ sage: M = matrix.random(QQ, max(0,n-1), algorithm='unimodular')
+ sage: B11 = matrix.identity(QQ,1)
+ sage: B22 = M.transpose()*M
+ sage: B = block_matrix(2,2,[ [B11,0 ],
+ ....: [0, B22 ] ])
+ sage: J = BilinearFormEJA(B, orthonormalize=False)
+ sage: eis = VectorSpace(M.base_ring(), M.ncols()).basis()
+ sage: V = J.vector_space()
+ sage: sis = [ J( V([0] + (M.inverse()*ei).list()).column() )
+ ....: 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, B, field=AA, **kwargs):
+ if not B.is_positive_definite():
+ raise ValueError("bilinear form is not positive-definite")
+
+ n = B.nrows()
+ V = VectorSpace(field, n)
+
+ def inner_product(x,y):
+ return (B*x).inner_product(y)
+
+ def jordan_product(x,y):
+ x0 = x[0]
+ xbar = x[1:]
+ y0 = y[0]
+ ybar = y[1:]
+ z0 = inner_product(x,y)
+ zbar = y0*xbar + x0*ybar
+ return V([z0] + zbar.list())
+
+ super(BilinearFormEJA, self).__init__(field,
+ V.basis(),
+ jordan_product,
+ inner_product,
+ **kwargs)
+
+ # 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).
+ self.rank.set_cache(min(n,2))
+
+ if n == 0:
+ self.one.set_cache( self.zero() )
+ else:
+ self.one.set_cache( self.monomial(0) )
+
+ @staticmethod
+ def _max_random_instance_size():
+ r"""
+ The maximum dimension of a random BilinearFormEJA.
+ """
+ return 5