]> gitweb.michael.orlitzky.com - sage.d.git/commitdiff
mjo/clan: begin implementing the clan on S^n
authorMichael Orlitzky <michael@orlitzky.com>
Thu, 15 Jan 2026 01:40:18 +0000 (20:40 -0500)
committerMichael Orlitzky <michael@orlitzky.com>
Thu, 15 Jan 2026 01:40:18 +0000 (20:40 -0500)
mjo/clan/unital_clan.py

index 927dd3b31b951b80e9a4aaf96a96539c6072f33d..37aa8abb241a47c94bb5647f10baefa7688c0358 100644 (file)
@@ -5,6 +5,8 @@ Proper implementation of unital clans (real, finite dimensional
 
 from sage.categories.magmatic_algebras import MagmaticAlgebras
 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
 
@@ -71,3 +73,157 @@ class UnitalClan(CombinatorialFreeModule):
 
         """
         return self._mt[i][j]
+
+    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
+
+
+class SnClan(UnitalClan):
+    r"""
+    The clan of real symmetric matrices of a given size with the
+    clan product and lower-triangular basis ordering of Ishi,
+    based on the up-hat and down-hat products of Vinberg.
+
+    EXAMPLES:
+
+    Verifying the axioms::
+
+        sage: n = 3
+        sage: C = SnClan(n)
+        sage: e = C.basis()
+        sage: all( e[i,i]*e[i,i] == e[i,i] for i in range(n) )
+        True
+        sage: all( e[i,i]*e[j,i] == e[j,i]/2
+        ....:      for i in range(n)
+        ....:      for j in range(i+1,n))
+        True
+        sage: all( e[i,i]*e[i,k] == e[i,k]/2
+        ....:      for i in range(n)
+        ....:      for k in range(i))
+        True
+        sage: all( (e[i,i]*e[j,k]).is_zero()
+        ....:      for i in range(n)
+        ....:      for j in range(n)
+        ....:      for k in range(j+1)
+        ....:      if not i in [j,k] )
+        True
+        sage: all( (e[i,k]*e[i,i]).is_zero()
+        ....:      for i in range(n)
+        ....:      for k in range(i) )
+        True
+        sage: all( (e[j,k]*e[i,i]).is_zero()
+        ....:      for i in range(n)
+        ....:      for j in range(n)
+        ....:      for k in range(j)
+        ....:      if not i in [j,k] )
+        True
+
+    With a little effort, we can lift elements of the clan back into
+    the original (asymmetric) matrix space::
+
+        sage: C = SnClan(3)
+        sage: x = C.an_element(); x
+        2*B(0, 0) + 2*B(1, 0) + 3*B(1, 1)
+        sage: x.lift().lift()
+        [2 2 0]
+        [2 3 0]
+        [0 0 0]
+
+    """
+    def __init__(self, n, scalar_field=QQ, **kwargs):
+        self._rank = n
+
+        Mn = MatrixSpace(scalar_field, n)
+        b = Mn.basis()
+
+        from sage.sets.family import Family
+        Sn_basis = Family({ (i,j) :
+            a*(b[(i,j)] + b[(j,i)])
+            for i in range(n)
+            for j in range(i+1)
+            if (a := 1 - (i == j)/scalar_field(2))
+        })
+
+        # Mn.submodule() destroys our basis keys, so use
+        # SubmoduleWithBasis directly.
+        Sn = SubmoduleWithBasis(Sn_basis,
+                                support_order=b.keys(),
+                                ambient=Mn)
+
+        def up_hat(x):
+            r"""
+            The "up-hat" function on a T-algebra.
+
+            This is the "top half" of a matrix, in the sense that we keep the
+            strictly upper-triangular part, and keep half of the diagonal (so
+            that ``up_hat(x) + down_hat(x) == x``. It is defined by Vinberg on
+            page 381, and is used to construct a clan whose associated cone
+            equals that of the the T-algebra.
+
+            """
+            def l(i,j):
+                if i < j:
+                    return x[i,j]
+                if i == j:
+                    return x[i,j]/scalar_field(2)
+                else:
+                    return 0
+            return Mn(l)
+
+        def down_hat(x):
+            r"""
+            The "down-hat" function on a T-algebra.
+
+            This is the "lower half" of a matrix, in the sense that we keep
+            the strictly lower-triangular part, and keep half of the diagonal
+            (so that ``up_hat(x) + down_hat(x) == x``. It is defined by
+            Vinberg on page 381, and is used to construct a clan whose
+            associated cone equals that of the the T-algebra.
+            """
+            return up_hat(x.T).T
+
+        def cp(x,y):
+            r"""
+            The clan product associated with a T-algebra.
+
+            This is defined by Vinberg on page 381 (as Delta), who
+            then proceeds to explain, over the course of the next few
+            pages, why it results in a clan when the we take the
+            underlying set to be the subspace of symmetric elements.
+
+            .. WARNING:
+
+                We use Ishi's version of this product that reverses
+                the indices.
+
+            """
+            x = x.lift()
+            y = y.lift()
+            return Sn(down_hat(x)*y + y*up_hat(x))
+
+        def ip(x,y):
+            p = cp(x,y) / scalar_field(2)
+            return sum( p[(i,i)] for i in range(n) )
+
+        super().__init__(Sn, cp, ip, **kwargs)
+
+
+    def rank(self) -> int:
+        return self._rank
+
+
+    def __repr__(self) -> str:
+        r"""
+        The string representation of this clan.
+
+        EXAMPLES::
+
+            sage: SnClan(5)
+            Clan S^5 over Rational Field
+
+        """
+        return f"Clan S^{self.rank()} over {self._vector_space().base_ring()}"