From: Michael Orlitzky Date: Sun, 22 Feb 2026 17:32:43 +0000 (-0500) Subject: mjo/clan: more class hierarchy refactoring X-Git-Url: https://gitweb.michael.orlitzky.com/?a=commitdiff_plain;h=3a8ba40e97f1af64badafdd674818ee27975d421;p=sage.d.git mjo/clan: more class hierarchy refactoring 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. --- diff --git a/mjo/clan/all.py b/mjo/clan/all.py index f98232a..bb8b79a 100644 --- a/mjo/clan/all.py +++ b/mjo/clan/all.py @@ -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 diff --git a/mjo/clan/clan.py b/mjo/clan/clan.py index 4c8d241..d543135 100644 --- a/mjo/clan/clan.py +++ b/mjo/clan/clan.py @@ -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()}" diff --git a/mjo/clan/clan_element.py b/mjo/clan/clan_element.py index be89d89..b64211d 100644 --- a/mjo/clan/clan_element.py +++ b/mjo/clan/clan_element.py @@ -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: diff --git a/mjo/clan/clan_operator.py b/mjo/clan/clan_operator.py index 743aa5f..e400edf 100644 --- a/mjo/clan/clan_operator.py +++ b/mjo/clan/clan_operator.py @@ -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 index 0000000..0bb945d --- /dev/null +++ b/mjo/clan/normal_decomposition.py @@ -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 diff --git a/mjo/clan/normal_decomposition_element.py b/mjo/clan/normal_decomposition_element.py index 586c1e5..b577109 100644 --- a/mjo/clan/normal_decomposition_element.py +++ b/mjo/clan/normal_decomposition_element.py @@ -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 index 0000000..7a8ccd3 --- /dev/null +++ b/mjo/clan/t_algebra_clan.py @@ -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()}" diff --git a/mjo/clan/vinberg_clan.py b/mjo/clan/vinberg_clan.py index 9f6051b..52d0d99 100644 --- a/mjo/clan/vinberg_clan.py +++ b/mjo/clan/vinberg_clan.py @@ -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):