Skip to content

gh-40113: Support implicitly defined species inside compositions#42442

Open
mwhansen wants to merge 2 commits into
sagemath:developfrom
mwhansen:40113-implicit-composition
Open

gh-40113: Support implicitly defined species inside compositions#42442
mwhansen wants to merge 2 commits into
sagemath:developfrom
mwhansen:40113-implicit-composition

Conversation

@mwhansen

@mwhansen mwhansen commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

Description

This makes define_implicitly work when an implicitly defined species occurs inside a composition (plethysm), resolving #40113. The motivating example is the implicit compositional inverse of E_{\geq 1}:

sage: L.<X> = LazyCombinatorialSpecies(QQ)
sage: E1 = L.Sets().restrict(1)
sage: Omega = L.undefined(1)
sage: L.define_implicitly([Omega], [E1(Omega) - X])
sage: Omega[:6] == E1.revert()[:6]
True

The obstruction and its resolution

The difficulty raised in #40113 was computing, for instance, E(f_0 X)_3 with f_0 an undetermined coefficient, since the result depends polynomially rather than linearly on f_0.

The resolution is to recognise which kind of \lambda-ring element an undetermined coefficient is: the f_{n,M} in \Omega_n = \sum_M f_{n,M} M are molecular multiplicities — eventually non-negative integers — hence constants, so the power-sum (Adams) operations fix them, p_k[f_{n,M}] = f_{n,M}. Only the genuine weight variables (your q) are monomials, with p_k[q] = q^k. With this convention,

E(f_0 X)_3 = f_0*E_3 + (f_0^2 - f_0)*X*E_2 + (1/6*f_0^3 - 1/2*f_0^2 + 1/3*f_0)*X^3

which specialises at f_0 = 3 to 3*E_3 + 6*X*E_2 + X^3, as it must.

The polynomial dependence does not break the solver: for an equation F(\Omega) = 0 of positive valuation, \Omega_n enters the degree-n comparison only through the degree-one (identity) part of the outer factors, hence linearly; any product or genuine plethysm of \Omega_n needs total degree > n. So the higher powers f_0^2, f_0^3 only ever multiply already determined lower-degree coefficients, and the existing "keep the linear equations" strategy still determines everything.

Implementation

  • p_k (the stretch in PolynomialSpecies._exponential) now fixes undetermined coefficients and stretches only base-ring monomials, via the new CoefficientRing._power_sum_plethysm.
  • The plethysm in _compose_with_weighted_singletons is computed over the coefficient ring extended by the solver's variables.
  • The solver is taught to see the new variables: the composite coefficient is built over its true parent (so back-substitution recognises it); Stream_function.input_streams now also detects streams captured inside a list/tuple in the closure, so the composition's argument streams (in particular derived ones such as q*\Omega) are substituted into; and the per-call argument snapshots are re-read rather than memoised while still undetermined.

Tested cases

Correctness is checked by cross-validating independent code paths — against revert(), against an explicit define() (which feeds determined coefficients into the composition), self-consistency, and counts:

Equation Cross-check
E_{>=1}(\Omega) = X matches revert() (implicit Lagrange inversion)
A = X*E(A) rooted trees; explicit define == implicit; count n^{n-1}
A = X + E_2(A) valuation-2 outer
A = X + E_2(B), B = X + E_2(A) coupled system; A = B
A = Y + q*E_2(A) weight on the composition term (over QQ[q])
B = Y + E_2(q*B) weight inside the argument
D = Y*E(q*D) weighted, full E, valuation 1
C = Y + (E(q*C) - 1) over QQ(q); needs the fraction field

One boundary

When the linear part scales the unknown by a weight (e.g. E_1(q\Omega) = q\Omega occurs in the equation), then (1 - q) f_{n,M} = (\text{lower}), so the solution lies in Frac(k) and the problem has to be posed over, say, QQ(q) rather than QQ[q]. This is the same reason revert() currently requires a field base ring.

Dependencies

mwhansen added 2 commits June 28, 2026 01:51
…ate species

Composing a univariate (or multivariate) species `F` with single sorts,
e.g. `F(X)`, previously went through the full composition machinery even
though it only relabels the sorts of the molecular species in the support
of `F`.  This was very slow: `L1.Cycles()(X)[20]` took ~2s (and ~19s on a
cold cache).

Add a fast path inside `CompositionSpeciesElement` that detects when every
argument is a singleton `Y_i` of the target ring and computes the result
by relabelling the domain partitions, via the new helper
`PolynomialSpeciesElement._relabel_sorts`.  The result is still a
`CompositionSpeciesElement`, so its specialized `structures`,
`generating_series` and `cycle_index_series` are preserved unchanged.

`L1.Cycles()(X)[20]` now takes ~0.0002s.
Make define_implicitly work when an implicitly defined species appears
inside a composition (plethysm), e.g. the implicit compositional inverse
defined by E_{>=1}(Omega) = X.

The undetermined coefficients of an implicitly defined species are
molecular multiplicities, hence constants of the lambda-ring, so the
power-sum (Adams) operations fix them; only the base-ring variables
(weights) are treated as monomials.  This keeps each degree's coefficient
comparison linear in the newest unknowns.

- species.py: in _compose_with_weighted_singletons, compute the plethysm
  over a ring containing the (possibly undetermined) multiplicities, and
  route stretch through CoefficientRing._power_sum_plethysm.
- stream.py: add CoefficientRing._power_sum_plethysm (fixes undetermined
  coefficients, stretches base-ring monomials); make
  Stream_function.input_streams also detect streams captured inside a
  list or tuple in the closure.
- lazy_species.py: build the composite coefficient over the ring its
  coefficients live in, rebuild the per-call argument snapshots so that
  values refined by the solver are re-read rather than memoized while
  undetermined, and expose the argument streams so the solver
  substitutes determined values into their caches.
@github-actions

Copy link
Copy Markdown

Documentation preview for this PR (built with commit 282fe74; changes) is ready! 🎉
This preview will update shortly after each push to this PR.

@mantepse mantepse self-assigned this Jun 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants