From 39820e93df1a46753245c9a1554a926efef27761 Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Fri, 16 Jan 2026 08:18:39 -0500 Subject: [PATCH] mjo/clan: factor out NormalDecomposition to its own class Some methods require knowledge of the normal decomposition coordinates. --- mjo/clan/clan_element.py | 34 ++++++++++++++++++++- mjo/clan/unital_clan.py | 64 ++++++++++++++++++++++++++++++---------- 2 files changed, 82 insertions(+), 16 deletions(-) diff --git a/mjo/clan/clan_element.py b/mjo/clan/clan_element.py index 2b578a1..8c9459d 100644 --- a/mjo/clan/clan_element.py +++ b/mjo/clan/clan_element.py @@ -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) ) diff --git a/mjo/clan/unital_clan.py b/mjo/clan/unital_clan.py index e576d52..90a4bdb 100644 --- a/mjo/clan/unital_clan.py +++ b/mjo/clan/unital_clan.py @@ -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. -- 2.51.0