]> gitweb.michael.orlitzky.com - sage.d.git/commitdiff
mjo/clan: more class hierarchy refactoring
authorMichael Orlitzky <michael@orlitzky.com>
Sun, 22 Feb 2026 17:32:43 +0000 (12:32 -0500)
committerMichael Orlitzky <michael@orlitzky.com>
Sun, 22 Feb 2026 17:32:43 +0000 (12:32 -0500)
Renamed MatrixAlgebra to TAlgebraClan, since in theory the up-hat and
down-hat work for any T-algebra. Separated that out along with the
NormalDecomposition class.

mjo/clan/all.py
mjo/clan/clan.py
mjo/clan/clan_element.py
mjo/clan/clan_operator.py
mjo/clan/normal_decomposition.py [new file with mode: 0644]
mjo/clan/normal_decomposition_element.py
mjo/clan/t_algebra_clan.py [new file with mode: 0644]
mjo/clan/vinberg_clan.py

index f98232a56063a5f4ad9e62ec68102dacf5fbd564..bb8b79ac9ff2a80c48a0c26ec018e38497b2d5da 100644 (file)
@@ -1,10 +1,7 @@
 r"""
 All user-facing imports from mjo.clan.
 """
-from mjo.clan.clan import (
-    Clans,
-    ComplexHermitianClan,
-    RealSymmetricClan
-)
-
+from mjo.clan.clan import Clans
+from mjo.clan.t_algebra_clan import ( ComplexHermitianClan,
+                                      RealSymmetricClan )
 from mjo.clan.vinberg_clan import VinbergClan
index 4c8d241a666a68dd0b619337c6643725777a454e..d543135375679f5cff0b8f1779742eb241538a58 100644 (file)
@@ -6,9 +6,7 @@ correspond to the homogeneous convex cones.
 """
 
 from sage.categories.category_types import Category_over_base_ring
-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
 
 class Clans(Category_over_base_ring):
@@ -26,6 +24,7 @@ class Clans(Category_over_base_ring):
 
     """
     def super_categories(self):
+        from sage.categories.magmatic_algebras import MagmaticAlgebras
         R = self.base_ring()
         return [MagmaticAlgebras(R).FiniteDimensional().WithBasis()]
     class ParentMethods:
@@ -35,6 +34,7 @@ class Clans(Category_over_base_ring):
     class MorphismMethods:
         pass
 
+
 class Clan(CombinatorialFreeModule):
     r"""
     Base class for clans.
@@ -151,487 +151,3 @@ class UnitalClan(Clan):
                          category=category,
                          prefix=prefix,
                          bracket=bracket)
-
-
-
-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.
-    """
-    from mjo.clan.normal_decomposition_element import (
-        NormalDecompositionElement as Element
-    )
-
-    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,k).
-        self._rank = 1 + max( k[0]
-                              for k in vector_space.indices()
-                              if k[0] == k[1] )
-
-        super().__init__(vector_space,
-                         clan_product,
-                         inner_product,
-                         scalar_field=scalar_field,
-                         category=None,
-                         prefix=None,
-                         bracket=False)
-
-    def idempotent(self, i):
-        r"""
-        Return the i'th idempotent in this clan.
-
-        SETUP::
-
-            sage: from mjo.clan.clan import RealSymmetricClan
-
-        EXAMPLES::
-
-            sage: C = RealSymmetricClan(3)
-            sage: C.idempotent(0)
-            B(0, 0, 1)
-            sage: C.idempotent(1)
-            B(1, 1, 1)
-            sage: C.idempotent(2)
-            B(2, 2, 1)
-
-        """
-        return self.sum_of_monomials(
-            (j,k,l)
-            for (j,k,l) in self.indices()
-            if j == i and k == i
-        )
-
-    def one(self):
-        r"""
-        Return the unit element of this clan.
-
-        SETUP::
-
-            sage: from mjo.clan.clan import RealSymmetricClan
-
-        EXAMPLES::
-
-            sage: C = RealSymmetricClan(4)
-            sage: I = C.one()
-            sage: I.lift().lift()
-            [1 0 0 0]
-            [0 1 0 0]
-            [0 0 1 0]
-            [0 0 0 1]
-            sage: all( I*b == b and b*I == b for b in C.basis() )
-            True
-
-        """
-        return self.sum_of_monomials(
-            (i,j,k)
-            for (i,j,k) in self.indices()
-            if i == j
-        )
-
-    def rank(self) -> int:
-        r"""
-        The rank of this clan. Only implemented for normal
-        decompositions because we don't know the rank until
-        we know the (number of) idempotents used for the
-        normal decomposition.
-
-        SETUP::
-
-            sage: from mjo.clan.clan import ComplexHermitianClan
-
-        EXAMPLES::
-
-            sage: C = ComplexHermitianClan(2)
-            sage: C.rank()
-            2
-
-        """
-        return self._rank
-
-
-class MatrixClan(NormalDecomposition):
-    r"""
-    A clan arising from a T-algebra of Hermitian matrices.
-    """
-    @staticmethod
-    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.
-        """
-        one = x.base_ring().one()
-        two = 2*one
-        return x.parent().sum_of_terms(
-            (idx, a*c)
-            for (idx, c) in x.items()
-            if idx[0] <= idx[1]
-            if (a := one - (idx[0] == idx[1])/two)
-        )
-
-    @staticmethod
-    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 MatrixClan._up_hat(x.transpose()).transpose()
-
-    @staticmethod
-    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.
-
-        """
-        # First we lift x,y out of the clan and into
-        # the general matrix space.
-        H = x.parent()
-        x = x.lift()
-        y = y.lift()
-
-        # Note: within a Hermitian matrix space we can't represent the
-        # (non-Hermitian) intermediate terms _down_hat(x) and _up_hat(x)!
-        return H(MatrixClan._down_hat(x)*y + y*MatrixClan._up_hat(x))
-
-    @staticmethod
-    def _ip(x,y):
-        r"""
-        The Ishi inner product (NOT the canonical one) on a clan.
-        """
-        two = x.base_ring()(2)
-        p = MatrixClan._cp(x,y) / two
-        return sum( p[idx]
-                    for idx in p.monomial_coefficients()
-                    if idx[0] == idx[1] )
-
-    def from_matrix(self, x):
-        r"""
-        Construct an element of this clan from a Hermitian matrix.
-
-        SETUP::
-
-            sage: from mjo.clan.clan import ( ComplexHermitianClan,
-            ....:                             RealSymmetricClan )
-            sage: from mjo.hurwitz import ComplexMatrixAlgebra
-
-        EXAMPLES::
-
-            sage: C = RealSymmetricClan(2)
-            sage: X = matrix(QQ, [[2,1],
-            ....:                 [1,4]])
-            sage: C.from_matrix(X).lift().lift()
-            [2 1]
-            [1 4]
-
-        ::
-
-            sage: C = ComplexHermitianClan(2)
-            sage: A = ComplexMatrixAlgebra(2, QQbar, QQ)
-            sage: X = A([ [ 2,       1 + 2*I],
-            ....:         [ 1 - 2*I,      -1] ])
-            sage: C.from_matrix(X).lift().lift()
-            ┌──────────┬─────────┐
-            │ 2        │ 2*I + 1 │
-            ├──────────┼─────────┤
-            │ -2*I + 1 │ -1      │
-            └──────────┴─────────┘
-
-        """
-        if not x.base_ring() == self.base_ring():
-            raise ValueError(f"base ring of matrix ({x.base_ring()})"
-                             " does not match clan ({self.base_ring()})")
-        if not x == x.conjugate_transpose():
-            raise ValueError("matrix is not Hermitian")
-        if not self.rank() == x.nrows():
-            raise ValueError(f"matrix must have {self.rank()} rows/columns")
-
-        try:
-            # HurwitzMatrixAlgebra
-            return self.sum_of_terms( (idx, x[idx])
-                                      for idx in self.indices() )
-        except IndexError:
-            # MatrixSpace
-            return self.sum_of_terms( ((i,j,k), x[i,j])
-                                      for (i,j,k) in self.indices() )
-
-
-
-
-    def from_list(self, l):
-        r"""
-        Construct an element of this clan from a list.
-
-        This is a shortcut for :meth:`from_matrix`, as the clan knows
-        what the ambient matrix space was.
-
-        SETUP::
-
-            sage: from mjo.clan.clan import ( ComplexHermitianClan,
-            ....:                             RealSymmetricClan )
-
-        EXAMPLES::
-
-            sage: C = ComplexHermitianClan(2)
-            sage: X = C.from_list([[0,I],[-I,0]])
-            sage: X.lift().lift()
-            ┌────┬───┐
-            │ 0  │ I │
-            ├────┼───┤
-            │ -I │ 0 │
-            └────┴───┘
-
-        This relies on the ambient vector space to convert a list to a
-        matrix, so in the real case, we can use one long list (as
-        opposed to a list of lists):
-
-            sage: C = RealSymmetricClan(3)
-            sage: X = C.from_list([2,6,10,6,10,14,10,14,18])
-            sage: X.lift().lift()
-            [ 2  6 10]
-            [ 6 10 14]
-            [10 14 18]
-
-        """
-        return self.from_matrix(self._vector_space.ambient()(l))
-
-
-class RealSymmetricClan(MatrixClan):
-    r"""
-    The normally-decomposed 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.
-
-    SETUP::
-
-        sage: from mjo.clan.clan import RealSymmetricClan
-
-    EXAMPLES:
-
-    The rank of this clan is the size of the matrices::
-
-        sage: n = ZZ.random_element(1,5)
-        sage: C = RealSymmetricClan(n)
-        sage: C.rank() == n
-        True
-
-    Verifying the axioms::
-
-        sage: n = 3
-        sage: C = RealSymmetricClan(n)
-        sage: e = C.basis()
-        sage: all( e[i,i,1]*e[i,i,1] == e[i,i,1] for i in range(n) )
-        True
-        sage: all( e[i,i,1]*e[j,i,1] == e[j,i,1]/2
-        ....:      for i in range(n)
-        ....:      for j in range(i+1,n))
-        True
-        sage: all( e[i,i,1]*e[i,k,1] == e[i,k,1]/2
-        ....:      for i in range(n)
-        ....:      for k in range(i))
-        True
-        sage: all( (e[i,i,1]*e[j,k,1]).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,1]*e[i,i,1]).is_zero()
-        ....:      for i in range(n)
-        ....:      for k in range(i) )
-        True
-        sage: all( (e[j,k,1]*e[i,i,1]).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 = RealSymmetricClan(3)
-        sage: x = C.an_element(); x
-        2*B(0, 0, 1) + 2*B(1, 0, 1) + 3*B(1, 1, 1)
-        sage: x.lift().lift()
-        [2 2 0]
-        [2 3 0]
-        [0 0 0]
-
-    """
-    def __init__(self, n, scalar_field=QQ, **kwargs):
-        from sage.matrix.matrix_space import MatrixSpace
-        Mn = MatrixSpace(scalar_field, n)
-        b = Mn.basis()
-
-        from sage.sets.family import Family
-        Sn_basis = Family({ (i,j,1) :
-            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)
-
-        super().__init__(Sn, MatrixClan._cp, MatrixClan._ip, **kwargs)
-
-
-    def __repr__(self) -> str:
-        r"""
-        The string representation of this clan.
-
-        SETUP::
-
-            sage: from mjo.clan.clan import RealSymmetricClan
-
-        EXAMPLES::
-
-            sage: RealSymmetricClan(5)
-            Clan S^5 over Rational Field
-
-        """
-        return f"Clan S^{self.rank()} over {self.base_ring()}"
-
-
-class ComplexHermitianClan(MatrixClan):
-    r"""
-    The normally-decomposed clan of complex Hermitian 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.
-
-    SETUP::
-
-        sage: from mjo.clan.clan import ComplexHermitianClan
-
-    EXAMPLES:
-
-    The rank of this clan is the size of the matrices::
-
-        sage: n = ZZ.random_element(1,5)
-        sage: C = ComplexHermitianClan(n)
-        sage: C.rank() == n
-        True
-
-    Verifying the axioms::
-
-        sage: n = 3
-        sage: C = ComplexHermitianClan(n)
-        sage: e = C.basis()
-        sage: r = C.rank()
-        sage: all( e[i,j,k]*e[i,j,k] == e[i,j,k]
-        ....:      for (i,j,k) in e.keys()
-        ....:      if i == j )
-        True
-        sage: all( C.idempotent(i)*e[j,i,k] == e[j,i,k]/2
-        ....:      for (i,j,k) in e.keys()
-        ....:      if i < j )
-        True
-        sage: all( C.idempotent(i)*e[i,k,z] == e[i,k,z]/2
-        ....:      for (i,k,z) in e.keys()
-        ....:      if k < i)
-        True
-        sage: all( (C.idempotent(i)*e[j,k,l]).is_zero()
-        ....:      for i in range(r)
-        ....:      for (j,k,l) in e.keys()
-        ....:      if k <= j and i not in [j,k] )
-        True
-        sage: all( (e[i,k,l]*C.idempotent(i)).is_zero()
-        ....:      for (i,k,l) in e.keys()
-        ....:      if k < i )
-        True
-        sage: all( (e[j,k,l]*C.idempotent(i)).is_zero()
-        ....:      for i in range(r)
-        ....:      for (j,k,l) in e.keys()
-        ....:      if i not in [j,k] )
-        True
-
-    With a little effort, we can lift elements of the clan back into
-    the original (asymmetric) matrix space::
-
-        sage: C = ComplexHermitianClan(3)
-        sage: x = C.an_element(); x
-        2*B(0, 0, 1) + 3*B(1, 0, I) + 2*B(1, 0, 1)
-        sage: x.lift().lift()
-        ┌─────────┬──────────┬───┐
-        │ 2       │ -3*I + 2 │ 0 │
-        ├─────────┼──────────┼───┤
-        │ 3*I + 2 │ 0        │ 0 │
-        ├─────────┼──────────┼───┤
-        │ 0       │ 0        │ 0 │
-        └─────────┴──────────┴───┘
-
-    """
-    def __init__(self, n, scalar_field=QQ, **kwargs):
-        from mjo.hurwitz import ComplexMatrixAlgebra
-        from sage.rings.qqbar import QQbar
-        Mn = ComplexMatrixAlgebra(n, QQbar, scalar_field)
-        b = Mn.basis()
-
-        from sage.sets.family import Family
-        Hn_basis = Family({
-          (i,j,k) : z
-          for (i,j,k) in b.keys()
-          if j <= i
-          if (a := 1 - (i == j)/scalar_field(2))
-          if (z := a*(b[i,j,k] + b[i,j,k].conjugate_transpose()))
-        })
-
-        # Mn.submodule() destroys our basis keys, so use
-        # SubmoduleWithBasis directly.
-        Hn = SubmoduleWithBasis(Hn_basis,
-                                support_order=b.keys(),
-                                ambient=Mn)
-
-        super().__init__(Hn, MatrixClan._cp, MatrixClan._ip, **kwargs)
-
-
-    def __repr__(self) -> str:
-        r"""
-        The string representation of this clan.
-
-        SETUP::
-
-            sage: from mjo.clan.clan import ComplexHermitianClan
-
-        EXAMPLES::
-
-            sage: ComplexHermitianClan(1)
-            Clan H^1 over Rational Field
-
-        """
-        return f"Clan H^{self.rank()} over {self.base_ring()}"
index be89d89d70a3cd6dee050f8aea03b17290a3b20e..b64211d65eebc698b1e0efbdb3d08e88de31fffc 100644 (file)
@@ -24,7 +24,7 @@ class ClanElement(IndexedFreeModuleElement):
 
         SETUP::
 
-            sage: from mjo.clan.clan import RealSymmetricClan
+            sage: from mjo.clan.t_algebra_clan import RealSymmetricClan
 
         EXAMPLES:
 
@@ -54,7 +54,7 @@ class ClanElement(IndexedFreeModuleElement):
 
         SETUP::
 
-            sage: from mjo.clan.clan import RealSymmetricClan
+            sage: from mjo.clan.t_algebra_clan import RealSymmetricClan
 
         EXAMPLES:
 
index 743aa5f67e2696b99910ad3ae9d598b6dab65861..e400edf2bfd6eb9385e152a8e47b92c4e331ffde 100644 (file)
@@ -8,8 +8,8 @@ class ClanOperator(Map):
 
     SETUP::
 
-        sage: from mjo.clan.clan import RealSymmetricClan
         sage: from mjo.clan.clan_operator import ClanOperator
+        sage: from mjo.clan.t_algebra_clan import RealSymmetricClan
 
     EXAMPLES:
 
@@ -94,8 +94,10 @@ class ClanOperator(Map):
         SETUP::
 
             sage: from mjo.clan.clan_operator import ClanOperator
-            sage: from mjo.clan.clan import ( ComplexHermitianClan,
-            ....:                             RealSymmetricClan )
+            sage: from mjo.clan.t_algebra_clan import (
+            ....:     ComplexHermitianClan,
+            ....:     RealSymmetricClan
+            ....: )
             sage: from mjo.clan.vinberg_clan import VinbergClan
 
         EXAMPLES::
@@ -137,7 +139,7 @@ class ClanOperator(Map):
 
         SETUP::
 
-            sage: from mjo.clan.clan import ComplexHermitianClan
+            sage: from mjo.clan.t_algebra_clan import ComplexHermitianClan
             sage: from mjo.clan.clan_operator import ClanOperator
 
         EXAMPLES:
@@ -187,8 +189,10 @@ class ClanOperator(Map):
         SETUP::
 
             sage: from mjo.clan.clan_operator import ClanOperator
-            sage: from mjo.clan.clan import ( ComplexHermitianClan,
-            ....:                             RealSymmetricClan )
+            sage: from mjo.clan.t_algebra_clan import (
+            ....:     ComplexHermitianClan,
+            ....:     RealSymmetricClan
+            ....: )
 
         EXAMPLES::
 
@@ -249,7 +253,7 @@ class ClanOperator(Map):
         SETUP::
 
             sage: from mjo.clan.clan_operator import ClanOperator
-            sage: from mjo.clan.clan import ComplexHermitianClan
+            sage: from mjo.clan.t_algebra_clan import ComplexHermitianClan
 
         EXAMPLES::
 
@@ -280,7 +284,7 @@ class ClanOperator(Map):
         SETUP::
 
             sage: from mjo.clan.clan_operator import ClanOperator
-            sage: from mjo.clan.clan import RealSymmetricClan
+            sage: from mjo.clan.t_algebra_clan import RealSymmetricClan
 
         EXAMPLES::
 
diff --git a/mjo/clan/normal_decomposition.py b/mjo/clan/normal_decomposition.py
new file mode 100644 (file)
index 0000000..0bb945d
--- /dev/null
@@ -0,0 +1,106 @@
+from mjo.clan.clan import UnitalClan
+
+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.
+    """
+    from mjo.clan.normal_decomposition_element import (
+        NormalDecompositionElement as Element
+    )
+
+    from sage.rings.rational_field import QQ
+    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,k).
+        self._rank = 1 + max( k[0]
+                              for k in vector_space.indices()
+                              if k[0] == k[1] )
+
+        super().__init__(vector_space,
+                         clan_product,
+                         inner_product,
+                         scalar_field=scalar_field,
+                         category=None,
+                         prefix=None,
+                         bracket=False)
+
+    def idempotent(self, i):
+        r"""
+        Return the i'th idempotent in this clan.
+
+        SETUP::
+
+            sage: from mjo.clan.t_algebra_clan import RealSymmetricClan
+
+        EXAMPLES::
+
+            sage: C = RealSymmetricClan(3)
+            sage: C.idempotent(0)
+            B(0, 0, 1)
+            sage: C.idempotent(1)
+            B(1, 1, 1)
+            sage: C.idempotent(2)
+            B(2, 2, 1)
+
+        """
+        return self.sum_of_monomials(
+            (j,k,l)
+            for (j,k,l) in self.indices()
+            if j == i and k == i
+        )
+
+    def one(self):
+        r"""
+        Return the unit element of this clan.
+
+        SETUP::
+
+            sage: from mjo.clan.t_algebra_clan import RealSymmetricClan
+
+        EXAMPLES::
+
+            sage: C = RealSymmetricClan(4)
+            sage: I = C.one()
+            sage: I.lift().lift()
+            [1 0 0 0]
+            [0 1 0 0]
+            [0 0 1 0]
+            [0 0 0 1]
+            sage: all( I*b == b and b*I == b for b in C.basis() )
+            True
+
+        """
+        return self.sum_of_monomials(
+            (i,j,k)
+            for (i,j,k) in self.indices()
+            if i == j
+        )
+
+    def rank(self) -> int:
+        r"""
+        The rank of this clan. Only implemented for normal
+        decompositions because we don't know the rank until
+        we know the (number of) idempotents used for the
+        normal decomposition.
+
+        SETUP::
+
+            sage: from mjo.clan.t_algebra_clan import ComplexHermitianClan
+
+        EXAMPLES::
+
+            sage: C = ComplexHermitianClan(2)
+            sage: C.rank()
+            2
+
+        """
+        return self._rank
index 586c1e5f1df3820a5397d7ade12e5eacd1e77db4..b577109ee5a90a0cfaf6979ca203fbbaf0068c19 100644 (file)
@@ -18,7 +18,7 @@ class NormalDecompositionElement(ClanElement):
 
         SETUP::
 
-            sage: from mjo.clan.clan import RealSymmetricClan
+            sage: from mjo.clan.t_algebra_clan import RealSymmetricClan
 
         EXAMPLES::
 
@@ -51,7 +51,7 @@ class NormalDecompositionElement(ClanElement):
 
         SETUP::
 
-            sage: from mjo.clan.clan import RealSymmetricClan
+            sage: from mjo.clan.t_algebra_clan import RealSymmetricClan
 
         EXAMPLES::
 
@@ -96,7 +96,7 @@ class NormalDecompositionElement(ClanElement):
 
         SETUP::
 
-            sage: from mjo.clan.clan import RealSymmetricClan
+            sage: from mjo.clan.t_algebra_clan import RealSymmetricClan
 
         EXAMPLES::
 
@@ -147,7 +147,7 @@ class NormalDecompositionElement(ClanElement):
 
         SETUP::
 
-            sage: from mjo.clan.clan import RealSymmetricClan
+            sage: from mjo.clan.t_algebra_clan import RealSymmetricClan
 
         EXAMPLES:
 
@@ -214,7 +214,7 @@ class NormalDecompositionElement(ClanElement):
 
         SETUP::
 
-            sage: from mjo.clan.clan import RealSymmetricClan
+            sage: from mjo.clan.t_algebra_clan import RealSymmetricClan
             sage: from mjo.clan.vinberg_clan import VinbergClan
 
         EXAMPLES:
@@ -277,7 +277,7 @@ class NormalDecompositionElement(ClanElement):
 
         SETUP::
 
-            sage: from mjo.clan.clan import RealSymmetricClan
+            sage: from mjo.clan.t_algebra_clan import RealSymmetricClan
 
         EXAMPLES:
 
diff --git a/mjo/clan/t_algebra_clan.py b/mjo/clan/t_algebra_clan.py
new file mode 100644 (file)
index 0000000..7a8ccd3
--- /dev/null
@@ -0,0 +1,387 @@
+from sage.rings.rational_field import QQ
+
+from mjo.clan.normal_decomposition import NormalDecomposition
+
+
+class TAlgebraClan(NormalDecomposition):
+    r"""
+    A clan arising from a T-algebra of Hermitian matrices.
+    """
+    @staticmethod
+    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.
+        """
+        one = x.base_ring().one()
+        two = 2*one
+        return x.parent().sum_of_terms(
+            (idx, a*c)
+            for (idx, c) in x.items()
+            if idx[0] <= idx[1]
+            if (a := one - (idx[0] == idx[1])/two)
+        )
+
+    @staticmethod
+    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 TAlgebraClan._up_hat(x.transpose()).transpose()
+
+    @staticmethod
+    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.
+
+        """
+        # First we lift x,y out of the clan and into
+        # the general matrix space.
+        H = x.parent()
+        x = x.lift()
+        y = y.lift()
+
+        # Note: within a Hermitian matrix space we can't represent the
+        # (non-Hermitian) intermediate terms _down_hat(x) and _up_hat(x)!
+        return H(TAlgebraClan._down_hat(x)*y + y*TAlgebraClan._up_hat(x))
+
+    @staticmethod
+    def _ip(x,y):
+        r"""
+        The Ishi inner product (NOT the canonical one) on a clan.
+        """
+        two = x.base_ring()(2)
+        p = TAlgebraClan._cp(x,y) / two
+        return sum( p[idx]
+                    for idx in p.monomial_coefficients()
+                    if idx[0] == idx[1] )
+
+    def from_matrix(self, x):
+        r"""
+        Construct an element of this clan from a Hermitian matrix.
+
+        SETUP::
+
+            sage: from mjo.clan.t_algebra_clan import (
+            ....:     ComplexHermitianClan,
+            ....:     RealSymmetricClan
+            ....: )
+            sage: from mjo.hurwitz import ComplexMatrixAlgebra
+
+        EXAMPLES::
+
+            sage: C = RealSymmetricClan(2)
+            sage: X = matrix(QQ, [[2,1],
+            ....:                 [1,4]])
+            sage: C.from_matrix(X).lift().lift()
+            [2 1]
+            [1 4]
+
+        ::
+
+            sage: C = ComplexHermitianClan(2)
+            sage: A = ComplexMatrixAlgebra(2, QQbar, QQ)
+            sage: X = A([ [ 2,       1 + 2*I],
+            ....:         [ 1 - 2*I,      -1] ])
+            sage: C.from_matrix(X).lift().lift()
+            ┌──────────┬─────────┐
+            │ 2        │ 2*I + 1 │
+            ├──────────┼─────────┤
+            │ -2*I + 1 │ -1      │
+            └──────────┴─────────┘
+
+        """
+        if not x.base_ring() == self.base_ring():
+            raise ValueError(f"base ring of matrix ({x.base_ring()})"
+                             " does not match clan ({self.base_ring()})")
+        if not x == x.conjugate_transpose():
+            raise ValueError("matrix is not Hermitian")
+        if not self.rank() == x.nrows():
+            raise ValueError(f"matrix must have {self.rank()} rows/columns")
+
+        try:
+            # HurwitzMatrixAlgebra
+            return self.sum_of_terms( (idx, x[idx])
+                                      for idx in self.indices() )
+        except IndexError:
+            # MatrixSpace
+            return self.sum_of_terms( ((i,j,k), x[i,j])
+                                      for (i,j,k) in self.indices() )
+
+
+
+
+    def from_list(self, l):
+        r"""
+        Construct an element of this clan from a list.
+
+        This is a shortcut for :meth:`from_matrix`, as the clan knows
+        what the ambient matrix space was.
+
+        SETUP::
+
+            sage: from mjo.clan.t_algebra_clan import (
+            ....:     ComplexHermitianClan,
+            ....:     RealSymmetricClan
+            ....: )
+
+        EXAMPLES::
+
+            sage: C = ComplexHermitianClan(2)
+            sage: X = C.from_list([[0,I],[-I,0]])
+            sage: X.lift().lift()
+            ┌────┬───┐
+            │ 0  │ I │
+            ├────┼───┤
+            │ -I │ 0 │
+            └────┴───┘
+
+        This relies on the ambient vector space to convert a list to a
+        matrix, so in the real case, we can use one long list (as
+        opposed to a list of lists):
+
+            sage: C = RealSymmetricClan(3)
+            sage: X = C.from_list([2,6,10,6,10,14,10,14,18])
+            sage: X.lift().lift()
+            [ 2  6 10]
+            [ 6 10 14]
+            [10 14 18]
+
+        """
+        return self.from_matrix(self._vector_space.ambient()(l))
+
+
+class RealSymmetricClan(TAlgebraClan):
+    r"""
+    The normally-decomposed 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.
+
+    SETUP::
+
+        sage: from mjo.clan.t_algebra_clan import RealSymmetricClan
+
+    EXAMPLES:
+
+    The rank of this clan is the size of the matrices::
+
+        sage: n = ZZ.random_element(1,5)
+        sage: C = RealSymmetricClan(n)
+        sage: C.rank() == n
+        True
+
+    Verifying the axioms::
+
+        sage: n = 3
+        sage: C = RealSymmetricClan(n)
+        sage: e = C.basis()
+        sage: all( e[i,i,1]*e[i,i,1] == e[i,i,1] for i in range(n) )
+        True
+        sage: all( e[i,i,1]*e[j,i,1] == e[j,i,1]/2
+        ....:      for i in range(n)
+        ....:      for j in range(i+1,n))
+        True
+        sage: all( e[i,i,1]*e[i,k,1] == e[i,k,1]/2
+        ....:      for i in range(n)
+        ....:      for k in range(i))
+        True
+        sage: all( (e[i,i,1]*e[j,k,1]).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,1]*e[i,i,1]).is_zero()
+        ....:      for i in range(n)
+        ....:      for k in range(i) )
+        True
+        sage: all( (e[j,k,1]*e[i,i,1]).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 = RealSymmetricClan(3)
+        sage: x = C.an_element(); x
+        2*B(0, 0, 1) + 2*B(1, 0, 1) + 3*B(1, 1, 1)
+        sage: x.lift().lift()
+        [2 2 0]
+        [2 3 0]
+        [0 0 0]
+
+    """
+    def __init__(self, n, scalar_field=QQ, **kwargs):
+        from sage.matrix.matrix_space import MatrixSpace
+        Mn = MatrixSpace(scalar_field, n)
+        b = Mn.basis()
+
+        from sage.sets.family import Family
+        Sn_basis = Family({ (i,j,1) :
+            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.
+        from sage.modules.with_basis.subquotient import SubmoduleWithBasis
+        Sn = SubmoduleWithBasis(Sn_basis,
+                                support_order=b.keys(),
+                                ambient=Mn)
+
+        super().__init__(Sn, TAlgebraClan._cp, TAlgebraClan._ip, **kwargs)
+
+
+    def __repr__(self) -> str:
+        r"""
+        The string representation of this clan.
+
+        SETUP::
+
+            sage: from mjo.clan.t_algebra_clan import RealSymmetricClan
+
+        EXAMPLES::
+
+            sage: RealSymmetricClan(5)
+            Clan S^5 over Rational Field
+
+        """
+        return f"Clan S^{self.rank()} over {self.base_ring()}"
+
+
+class ComplexHermitianClan(TAlgebraClan):
+    r"""
+    The normally-decomposed clan of complex Hermitian 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.
+
+    SETUP::
+
+        sage: from mjo.clan.t_algebra_clan import ComplexHermitianClan
+
+    EXAMPLES:
+
+    The rank of this clan is the size of the matrices::
+
+        sage: n = ZZ.random_element(1,5)
+        sage: C = ComplexHermitianClan(n)
+        sage: C.rank() == n
+        True
+
+    Verifying the axioms::
+
+        sage: n = 3
+        sage: C = ComplexHermitianClan(n)
+        sage: e = C.basis()
+        sage: r = C.rank()
+        sage: all( e[i,j,k]*e[i,j,k] == e[i,j,k]
+        ....:      for (i,j,k) in e.keys()
+        ....:      if i == j )
+        True
+        sage: all( C.idempotent(i)*e[j,i,k] == e[j,i,k]/2
+        ....:      for (i,j,k) in e.keys()
+        ....:      if i < j )
+        True
+        sage: all( C.idempotent(i)*e[i,k,z] == e[i,k,z]/2
+        ....:      for (i,k,z) in e.keys()
+        ....:      if k < i)
+        True
+        sage: all( (C.idempotent(i)*e[j,k,l]).is_zero()
+        ....:      for i in range(r)
+        ....:      for (j,k,l) in e.keys()
+        ....:      if k <= j and i not in [j,k] )
+        True
+        sage: all( (e[i,k,l]*C.idempotent(i)).is_zero()
+        ....:      for (i,k,l) in e.keys()
+        ....:      if k < i )
+        True
+        sage: all( (e[j,k,l]*C.idempotent(i)).is_zero()
+        ....:      for i in range(r)
+        ....:      for (j,k,l) in e.keys()
+        ....:      if i not in [j,k] )
+        True
+
+    With a little effort, we can lift elements of the clan back into
+    the original (asymmetric) matrix space::
+
+        sage: C = ComplexHermitianClan(3)
+        sage: x = C.an_element(); x
+        2*B(0, 0, 1) + 3*B(1, 0, I) + 2*B(1, 0, 1)
+        sage: x.lift().lift()
+        ┌─────────┬──────────┬───┐
+        │ 2       │ -3*I + 2 │ 0 │
+        ├─────────┼──────────┼───┤
+        │ 3*I + 2 │ 0        │ 0 │
+        ├─────────┼──────────┼───┤
+        │ 0       │ 0        │ 0 │
+        └─────────┴──────────┴───┘
+
+    """
+    def __init__(self, n, scalar_field=QQ, **kwargs):
+        from mjo.hurwitz import ComplexMatrixAlgebra
+        from sage.rings.qqbar import QQbar
+        Mn = ComplexMatrixAlgebra(n, QQbar, scalar_field)
+        b = Mn.basis()
+
+        from sage.sets.family import Family
+        Hn_basis = Family({
+          (i,j,k) : z
+          for (i,j,k) in b.keys()
+          if j <= i
+          if (a := 1 - (i == j)/scalar_field(2))
+          if (z := a*(b[i,j,k] + b[i,j,k].conjugate_transpose()))
+        })
+
+        # Mn.submodule() destroys our basis keys, so use
+        # SubmoduleWithBasis directly.
+        from sage.modules.with_basis.subquotient import SubmoduleWithBasis
+        Hn = SubmoduleWithBasis(Hn_basis,
+                                support_order=b.keys(),
+                                ambient=Mn)
+
+        super().__init__(Hn, TAlgebraClan._cp, TAlgebraClan._ip, **kwargs)
+
+
+    def __repr__(self) -> str:
+        r"""
+        The string representation of this clan.
+
+        SETUP::
+
+            sage: from mjo.clan.t_algebra_clan import ComplexHermitianClan
+
+        EXAMPLES::
+
+            sage: ComplexHermitianClan(1)
+            Clan H^1 over Rational Field
+
+        """
+        return f"Clan H^{self.rank()} over {self.base_ring()}"
index 9f6051b42d446c9517a38e3baa8c37e81a95412d..52d0d99dc638accb2b351ee91b595a3c88758966 100644 (file)
@@ -1,5 +1,5 @@
 from sage.rings.rational_field import QQ
-from mjo.clan.clan import MatrixClan, NormalDecomposition
+from mjo.clan.normal_decomposition import NormalDecomposition
 
 class VinbergClan(NormalDecomposition):
     r"""
@@ -186,6 +186,9 @@ class VinbergClan(NormalDecomposition):
         # clan product rather than duplicating them locally.
         self._vector_space = R5
 
+        # Let's borrow these up_hat() and down_hat() implementations
+        from mjo.clan.t_algebra_clan import TAlgebraClan
+
         def cp(x,y):
             # up_hat and down_hat need MatrixSpace elements, not
             # submodules with custom bases, so we need to lift
@@ -196,8 +199,8 @@ class VinbergClan(NormalDecomposition):
             X2 = X2.lift()
             Y1 = Y1.lift()
             Y2 = Y2.lift()
-            Z1 = MatrixClan._down_hat(X1)*Y1 + Y1*MatrixClan._up_hat(X1)
-            Z2 = MatrixClan._down_hat(X2)*Y2 + Y2*MatrixClan._up_hat(X2)
+            Z1 = TAlgebraClan._down_hat(X1)*Y1 + Y1*TAlgebraClan._up_hat(X1)
+            Z2 = TAlgebraClan._down_hat(X2)*Y2 + Y2*TAlgebraClan._up_hat(X2)
             return self._unlift((self._S2(Z1), self._S2(Z2)))
 
         def ip(x,y):