Skip to content

Commit

Permalink
sagemathgh-38975: support left and right actions for species
Browse files Browse the repository at this point in the history
    
As in other modules, we allow the user to specify whether the group
action is a left or a right action. In line with permutation groups, and
also gap, the default side for an action is from the right.
    
URL: sagemath#38975
Reported by: Martin Rubey
Reviewer(s): Travis Scrimshaw
  • Loading branch information
Release Manager committed Dec 5, 2024
2 parents d876e10 + 8033c9c commit 8f68852
Showing 1 changed file with 84 additions and 30 deletions.
114 changes: 84 additions & 30 deletions src/sage/rings/species.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"""

from itertools import accumulate, chain
from itertools import accumulate, chain, product

from sage.categories.graded_algebras_with_basis import GradedAlgebrasWithBasis
from sage.categories.modules import Modules
Expand Down Expand Up @@ -730,34 +730,35 @@ def _an_element_(self):
Element = AtomicSpeciesElement


def _stabilizer_subgroups(G, X, a):
def _stabilizer_subgroups(G, X, a, side='right', check=True):
r"""
Return subgroups conjugate to the stabilizer subgroups of the
given (left) group action.
given group action.
INPUT:
- ``G`` -- the acting group
- ``X`` -- the set ``G`` is acting on
- ``a`` -- the (left) action
- ``a`` -- the action, a function `G\times X\to X` if ``side`` is
``'left'``, `X\times G\to X` if ``side`` is ``'right'``
EXAMPLES::
sage: from sage.rings.species import _stabilizer_subgroups
sage: S = SymmetricGroup(4)
sage: X = SetPartitions(S.degree(), [2,2])
sage: a = lambda g, x: SetPartition([[g(e) for e in b] for b in x])
sage: _stabilizer_subgroups(S, X, a)
sage: _stabilizer_subgroups(S, X, a, side='left')
[Permutation Group with generators [(1,2), (1,3)(2,4)]]
sage: S = SymmetricGroup(8)
sage: X = SetPartitions(S.degree(), [3,3,2])
sage: _stabilizer_subgroups(S, X, a)
sage: _stabilizer_subgroups(S, X, a, side='left', check=False)
[Permutation Group with generators [(7,8), (6,7), (4,5), (1,3)(2,6)(4,7)(5,8), (1,3)]]
sage: S = SymmetricGroup(4)
sage: X = SetPartitions(S.degree(), 2)
sage: _stabilizer_subgroups(S, X, a)
sage: _stabilizer_subgroups(S, X, a, side='left')
[Permutation Group with generators [(1,4), (1,3,4)],
Permutation Group with generators [(1,3)(2,4), (1,4)]]
Expand All @@ -766,19 +767,54 @@ def _stabilizer_subgroups(G, X, a):
sage: S = SymmetricGroup([1,2,4,5,3,6]).young_subgroup([4, 2])
sage: X = [pi for pi in SetPartitions(6, [3,3]) if all(sum(1 for e in b if e % 3) == 2 for b in pi)]
sage: _stabilizer_subgroups(S, X, a)
sage: _stabilizer_subgroups(S, X, a, side='left')
[Permutation Group with generators [(1,2), (4,5), (1,4)(2,5)(3,6)]]
TESTS::
sage: _stabilizer_subgroups(SymmetricGroup(2), [1], lambda pi, H: H)
sage: _stabilizer_subgroups(SymmetricGroup(2), [1], lambda pi, H: H, side='left')
[Permutation Group with generators [(1,2)]]
sage: _stabilizer_subgroups(SymmetricGroup(2), [1, 1], lambda pi, H: H, side='left')
Traceback (most recent call last):
...
ValueError: The argument X must be a set, but [1, 1] contains duplicates
sage: G = SymmetricGroup(3)
sage: X = [1, 2, 3]
sage: _stabilizer_subgroups(G, X, lambda pi, x: pi(x), side='left')
[Permutation Group with generators [(2,3)]]
sage: _stabilizer_subgroups(G, X, lambda x, pi: pi(x), side='right')
Traceback (most recent call last):
...
ValueError: The given function is not a right group action: g=(1,3,2), h=(2,3), x=1 do not satisfy the condition
"""
to_gap = {x: i for i, x in enumerate(X, 1)}

X = set(X) # because orbit_decomposition turns X into a set
g_orbits = [orbit_decomposition(X, lambda x: a(g, x))
for g in G.gens()]
X_set = set(X) # because orbit_decomposition turns X into a set

if len(X_set) != len(X):
raise ValueError(f"The argument X must be a set, but {X} contains duplicates")

if side == "left":
if check:
for g, h, x in product(G, G, X):
# Warning: the product in permutation groups is left-to-right composition
if not a(h * g, x) == a(g, a(h, x)):
raise ValueError(f"The given function is not a left group action: g={g}, h={h}, x={x} do not satisfy the condition")

g_orbits = [orbit_decomposition(X_set, lambda x: a(g, x))
for g in G.gens()]
elif side == "right":
if check:
for g, h, x in product(G, G, X):
if not a(x, h * g) == a(a(x, g), h):
raise ValueError(f"The given function is not a right group action: g={g}, h={h}, x={x} do not satisfy the condition")

g_orbits = [orbit_decomposition(X_set, lambda x: a(x, g))
for g in G.gens()]
else:
raise ValueError(f"The argument side must be 'left' or 'right' but is {side}")

gens = [PermutationGroupElement([tuple([to_gap[x] for x in o])
for o in g_orbit])
Expand Down Expand Up @@ -873,13 +909,15 @@ def _element_constructor_(self, G, pi=None, check=True):
INPUT:
- ``G`` -- element of ``self`` (in this case ``pi`` must be
``None``) permutation group, or pair ``(X, a)`` consisting
of a finite set and a transitive action
``None``) or permutation group, or triple ``(X, a, side)``
consisting of a finite set, a transitive action and
a string 'left' or 'right'; the side can be omitted, it is
then assumed to be 'right'
- ``pi`` -- ``dict`` mapping sorts to iterables whose union
is the domain of ``G`` (if ``G`` is a permutation group) or
the domain of the acting symmetric group (if ``G`` is a
pair ``(X, a)``); if `k=1` and ``G`` is a permutation
group, ``pi`` can be omitted
triple ``(X, a, side)``); if `k=1` and ``G`` is a
permutation group, ``pi`` can be omitted
- ``check`` -- boolean (default: ``True``); skip input
checking if ``False``
Expand All @@ -901,11 +939,11 @@ def _element_constructor_(self, G, pi=None, check=True):
sage: M = MolecularSpecies("X")
sage: a = lambda g, x: SetPartition([[g(e) for e in b] for b in x])
sage: X = SetPartitions(4, [2, 2])
sage: M((X, a), {0: X.base_set()})
sage: M((X, a, 'left'), {0: X.base_set()})
P_4
sage: X = SetPartitions(8, [4, 2, 2])
sage: M((X, a), {0: X.base_set()})
sage: M((X, a, 'left'), {0: X.base_set()}, check=False)
E_4*P_4
TESTS::
Expand All @@ -922,7 +960,7 @@ def _element_constructor_(self, G, pi=None, check=True):
sage: X = SetPartitions(4, 2)
sage: a = lambda g, x: SetPartition([[g(e) for e in b] for b in x])
sage: M((X, a), {0: [1,2], 1: [3,4]})
sage: M((X, a, 'left'), {0: [1,2], 1: [3,4]})
Traceback (most recent call last):
...
ValueError: action is not transitive
Expand Down Expand Up @@ -962,18 +1000,25 @@ def _element_constructor_(self, G, pi=None, check=True):
...
ValueError: 0 must be a permutation group or a pair (X, a)
specifying a group action of the symmetric group on pi=None
"""
if parent(G) is self:
# pi cannot be None because of framework
raise ValueError("cannot reassign sorts to a molecular species")

if isinstance(G, tuple):
X, a = G
if len(G) == 2:
X, a = G
side = 'right'
else:
X, a, side = G
if side not in ['left', 'right']:
raise ValueError(f"the side must be 'right' or 'left', but is {side}")
dompart = [sorted(pi.get(s, [])) for s in range(self._arity)]
S = PermutationGroup([tuple(b) for b in dompart if len(b) > 2]
+ [(b[0], b[1]) for b in dompart if len(b) > 1],
domain=list(chain(*dompart)))
H = _stabilizer_subgroups(S, X, a)
H = _stabilizer_subgroups(S, X, a, side=side, check=check)
if len(H) > 1:
raise ValueError("action is not transitive")
G = H[0]
Expand Down Expand Up @@ -2121,8 +2166,10 @@ def _element_constructor_(self, G, pi=None, check=True):
INPUT:
- ``G`` -- element of ``self`` (in this case ``pi`` must be
``None``), permutation group, or pair ``(X, a)`` consisting
of a finite set and an action
``None``) or permutation group, or triple ``(X, a, side)``
consisting of a finite set, an action and a string 'left'
or 'right'; the side can be omitted, it is then assumed to
be 'right'
- ``pi`` -- ``dict`` mapping sorts to iterables whose union
is the domain of ``G`` (if ``G`` is a permutation group) or
the domain of the acting symmetric group (if ``G`` is a
Expand All @@ -2140,13 +2187,13 @@ def _element_constructor_(self, G, pi=None, check=True):
sage: X = SetPartitions(4, 2)
sage: a = lambda g, x: SetPartition([[g(e) for e in b] for b in x])
sage: P((X, a), {0: [1,2], 1: [3,4]})
sage: P((X, a, 'left'), {0: [1,2], 1: [3,4]})
E_2(X)*E_2(Y) + X^2*E_2(Y) + E_2(XY) + Y^2*E_2(X)
sage: P = PolynomialSpecies(ZZ, ["X"])
sage: X = SetPartitions(4, 2)
sage: a = lambda g, x: SetPartition([[g(e) for e in b] for b in x])
sage: P((X, a), {0: [1,2,3,4]})
sage: P((X, a, 'left'), {0: [1,2,3,4]})
X*E_3 + P_4
The species of permutation groups::
Expand All @@ -2155,10 +2202,10 @@ def _element_constructor_(self, G, pi=None, check=True):
sage: n = 4
sage: S = SymmetricGroup(n)
sage: X = S.subgroups()
sage: def act(pi, G): # WARNING: returning H does not work because of equality problems
sage: def act(G, pi): # WARNING: returning H does not work because of equality problems
....: H = S.subgroup(G.conjugate(pi).gens())
....: return next(K for K in X if K == H)
sage: P((X, act), {0: range(1, n+1)})
sage: P((X, act), {0: range(1, n+1)}, check=False)
4*E_4 + 4*P_4 + E_2^2 + 2*X*E_3
Create a multisort species given an action::
Expand All @@ -2176,7 +2223,7 @@ def _element_constructor_(self, G, pi=None, check=True):
....: if r == t and c == b:
....: return r, c
....: raise ValueError
sage: P((X, lambda g, x: act(x[0], x[1], g)), pi)
sage: P((X, lambda g, x: act(x[0], x[1], g), 'left'), pi)
2*Y*E_2(X)
TESTS::
Expand Down Expand Up @@ -2206,18 +2253,25 @@ def _element_constructor_(self, G, pi=None, check=True):
ValueError: 1/2 must be an element of the base ring, a permutation
group or a pair (X, a) specifying a group action of the symmetric
group on pi=None
"""
if parent(G) is self:
# pi cannot be None because of framework
raise ValueError("cannot reassign sorts to a polynomial species")

if isinstance(G, tuple):
X, a = G
if len(G) == 2:
X, a = G
side = 'right'
else:
X, a, side = G
if side not in ['left', 'right']:
raise ValueError(f"the side must be 'right' or 'left', but is {side}")
dompart = [sorted(pi.get(s, [])) for s in range(self._arity)]
S = PermutationGroup([tuple(b) for b in dompart if len(b) > 2]
+ [(b[0], b[1]) for b in dompart if len(b) > 1],
domain=list(chain(*dompart)))
Hs = _stabilizer_subgroups(S, X, a)
Hs = _stabilizer_subgroups(S, X, a, side=side, check=check)
return self.sum_of_terms((self._indices(H, pi, check=check), ZZ.one())
for H in Hs)

Expand Down

0 comments on commit 8f68852

Please sign in to comment.