]> gitweb.michael.orlitzky.com - sage.d.git/blobdiff - mjo/eja/eja_algebra.py
eja: simplify and justify the implementation of is_nilpotent() for elements.
[sage.d.git] / mjo / eja / eja_algebra.py
index ed8c85c9f60dc30c0b1f959533208b81e542b734..edba0987e946a23e193f45c62fec5288a416e617 100644 (file)
@@ -32,10 +32,10 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra):
     def __classcall_private__(cls,
                               field,
                               mult_table,
+                              rank,
                               names='e',
                               assume_associative=False,
                               category=None,
-                              rank=None,
                               natural_basis=None):
         n = len(mult_table)
         mult_table = [b.base_extend(field) for b in mult_table]
@@ -56,20 +56,20 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra):
         return fda.__classcall__(cls,
                                  field,
                                  mult_table,
+                                 rank=rank,
                                  assume_associative=assume_associative,
                                  names=names,
                                  category=cat,
-                                 rank=rank,
                                  natural_basis=natural_basis)
 
 
     def __init__(self,
                  field,
                  mult_table,
+                 rank,
                  names='e',
                  assume_associative=False,
                  category=None,
-                 rank=None,
                  natural_basis=None):
         """
         SETUP::
@@ -101,6 +101,20 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra):
     def _repr_(self):
         """
         Return a string representation of ``self``.
+
+        SETUP::
+
+            sage: from mjo.eja.eja_algebra import JordanSpinEJA
+
+        TESTS:
+
+        Ensure that it says what we think it says::
+
+            sage: JordanSpinEJA(2, field=QQ)
+            Euclidean Jordan algebra of degree 2 over Rational Field
+            sage: JordanSpinEJA(3, field=RDF)
+            Euclidean Jordan algebra of degree 3 over Real Double Field
+
         """
         fmt = "Euclidean Jordan algebra of degree {} over {}"
         return fmt.format(self.degree(), self.base_ring())
@@ -110,6 +124,21 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra):
         """
         Guess a regular element. Needed to compute the basis for our
         characteristic polynomial coefficients.
+
+        SETUP::
+
+            sage: from mjo.eja.eja_algebra import random_eja
+
+        TESTS:
+
+        Ensure that this hacky method succeeds for every algebra that we
+        know how to construct::
+
+            sage: set_random_seed()
+            sage: J = random_eja()
+            sage: J._a_regular_element().is_regular()
+            True
+
         """
         gs = self.gens()
         z = self.sum( (i+1)*gs[i] for i in range(len(gs)) )
@@ -218,19 +247,16 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra):
     @cached_method
     def characteristic_polynomial(self):
         """
+        Return a characteristic polynomial that works for all elements
+        of this algebra.
 
-        .. WARNING::
-
-            This implementation doesn't guarantee that the polynomial
-            denominator in the coefficients is not identically zero, so
-            theoretically it could crash. The way that this is handled
-            in e.g. Faraut and Koranyi is to use a basis that guarantees
-            the denominator is non-zero. But, doing so requires knowledge
-            of at least one regular element, and we don't even know how
-            to do that. The trade-off is that, if we use the standard basis,
-            the resulting polynomial will accept the "usual" coordinates. In
-            other words, we don't have to do a change of basis before e.g.
-            computing the trace or determinant.
+        The resulting polynomial has `n+1` variables, where `n` is the
+        dimension of this algebra. The first `n` variables correspond to
+        the coordinates of an algebra element: when evaluated at the
+        coordinates of an algebra element with respect to a certain
+        basis, the result is a univariate polynomial (in the one
+        remaining variable ``t``), namely the characteristic polynomial
+        of that element.
 
         SETUP::
 
@@ -360,11 +386,61 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra):
     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(2).rank()
+            2
+            sage: ComplexHermitianEJA(2).rank()
+            2
+            sage: QuaternionHermitianEJA(2).rank()
+            2
+            sage: RealSymmetricEJA(5).rank()
+            5
+            sage: ComplexHermitianEJA(5).rank()
+            5
+            sage: QuaternionHermitianEJA(5).rank()
+            5
+
+        TESTS:
+
+        Ensure that every EJA that we know how to construct has a
+        positive integer rank::
+
+            sage: set_random_seed()
+            sage: r = random_eja().rank()
+            sage: r in ZZ and r > 0
+            True
+
         """
-        if self._rank is None:
-            raise ValueError("no rank specified at genesis")
-        else:
-            return self._rank
+        return self._rank
 
 
     def vector_space(self):
@@ -405,7 +481,8 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra):
 
             SETUP::
 
-                sage: from mjo.eja.eja_algebra import RealSymmetricEJA
+                sage: from mjo.eja.eja_algebra import (RealSymmetricEJA,
+                ....:                                  random_eja)
 
             EXAMPLES:
 
@@ -425,6 +502,18 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra):
                 ...
                 ArithmeticError: vector is not in free module
 
+            TESTS:
+
+            Ensure that we can convert any element of the parent's
+            underlying vector space back into an algebra element whose
+            vector representation is what we started with::
+
+                sage: set_random_seed()
+                sage: J = random_eja()
+                sage: v = J.vector_space().random_element()
+                sage: J(v).vector() == v
+                True
+
             """
             # Goal: if we're given a matrix, and if it lives in our
             # parent algebra's "natural ambient space," convert it
@@ -457,21 +546,21 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra):
             Jordan algebras are always power-associative; see for
             example Faraut and Koranyi, Proposition II.1.2 (ii).
 
-            .. WARNING:
-
-                We have to override this because our superclass uses row vectors
-                instead of column vectors! We, on the other hand, assume column
-                vectors everywhere.
+            We have to override this because our superclass uses row
+            vectors instead of column vectors! We, on the other hand,
+            assume column vectors everywhere.
 
             SETUP::
 
                 sage: from mjo.eja.eja_algebra import random_eja
 
-            EXAMPLES::
+            TESTS:
+
+            The definition of `x^2` is the unambiguous `x*x`::
 
                 sage: set_random_seed()
                 sage: x = random_eja().random_element()
-                sage: x.operator()(x) == (x^2)
+                sage: x*x == (x^2)
                 True
 
             A few examples of power-associativity::
@@ -577,6 +666,8 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra):
                 sage: J.zero().characteristic_polynomial()
                 t^3
 
+            TESTS:
+
             The characteristic polynomial of an element should evaluate
             to zero on that element::
 
@@ -694,8 +785,8 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra):
                 sage: lhs == rhs
                 True
 
-            Test the first polarization identity from my notes, Koecher Chapter
-            III, or from Baes (2.3)::
+            Test the first polarization identity from my notes, Koecher
+            Chapter III, or from Baes (2.3)::
 
                 sage: set_random_seed()
                 sage: J = random_eja()
@@ -878,9 +969,6 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra):
             """
             Return whether or not this element is invertible.
 
-            We can't use the superclass method because it relies on
-            the algebra being associative.
-
             ALGORITHM:
 
             The usual way to do this is to check if the determinant is
@@ -890,6 +978,9 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra):
             whether or not the paren't algebra's zero element is a root
             of this element's minimal polynomial.
 
+            Beware that we can't use the superclass method, because it
+            relies on the algebra being associative.
+
             SETUP::
 
                 sage: from mjo.eja.eja_algebra import random_eja
@@ -920,14 +1011,26 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra):
             """
             Return whether or not some power of this element is zero.
 
-            The superclass method won't work unless we're in an
-            associative algebra, and we aren't. However, we generate
-            an assocoative subalgebra and we're nilpotent there if and
-            only if we're nilpotent here (probably).
+            ALGORITHM:
+
+            We use Theorem 5 in Chapter III of Koecher, which says that
+            an element ``x`` is nilpotent if and only if ``x.operator()``
+            is nilpotent. And it is a basic fact of linear algebra that
+            an operator on an `n`-dimensional space is nilpotent if and
+            only if, when raised to the `n`th power, it equals the zero
+            operator (for example, see Axler Corollary 8.8).
 
             SETUP::
 
-                sage: from mjo.eja.eja_algebra import random_eja
+                sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
+                ....:                                  random_eja)
+
+            EXAMPLES::
+
+                sage: J = JordanSpinEJA(3)
+                sage: x = sum(J.gens())
+                sage: x.is_nilpotent()
+                False
 
             TESTS:
 
@@ -944,25 +1047,9 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra):
                 True
 
             """
-            # The element we're going to call "is_nilpotent()" on.
-            # Either myself, interpreted as an element of a finite-
-            # dimensional algebra, or an element of an associative
-            # subalgebra.
-            elt = None
-
-            if self.parent().is_associative():
-                elt = FiniteDimensionalAlgebraElement(self.parent(), self)
-            else:
-                V = self.span_of_powers()
-                assoc_subalg = self.subalgebra_generated_by()
-                # Mis-design warning: the basis used for span_of_powers()
-                # and subalgebra_generated_by() must be the same, and in
-                # the same order!
-                elt = assoc_subalg(V.coordinates(self.vector()))
-
-            # Recursive call, but should work since elt lives in an
-            # associative algebra.
-            return elt.is_nilpotent()
+            P = self.parent()
+            zero_operator = P.zero().operator()
+            return self.operator()**P.dimension() == zero_operator
 
 
         def is_regular(self):
@@ -1388,7 +1475,17 @@ class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra):
             # are power-associative.
             #
             # TODO: choose generator names intelligently.
-            return FiniteDimensionalEuclideanJordanAlgebra(F, mats, assume_associative=True, names='f')
+            #
+            # The rank is the highest possible degree of a minimal polynomial,
+            # and is bounded above by the dimension. We know in this case that
+            # there's an element whose minimal polynomial has the same degree
+            # as the space's dimension, so that must be its rank too.
+            return FiniteDimensionalEuclideanJordanAlgebra(
+                     F,
+                     mats,
+                     V.dimension(),
+                     assume_associative=True,
+                     names='f')
 
 
         def subalgebra_idempotent(self):