From b63622ed2c158997e0b0a05a9ffb69717dfaf0f1 Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Sat, 14 Feb 2026 21:42:04 -0500 Subject: [PATCH] mjo/clan/unital_clan.py: VinbergClan inits and passes tests --- mjo/clan/unital_clan.py | 82 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 77 insertions(+), 5 deletions(-) diff --git a/mjo/clan/unital_clan.py b/mjo/clan/unital_clan.py index 0e497bb..63fb737 100644 --- a/mjo/clan/unital_clan.py +++ b/mjo/clan/unital_clan.py @@ -534,6 +534,7 @@ class HnClan(MatrixClan): """ return f"Clan H^{self.rank()} over {self.base_ring()}" + class VinbergClan(NormalDecomposition): r""" The clan corresponding to the Vinberg cone (as defined by Ishi). @@ -556,6 +557,67 @@ class VinbergClan(NormalDecomposition): real numbers, and provide lifts to the matrix representation. The clan product is in terms of the up-hat and down-hat that is defined on `S^{2}. + + Following Gikdikin (p. 91), the basis for this clan is + + e[(0,0,1)] = [1,0], [1,0] + [0,0] [0,0] + + e[(1,0,1)] = [0,1], [0,0] + [1,0] [0,0] + + e[(1,1,1)] = [0,0], [0,0] + [0,1] [0,0] + + e[(2,0,1)] = [0,0], [0,1] + [0,0] [1,0] + + e[(2,1,1)] = nonexistent, component is trivial + + e[(2,2,1)] = [0,0], [0,0] + [0,0] [0,1] + + Check the unit element:: + + sage: C = VinbergClan() + sage: x = C.an_element() + sage: x*C.one() == x + True + sage: C.one()*x == x + True + + Verifying the axioms:: + + sage: C = VinbergClan() + sage: e = C.basis() + sage: all( e[i,i,1]*e[i,i,1] == e[i,i,1] for i in range(3) ) + True + sage: all( e[i,i,1]*e[j,i,1] == e[j,i,1]/2 + ....: for i in range(3) + ....: for j in range(i+1,3) + ....: if e.has_key((j,i,1))) + True + sage: all( e[i,i,1]*e[i,k,1] == e[i,k,1]/2 + ....: for i in range(2) + ....: for k in range(i)) + True + sage: all( (e[i,i,1]*e[j,k,1]).is_zero() + ....: for i in range(2) + ....: for j in range(2) + ....: 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(2) + ....: for k in range(i) ) + True + sage: all( (e[j,k,1]*e[i,i,1]).is_zero() + ....: for i in range(2) + ....: for j in range(2) + ....: for k in range(j) + ....: if not i in [j,k] ) + True + """ def __init__(self, scalar_field=QQ, **kwargs): from sage.matrix.matrix_space import MatrixSpace @@ -579,12 +641,15 @@ class VinbergClan(NormalDecomposition): ambient=M2) # We need an Ishi basis (i,j,k) for R^5 if we want to use - # NormalDecomposition. + # NormalDecomposition. We imagine the (2,1,1) component + # being trivial to keep the "triangle" intact. We are + # example (f) on page 91 of Gindikin where we can cheat + # and see the normal decomposition. R5 = VectorSpace(scalar_field, [ (0,0,1), (1,0,1), (1,1,1), - (2,1,1), + (2,0,1), (2,2,1) ]) @@ -593,7 +658,7 @@ class VinbergClan(NormalDecomposition): return ( A[(0,0,1)]*R5((0,0,1)) + A[(1,0,1)]*R5((1,0,1)) + A[(1,1,1)]*R5((1,1,1)) + - B[(1,0,1)]*R5((2,1,1)) + + B[(1,0,1)]*R5((2,0,1)) + B[(1,1,1)]*R5((2,2,1)) ) def lift(v): @@ -601,16 +666,23 @@ class VinbergClan(NormalDecomposition): self._S2((1,0,1))*v[(1,0,1)] + self._S2((1,1,1))*v[(1,1,1)] ) M2 = ( self._S2((0,0,1))*v[(0,0,1)] + - self._S2((1,0,1))*v[(2,1,1)] + + self._S2((1,0,1))*v[(2,0,1)] + self._S2((1,1,1))*v[(2,2,1)] ) return (M1,M2) def cp(x,y): + # up_hat and down_hat need MatrixSpace elements, not + # submodules with custom bases, so we need to lift + # everything twice. X1,X2 = lift(x) Y1,Y2 = lift(y) + X1 = X1.lift() + 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) - return unlift(Z1,Z2) + return unlift((self._S2(Z1), self._S2(Z2))) def ip(x,y): two = x.base_ring()(2) -- 2.51.0