]> gitweb.michael.orlitzky.com - sage.d.git/blobdiff - mjo/eja/eja_algebra.py
eja: make AA the default field because everything cool requires it.
[sage.d.git] / mjo / eja / eja_algebra.py
index 26b72480ffd6d18c1bb8ca43ae3358afda951996..0a260653a7c5480acea6ffc00a9158169e4e7a18 100644 (file)
@@ -17,7 +17,7 @@ from sage.misc.lazy_import import lazy_import
 from sage.misc.prandom import choice
 from sage.misc.table import table
 from sage.modules.free_module import FreeModule, VectorSpace
-from sage.rings.all import (ZZ, QQ, RR, RLF, CLF,
+from sage.rings.all import (ZZ, QQ, AA, QQbar, RR, RLF, CLF,
                             PolynomialRing,
                             QuadraticField)
 from mjo.eja.eja_element import FiniteDimensionalEuclideanJordanAlgebraElement
@@ -26,13 +26,30 @@ lazy_import('mjo.eja.eja_subalgebra',
 from mjo.eja.eja_utils import _mat2vec
 
 class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule):
-    # This is an ugly hack needed to prevent the category framework
-    # from implementing a coercion from our base ring (e.g. the
-    # rationals) into the algebra. First of all -- such a coercion is
-    # nonsense to begin with. But more importantly, it tries to do so
-    # in the category of rings, and since our algebras aren't
-    # associative they generally won't be rings.
-    _no_generic_basering_coercion = True
+
+    def _coerce_map_from_base_ring(self):
+        """
+        Disable the map from the base ring into the algebra.
+
+        Performing a nonsense conversion like this automatically
+        is counterpedagogical. The fallback is to try the usual
+        element constructor, which should also fail.
+
+        SETUP::
+
+            sage: from mjo.eja.eja_algebra import random_eja
+
+        TESTS::
+
+            sage: set_random_seed()
+            sage: J = random_eja()
+            sage: J(1)
+            Traceback (most recent call last):
+            ...
+            ValueError: not a naturally-represented algebra element
+
+        """
+        return None
 
     def __init__(self,
                  field,
@@ -149,15 +166,22 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule):
             True
 
         """
+        msg = "not a naturally-represented algebra element"
         if elt == 0:
             # The superclass implementation of random_element()
             # needs to be able to coerce "0" into the algebra.
             return self.zero()
+        elif elt in self.base_ring():
+            # Ensure that no base ring -> algebra coercion is performed
+            # by this method. There's some stupidity in sage that would
+            # otherwise propagate to this method; for example, sage thinks
+            # that the integer 3 belongs to the space of 2-by-2 matrices.
+            raise ValueError(msg)
 
         natural_basis = self.natural_basis()
         basis_space = natural_basis[0].matrix_space()
         if elt not in basis_space:
-            raise ValueError("not a naturally-represented algebra element")
+            raise ValueError(msg)
 
         # Thanks for nothing! Matrix spaces aren't vector spaces in
         # Sage, so we have to figure out its natural-basis coordinates
@@ -183,8 +207,8 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule):
 
         Ensure that it says what we think it says::
 
-            sage: JordanSpinEJA(2, field=QQ)
-            Euclidean Jordan algebra of dimension 2 over Rational Field
+            sage: JordanSpinEJA(2, field=AA)
+            Euclidean Jordan algebra of dimension 2 over Algebraic Real Field
             sage: JordanSpinEJA(3, field=RDF)
             Euclidean Jordan algebra of dimension 3 over Real Double Field
 
@@ -527,8 +551,8 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule):
             Finite family {0: e0, 1: e1, 2: e2}
             sage: J.natural_basis()
             (
-            [1 0]  [        0 1/2*sqrt2]  [0 0]
-            [0 0], [1/2*sqrt2         0], [0 1]
+            [1 0]  [                  0 0.7071067811865475?]  [0 0]
+            [0 0], [0.7071067811865475?                   0], [0 1]
             )
 
         ::
@@ -733,7 +757,7 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule):
         J5 = VectorSpace(self.base_ring(), 0) # eigenvalue one-half
         J1 = trivial                          # eigenvalue one
 
-        for (eigval, eigspace) in c.operator().matrix().left_eigenspaces():
+        for (eigval, eigspace) in c.operator().matrix().right_eigenspaces():
             if eigval == ~(self.base_ring()(2)):
                 J5 = eigspace
             else:
@@ -749,6 +773,108 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule):
         return (J0, J5, J1)
 
 
+    def a_jordan_frame(self):
+        r"""
+        Generate a Jordan frame for this algebra.
+
+        This implementation is based on the so-called "central
+        orthogonal idempotents" implemented for (semisimple) centers
+        of SageMath ``FiniteDimensionalAlgebrasWithBasis``. Since all
+        Euclidean Jordan algebas are commutative (and thus equal to
+        their own centers) and semisimple, the method should work more
+        or less as implemented, if it ever worked in the first place.
+        (I don't know the justification for the original implementation.
+        yet).
+
+        How it works: we loop through the algebras generators, looking
+        for their eigenspaces. If there's more than one eigenspace,
+        and if they result in more than one subalgebra, then we split
+        those subalgebras recursively until we get to subalgebras of
+        dimension one (whose idempotent is the unit element). Why does
+        some generator have to produce at least two subalgebras? I
+        dunno. But it seems to work.
+
+        Beware that Koecher defines the "center" of a Jordan algebra to
+        be something else, because the usual definition is stupid in a
+        (necessarily commutative) Jordan algebra.
+
+        SETUP::
+
+            sage: from mjo.eja.eja_algebra import (random_eja,
+            ....:                                  JordanSpinEJA,
+            ....:                                  TrivialEJA)
+
+        EXAMPLES:
+
+        A Jordan frame for the trivial algebra has to be empty
+        (zero-length) since its rank is zero. More to the point, there
+        are no non-zero idempotents in the trivial EJA. This does not
+        cause any problems so long as we adopt the convention that the
+        empty sum is zero, since then the sole element of the trivial
+        EJA has an (empty) spectral decomposition::
+
+            sage: J = TrivialEJA()
+            sage: J.a_jordan_frame()
+            ()
+
+        A one-dimensional algebra has rank one (equal to its dimension),
+        and only one primitive idempotent, namely the algebra's unit
+        element::
+
+            sage: J = JordanSpinEJA(1)
+            sage: J.a_jordan_frame()
+            (e0,)
+
+        TESTS::
+
+            sage: J = random_eja()
+            sage: c = J.a_jordan_frame()
+            sage: all( x^2 == x for x in c )
+            True
+            sage: r = len(c)
+            sage: all( c[i]*c[j] == c[i]*(i==j) for i in range(r)
+            ....:                               for j in range(r) )
+            True
+
+        """
+        if self.dimension() == 0:
+            return ()
+        if self.dimension() == 1:
+            return (self.one(),)
+
+        for g in self.gens():
+            eigenpairs = g.operator().matrix().right_eigenspaces()
+            if len(eigenpairs) >= 2:
+                subalgebras = []
+                for eigval, eigspace in eigenpairs:
+                    # Make sub-EJAs from the matrix eigenspaces...
+                    sb = tuple( self.from_vector(b) for b in eigspace.basis() )
+                    try:
+                        # This will fail if e.g. the eigenspace basis
+                        # contains two elements and their product
+                        # isn't a linear combination of the two of
+                        # them (i.e. the generated EJA isn't actually
+                        # two dimensional).
+                        s = FiniteDimensionalEuclideanJordanSubalgebra(self, sb)
+                        subalgebras.append(s)
+                    except ArithmeticError as e:
+                        if str(e) == "vector is not in free module":
+                            # Ignore only the "not a sub-EJA" error
+                            pass
+
+                if len(subalgebras) >= 2:
+                    # apply this method recursively.
+                    return tuple( c.superalgebra_element()
+                                  for subalgebra in subalgebras
+                                  for c in subalgebra.a_jordan_frame() )
+
+        # If we got here, the algebra didn't decompose, at least not when we looked at
+        # the eigenspaces corresponding only to basis elements of the algebra. The
+        # implementation I stole says that this should work because of Schur's Lemma,
+        # so I personally blame Schur's Lemma if it does not.
+        raise Exception("Schur's Lemma didn't work!")
+
+
     def random_elements(self, count):
         """
         Return ``count`` random elements as a tuple.
@@ -887,7 +1013,7 @@ class KnownRankEJA(object):
         return 5
 
     @classmethod
-    def random_instance(cls, field=QQ, **kwargs):
+    def random_instance(cls, field=AA, **kwargs):
         """
         Return a random instance of this type of algebra.
 
@@ -943,7 +1069,7 @@ class HadamardEJA(FiniteDimensionalEuclideanJordanAlgebra, KnownRankEJA):
         (r0, r1, r2)
 
     """
-    def __init__(self, n, field=QQ, **kwargs):
+    def __init__(self, n, field=AA, **kwargs):
         V = VectorSpace(field, n)
         mult_table = [ [ V.gen(i)*(i == j) for j in range(n) ]
                        for i in range(n) ]
@@ -976,7 +1102,7 @@ class HadamardEJA(FiniteDimensionalEuclideanJordanAlgebra, KnownRankEJA):
         return x.to_vector().inner_product(y.to_vector())
 
 
-def random_eja(field=QQ, nontrivial=False):
+def random_eja(field=AA, nontrivial=False):
     """
     Return a "random" finite-dimensional Euclidean Jordan Algebra.
 
@@ -1196,8 +1322,8 @@ class RealSymmetricEJA(RealMatrixEuclideanJordanAlgebra, KnownRankEJA):
 
     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, RDF)
+        Euclidean Jordan algebra of dimension 3 over Real Double Field
         sage: RealSymmetricEJA(2, RR)
         Euclidean Jordan algebra of dimension 3 over Real Field with
         53 bits of precision
@@ -1289,7 +1415,7 @@ class RealSymmetricEJA(RealMatrixEuclideanJordanAlgebra, KnownRankEJA):
         return 4 # Dimension 10
 
 
-    def __init__(self, n, field=QQ, **kwargs):
+    def __init__(self, n, field=AA, **kwargs):
         basis = self._denormalized_basis(n, field)
         super(RealSymmetricEJA, self).__init__(field, basis, n, **kwargs)
 
@@ -1309,7 +1435,7 @@ class ComplexMatrixEuclideanJordanAlgebra(MatrixEuclideanJordanAlgebra):
 
         EXAMPLES::
 
-            sage: F = QuadraticField(-1, 'i')
+            sage: F = QuadraticField(-1, 'I')
             sage: x1 = F(4 - 2*i)
             sage: x2 = F(1 + 2*i)
             sage: x3 = F(-i)
@@ -1329,7 +1455,7 @@ class ComplexMatrixEuclideanJordanAlgebra(MatrixEuclideanJordanAlgebra):
             sage: set_random_seed()
             sage: n_max = ComplexMatrixEuclideanJordanAlgebra._max_test_case_size()
             sage: n = ZZ.random_element(n_max)
-            sage: F = QuadraticField(-1, 'i')
+            sage: F = QuadraticField(-1, 'I')
             sage: X = random_matrix(F, n)
             sage: Y = random_matrix(F, n)
             sage: Xe = ComplexMatrixEuclideanJordanAlgebra.real_embed(X)
@@ -1372,15 +1498,15 @@ class ComplexMatrixEuclideanJordanAlgebra(MatrixEuclideanJordanAlgebra):
             ....:                 [ 9,  10, 11, 12],
             ....:                 [-10, 9, -12, 11] ])
             sage: ComplexMatrixEuclideanJordanAlgebra.real_unembed(A)
-            [  2*i + 1   4*i + 3]
-            [ 10*i + 9 12*i + 11]
+            [  2*I + 1   4*I + 3]
+            [ 10*I + 9 12*I + 11]
 
         TESTS:
 
         Unembedding is the inverse of embedding::
 
             sage: set_random_seed()
-            sage: F = QuadraticField(-1, 'i')
+            sage: F = QuadraticField(-1, 'I')
             sage: M = random_matrix(F, 3)
             sage: Me = ComplexMatrixEuclideanJordanAlgebra.real_embed(M)
             sage: ComplexMatrixEuclideanJordanAlgebra.real_unembed(Me) == M
@@ -1398,7 +1524,12 @@ class ComplexMatrixEuclideanJordanAlgebra(MatrixEuclideanJordanAlgebra):
         field = M.base_ring()
         R = PolynomialRing(field, 'z')
         z = R.gen()
-        F = field.extension(z**2 + 1, 'i', embedding=CLF(-1).sqrt())
+        if field is AA:
+            # Sage doesn't know how to embed AA into QQbar, i.e. how
+            # to adjoin sqrt(-1) to AA.
+            F = QQbar
+        else:
+            F = field.extension(z**2 + 1, 'I', embedding=CLF(-1).sqrt())
         i = F.gen()
 
         # Go top-left to bottom-right (reading order), converting every
@@ -1439,7 +1570,7 @@ class ComplexMatrixEuclideanJordanAlgebra(MatrixEuclideanJordanAlgebra):
             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: expected = (X*Y).trace().real()
             sage: actual = ComplexHermitianEJA.natural_inner_product(Xe,Ye)
             sage: actual == expected
             True
@@ -1463,8 +1594,8 @@ class ComplexHermitianEJA(ComplexMatrixEuclideanJordanAlgebra, KnownRankEJA):
 
     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, RDF)
+        Euclidean Jordan algebra of dimension 4 over Real Double Field
         sage: ComplexHermitianEJA(2, RR)
         Euclidean Jordan algebra of dimension 4 over Real Field with
         53 bits of precision
@@ -1574,7 +1705,7 @@ class ComplexHermitianEJA(ComplexMatrixEuclideanJordanAlgebra, KnownRankEJA):
         return ( s.change_ring(field) for s in S )
 
 
-    def __init__(self, n, field=QQ, **kwargs):
+    def __init__(self, n, field=AA, **kwargs):
         basis = self._denormalized_basis(n,field)
         super(ComplexHermitianEJA,self).__init__(field, basis, n, **kwargs)
 
@@ -1626,7 +1757,7 @@ class QuaternionMatrixEuclideanJordanAlgebra(MatrixEuclideanJordanAlgebra):
         if M.ncols() != n:
             raise ValueError("the matrix 'M' must be square")
 
-        F = QuadraticField(-1, 'i')
+        F = QuadraticField(-1, 'I')
         i = F.gen()
 
         blocks = []
@@ -1702,10 +1833,10 @@ class QuaternionMatrixEuclideanJordanAlgebra(MatrixEuclideanJordanAlgebra):
                     raise ValueError('bad on-diagonal submatrix')
                 if submat[0,1] != -submat[1,0].conjugate():
                     raise ValueError('bad off-diagonal submatrix')
-                z  = submat[0,0].vector()[0]   # real part
-                z += submat[0,0].vector()[1]*i # imag part
-                z += submat[0,1].vector()[0]*j # real part
-                z += submat[0,1].vector()[1]*k # imag part
+                z  = submat[0,0].real()
+                z += submat[0,0].imag()*i
+                z += submat[0,1].real()*j
+                z += submat[0,1].imag()*k
                 elements.append(z)
 
         return matrix(Q, n/4, elements)
@@ -1758,8 +1889,8 @@ class QuaternionHermitianEJA(QuaternionMatrixEuclideanJordanAlgebra,
 
     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, RDF)
+        Euclidean Jordan algebra of dimension 6 over Real Double Field
         sage: QuaternionHermitianEJA(2, RR)
         Euclidean Jordan algebra of dimension 6 over Real Field with
         53 bits of precision
@@ -1870,7 +2001,7 @@ class QuaternionHermitianEJA(QuaternionMatrixEuclideanJordanAlgebra,
         return ( s.change_ring(field) for s in S )
 
 
-    def __init__(self, n, field=QQ, **kwargs):
+    def __init__(self, n, field=AA, **kwargs):
         basis = self._denormalized_basis(n,field)
         super(QuaternionHermitianEJA,self).__init__(field, basis, n, **kwargs)
 
@@ -1886,7 +2017,8 @@ class BilinearFormEJA(FiniteDimensionalEuclideanJordanAlgebra, KnownRankEJA):
 
     SETUP::
 
-        sage: from mjo.eja.eja_algebra import BilinearFormEJA
+        sage: from mjo.eja.eja_algebra import (BilinearFormEJA,
+        ....:                                  JordanSpinEJA)
 
     EXAMPLES:
 
@@ -1929,7 +2061,7 @@ class BilinearFormEJA(FiniteDimensionalEuclideanJordanAlgebra, KnownRankEJA):
         sage: actual == expected
         True
     """
-    def __init__(self, n, field=QQ, B=None, **kwargs):
+    def __init__(self, n, field=AA, B=None, **kwargs):
         if B is None:
             self._B = matrix.identity(field, max(0,n-1))
         else:
@@ -1969,23 +2101,20 @@ class BilinearFormEJA(FiniteDimensionalEuclideanJordanAlgebra, KnownRankEJA):
 
         TESTS:
 
-        Ensure that this is one-half of the trace inner-product::
+        Ensure that this is one-half of the trace inner-product when
+        the algebra isn't just the reals (when ``n`` isn't one). This
+        is in Faraut and Koranyi, and also my "On the symmetry..."
+        paper::
 
             sage: set_random_seed()
-            sage: n = ZZ.random_element(5)
-            sage: M = matrix.random(QQ, n-1, algorithm='unimodular')
+            sage: n = ZZ.random_element(2,5)
+            sage: M = matrix.random(QQ, max(0,n-1), algorithm='unimodular')
             sage: B = M.transpose()*M
             sage: J = BilinearFormEJA(n, B=B)
-            sage: eis = VectorSpace(M.base_ring(), M.ncols()).basis()
-            sage: V = J.vector_space()
-            sage: sis = [ J.from_vector(V([0] + (M.inverse()*ei).list()))
-            ....:         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: x = J.random_element()
+            sage: y = J.random_element()
+            sage: x.inner_product(y) == (x*y).trace()/2
+            True
 
         """
         xvec = x.to_vector()
@@ -1994,7 +2123,8 @@ class BilinearFormEJA(FiniteDimensionalEuclideanJordanAlgebra, KnownRankEJA):
         ybar = yvec[1:]
         return x[0]*y[0] + (self._B*xbar).inner_product(ybar)
 
-class JordanSpinEJA(FiniteDimensionalEuclideanJordanAlgebra, KnownRankEJA):
+
+class JordanSpinEJA(BilinearFormEJA):
     """
     The rank-2 simple EJA consisting of real vectors ``x=(x0, x_bar)``
     with the usual inner product and jordan product ``x*y =
@@ -2031,42 +2161,9 @@ class JordanSpinEJA(FiniteDimensionalEuclideanJordanAlgebra, KnownRankEJA):
         sage: JordanSpinEJA(2, prefix='B').gens()
         (B0, B1)
 
-    """
-    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):
-                x = V.gen(i)
-                y = V.gen(j)
-                x0 = x[0]
-                xbar = x[1:]
-                y0 = y[0]
-                ybar = y[1:]
-                # z = x*y
-                z0 = x.inner_product(y)
-                zbar = y0*xbar + x0*ybar
-                z = V([z0] + zbar.list())
-                mult_table[i][j] = z
-
-        # The rank of the spin algebra is two, unless we're in a
-        # one-dimensional ambient space (because the rank is bounded by
-        # the ambient dimension).
-        fdeja = super(JordanSpinEJA, self)
-        return fdeja.__init__(field, mult_table, rank=min(n,2), **kwargs)
-
-    def inner_product(self, x, y):
-        """
-        Faster to reimplement than to use natural representations.
-
-        SETUP::
-
-            sage: from mjo.eja.eja_algebra import JordanSpinEJA
+    TESTS:
 
-        TESTS:
-
-        Ensure that this is the usual inner product for the algebras
-        over `R^n`::
+        Ensure that we have the usual inner product on `R^n`::
 
             sage: set_random_seed()
             sage: J = JordanSpinEJA.random_instance()
@@ -2076,8 +2173,11 @@ class JordanSpinEJA(FiniteDimensionalEuclideanJordanAlgebra, KnownRankEJA):
             sage: x.inner_product(y) == J.natural_inner_product(X,Y)
             True
 
-        """
-        return x.to_vector().inner_product(y.to_vector())
+    """
+    def __init__(self, n, field=AA, **kwargs):
+        # This is a special case of the BilinearFormEJA with the identity
+        # matrix as its bilinear form.
+        return super(JordanSpinEJA, self).__init__(n, field, **kwargs)
 
 
 class TrivialEJA(FiniteDimensionalEuclideanJordanAlgebra, KnownRankEJA):
@@ -2104,12 +2204,12 @@ class TrivialEJA(FiniteDimensionalEuclideanJordanAlgebra, KnownRankEJA):
         sage: J.one().norm()
         0
         sage: J.one().subalgebra_generated_by()
-        Euclidean Jordan algebra of dimension 0 over Rational Field
+        Euclidean Jordan algebra of dimension 0 over Algebraic Real Field
         sage: J.rank()
         0
 
     """
-    def __init__(self, field=QQ, **kwargs):
+    def __init__(self, field=AA, **kwargs):
         mult_table = []
         fdeja = super(TrivialEJA, self)
         # The rank is zero using my definition, namely the dimension of the