]> gitweb.michael.orlitzky.com - sage.d.git/commitdiff
eja: optionally pass matrix space into FDEJA instead of guessing it.
authorMichael Orlitzky <michael@orlitzky.com>
Tue, 9 Mar 2021 21:22:16 +0000 (16:22 -0500)
committerMichael Orlitzky <michael@orlitzky.com>
Tue, 9 Mar 2021 21:22:16 +0000 (16:22 -0500)
mjo/eja/eja_algebra.py
mjo/eja/eja_subalgebra.py

index c6de22399133794ae2485df41114dcbe90e55094..4ee5aba352bf870b679e8c782559c53e2db579ad 100644 (file)
@@ -104,6 +104,11 @@ class FiniteDimensionalEJA(CombinatorialFreeModule):
         product. This will be applied to ``basis`` to compute an
         inner-product table (basically a matrix) for this algebra.
 
+      - ``matrix_space`` -- the space that your matrix basis lives in,
+        or ``None`` (the default). So long as your basis does not have
+        length zero you can omit this. But in trivial algebras, it is
+        required.
+
       - ``field`` -- a subfield of the reals (default: ``AA``); the scalar
         field for the algebra.
 
@@ -128,7 +133,6 @@ class FiniteDimensionalEJA(CombinatorialFreeModule):
         sage: basis = tuple(b.superalgebra_element() for b in A.basis())
         sage: J.subalgebra(basis, orthonormalize=False).is_associative()
         True
-
     """
     Element = FiniteDimensionalEJAElement
 
@@ -137,6 +141,7 @@ class FiniteDimensionalEJA(CombinatorialFreeModule):
                  jordan_product,
                  inner_product,
                  field=AA,
+                 matrix_space=None,
                  orthonormalize=True,
                  associative=None,
                  cartesian_product=False,
@@ -236,8 +241,14 @@ class FiniteDimensionalEJA(CombinatorialFreeModule):
             basis = tuple(gram_schmidt(basis, inner_product))
 
         # Save the (possibly orthonormalized) matrix basis for
-        # later...
+        # later, as well as the space that its elements live in.
+        # In most cases we can deduce the matrix space, but when
+        # n == 0 (that is, there are no basis elements) we cannot.
         self._matrix_basis = basis
+        if matrix_space is None:
+            self._matrix_space = self._matrix_basis[0].parent()
+        else:
+            self._matrix_space = matrix_space
 
         # Now create the vector space for the algebra, which will have
         # its own set of non-ambient coordinates (in terms of the
@@ -1030,10 +1041,7 @@ class FiniteDimensionalEJA(CombinatorialFreeModule):
             the scalar ring Rational Field
 
         """
-        if self.is_trivial():
-            return MatrixSpace(self.base_ring(), 0)
-        else:
-            return self.matrix_basis()[0].parent()
+        return self._matrix_space
 
 
     @cached_method
@@ -1610,6 +1618,7 @@ class RationalBasisEJA(FiniteDimensionalEJA):
                                        jordan_product,
                                        inner_product,
                                        field=QQ,
+                                       matrix_space=self.matrix_space(),
                                        associative=self.is_associative(),
                                        orthonormalize=False,
                                        check_field=False,
@@ -1966,16 +1975,14 @@ class RealSymmetricEJA(RationalBasisEJA, ConcreteEJA, MatrixEJA):
                          self.jordan_product,
                          self.trace_inner_product,
                          field=field,
+                         matrix_space=A,
                          **kwargs)
 
         # TODO: this could be factored out somehow, but is left here
         # because the MatrixEJA is not presently a subclass of the
         # FDEJA class that defines rank() and one().
         self.rank.set_cache(n)
-        if n == 0:
-            self.one.set_cache( self.zero() )
-        else:
-            self.one.set_cache(self(A.one()))
+        self.one.set_cache( self(A.one()) )
 
 
 
@@ -1992,13 +1999,28 @@ class ComplexHermitianEJA(RationalBasisEJA, ConcreteEJA, MatrixEJA):
 
     EXAMPLES:
 
-    In theory, our "field" can be any subfield of the reals::
+    In theory, our "field" can be any subfield of the reals, but we
+    can't use inexact real fields at the moment because SageMath
+    doesn't know how to convert their elements into complex numbers,
+    or even into algebraic reals::
 
-        sage: ComplexHermitianEJA(2, field=RDF, check_axioms=True)
-        Euclidean Jordan algebra of dimension 4 over Real Double Field
-        sage: ComplexHermitianEJA(2, field=RR, check_axioms=True)
-        Euclidean Jordan algebra of dimension 4 over Real Field with
-        53 bits of precision
+        sage: QQbar(RDF(1))
+        Traceback (most recent call last):
+        ...
+        TypeError: Illegal initializer for algebraic number
+        sage: AA(RR(1))
+        Traceback (most recent call last):
+        ...
+        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:
 
@@ -2034,7 +2056,6 @@ class ComplexHermitianEJA(RationalBasisEJA, ConcreteEJA, MatrixEJA):
 
         sage: ComplexHermitianEJA(0)
         Euclidean Jordan algebra of dimension 0 over Algebraic Real Field
-
     """
     def __init__(self, n, field=AA, **kwargs):
         # We know this is a valid EJA, but will double-check
@@ -2047,16 +2068,15 @@ class ComplexHermitianEJA(RationalBasisEJA, ConcreteEJA, MatrixEJA):
                          self.jordan_product,
                          self.trace_inner_product,
                          field=field,
+                         matrix_space=A,
                          **kwargs)
 
         # TODO: this could be factored out somehow, but is left here
         # because the MatrixEJA is not presently a subclass of the
         # FDEJA class that defines rank() and one().
         self.rank.set_cache(n)
-        if n == 0:
-            self.one.set_cache( self.zero() )
-        else:
-            self.one.set_cache(self(A.one()))
+        self.one.set_cache( self(A.one()) )
+
 
     @staticmethod
     def _max_random_instance_size():
@@ -2139,16 +2159,14 @@ class QuaternionHermitianEJA(RationalBasisEJA, ConcreteEJA, MatrixEJA):
                          self.jordan_product,
                          self.trace_inner_product,
                          field=field,
+                         matrix_space=A,
                          **kwargs)
 
         # TODO: this could be factored out somehow, but is left here
         # because the MatrixEJA is not presently a subclass of the
         # FDEJA class that defines rank() and one().
         self.rank.set_cache(n)
-        if n == 0:
-            self.one.set_cache( self.zero() )
-        else:
-            self.one.set_cache(self(A.one()))
+        self.one.set_cache( self(A.one()) )
 
 
     @staticmethod
@@ -2283,16 +2301,14 @@ class OctonionHermitianEJA(RationalBasisEJA, ConcreteEJA, MatrixEJA):
                          self.jordan_product,
                          self.trace_inner_product,
                          field=field,
+                         matrix_space=A,
                          **kwargs)
 
         # TODO: this could be factored out somehow, but is left here
         # because the MatrixEJA is not presently a subclass of the
         # FDEJA class that defines rank() and one().
         self.rank.set_cache(n)
-        if n == 0:
-            self.one.set_cache( self.zero() )
-        else:
-            self.one.set_cache(self(A.one()))
+        self.one.set_cache( self(A.one()) )
 
 
 class AlbertEJA(OctonionHermitianEJA):
@@ -2357,13 +2373,14 @@ class HadamardEJA(RationalBasisEJA, ConcreteEJA):
         (r0, r1, r2)
     """
     def __init__(self, n, field=AA, **kwargs):
+        MS = MatrixSpace(field, n, 1)
+
         if n == 0:
             jordan_product = lambda x,y: x
             inner_product = lambda x,y: x
         else:
             def jordan_product(x,y):
-                P = x.parent()
-                return P( xi*yi for (xi,yi) in zip(x,y) )
+                return MS( xi*yi for (xi,yi) in zip(x,y) )
 
             def inner_product(x,y):
                 return (x.T*y)[0,0]
@@ -2377,20 +2394,17 @@ class HadamardEJA(RationalBasisEJA, ConcreteEJA):
         if "orthonormalize" not in kwargs: kwargs["orthonormalize"] = False
         if "check_axioms" not in kwargs: kwargs["check_axioms"] = False
 
-        column_basis = tuple( b.column()
-                              for b in FreeModule(field, n).basis() )
+        column_basis = tuple( MS(b) for b in FreeModule(field, n).basis() )
         super().__init__(column_basis,
                          jordan_product,
                          inner_product,
                          field=field,
+                         matrix_space=MS,
                          associative=True,
                          **kwargs)
         self.rank.set_cache(n)
 
-        if n == 0:
-            self.one.set_cache( self.zero() )
-        else:
-            self.one.set_cache( sum(self.gens()) )
+        self.one.set_cache( self.sum(self.gens()) )
 
     @staticmethod
     def _max_random_instance_size():
@@ -2504,22 +2518,22 @@ class BilinearFormEJA(RationalBasisEJA, ConcreteEJA):
         # verify things, we'll skip the rest of the checks.
         if "check_axioms" not in kwargs: kwargs["check_axioms"] = False
 
+        n = B.nrows()
+        MS = MatrixSpace(field, n, 1)
+
         def inner_product(x,y):
             return (y.T*B*x)[0,0]
 
         def jordan_product(x,y):
-            P = x.parent()
             x0 = x[0,0]
             xbar = x[1:,0]
             y0 = y[0,0]
             ybar = y[1:,0]
             z0 = inner_product(y,x)
             zbar = y0*xbar + x0*ybar
-            return P([z0] + zbar.list())
+            return MS([z0] + zbar.list())
 
-        n = B.nrows()
-        column_basis = tuple( b.column()
-                              for b in FreeModule(field, n).basis() )
+        column_basis = tuple( MS(b) for b in FreeModule(field, n).basis() )
 
         # TODO: I haven't actually checked this, but it seems legit.
         associative = False
@@ -2530,6 +2544,7 @@ class BilinearFormEJA(RationalBasisEJA, ConcreteEJA):
                          jordan_product,
                          inner_product,
                          field=field,
+                         matrix_space=MS,
                          associative=associative,
                          **kwargs)
 
@@ -2537,7 +2552,6 @@ class BilinearFormEJA(RationalBasisEJA, ConcreteEJA):
         # one-dimensional ambient space (because the rank is bounded
         # by the ambient dimension).
         self.rank.set_cache(min(n,2))
-
         if n == 0:
             self.one.set_cache( self.zero() )
         else:
@@ -2685,10 +2699,11 @@ class TrivialEJA(RationalBasisEJA, ConcreteEJA):
         0
 
     """
-    def __init__(self, **kwargs):
+    def __init__(self, field=AA, **kwargs):
         jordan_product = lambda x,y: x
-        inner_product = lambda x,y: 0
+        inner_product = lambda x,y: field.zero()
         basis = ()
+        MS = MatrixSpace(field,0)
 
         # New defaults for keyword arguments
         if "orthonormalize" not in kwargs: kwargs["orthonormalize"] = False
@@ -2698,6 +2713,8 @@ class TrivialEJA(RationalBasisEJA, ConcreteEJA):
                          jordan_product,
                          inner_product,
                          associative=True,
+                         field=field,
+                         matrix_space=MS,
                          **kwargs)
 
         # The rank is zero using my definition, namely the dimension of the
@@ -2875,7 +2892,14 @@ class CartesianProductEJA(FiniteDimensionalEJA):
 
         associative = all( f.is_associative() for f in factors )
 
-        MS = self.matrix_space()
+        # Compute my matrix space. This category isn't perfect, but
+        # is good enough for what we need to do.
+        MS_cat = MagmaticAlgebras(field).FiniteDimensional().WithBasis()
+        MS_cat = MS_cat.Unital().CartesianProducts()
+        MS_factors = tuple( J.matrix_space() for J in factors )
+        from sage.sets.cartesian_product import CartesianProduct
+        MS = CartesianProduct(MS_factors, MS_cat)
+
         basis = []
         zero = MS.zero()
         for i in range(m):
@@ -2910,15 +2934,16 @@ class CartesianProductEJA(FiniteDimensionalEJA):
                                       jordan_product,
                                       inner_product,
                                       field=field,
+                                      matrix_space=MS,
                                       orthonormalize=False,
                                       associative=associative,
                                       cartesian_product=True,
                                       check_field=False,
                                       check_axioms=False)
 
+        self.rank.set_cache(sum(J.rank() for J in factors))
         ones = tuple(J.one().to_matrix() for J in factors)
         self.one.set_cache(self(ones))
-        self.rank.set_cache(sum(J.rank() for J in factors))
 
     def cartesian_factors(self):
         # Copy/pasted from CombinatorialFreeModule_CartesianProduct.
@@ -2993,16 +3018,7 @@ class CartesianProductEJA(FiniteDimensionalEJA):
             +----+
 
         """
-        scalars = self.cartesian_factor(0).base_ring()
-
-        # This category isn't perfect, but is good enough for what we
-        # need to do.
-        cat = MagmaticAlgebras(scalars).FiniteDimensional().WithBasis()
-        cat = cat.Unital().CartesianProducts()
-        factors = tuple( J.matrix_space() for J in self.cartesian_factors() )
-
-        from sage.sets.cartesian_product import CartesianProduct
-        return CartesianProduct(factors, cat)
+        return super().matrix_space()
 
 
     @cached_method
index 1b86d236c390691939fd84cabb597e6b6159d406..4458a7e06d9d905ab276665a210eb7dc8275320b 100644 (file)
@@ -171,6 +171,7 @@ class FiniteDimensionalEJASubalgebra(FiniteDimensionalEJA):
                          jordan_product,
                          inner_product,
                          field=field,
+                         matrix_space=superalgebra.matrix_space(),
                          prefix=prefix,
                          **kwargs)
 
@@ -213,19 +214,6 @@ class FiniteDimensionalEJASubalgebra(FiniteDimensionalEJA):
             return super()._element_constructor_(elt)
 
 
-
-    def matrix_space(self):
-        """
-        Return the matrix space of this algebra, which is identical to
-        that of its superalgebra.
-
-        This is correct "by definition," and avoids a mismatch when
-        the subalgebra is trivial (with no matrix basis elements to
-        infer anything from) and the parent is not.
-        """
-        return self.superalgebra().matrix_space()
-
-
     def superalgebra(self):
         """
         Return the superalgebra that this algebra was generated from.