]> gitweb.michael.orlitzky.com - sage.d.git/commitdiff
mjo/clan: factor out NormalDecomposition to its own class
authorMichael Orlitzky <michael@orlitzky.com>
Fri, 16 Jan 2026 13:18:39 +0000 (08:18 -0500)
committerMichael Orlitzky <michael@orlitzky.com>
Fri, 16 Jan 2026 13:18:39 +0000 (08:18 -0500)
Some methods require knowledge of the normal decomposition
coordinates.

mjo/clan/clan_element.py
mjo/clan/unital_clan.py

index 2b578a197834d0b36afc15c22a4336727c4d1786..8c9459dcff59f2ef490193068b027a33ac5a2602 100644 (file)
@@ -2,7 +2,7 @@ from sage.modules.with_basis.indexed_element import IndexedFreeModuleElement
 
 class ClanElement(IndexedFreeModuleElement):
     """
-    An element of a Clan.
+    An element of a :class:`UnitalClan`.
     """
 
     def lift(self):
@@ -40,3 +40,35 @@ class ClanElement(IndexedFreeModuleElement):
             for (bi, xi) in self.items()
             for (bj, yj)  in other.items()
         )
+
+
+class NormalDecompositionElement(ClanElement):
+    """
+    An element of a :class:`NormalDecomposition`.
+
+    These may have additional methods that require knowledge of the
+    coordinates in a normal decomposition.
+    """
+    def tr(self):
+        r"""
+        The clan trace of this element; the sum of its diagonal
+        coordinates.
+
+        EXAMPLES::
+
+            sage: from mjo.clan.unital_clan import SnClan
+            sage: C = SnClan(4)
+            sage: C.one().tr()
+            4
+
+        ::
+
+            sage: C = SnClan(3)
+            sage: x = C.an_element(); x
+            2*B(0, 0) + 2*B(1, 0) + 3*B(1, 1)
+            sage: x.tr()
+            5
+
+        """
+        r = self.parent().rank()
+        return sum( self.coefficient((i,i)) for i in range(r) )
index e576d52143076bc2c707e5d3fdd6a7cf3a5e9306..90a4bdbf475ad90289c33f8cfa91f7c0f1bd4071 100644 (file)
@@ -8,7 +8,8 @@ from sage.combinat.free_module import CombinatorialFreeModule
 from sage.modules.with_basis.subquotient import SubmoduleWithBasis
 from sage.rings.rational_field import QQ
 
-from mjo.clan.clan_element import ClanElement
+from mjo.clan.clan_element import (ClanElement,
+                                   NormalDecompositionElement)
 
 class UnitalClan(CombinatorialFreeModule):
     Element = ClanElement
@@ -42,12 +43,11 @@ class UnitalClan(CombinatorialFreeModule):
 
         # Use the vector_space basis keys so we can convert
         # easily between them.
-        CombinatorialFreeModule.__init__(self,
-                                         scalar_field,
-                                         vector_space.basis().keys(),
-                                         category=category,
-                                         prefix=prefix,
-                                         bracket=bracket)
+        super().__init__(scalar_field,
+                         vector_space.basis().keys(),
+                         category=category,
+                         prefix=prefix,
+                         bracket=bracket)
 
         # Now that the CFM has been initialized, we can coerce
         # elements into it to update the multiplication table.
@@ -93,15 +93,48 @@ class UnitalClan(CombinatorialFreeModule):
         return self._mt[i][j]
 
 
+
+class NormalDecomposition(UnitalClan):
+    r"""
+    A unital clan for which a normal decomposition is available.
+    This is needed to compute the rank of the cone, and the clan trace
+    as defined by Ishi.
+    """
+    Element = NormalDecompositionElement
+
+    def __init__(self,
+                 vector_space,
+                 clan_product,
+                 inner_product,
+                 scalar_field=QQ,
+                 category=None,
+                 prefix=None,
+                 bracket=False):
+        # The normal decomposition assumes that we already have a
+        # lower-triangular (Ishi) basis of the form (i,j) or (i,j,k)
+        # -- the latter when the off-diagonals are greater-than-one
+        # dimensional.
+        self._rank = 1 + max( k[0]
+                              for k in vector_space.basis().keys()
+                              if k[0] == k[1] )
+
+        super().__init__(vector_space,
+                         clan_product,
+                         inner_product,
+                         scalar_field=QQ,
+                         category=None,
+                         prefix=None,
+                         bracket=False)
+
     def rank(self) -> int:
         r"""
         The rank of this clan. Not implemented by default,
         because it is only easy to deduce from a normal decomposition.
         """
-        raise NotImplementedError
+        return self._rank
 
 
-class SnClan(UnitalClan):
+class SnClan(NormalDecomposition):
     r"""
     The normally-decomposed clan of real symmetric matrices of a
     given size with the clan product and lower-triangular basis
@@ -110,6 +143,13 @@ class SnClan(UnitalClan):
 
     EXAMPLES:
 
+    The rank of this clan is the size of the matrices::
+
+        sage: n = ZZ.random_element(1,5)
+        sage: C = SnClan(n)
+        sage: C.rank() == n
+        True
+
     Verifying the axioms::
 
         sage: n = 3
@@ -155,8 +195,6 @@ class SnClan(UnitalClan):
 
     """
     def __init__(self, n, scalar_field=QQ, **kwargs):
-        self._rank = n
-
         from sage.matrix.matrix_space import MatrixSpace
         Mn = MatrixSpace(scalar_field, n)
         b = Mn.basis()
@@ -233,10 +271,6 @@ class SnClan(UnitalClan):
         super().__init__(Sn, cp, ip, **kwargs)
 
 
-    def rank(self) -> int:
-        return self._rank
-
-
     def __repr__(self) -> str:
         r"""
         The string representation of this clan.