]> gitweb.michael.orlitzky.com - sage.d.git/blobdiff - mjo/eja/eja_algebra.py
eja: define subalgebra_generated_by() to contain the identity.
[sage.d.git] / mjo / eja / eja_algebra.py
index a207250b4092f97e07d93a62c0ed7e23d9f9536d..658957556cf382a62d0d252359ce735019996973 100644 (file)
@@ -5,6 +5,8 @@ are used in optimization, and have some additional nice methods beyond
 what can be supported in a general Jordan Algebra.
 """
 
+from itertools import izip, repeat
+
 from sage.algebras.quatalg.quaternion_algebra import QuaternionAlgebra
 from sage.categories.magmatic_algebras import MagmaticAlgebras
 from sage.combinat.free_module import CombinatorialFreeModule
@@ -14,13 +16,9 @@ from sage.misc.cachefunc import cached_method
 from sage.misc.prandom import choice
 from sage.misc.table import table
 from sage.modules.free_module import FreeModule, VectorSpace
-from sage.rings.integer_ring import ZZ
-from sage.rings.number_field.number_field import NumberField, QuadraticField
-from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
-from sage.rings.rational_field import QQ
-from sage.rings.real_lazy import CLF, RLF
-from sage.structure.element import is_Matrix
-
+from sage.rings.all import (ZZ, QQ, RR, RLF, CLF,
+                            PolynomialRing,
+                            QuadraticField)
 from mjo.eja.eja_element import FiniteDimensionalEuclideanJordanAlgebraElement
 from mjo.eja.eja_utils import _mat2vec
 
@@ -39,11 +37,12 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule):
                  rank,
                  prefix='e',
                  category=None,
-                 natural_basis=None):
+                 natural_basis=None,
+                 check=True):
         """
         SETUP::
 
-            sage: from mjo.eja.eja_algebra import random_eja
+            sage: from mjo.eja.eja_algebra import (JordanSpinEJA, random_eja)
 
         EXAMPLES:
 
@@ -51,18 +50,30 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule):
 
             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*y == y*x
             True
 
+        TESTS:
+
+        The ``field`` we're given must be real::
+
+            sage: JordanSpinEJA(2,QQbar)
+            Traceback (most recent call last):
+            ...
+            ValueError: field is not real
+
         """
+        if check:
+            if not field.is_subring(RR):
+                # Note: this does return true for the real algebraic
+                # field, and any quadratic field where we've specified
+                # a real embedding.
+                raise ValueError('field is not real')
+
         self._rank = rank
         self._natural_basis = natural_basis
 
-        # TODO: HACK for the charpoly.. needs redesign badly.
-        self._basis_normalizers = None
-
         if category is None:
             category = MagmaticAlgebras(field).FiniteDimensional()
             category = category.WithBasis().Unital()
@@ -155,26 +166,6 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule):
         return self.from_vector(coords)
 
 
-    @staticmethod
-    def _max_test_case_size():
-        """
-        Return an integer "size" that is an upper bound on the size of
-        this algebra when it is used in a random test
-        case. Unfortunately, the term "size" is quite vague -- when
-        dealing with `R^n` under either the Hadamard or Jordan spin
-        product, the "size" refers to the dimension `n`. When dealing
-        with a matrix algebra (real symmetric or complex/quaternion
-        Hermitian), it refers to the size of the matrix, which is
-        far less than the dimension of the underlying vector space.
-
-        We default to five in this class, which is safe in `R^n`. The
-        matrix algebra subclasses (or any class where the "size" is
-        interpreted to be far less than the dimension) should override
-        with a smaller number.
-        """
-        return 5
-
-
     def _repr_(self):
         """
         Return a string representation of ``self``.
@@ -258,19 +249,6 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule):
         store the trace/determinant (a_{r-1} and a_{0} respectively)
         separate from the entire characteristic polynomial.
         """
-        if self._basis_normalizers is not None:
-             # Must be a matrix class?
-             # WARNING/TODO: this whole mess is mis-designed.
-             n = self.natural_basis_space().nrows()
-             field = self.base_ring().base_ring() # yeeeeaaaahhh
-             J = self.__class__(n, field, False)
-             (_,x,_,_) = J._charpoly_matrix_system()
-             p = J._charpoly_coeff(i)
-             # p might be missing some vars, have to substitute "optionally"
-             pairs = zip(x.base_ring().gens(), self._basis_normalizers)
-             substitutions = { v: v*c for (v,c) in pairs }
-             return p.subs(substitutions)
-
         (A_of_x, x, xr, detA) = self._charpoly_matrix_system()
         R = A_of_x.base_ring()
         if i >= self.rank():
@@ -424,7 +402,7 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule):
             # assign a[r] goes out-of-bounds.
             a.append(1) # corresponds to x^r
 
-        return sum( a[k]*(t**k) for k in range(len(a)) )
+        return sum( a[k]*(t**k) for k in xrange(len(a)) )
 
 
     def inner_product(self, x, y):
@@ -441,14 +419,12 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule):
 
         EXAMPLES:
 
-        The inner product must satisfy its axiom for this algebra to truly
-        be a Euclidean Jordan Algebra::
+        Our inner product is "associative," which means the following for
+        a symmetric bilinear form::
 
             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 = J.random_elements(3)
             sage: (x*y).inner_product(z) == y.inner_product(x*z)
             True
 
@@ -473,9 +449,6 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule):
             sage: J = ComplexHermitianEJA(3)
             sage: J.is_trivial()
             False
-            sage: A = J.zero().subalgebra_generated_by()
-            sage: A.is_trivial()
-            True
 
         """
         return self.dimension() == 0
@@ -508,7 +481,7 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule):
 
         """
         M = list(self._multiplication_table) # copy
-        for i in range(len(M)):
+        for i in xrange(len(M)):
             # M had better be "square"
             M[i] = [self.monomial(i)] + M[i]
         M = [["*"] + list(self.gens())] + M
@@ -649,35 +622,25 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule):
         return self.linear_combination(zip(self.gens(), coeffs))
 
 
-    def random_element(self):
-        # Temporary workaround for https://trac.sagemath.org/ticket/28327
-        if self.is_trivial():
-            return self.zero()
-        else:
-            s = super(FiniteDimensionalEuclideanJordanAlgebra, self)
-            return s.random_element()
+    def random_elements(self, count):
+        """
+        Return ``count`` random elements as a tuple.
 
+        SETUP::
 
-    @classmethod
-    def random_instance(cls, field=QQ, **kwargs):
-        """
-        Return a random instance of this type of algebra.
+            sage: from mjo.eja.eja_algebra import JordanSpinEJA
 
-        In subclasses for algebras that we know how to construct, this
-        is a shortcut for constructing test cases and examples.
-        """
-        if cls is FiniteDimensionalEuclideanJordanAlgebra:
-            # Red flag! But in theory we could do this I guess. The
-            # only finite-dimensional exceptional EJA is the
-            # octononions. So, we could just create an EJA from an
-            # associative matrix algebra (generated by a subset of
-            # elements) with the symmetric product. Or, we could punt
-            # to random_eja() here, override it in our subclasses, and
-            # not worry about it.
-            raise NotImplementedError
+        EXAMPLES::
 
-        n = ZZ.random_element(1, cls._max_test_case_size())
-        return cls(n, field, **kwargs)
+            sage: J = JordanSpinEJA(3)
+            sage: x,y,z = J.random_elements(3)
+            sage: all( [ x in J, y in J, z in J ])
+            True
+            sage: len( J.random_elements(10) ) == 10
+            True
+
+        """
+        return  tuple( self.random_element() for idx in xrange(count) )
 
 
     def rank(self):
@@ -713,18 +676,12 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule):
         The rank of the `n`-by-`n` Hermitian real, complex, or
         quaternion matrices is `n`::
 
-            sage: RealSymmetricEJA(2).rank()
-            2
-            sage: ComplexHermitianEJA(2).rank()
-            2
+            sage: RealSymmetricEJA(4).rank()
+            4
+            sage: ComplexHermitianEJA(3).rank()
+            3
             sage: QuaternionHermitianEJA(2).rank()
             2
-            sage: RealSymmetricEJA(5).rank()
-            5
-            sage: ComplexHermitianEJA(5).rank()
-            5
-            sage: QuaternionHermitianEJA(5).rank()
-            5
 
         TESTS:
 
@@ -761,7 +718,57 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule):
     Element = FiniteDimensionalEuclideanJordanAlgebraElement
 
 
-class RealCartesianProductEJA(FiniteDimensionalEuclideanJordanAlgebra):
+class KnownRankEJA(object):
+    """
+    A class for algebras that we actually know we can construct.  The
+    main issue is that, for most of our methods to make sense, we need
+    to know the rank of our algebra. Thus we can't simply generate a
+    "random" algebra, or even check that a given basis and product
+    satisfy the axioms; because even if everything looks OK, we wouldn't
+    know the rank we need to actuallty build the thing.
+
+    Not really a subclass of FDEJA because doing that causes method
+    resolution errors, e.g.
+
+      TypeError: Error when calling the metaclass bases
+      Cannot create a consistent method resolution
+      order (MRO) for bases FiniteDimensionalEuclideanJordanAlgebra,
+      KnownRankEJA
+
+    """
+    @staticmethod
+    def _max_test_case_size():
+        """
+        Return an integer "size" that is an upper bound on the size of
+        this algebra when it is used in a random test
+        case. Unfortunately, the term "size" is quite vague -- when
+        dealing with `R^n` under either the Hadamard or Jordan spin
+        product, the "size" refers to the dimension `n`. When dealing
+        with a matrix algebra (real symmetric or complex/quaternion
+        Hermitian), it refers to the size of the matrix, which is
+        far less than the dimension of the underlying vector space.
+
+        We default to five in this class, which is safe in `R^n`. The
+        matrix algebra subclasses (or any class where the "size" is
+        interpreted to be far less than the dimension) should override
+        with a smaller number.
+        """
+        return 5
+
+    @classmethod
+    def random_instance(cls, field=QQ, **kwargs):
+        """
+        Return a random instance of this type of algebra.
+
+        Beware, this will crash for "most instances" because the
+        constructor below looks wrong.
+        """
+        n = ZZ.random_element(cls._max_test_case_size()) + 1
+        return cls(n, field, **kwargs)
+
+
+class RealCartesianProductEJA(FiniteDimensionalEuclideanJordanAlgebra,
+                              KnownRankEJA):
     """
     Return the Euclidean Jordan Algebra corresponding to the set
     `R^n` under the Hadamard product.
@@ -800,21 +807,11 @@ class RealCartesianProductEJA(FiniteDimensionalEuclideanJordanAlgebra):
         sage: RealCartesianProductEJA(3, prefix='r').gens()
         (r0, r1, r2)
 
-    Our inner product satisfies the Jordan axiom::
-
-        sage: set_random_seed()
-        sage: J = RealCartesianProductEJA.random_instance()
-        sage: x = J.random_element()
-        sage: y = J.random_element()
-        sage: z = J.random_element()
-        sage: (x*y).inner_product(z) == y.inner_product(x*z)
-        True
-
     """
     def __init__(self, n, field=QQ, **kwargs):
         V = VectorSpace(field, n)
-        mult_table = [ [ V.gen(i)*(i == j) for j in range(n) ]
-                       for i in range(n) ]
+        mult_table = [ [ V.gen(i)*(i == j) for j in xrange(n) ]
+                       for i in xrange(n) ]
 
         fdeja = super(RealCartesianProductEJA, self)
         return fdeja.__init__(field, mult_table, rank=n, **kwargs)
@@ -834,8 +831,7 @@ class RealCartesianProductEJA(FiniteDimensionalEuclideanJordanAlgebra):
 
             sage: set_random_seed()
             sage: J = RealCartesianProductEJA.random_instance()
-            sage: x = J.random_element()
-            sage: y = J.random_element()
+            sage: x,y = J.random_elements(2)
             sage: X = x.natural_representation()
             sage: Y = y.natural_representation()
             sage: x.inner_product(y) == J.natural_inner_product(X,Y)
@@ -845,7 +841,7 @@ class RealCartesianProductEJA(FiniteDimensionalEuclideanJordanAlgebra):
         return x.to_vector().inner_product(y.to_vector())
 
 
-def random_eja():
+def random_eja(field=QQ):
     """
     Return a "random" finite-dimensional Euclidean Jordan Algebra.
 
@@ -881,12 +877,8 @@ def random_eja():
         Euclidean Jordan algebra of dimension...
 
     """
-    classname = choice([RealCartesianProductEJA,
-                        JordanSpinEJA,
-                        RealSymmetricEJA,
-                        ComplexHermitianEJA,
-                        QuaternionHermitianEJA])
-    return classname.random_instance()
+    classname = choice(KnownRankEJA.__subclasses__())
+    return classname.random_instance(field=field)
 
 
 
@@ -898,16 +890,22 @@ class MatrixEuclideanJordanAlgebra(FiniteDimensionalEuclideanJordanAlgebra):
     def _max_test_case_size():
         # Play it safe, since this will be squared and the underlying
         # field can have dimension 4 (quaternions) too.
-        return 3
+        return 2
 
-    @classmethod
-    def _denormalized_basis(cls, n, field):
-        raise NotImplementedError
+    def __init__(self, field, basis, rank, normalize_basis=True, **kwargs):
+        """
+        Compared to the superclass constructor, we take a basis instead of
+        a multiplication table because the latter can be computed in terms
+        of the former when the product is known (like it is here).
+        """
+        # Used in this class's fast _charpoly_coeff() override.
+        self._basis_normalizers = None
 
-    def __init__(self, n, field=QQ, normalize_basis=True, **kwargs):
-        S = self._denormalized_basis(n, field)
+        # We're going to loop through this a few times, so now's a good
+        # time to ensure that it isn't a generator expression.
+        basis = tuple(basis)
 
-        if n > 1 and normalize_basis:
+        if rank > 1 and normalize_basis:
             # We'll need sqrt(2) to normalize the basis, and this
             # winds up in the multiplication table, so the whole
             # algebra needs to be over the field extension.
@@ -915,22 +913,56 @@ class MatrixEuclideanJordanAlgebra(FiniteDimensionalEuclideanJordanAlgebra):
             z = R.gen()
             p = z**2 - 2
             if p.is_irreducible():
-                field = NumberField(p, 'sqrt2', embedding=RLF(2).sqrt())
-                S = [ s.change_ring(field) for s in S ]
+                field = field.extension(p, 'sqrt2', embedding=RLF(2).sqrt())
+                basis = tuple( s.change_ring(field) for s in basis )
             self._basis_normalizers = tuple(
-                ~(self.natural_inner_product(s,s).sqrt()) for s in S )
-            S = tuple( s*c for (s,c) in zip(S,self._basis_normalizers) )
+                ~(self.natural_inner_product(s,s).sqrt()) for s in basis )
+            basis = tuple(s*c for (s,c) in izip(basis,self._basis_normalizers))
 
-        Qs = self.multiplication_table_from_matrix_basis(S)
+        Qs = self.multiplication_table_from_matrix_basis(basis)
 
         fdeja = super(MatrixEuclideanJordanAlgebra, self)
         return fdeja.__init__(field,
                               Qs,
-                              rank=n,
-                              natural_basis=S,
+                              rank=rank,
+                              natural_basis=basis,
                               **kwargs)
 
 
+    @cached_method
+    def _charpoly_coeff(self, i):
+        """
+        Override the parent method with something that tries to compute
+        over a faster (non-extension) field.
+        """
+        if self._basis_normalizers is None:
+            # We didn't normalize, so assume that the basis we started
+            # 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) )
+
+            # Do this over the rationals and convert back at the end.
+            J = MatrixEuclideanJordanAlgebra(QQ,
+                                             basis,
+                                             self.rank(),
+                                             normalize_basis=False)
+            (_,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)
+            substitutions = { v: v*c for (v,c) in pairs }
+            result = p.subs(substitutions)
+
+            # The result of "subs" can be either a coefficient-ring
+            # element or a polynomial. Gotta handle both cases.
+            if result in QQ:
+                return self.base_ring()(result)
+            else:
+                return result.change_ring(self.base_ring())
+
+
     @staticmethod
     def multiplication_table_from_matrix_basis(basis):
         """
@@ -952,9 +984,9 @@ class MatrixEuclideanJordanAlgebra(FiniteDimensionalEuclideanJordanAlgebra):
         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 range(n)] for i in range(n)]
-        for i in range(n):
-            for j in range(n):
+        mult_table = [[W.zero() for j in xrange(n)] for i in xrange(n)]
+        for i in xrange(n):
+            for j in xrange(n):
                 mat_entry = (basis[i]*basis[j] + basis[j]*basis[i])/2
                 mult_table[i][j] = W.coordinate_vector(_mat2vec(mat_entry))
 
@@ -991,6 +1023,7 @@ class MatrixEuclideanJordanAlgebra(FiniteDimensionalEuclideanJordanAlgebra):
         Xu = cls.real_unembed(X)
         Yu = cls.real_unembed(Y)
         tr = (Xu*Yu).trace()
+
         if tr in RLF:
             # It's real already.
             return tr
@@ -1006,7 +1039,25 @@ class MatrixEuclideanJordanAlgebra(FiniteDimensionalEuclideanJordanAlgebra):
             return tr.coefficient_tuple()[0]
 
 
-class RealSymmetricEJA(MatrixEuclideanJordanAlgebra):
+class RealMatrixEuclideanJordanAlgebra(MatrixEuclideanJordanAlgebra):
+    @staticmethod
+    def real_embed(M):
+        """
+        The identity function, for embedding real matrices into real
+        matrices.
+        """
+        return M
+
+    @staticmethod
+    def real_unembed(M):
+        """
+        The identity function, for unembedding real matrices from real
+        matrices.
+        """
+        return M
+
+
+class RealSymmetricEJA(RealMatrixEuclideanJordanAlgebra, KnownRankEJA):
     """
     The rank-n simple EJA consisting of real symmetric n-by-n
     matrices, the usual symmetric Jordan product, and the trace inner
@@ -1027,6 +1078,14 @@ class RealSymmetricEJA(MatrixEuclideanJordanAlgebra):
         sage: e2*e2
         e2
 
+    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, RR)
+        Euclidean Jordan algebra of dimension 3 over Real Field with
+        53 bits of precision
+
     TESTS:
 
     The dimension of this algebra is `(n^2 + n) / 2`::
@@ -1042,8 +1101,7 @@ class RealSymmetricEJA(MatrixEuclideanJordanAlgebra):
 
         sage: set_random_seed()
         sage: J = RealSymmetricEJA.random_instance()
-        sage: x = J.random_element()
-        sage: y = J.random_element()
+        sage: x,y = J.random_elements(2)
         sage: actual = (x*y).natural_representation()
         sage: X = x.natural_representation()
         sage: Y = y.natural_representation()
@@ -1058,16 +1116,6 @@ class RealSymmetricEJA(MatrixEuclideanJordanAlgebra):
         sage: RealSymmetricEJA(3, prefix='q').gens()
         (q0, q1, q2, q3, q4, q5)
 
-    Our inner product satisfies the Jordan axiom::
-
-        sage: set_random_seed()
-        sage: J = RealSymmetricEJA.random_instance()
-        sage: x = J.random_element()
-        sage: y = J.random_element()
-        sage: z = J.random_element()
-        sage: (x*y).inner_product(z) == y.inner_product(x*z)
-        True
-
     Our natural basis is normalized with respect to the natural inner
     product unless we specify otherwise::
 
@@ -1117,37 +1165,17 @@ class RealSymmetricEJA(MatrixEuclideanJordanAlgebra):
                 else:
                     Sij = Eij + Eij.transpose()
                 S.append(Sij)
-        return tuple(S)
+        return S
 
 
     @staticmethod
     def _max_test_case_size():
-        return 5 # Dimension 10
-
-    @staticmethod
-    def real_embed(M):
-        """
-        Embed the matrix ``M`` into a space of real matrices.
-
-        The matrix ``M`` can have entries in any field at the moment:
-        the real numbers, complex numbers, or quaternions. And although
-        they are not a field, we can probably support octonions at some
-        point, too. This function returns a real matrix that "acts like"
-        the original with respect to matrix multiplication; i.e.
-
-          real_embed(M*N) = real_embed(M)*real_embed(N)
-
-        """
-        return M
+        return 4 # Dimension 10
 
 
-    @staticmethod
-    def real_unembed(M):
-        """
-        The inverse of :meth:`real_embed`.
-        """
-        return M
-
+    def __init__(self, n, field=QQ, **kwargs):
+        basis = self._denormalized_basis(n, field)
+        super(RealSymmetricEJA, self).__init__(field, basis, n, **kwargs)
 
 
 class ComplexMatrixEuclideanJordanAlgebra(MatrixEuclideanJordanAlgebra):
@@ -1198,15 +1226,17 @@ class ComplexMatrixEuclideanJordanAlgebra(MatrixEuclideanJordanAlgebra):
         n = M.nrows()
         if M.ncols() != n:
             raise ValueError("the matrix 'M' must be square")
-        field = M.base_ring()
+
+        # We don't need any adjoined elements...
+        field = M.base_ring().base_ring()
+
         blocks = []
         for z in M.list():
-            a = z.vector()[0] # real part, I guess
-            b = z.vector()[1] # imag part, I guess
+            a = z.list()[0] # real part, I guess
+            b = z.list()[1] # imag part, I guess
             blocks.append(matrix(field, 2, [[a,b],[-b,a]]))
 
-        # We can drop the imaginaries here.
-        return matrix.block(field.base_ring(), n, blocks)
+        return matrix.block(field, n, blocks)
 
 
     @staticmethod
@@ -1247,10 +1277,12 @@ class ComplexMatrixEuclideanJordanAlgebra(MatrixEuclideanJordanAlgebra):
         if not n.mod(2).is_zero():
             raise ValueError("the matrix 'M' must be a complex embedding")
 
-        field = M.base_ring() # This should already have sqrt2
+        # If "M" was normalized, its base ring might have roots
+        # adjoined and they can stick around after unembedding.
+        field = M.base_ring()
         R = PolynomialRing(field, 'z')
         z = R.gen()
-        F = NumberField(z**2 + 1,'i', embedding=CLF(-1).sqrt())
+        F = field.extension(z**2 + 1, 'i', embedding=CLF(-1).sqrt())
         i = F.gen()
 
         # Go top-left to bottom-right (reading order), converting every
@@ -1269,7 +1301,38 @@ class ComplexMatrixEuclideanJordanAlgebra(MatrixEuclideanJordanAlgebra):
         return matrix(F, n/2, elements)
 
 
-class ComplexHermitianEJA(ComplexMatrixEuclideanJordanAlgebra):
+    @classmethod
+    def natural_inner_product(cls,X,Y):
+        """
+        Compute a natural inner product in this algebra directly from
+        its real embedding.
+
+        SETUP::
+
+            sage: from mjo.eja.eja_algebra import ComplexHermitianEJA
+
+        TESTS:
+
+        This gives the same answer as the slow, default method implemented
+        in :class:`MatrixEuclideanJordanAlgebra`::
+
+            sage: set_random_seed()
+            sage: J = ComplexHermitianEJA.random_instance()
+            sage: x,y = J.random_elements(2)
+            sage: Xe = x.natural_representation()
+            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: actual = ComplexHermitianEJA.natural_inner_product(Xe,Ye)
+            sage: actual == expected
+            True
+
+        """
+        return RealMatrixEuclideanJordanAlgebra.natural_inner_product(X,Y)/2
+
+
+class ComplexHermitianEJA(ComplexMatrixEuclideanJordanAlgebra, KnownRankEJA):
     """
     The rank-n simple EJA consisting of complex Hermitian n-by-n
     matrices over the real numbers, the usual symmetric Jordan product,
@@ -1280,6 +1343,16 @@ class ComplexHermitianEJA(ComplexMatrixEuclideanJordanAlgebra):
 
         sage: from mjo.eja.eja_algebra import ComplexHermitianEJA
 
+    EXAMPLES:
+
+    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, RR)
+        Euclidean Jordan algebra of dimension 4 over Real Field with
+        53 bits of precision
+
     TESTS:
 
     The dimension of this algebra is `n^2`::
@@ -1295,8 +1368,7 @@ class ComplexHermitianEJA(ComplexMatrixEuclideanJordanAlgebra):
 
         sage: set_random_seed()
         sage: J = ComplexHermitianEJA.random_instance()
-        sage: x = J.random_element()
-        sage: y = J.random_element()
+        sage: x,y = J.random_elements(2)
         sage: actual = (x*y).natural_representation()
         sage: X = x.natural_representation()
         sage: Y = y.natural_representation()
@@ -1311,16 +1383,6 @@ class ComplexHermitianEJA(ComplexMatrixEuclideanJordanAlgebra):
         sage: ComplexHermitianEJA(2, prefix='z').gens()
         (z0, z1, z2, z3)
 
-    Our inner product satisfies the Jordan axiom::
-
-        sage: set_random_seed()
-        sage: J = ComplexHermitianEJA.random_instance()
-        sage: x = J.random_element()
-        sage: y = J.random_element()
-        sage: z = J.random_element()
-        sage: (x*y).inner_product(z) == y.inner_product(x*z)
-        True
-
     Our natural basis is normalized with respect to the natural inner
     product unless we specify otherwise::
 
@@ -1341,6 +1403,7 @@ class ComplexHermitianEJA(ComplexMatrixEuclideanJordanAlgebra):
         True
 
     """
+
     @classmethod
     def _denormalized_basis(cls, n, field):
         """
@@ -1368,7 +1431,7 @@ class ComplexHermitianEJA(ComplexMatrixEuclideanJordanAlgebra):
         """
         R = PolynomialRing(field, 'z')
         z = R.gen()
-        F = NumberField(z**2 + 1, 'I', embedding=CLF(-1).sqrt())
+        F = field.extension(z**2 + 1, 'I')
         I = F.gen()
 
         # This is like the symmetric case, but we need to be careful:
@@ -1392,9 +1455,13 @@ class ComplexHermitianEJA(ComplexMatrixEuclideanJordanAlgebra):
 
         # Since we embedded these, we can drop back to the "field" that we
         # started with instead of the complex extension "F".
-        return tuple( s.change_ring(field) for s in S )
+        return ( s.change_ring(field) for s in S )
 
 
+    def __init__(self, n, field=QQ, **kwargs):
+        basis = self._denormalized_basis(n,field)
+        super(ComplexHermitianEJA,self).__init__(field, basis, n, **kwargs)
+
 
 class QuaternionMatrixEuclideanJordanAlgebra(MatrixEuclideanJordanAlgebra):
     @staticmethod
@@ -1499,7 +1566,7 @@ class QuaternionMatrixEuclideanJordanAlgebra(MatrixEuclideanJordanAlgebra):
         if M.ncols() != n:
             raise ValueError("the matrix 'M' must be square")
         if not n.mod(4).is_zero():
-            raise ValueError("the matrix 'M' must be a complex embedding")
+            raise ValueError("the matrix 'M' must be a quaternion embedding")
 
         # Use the base ring of the matrix to ensure that its entries can be
         # multiplied by elements of the quaternion algebra.
@@ -1528,8 +1595,39 @@ class QuaternionMatrixEuclideanJordanAlgebra(MatrixEuclideanJordanAlgebra):
         return matrix(Q, n/4, elements)
 
 
+    @classmethod
+    def natural_inner_product(cls,X,Y):
+        """
+        Compute a natural inner product in this algebra directly from
+        its real embedding.
+
+        SETUP::
+
+            sage: from mjo.eja.eja_algebra import QuaternionHermitianEJA
+
+        TESTS:
 
-class QuaternionHermitianEJA(QuaternionMatrixEuclideanJordanAlgebra):
+        This gives the same answer as the slow, default method implemented
+        in :class:`MatrixEuclideanJordanAlgebra`::
+
+            sage: set_random_seed()
+            sage: J = QuaternionHermitianEJA.random_instance()
+            sage: x,y = J.random_elements(2)
+            sage: Xe = x.natural_representation()
+            sage: Ye = y.natural_representation()
+            sage: X = QuaternionHermitianEJA.real_unembed(Xe)
+            sage: Y = QuaternionHermitianEJA.real_unembed(Ye)
+            sage: expected = (X*Y).trace().coefficient_tuple()[0]
+            sage: actual = QuaternionHermitianEJA.natural_inner_product(Xe,Ye)
+            sage: actual == expected
+            True
+
+        """
+        return RealMatrixEuclideanJordanAlgebra.natural_inner_product(X,Y)/4
+
+
+class QuaternionHermitianEJA(QuaternionMatrixEuclideanJordanAlgebra,
+                             KnownRankEJA):
     """
     The rank-n simple EJA consisting of self-adjoint n-by-n quaternion
     matrices, the usual symmetric Jordan product, and the
@@ -1540,6 +1638,16 @@ class QuaternionHermitianEJA(QuaternionMatrixEuclideanJordanAlgebra):
 
         sage: from mjo.eja.eja_algebra import QuaternionHermitianEJA
 
+    EXAMPLES:
+
+    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, RR)
+        Euclidean Jordan algebra of dimension 6 over Real Field with
+        53 bits of precision
+
     TESTS:
 
     The dimension of this algebra is `2*n^2 - n`::
@@ -1555,8 +1663,7 @@ class QuaternionHermitianEJA(QuaternionMatrixEuclideanJordanAlgebra):
 
         sage: set_random_seed()
         sage: J = QuaternionHermitianEJA.random_instance()
-        sage: x = J.random_element()
-        sage: y = J.random_element()
+        sage: x,y = J.random_elements(2)
         sage: actual = (x*y).natural_representation()
         sage: X = x.natural_representation()
         sage: Y = y.natural_representation()
@@ -1571,16 +1678,6 @@ class QuaternionHermitianEJA(QuaternionMatrixEuclideanJordanAlgebra):
         sage: QuaternionHermitianEJA(2, prefix='a').gens()
         (a0, a1, a2, a3, a4, a5)
 
-    Our inner product satisfies the Jordan axiom::
-
-        sage: set_random_seed()
-        sage: J = QuaternionHermitianEJA.random_instance()
-        sage: x = J.random_element()
-        sage: y = J.random_element()
-        sage: z = J.random_element()
-        sage: (x*y).inner_product(z) == y.inner_product(x*z)
-        True
-
     Our natural basis is normalized with respect to the natural inner
     product unless we specify otherwise::
 
@@ -1651,11 +1748,18 @@ class QuaternionHermitianEJA(QuaternionMatrixEuclideanJordanAlgebra):
                     S.append(Sij_J)
                     Sij_K = cls.real_embed(K*Eij - K*Eij.transpose())
                     S.append(Sij_K)
-        return tuple(S)
 
+        # Since we embedded these, we can drop back to the "field" that we
+        # started with instead of the quaternion algebra "Q".
+        return ( s.change_ring(field) for s in S )
+
+
+    def __init__(self, n, field=QQ, **kwargs):
+        basis = self._denormalized_basis(n,field)
+        super(QuaternionHermitianEJA,self).__init__(field, basis, n, **kwargs)
 
 
-class JordanSpinEJA(FiniteDimensionalEuclideanJordanAlgebra):
+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 =
@@ -1692,22 +1796,12 @@ class JordanSpinEJA(FiniteDimensionalEuclideanJordanAlgebra):
         sage: JordanSpinEJA(2, prefix='B').gens()
         (B0, B1)
 
-    Our inner product satisfies the Jordan axiom::
-
-        sage: set_random_seed()
-        sage: J = JordanSpinEJA.random_instance()
-        sage: x = J.random_element()
-        sage: y = J.random_element()
-        sage: z = J.random_element()
-        sage: (x*y).inner_product(z) == y.inner_product(x*z)
-        True
-
     """
     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):
+        mult_table = [[V.zero() for j in xrange(n)] for i in xrange(n)]
+        for i in xrange(n):
+            for j in xrange(n):
                 x = V.gen(i)
                 y = V.gen(j)
                 x0 = x[0]
@@ -1741,8 +1835,7 @@ class JordanSpinEJA(FiniteDimensionalEuclideanJordanAlgebra):
 
             sage: set_random_seed()
             sage: J = JordanSpinEJA.random_instance()
-            sage: x = J.random_element()
-            sage: y = J.random_element()
+            sage: x,y = J.random_elements(2)
             sage: X = x.natural_representation()
             sage: Y = y.natural_representation()
             sage: x.inner_product(y) == J.natural_inner_product(X,Y)