]> gitweb.michael.orlitzky.com - sage.d.git/blobdiff - mjo/eja/eja_algebra.py
eja: fix tests and pre-cache ranks.
[sage.d.git] / mjo / eja / eja_algebra.py
index 7436ed36ce676dd9bbf0eda9922ac2aecacc6e6e..19db8b0ccefef297623d81721d4a774f99a9d3d1 100644 (file)
@@ -54,7 +54,6 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule):
     def __init__(self,
                  field,
                  mult_table,
-                 rank,
                  prefix='e',
                  category=None,
                  natural_basis=None,
@@ -91,7 +90,6 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule):
                 # a real embedding.
                 raise ValueError('field is not real')
 
-        self._rank = rank
         self._natural_basis = natural_basis
 
         if category is None:
@@ -791,12 +789,21 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule):
             True
 
         """
-        return  tuple( self.random_element() for idx in range(count) )
+        return tuple( self.random_element() for idx in range(count) )
 
+    @cached_method
+    def rank(self):
+        """
+        Return the rank of this EJA.
 
-    def _rank_computation(self):
-        r"""
-        Compute the rank of this algebra.
+        ALGORITHM:
+
+        We first compute the polynomial "column matrices" `p_{k}` that
+        evaluate to `x^k` on the coordinates of `x`. Then, we begin
+        adding them to a matrix one at a time, and trying to solve the
+        system that makes `p_{0}`,`p_{1}`,..., `p_{s-1}` add up to
+        `p_{s}`. This will succeed only when `s` is the rank of the
+        algebra, as proven in a recent draft paper of mine.
 
         SETUP::
 
@@ -804,25 +811,79 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule):
             ....:                                  JordanSpinEJA,
             ....:                                  RealSymmetricEJA,
             ....:                                  ComplexHermitianEJA,
-            ....:                                  QuaternionHermitianEJA)
+            ....:                                  QuaternionHermitianEJA,
+            ....:                                  random_eja)
 
-        EXAMPLES::
+        EXAMPLES:
 
-            sage: J = HadamardEJA(4)
-            sage: J._rank_computation() == J.rank()
+        The rank of the Jordan spin algebra is always two::
+
+            sage: JordanSpinEJA(2).rank()
+            2
+            sage: JordanSpinEJA(3).rank()
+            2
+            sage: JordanSpinEJA(4).rank()
+            2
+
+        The rank of the `n`-by-`n` Hermitian real, complex, or
+        quaternion matrices is `n`::
+
+            sage: RealSymmetricEJA(4).rank()
+            4
+            sage: ComplexHermitianEJA(3).rank()
+            3
+            sage: QuaternionHermitianEJA(2).rank()
+            2
+
+        TESTS:
+
+        Ensure that every EJA that we know how to construct has a
+        positive integer rank, unless the algebra is trivial in
+        which case its rank will be zero::
+
+            sage: set_random_seed()
+            sage: J = random_eja()
+            sage: r = J.rank()
+            sage: r in ZZ
             True
-            sage: J = JordanSpinEJA(4)
-            sage: J._rank_computation() == J.rank()
+            sage: r > 0 or (r == 0 and J.is_trivial())
             True
+
+        Ensure that computing the rank actually works, since the ranks
+        of all simple algebras are known and will be cached by default::
+
+            sage: J = HadamardEJA(4)
+            sage: J.rank.clear_cache()
+            sage: J.rank()
+            4
+
+        ::
+
+            sage: J = JordanSpinEJA(4)
+            sage: J.rank.clear_cache()
+            sage: J.rank()
+            2
+
+        ::
+
             sage: J = RealSymmetricEJA(3)
-            sage: J._rank_computation() == J.rank()
-            True
+            sage: J.rank.clear_cache()
+            sage: J.rank()
+            3
+
+        ::
+
             sage: J = ComplexHermitianEJA(2)
-            sage: J._rank_computation() == J.rank()
-            True
+            sage: J.rank.clear_cache()
+            sage: J.rank()
+            2
+
+        ::
+
             sage: J = QuaternionHermitianEJA(2)
-            sage: J._rank_computation() == J.rank()
-            True
+            sage: J.rank.clear_cache()
+            sage: J.rank()
+            2
 
         """
         n = self.dimension()
@@ -852,6 +913,9 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule):
         for d in range(1,n):
             M = matrix(M.rows() + [x_powers[d]])
             M.echelonize()
+            # TODO: we've basically solved the system here.
+            # We should save the echelonized matrix somehow
+            # so that it can be reused in the charpoly method.
             new_rank = M.rank()
             if new_rank == old_rank:
                 return new_rank
@@ -860,63 +924,6 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule):
 
         return n
 
-    def rank(self):
-        """
-        Return the rank of this EJA.
-
-        ALGORITHM:
-
-        The author knows of no algorithm to compute the rank of an EJA
-        where only the multiplication table is known. In lieu of one, we
-        require the rank to be specified when the algebra is created,
-        and simply pass along that number here.
-
-        SETUP::
-
-            sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
-            ....:                                  RealSymmetricEJA,
-            ....:                                  ComplexHermitianEJA,
-            ....:                                  QuaternionHermitianEJA,
-            ....:                                  random_eja)
-
-        EXAMPLES:
-
-        The rank of the Jordan spin algebra is always two::
-
-            sage: JordanSpinEJA(2).rank()
-            2
-            sage: JordanSpinEJA(3).rank()
-            2
-            sage: JordanSpinEJA(4).rank()
-            2
-
-        The rank of the `n`-by-`n` Hermitian real, complex, or
-        quaternion matrices is `n`::
-
-            sage: RealSymmetricEJA(4).rank()
-            4
-            sage: ComplexHermitianEJA(3).rank()
-            3
-            sage: QuaternionHermitianEJA(2).rank()
-            2
-
-        TESTS:
-
-        Ensure that every EJA that we know how to construct has a
-        positive integer rank, unless the algebra is trivial in
-        which case its rank will be zero::
-
-            sage: set_random_seed()
-            sage: J = random_eja()
-            sage: r = J.rank()
-            sage: r in ZZ
-            True
-            sage: r > 0 or (r == 0 and J.is_trivial())
-            True
-
-        """
-        return self._rank
-
 
     def vector_space(self):
         """
@@ -1039,7 +1046,8 @@ class HadamardEJA(FiniteDimensionalEuclideanJordanAlgebra, KnownRankEJA):
                        for i in range(n) ]
 
         fdeja = super(HadamardEJA, self)
-        return fdeja.__init__(field, mult_table, rank=n, **kwargs)
+        fdeja.__init__(field, mult_table, **kwargs)
+        self.rank.set_cache(n)
 
     def inner_product(self, x, y):
         """
@@ -1098,7 +1106,7 @@ class MatrixEuclideanJordanAlgebra(FiniteDimensionalEuclideanJordanAlgebra):
         # field can have dimension 4 (quaternions) too.
         return 2
 
-    def __init__(self, field, basis, rank, normalize_basis=True, **kwargs):
+    def __init__(self, field, basis, 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
@@ -1111,7 +1119,7 @@ class MatrixEuclideanJordanAlgebra(FiniteDimensionalEuclideanJordanAlgebra):
         # time to ensure that it isn't a generator expression.
         basis = tuple(basis)
 
-        if rank > 1 and normalize_basis:
+        if len(basis) > 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.
@@ -1128,14 +1136,12 @@ class MatrixEuclideanJordanAlgebra(FiniteDimensionalEuclideanJordanAlgebra):
         Qs = self.multiplication_table_from_matrix_basis(basis)
 
         fdeja = super(MatrixEuclideanJordanAlgebra, self)
-        return fdeja.__init__(field,
-                              Qs,
-                              rank=rank,
-                              natural_basis=basis,
-                              **kwargs)
+        fdeja.__init__(field, Qs, natural_basis=basis, **kwargs)
+        return
 
 
-    def _rank_computation(self):
+    @cached_method
+    def rank(self):
         r"""
         Override the parent method with something that tries to compute
         over a faster (non-extension) field.
@@ -1143,7 +1149,7 @@ class MatrixEuclideanJordanAlgebra(FiniteDimensionalEuclideanJordanAlgebra):
         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)._rank_computation()
+            return super(MatrixEuclideanJordanAlgebra, self).rank()
         else:
             basis = ( (b/n) for (b,n) in zip(self.natural_basis(),
                                              self._basis_normalizers) )
@@ -1153,9 +1159,8 @@ class MatrixEuclideanJordanAlgebra(FiniteDimensionalEuclideanJordanAlgebra):
             # integers.
             J = MatrixEuclideanJordanAlgebra(QQ,
                                              basis,
-                                             self.rank(),
                                              normalize_basis=False)
-            return J._rank_computation()
+            return J.rank()
 
     @cached_method
     def _charpoly_coeff(self, i):
@@ -1174,7 +1179,6 @@ class MatrixEuclideanJordanAlgebra(FiniteDimensionalEuclideanJordanAlgebra):
             # 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)
@@ -1403,7 +1407,8 @@ class RealSymmetricEJA(RealMatrixEuclideanJordanAlgebra, KnownRankEJA):
 
     def __init__(self, n, field=AA, **kwargs):
         basis = self._denormalized_basis(n, field)
-        super(RealSymmetricEJA, self).__init__(field, basis, n, **kwargs)
+        super(RealSymmetricEJA, self).__init__(field, basis, **kwargs)
+        self.rank.set_cache(n)
 
 
 class ComplexMatrixEuclideanJordanAlgebra(MatrixEuclideanJordanAlgebra):
@@ -1693,7 +1698,8 @@ class ComplexHermitianEJA(ComplexMatrixEuclideanJordanAlgebra, KnownRankEJA):
 
     def __init__(self, n, field=AA, **kwargs):
         basis = self._denormalized_basis(n,field)
-        super(ComplexHermitianEJA,self).__init__(field, basis, n, **kwargs)
+        super(ComplexHermitianEJA,self).__init__(field, basis, **kwargs)
+        self.rank.set_cache(n)
 
 
 class QuaternionMatrixEuclideanJordanAlgebra(MatrixEuclideanJordanAlgebra):
@@ -1989,7 +1995,8 @@ class QuaternionHermitianEJA(QuaternionMatrixEuclideanJordanAlgebra,
 
     def __init__(self, n, field=AA, **kwargs):
         basis = self._denormalized_basis(n,field)
-        super(QuaternionHermitianEJA,self).__init__(field, basis, n, **kwargs)
+        super(QuaternionHermitianEJA,self).__init__(field, basis, **kwargs)
+        self.rank.set_cache(n)
 
 
 class BilinearFormEJA(FiniteDimensionalEuclideanJordanAlgebra, KnownRankEJA):
@@ -2072,7 +2079,8 @@ class BilinearFormEJA(FiniteDimensionalEuclideanJordanAlgebra, KnownRankEJA):
         # 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)
+        fdeja.__init__(field, mult_table, **kwargs)
+        self.rank.set_cache(min(n,2))
 
     def inner_product(self, x, y):
         r"""
@@ -2200,4 +2208,5 @@ class TrivialEJA(FiniteDimensionalEuclideanJordanAlgebra, KnownRankEJA):
         fdeja = super(TrivialEJA, self)
         # The rank is zero using my definition, namely the dimension of the
         # largest subalgebra generated by any element.
-        return fdeja.__init__(field, mult_table, rank=0, **kwargs)
+        fdeja.__init__(field, mult_table, **kwargs)
+        self.rank.set_cache(0)