]> gitweb.michael.orlitzky.com - sage.d.git/blobdiff - mjo/eja/eja_algebra.py
eja: rename operator_inner_product -> operator_trace inner_product.
[sage.d.git] / mjo / eja / eja_algebra.py
index fb016462abaef5b05e24defe19810855de4040bd..adcc3436b1302e09cd20007d0525aee08e32a48f 100644 (file)
@@ -1,4 +1,4 @@
-"""
+r"""
 Representations and constructions for Euclidean Jordan algebras.
 
 A Euclidean Jordan algebra is a Jordan algebra that has some
@@ -34,12 +34,13 @@ for these simple algebras:
   * :class:`QuaternionHermitianEJA`
   * :class:`OctonionHermitianEJA`
 
-In addition to these, we provide two other example constructions,
+In addition to these, we provide a few other example constructions,
 
   * :class:`JordanSpinEJA`
   * :class:`HadamardEJA`
   * :class:`AlbertEJA`
   * :class:`TrivialEJA`
+  * :class:`ComplexSkewSymmetricEJA`
 
 The Jordan spin algebra is a bilinear form algebra where the bilinear
 form is the identity. The Hadamard EJA is simply a Cartesian product
@@ -71,18 +72,18 @@ matrix, whereas the inner product must return a scalar. Our basis for
 the one-by-one matrices is of course the set consisting of a single
 matrix with its sole entry non-zero::
 
-    sage: from mjo.eja.eja_algebra import FiniteDimensionalEJA
+    sage: from mjo.eja.eja_algebra import EJA
     sage: jp = lambda X,Y: X*Y
     sage: ip = lambda X,Y: X[0,0]*Y[0,0]
     sage: b1 = matrix(AA, [[1]])
-    sage: J1 = FiniteDimensionalEJA((b1,), jp, ip)
+    sage: J1 = EJA((b1,), jp, ip)
     sage: J1
     Euclidean Jordan algebra of dimension 1 over Algebraic Real Field
 
 In fact, any positive scalar multiple of that inner-product would work::
 
     sage: ip2 = lambda X,Y: 16*ip(X,Y)
-    sage: J2 = FiniteDimensionalEJA((b1,), jp, ip2)
+    sage: J2 = EJA((b1,), jp, ip2)
     sage: J2
     Euclidean Jordan algebra of dimension 1 over Algebraic Real Field
 
@@ -90,7 +91,7 @@ But beware that your basis will be orthonormalized _with respect to the
 given inner-product_ unless you pass ``orthonormalize=False`` to the
 constructor. For example::
 
-    sage: J3 = FiniteDimensionalEJA((b1,), jp, ip2, orthonormalize=False)
+    sage: J3 = EJA((b1,), jp, ip2, orthonormalize=False)
     sage: J3
     Euclidean Jordan algebra of dimension 1 over Algebraic Real Field
 
@@ -117,7 +118,7 @@ Another option for your basis is to use elemebts of a
 
     sage: from mjo.matrix_algebra import MatrixAlgebra
     sage: A = MatrixAlgebra(1,AA,AA)
-    sage: J4 = FiniteDimensionalEJA(A.gens(), jp, ip)
+    sage: J4 = EJA(A.gens(), jp, ip)
     sage: J4
     Euclidean Jordan algebra of dimension 1 over Algebraic Real Field
     sage: J4.basis()[0].to_matrix()
@@ -167,8 +168,8 @@ from sage.rings.all import (ZZ, QQ, AA, QQbar, RR, RLF, CLF,
                             PolynomialRing,
                             QuadraticField)
 from mjo.eja.eja_element import (CartesianProductEJAElement,
-                                 FiniteDimensionalEJAElement)
-from mjo.eja.eja_operator import FiniteDimensionalEJAOperator
+                                 EJAElement)
+from mjo.eja.eja_operator import EJAOperator
 from mjo.eja.eja_utils import _all2list
 
 def EuclideanJordanAlgebras(field):
@@ -182,7 +183,7 @@ def EuclideanJordanAlgebras(field):
     category = category.WithBasis().Unital().Commutative()
     return category
 
-class FiniteDimensionalEJA(CombinatorialFreeModule):
+class EJA(CombinatorialFreeModule):
     r"""
     A finite-dimensional Euclidean Jordan algebra.
 
@@ -237,7 +238,7 @@ class FiniteDimensionalEJA(CombinatorialFreeModule):
         sage: J.subalgebra(basis, orthonormalize=False).is_associative()
         True
     """
-    Element = FiniteDimensionalEJAElement
+    Element = EJAElement
 
     @staticmethod
     def _check_input_field(field):
@@ -1193,7 +1194,7 @@ class FiniteDimensionalEJA(CombinatorialFreeModule):
             sage: x = J.random_element()
             sage: J.one()*x == x and x*J.one() == x
             True
-            sage: A = x.subalgebra_generated_by()
+            sage: A = x.subalgebra_generated_by(orthonormalize=False)
             sage: y = A.random_element()
             sage: A.one()*y == y and y*A.one() == y
             True
@@ -1219,7 +1220,7 @@ class FiniteDimensionalEJA(CombinatorialFreeModule):
             sage: actual == expected
             True
             sage: x = J.random_element()
-            sage: A = x.subalgebra_generated_by()
+            sage: A = x.subalgebra_generated_by(orthonormalize=False)
             sage: actual = A.one().operator().matrix()
             sage: expected = matrix.identity(A.base_ring(), A.dimension())
             sage: actual == expected
@@ -1447,26 +1448,13 @@ class FiniteDimensionalEJA(CombinatorialFreeModule):
         # For a general base ring... maybe we can trust this to do the
         # right thing? Unlikely, but.
         V = self.vector_space()
-        v = V.random_element()
-
-        if self.base_ring() is AA:
-            # The "random element" method of the algebraic reals is
-            # stupid at the moment, and only returns integers between
-            # -2 and 2, inclusive:
-            #
-            #   https://trac.sagemath.org/ticket/30875
-            #
-            # Instead, we implement our own "random vector" method,
-            # and then coerce that into the algebra. We use the vector
-            # space degree here instead of the dimension because a
-            # subalgebra could (for example) be spanned by only two
-            # vectors, each with five coordinates.  We need to
-            # generate all five coordinates.
-            if thorough:
-                v *= QQbar.random_element().real()
-            else:
-                v *= QQ.random_element()
+        if self.base_ring() is AA and not thorough:
+            # Now that AA generates actually random random elements
+            # (post Trac 30875), we only need to de-thorough the
+            # randomness when asked to.
+            V = V.change_ring(QQ)
 
+        v = V.random_element()
         return self.from_vector(V.coordinate_vector(v))
 
     def random_elements(self, count, thorough=False):
@@ -1686,8 +1674,8 @@ class FiniteDimensionalEJA(CombinatorialFreeModule):
         r"""
         Create a subalgebra of this algebra from the given basis.
         """
-        from mjo.eja.eja_subalgebra import FiniteDimensionalEJASubalgebra
-        return FiniteDimensionalEJASubalgebra(self, basis, **kwargs)
+        from mjo.eja.eja_subalgebra import EJASubalgebra
+        return EJASubalgebra(self, basis, **kwargs)
 
 
     def vector_space(self):
@@ -1709,7 +1697,7 @@ class FiniteDimensionalEJA(CombinatorialFreeModule):
 
 
 
-class RationalBasisEJA(FiniteDimensionalEJA):
+class RationalBasisEJA(EJA):
     r"""
     Algebras whose supplied basis elements have all rational entries.
 
@@ -1764,7 +1752,7 @@ class RationalBasisEJA(FiniteDimensionalEJA):
             # Note: the same Jordan and inner-products work here,
             # because they are necessarily defined with respect to
             # ambient coordinates and not any particular basis.
-            self._rational_algebra = FiniteDimensionalEJA(
+            self._rational_algebra = EJA(
                                        basis,
                                        jordan_product,
                                        inner_product,
@@ -1812,14 +1800,13 @@ class RationalBasisEJA(FiniteDimensionalEJA):
             # Bypass the hijinks if they won't benefit us.
             return super()._charpoly_coefficients()
 
-        # Do the computation over the rationals. The answer will be
-        # the same, because all we've done is a change of basis.
-        # Then, change back from QQ to our real base ring
+        # Do the computation over the rationals.
         a = ( a_i.change_ring(self.base_ring())
               for a_i in self.rational_algebra()._charpoly_coefficients() )
 
-        # Otherwise, convert the coordinate variables back to the
-        # deorthonormalized ones.
+        # Convert our coordinate variables into deorthonormalized ones
+        # and substitute them into the deorthonormalized charpoly
+        # coefficients.
         R = self.coordinate_polynomial_ring()
         from sage.modules.free_module_element import vector
         X = vector(R, R.gens())
@@ -1828,7 +1815,7 @@ class RationalBasisEJA(FiniteDimensionalEJA):
         subs_dict = { X[i]: BX[i] for i in range(len(X)) }
         return tuple( a_i.subs(subs_dict) for a_i in a )
 
-class ConcreteEJA(FiniteDimensionalEJA):
+class ConcreteEJA(EJA):
     r"""
     A class for the Euclidean Jordan algebras that we know by name.
 
@@ -1929,7 +1916,7 @@ class ConcreteEJA(FiniteDimensionalEJA):
         return eja_class.random_instance(max_dimension, *args, **kwargs)
 
 
-class HermitianMatrixEJA(FiniteDimensionalEJA):
+class HermitianMatrixEJA(EJA):
     @staticmethod
     def _denormalized_basis(A):
         """
@@ -2200,15 +2187,6 @@ class ComplexHermitianEJA(HermitianMatrixEJA, RationalBasisEJA, ConcreteEJA):
         ...
         TypeError: Illegal initializer for algebraic number
 
-    This causes the following error when we try to scale a matrix of
-    complex numbers by an inexact real number::
-
-        sage: ComplexHermitianEJA(2,field=RR)
-        Traceback (most recent call last):
-        ...
-        TypeError: Unable to coerce entries (=(1.00000000000000,
-        -0.000000000000000)) to coefficients in Algebraic Real Field
-
     TESTS:
 
     The dimension of this algebra is `n^2`::
@@ -2364,7 +2342,7 @@ class OctonionHermitianEJA(HermitianMatrixEJA, RationalBasisEJA, ConcreteEJA):
     r"""
     SETUP::
 
-        sage: from mjo.eja.eja_algebra import (FiniteDimensionalEJA,
+        sage: from mjo.eja.eja_algebra import (EJA,
         ....:                                  OctonionHermitianEJA)
         sage: from mjo.hurwitz import Octonions, OctonionMatrixAlgebra
 
@@ -2386,7 +2364,7 @@ class OctonionHermitianEJA(HermitianMatrixEJA, RationalBasisEJA, ConcreteEJA):
         sage: basis = (b[0] + b[9],) + b[1:9] + (b[0] - b[9],)
         sage: jp = OctonionHermitianEJA.jordan_product
         sage: ip = OctonionHermitianEJA.trace_inner_product
-        sage: J = FiniteDimensionalEJA(basis,
+        sage: J = EJA(basis,
         ....:                          jp,
         ....:                          ip,
         ....:                          field=QQ,
@@ -2450,7 +2428,7 @@ class OctonionHermitianEJA(HermitianMatrixEJA, RationalBasisEJA, ConcreteEJA):
     @staticmethod
     def _max_random_instance_size(max_dimension):
         r"""
-        The maximum rank of a random QuaternionHermitianEJA.
+        The maximum rank of a random OctonionHermitianEJA.
         """
         # There's certainly a formula for this, but with only four
         # cases to worry about, I'm not that motivated to derive it.
@@ -2936,7 +2914,7 @@ class TrivialEJA(RationalBasisEJA, ConcreteEJA):
         return cls(**kwargs)
 
 
-class CartesianProductEJA(FiniteDimensionalEJA):
+class CartesianProductEJA(EJA):
     r"""
     The external (orthogonal) direct sum of two or more Euclidean
     Jordan algebras. Every Euclidean Jordan algebra decomposes into an
@@ -3327,7 +3305,7 @@ class CartesianProductEJA(FiniteDimensionalEJA):
         Pi = self._module_morphism(lambda j: Ji.monomial(j - offset),
                                    codomain=Ji)
 
-        return FiniteDimensionalEJAOperator(self,Ji,Pi.matrix())
+        return EJAOperator(self,Ji,Pi.matrix())
 
     @cached_method
     def cartesian_embedding(self, i):
@@ -3435,11 +3413,39 @@ class CartesianProductEJA(FiniteDimensionalEJA):
         Ji = self.cartesian_factor(i)
         Ei = Ji._module_morphism(lambda j: self.monomial(j + offset),
                                  codomain=self)
-        return FiniteDimensionalEJAOperator(Ji,self,Ei.matrix())
+        return EJAOperator(Ji,self,Ei.matrix())
+
+
+    def subalgebra(self, basis, **kwargs):
+        r"""
+        Create a subalgebra of this algebra from the given basis.
+
+        Only overridden to allow us to use a special Cartesian product
+        subalgebra class.
+
+        SETUP::
+
+            sage: from mjo.eja.eja_algebra import (HadamardEJA,
+            ....:                                  QuaternionHermitianEJA)
+
+        EXAMPLES:
 
+        Subalgebras of Cartesian product EJAs have a different class
+        than those of non-Cartesian-product EJAs::
 
+            sage: J1 = HadamardEJA(2,field=QQ,orthonormalize=False)
+            sage: J2 = QuaternionHermitianEJA(0,field=QQ,orthonormalize=False)
+            sage: J = cartesian_product([J1,J2])
+            sage: K1 = J1.subalgebra((J1.one(),), orthonormalize=False)
+            sage: K = J.subalgebra((J.one(),), orthonormalize=False)
+            sage: K1.__class__ is K.__class__
+            False
+
+        """
+        from mjo.eja.eja_subalgebra import CartesianProductEJASubalgebra
+        return CartesianProductEJASubalgebra(self, basis, **kwargs)
 
-FiniteDimensionalEJA.CartesianProduct = CartesianProductEJA
+EJA.CartesianProduct = CartesianProductEJA
 
 class RationalBasisCartesianProductEJA(CartesianProductEJA,
                                        RationalBasisEJA):
@@ -3449,7 +3455,7 @@ class RationalBasisCartesianProductEJA(CartesianProductEJA,
 
     SETUP::
 
-        sage: from mjo.eja.eja_algebra import (FiniteDimensionalEJA,
+        sage: from mjo.eja.eja_algebra import (EJA,
         ....:                                  HadamardEJA,
         ....:                                  JordanSpinEJA,
         ....:                                  RealSymmetricEJA)
@@ -3479,7 +3485,7 @@ class RationalBasisCartesianProductEJA(CartesianProductEJA,
         sage: jp = lambda X,Y: X*Y
         sage: ip = lambda X,Y: X[0,0]*Y[0,0]
         sage: b1 = matrix(QQ, [[1]])
-        sage: J2 = FiniteDimensionalEJA((b1,), jp, ip)
+        sage: J2 = EJA((b1,), jp, ip)
         sage: cartesian_product([J2,J1]) # factor one not RationalBasisEJA
         Euclidean Jordan algebra of dimension 1 over Algebraic Real
         Field (+) Euclidean Jordan algebra of dimension 2 over Algebraic
@@ -3544,24 +3550,124 @@ def random_eja(max_dimension=None, *args, **kwargs):
         return cartesian_product([J1,J2])
 
 
-class ComplexSkewHermitianEJA(RationalBasisEJA):
+class ComplexSkewSymmetricEJA(RationalBasisEJA, ConcreteEJA):
     r"""
-    The EJA described in Faraut and Koranyi's Exercise III.1.b.
+    The skew-symmetric EJA of order `2n` described in Faraut and
+    Koranyi's Exercise III.1.b. It has dimension `2n^2 - n`.
+
+    It is (not obviously) isomorphic to the QuaternionHermitianEJA of
+    order `n`, as can be inferred by comparing rank/dimension or
+    explicitly from their "characteristic polynomial of" functions,
+    which just so happen to align nicely.
+
+    SETUP::
+
+        sage: from mjo.eja.eja_algebra import (ComplexSkewSymmetricEJA,
+        ....:                                  QuaternionHermitianEJA)
+        sage: from mjo.eja.eja_operator import EJAOperator
+
+    EXAMPLES:
+
+    This EJA is isomorphic to the quaternions::
+
+        sage: J = ComplexSkewSymmetricEJA(2, field=QQ, orthonormalize=False)
+        sage: K = QuaternionHermitianEJA(2, field=QQ, orthonormalize=False)
+        sage: jordan_isom_matrix = matrix.diagonal(QQ,[-1,1,1,1,1,-1])
+        sage: phi = EJAOperator(J,K,jordan_isom_matrix)
+        sage: all( phi(x*y) == phi(x)*phi(y)
+        ....:      for x in J.gens()
+        ....:      for y in J.gens() )
+        True
+        sage: x,y = J.random_elements(2)
+        sage: phi(x*y) == phi(x)*phi(y)
+        True
+
+    TESTS:
+
+    Random elements should satisfy the same conditions that the basis
+    elements do::
+
+        sage: K = ComplexSkewSymmetricEJA.random_instance(field=QQ,
+        ....:                                             orthonormalize=False)
+        sage: x,y = K.random_elements(2)
+        sage: z = x*y
+        sage: x = x.to_matrix()
+        sage: y = y.to_matrix()
+        sage: z = z.to_matrix()
+        sage: all( e.is_skew_symmetric() for e in (x,y,z) )
+        True
+        sage: J = -K.one().to_matrix()
+        sage: all( e*J == J*e.conjugate() for e in (x,y,z) )
+        True
+
+    The power law in Faraut & Koranyi's II.7.a is satisfied.
+    We're in a subalgebra of theirs, but powers are still
+    defined the same::
+
+        sage: K = ComplexSkewSymmetricEJA.random_instance(field=QQ,
+        ....:                                             orthonormalize=False)
+        sage: x = K.random_element()
+        sage: k = ZZ.random_element(5)
+        sage: actual = x^k
+        sage: J = -K.one().to_matrix()
+        sage: expected = K(-J*(J*x.to_matrix())^k)
+        sage: actual == expected
+        True
+
     """
+    @staticmethod
+    def _max_random_instance_size(max_dimension):
+        # Obtained by solving d = 2n^2 - n, which comes from noticing
+        # that, in 2x2 block form, any element of this algebra has a
+        # free skew-symmetric top-left block, a Hermitian top-right
+        # block, and two bottom blocks that are determined by the top.
+        # The ZZ-int-ZZ thing is just "floor."
+        return ZZ(int(ZZ(8*max_dimension + 1).sqrt()/4 + 1/4))
+
+    @classmethod
+    def random_instance(cls, max_dimension=None, *args, **kwargs):
+        """
+        Return a random instance of this type of algebra.
+        """
+        class_max_d = cls._max_random_instance_dimension()
+        if (max_dimension is None or max_dimension > class_max_d):
+            max_dimension = class_max_d
+        max_size = cls._max_random_instance_size(max_dimension)
+        n = ZZ.random_element(max_size + 1)
+        return cls(n, **kwargs)
+
     @staticmethod
     def _denormalized_basis(A):
         """
         SETUP::
 
             sage: from mjo.hurwitz import ComplexMatrixAlgebra
-            sage: from mjo.eja.eja_algebra import ComplexSkewHermitianEJA
+            sage: from mjo.eja.eja_algebra import ComplexSkewSymmetricEJA
 
-        TESTS::
+        TESTS:
 
-            sage: n = ZZ.random_element(1,2)
+        The basis elements are all skew-Hermitian::
+
+            sage: d_max = ComplexSkewSymmetricEJA._max_random_instance_dimension()
+            sage: n_max = ComplexSkewSymmetricEJA._max_random_instance_size(d_max)
+            sage: n = ZZ.random_element(n_max + 1)
             sage: A = ComplexMatrixAlgebra(2*n, scalars=QQ)
-            sage: B = ComplexSkewHermitianEJA._denormalized_basis(A)
-            sage: all( M.is_skew_hermitian() for M in  B)
+            sage: B = ComplexSkewSymmetricEJA._denormalized_basis(A)
+            sage: all( M.is_skew_symmetric() for M in  B)
+            True
+
+        The basis elements ``b`` all satisfy ``b*J == J*b.conjugate()``,
+        as in the definition of the algebra::
+
+            sage: d_max = ComplexSkewSymmetricEJA._max_random_instance_dimension()
+            sage: n_max = ComplexSkewSymmetricEJA._max_random_instance_size(d_max)
+            sage: n = ZZ.random_element(n_max + 1)
+            sage: A = ComplexMatrixAlgebra(2*n, scalars=QQ)
+            sage: I_n = matrix.identity(ZZ, n)
+            sage: J = matrix.block(ZZ, 2, 2, (0, I_n, -I_n, 0), subdivide=False)
+            sage: J = A.from_list(J.rows())
+            sage: B = ComplexSkewSymmetricEJA._denormalized_basis(A)
+            sage: all( b*J == J*b.conjugate()  for b in B )
             True
 
         """
@@ -3573,53 +3679,44 @@ class ComplexSkewHermitianEJA(RationalBasisEJA):
         # The size of the blocks. We're going to treat these thing as
         # 2x2 block matrices,
         #
-        #   [  x1      x2      ]
-        #   [ -x2^*    x1-conj ]
+        #   [  x1        x2      ]
+        #   [ -x2-conj   x1-conj ]
         #
-        # where x1 is skew-Hermitian and x2 is symmetric.
+        # where x1 is skew-symmetric and x2 is Hermitian.
         #
         m = A.nrows()/2
 
         # We only loop through the top half of the matrix, because the
         # bottom can be constructed from the top.
         for i in range(m):
-
-            # First do the top-left block, which is skew-Hermitian.
+            # First do the top-left block, which is skew-symmetric.
             # We can compute the bottom-right block in the process.
             for j in range(i+1):
-                if i == j:
-                    # Top-left block's entry.
-                    E_ii = gen(A, (i,j,es[1]))
-
-                    # Bottom-right block's entry.
-                    E_ii += gen(A, (i+m,j+m,es[1])).conjugate()
-                    basis.append(E_ii)
-                else:
+                if i != j:
+                    # Skew-symmetry implies zeros for (i == j).
                     for e in es:
                         # Top-left block's entry.
                         E_ij  = gen(A, (i,j,e))
-                        E_ij -= E_ij.conjugate_transpose()
+                        E_ij -= gen(A, (j,i,e))
 
                         # Bottom-right block's entry.
                         F_ij  = gen(A, (i+m,j+m,e)).conjugate()
-                        F_ij -= F_ij.conjugate_transpose()
+                        F_ij -= gen(A, (j+m,i+m,e)).conjugate()
 
                         basis.append(E_ij + F_ij)
 
-            # Now do the top-right block, which is symmetric, and compute
+            # Now do the top-right block, which is Hermitian, and compute
             # the bottom-left block along the way.
             for j in range(m,i+m+1):
                 if (i+m) == j:
-                    # A symmetric (not Hermitian!) complex matrix can
-                    # have both real and complex entries on its
-                    # diagonal.
-                    for e in es:
-                        # Top-right block's entry.
-                        E_ii = gen(A, (i,j,e))
+                    # Hermitian matrices have real diagonal entries.
+                    # Top-right block's entry.
+                    E_ii = gen(A, (i,j,es[0]))
 
-                        # Bottom-left block's entry.
-                        E_ii -= gen(A, (i-m,j-m,e)).conjugate()
-                        basis.append(E_ii)
+                    # Bottom-left block's entry. Don't conjugate
+                    # 'cause it's real.
+                    E_ii -= gen(A, (i+m,j-m,es[0]))
+                    basis.append(E_ii)
                 else:
                     for e in es:
                         # Top-right block's entry. BEWARE! We're not
@@ -3629,38 +3726,46 @@ class ComplexSkewHermitianEJA(RationalBasisEJA):
                         E_ij  = gen(A, (i,j,e))
 
                         # Shift it back to non-offset coords, transpose,
-                        # and put it back:
+                        # conjugate, and put it back:
                         #
                         # (i,j) -> (i,j-m) -> (j-m, i) -> (j-m, i+m)
-                        E_ij += gen(A, (j-m,i+m,e))
+                        E_ij += gen(A, (j-m,i+m,e)).conjugate()
 
                         # Bottom-left's block's below-diagonal entry.
                         # Just shift the top-right coords down m and
                         # left m.
                         F_ij  = -gen(A, (i+m,j-m,e)).conjugate()
-                        F_ij += -gen(A, (j,i,e)).conjugate()
+                        F_ij += -gen(A, (j,i,e)) # double-conjugate cancels
 
                         basis.append(E_ij + F_ij)
 
         return tuple( basis )
 
+    @staticmethod
+    @cached_method
+    def _J_matrix(matrix_space):
+        n = matrix_space.nrows() // 2
+        F = matrix_space.base_ring()
+        I_n = matrix.identity(F, n)
+        J = matrix.block(F, 2, 2, (0, I_n, -I_n, 0), subdivide=False)
+        return matrix_space.from_list(J.rows())
+
+    def J_matrix(self):
+        return ComplexSkewSymmetricEJA._J_matrix(self.matrix_space())
 
     def __init__(self, n, field=AA, **kwargs):
         # New code; always check the axioms.
-        if "check_axioms" not in kwargs: kwargs["check_axioms"] = False
+        #if "check_axioms" not in kwargs: kwargs["check_axioms"] = False
 
         from mjo.hurwitz import ComplexMatrixAlgebra
         A = ComplexMatrixAlgebra(2*n, scalars=field)
-
-        I_n = matrix.identity(ZZ, n)
-        J = matrix.block(ZZ, 2, 2, (0, I_n, -I_n, 0), subdivide=False)
-        J = A.from_list(J.rows())
+        J = ComplexSkewSymmetricEJA._J_matrix(A)
 
         def jordan_product(X,Y):
             return (X*J*Y + Y*J*X)/2
 
         def inner_product(X,Y):
-            return (X*Y.conjugate_transpose()).trace().real()
+            return (X.conjugate_transpose()*Y).trace().real()
 
         super().__init__(self._denormalized_basis(A),
                          jordan_product,
@@ -3668,3 +3773,9 @@ class ComplexSkewHermitianEJA(RationalBasisEJA):
                          field=field,
                          matrix_space=A,
                          **kwargs)
+
+        # This algebra is conjectured (by me) to be isomorphic to
+        # the quaternion Hermitian EJA of size n, and the rank
+        # would follow from that.
+        #self.rank.set_cache(n)
+        self.one.set_cache( self(-J) )