Skip to content

Commit

Permalink
add class for restriction of species
Browse files Browse the repository at this point in the history
  • Loading branch information
mantepse committed Feb 7, 2025
1 parent 8c3a64e commit 3797aed
Show file tree
Hide file tree
Showing 2 changed files with 123 additions and 38 deletions.
128 changes: 91 additions & 37 deletions src/sage/rings/lazy_species.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,19 @@
sage: L.<X> = LazySpecies(QQ)
sage: E = L.Sets()
sage: E_2 = L(SymmetricGroup(2))
sage: Ep = L.Sets(1)
sage: Ep = L.Sets().restrict(1)
sage: G = L.Graphs()
sage: E_2(X^2)[4].support()[0].support()[0].rename("E_2(X^2)")
The molecular decomposition begins with::
sage: P = G(Ep.revert())
sage: P.truncate(6)
1 + X + E_2 + (E_3+X*E_2) + (E_4+X*E_3+P_4+X^2*E_2+E_2(X^2))
+ (E_5+E_2*E_3+X*E_4+X*E_2^2+X^2*E_3+2*X*P_4+P_5+5*X*E_2(X^2)+3*X^3*E_2)
1 + X + E_2 + (E_3+X*E_2) + (E_4+X*E_3+E_2(E_2)+X^2*E_2+E_2(X^2))
+ (E_5+E_2*E_3+X*E_4+X*E_2^2+X^2*E_3+2*X*E_2(E_2)+P_5+5*X*E_2(X^2)+3*X^3*E_2)
Note that [GL2011]_ write `E_2(E_2)` instead of `P_4` and `D_5`
instead of `P_5`, and there is apparently a misprint: `X*P_4 + 4
X^3 E_2` should be `2 X P_4 + 3 X^3 E_2`.
Note that [GL2011]_ write `D_5` instead of `P_5`, and there is
apparently a misprint: `X*E_2(E_2) + 4 X^3 E_2` should be `2 X
E_2(E_2) + 3 X^3 E_2`.
To compute the molecular decomposition of the species of
connected graphs with no endpoints, we use Equation (3.3) in
Expand All @@ -48,7 +47,7 @@
sage: Gc = Ep.revert()(G-1)
sage: Mc = Gc(X*E(-X)) + E_2(-X)
sage: E(Mc).truncate(5)
1 + X + E_2 + 2*E_3 + (2*E_4+P_4+E_2^2+X*E_3)
1 + X + E_2 + 2*E_3 + (2*E_4+E_2(E_2)+E_2^2+X*E_3)
Note that [GL2011]_ apparently contains a misprint: `2 X E_3`
should be `X E_3 + E_2^2`. Indeed, the graphs on four vertices
Expand All @@ -64,6 +63,7 @@
sage: B = G(2*Ep.revert() - X)
sage: B.truncate(6)
1 + X + E_2(X^2) + (P_5+5*X*E_2(X^2))
"""

from sage.misc.lazy_list import lazy_list
Expand All @@ -75,6 +75,7 @@
from sage.rings.species import PolynomialSpecies, _label_sets
from sage.data_structures.stream import (Stream_zero,
Stream_exact,
Stream_truncated,
Stream_function)
from sage.categories.sets_cat import cartesian_product
from sage.categories.tensor import tensor
Expand Down Expand Up @@ -149,6 +150,7 @@ def weighted_compositions(n, d, weight_multiplicities, _w0=0):
for v in map(list, IntegerVectors(s, length=m)):
yield v + c


def weighted_vector_compositions(n_vec, d, weight_multiplicities_vec):
r"""
Return all compositions of the vector `n` of weight `d`.
Expand Down Expand Up @@ -347,6 +349,29 @@ def coefficient(n):

return L(coefficient)

def restrict(self, min_degree=None, max_degree=None):
r"""
Return the series obtained by keeping only terms of
degree between ``min_degree`` and ``max_degree``.
INPUT:
- ``min_degree``, ``max_degree`` -- (optional) integers
indicating which degrees to keep
EXAMPLES::
sage: from sage.rings.lazy_species import LazySpecies
sage: L = LazySpecies(ZZ, "X")
sage: G = L.Graphs()
sage: list(G.isotypes(2))
[Graph on 2 vertices, Graph on 2 vertices]
sage: list(G.restrict(2, 2).isotypes(2))
[Graph on 2 vertices, Graph on 2 vertices]
"""
return RestrictedSpeciesElement(self, min_degree, max_degree)

def _add_(self, other):
r"""
Return the sum of ``self`` and ``other``.
Expand Down Expand Up @@ -446,17 +471,7 @@ def structures(self, *labels):
sage: list(XY.structures([], [1, 2]))
[]
"""
labels = _label_sets(self.parent()._arity, labels)
F = self[sum(map(len, labels))]
for M, c in F.monomial_coefficients().items():
if c not in ZZ or c < 0:
raise NotImplementedError("only implemented for proper non-virtual species")
if c == 1:
for s in M.structures(*labels):
yield M, s
else:
for e, s in cartesian_product([range(c), M.structures(*labels)]):
yield M, s, e
yield from self[sum(map(len, labels))].structures(*labels)

def isotypes(self, labels):
pass
Expand Down Expand Up @@ -512,7 +527,7 @@ def __call__(self, *args):
sage: L = LazySpecies(QQ, "X")
sage: E2 = L(SymmetricGroup(2))
sage: E2(E2)
P_4 + O^11
E_2(E_2) + O^11
sage: from sage.rings.species import PolynomialSpecies
sage: P = PolynomialSpecies(QQ, "X")
Expand Down Expand Up @@ -542,7 +557,7 @@ def __call__(self, *args):
sage: E = L(lambda n: SymmetricGroup(n))
sage: E1 = L(lambda n: SymmetricGroup(n) if n else 0)
sage: E(q*E1)[4]
(q^4+q)*E_4 + q^2*P_4 + q^2*X*E_3 + q^3*E_2^2
(q^4+q)*E_4 + q^2*E_2(E_2) + q^2*X*E_3 + q^3*E_2^2
TESTS::
Expand All @@ -551,7 +566,7 @@ def __call__(self, *args):
sage: X(X + E2)
X + E_2 + O^8
sage: E2(X + E2)
E_2 + X*E_2 + P_4 + O^9
E_2 + X*E_2 + E_2(E_2) + O^9
sage: (1+E2)(X)
1 + E_2 + O^7
Expand All @@ -570,7 +585,7 @@ def __call__(self, *args):
poses theoretical problems::
sage: L.<X> = LazySpecies(QQ)
sage: E1 = L.Sets(min=1)
sage: E1 = L.Sets().restrict(1)
sage: Omega = L.undefined(1)
sage: L.define_implicitly([Omega], [E1(Omega) - X])

Check failure on line 590 in src/sage/rings/lazy_species.py

View workflow job for this annotation

GitHub Actions / test-new

Failed example:

Failed example:: Exception raised: Traceback (most recent call last): File "sage/structure/category_object.pyx", line 857, in sage.structure.category_object.CategoryObject.getattr_from_category return self._cached_methods[name] KeyError: 'define_implicitly' During handling of the above exception, another exception occurred: Traceback (most recent call last): File "/sage/src/sage/doctest/forker.py", line 728, in _run self.compile_and_execute(example, compiler, test.globs) File "/sage/src/sage/doctest/forker.py", line 1152, in compile_and_execute exec(compiled, globs) File "<doctest sage.rings.lazy_species.LazySpeciesElement.__call__[37]>", line 1, in <module> L.define_implicitly([Omega], [E1(Omega) - X]) File "sage/structure/category_object.pyx", line 851, in sage.structure.category_object.CategoryObject.__getattr__ return self.getattr_from_category(name) File "sage/structure/category_object.pyx", line 866, in sage.structure.category_object.CategoryObject.getattr_from_category attr = getattr_from_other_class(self, cls, name) File "sage/cpython/getattr.pyx", line 358, in sage.cpython.getattr.getattr_from_other_class raise AttributeError(dummy_error_message) AttributeError: 'LazySpecies_with_category' object has no attribute 'define_implicitly'
sage: Omega[1] # not tested
Expand All @@ -593,6 +608,12 @@ def __call__(self, *args):
for g in args):
return P(self[0])

# f is a constant polynomial
if (isinstance(self._coeff_stream, Stream_exact)
and not self._coeff_stream._constant
and self.polynomial().is_constant()):
return P(self.polynomial())

return CompositionSpeciesElement(self, *args)

def revert(self):
Expand All @@ -603,10 +624,10 @@ def revert(self):
sage: from sage.rings.lazy_species import LazySpecies
sage: L.<X> = LazySpecies(QQ)
sage: E1 = L.Sets(1)
sage: E1 = L.Sets().restrict(1)
sage: g = E1.revert()
sage: g[:5]
[X, -E_2, -E_3 + X*E_2, -E_4 + P_4 + X*E_3 - X^2*E_2]
[X, -E_2, -E_3 + X*E_2, -E_4 + E_2(E_2) + X*E_3 - X^2*E_2]
sage: E = L.Sets()
sage: P = E(X*E1(-X))*(1+X) - 1
Expand Down Expand Up @@ -727,12 +748,6 @@ def __init__(self, left, *args):
cm = get_coercion_model()
P = cm.common_parent(left.base_ring(), *[parent(g) for g in args])

# f is a constant polynomial
if (isinstance(left._coeff_stream, Stream_exact)
and not left._coeff_stream._constant
and left.polynomial().is_constant()):
return P(left.polynomial())

args = [P(g) for g in args]

for g in args:
Expand All @@ -755,9 +770,11 @@ def coeff(g, i):
# args_flat and weights contain one list for each g
weight_exp = [lazy_list(lambda j, g=g: len(coeff(g, j+1)))
for g in args]
# work around python's scoping rules

def flat(g):
# function needed to work around python's scoping rules
return itertools.chain.from_iterable((coeff(g, j) for j in itertools.count()))

args_flat1 = [lazy_list(flat(g)) for g in args]

def coefficient(n):
Expand Down Expand Up @@ -802,7 +819,7 @@ def structures(self, *labels):
sage: from sage.rings.lazy_species import LazySpecies
sage: L = LazySpecies(QQ, "X")
sage: E = L.Sets()
sage: E1 = L.Sets(min=1)
sage: E1 = L.Sets().restrict(1)
sage: sorted(E(E1).structures([1,2,3]))
[((((1, 'X'),), ((2, 'X'),), ((3, 'X'),)), ((1,), (2,), (3,))),
((((1, 'X'),), ((2, 'X'), (3, 'X'))), ((1,), (2, 3))),
Expand Down Expand Up @@ -924,13 +941,13 @@ def __init__(self, base_ring, names, sparse):
if self._arity == 1:
self.Graphs = lambda: GraphSpecies(self)
self.SetPartitions = lambda: SetPartitionSpecies(self)
self.Sets = lambda min=0: SetSpecies(self, min)
self.Sets = lambda: SetSpecies(self)
self.Cycles = lambda: CycleSpecies(self)


class SetSpecies(LazySpeciesElement):
def __init__(self, parent, min):
S = parent(lambda n: SymmetricGroup(n) if n >= min else 0)
def __init__(self, parent):
S = parent(lambda n: SymmetricGroup(n))
super().__init__(parent, S._coeff_stream)

def structures(self, *labels):
Expand Down Expand Up @@ -988,7 +1005,7 @@ def isotypes(self, labels):
class SetPartitionSpecies(LazySpeciesElement):
def __init__(self, parent):
E = parent.Sets()
E1 = parent.Sets(min=1)
E1 = parent.Sets().restrict(1)
super().__init__(parent, E(E1)._coeff_stream)

def isotypes(self, labels):
Expand All @@ -998,3 +1015,40 @@ def isotypes(self, labels):
def structures(self, labels):
labels = _label_sets(self.parent()._arity, labels)
yield from SetPartitions(labels)


class RestrictedSpeciesElement(LazySpeciesElement):
def __init__(self, F, min_degree, max_degree):
self._F = F
self._min = min_degree
self._max = max_degree

if max_degree is None and min_degree is None:
coeff_stream = F._coeff_stream
elif max_degree is None:
v = max(F._coeff_stream._approximate_order, min_degree)
coeff_stream = Stream_truncated(F._coeff_stream, 0, v)
else:
if min_degree is None:
v = F._coeff_stream._approximate_order
else:
v = max(F._coeff_stream._approximate_order, min_degree)
initial_coefficients = [F._coeff_stream[i] for i in range(v, max_degree + 1)]
if not any(initial_coefficients):
coeff_stream = Stream_zero()
else:
coeff_stream = Stream_exact(initial_coefficients, order=v)

super().__init__(F.parent(), coeff_stream)

def isotypes(self, labels):
if (labels in ZZ
and (self._min is None or self._min <= labels)
and (self._max is None or labels <= self._max)):
yield from self._F.isotypes(labels)

def structures(self, *labels):
n = sum(map(len, labels))
if ((self._min is None or self._min <= n)
and (self._max is None or n <= self._max)):
yield from self._F.structures(*labels)
33 changes: 32 additions & 1 deletion src/sage/rings/species.py
Original file line number Diff line number Diff line change
Expand Up @@ -1606,7 +1606,7 @@ def structures(self, *labels):
sage: G = PermutationGroup([[(2,3),(4,5)]])
sage: a = M(G, {0: [1, 2, 3], 1: [4, 5]})
sage: a
X*E_2(XY)
X*E_2(X*Y)
sage: list(a.structures([1, 2, 3], ["a", "b"]))
[((1,), (2, 3, 'a', 'b')),
((1,), (2, 3, 'b', 'a')),
Expand Down Expand Up @@ -2196,6 +2196,37 @@ def _from_etuple(e):
for factor, exponent in factors]
return Factorization(factors, unit=unit, sort=False)

def structures(self, *labels):
r"""
Iterate over the structures on the given set of labels.
EXAMPLES::
sage: from sage.rings.species import PolynomialSpecies
sage: P = PolynomialSpecies(ZZ, ["X"])
sage: C3 = P(CyclicPermutationGroup(3))
sage: X = P(SymmetricGroup(1))
sage: E2 = P(SymmetricGroup(2))
sage: f = 2*E2*X + E2^2
sage: list(f.structures([1, 2, 3]))
[(X*E_2, ((1, 3), (2,)), 0),
(X*E_2, ((2, 3), (1,)), 0),
(X*E_2, ((1, 2), (3,)), 0),
(X*E_2, ((1, 3), (2,)), 1),
(X*E_2, ((2, 3), (1,)), 1),
(X*E_2, ((1, 2), (3,)), 1)]
"""
labels = _label_sets(self.parent()._arity, labels)
for M, c in self.monomial_coefficients().items():
if c not in ZZ or c < 0:
raise NotImplementedError("only implemented for proper non-virtual species")
if c == 1:
for s in M.structures(*labels):
yield M, s
else:
for e, s in cartesian_product([range(c), M.structures(*labels)]):
yield M, s, e


class PolynomialSpecies(CombinatorialFreeModule):
r"""
Expand Down

0 comments on commit 3797aed

Please sign in to comment.