Skip to content

Commit 8ca3624

Browse files
committed
polish functorial composition
1 parent 760372a commit 8ca3624

File tree

1 file changed

+71
-32
lines changed

1 file changed

+71
-32
lines changed

src/sage/rings/lazy_series.py

Lines changed: 71 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4375,7 +4375,7 @@ def __call__(self, *g, check=True):
43754375
sage: E1 = S(lambda n: s[n], valuation=1)
43764376
sage: E = 1 + E1
43774377
sage: P = E(E1)
4378-
sage: [s(x) for x in P[:5]]
4378+
sage: P[:5]
43794379
[s[], s[1], 2*s[2], s[2, 1] + 3*s[3], 2*s[2, 2] + 2*s[3, 1] + 5*s[4]]
43804380
43814381
The plethysm with a tensor product is also implemented::
@@ -4496,15 +4496,16 @@ def __call__(self, *g, check=True):
44964496
g._coeff_stream._approximate_order = 1
44974497

44984498
if P._arity == 1:
4499-
ps = P._laurent_poly_ring.realization_of().p()
4499+
ps = R.realization_of().p()
45004500
else:
4501-
ps = tensor([P._laurent_poly_ring._sets[0].realization_of().p()]*P._arity)
4502-
coeff_stream = Stream_plethysm(self._coeff_stream, g._coeff_stream, ps)
4501+
ps = tensor([R._sets[0].realization_of().p()]*P._arity)
4502+
coeff_stream = Stream_plethysm(self._coeff_stream, g._coeff_stream,
4503+
ps, R)
4504+
return P.element_class(P, coeff_stream)
4505+
45034506
else:
45044507
raise NotImplementedError("only implemented for arity 1")
45054508

4506-
return P.element_class(P, coeff_stream)
4507-
45084509
plethysm = __call__
45094510

45104511
def revert(self):
@@ -4675,14 +4676,22 @@ def functorial_composition(self, *args):
46754676
r"""
46764677
Return the functorial composition of ``self`` and ``g``.
46774678
4678-
If `F` and `G` are species, their functorial composition is
4679-
the species `F \Box G` obtained by setting `(F \Box G) [A] =
4680-
F[ G[A] ]`. In other words, an `(F \Box G)`-structure on a
4681-
set `A` of labels is an `F`-structure whose labels are the
4682-
set of all `G`-structures on `A`.
4679+
Let `X` be a finite set of cardinality `m`. For a group
4680+
action of the symmetric group `g: S_n \to S_X` and a
4681+
(possibly virtual) representation of the symmetric group on
4682+
`X`, `f: S_X \to GL(V)`, the functorial composition is the
4683+
(virtual) representation of the symmetric group `f \Box g:
4684+
S_n \to GL(V)` given by `\sigma \mapsto f(g(\sigma))`.
46834685
4684-
It can be shown (as in section 2.2 of [BLL]_) that there is a
4685-
corresponding operation on cycle indices:
4686+
This is more naturally phrased in the language of
4687+
combinatorial species. Let `F` and `G` be species, then
4688+
their functorial composition is the species `F \Box G` with
4689+
`(F \Box G) [A] = F[ G[A] ]`. In other words, an `(F \Box
4690+
G)`-structure on a set `A` of labels is an `F`-structure
4691+
whose labels are the set of all `G`-structures on `A`.
4692+
4693+
The Frobenius character (or cycle index series) of `F \Box G`
4694+
can be computed as follows, see section 2.2 of [BLL]_):
46864695
46874696
.. MATH::
46884697
@@ -4691,8 +4700,6 @@ def functorial_composition(self, *args):
46914700
\operatorname{fix} F[ (G[\sigma])_{1}, (G[\sigma])_{2}, \ldots ]
46924701
\, p_{1}^{\sigma_{1}} p_{2}^{\sigma_{2}} \cdots.
46934702
4694-
This method implements that operation on cycle index series.
4695-
46964703
.. WARNING::
46974704
46984705
The operation `f \Box g` only makes sense when `g`
@@ -4701,9 +4708,10 @@ def functorial_composition(self, *args):
47014708
47024709
EXAMPLES:
47034710
4704-
The species `G` of simple graphs can be expressed in terms of a functorial
4705-
composition: `G = \mathfrak{p} \Box \mathfrak{p}_{2}`, where
4706-
`\mathfrak{p}` is the :class:`~sage.combinat.species.subset_species.SubsetSpecies`.::
4711+
The species `G` of simple graphs can be expressed in terms of
4712+
a functorial composition: `G = \mathfrak{p} \Box
4713+
\mathfrak{p}_{2}`, where `\mathfrak{p}` is the
4714+
:class:`~sage.combinat.species.subset_species.SubsetSpecies`.::
47074715
47084716
sage: R.<q> = QQ[]
47094717
sage: h = SymmetricFunctions(R).h()
@@ -4735,14 +4743,24 @@ def functorial_composition(self, *args):
47354743
47364744
sage: p = SymmetricFunctions(QQ).p()
47374745
sage: h = SymmetricFunctions(QQ).h()
4746+
sage: e = SymmetricFunctions(QQ).e()
47384747
sage: L = LazySymmetricFunctions(h)
47394748
sage: E = L(lambda n: h[n])
47404749
sage: Ep = p[1]*E.derivative_with_respect_to_p1(); Ep
47414750
h[1] + (h[1,1]) + (h[2,1]) + (h[3,1]) + (h[4,1]) + (h[5,1]) + O^7
4742-
sage: f = L(lambda n: randint(3, 6)*h[n])
4751+
sage: f = L(lambda n: h[n-n//2, n//2])
47434752
sage: f - Ep.functorial_composition(f)
47444753
O^7
47454754
4755+
The functorial composition distributes over the sum::
4756+
4757+
sage: F1 = L(lambda n: h[n])
4758+
sage: F2 = L(lambda n: e[n])
4759+
sage: f1 = F1.functorial_composition(f)
4760+
sage: f2 = F2.functorial_composition(f)
4761+
sage: (F1 + F2).functorial_composition(f) - f1 - f2
4762+
O^7
4763+
47464764
TESTS:
47474765
47484766
Check a corner case::
@@ -4752,6 +4770,17 @@ def functorial_composition(self, *args):
47524770
sage: L(h[2,1]).functorial_composition(L([3*h[0]]))
47534771
3*h[] + O^7
47544772
4773+
Check an instance of a non-group action::
4774+
4775+
sage: s = SymmetricFunctions(QQ).s()
4776+
sage: p = SymmetricFunctions(QQ).p()
4777+
sage: L = LazySymmetricFunctions(p)
4778+
sage: f = L(lambda n: s[n])
4779+
sage: g = L(2*s[2, 1, 1] + s[2, 2] + 3*s[4])
4780+
sage: r = f.functorial_composition(g); r[4]
4781+
Traceback (most recent call last):
4782+
...
4783+
ValueError: the argument is not the Frobenius character of a permutation representation
47554784
"""
47564785
if len(args) != self.parent()._arity:
47574786
raise ValueError("arity must be equal to the number of arguments provided")
@@ -4770,39 +4799,49 @@ def functorial_composition(self, *args):
47704799
g = Stream_map_coefficients(g._coeff_stream, p)
47714800
f = Stream_map_coefficients(self._coeff_stream, p)
47724801

4773-
def g_cycle_type(s):
4802+
def g_cycle_type(s, n):
47744803
# the cycle type of G[sigma] of any permutation sigma
4775-
# with cycle type s
4776-
if not s:
4804+
# with cycle type s, which is a partition of n
4805+
if not n:
47774806
if g[0]:
47784807
return Partition([1]*ZZ(g[0].coefficient([])))
47794808
return Partition([])
47804809
res = []
47814810
# in the species case, k is at most
4782-
# factorial(n) * g[n].coefficient([1]*n) with n = sum(s)
4811+
# factorial(n) * g[n].coefficient([1]*n)
47834812
for k in range(1, lcm(s) + 1):
47844813
e = 0
47854814
for d in divisors(k):
47864815
m = moebius(d)
47874816
if not m:
47884817
continue
47894818
u = s.power(k // d)
4790-
g_u = g[sum(u)]
4819+
# it could be, that we never need to compute
4820+
# g[n], so we only do this here
4821+
g_u = g[n]
47914822
if g_u:
47924823
e += m * u.aut() * g_u.coefficient(u)
4793-
res.extend([k] * int(e // k))
4824+
# e / k might not be an integer if g is not a
4825+
# group action, so it is good to check
4826+
res.extend([k] * ZZ(e / k))
47944827
res.reverse()
47954828
return Partition(res)
47964829

47974830
def coefficient(n):
4798-
res = p.zero()
4831+
terms = {}
4832+
t_size = None
47994833
for s in Partitions(n):
4800-
t = g_cycle_type(s)
4801-
f_t = f[t.size()]
4802-
if f_t:
4803-
q = t.aut() * f_t.coefficient(t) / s.aut()
4804-
res += q * p(s)
4805-
return res
4834+
t = g_cycle_type(s, n)
4835+
if t_size is None:
4836+
t_size = sum(t)
4837+
f_t = f[t_size]
4838+
if not f_t:
4839+
break
4840+
elif t_size != sum(t):
4841+
raise ValueError("the argument is not the Frobenius character of a permutation representation")
4842+
4843+
terms[s] = t.aut() * f_t.coefficient(t) / s.aut()
4844+
return R(p.element_class(p, terms))
48064845

48074846
coeff_stream = Stream_function(coefficient, P._sparse, 0)
48084847
return P.element_class(P, coeff_stream)

0 commit comments

Comments
 (0)