Skip to content

Commit d5b86a8

Browse files
committed
implement revert, improve plethysm
1 parent e780472 commit d5b86a8

File tree

4 files changed

+232
-54
lines changed

4 files changed

+232
-54
lines changed

src/sage/combinat/sf/sfa.py

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3076,19 +3076,20 @@ def plethysm(self, x, include=None, exclude=None):
30763076
# Handle degree one elements
30773077
if include is not None and exclude is not None:
30783078
raise RuntimeError("include and exclude cannot both be specified")
3079+
R = p.base_ring()
30793080

3080-
try:
3081-
degree_one = [R(g) for g in R.variable_names_recursive()]
3082-
except AttributeError:
3083-
try:
3084-
degree_one = R.gens()
3085-
except NotImplementedError:
3086-
degree_one = []
3087-
3088-
if include:
3081+
if include is not None:
30893082
degree_one = [R(g) for g in include]
3090-
if exclude:
3091-
degree_one = [g for g in degree_one if g not in exclude]
3083+
else:
3084+
try:
3085+
degree_one = [R(g) for g in R.variable_names_recursive()]
3086+
except AttributeError:
3087+
try:
3088+
degree_one = R.gens()
3089+
except NotImplementedError:
3090+
degree_one = []
3091+
if exclude is not None:
3092+
degree_one = [g for g in degree_one if g not in exclude]
30923093

30933094
degree_one = [g for g in degree_one if g != R.one()]
30943095

src/sage/data_structures/stream.py

Lines changed: 133 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,6 @@
8080
- Kwankyu Lee (2019-02-24): initial version
8181
- Tejasvi Chebrolu, Martin Rubey, Travis Scrimshaw (2021-08):
8282
refactored and expanded functionality
83-
8483
"""
8584

8685
# ****************************************************************************
@@ -168,6 +167,15 @@ class Stream_inexact(Stream):
168167
- ``sparse`` -- boolean; whether the implementation of the stream is sparse
169168
- ``approximate_order`` -- integer; a lower bound for the order
170169
of the stream
170+
171+
.. TODO::
172+
173+
The ``approximate_order`` is currently only updated when
174+
invoking :meth:`order`. It might make sense to update it
175+
whenever the coefficient one larger than the current
176+
``approximate_order`` is computed, since in some methods this
177+
will allow shortcuts.
178+
171179
"""
172180
def __init__(self, is_sparse, approximate_order):
173181
"""
@@ -1659,30 +1667,73 @@ class Stream_plethysm(Stream_binary):
16591667
- ``f`` -- a :class:`Stream`
16601668
- ``g`` -- a :class:`Stream` with positive order
16611669
- ``p`` -- the powersum symmetric functions
1670+
- ``ring`` (optional, default ``None``) -- the ring the result
1671+
should be in, by default ``p``
1672+
- ``include`` -- a list of variables to be treated as degree one
1673+
elements instead of the default degree one elements
1674+
- ``exclude`` -- a list of variables to be excluded from the
1675+
default degree one elements
16621676
16631677
EXAMPLES::
16641678
1665-
sage: from sage.data_structures.stream import Stream_function, Stream_plethysm
1679+
sage: from sage.data_structures.stream import Stream_function, Stream_plethysm
16661680
sage: s = SymmetricFunctions(QQ).s()
16671681
sage: p = SymmetricFunctions(QQ).p()
16681682
sage: f = Stream_function(lambda n: s[n], s, True, 1)
16691683
sage: g = Stream_function(lambda n: s[[1]*n], s, True, 1)
1670-
sage: h = Stream_plethysm(f, g, p)
1671-
sage: [s(h[i]) for i in range(5)]
1684+
sage: h = Stream_plethysm(f, g, p, s)
1685+
sage: [h[i] for i in range(5)]
16721686
[0,
16731687
s[1],
16741688
s[1, 1] + s[2],
16751689
2*s[1, 1, 1] + s[2, 1] + s[3],
16761690
3*s[1, 1, 1, 1] + 2*s[2, 1, 1] + s[2, 2] + s[3, 1] + s[4]]
1677-
sage: u = Stream_plethysm(g, f, p)
1678-
sage: [s(u[i]) for i in range(5)]
1691+
sage: u = Stream_plethysm(g, f, p, s)
1692+
sage: [u[i] for i in range(5)]
16791693
[0,
16801694
s[1],
16811695
s[1, 1] + s[2],
16821696
s[1, 1, 1] + s[2, 1] + 2*s[3],
16831697
s[1, 1, 1, 1] + s[2, 1, 1] + 3*s[3, 1] + 2*s[4]]
1698+
1699+
TESTS:
1700+
1701+
Check corner cases::
1702+
1703+
sage: from sage.data_structures.stream import Stream_exact
1704+
sage: p = SymmetricFunctions(QQ).p()
1705+
sage: f0 = Stream_exact([p([])], True)
1706+
sage: f1 = Stream_exact([p[1]], True, order=1)
1707+
sage: f2 = Stream_exact([p[2]], True, order=2 )
1708+
sage: f11 = Stream_exact([p[1,1]], True, order=2 )
1709+
sage: r = Stream_plethysm(f0, f1, p); [r[n] for n in range(3)]
1710+
[p[], 0, 0]
1711+
sage: r = Stream_plethysm(f0, f2, p); [r[n] for n in range(3)]
1712+
[p[], 0, 0]
1713+
sage: r = Stream_plethysm(f0, f11, p); [r[n] for n in range(3)]
1714+
[p[], 0, 0]
1715+
1716+
Check that degree one elements are treated in the correct way::
1717+
1718+
sage: R.<a1,a2,a11,b1,b21,b111> = QQ[]; p = SymmetricFunctions(R).p()
1719+
sage: f_s = a1*p[1] + a2*p[2] + a11*p[1,1]
1720+
sage: g_s = b1*p[1] + b21*p[2,1] + b111*p[1,1,1]
1721+
sage: r_s = f_s(g_s)
1722+
sage: f = Stream_exact([f_s.restrict_degree(k) for k in range(f_s.degree()+1)], True)
1723+
sage: g = Stream_exact([g_s.restrict_degree(k) for k in range(g_s.degree()+1)], True)
1724+
sage: r = Stream_plethysm(f, g, p)
1725+
sage: r_s == sum(r[n] for n in range(2*(r_s.degree()+1)))
1726+
True
1727+
1728+
sage: r_s - f_s(g_s, include=[])
1729+
(a2*b1^2-a2*b1)*p[2] + (a2*b111^2-a2*b111)*p[2, 2, 2] + (a2*b21^2-a2*b21)*p[4, 2]
1730+
1731+
sage: r2 = Stream_plethysm(f, g, p, include=[])
1732+
sage: r_s - sum(r2[n] for n in range(2*(r_s.degree()+1)))
1733+
(a2*b1^2-a2*b1)*p[2] + (a2*b111^2-a2*b111)*p[2, 2, 2] + (a2*b21^2-a2*b21)*p[4, 2]
1734+
16841735
"""
1685-
def __init__(self, f, g, p):
1736+
def __init__(self, f, g, p, ring=None, include=None, exclude=None):
16861737
r"""
16871738
Initialize ``self``.
16881739
@@ -1695,12 +1746,38 @@ def __init__(self, f, g, p):
16951746
sage: g = Stream_function(lambda n: s[n-1,1], s, True, 2)
16961747
sage: h = Stream_plethysm(f, g, p)
16971748
"""
1698-
#assert g._approximate_order > 0
1699-
self._fv = f._approximate_order
1700-
self._gv = g._approximate_order
1749+
# handle degree one elements
1750+
if include is not None and exclude is not None:
1751+
raise RuntimeError("include and exclude cannot both be specified")
1752+
R = p.base_ring()
1753+
1754+
if include is not None:
1755+
degree_one = [R(g) for g in include]
1756+
else:
1757+
try:
1758+
degree_one = [R(g) for g in R.variable_names_recursive()]
1759+
except AttributeError:
1760+
try:
1761+
degree_one = R.gens()
1762+
except NotImplementedError:
1763+
degree_one = []
1764+
if exclude is not None:
1765+
degree_one = [g for g in degree_one if g not in exclude]
1766+
1767+
self._degree_one = [g for g in degree_one if g != R.one()]
1768+
1769+
# assert g._approximate_order > 0
1770+
val = f._approximate_order * g._approximate_order
1771+
p_f = Stream_map_coefficients(f, lambda x: x, p)
1772+
p_g = Stream_map_coefficients(g, lambda x: x, p)
1773+
self._powers = [p_g] # a cache for the powers of p_g
1774+
if ring is None:
1775+
self._basis = p
1776+
else:
1777+
self._basis = ring
17011778
self._p = p
1702-
val = self._fv * self._gv
1703-
super().__init__(f, g, f._is_sparse, val)
1779+
1780+
super().__init__(p_f, p_g, f._is_sparse, val)
17041781

17051782
def get_coefficient(self, n):
17061783
r"""
@@ -1731,18 +1808,12 @@ def get_coefficient(self, n):
17311808
if not n: # special case of 0
17321809
return self._left[0]
17331810

1734-
# We assume n > 0
1735-
p = self._p
1736-
ret = p.zero()
1737-
for k in range(n+1):
1738-
temp = p(self._left[k])
1739-
for la, c in temp:
1740-
inner = self._compute_product(n, la, c)
1741-
if inner is not None:
1742-
ret += inner
1743-
return ret
1811+
return sum((c * self._compute_product(n, la)
1812+
for k in range(self._left._approximate_order, n+1)
1813+
for la, c in self._left[k]),
1814+
self._basis.zero())
17441815

1745-
def _compute_product(self, n, la, c):
1816+
def _compute_product(self, n, la):
17461817
"""
17471818
Compute the product ``c * p[la](self._right)`` in degree ``n``.
17481819
@@ -1754,25 +1825,53 @@ def _compute_product(self, n, la, c):
17541825
sage: f = Stream_function(lambda n: s[n], s, True, 1)
17551826
sage: g = Stream_exact([s[2], s[3]], False, 0, 4, 2)
17561827
sage: h = Stream_plethysm(f, g, p)
1757-
sage: ret = h._compute_product(7, [2, 1], 1); ret
1828+
sage: ret = h._compute_product(7, Partition([2, 1])); ret
17581829
1/12*p[2, 2, 1, 1, 1] + 1/4*p[2, 2, 2, 1] + 1/6*p[3, 2, 2]
17591830
+ 1/12*p[4, 1, 1, 1] + 1/4*p[4, 2, 1] + 1/6*p[4, 3]
17601831
sage: ret == p[2,1](s[2] + s[3]).homogeneous_component(7)
17611832
True
17621833
"""
1763-
p = self._p
1764-
ret = p.zero()
1765-
for mu in wt_int_vec_iter(n, la):
1766-
temp = c
1767-
for i, j in zip(la, mu):
1768-
gs = self._right[j]
1769-
if not gs:
1770-
temp = p.zero()
1771-
break
1772-
temp *= p[i](gs)
1834+
ret = self._basis.zero()
1835+
la_exp = la.to_exp()
1836+
wgt = [i for i, m in enumerate(la_exp, 1) if m]
1837+
exp = [m for m in la_exp if m]
1838+
for k in wt_int_vec_iter(n, wgt):
1839+
# TODO: it may make a big difference here if the
1840+
# approximate order would be updated
1841+
if any(d < self._right._approximate_order * m
1842+
for m, d in zip(exp, k)):
1843+
continue
1844+
temp = self._basis.one()
1845+
for i, m, d in zip(wgt, exp, k):
1846+
temp *= self._stretched_power_restrict_degree(i, m, d)
17731847
ret += temp
17741848
return ret
17751849

1850+
def _scale_part(self, mu, i):
1851+
return mu.__class__(mu.parent(), [j * i for j in mu])
1852+
1853+
def _raise_c(self, c, i):
1854+
return c.subs(**{str(g): g ** i
1855+
for g in self._degree_one})
1856+
1857+
def _stretched_power_restrict_degree(self, i, m, d):
1858+
"""
1859+
Return the degree ``d*i`` part of ``p([i]*m)(g)``.
1860+
"""
1861+
while len(self._powers) < m:
1862+
self._powers.append(Stream_cauchy_mul(self._powers[-1], self._powers[0]))
1863+
power = self._powers[m-1]
1864+
1865+
# we have to check power[d] for zero because it might be an
1866+
# integer and not a symmetric function
1867+
if power[d]:
1868+
terms = [(self._scale_part(m, i), self._raise_c(c, i)) for m, c in power[d]]
1869+
else:
1870+
terms = []
1871+
1872+
return self._p.sum_of_terms(terms, distinct = True)
1873+
1874+
17761875
#####################################################################
17771876
# Unary operations
17781877

@@ -2324,4 +2423,3 @@ def is_nonzero(self):
23242423
True
23252424
"""
23262425
return self._series.is_nonzero()
2327-

src/sage/rings/lazy_series.py

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -965,7 +965,10 @@ def change_ring(self, ring):
965965
Lazy Taylor Series Ring in z over Rational Field
966966
"""
967967
P = self.parent()
968-
Q = type(P)(ring, names=P.variable_names(), sparse=P._sparse)
968+
if P._names is not None:
969+
Q = type(P)(ring, names=P.variable_names(), sparse=P._sparse)
970+
else:
971+
Q = type(P)(ring, sparse=P._sparse)
969972
return Q.element_class(Q, self._coeff_stream)
970973

971974
# === module structure ===
@@ -3076,6 +3079,8 @@ def revert(self):
30763079
raise ValueError("compositional inverse does not exist")
30773080
raise ValueError("cannot determine whether the compositional inverse exists")
30783081

3082+
compositional_inverse = revert
3083+
30793084
def approximate_series(self, prec, name=None):
30803085
r"""
30813086
Return the Laurent series with absolute precision ``prec`` approximated
@@ -3643,6 +3648,10 @@ def __call__(self, *args, check=True):
36433648
36443649
- ``args`` -- other (lazy) symmetric functions
36453650
3651+
.. TODO::
3652+
3653+
allow specification of degree one elements
3654+
36463655
EXAMPLES::
36473656
36483657
sage: P.<q> = QQ[]
@@ -3752,6 +3761,78 @@ def __call__(self, *args, check=True):
37523761

37533762
plethysm = __call__
37543763

3764+
def revert(self):
3765+
r"""
3766+
Return the compositional inverse of ``self`` if possible.
3767+
3768+
(Specifically, if ``self`` is of the form `0 + p_{1} + \cdots`.)
3769+
3770+
The compositional inverse is the inverse with respect to
3771+
plethystic substitution. This is the operation on cycle index
3772+
series which corresponds to substitution, a.k.a. partitional
3773+
composition, on the level of species. See Section 2.2 of
3774+
[BLL]_ for a definition of this operation.
3775+
3776+
EXAMPLES::
3777+
3778+
sage: h = SymmetricFunctions(QQ).h()
3779+
sage: p = SymmetricFunctions(QQ).p()
3780+
sage: L = LazySymmetricFunctions(p)
3781+
sage: Eplus = L(lambda n: h[n]) - 1
3782+
sage: Eplus(Eplus.revert())
3783+
p[1] + O^8
3784+
3785+
TESTS::
3786+
3787+
sage: Eplus = L(lambda n: h[n]) - 1 - h[1]
3788+
sage: Eplus.compositional_inverse()
3789+
Traceback (most recent call last):
3790+
...
3791+
ValueError: compositional inverse does not exist
3792+
3793+
ALGORITHM:
3794+
3795+
Let `F` be a species satisfying `F = 0 + X + F_2 + F_3 + \cdots` for
3796+
`X` the species of singletons. (Equivalently, `\lvert F[\varnothing]
3797+
\rvert = 0` and `\lvert F[\{1\}] \rvert = 1`.) Then there exists a
3798+
(virtual) species `G` satisfying `F \circ G = G \circ F = X`.
3799+
3800+
It follows that `(F - X) \circ G = F \circ G - X \circ G = X - G`.
3801+
Rearranging, we obtain the recursive equation `G = X - (F - X) \circ G`,
3802+
which can be solved using iterative methods.
3803+
3804+
.. WARNING::
3805+
3806+
This algorithm is functional but can be very slow.
3807+
Use with caution!
3808+
3809+
.. SEEALSO::
3810+
3811+
The compositional inverse `\Omega` of the species `E_{+}`
3812+
of nonempty sets can be handled much more efficiently
3813+
using specialized methods. See
3814+
:func:`~sage.combinat.species.generating_series.LogarithmCycleIndexSeries`
3815+
3816+
AUTHORS:
3817+
3818+
- Andrew Gainer-Dewar
3819+
"""
3820+
P = self.parent()
3821+
if P._arity != 1:
3822+
raise ValueError("arity must be equal to 1")
3823+
if self.valuation() == 1:
3824+
from sage.combinat.sf.sf import SymmetricFunctions
3825+
p = SymmetricFunctions(P.base()).p()
3826+
X = P(p[1])
3827+
f1 = self - X
3828+
g = P(None, valuation=1)
3829+
g.define(X - f1(g))
3830+
return g
3831+
raise ValueError("compositional inverse does not exist")
3832+
3833+
plethystic_inverse = revert
3834+
compositional_inverse = revert
3835+
37553836
def _format_series(self, formatter, format_strings=False):
37563837
r"""
37573838
Return nonzero ``self`` formatted by ``formatter``.

0 commit comments

Comments
 (0)