Skip to content

Commit

Permalink
Merge branch 'species/left_and_right_actions' into lazy-species
Browse files Browse the repository at this point in the history
  • Loading branch information
mantepse committed Nov 16, 2024
2 parents 3c6159b + cf3cdf3 commit a7ada76
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 @@ -2072,8 +2117,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 @@ -2091,13 +2138,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 @@ -2106,10 +2153,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 @@ -2127,7 +2174,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 @@ -2157,18 +2204,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 a7ada76

Please sign in to comment.