]> gitweb.michael.orlitzky.com - sage.d.git/commitdiff
mjo/clan/unital_clan.py: begin clan implementation
authorMichael Orlitzky <michael@orlitzky.com>
Fri, 2 Jan 2026 16:39:30 +0000 (11:39 -0500)
committerMichael Orlitzky <michael@orlitzky.com>
Fri, 2 Jan 2026 16:39:30 +0000 (11:39 -0500)
mjo/clan/unital_clan.py [new file with mode: 0644]

diff --git a/mjo/clan/unital_clan.py b/mjo/clan/unital_clan.py
new file mode 100644 (file)
index 0000000..f4e3f59
--- /dev/null
@@ -0,0 +1,144 @@
+r"""
+Proper implementation of unital clans (real, finite dimensional
+"compact" left-symmetric algebras with a unit element).
+"""
+
+from sage.categories.magmatic_algebras import MagmaticAlgebras
+from sage.combinat.free_module import CombinatorialFreeModule
+from sage.modules.with_basis.indexed_element import IndexedFreeModuleElement
+
+from mjo.eja.eja_utils import _all2list
+
+class UnitalClan(CombinatorialFreeModule):
+    Element = IndexedFreeModuleElement
+
+    def __init__(self,
+                 basis,
+                 clan_product,
+                 inner_product,
+                 field=QQ,
+                 prefix='c',
+                 check_axioms=True):
+
+        n = len(basis)
+        if n == 0:
+            raise ValueError("not yet")
+
+        associative = None
+        commutative = None
+        if n <= 1:
+            # All zero- and one-dimensional algebras are just the real
+            # numbers with (some positive multiples of) the usual
+            # multiplication as its Jordan and inner-product.
+            associative = True
+            commutative = True
+
+        if associative is None:
+            # We should figure it out. As with check_axioms, we have to do
+            # this without the help of the _jordan_product_is_associative()
+            # method because we need to know the category before we
+            # initialize the algebra.
+            associative = all( clan_product(clan_product(bi,bj),bk)
+                               ==
+                               clan_product(bi,clan_product(bj,bk))
+                               for bi in basis
+                               for bj in basis
+                               for bk in basis )
+
+        # Compute the multiplication tables and inner-product matrix.
+        self._inner_product_matrix = matrix.identity(field, n)
+        clan_mult = [ [ None for j in range(n) ]
+                      for i in range(n) ]
+
+        for i in range(n):
+            for j in range(n):
+                clan_mult[i][j] = clan_product(basis[i], basis[j])
+                if j <= i+1:
+                    ip = inner_product(basis[i], basis[j])
+                    self._inner_product_matrix[i,j] = ip
+                    self._inner_product_matrix[j,i] = ip
+        self._inner_product_matrix._cache = {'hermitian': True}
+        self._inner_product_matrix.set_immutable()
+
+        if commutative is None:
+            commutative = all( clan_mult[i][j] == clan_mult[j][i]
+                               for i in range(n)
+                               for j in range(n) )
+
+        category = MagmaticAlgebras(field).FiniteDimensional()
+        category = category.WithBasis().Unital()
+
+        if associative:
+            category = category.Associative()
+        if commutative:
+            category = category.Commutative()
+
+        # Call the superclass constructor so that we can use its from_vector()
+        # method to build our multiplication table.
+        CombinatorialFreeModule.__init__(self,
+                                         field,
+                                         range(n),
+                                         prefix=prefix,
+                                         category=category,
+                                         bracket=False)
+
+        # Now we have to compute the multiplication table by turning
+        # whatever the input basis was into vectors whose coordinates
+        # we can compute.
+        self._input_basis = basis
+        self._input_basis_space = self._input_basis[0].parent()
+
+        # Now create the vector space for the algebra, which will have
+        # its own set of non-ambient coordinates (in terms of the
+        # supplied basis).
+        degree = len(_all2list(basis[0]))
+
+        # Build an ambient space that fits our matrix basis when
+        # written out as "long vectors."
+        V = VectorSpace(field, degree)
+        vector_basis = tuple( V(_all2list(b)) for b in basis )
+
+        # Save the span of our matrix basis (when written out as long
+        # vectors) because otherwise we'll have to reconstruct it
+        # every time we want to coerce a matrix into the algebra.
+        self._input_basis_span = V.span_of_basis( vector_basis )
+
+        for i in range(n):
+            for j in range(n):
+                # The jordan product returns a matrixy answer, so we
+                # have to convert it to the algebra coordinates.
+                elt = clan_mult[i][j]
+                elt = self._input_basis_span.coordinate_vector(V(_all2list(elt)))
+                clan_mult[i][j] = self.from_vector(elt)
+
+        self._multiplication_table = clan_mult
+        if check_axioms:
+            pass
+
+    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.
+        """
+        return None
+
+    def product_on_basis(self, i, j):
+        r"""
+        Returns the Jordan product of the `i` and `j`th basis elements.
+
+        This completely defines the clan product, and is used directly
+        by our superclass machinery to implement :meth:`product`.
+
+        """
+        return self._multiplication_table[i][j]
+
+
+    def inner_product(self, x, y):
+        """
+        The inner product associated with this clan.
+        """
+        B = self._inner_product_matrix
+        return (B*x.to_vector()).inner_product(y.to_vector())