diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index 1dbd6f036e7..9b59029d1b0 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -4198,6 +4198,7 @@ REFERENCES: .. [MagmaHGM] *Hypergeometric motives* in Magma, http://magma.maths.usyd.edu.au/~watkins/papers/HGM-chapter.pdf + .. [Mar1980] Jacques Martinet, Petits discriminants des corps de nombres, Journ. Arithm. 1980, Cambridge Univ. Press, 1982, 151--193. @@ -4384,6 +4385,11 @@ REFERENCES: *Symmetric cyclotomic Hecke algebras* J. Algebra. **205** (1998) pp. 275-293. +.. [MM2008] Manel Maia and Miguel Méndez. + On the arithmetic product of combinatorial species. + Discrete Mathematics (2008), Volume 308, Issue 23, pp. 5407-5427, + :arxiv:`math/0503436v2`. + .. [MM2015] \J. Matherne and \G. Muller, *Computing upper cluster algebras*, Int. Math. Res. Not. IMRN, 2015, 3121-3149. diff --git a/src/sage/categories/commutative_algebras.py b/src/sage/categories/commutative_algebras.py index ec4037f9a84..0f24e90c524 100644 --- a/src/sage/categories/commutative_algebras.py +++ b/src/sage/categories/commutative_algebras.py @@ -10,8 +10,11 @@ # http://www.gnu.org/licenses/ #****************************************************************************** +from sage.misc.cachefunc import cached_method from sage.categories.category_with_axiom import CategoryWithAxiom_over_base_ring from sage.categories.algebras import Algebras +from sage.categories.commutative_rings import CommutativeRings +from sage.categories.tensor import TensorProductsCategory class CommutativeAlgebras(CategoryWithAxiom_over_base_ring): """ @@ -36,7 +39,7 @@ class CommutativeAlgebras(CategoryWithAxiom_over_base_ring): True sage: TestSuite(CommutativeAlgebras(ZZ)).run() - Todo: + .. TODO:: - product ( = Cartesian product) - coproduct ( = tensor product over base ring) @@ -58,3 +61,32 @@ def __contains__(self, A): """ return super().__contains__(A) or \ (A in Algebras(self.base_ring()) and hasattr(A, "is_commutative") and A.is_commutative()) + + class TensorProducts(TensorProductsCategory): + """ + The category of commutative algebras constructed by tensor product of commutative algebras. + """ + + @cached_method + def extra_super_categories(self): + """ + EXAMPLES:: + + sage: Algebras(QQ).Commutative().TensorProducts().extra_super_categories() + [Category of commutative rings] + sage: Algebras(QQ).Commutative().TensorProducts().super_categories() + [Category of tensor products of algebras over Rational Field, + Category of commutative algebras over Rational Field] + + TESTS:: + + sage: X = algebras.Shuffle(QQ, 'ab') + sage: Y = algebras.Shuffle(QQ, 'bc') + sage: X in Algebras(QQ).Commutative() + True + sage: T = tensor([X, Y]) + sage: T in CommutativeRings() + True + """ + return [CommutativeRings()] + diff --git a/src/sage/categories/highest_weight_crystals.py b/src/sage/categories/highest_weight_crystals.py index e063e388998..b8afdee2117 100644 --- a/src/sage/categories/highest_weight_crystals.py +++ b/src/sage/categories/highest_weight_crystals.py @@ -322,14 +322,9 @@ def q_dimension(self, q=None, prec=None, use_product=False): 1 + q + 2*q^2 + 2*q^3 + 4*q^4 + 5*q^5 + 7*q^6 + 9*q^7 + 13*q^8 + 16*q^9 + O(q^10) sage: qdim = C.q_dimension(); qdim - 1 + q + 2*q^2 + 2*q^3 + 4*q^4 + 5*q^5 + 7*q^6 - + 9*q^7 + 13*q^8 + 16*q^9 + 22*q^10 + O(x^11) - sage: qdim.compute_coefficients(15) - sage: qdim - 1 + q + 2*q^2 + 2*q^3 + 4*q^4 + 5*q^5 + 7*q^6 - + 9*q^7 + 13*q^8 + 16*q^9 + 22*q^10 + 27*q^11 - + 36*q^12 + 44*q^13 + 57*q^14 + 70*q^15 + O(x^16) - + 1 + q + 2*q^2 + 2*q^3 + 4*q^4 + 5*q^5 + 7*q^6 + O(q^7) + sage: qdim[:16] + [1, 1, 2, 2, 4, 5, 7, 9, 13, 16, 22, 27, 36, 44, 57, 70] """ from sage.rings.integer_ring import ZZ WLR = self.weight_lattice_realization() @@ -375,7 +370,7 @@ def iter_by_deg(gens): elif prec is None: # If we're here, we may not be a finite crystal. # In fact, we're probably infinite. - from sage.combinat.species.series import LazyPowerSeriesRing + from sage.rings.lazy_series_ring import LazyPowerSeriesRing if q is None: P = LazyPowerSeriesRing(ZZ, names='q') else: @@ -383,14 +378,13 @@ def iter_by_deg(gens): if not isinstance(P, LazyPowerSeriesRing): raise TypeError("the parent of q must be a lazy power series ring") ret = P(iter_by_deg(mg)) - ret.compute_coefficients(10) return ret from sage.rings.power_series_ring import PowerSeriesRing, PowerSeriesRing_generic if q is None: q = PowerSeriesRing(ZZ, 'q', default_prec=prec).gen(0) P = q.parent() - ret = P.sum(c * q**deg for deg,c in enumerate(iter_by_deg(mg))) + ret = P.sum(c * q**deg for deg, c in enumerate(iter_by_deg(mg))) if ret.degree() == max_deg and isinstance(P, PowerSeriesRing_generic): ret = P(ret, prec) return ret diff --git a/src/sage/categories/sets_with_grading.py b/src/sage/categories/sets_with_grading.py index 8ef7b8b0256..5d01bfab999 100644 --- a/src/sage/categories/sets_with_grading.py +++ b/src/sage/categories/sets_with_grading.py @@ -212,11 +212,21 @@ def generating_series(self): Non negative integers sage: N.generating_series() 1/(-z + 1) + + sage: Permutations().generating_series() + 1 + z + 2*z^2 + 6*z^3 + 24*z^4 + 120*z^5 + 720*z^6 + O(z^7) + + .. TODO:: + + - Very likely, this should always return a lazy power series. """ - from sage.combinat.species.series import LazyPowerSeriesRing + from sage.sets.non_negative_integers import NonNegativeIntegers + from sage.rings.lazy_series_ring import LazyPowerSeriesRing from sage.rings.integer_ring import ZZ - R = LazyPowerSeriesRing(ZZ) - R(self.graded_component(grade).cardinality() for grade in self.grading_set()) + if isinstance(self.grading_set(), NonNegativeIntegers): + R = LazyPowerSeriesRing(ZZ, names="z") + return R(lambda n: self.graded_component(n).cardinality()) + raise NotImplementedError # TODO: # * asymptotic behavior: we need an object for asymptotic behavior and diff --git a/src/sage/combinat/partition.py b/src/sage/combinat/partition.py index 590b0df65e5..d4fb1aeab1e 100644 --- a/src/sage/combinat/partition.py +++ b/src/sage/combinat/partition.py @@ -1057,6 +1057,19 @@ def __truediv__(self, p): return SkewPartition([self[:], p]) + def stretch(self, k): + """ + Return the partition obtained by multiplying each part with the + given number. + + EXAMPLES:: + + sage: p = Partition([4,2,2,1,1]) + sage: p.stretch(3) + [12, 6, 6, 3, 3] + """ + return _Partitions([k * p for p in self]) + def power(self, k): r""" Return the cycle type of the `k`-th power of any permutation diff --git a/src/sage/combinat/sf/powersum.py b/src/sage/combinat/sf/powersum.py index f7542d7929e..f7019407840 100644 --- a/src/sage/combinat/sf/powersum.py +++ b/src/sage/combinat/sf/powersum.py @@ -476,8 +476,8 @@ def frobenius(self, n): :meth:`~sage.combinat.sf.sfa.SymmetricFunctionAlgebra_generic_Element.plethysm` """ - dct = {Partition([n * i for i in lam]): coeff - for (lam, coeff) in self.monomial_coefficients().items()} + dct = {lam.stretch(n): coeff + for lam, coeff in self.monomial_coefficients().items()} return self.parent()._from_dict(dct) adams_operation = frobenius diff --git a/src/sage/combinat/sf/sfa.py b/src/sage/combinat/sf/sfa.py index afeaf70a3e0..1fb9d204e34 100644 --- a/src/sage/combinat/sf/sfa.py +++ b/src/sage/combinat/sf/sfa.py @@ -3072,6 +3072,31 @@ def plethysm(self, x, include=None, exclude=None): sage: sum(s[mu](X)*s[mu.conjugate()](Y) for mu in P5) == sum(m[mu](X)*e[mu](Y) for mu in P5) True + Sage can also do the plethysm with an element in the completion:: + + sage: L = LazySymmetricFunctions(s) + sage: f = s[2,1] + sage: g = L(s[1]) / (1 - L(s[1])); g + s[1] + (s[1,1]+s[2]) + (s[1,1,1]+2*s[2,1]+s[3]) + + (s[1,1,1,1]+3*s[2,1,1]+2*s[2,2]+3*s[3,1]+s[4]) + + (s[1,1,1,1,1]+4*s[2,1,1,1]+5*s[2,2,1]+6*s[3,1,1]+5*s[3,2]+4*s[4,1]+s[5]) + + ... + O^8 + sage: fog = f(g) + sage: fog[:8] + [s[2, 1], + s[1, 1, 1, 1] + 3*s[2, 1, 1] + 2*s[2, 2] + 3*s[3, 1] + s[4], + 2*s[1, 1, 1, 1, 1] + 8*s[2, 1, 1, 1] + 10*s[2, 2, 1] + + 12*s[3, 1, 1] + 10*s[3, 2] + 8*s[4, 1] + 2*s[5], + 3*s[1, 1, 1, 1, 1, 1] + 17*s[2, 1, 1, 1, 1] + 30*s[2, 2, 1, 1] + + 16*s[2, 2, 2] + 33*s[3, 1, 1, 1] + 54*s[3, 2, 1] + 16*s[3, 3] + + 33*s[4, 1, 1] + 30*s[4, 2] + 17*s[5, 1] + 3*s[6], + 5*s[1, 1, 1, 1, 1, 1, 1] + 30*s[2, 1, 1, 1, 1, 1] + 70*s[2, 2, 1, 1, 1] + + 70*s[2, 2, 2, 1] + 75*s[3, 1, 1, 1, 1] + 175*s[3, 2, 1, 1] + + 105*s[3, 2, 2] + 105*s[3, 3, 1] + 100*s[4, 1, 1, 1] + 175*s[4, 2, 1] + + 70*s[4, 3] + 75*s[5, 1, 1] + 70*s[5, 2] + 30*s[6, 1] + 5*s[7]] + sage: parent(fog) + Lazy completion of Symmetric Functions over Rational Field in the Schur basis + .. SEEALSO:: :meth:`frobenius` @@ -3080,66 +3105,100 @@ def plethysm(self, x, include=None, exclude=None): sage: (1+p[2]).plethysm(p[2]) p[] + p[4] + + Check that degree one elements are treated in the correct way:: + + sage: R. = QQ[] + sage: p = SymmetricFunctions(R).p() + sage: f = a1*p[1] + a2*p[2] + a11*p[1,1] + sage: g = b1*p[1] + b21*p[2,1] + b111*p[1,1,1] + sage: r = f(g); r + a1*b1*p[1] + a11*b1^2*p[1, 1] + a1*b111*p[1, 1, 1] + + 2*a11*b1*b111*p[1, 1, 1, 1] + a11*b111^2*p[1, 1, 1, 1, 1, 1] + + a2*b1^2*p[2] + a1*b21*p[2, 1] + 2*a11*b1*b21*p[2, 1, 1] + + 2*a11*b21*b111*p[2, 1, 1, 1, 1] + a11*b21^2*p[2, 2, 1, 1] + + a2*b111^2*p[2, 2, 2] + a2*b21^2*p[4, 2] + sage: r - f(g, include=[]) + (a2*b1^2-a2*b1)*p[2] + (a2*b111^2-a2*b111)*p[2, 2, 2] + (a2*b21^2-a2*b21)*p[4, 2] + + Check that we can compute the plethysm with a constant:: + + sage: p[2,2,1](2) + 8*p[] + + sage: p[2,2,1](int(2)) + 8*p[] + + sage: p[2,2,1](a1) + a1^5*p[] + + sage: X = algebras.Shuffle(QQ, 'ab') + sage: Y = algebras.Shuffle(QQ, 'bc') + sage: T = tensor([X,Y]) + sage: s = SymmetricFunctions(T).s() + sage: s(2*T.one()) + (2*B[word:]#B[word:])*s[] + + .. TODO:: + + The implementation of plethysm in + :class:`sage.data_structures.stream.Stream_plethysm` seems + to be faster. This should be investigated. """ parent = self.parent() - R = parent.base_ring() - tHA = HopfAlgebrasWithBasis(R).TensorProducts() - tensorflag = tHA in x.parent().categories() - if not (is_SymmetricFunction(x) or tensorflag): - raise TypeError("only know how to compute plethysms " - "between symmetric functions or tensors " - "of symmetric functions") - p = parent.realization_of().power() - if self == parent.zero(): + if not self: return self - # Handle degree one elements - if include is not None and exclude is not None: - raise RuntimeError("include and exclude cannot both be specified") - - try: - degree_one = [R(g) for g in R.variable_names_recursive()] - except AttributeError: - try: - degree_one = R.gens() - except NotImplementedError: - degree_one = [] - - if include: - degree_one = [R(g) for g in include] - if exclude: - degree_one = [g for g in degree_one if g not in exclude] + R = parent.base_ring() + tHA = HopfAlgebrasWithBasis(R).TensorProducts() + from sage.structure.element import parent as get_parent + Px = get_parent(x) + tensorflag = Px in tHA + if not is_SymmetricFunction(x): + if Px is R: # Handle stuff that is directly in the base ring + x = parent(x) + elif (not tensorflag or any(not isinstance(factor, SymmetricFunctionAlgebra_generic) + for factor in Px._sets)): + from sage.rings.lazy_series import LazySymmetricFunction + if isinstance(x, LazySymmetricFunction): + from sage.rings.lazy_series_ring import LazySymmetricFunctions + L = LazySymmetricFunctions(parent) + return L(self)(x) + + # Try to coerce into a symmetric function + phi = parent.coerce_map_from(Px) + if phi is not None: + x = phi(x) + elif not tensorflag: + raise TypeError("only know how to compute plethysms " + "between symmetric functions or tensors " + "of symmetric functions") - degree_one = [g for g in degree_one if g != R.one()] + p = parent.realization_of().power() - def raise_c(n): - return lambda c: c.subs(**{str(g): g ** n for g in degree_one}) + degree_one = _variables_recursive(R, include=include, exclude=exclude) if tensorflag: - tparents = x.parent()._sets - return tensor([parent]*len(tparents))(sum(d*prod(sum(raise_c(r)(c) - * tensor([p[r].plethysm(base(la)) - for (base,la) in zip(tparents,trm)]) - for (trm,c) in x) - for r in mu) - for (mu, d) in p(self))) - - # Takes in n, and returns a function which takes in a partition and - # scales all of the parts of that partition by n - def scale_part(n): - return lambda m: m.__class__(m.parent(), [i * n for i in m]) - - # Takes n an symmetric function f, and an n and returns the + tparents = Px._sets + s = sum(d * prod(sum(_raise_variables(c, r, degree_one) + * tensor([p[r].plethysm(base(la)) + for base, la in zip(tparents, trm)]) + for trm, c in x) + for r in mu) + for mu, d in p(self)) + return tensor([parent]*len(tparents))(s) + + # Takes a symmetric function f, and an n and returns the # symmetric function with all of its basis partitions scaled # by n def pn_pleth(f, n): - return f.map_support(scale_part(n)) + return f.map_support(lambda mu: mu.stretch(n)) # Takes in a partition and applies p_x = p(x) def f(part): - return p.prod(pn_pleth(p_x.map_coefficients(raise_c(i)), i) + return p.prod(pn_pleth(p_x.map_coefficients(lambda c: _raise_variables(c, i, degree_one)), i) for i in part) return parent(p._apply_module_morphism(p(self), f, codomain=p)) @@ -4917,9 +4976,7 @@ def frobenius(self, n): # then convert back. parent = self.parent() m = parent.realization_of().monomial() - from sage.combinat.partition import Partition - dct = {Partition([n * i for i in lam]): coeff - for (lam, coeff) in m(self)} + dct = {lam.stretch(n): coeff for lam, coeff in m(self)} result_in_m_basis = m._from_dict(dct) return parent(result_in_m_basis) @@ -6125,3 +6182,82 @@ def _nonnegative_coefficients(x): return all(c >= 0 for c in x.coefficients(sparse=False)) else: return x >= 0 + +def _variables_recursive(R, include=None, exclude=None): + """ + Return all variables appearing in the ring ``R``. + + INPUT: + + - ``R`` -- a :class:`Ring` + - ``include``, ``exclude`` (optional, default ``None``) -- + iterables of variables in ``R`` + + OUTPUT: + + - If ``include`` is specified, only these variables are returned + as elements of ``R``. Otherwise, all variables in ``R`` + (recursively) with the exception of those in ``exclude`` are + returned. + + EXAMPLES:: + + sage: from sage.combinat.sf.sfa import _variables_recursive + sage: R. = QQ[] + sage: S. = R[] + sage: _variables_recursive(S) + [a, b, t] + + sage: _variables_recursive(S, exclude=[b]) + [a, t] + + sage: _variables_recursive(S, include=[b]) + [b] + + TESTS:: + + sage: _variables_recursive(R.fraction_field(), exclude=[b]) + [a] + + sage: _variables_recursive(S.fraction_field(), exclude=[b]) # known bug + [a, t] + """ + if include is not None and exclude is not None: + raise RuntimeError("include and exclude cannot both be specified") + + if include is not None: + degree_one = [R(g) for g in include] + else: + try: + degree_one = [R(g) for g in R.variable_names_recursive()] + except AttributeError: + try: + degree_one = R.gens() + except NotImplementedError: + degree_one = [] + if exclude is not None: + degree_one = [g for g in degree_one if g not in exclude] + + return [g for g in degree_one if g != R.one()] + +def _raise_variables(c, n, variables): + """ + Replace the given variables in the ring element ``c`` with their + ``n``-th power. + + INPUT: + + - ``c`` -- an element of a ring + - ``n`` -- the power to raise the given variables to + - ``variables`` -- the variables to raise + + EXAMPLES:: + + sage: from sage.combinat.sf.sfa import _raise_variables + sage: R. = QQ[] + sage: S. = R[] + sage: _raise_variables(2*a + 3*b*t, 2, [a, t]) + 3*b*t^2 + 2*a^2 + + """ + return c.subs(**{str(g): g ** n for g in variables}) diff --git a/src/sage/combinat/species/all.py b/src/sage/combinat/species/all.py index 65e18ade5d2..95c495603bb 100644 --- a/src/sage/combinat/species/all.py +++ b/src/sage/combinat/species/all.py @@ -11,14 +11,6 @@ - :ref:`section-examples-catalan` - :ref:`section-generic-species` -Lazy Power Series ------------------ - -- :ref:`sage.combinat.species.stream` -- :ref:`sage.combinat.species.series_order` -- :ref:`sage.combinat.species.series` -- :ref:`sage.combinat.species.generating_series` - Basic Species ------------- @@ -52,6 +44,5 @@ from sage.misc.namespace_package import install_doc install_doc(__package__, __doc__) -from .series import LazyPowerSeriesRing from .recursive_species import CombinatorialSpecies from . import library as species diff --git a/src/sage/combinat/species/characteristic_species.py b/src/sage/combinat/species/characteristic_species.py index dbb1d5c262a..2bd5a50ff00 100644 --- a/src/sage/combinat/species/characteristic_species.py +++ b/src/sage/combinat/species/characteristic_species.py @@ -109,15 +109,15 @@ def __init__(self, n, min=None, max=None, weight=None): [1] sage: X.structures([1,2]).list() [] - sage: X.generating_series().coefficients(4) + sage: X.generating_series()[0:4] [0, 1, 0, 0] - sage: X.isotype_generating_series().coefficients(4) + sage: X.isotype_generating_series()[0:4] [0, 1, 0, 0] - sage: X.cycle_index_series().coefficients(4) + sage: X.cycle_index_series()[0:4] [0, p[1], 0, 0] sage: F = species.CharacteristicSpecies(3) - sage: c = F.generating_series().coefficients(4) + sage: c = F.generating_series()[0:4] sage: F._check() True sage: F == loads(dumps(F)) @@ -163,7 +163,7 @@ def _gs_term(self, base_ring): EXAMPLES:: sage: F = species.CharacteristicSpecies(2) - sage: F.generating_series().coefficients(5) + sage: F.generating_series()[0:5] [0, 0, 1/2, 0, 0] sage: F.generating_series().count(2) 1 @@ -187,7 +187,7 @@ def _itgs_term(self, base_ring): EXAMPLES:: sage: F = species.CharacteristicSpecies(2) - sage: F.isotype_generating_series().coefficients(5) + sage: F.isotype_generating_series()[0:5] [0, 0, 1, 0, 0] Here we test out weighting each structure by q. @@ -196,7 +196,7 @@ def _itgs_term(self, base_ring): sage: R. = ZZ[] sage: Fq = species.CharacteristicSpecies(2, weight=q) - sage: Fq.isotype_generating_series().coefficients(5) + sage: Fq.isotype_generating_series()[0:5] [0, 0, q, 0, 0] """ return base_ring(self._weight) @@ -207,7 +207,7 @@ def _cis_term(self, base_ring): sage: F = species.CharacteristicSpecies(2) sage: g = F.cycle_index_series() - sage: g.coefficients(5) + sage: g[0:5] [0, 0, 1/2*p[1, 1] + 1/2*p[2], 0, 0] """ cis = SetSpecies(weight=self._weight).cycle_index_series(base_ring) @@ -248,11 +248,11 @@ def __init__(self, min=None, max=None, weight=None): [{}] sage: X.structures([1,2]).list() [] - sage: X.generating_series().coefficients(4) + sage: X.generating_series()[0:4] [1, 0, 0, 0] - sage: X.isotype_generating_series().coefficients(4) + sage: X.isotype_generating_series()[0:4] [1, 0, 0, 0] - sage: X.cycle_index_series().coefficients(4) + sage: X.cycle_index_series()[0:4] [p[], 0, 0, 0] TESTS:: @@ -290,11 +290,11 @@ def __init__(self, min=None, max=None, weight=None): [1] sage: X.structures([1,2]).list() [] - sage: X.generating_series().coefficients(4) + sage: X.generating_series()[0:4] [0, 1, 0, 0] - sage: X.isotype_generating_series().coefficients(4) + sage: X.isotype_generating_series()[0:4] [0, 1, 0, 0] - sage: X.cycle_index_series().coefficients(4) + sage: X.cycle_index_series()[0:4] [0, p[1], 0, 0] TESTS:: diff --git a/src/sage/combinat/species/composition_species.py b/src/sage/combinat/species/composition_species.py index efc77ce508b..9050b8d89f0 100644 --- a/src/sage/combinat/species/composition_species.py +++ b/src/sage/combinat/species/composition_species.py @@ -105,7 +105,7 @@ def __init__(self, F, G, min=None, max=None, weight=None): sage: E = species.SetSpecies() sage: C = species.CycleSpecies() sage: S = E(C) - sage: S.generating_series().coefficients(5) + sage: S.generating_series()[:5] [1, 1, 1, 1, 1] sage: E(C) is S True @@ -114,7 +114,7 @@ def __init__(self, F, G, min=None, max=None, weight=None): sage: E = species.SetSpecies(); C = species.CycleSpecies() sage: L = E(C) - sage: c = L.generating_series().coefficients(3) + sage: c = L.generating_series()[:3] sage: L._check() #False due to isomorphism types not being implemented False sage: L == loads(dumps(L)) @@ -193,7 +193,7 @@ def _gs(self, series_ring, base_ring): sage: E = species.SetSpecies(); C = species.CycleSpecies() sage: L = E(C) - sage: L.generating_series().coefficients(5) + sage: L.generating_series()[:5] [1, 1, 1, 1, 1] """ return self._F.generating_series(base_ring)(self._G.generating_series(base_ring)) @@ -204,7 +204,7 @@ def _itgs(self, series_ring, base_ring): sage: E = species.SetSpecies(); C = species.CycleSpecies() sage: L = E(C) - sage: L.isotype_generating_series().coefficients(10) + sage: L.isotype_generating_series()[:10] [1, 1, 2, 3, 5, 7, 11, 15, 22, 30] """ cis = self.cycle_index_series(base_ring) @@ -216,7 +216,7 @@ def _cis(self, series_ring, base_ring): sage: E = species.SetSpecies(); C = species.CycleSpecies() sage: L = E(C) - sage: L.cycle_index_series().coefficients(5) + sage: L.cycle_index_series()[:5] [p[], p[1], p[1, 1] + p[2], @@ -233,7 +233,7 @@ def _cis(self, series_ring, base_ring): sage: E = species.SetSpecies() sage: C = species.CycleSpecies(weight=t) sage: S = E(C) - sage: S.isotype_generating_series().coefficients(5) #indirect + sage: S.isotype_generating_series()[:5] #indirect [1, t, t^2 + t, t^3 + t^2 + t, t^4 + t^3 + 2*t^2 + t] We do the same thing with set partitions weighted by the number of @@ -245,17 +245,11 @@ def _cis(self, series_ring, base_ring): sage: E = species.SetSpecies() sage: E_t = species.SetSpecies(min=1,weight=t) sage: Par = E(E_t) - sage: Par.isotype_generating_series().coefficients(5) + sage: Par.isotype_generating_series()[:5] [1, t, t^2 + t, t^3 + t^2 + t, t^4 + t^3 + 2*t^2 + t] """ f_cis = self._F.cycle_index_series(base_ring) g_cis = self._G.cycle_index_series(base_ring) - - #If G is a weighted species, then we can't use the default - #algorithm for the composition of the cycle index series - #since we must raise the weighting to the power. - if self._G.is_weighted(): - return f_cis.weighted_composition(self._G) return f_cis(g_cis) def weight_ring(self): diff --git a/src/sage/combinat/species/cycle_species.py b/src/sage/combinat/species/cycle_species.py index 83c82fc44a3..0e82416b54f 100644 --- a/src/sage/combinat/species/cycle_species.py +++ b/src/sage/combinat/species/cycle_species.py @@ -14,7 +14,6 @@ from .species import GenericCombinatorialSpecies from .structure import GenericSpeciesStructure -from .generating_series import _integers_from from sage.structure.unique_representation import UniqueRepresentation from sage.rings.integer_ring import ZZ from sage.arith.all import divisors, euler_phi @@ -142,7 +141,7 @@ def __init__(self, min=None, max=None, weight=None): True sage: P = species.CycleSpecies() - sage: c = P.generating_series().coefficients(3) + sage: c = P.generating_series()[:3] sage: P._check() True sage: P == loads(dumps(P)) @@ -177,7 +176,7 @@ def _isotypes(self, structure_class, labels): if len(labels) != 0: yield structure_class(self, labels, range(1, len(labels)+1)) - def _gs_iterator(self, base_ring): + def _gs_callable(self, base_ring, n): r""" The generating series for cyclic permutations is `-\log(1-x) = \sum_{n=1}^\infty x^n/n`. @@ -186,20 +185,19 @@ def _gs_iterator(self, base_ring): sage: P = species.CycleSpecies() sage: g = P.generating_series() - sage: g.coefficients(10) + sage: g[0:10] [0, 1, 1/2, 1/3, 1/4, 1/5, 1/6, 1/7, 1/8, 1/9] TESTS:: sage: P = species.CycleSpecies() sage: g = P.generating_series(RR) - sage: g.coefficients(3) + sage: g[0:3] [0.000000000000000, 1.00000000000000, 0.500000000000000] """ - one = base_ring(1) - yield base_ring(0) - for n in _integers_from(ZZ(1)): - yield self._weight*one/n + if n: + return self._weight * base_ring.one() / n + return base_ring.zero() def _order(self): """ @@ -213,7 +211,7 @@ def _order(self): """ return 1 - def _itgs_list(self, base_ring): + def _itgs_list(self, base_ring, n): """ The isomorphism type generating series for cyclic permutations is given by `x/(1-x)`. @@ -222,19 +220,21 @@ def _itgs_list(self, base_ring): sage: P = species.CycleSpecies() sage: g = P.isotype_generating_series() - sage: g.coefficients(5) + sage: g[0:5] [0, 1, 1, 1, 1] TESTS:: sage: P = species.CycleSpecies() sage: g = P.isotype_generating_series(RR) - sage: g.coefficients(3) + sage: g[0:3] [0.000000000000000, 1.00000000000000, 1.00000000000000] """ - return [base_ring(0), self._weight*base_ring(1)] + if n: + return self._weight * base_ring.one() + return base_ring.zero() - def _cis_iterator(self, base_ring): + def _cis_callable(self, base_ring, n): r""" The cycle index series of the species of cyclic permutations is given by @@ -256,7 +256,7 @@ def _cis_iterator(self, base_ring): sage: P = species.CycleSpecies() sage: cis = P.cycle_index_series() - sage: cis.coefficients(7) + sage: cis[0:7] [0, p[1], 1/2*p[1, 1] + 1/2*p[2], @@ -268,15 +268,15 @@ def _cis_iterator(self, base_ring): from sage.combinat.sf.sf import SymmetricFunctions p = SymmetricFunctions(base_ring).power() - zero = base_ring(0) + zero = base_ring.zero() - yield zero - for n in _integers_from(1): - res = zero - for k in divisors(n): - res += euler_phi(k)*p([k])**(n//k) - res /= n - yield self._weight*res + if not n: + return zero + res = zero + for k in divisors(n): + res += euler_phi(k)*p([k])**(n//k) + res /= n + return self._weight * res #Backward compatibility CycleSpecies_class = CycleSpecies diff --git a/src/sage/combinat/species/empty_species.py b/src/sage/combinat/species/empty_species.py index 600ea3ee2b7..5fd8081d1f8 100644 --- a/src/sage/combinat/species/empty_species.py +++ b/src/sage/combinat/species/empty_species.py @@ -16,7 +16,6 @@ # http://www.gnu.org/licenses/ #***************************************************************************** from .species import GenericCombinatorialSpecies -from .series_order import inf from sage.structure.unique_representation import UniqueRepresentation class EmptySpecies(GenericCombinatorialSpecies, UniqueRepresentation): @@ -34,11 +33,11 @@ class EmptySpecies(GenericCombinatorialSpecies, UniqueRepresentation): [] sage: X.structures([1,2]).list() [] - sage: X.generating_series().coefficients(4) + sage: X.generating_series()[0:4] [0, 0, 0, 0] - sage: X.isotype_generating_series().coefficients(4) + sage: X.isotype_generating_series()[0:4] [0, 0, 0, 0] - sage: X.cycle_index_series().coefficients(4) + sage: X.cycle_index_series()[0:4] [0, 0, 0, 0] The empty species is the zero of the semi-ring of species. @@ -49,14 +48,14 @@ class EmptySpecies(GenericCombinatorialSpecies, UniqueRepresentation): sage: X = S + Empt sage: X == S # TODO: Not Implemented True - sage: (X.generating_series().coefficients(4) == - ....: S.generating_series().coefficients(4)) + sage: (X.generating_series()[0:4] == + ....: S.generating_series()[0:4]) True - sage: (X.isotype_generating_series().coefficients(4) == - ....: S.isotype_generating_series().coefficients(4)) + sage: (X.isotype_generating_series()[0:4] == + ....: S.isotype_generating_series()[0:4]) True - sage: (X.cycle_index_series().coefficients(4) == - ....: S.cycle_index_series().coefficients(4)) + sage: (X.cycle_index_series()[0:4] == + ....: S.cycle_index_series()[0:4]) True The following tests that it is the zero element with respect to @@ -65,11 +64,11 @@ class EmptySpecies(GenericCombinatorialSpecies, UniqueRepresentation): sage: Y = Empt*S sage: Y == Empt # TODO: Not Implemented True - sage: Y.generating_series().coefficients(4) + sage: Y.generating_series()[0:4] [0, 0, 0, 0] - sage: Y.isotype_generating_series().coefficients(4) + sage: Y.isotype_generating_series()[0:4] [0, 0, 0, 0] - sage: Y.cycle_index_series().coefficients(4) + sage: Y.cycle_index_series()[0:4] [0, 0, 0, 0] TESTS:: @@ -102,7 +101,7 @@ def _gs(self, series_ring, base_ring): EXAMPLES:: sage: F = species.EmptySpecies() - sage: F.generating_series().coefficients(5) # indirect doctest + sage: F.generating_series()[0:5] # indirect doctest [0, 0, 0, 0, 0] sage: F.generating_series().count(3) 0 @@ -114,18 +113,6 @@ def _gs(self, series_ring, base_ring): _itgs = _gs _cis = _gs - def _order(self): - """ - Returns the order of the generating series. - - EXAMPLES:: - - sage: F = species.EmptySpecies() - sage: F._order() - Infinite series order - """ - return inf - def _structures(self, structure_class, labels): """ Thanks to the counting optimisation, this is never called... Otherwise diff --git a/src/sage/combinat/species/functorial_composition_species.py b/src/sage/combinat/species/functorial_composition_species.py index 4930a70e6da..1f082753638 100644 --- a/src/sage/combinat/species/functorial_composition_species.py +++ b/src/sage/combinat/species/functorial_composition_species.py @@ -33,11 +33,11 @@ def __init__(self, F, G, min=None, max=None, weight=None): sage: WP = species.SubsetSpecies() sage: P2 = E2*E sage: G = WP.functorial_composition(P2) - sage: G.isotype_generating_series().coefficients(5) + sage: G.isotype_generating_series()[0:5] [1, 1, 2, 4, 11] sage: G = species.SimpleGraphSpecies() - sage: c = G.generating_series().coefficients(2) + sage: c = G.generating_series()[0:2] sage: type(G) sage: G == loads(dumps(G)) @@ -93,7 +93,7 @@ def _gs(self, series_ring, base_ring): EXAMPLES:: sage: G = species.SimpleGraphSpecies() - sage: G.generating_series().coefficients(5) + sage: G.generating_series()[0:5] [1, 1, 1, 4/3, 8/3] """ return self._F.generating_series(base_ring).functorial_composition(self._G.generating_series(base_ring)) @@ -103,7 +103,7 @@ def _itgs(self, series_ring, base_ring): EXAMPLES:: sage: G = species.SimpleGraphSpecies() - sage: G.isotype_generating_series().coefficients(5) + sage: G.isotype_generating_series()[0:5] [1, 1, 2, 4, 11] """ return self.cycle_index_series(base_ring).isotype_generating_series() @@ -113,7 +113,7 @@ def _cis(self, series_ring, base_ring): EXAMPLES:: sage: G = species.SimpleGraphSpecies() - sage: G.cycle_index_series().coefficients(5) + sage: G.cycle_index_series()[0:5] [p[], p[1], p[1, 1] + p[2], diff --git a/src/sage/combinat/species/generating_series.py b/src/sage/combinat/species/generating_series.py index 02e1bb52332..8c0910f0043 100644 --- a/src/sage/combinat/species/generating_series.py +++ b/src/sage/combinat/species/generating_series.py @@ -15,37 +15,11 @@ TESTS:: - sage: from sage.combinat.species.stream import Stream, _integers_from sage: from sage.combinat.species.generating_series import CycleIndexSeriesRing sage: p = SymmetricFunctions(QQ).power() sage: CIS = CycleIndexSeriesRing(QQ) - -:: - - sage: geo1 = CIS((p([1])^i for i in _integers_from(0))) - sage: geo2 = CIS((p([2])^i for i in _integers_from(0))) - sage: s = geo1 * geo2 - sage: s[0] - p[] - sage: s[1] - p[1] + p[2] - sage: s[2] - p[1, 1] + p[2, 1] + p[2, 2] - sage: s[3] - p[1, 1, 1] + p[2, 1, 1] + p[2, 2, 1] + p[2, 2, 2] - -Whereas the coefficients of the above test are homogeneous with -respect to total degree, the following test groups with respect to -weighted degree where each variable x_i has weight i. - -:: - - sage: def g(): - ....: for i in _integers_from(0): - ....: yield p([2])^i - ....: yield p(0) - sage: geo1 = CIS((p([1])^i for i in _integers_from(0))) - sage: geo2 = CIS(g()) + sage: geo1 = CIS(lambda i: p([1])^i) + sage: geo2 = CIS(lambda i: p([2])^(i // 2) if is_even(i) else 0) sage: s = geo1 * geo2 sage: s[0] p[] @@ -55,8 +29,6 @@ p[1, 1] + p[2] sage: s[3] p[1, 1, 1] + p[2, 1] - sage: s[4] - p[1, 1, 1, 1] + p[2, 1, 1] + p[2, 2] REFERENCES: @@ -77,8 +49,8 @@ # https://www.gnu.org/licenses/ # **************************************************************************** -from .series import LazyPowerSeriesRing, LazyPowerSeries -from .stream import Stream, _integers_from +from sage.rings.lazy_series import LazyPowerSeries, LazySymmetricFunction +from sage.rings.lazy_series_ring import LazyPowerSeriesRing, LazySymmetricFunctions from sage.rings.integer import Integer from sage.rings.rational_field import RationalField from sage.arith.all import moebius, gcd, lcm, divisors @@ -89,55 +61,30 @@ from sage.arith.misc import factorial -@cached_function -def OrdinaryGeneratingSeriesRing(R): - """ - Return the ring of ordinary generating series over ``R``. +class OrdinaryGeneratingSeries(LazyPowerSeries): + r""" + A class for ordinary generating series. - Note that it is just a - :class:`LazyPowerSeriesRing` whose elements have - some extra methods. + Note that it is just a :class:`LazyPowerSeries` whose elements + have some extra methods. EXAMPLES:: sage: from sage.combinat.species.generating_series import OrdinaryGeneratingSeriesRing - sage: R = OrdinaryGeneratingSeriesRing(QQ); R - Lazy Power Series Ring over Rational Field - sage: R([1]).coefficients(4) - [1, 1, 1, 1] - sage: R([1]).counts(4) - [1, 1, 1, 1] + sage: R = OrdinaryGeneratingSeriesRing(QQ) + sage: f = R(lambda n: n) + sage: f + z + 2*z^2 + 3*z^3 + 4*z^4 + 5*z^5 + 6*z^6 + O(z^7) - TESTS: - - We test to make sure that caching works. - - :: - - sage: R is OrdinaryGeneratingSeriesRing(QQ) - True """ - return OrdinaryGeneratingSeriesRing_class(R) - - -class OrdinaryGeneratingSeriesRing_class(LazyPowerSeriesRing): - def __init__(self, R): - """ - EXAMPLES:: - - sage: from sage.combinat.species.generating_series import OrdinaryGeneratingSeriesRing - sage: R = OrdinaryGeneratingSeriesRing(QQ) - sage: R == loads(dumps(R)) - True - """ - LazyPowerSeriesRing.__init__(self, R, element_class=OrdinaryGeneratingSeries) - - -class OrdinaryGeneratingSeries(LazyPowerSeries): def count(self, n): """ Return the number of structures on a set of size ``n``. + INPUT: + + - ``n`` -- the size of the set + EXAMPLES:: sage: from sage.combinat.species.generating_series import OrdinaryGeneratingSeriesRing @@ -145,6 +92,7 @@ def count(self, n): sage: f = R(range(20)) sage: f.count(10) 10 + """ return self.coefficient(n) @@ -160,14 +108,14 @@ def counts(self, n): sage: f = R(range(20)) sage: f.counts(10) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + """ return [self.count(i) for i in range(n)] -@cached_function -def ExponentialGeneratingSeriesRing(R): - """ - Return the ring of exponential generating series over ``R``. +class OrdinaryGeneratingSeriesRing(LazyPowerSeriesRing): + r""" + Return the ring of ordinary generating series over ``R``. Note that it is just a :class:`LazyPowerSeriesRing` whose elements have @@ -175,39 +123,53 @@ def ExponentialGeneratingSeriesRing(R): EXAMPLES:: - sage: from sage.combinat.species.generating_series import ExponentialGeneratingSeriesRing - sage: R = ExponentialGeneratingSeriesRing(QQ); R - Lazy Power Series Ring over Rational Field - sage: R([1]).coefficients(4) + sage: from sage.combinat.species.generating_series import OrdinaryGeneratingSeriesRing + sage: R = OrdinaryGeneratingSeriesRing(QQ); R + Lazy Taylor Series Ring in z over Rational Field + sage: [R(lambda n: 1).coefficient(i) for i in range(4)] [1, 1, 1, 1] - sage: R([1]).counts(4) - [1, 1, 2, 6] + sage: R(lambda n: 1).counts(4) + [1, 1, 1, 1] + sage: R == loads(dumps(R)) + True TESTS: - We test to make sure that caching works. - - :: + We test to make sure that caching works.:: - sage: R is ExponentialGeneratingSeriesRing(QQ) + sage: R is OrdinaryGeneratingSeriesRing(QQ) True - """ - return ExponentialGeneratingSeriesRing_class(R) - -class ExponentialGeneratingSeriesRing_class(LazyPowerSeriesRing): - def __init__(self, R): + """ + def __init__(self, base_ring): """ - EXAMPLES:: + TESTS:: - sage: from sage.combinat.species.generating_series import ExponentialGeneratingSeriesRing - sage: R = ExponentialGeneratingSeriesRing(QQ) - sage: R == loads(dumps(R)) - True + self: R = OrdinaryGeneratingSeriesRing(QQ); R + Lazy Taylor Series Ring in z over Rational Field """ - LazyPowerSeriesRing.__init__(self, R, element_class=ExponentialGeneratingSeries) + super().__init__(base_ring, names="z") + + Element = OrdinaryGeneratingSeries + class ExponentialGeneratingSeries(LazyPowerSeries): + r""" + A class for ordinary generating series. + + Note that it is just a + :class:`LazyPowerSeries` whose elements have + some extra methods. + + EXAMPLES:: + + sage: from sage.combinat.species.generating_series import OrdinaryGeneratingSeriesRing + sage: R = OrdinaryGeneratingSeriesRing(QQ) + sage: f = R(lambda n: n) + sage: f + z + 2*z^2 + 3*z^3 + 4*z^4 + 5*z^5 + 6*z^6 + O(z^7) + + """ def count(self, n): """ Return the number of structures of size ``n``. @@ -216,9 +178,10 @@ def count(self, n): sage: from sage.combinat.species.generating_series import ExponentialGeneratingSeriesRing sage: R = ExponentialGeneratingSeriesRing(QQ) - sage: f = R([1]) + sage: f = R(lambda n: 1) sage: [f.count(i) for i in range(7)] [1, 1, 2, 6, 24, 120, 720] + """ return factorial(n) * self.coefficient(n) @@ -234,6 +197,7 @@ def counts(self, n): sage: f = R(range(20)) sage: f.counts(5) [0, 1, 4, 18, 96] + """ return [self.count(i) for i in range(n)] @@ -258,17 +222,8 @@ def functorial_composition(self, y): sage: G = species.SimpleGraphSpecies() sage: g = G.generating_series() - sage: g.coefficients(10) + sage: [g.coefficient(i) for i in range(10)] [1, 1, 1, 4/3, 8/3, 128/15, 2048/45, 131072/315, 2097152/315, 536870912/2835] - """ - return self._new(partial(self._functorial_compose_gen, y), lambda a,b: 0, self, y) - - def _functorial_compose_gen(self, y, ao): - """ - Returns a generator for the coefficients of the functorial - composition of self with y. - - EXAMPLES:: sage: E = species.SetSpecies() sage: E2 = E.restricted(min=2, max=3) @@ -276,117 +231,52 @@ def _functorial_compose_gen(self, y, ao): sage: P2 = E2*E sage: g1 = WP.generating_series() sage: g2 = P2.generating_series() - sage: g = g1._functorial_compose_gen(g2, 0) - sage: [next(g) for i in range(10)] + sage: g1.functorial_composition(g2)[:10] [1, 1, 1, 4/3, 8/3, 128/15, 2048/45, 131072/315, 2097152/315, 536870912/2835] - """ - n = 0 - while True: - yield self.count(y.count(n)) / factorial(n) - n += 1 - - -def factorial_gen(): - """ - A generator for the factorials starting at 0. - - EXAMPLES:: - - sage: from sage.combinat.species.generating_series import factorial_gen - sage: g = factorial_gen() - sage: [next(g) for i in range(5)] - [1, 1, 2, 6, 24] - """ - z = Integer(1) - yield z - yield z - n = Integer(2) - while True: - z *= n - yield z - n += 1 + """ + P = self.parent() + return P(lambda n: self.count(y.count(n)) / factorial(n), 0) -@cached_function -def CycleIndexSeriesRing(R): +class ExponentialGeneratingSeriesRing(LazyPowerSeriesRing): r""" - Return the ring of cycle index series over ``R``. - - This is the ring of formal power series `\Lambda[x]`, where - `\Lambda` is the ring of symmetric functions over ``R`` in the - `p`-basis. Its purpose is to house the cycle index series of - species (in a somewhat nonstandard notation tailored to Sage): - If `F` is a species, then the *cycle index series* of `F` is - defined to be the formal power series - - .. MATH:: - - \sum_{n \geq 0} \frac{1}{n!} (\sum_{\sigma \in S_n} - \operatorname{fix} F[\sigma] - \prod_{z \text{ is a cycle of } \sigma} - p_{\text{length of } z}) x^n - \in \Lambda_\QQ [x], - - where `\operatorname{fix} F[\sigma]` denotes the number of - fixed points of the permutation `F[\sigma]` of `F[n]`. We - notice that this power series is "equigraded" (meaning that - its `x^n`-coefficient is homogeneous of degree `n`). A more - standard convention in combinatorics would be to use - `x_i` instead of `p_i`, and drop the `x` (that is, evaluate - the above power series at `x = 1`); but this would be more - difficult to implement in Sage, as it would be an element - of a power series ring in infinitely many variables. + Return the ring of exponential generating series over ``R``. - Note that it is just a :class:`LazyPowerSeriesRing` (whose base - ring is `\Lambda`) whose elements have some extra methods. + Note that it is just a + :class:`LazyPowerSeriesRing` whose elements have + some extra methods. EXAMPLES:: - sage: from sage.combinat.species.generating_series import CycleIndexSeriesRing - sage: R = CycleIndexSeriesRing(QQ); R - Cycle Index Series Ring over Symmetric Functions over Rational Field in the powersum basis - sage: R([1]).coefficients(4) # This is not combinatorially - ....: # meaningful. + sage: from sage.combinat.species.generating_series import ExponentialGeneratingSeriesRing + sage: R = ExponentialGeneratingSeriesRing(QQ); R + Lazy Taylor Series Ring in z over Rational Field + sage: [R(lambda n: 1).coefficient(i) for i in range(4)] [1, 1, 1, 1] + sage: R(lambda n: 1).counts(4) + [1, 1, 2, 6] TESTS: - We test to make sure that caching works. + We test to make sure that caching works:: - :: - - sage: R is CycleIndexSeriesRing(QQ) + sage: R is ExponentialGeneratingSeriesRing(QQ) True - """ - return CycleIndexSeriesRing_class(R) - -class CycleIndexSeriesRing_class(LazyPowerSeriesRing): - def __init__(self, R): - """ - EXAMPLES:: - - sage: from sage.combinat.species.generating_series import CycleIndexSeriesRing - sage: R = CycleIndexSeriesRing(QQ); R - Cycle Index Series Ring over Symmetric Functions over Rational Field in the powersum basis - sage: R == loads(dumps(R)) - True + """ + def __init__(self, base_ring): """ - R = SymmetricFunctions(R).power() - LazyPowerSeriesRing.__init__(self, R, element_class=CycleIndexSeries) + TESTS:: - def __repr__(self): + self: R = ExponentialGeneratingSeriesRing(QQ); R + Lazy Taylor Series Ring in z over Rational Field """ - EXAMPLES:: + super().__init__(base_ring, names="z") - sage: from sage.combinat.species.generating_series import CycleIndexSeriesRing - sage: CycleIndexSeriesRing(QQ) - Cycle Index Series Ring over Symmetric Functions over Rational Field in the powersum basis - """ - return "Cycle Index Series Ring over %s" % self.base_ring() + Element = ExponentialGeneratingSeries -class CycleIndexSeries(LazyPowerSeries): +class CycleIndexSeries(LazySymmetricFunction): def count(self, t): """ Return the number of structures corresponding to a certain cycle @@ -429,63 +319,6 @@ def coefficient_cycle_type(self, t): p = self.coefficient(t.size()) return p.coefficient(t) - - def stretch(self, k): - r""" - Return the stretch of the cycle index series ``self`` by a positive - integer `k`. - - If - - .. MATH:: - - f = \sum_{n=0}^{\infty} f_n(p_1, p_2, p_3, \ldots ), - - then the stretch `g` of `f` by `k` is - - .. MATH:: - - g = \sum_{n=0}^{\infty} f_n(p_k, p_{2k}, p_{3k}, \ldots ). - - EXAMPLES:: - - sage: from sage.combinat.species.generating_series import CycleIndexSeriesRing - sage: p = SymmetricFunctions(QQ).power() - sage: CIS = CycleIndexSeriesRing(QQ) - sage: f = CIS([p([]), p([1]), p([2]), p.zero()]) - sage: f.stretch(3).coefficients(10) - [p[], 0, 0, p[3], 0, 0, p[6], 0, 0, 0] - """ - return self._new(partial(self._stretch_gen, k), lambda ao: k*ao, self) - - def _stretch_gen(self, k, ao): - """ - EXAMPLES:: - - sage: from sage.combinat.species.generating_series import CycleIndexSeriesRing - sage: p = SymmetricFunctions(QQ).power() - sage: CIS = CycleIndexSeriesRing(QQ) - sage: f = CIS([p([1])]) # This is the power series whose all coefficients - ....: # are p[1]. Not combinatorially meaningful! - sage: g = f._stretch_gen(2,0) - sage: [next(g) for i in range(10)] - [p[2], 0, p[2], 0, p[2], 0, p[2], 0, p[2], 0] - """ - from sage.combinat.partition import Partition - BR = self.base_ring() - zero = BR.zero() - - stretch_k = lambda p: Partition([k*i for i in p]) - - yield self.coefficient(0).map_support(stretch_k) - - n = 1 - while True: - for i in range(k-1): - yield zero - yield self.coefficient(n).map_support(stretch_k) - n += 1 - def isotype_generating_series(self): """ EXAMPLES:: @@ -493,48 +326,14 @@ def isotype_generating_series(self): sage: P = species.PermutationSpecies() sage: cis = P.cycle_index_series() sage: f = cis.isotype_generating_series() - sage: f.coefficients(10) + sage: f[:10] [1, 1, 2, 3, 5, 7, 11, 15, 22, 30] """ - R = self.base_ring().base_ring() - OGS = OrdinaryGeneratingSeriesRing(R)() - return OGS._new(self._ogs_gen, lambda ao: ao, self) - - def expand_as_sf(self, n, alphabet='x'): - """ - Returns the expansion of a cycle index series as a symmetric function in - ``n`` variables. - - Specifically, this returns a :class:`~sage.combinat.species.series.LazyPowerSeries` whose - ith term is obtained by calling :meth:`~sage.combinat.sf.sfa.SymmetricFunctionAlgebra_generic_Element.expand` - on the ith term of ``self``. - - This relies on the (standard) interpretation of a cycle index series as a symmetric function - in the power sum basis. - - INPUT: - - - ``self`` -- a cycle index series - - - ``n`` -- a positive integer - - - ``alphabet`` -- a variable for the expansion (default: `x`) - - EXAMPLES:: - - sage: from sage.combinat.species.set_species import SetSpecies - sage: SetSpecies().cycle_index_series().expand_as_sf(2).coefficients(4) - [1, x0 + x1, x0^2 + x0*x1 + x1^2, x0^3 + x0^2*x1 + x0*x1^2 + x1^3] + R = self.base_ring() + OGS = OrdinaryGeneratingSeriesRing(R) + return OGS(lambda n: self._ogs_gen(n, self._coeff_stream._approximate_order), self._coeff_stream._approximate_order) - """ - expanded_poly_ring = self.coefficient(0).expand(n, alphabet).parent() - LPSR = LazyPowerSeriesRing(expanded_poly_ring) - - expander_gen = (LPSR.term(self.coefficient(i).expand(n, alphabet), i) for i in _integers_from(0)) - - return LPSR.sum_generator(expander_gen) - - def _ogs_gen(self, ao): + def _ogs_gen(self, n, ao): """ Returns a generator for the coefficients of the ordinary generating series obtained from a cycle index series. @@ -543,14 +342,12 @@ def _ogs_gen(self, ao): sage: P = species.PermutationSpecies() sage: cis = P.cycle_index_series() - sage: g = cis._ogs_gen(0) - sage: [next(g) for i in range(10)] + sage: [cis._ogs_gen(i, 0) for i in range(10)] [1, 1, 2, 3, 5, 7, 11, 15, 22, 30] """ - for i in range(ao): - yield 0 - for i in _integers_from(ao): - yield sum( self.coefficient(i).coefficients() ) + if n < ao: + return 0 + return sum(self.coefficient(n).coefficients()) def generating_series(self): """ @@ -559,14 +356,14 @@ def generating_series(self): sage: P = species.PartitionSpecies() sage: cis = P.cycle_index_series() sage: f = cis.generating_series() - sage: f.coefficients(5) + sage: f[:5] [1, 1, 1, 5/6, 5/8] """ - R = self.base_ring().base_ring() - EGS = ExponentialGeneratingSeriesRing(R)() - return EGS._new(self._egs_gen, lambda ao: ao, self) + R = self.base_ring() + EGS = ExponentialGeneratingSeriesRing(R) + return EGS(lambda n: self._egs_gen(n, self._coeff_stream._approximate_order), self._coeff_stream._approximate_order) - def _egs_gen(self, ao): + def _egs_gen(self, n, ao): """ Returns a generator for the coefficients of the exponential generating series obtained from a cycle index series. @@ -575,522 +372,16 @@ def _egs_gen(self, ao): sage: P = species.PermutationSpecies() sage: cis = P.cycle_index_series() - sage: g = cis._egs_gen(0) - sage: [next(g) for i in range(10)] + sage: [cis._egs_gen(i, 0) for i in range(10)] [1, 1, 1, 1, 1, 1, 1, 1, 1, 1] """ - for i in range(ao): - yield 0 - for i in _integers_from(ao): - yield self.coefficient(i).coefficient([1]*i) - - def __invert__(self): - r""" - Return the multiplicative inverse of ``self``. - - This algorithm is derived from [BLL]_. - - EXAMPLES:: - - sage: E = species.SetSpecies().cycle_index_series() - sage: E.__invert__().coefficients(4) - [p[], -p[1], 1/2*p[1, 1] - 1/2*p[2], -1/6*p[1, 1, 1] + 1/2*p[2, 1] - 1/3*p[3]] - - The defining characteristic of the multiplicative inverse `F^{-1}` of - a cycle index series `F` is that `F \cdot F^{-1} = F^{-1} \cdot F = 1` - (that is, both products with `F` yield the multiplicative identity `1`):: - - sage: E = species.SetSpecies().cycle_index_series() - sage: (E * ~E).coefficients(6) - [p[], 0, 0, 0, 0, 0] - - REFERENCES: - - - [BLL]_ - - [BLL-Intro]_ - - http://bergeron.math.uqam.ca/Site/bergeron_anglais_files/livre_combinatoire.pdf - - AUTHORS: - - - Andrew Gainer-Dewar - """ - if self.coefficient(0) == 0: - raise ValueError("constant term must be non-zero") - - def multinv_builder(i): - return self.coefficient(0)**(-i-1) * (self.coefficient(0) + (-1)*self)**i - - return self.parent().sum_generator(multinv_builder(i) for i in _integers_from(0)) - - def _div_(self, y): - """ - TESTS:: + if n < ao: + return 0 + return self.coefficient(n).coefficient([1]*n) - sage: E = species.SetSpecies().cycle_index_series() - sage: (E / E).coefficients(6) - [p[], 0, 0, 0, 0, 0] - """ - return self*(~y) - - def functorial_composition(self, g): + def derivative(self, n=1): r""" - Returns the functorial composition of ``self`` and ``g``. - - If `F` and `G` are species, their functorial composition is the species - `F \Box G` obtained by setting `(F \Box G) [A] = F[ G[A] ]`. - In other words, an `(F \Box G)`-structure on a set `A` of labels is an - `F`-structure whose labels are the set of all `G`-structures on `A`. - - It can be shown (as in section 2.2 of [BLL]_) that there is a - corresponding operation on cycle indices: - - .. MATH:: - - Z_{F} \Box Z_{G} = \sum_{n \geq 0} \frac{1}{n!} - \sum_{\sigma \in \mathfrak{S}_{n}} - \operatorname{fix} F[ (G[\sigma])_{1}, (G[\sigma])_{2}, \ldots ] - \, p_{1}^{\sigma_{1}} p_{2}^{\sigma_{2}} \cdots. - - This method implements that operation on cycle index series. - - EXAMPLES: - - The species `G` of simple graphs can be expressed in terms of a functorial - composition: `G = \mathfrak{p} \Box \mathfrak{p}_{2}`, where - `\mathfrak{p}` is the :class:`~sage.combinat.species.subset_species.SubsetSpecies`. - This is how it is implemented in - :meth:`~sage.combinat.species.library.SimpleGraphSpecies`:: - - sage: S = species.SimpleGraphSpecies() - sage: S.cycle_index_series().coefficients(5) - [p[], - p[1], - p[1, 1] + p[2], - 4/3*p[1, 1, 1] + 2*p[2, 1] + 2/3*p[3], - 8/3*p[1, 1, 1, 1] + 4*p[2, 1, 1] + 2*p[2, 2] + 4/3*p[3, 1] + p[4]] - """ - return self._new(partial(self._functorial_compose_gen, g), lambda a,b: 0, self, g) - - def _functorial_compose_gen(self, g, ao): - """ - Return a generator for the coefficients of the functorial - composition of ``self`` with ``g``. - - EXAMPLES:: - - sage: E = species.SetSpecies() - sage: E2 = species.SetSpecies(size=2) - sage: WP = species.SubsetSpecies() - sage: P2 = E2*E - sage: P2_cis = P2.cycle_index_series() - sage: WP_cis = WP.cycle_index_series() - sage: g = WP_cis._functorial_compose_gen(P2_cis,0) - sage: [next(g) for i in range(5)] - [p[], - p[1], - p[1, 1] + p[2], - 4/3*p[1, 1, 1] + 2*p[2, 1] + 2/3*p[3], - 8/3*p[1, 1, 1, 1] + 4*p[2, 1, 1] + 2*p[2, 2] + 4/3*p[3, 1] + p[4]] - """ - p = self.parent().base_ring() - n = 0 - while True: - res = p(0) - for s in Partitions(n): - t = g._cycle_type(s) - q = self.count(t) / s.aut() - res += q*p(s) - yield res - n += 1 - - def arithmetic_product(self, g, check_input = True): - r""" - Return the arithmetic product of ``self`` with ``g``. - - For species `M` and `N` such that `M[\\varnothing] = N[\\varnothing] = \\varnothing`, - their arithmetic product is the species `M \\boxdot N` of "`M`-assemblies of cloned `N`-structures". - This operation is defined and several examples are given in [MM]_. - - The cycle index series for `M \\boxdot N` can be computed in terms of the component series `Z_M` and `Z_N`, - as implemented in this method. - - INPUT: - - - ``g`` -- a cycle index series having the same parent as ``self``. - - - ``check_input`` -- (default: ``True``) a Boolean which, when set - to ``False``, will cause input checks to be skipped. - - OUTPUT: - - The arithmetic product of ``self`` with ``g``. This is a cycle - index series defined in terms of ``self`` and ``g`` such that - if ``self`` and ``g`` are the cycle index series of two species - `M` and `N`, their arithmetic product is the cycle index series - of the species `M \\boxdot N`. - - EXAMPLES: - - For `C` the species of (oriented) cycles and `L_{+}` the species of nonempty linear orders, `C \\boxdot L_{+}` corresponds - to the species of "regular octopuses"; a `(C \\boxdot L_{+})`-structure is a cycle of some length, each of whose elements - is an ordered list of a length which is consistent for all the lists in the structure. :: - - sage: C = species.CycleSpecies().cycle_index_series() - sage: Lplus = species.LinearOrderSpecies(min=1).cycle_index_series() - sage: RegularOctopuses = C.arithmetic_product(Lplus) - sage: RegOctSpeciesSeq = RegularOctopuses.generating_series().counts(8) - sage: RegOctSpeciesSeq - [0, 1, 3, 8, 42, 144, 1440, 5760] - - It is shown in [MM]_ that the exponential generating function for regular octopuses satisfies - `(C \\boxdot L_{+}) (x) = \\sum_{n \geq 1} \\sigma (n) (n - 1)! \\frac{x^{n}}{n!}` (where `\\sigma (n)` is - the sum of the divisors of `n`). :: - - sage: RegOctDirectSeq = [0] + [sum(divisors(i))*factorial(i-1) for i in range(1,8)] - sage: RegOctDirectSeq == RegOctSpeciesSeq - True - - AUTHORS: - - - Andrew Gainer-Dewar (2013) - - REFERENCES: - - .. [MM] \M. Maia and M. Mendez. "On the arithmetic product of combinatorial species". - Discrete Mathematics, vol. 308, issue 23, 2008, pp. 5407-5427. - :arxiv:`math/0503436v2`. - - """ - from itertools import product, repeat, chain - - p = self.base_ring() - - if check_input: - assert self.coefficient(0) == p.zero() - assert g.coefficient(0) == p.zero() - - # We first define an operation `\\boxtimes` on partitions as in Lemma 2.1 of [MM]_. - def arith_prod_of_partitions(l1, l2): - # Given two partitions `l_1` and `l_2`, we construct a new partition `l_1 \\boxtimes l_2` by - # the following procedure: each pair of parts `a \\in l_1` and `b \\in l_2` contributes - # `\\gcd (a, b)`` parts of size `\\lcm (a, b)` to `l_1 \\boxtimes l_2`. If `l_1` and `l_2` - # are partitions of integers `n` and `m`, respectively, then `l_1 \\boxtimes l_2` is a - # partition of `nm`. - term_iterable = chain.from_iterable(repeat(lcm(pair), gcd(pair)) - for pair in product(l1, l2)) - return Partition(sorted(term_iterable, reverse=True)) - - # We then extend this to an operation on symmetric functions as per eq. (52) of [MM]_. - # (Maia and Mendez, in [MM]_, are talking about polynomials instead of symmetric - # functions, but this boils down to the same: Their x_i corresponds to the i-th power - # sum symmetric function.) - def arith_prod_sf(x, y): - ap_sf_wrapper = lambda l1, l2: p(arith_prod_of_partitions(l1, l2)) - return p._apply_multi_module_morphism(x, y, ap_sf_wrapper) - - # Sage stores cycle index series by degree. - # Thus, to compute the arithmetic product `Z_M \\boxdot Z_N` it is useful - # to compute all terms of a given degree `n` at once. - def arith_prod_coeff(n): - if n == 0: - res = p.zero() - else: - index_set = ((d, n // d) for d in divisors(n)) - res = sum(arith_prod_sf(self.coefficient(i), g.coefficient(j)) for i,j in index_set) - - # Build a list which has res in the `n`th slot and 0's before and after - # to feed to sum_generator - res_in_seq = [p.zero()]*n + [res, p.zero()] - - return self.parent(res_in_seq) - - # Finally, we use the sum_generator method to assemble these results into a single - # LazyPowerSeries object. - return self.parent().sum_generator(arith_prod_coeff(n) for n in _integers_from(0)) - - def _cycle_type(self, s): - """ - EXAMPLES:: - - sage: cis = species.PartitionSpecies().cycle_index_series() - sage: [cis._cycle_type(p) for p in Partitions(3)] - [[3, 1, 1], [2, 1, 1, 1], [1, 1, 1, 1, 1]] - sage: cis = species.PermutationSpecies().cycle_index_series() - sage: [cis._cycle_type(p) for p in Partitions(3)] - [[3, 1, 1, 1], [2, 2, 1, 1], [1, 1, 1, 1, 1, 1]] - sage: cis = species.SetSpecies().cycle_index_series() - sage: [cis._cycle_type(p) for p in Partitions(3)] - [[1], [1], [1]] - """ - if s == []: - return self._card(0) - res = [] - for k in range(1, self._upper_bound_for_longest_cycle(s)+1): - e = 0 - for d in divisors(k): - m = moebius(d) - if m == 0: - continue - u = s.power(k/d) - e += m*self.count(u) - res.extend([k]*int(e/k)) - res.reverse() - return Partition(res) - - - def _upper_bound_for_longest_cycle(self, s): - """ - EXAMPLES:: - - sage: cis = species.PartitionSpecies().cycle_index_series() - sage: cis._upper_bound_for_longest_cycle([4]) - 4 - sage: cis._upper_bound_for_longest_cycle([3,1]) - 3 - sage: cis._upper_bound_for_longest_cycle([2,2]) - 2 - sage: cis._upper_bound_for_longest_cycle([2,1,1]) - 2 - sage: cis._upper_bound_for_longest_cycle([1,1,1,1]) - 1 - """ - if s == []: - return 1 - return min(self._card(sum(s)), lcm(list(s))) - - def _card(self, n): - r""" - Return the number of structures on an underlying set of size ``n`` for - the species associated with ``self``. - - This is just ``n!`` times the coefficient of ``p[1]n`` in ``self``. - - EXAMPLES:: - - sage: cis = species.PartitionSpecies().cycle_index_series() - sage: cis._card(4) - 15 - """ - p = self.coefficient(n) - return factorial(n) * p.coefficient([1] * n) - - def _compose_gen(self, y, ao): - """ - Return a generator for the coefficients of the composition of this - cycle index series and the cycle index series ``y``. This overrides - the method defined in ``LazyPowerSeries``. - - The notion "composition" means plethystic substitution here, as - defined in Section 2.2 of [BLL-Intro]_. - - EXAMPLES:: - - sage: E = species.SetSpecies(); C = species.CycleSpecies() - sage: E_cis = E.cycle_index_series() - sage: g = E_cis._compose_gen(C.cycle_index_series(),0) - sage: [next(g) for i in range(4)] - [p[], p[1], p[1, 1] + p[2], p[1, 1, 1] + p[2, 1] + p[3]] - """ - assert y.coefficient(0) == 0 - y_powers = Stream(y._power_gen()) - - parent = self.parent() - res = parent.sum_generator(self._compose_term(self.coefficient(i), y_powers) - for i in _integers_from(0)) - - for i in _integers_from(0): - yield res.coefficient(i) - - def _compose_term(self, p, y_powers): - r""" - Return the composition of one term in ``self`` with `y`. - - INPUT: - - - ``p`` - a term in ``self`` - - ``y_powers`` - a stream for the powers of `y` - starting with `y` - - EXAMPLES:: - - sage: from sage.combinat.species.stream import Stream - sage: E = species.SetSpecies(); C = species.CycleSpecies() - sage: E_cis = E.cycle_index_series() - sage: C_cis = C.cycle_index_series() - sage: c_powers = Stream(C_cis._power_gen()) - sage: p2 = E_cis.coefficient(2); p2 - 1/2*p[1, 1] + 1/2*p[2] - sage: E_cis._compose_term(p2, c_powers).coefficients(4) - [0, 0, 1/2*p[1, 1] + 1/2*p[2], 1/2*p[1, 1, 1] + 1/2*p[2, 1]] - """ - parent = self.parent() - if p == 0: - return parent(0) - - res = [] - #Go through all the partition, coefficient pairs in the term p - for m, c in p: - res_t = parent.term(c, 0) - - for e,v in enumerate(m.to_exp()): - if v == 0: - continue - res_t = res_t * y_powers[v-1].stretch(e+1) - res.append(res_t) - - return parent.sum(res) - - def weighted_composition(self, y_species): - r""" - Return the composition of this cycle index series with the cycle - index series of the weighted species ``y_species``. - - Note that this is basically the same algorithm as composition - except we can not use the optimization that the powering of cycle - index series commutes with 'stretching'. - - EXAMPLES:: - - sage: E = species.SetSpecies(); C = species.CycleSpecies() - sage: E_cis = E.cycle_index_series() - sage: E_cis.weighted_composition(C).coefficients(4) - [p[], p[1], p[1, 1] + p[2], p[1, 1, 1] + p[2, 1] + p[3]] - sage: E(C).cycle_index_series().coefficients(4) - [p[], p[1], p[1, 1] + p[2], p[1, 1, 1] + p[2, 1] + p[3]] - """ - base_ring = self.base_ring() - y = y_species.cycle_index_series(base_ring) - assert y.coefficient(0) == 0 - return self._new(partial(self._weighted_compose_gen, y_species), lambda a,b:a*b, self, y) - - - def _weighted_compose_gen(self, y_species, ao): - r""" - Return an iterator for the composition of this cycle index series - and the cycle index series of the weighted species ``y_species``. - - EXAMPLES:: - - sage: E = species.SetSpecies(); C = species.CycleSpecies() - sage: E_cis = E.cycle_index_series() - sage: g = E_cis._weighted_compose_gen(C,0) - sage: [next(g) for i in range(4)] - [p[], p[1], p[1, 1] + p[2], p[1, 1, 1] + p[2, 1] + p[3]] - """ - parent = self.parent() - res = parent.sum_generator(self._weighted_compose_term(self.coefficient(i), y_species) - for i in _integers_from(0)) - - for i in _integers_from(0): - yield res.coefficient(i) - - def _weighted_compose_term(self, p, y_species): - r""" - Return the weighted composition of one term in ``self`` with ``y``. - - INPUT: - - - ``p`` -- a term in ``self`` - - ``y_species`` -- a species - - EXAMPLES:: - - sage: E = species.SetSpecies(); C = species.CycleSpecies() - sage: E_cis = E.cycle_index_series() - sage: p2 = E_cis.coefficient(2); p2 - 1/2*p[1, 1] + 1/2*p[2] - sage: E_cis._weighted_compose_term(p2, C).coefficients(4) - [0, 0, 1/2*p[1, 1] + 1/2*p[2], 1/2*p[1, 1, 1] + 1/2*p[2, 1]] - """ - parent = self.parent() - if p == 0: - return parent(0) - - base_ring = self.base_ring().base_ring() - - res = [] - #Go through all the partition, coefficient pairs in the term p - for m, c in p: - res_t = parent.term(c, 0) - - for e,v in enumerate(m.to_exp()): - if v == 0: - continue - res_t = res_t * (y_species.weighted(y_species._weight**(e+1)).cycle_index_series(base_ring)**v).stretch(e+1) - res.append(res_t) - - return parent.sum(res) - - def compositional_inverse(self): - r""" - Return the compositional inverse of ``self`` if possible. - - (Specifically, if ``self`` is of the form `0 + p_{1} + \cdots`.) - - The compositional inverse is the inverse with respect to - plethystic substitution. This is the operation on cycle index - series which corresponds to substitution, a.k.a. partitional - composition, on the level of species. See Section 2.2 of - [BLL]_ for a definition of this operation. - - EXAMPLES:: - - sage: Eplus = species.SetSpecies(min=1).cycle_index_series() - sage: Eplus(Eplus.compositional_inverse()).coefficients(8) - [0, p[1], 0, 0, 0, 0, 0, 0] - - TESTS:: - - sage: Eplus = species.SetSpecies(min=2).cycle_index_series() - sage: Eplus.compositional_inverse() - Traceback (most recent call last): - ... - ValueError: not an invertible series - - ALGORITHM: - - Let `F` be a species satisfying `F = 0 + X + F_2 + F_3 + \cdots` for - `X` the species of singletons. (Equivalently, `\lvert F[\varnothing] - \rvert = 0` and `\lvert F[\{1\}] \rvert = 1`.) Then there exists a - (virtual) species `G` satisfying `F \circ G = G \circ F = X`. - - It follows that `(F - X) \circ G = F \circ G - X \circ G = X - G`. - Rearranging, we obtain the recursive equation `G = X - (F - X) \circ G`, - which can be solved using iterative methods. - - .. WARNING:: - - This algorithm is functional but can be very slow. - Use with caution! - - .. SEEALSO:: - - The compositional inverse `\Omega` of the species `E_{+}` - of nonempty sets can be handled much more efficiently - using specialized methods. See - :func:`~sage.combinat.species.generating_series.LogarithmCycleIndexSeries` - - AUTHORS: - - - Andrew Gainer-Dewar - """ - cisr = self.parent() - sfa = cisr._base - - X = cisr([0, sfa([1]), 0]) - - if self.coefficients(2) != X.coefficients(2): - raise ValueError('not an invertible series') - - res = cisr() - res.define(X - (self - X).compose(res)) - - return res - - def derivative(self, order=1): - r""" - Return the species-theoretic `n`-th derivative of ``self``, - where `n` is ``order``. + Return the species-theoretic `n`-th derivative of ``self``. For a cycle index series `F (p_{1}, p_{2}, p_{3}, \ldots)`, its derivative is the cycle index series `F' = D_{p_{1}} F` (that is, @@ -1105,7 +396,7 @@ def derivative(self, order=1): The species `E` of sets satisfies the relationship `E' = E`:: sage: E = species.SetSpecies().cycle_index_series() - sage: E.coefficients(8) == E.derivative().coefficients(8) + sage: E[:8] == E.derivative()[:8] True The species `C` of cyclic orderings and the species `L` of linear @@ -1113,25 +404,10 @@ def derivative(self, order=1): sage: C = species.CycleSpecies().cycle_index_series() sage: L = species.LinearOrderSpecies().cycle_index_series() - sage: L.coefficients(8) == C.derivative().coefficients(8) + sage: L[:8] == C.derivative()[:8] True """ - # Make sure that order is integral - order = Integer(order) - - if order < 0: - raise ValueError("Order must be a non-negative integer") - - elif order == 0: - return self - - elif order == 1: - parent = self.parent() - derivative_term = lambda n: parent.term(self.coefficient(n+1).derivative_with_respect_to_p1(), n) - return parent.sum_generator(derivative_term(i) for i in _integers_from(0)) - - else: - return self.derivative(order-1) + return self.derivative_with_respect_to_p1(n=n) def pointing(self): r""" @@ -1151,42 +427,12 @@ def pointing(self): sage: E = species.SetSpecies().cycle_index_series() sage: X = species.SingletonSpecies().cycle_index_series() - sage: E.pointing().coefficients(8) == (X*E).coefficients(8) + sage: E.pointing()[:8] == (X*E)[:8] True """ - p1 = self.base_ring()([1]) - X = self.parent()([0, p1, 0]) - - return X*self.derivative() - - def integral(self, *args): - r""" - Given a cycle index `G`, it is not in general possible to recover a - single cycle index `F` such that `F' = G` (even up to addition of a - constant term). - - More broadly, it may be the case that there are many non-isomorphic - species `S` such that `S' = T` for a given species `T`. - For example, the species `3 C_{3}` of 3-cycles from three distinct - classes and the species `X^{3}` of 3-sets are not isomorphic, but - `(3 C_{3})' = (X^{3})' = 3 X^{2}`. - - EXAMPLES:: - - sage: C3 = species.CycleSpecies(size=3).cycle_index_series() - sage: X = species.SingletonSpecies().cycle_index_series() - sage: (3*C3).derivative().coefficients(8) == (3*X^2).coefficients(8) - True - sage: (X^3).derivative().coefficients(8) == (3*X^2).coefficients(8) - True - - .. WARNING:: - - This method has no implementation and exists only to prevent you from - doing something strange. Calling it raises a ``NotImplementedError``! - """ - raise NotImplementedError + X = self.parent()([1], valuation=1) + return X*self.derivative_with_respect_to_p1() def exponential(self): r""" @@ -1206,12 +452,12 @@ def exponential(self): sage: BT = species.BinaryTreeSpecies().cycle_index_series() sage: BF = species.BinaryForestSpecies().cycle_index_series() - sage: BT.exponential().isotype_generating_series().coefficients(8) == BF.isotype_generating_series().coefficients(8) + sage: BT.exponential().isotype_generating_series()[:8] == BF.isotype_generating_series()[:8] True """ base_ring = self.parent().base_ring().base_ring() E = ExponentialCycleIndexSeries(base_ring) - return E.compose(self) + return E(self) def logarithm(self): r""" @@ -1233,13 +479,82 @@ def logarithm(self): sage: G = species.SimpleGraphSpecies().cycle_index_series() - 1 sage: from sage.combinat.species.generating_series import LogarithmCycleIndexSeries - sage: CG = LogarithmCycleIndexSeries().compose(G) - sage: CG.isotype_generating_series().coefficients(8) + sage: CG = LogarithmCycleIndexSeries()(G) + sage: CG.isotype_generating_series()[:8] [0, 1, 1, 2, 6, 21, 112, 853] """ base_ring = self.parent().base_ring().base_ring() Omega = LogarithmCycleIndexSeries(base_ring) - return Omega.compose(self) + return Omega(self) + + +class CycleIndexSeriesRing(LazySymmetricFunctions): + r""" + Return the ring of cycle index series over ``R``. + + This is the ring of formal power series `\Lambda[x]`, where + `\Lambda` is the ring of symmetric functions over ``R`` in the + `p`-basis. Its purpose is to house the cycle index series of + species (in a somewhat nonstandard notation tailored to Sage): + If `F` is a species, then the *cycle index series* of `F` is + defined to be the formal power series + + .. MATH:: + + \sum_{n \geq 0} \frac{1}{n!} (\sum_{\sigma \in S_n} + \operatorname{fix} F[\sigma] + \prod_{z \text{ is a cycle of } \sigma} + p_{\text{length of } z}) x^n + \in \Lambda_\QQ [x], + + where `\operatorname{fix} F[\sigma]` denotes the number of + fixed points of the permutation `F[\sigma]` of `F[n]`. We + notice that this power series is "equigraded" (meaning that + its `x^n`-coefficient is homogeneous of degree `n`). A more + standard convention in combinatorics would be to use + `x_i` instead of `p_i`, and drop the `x` (that is, evaluate + the above power series at `x = 1`); but this would be more + difficult to implement in Sage, as it would be an element + of a power series ring in infinitely many variables. + + Note that it is just a :class:`LazyPowerSeriesRing` (whose base + ring is `\Lambda`) whose elements have some extra methods. + + EXAMPLES:: + + sage: from sage.combinat.species.generating_series import CycleIndexSeriesRing + sage: R = CycleIndexSeriesRing(QQ); R + Cycle Index Series Ring over Rational Field + sage: p = SymmetricFunctions(QQ).p() + sage: R(lambda n: p[n]) + p[] + p[1] + p[2] + p[3] + p[4] + p[5] + p[6] + O^7 + + TESTS: + + We test to make sure that caching works. + + :: + + sage: R is CycleIndexSeriesRing(QQ) + True + + """ + Element = CycleIndexSeries + + def __init__(self, base_ring, sparse=True): + p = SymmetricFunctions(base_ring).power() + super().__init__(p) + + def __repr__(self): + """ + EXAMPLES:: + + sage: from sage.combinat.species.generating_series import CycleIndexSeriesRing + sage: CycleIndexSeriesRing(QQ) + Cycle Index Series Ring over Rational Field + """ + return "Cycle Index Series Ring over %s" % self.base_ring() + @cached_function def _exp_term(n, R = RationalField()): @@ -1256,20 +571,6 @@ def _exp_term(n, R = RationalField()): return sum(p(part) / part.aut() for part in Partitions(n)) -def _exp_gen(R = RationalField()): - r""" - Produce a generator which yields the terms of the cycle index - series of the species `E` of sets. - - EXAMPLES:: - - sage: from sage.combinat.species.generating_series import _exp_gen - sage: g = _exp_gen() - sage: [next(g) for i in range(4)] - [p[], p[1], 1/2*p[1, 1] + 1/2*p[2], 1/6*p[1, 1, 1] + 1/2*p[2, 1] + 1/3*p[3]] - """ - return (_exp_term(i, R) for i in _integers_from(0)) - @cached_function def ExponentialCycleIndexSeries(R = RationalField()): r""" @@ -1285,13 +586,13 @@ def ExponentialCycleIndexSeries(R = RationalField()): EXAMPLES:: sage: from sage.combinat.species.generating_series import ExponentialCycleIndexSeries - sage: ExponentialCycleIndexSeries().coefficients(5) + sage: ExponentialCycleIndexSeries()[:5] [p[], p[1], 1/2*p[1, 1] + 1/2*p[2], 1/6*p[1, 1, 1] + 1/2*p[2, 1] + 1/3*p[3], 1/24*p[1, 1, 1, 1] + 1/4*p[2, 1, 1] + 1/8*p[2, 2] + 1/3*p[3, 1] + 1/4*p[4]] """ CIS = CycleIndexSeriesRing(R) - return CIS(_exp_gen(R)) + return CIS(_exp_term) @cached_function @@ -1318,23 +619,6 @@ def _cl_term(n, R = RationalField()): return res - -def _cl_gen (R = RationalField()): - r""" - Produce a generator which yields the terms of the cycle index series - of the virtual species `\Omega`, the compositional inverse of the - species `E^{+}` of nonempty sets. - - EXAMPLES:: - - sage: from sage.combinat.species.generating_series import _cl_gen - sage: g = _cl_gen() - sage: [next(g) for i in range(4)] - [0, p[1], -1/2*p[1, 1] - 1/2*p[2], 1/3*p[1, 1, 1] - 1/3*p[3]] - """ - return (_cl_term(i, R) for i in _integers_from(0)) - - @cached_function def LogarithmCycleIndexSeries(R = RationalField()): r""" @@ -1351,7 +635,7 @@ def LogarithmCycleIndexSeries(R = RationalField()): its cycle index has negative coefficients:: sage: from sage.combinat.species.generating_series import LogarithmCycleIndexSeries - sage: LogarithmCycleIndexSeries().coefficients(4) + sage: LogarithmCycleIndexSeries()[:4] [0, p[1], -1/2*p[1, 1] - 1/2*p[2], 1/3*p[1, 1, 1] - 1/3*p[3]] Its defining property is that `\Omega \circ E^{+} = E^{+} \circ \Omega = X` @@ -1359,8 +643,8 @@ def LogarithmCycleIndexSeries(R = RationalField()): multiplicative identity `X`):: sage: Eplus = sage.combinat.species.set_species.SetSpecies(min=1).cycle_index_series() - sage: LogarithmCycleIndexSeries().compose(Eplus).coefficients(4) + sage: LogarithmCycleIndexSeries()(Eplus)[:4] [0, p[1], 0, 0] """ CIS = CycleIndexSeriesRing(R) - return CIS(_cl_gen(R)) + return CIS(_cl_term) diff --git a/src/sage/combinat/species/library.py b/src/sage/combinat/species/library.py index c107edbfa33..4b63b1a4730 100644 --- a/src/sage/combinat/species/library.py +++ b/src/sage/combinat/species/library.py @@ -42,13 +42,13 @@ def SimpleGraphSpecies(): sage: S = species.SimpleGraphSpecies() sage: S.generating_series().counts(10) [1, 1, 2, 8, 64, 1024, 32768, 2097152, 268435456, 68719476736] - sage: S.cycle_index_series().coefficients(5) + sage: S.cycle_index_series()[:5] [p[], p[1], p[1, 1] + p[2], 4/3*p[1, 1, 1] + 2*p[2, 1] + 2/3*p[3], 8/3*p[1, 1, 1, 1] + 4*p[2, 1, 1] + 2*p[2, 2] + 4/3*p[3, 1] + p[4]] - sage: S.isotype_generating_series().coefficients(6) + sage: S.isotype_generating_series()[:6] [1, 1, 2, 4, 11, 34] TESTS:: @@ -103,9 +103,9 @@ def BinaryTreeSpecies(): sage: oeis(seq)[0] # optional -- internet A000108: Catalan numbers: ... """ - B = CombinatorialSpecies() + B = CombinatorialSpecies(min=1) X = SingletonSpecies() - B.define(X+B*B) + B.define(X + B*B) return B @cached_function @@ -121,7 +121,7 @@ def BinaryForestSpecies(): [1, 1, 3, 19, 193, 2721, 49171, 1084483, 28245729, 848456353] sage: F.isotype_generating_series().counts(10) [1, 1, 2, 4, 10, 26, 77, 235, 758, 2504] - sage: F.cycle_index_series().coefficients(7) + sage: F.cycle_index_series()[:7] [p[], p[1], 3/2*p[1, 1] + 1/2*p[2], diff --git a/src/sage/combinat/species/linear_order_species.py b/src/sage/combinat/species/linear_order_species.py index 98c4f62eb44..bd4b4a36f7d 100644 --- a/src/sage/combinat/species/linear_order_species.py +++ b/src/sage/combinat/species/linear_order_species.py @@ -17,7 +17,6 @@ #***************************************************************************** from .species import GenericCombinatorialSpecies from .structure import GenericSpeciesStructure -from .generating_series import _integers_from from sage.structure.unique_representation import UniqueRepresentation from sage.combinat.species.misc import accept_size @@ -87,7 +86,7 @@ def __init__(self, min=None, max=None, weight=None): EXAMPLES:: sage: L = species.LinearOrderSpecies() - sage: L.generating_series().coefficients(5) + sage: L.generating_series()[0:5] [1, 1, 1, 1, 1] sage: L = species.LinearOrderSpecies() @@ -123,7 +122,7 @@ def _isotypes(self, structure_class, labels): """ yield structure_class(self, labels, range(1, len(labels)+1)) - def _gs_list(self, base_ring): + def _gs_list(self, base_ring, n): r""" The generating series for the species of linear orders is `\frac{1}{1-x}`. @@ -132,12 +131,12 @@ def _gs_list(self, base_ring): sage: L = species.LinearOrderSpecies() sage: g = L.generating_series() - sage: g.coefficients(10) + sage: g[0:10] [1, 1, 1, 1, 1, 1, 1, 1, 1, 1] """ - return [base_ring(1)] + return base_ring.one() - def _itgs_list(self, base_ring): + def _itgs_list(self, base_ring, n): r""" The isomorphism type generating series is given by `\frac{1}{1-x}`. @@ -146,25 +145,24 @@ def _itgs_list(self, base_ring): sage: L = species.LinearOrderSpecies() sage: g = L.isotype_generating_series() - sage: g.coefficients(10) + sage: g[0:10] [1, 1, 1, 1, 1, 1, 1, 1, 1, 1] """ - return [base_ring(1)] + return base_ring.one() - def _cis_iterator(self, base_ring): + def _cis_callable(self, base_ring, n): """ EXAMPLES:: sage: L = species.LinearOrderSpecies() sage: g = L.cycle_index_series() - sage: g.coefficients(5) + sage: g[0:5] [p[], p[1], p[1, 1], p[1, 1, 1], p[1, 1, 1, 1]] """ from sage.combinat.sf.sf import SymmetricFunctions p = SymmetricFunctions(base_ring).power() - for n in _integers_from(0): - yield p([1]*n) + return p([1]*n) #Backward compatibility LinearOrderSpecies_class = LinearOrderSpecies diff --git a/src/sage/combinat/species/partition_species.py b/src/sage/combinat/species/partition_species.py index 7ba6ea84a13..cc8d1818821 100644 --- a/src/sage/combinat/species/partition_species.py +++ b/src/sage/combinat/species/partition_species.py @@ -17,7 +17,6 @@ #***************************************************************************** from .species import GenericCombinatorialSpecies -from .generating_series import _integers_from from sage.arith.misc import factorial from .subset_species import SubsetSpeciesStructure from .set_species import SetSpecies @@ -152,9 +151,9 @@ def __init__(self, min=None, max=None, weight=None): EXAMPLES:: sage: P = species.PartitionSpecies() - sage: P.generating_series().coefficients(5) + sage: P.generating_series()[0:5] [1, 1, 1, 5/6, 5/8] - sage: P.isotype_generating_series().coefficients(5) + sage: P.isotype_generating_series()[0:5] [1, 1, 2, 3, 5] sage: P = species.PartitionSpecies() @@ -233,20 +232,19 @@ def _canonical_rep_from_partition(self, structure_class, labels, p): breaks = [sum(p[:i]) for i in range(len(p) + 1)] return structure_class(self, labels, [list(range(breaks[i]+1, breaks[i+1]+1)) for i in range(len(p))]) - def _gs_iterator(self, base_ring): + def _gs_callable(self, base_ring, n): r""" EXAMPLES:: sage: P = species.PartitionSpecies() sage: g = P.generating_series() - sage: g.coefficients(5) + sage: [g.coefficient(i) for i in range(5)] [1, 1, 1, 5/6, 5/8] """ from sage.combinat.combinat import bell_number - for n in _integers_from(0): - yield self._weight * base_ring(bell_number(n) / factorial(n)) + return self._weight * base_ring(bell_number(n) / factorial(n)) - def _itgs_iterator(self, base_ring): + def _itgs_callable(self, base_ring, n): r""" The isomorphism type generating series is given by `\frac{1}{1-x}`. @@ -255,12 +253,11 @@ def _itgs_iterator(self, base_ring): sage: P = species.PartitionSpecies() sage: g = P.isotype_generating_series() - sage: g.coefficients(10) + sage: [g.coefficient(i) for i in range(10)] [1, 1, 2, 3, 5, 7, 11, 15, 22, 30] """ from sage.combinat.partition import number_of_partitions - for n in _integers_from(0): - yield self._weight*base_ring(number_of_partitions(n)) + return self._weight*base_ring(number_of_partitions(n)) def _cis(self, series_ring, base_ring): r""" @@ -276,7 +273,7 @@ def _cis(self, series_ring, base_ring): sage: P = species.PartitionSpecies() sage: g = P.cycle_index_series() - sage: g.coefficients(5) + sage: g[0:5] [p[], p[1], p[1, 1] + p[2], @@ -284,7 +281,7 @@ def _cis(self, series_ring, base_ring): 5/8*p[1, 1, 1, 1] + 7/4*p[2, 1, 1] + 7/8*p[2, 2] + p[3, 1] + 3/4*p[4]] """ ciset = SetSpecies().cycle_index_series(base_ring) - res = ciset.composition(ciset - 1) + res = ciset(ciset - 1) if self.is_weighted(): res *= self._weight return res diff --git a/src/sage/combinat/species/permutation_species.py b/src/sage/combinat/species/permutation_species.py index d8ae5c216ea..f6471e0e66e 100644 --- a/src/sage/combinat/species/permutation_species.py +++ b/src/sage/combinat/species/permutation_species.py @@ -18,7 +18,6 @@ from .species import GenericCombinatorialSpecies from .structure import GenericSpeciesStructure -from .generating_series import _integers_from from sage.structure.unique_representation import UniqueRepresentation from sage.rings.integer_ring import ZZ from sage.combinat.permutation import Permutation, Permutations @@ -123,13 +122,13 @@ def __init__(self, min=None, max=None, weight=None): EXAMPLES:: sage: P = species.PermutationSpecies() - sage: P.generating_series().coefficients(5) + sage: P.generating_series()[0:5] [1, 1, 1, 1, 1] - sage: P.isotype_generating_series().coefficients(5) + sage: P.isotype_generating_series()[0:5] [1, 1, 2, 3, 5] sage: P = species.PermutationSpecies() - sage: c = P.generating_series().coefficients(3) + sage: c = P.generating_series()[0:3] sage: P._check() True sage: P == loads(dumps(P)) @@ -186,7 +185,7 @@ def _canonical_rep_from_partition(self, structure_class, labels, p): return structure_class(self, labels, perm) - def _gs_list(self, base_ring): + def _gs_list(self, base_ring, n): r""" The generating series for the species of linear orders is `\frac{1}{1-x}`. @@ -195,13 +194,13 @@ def _gs_list(self, base_ring): sage: P = species.PermutationSpecies() sage: g = P.generating_series() - sage: g.coefficients(10) + sage: g[0:10] [1, 1, 1, 1, 1, 1, 1, 1, 1, 1] """ - return [base_ring(1)] + return base_ring.one() - def _itgs_iterator(self, base_ring): + def _itgs_callable(self, base_ring, n): r""" The isomorphism type generating series is given by `\frac{1}{1-x}`. @@ -210,12 +209,11 @@ def _itgs_iterator(self, base_ring): sage: P = species.PermutationSpecies() sage: g = P.isotype_generating_series() - sage: g.coefficients(10) + sage: [g.coefficient(i) for i in range(10)] [1, 1, 2, 3, 5, 7, 11, 15, 22, 30] """ from sage.combinat.partition import number_of_partitions - for n in _integers_from(0): - yield base_ring(number_of_partitions(n)) + return base_ring(number_of_partitions(n)) def _cis(self, series_ring, base_ring): @@ -226,43 +224,45 @@ def _cis(self, series_ring, base_ring): \prod{n=1}^\infty \frac{1}{1-x_n}. - - EXAMPLES:: sage: P = species.PermutationSpecies() sage: g = P.cycle_index_series() - sage: g.coefficients(5) + sage: g[0:5] [p[], p[1], p[1, 1] + p[2], p[1, 1, 1] + p[2, 1] + p[3], p[1, 1, 1, 1] + p[2, 1, 1] + p[2, 2] + p[3, 1] + p[4]] """ + from sage.combinat.sf.sf import SymmetricFunctions + from sage.combinat.partition import Partitions + p = SymmetricFunctions(base_ring).p() CIS = series_ring - return CIS.product_generator( CIS(self._cis_gen(base_ring, i)) for i in _integers_from(ZZ(1)) ) + return CIS(lambda n: sum(p(la) for la in Partitions(n))) - def _cis_gen(self, base_ring, n): + def _cis_gen(self, base_ring, m, n): """ EXAMPLES:: sage: P = species.PermutationSpecies() - sage: g = P._cis_gen(QQ, 2) - sage: [next(g) for i in range(10)] + sage: [P._cis_gen(QQ, 2, i) for i in range(10)] [p[], 0, p[2], 0, p[2, 2], 0, p[2, 2, 2], 0, p[2, 2, 2, 2], 0] """ from sage.combinat.sf.sf import SymmetricFunctions p = SymmetricFunctions(base_ring).power() - pn = p([n]) - - n = n - 1 - yield p(1) - - for k in _integers_from(1): - for i in range(n): - yield base_ring(0) - yield pn**k + pn = p([m]) + + if not n: + return p(1) + if m == 1: + if n % 2: + return base_ring.zero() + return pn**(n//2) + elif n % m: + return base_ring.zero() + return pn**(n//m) #Backward compatibility PermutationSpecies_class = PermutationSpecies diff --git a/src/sage/combinat/species/product_species.py b/src/sage/combinat/species/product_species.py index 0f7496a7dca..74004849b79 100644 --- a/src/sage/combinat/species/product_species.py +++ b/src/sage/combinat/species/product_species.py @@ -206,7 +206,7 @@ def __init__(self, F, G, min=None, max=None, weight=None): sage: X = species.SingletonSpecies() sage: A = X*X - sage: A.generating_series().coefficients(4) + sage: A.generating_series()[0:4] [0, 0, 1, 0] sage: P = species.PermutationSpecies() @@ -324,7 +324,7 @@ def _gs(self, series_ring, base_ring): sage: P = species.PermutationSpecies() sage: F = P * P - sage: F.generating_series().coefficients(5) + sage: F.generating_series()[0:5] [1, 2, 3, 4, 5] """ res = (self.left_factor().generating_series(base_ring) * @@ -339,7 +339,7 @@ def _itgs(self, series_ring, base_ring): sage: P = species.PermutationSpecies() sage: F = P * P - sage: F.isotype_generating_series().coefficients(5) + sage: F.isotype_generating_series()[0:5] [1, 2, 5, 10, 20] """ res = (self.left_factor().isotype_generating_series(base_ring) * @@ -354,7 +354,7 @@ def _cis(self, series_ring, base_ring): sage: P = species.PermutationSpecies() sage: F = P * P - sage: F.cycle_index_series().coefficients(5) + sage: F.cycle_index_series()[0:5] [p[], 2*p[1], 3*p[1, 1] + 2*p[2], diff --git a/src/sage/combinat/species/recursive_species.py b/src/sage/combinat/species/recursive_species.py index d6023264808..2e3e8cd895c 100644 --- a/src/sage/combinat/species/recursive_species.py +++ b/src/sage/combinat/species/recursive_species.py @@ -25,7 +25,7 @@ class CombinatorialSpeciesStructure(SpeciesStructureWrapper): class CombinatorialSpecies(GenericCombinatorialSpecies): - def __init__(self): + def __init__(self, min=None): """ EXAMPLES:: @@ -39,19 +39,16 @@ def __init__(self): sage: E = species.EmptySetSpecies() sage: L = CombinatorialSpecies() sage: L.define(E+X*L) - sage: L.generating_series().coefficients(4) + sage: L.generating_series()[0:4] [1, 1, 1, 1] sage: LL = loads(dumps(L)) - sage: LL.generating_series().coefficients(4) + sage: LL.generating_series()[0:4] [1, 1, 1, 1] """ self._generating_series = {} self._isotype_generating_series = {} self._cycle_index_series = {} - self._min = None - self._max = None - self._weight = 1 - GenericCombinatorialSpecies.__init__(self, min=None, max=None, weight=None) + GenericCombinatorialSpecies.__init__(self, min=min, max=None, weight=None) _default_structure_class = CombinatorialSpeciesStructure @@ -233,10 +230,10 @@ def _gs(self, series_ring, base_ring): sage: F = CombinatorialSpecies() sage: F.generating_series() - Uninitialized lazy power series + Uninitialized Lazy Laurent Series """ if base_ring not in self._generating_series: - self._generating_series[base_ring] = series_ring() + self._generating_series[base_ring] = series_ring.undefined(valuation=(0 if self._min is None else self._min)) res = self._generating_series[base_ring] if hasattr(self, "_reference") and not hasattr(res, "_reference"): @@ -250,10 +247,10 @@ def _itgs(self, series_ring, base_ring): sage: F = CombinatorialSpecies() sage: F.isotype_generating_series() - Uninitialized lazy power series + Uninitialized Lazy Laurent Series """ if base_ring not in self._isotype_generating_series: - self._isotype_generating_series[base_ring] = series_ring() + self._isotype_generating_series[base_ring] = series_ring.undefined(valuation=(0 if self._min is None else self._min)) res = self._isotype_generating_series[base_ring] if hasattr(self, "_reference") and not hasattr(res, "_reference"): @@ -267,10 +264,10 @@ def _cis(self, series_ring, base_ring): sage: F = CombinatorialSpecies() sage: F.cycle_index_series() - Uninitialized lazy power series + Uninitialized Lazy Laurent Series """ if base_ring not in self._cycle_index_series: - self._cycle_index_series[base_ring] = series_ring() + self._cycle_index_series[base_ring] = series_ring.undefined(valuation=(0 if self._min is None else self._min)) res = self._cycle_index_series[base_ring] if hasattr(self, "_reference") and not hasattr(res, "_reference"): @@ -325,7 +322,7 @@ def define(self, x): sage: E = species.EmptySetSpecies() sage: L = CombinatorialSpecies() sage: L.define(E+X*L) - sage: L.generating_series().coefficients(4) + sage: L.generating_series()[0:4] [1, 1, 1, 1] sage: L.structures([1,2,3]).cardinality() 6 @@ -340,7 +337,7 @@ def define(self, x): :: sage: L = species.LinearOrderSpecies() - sage: L.generating_series().coefficients(4) + sage: L.generating_series()[0:4] [1, 1, 1, 1] sage: L.structures([1,2,3]).cardinality() 6 @@ -351,28 +348,28 @@ def define(self, x): sage: A = CombinatorialSpecies() sage: A.define(E+X*A*A) - sage: A.generating_series().coefficients(6) + sage: A.generating_series()[0:6] [1, 1, 2, 5, 14, 42] sage: A.generating_series().counts(6) [1, 1, 4, 30, 336, 5040] sage: len(A.structures([1,2,3,4]).list()) 336 - sage: A.isotype_generating_series().coefficients(6) + sage: A.isotype_generating_series()[0:6] [1, 1, 2, 5, 14, 42] sage: len(A.isotypes([1,2,3,4]).list()) 14 :: - sage: A = CombinatorialSpecies() + sage: A = CombinatorialSpecies(min=1) sage: A.define(X+A*A) - sage: A.generating_series().coefficients(6) + sage: A.generating_series()[0:6] [0, 1, 1, 2, 5, 14] sage: A.generating_series().counts(6) [0, 1, 2, 12, 120, 1680] sage: len(A.structures([1,2,3]).list()) 12 - sage: A.isotype_generating_series().coefficients(6) + sage: A.isotype_generating_series()[0:6] [0, 1, 1, 2, 5, 14] sage: len(A.isotypes([1,2,3,4]).list()) 5 @@ -381,19 +378,17 @@ def define(self, x): sage: X2 = X*X sage: X5 = X2*X2*X - sage: A = CombinatorialSpecies() - sage: B = CombinatorialSpecies() - sage: C = CombinatorialSpecies() + sage: A = CombinatorialSpecies(min=1) + sage: B = CombinatorialSpecies(min=1) + sage: C = CombinatorialSpecies(min=1) sage: A.define(X5+B*B) sage: B.define(X5+C*C) sage: C.define(X2+C*C+A*A) - sage: A.generating_series().coefficients(Integer(10)) - [0, 0, 0, 0, 0, 1, 0, 0, 1, 2] - sage: A.generating_series().coefficients(15) + sage: A.generating_series()[0:15] [0, 0, 0, 0, 0, 1, 0, 0, 1, 2, 5, 4, 14, 10, 48] - sage: B.generating_series().coefficients(15) + sage: B.generating_series()[0:15] [0, 0, 0, 0, 1, 1, 2, 0, 5, 0, 14, 0, 44, 0, 138] - sage: C.generating_series().coefficients(15) + sage: C.generating_series()[0:15] [0, 0, 1, 0, 1, 0, 2, 0, 5, 0, 15, 0, 44, 2, 142] :: @@ -402,9 +397,9 @@ def define(self, x): sage: F.define(E+X+(X*F+X*X*F)) sage: F.generating_series().counts(10) [1, 2, 6, 30, 192, 1560, 15120, 171360, 2217600, 32296320] - sage: F.generating_series().coefficients(10) + sage: F.generating_series()[0:10] [1, 2, 3, 5, 8, 13, 21, 34, 55, 89] - sage: F.isotype_generating_series().coefficients(10) + sage: F.isotype_generating_series()[0:10] [1, 2, 3, 5, 8, 13, 21, 34, 55, 89] """ if not isinstance(x, GenericCombinatorialSpecies): diff --git a/src/sage/combinat/species/series.py b/src/sage/combinat/species/series.py deleted file mode 100644 index 37e3e6e19ed..00000000000 --- a/src/sage/combinat/species/series.py +++ /dev/null @@ -1,1832 +0,0 @@ -""" -Lazy Power Series - -This file provides an implementation of lazy univariate power -series, which uses the stream class for its internal data -structure. The lazy power series keep track of their approximate -order as much as possible without forcing the computation of any -additional coefficients. This is required for recursively defined -power series. - -This code is based on the work of Ralf Hemmecke and Martin Rubey's -Aldor-Combinat, which can be found at -http://www.risc.uni-linz.ac.at/people/hemmecke/aldor/combinat/index.html. -In particular, the relevant section for this file can be found at -http://www.risc.uni-linz.ac.at/people/hemmecke/AldorCombinat/combinatse9.html. -""" -# **************************************************************************** -# Copyright (C) 2008 Mike Hansen , -# -# Distributed under the terms of the GNU General Public License (GPL) -# -# This code is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# The full text of the GPL is available at: -# -# https://www.gnu.org/licenses/ -# **************************************************************************** - -import builtins - -from collections.abc import Iterable -from .stream import Stream, Stream_class -from .series_order import bounded_decrement, increment, inf, unk -from sage.rings.integer import Integer -from sage.misc.misc_c import prod -from functools import partial -from sage.misc.misc import is_iterator -from sage.misc.repr import repr_lincomb -from sage.misc.cachefunc import cached_method - -from sage.rings.ring import Algebra -from sage.structure.parent import Parent -from sage.categories.all import Rings -from sage.structure.element import Element, parent, AlgebraElement - - -class LazyPowerSeriesRing(Algebra): - def __init__(self, R, names=None, element_class=None): - """ - TESTS:: - - sage: from sage.combinat.species.series import LazyPowerSeriesRing - sage: L = LazyPowerSeriesRing(QQ) - - Equality testing is undecidable in general, and not much - efforts are done at this stage to implement equality when - possible. Hence the failing tests below:: - - sage: TestSuite(L).run() - Failure in ... - The following tests failed: _test_additive_associativity, _test_associativity, _test_distributivity, _test_elements, _test_one, _test_prod, _test_zero - - :: - - sage: LazyPowerSeriesRing(QQ, 'z').gen() - z - sage: LazyPowerSeriesRing(QQ, ['z']).gen() - z - sage: LazyPowerSeriesRing(QQ, ['x', 'z']) - Traceback (most recent call last): - ... - NotImplementedError: only univariate lazy power series rings are supported - """ - #Make sure R is a ring with unit element - if R not in Rings(): - raise TypeError("argument R must be a ring") - - #Take care of the names - if names is None: - names = 'x' - elif isinstance(names, (list, tuple)): - if len(names) != 1: - raise NotImplementedError( - 'only univariate lazy power series rings are supported') - names = names[0] - else: - names = str(names) - - self._element_class = element_class if element_class is not None else LazyPowerSeries - self._order = None - self._name = names - self._zero_base_ring = R.zero() - Parent.__init__(self, R, category=Rings()) - - def ngens(self): - """ - EXAMPLES:: - - sage: LazyPowerSeriesRing(QQ).ngens() - 1 - """ - return 1 - - def __repr__(self): - """ - EXAMPLES:: - - sage: LazyPowerSeriesRing(QQ) - Lazy Power Series Ring over Rational Field - """ - return "Lazy Power Series Ring over %s" % self.base_ring() - - def __eq__(self, x): - """ - Check whether ``self`` is equal to ``x``. - - EXAMPLES:: - - sage: LQ = LazyPowerSeriesRing(QQ) - sage: LZ = LazyPowerSeriesRing(ZZ) - sage: LQ == LQ - True - sage: LZ == LQ - False - """ - if not isinstance(x, LazyPowerSeriesRing): - return False - return self.base_ring() == x.base_ring() - - def __ne__(self, other): - """ - Check whether ``self`` is not equal to ``other``. - - EXAMPLES:: - - sage: LQ = LazyPowerSeriesRing(QQ) - sage: LZ = LazyPowerSeriesRing(ZZ) - sage: LQ != LQ - False - sage: LZ != LQ - True - """ - return not (self == other) - - def __hash__(self): - """ - Return the hash of ``self``. - - EXAMPLES:: - - sage: LQ = LazyPowerSeriesRing(QQ) - sage: LZ = LazyPowerSeriesRing(ZZ) - sage: hash(LQ) == hash(LQ) - True - sage: hash(LZ) == hash(LQ) - False - """ - # with a random number, so that the hash is not that of the base ring - return hash((16079305, self.base_ring())) - - def _coerce_impl(self, x): - """ - EXAMPLES:: - - sage: L1 = LazyPowerSeriesRing(QQ) - sage: L2 = LazyPowerSeriesRing(RR) - sage: L2.has_coerce_map_from(L1) - True - sage: L1.has_coerce_map_from(L2) - False - - :: - - sage: a = L1([1]) + L2([1]) - sage: a.coefficients(3) - [2.00000000000000, 2.00000000000000, 2.00000000000000] - """ - return self(x) - - def __call__(self, x=None, order=unk): - """ - EXAMPLES:: - - sage: from sage.combinat.species.stream import Stream - sage: L = LazyPowerSeriesRing(QQ) - sage: L() - Uninitialized lazy power series - sage: L(1) - 1 - sage: L(ZZ).coefficients(10) - [0, 1, -1, 2, -2, 3, -3, 4, -4, 5] - sage: L(iter(ZZ)).coefficients(10) - [0, 1, -1, 2, -2, 3, -3, 4, -4, 5] - sage: L(Stream(ZZ)).coefficients(10) - [0, 1, -1, 2, -2, 3, -3, 4, -4, 5] - - :: - - sage: a = L([1,2,3]) - sage: a.coefficients(3) - [1, 2, 3] - sage: L(a) is a - True - sage: L_RR = LazyPowerSeriesRing(RR) - sage: b = L_RR(a) - sage: b.coefficients(3) - [1.00000000000000, 2.00000000000000, 3.00000000000000] - sage: L(b) - Traceback (most recent call last): - ... - TypeError: do not know how to coerce ... into self - - TESTS:: - - sage: L(pi) - Traceback (most recent call last): - ... - TypeError: do not know how to coerce pi into self - """ - cls = self._element_class - BR = self.base_ring() - - if x is None: - res = cls(self, stream=None, order=unk, aorder=unk, - aorder_changed=True, is_initialized=False) - res.compute_aorder = uninitialized - return res - - if isinstance(x, LazyPowerSeries): - x_parent = x.parent() - if x_parent.__class__ != self.__class__: - raise ValueError - - if x_parent.base_ring() == self.base_ring(): - return x - else: - if self.base_ring().has_coerce_map_from(x_parent.base_ring()): - return x._new(partial(x._change_ring_gen, self.base_ring()), lambda ao: ao, x, parent=self) - - - if BR.has_coerce_map_from(parent(x)): - x = BR(x) - return self.term(x, 0) - - if isinstance(x, Iterable) and not isinstance(x, Stream_class): - x = iter(x) - - if is_iterator(x): - x = Stream(x) - - if isinstance(x, Stream_class): - aorder = order if order != unk else 0 - return cls(self, stream=x, order=order, aorder=aorder, - aorder_changed=False, is_initialized=True) - elif not isinstance(x, Element): - x = BR(x) - return self.term(x, 0) - - raise TypeError("do not know how to coerce %s into self" % x) - - @cached_method - def zero(self): - """ - Return the zero power series. - - EXAMPLES:: - - sage: L = LazyPowerSeriesRing(QQ) - sage: L.zero() - 0 - """ - return self.term(self._zero_base_ring, 0) - - def identity_element(self): - """ - Return the one power series. - - EXAMPLES:: - - sage: L = LazyPowerSeriesRing(QQ) - sage: L.identity_element() - 1 - """ - return self(self.base_ring()(1)) - - def gen(self, i=0): - """ - EXAMPLES:: - - sage: L = LazyPowerSeriesRing(QQ) - sage: L.gen().coefficients(5) - [0, 1, 0, 0, 0] - """ - res = self._new_initial(1, Stream([0,1,0])) - res._name = self._name - return res - - def term(self, r, n): - """ - EXAMPLES:: - - sage: L = LazyPowerSeriesRing(QQ) - sage: L.term(0,0) - 0 - sage: L.term(3,2).coefficients(5) - [0, 0, 3, 0, 0] - """ - if n < 0: - raise ValueError("n must be non-negative") - BR = self.base_ring() - if r == 0: - res = self._new_initial(inf, Stream([0])) - res._name = "0" - else: - zero = BR(0) - s = [zero]*n+[BR(r),zero] - res = self._new_initial(n, Stream(s)) - - if n == 0: - res._name = repr(r) - elif n == 1: - res._name = repr(r) + "*" + self._name - else: - res._name = "%s*%s^%s" % (repr(r), self._name, n) - - return res - - def _new_initial(self, order, stream): - """ - Return a new power series with specified order. - - INPUT: - - - - ``order`` - a non-negative integer - - - ``stream`` - a Stream object - - - EXAMPLES:: - - sage: from sage.combinat.species.stream import Stream - sage: L = LazyPowerSeriesRing(QQ) - sage: L._new_initial(0, Stream([1,2,3,0])).coefficients(5) - [1, 2, 3, 0, 0] - """ - return self._element_class(self, stream=stream, order=order, aorder=order, - aorder_changed=False, is_initialized=True) - - - def _sum_gen(self, series_list): - """ - Return a generator for the coefficients of the sum of the lazy - power series in series_list. - - INPUT: - - - - ``series_list`` - a list of lazy power series - - - EXAMPLES:: - - sage: L = LazyPowerSeriesRing(QQ) - sage: series_list = [ L([1]), L([0,1]), L([0,0,1]) ] - sage: g = L._sum_gen(series_list) - sage: [next(g) for i in range(5)] - [1, 2, 3, 3, 3] - """ - last_index = len(series_list) - 1 - assert last_index >= 0 - n = 0 - while True: - r = sum( [f.coefficient(n) for f in series_list] ) - yield r - n += 1 - - def sum(self, a): - """ - EXAMPLES:: - - sage: L = LazyPowerSeriesRing(QQ) - sage: l = [L(ZZ)]*3 - sage: L.sum(l).coefficients(10) - [0, 3, -3, 6, -6, 9, -9, 12, -12, 15] - """ - return self( self._sum_gen(a) ) - - #Potentially infinite sum - def _sum_generator_gen(self, g): - """ - EXAMPLES:: - - sage: L = LazyPowerSeriesRing(QQ) - sage: s = L([1]) - sage: def f(): - ....: while True: - ....: yield s - sage: g = L._sum_generator_gen(f()) - sage: [next(g) for i in range(10)] - [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] - """ - s = Stream(g) - n = 0 - while True: - r = s[n].coefficient(n) - for i in range(len(s)-1): - r += s[i].coefficient(n) - yield r - n += 1 - - def sum_generator(self, g): - """ - EXAMPLES:: - - sage: L = LazyPowerSeriesRing(QQ) - sage: g = [L([1])]*6 + [L(0)] - sage: t = L.sum_generator(g) - sage: t.coefficients(10) - [1, 2, 3, 4, 5, 6, 6, 6, 6, 6] - - :: - - sage: s = L([1]) - sage: def g(): - ....: while True: - ....: yield s - sage: t = L.sum_generator(g()) - sage: t.coefficients(9) - [1, 2, 3, 4, 5, 6, 7, 8, 9] - """ - return self(self._sum_generator_gen(g)) - - #Potentially infinite product - def _product_generator_gen(self, g): - """ - EXAMPLES:: - - sage: from sage.combinat.species.stream import _integers_from - sage: L = LazyPowerSeriesRing(QQ) - sage: g = (L([1]+[0]*i+[1]) for i in _integers_from(0)) - sage: g2 = L._product_generator_gen(g) - sage: [next(g2) for i in range(10)] - [1, 1, 2, 4, 7, 12, 20, 33, 53, 84] - """ - z = next(g) - yield z.coefficient(0) - yield z.coefficient(1) - - n = 2 - - for x in g: - z = z * x - yield z.coefficient(n) - n += 1 - - while True: - yield z.coefficient(n) - n += 1 - - def product_generator(self, g): - """ - EXAMPLES:: - - sage: L = LazyPowerSeriesRing(QQ) - sage: s1 = L([1,1,0]) - sage: s2 = L([1,0,1,0]) - sage: s3 = L([1,0,0,1,0]) - sage: s4 = L([1,0,0,0,1,0]) - sage: s5 = L([1,0,0,0,0,1,0]) - sage: s6 = L([1,0,0,0,0,0,1,0]) - sage: s = [s1, s2, s3, s4, s5, s6] - sage: def g(): - ....: for a in s: - ....: yield a - sage: p = L.product_generator(g()) - sage: p.coefficients(26) - [1, 1, 1, 2, 2, 3, 4, 4, 4, 5, 5, 5, 5, 4, 4, 4, 3, 2, 2, 1, 1, 1, 0, 0, 0, 0] - - :: - - sage: def m(n): - ....: yield 1 - ....: while True: - ....: for i in range(n-1): - ....: yield 0 - ....: yield 1 - sage: def s(n): - ....: q = 1/n - ....: yield 0 - ....: while True: - ....: for i in range(n-1): - ....: yield 0 - ....: yield q - - :: - - sage: def lhs_gen(): - ....: n = 1 - ....: while True: - ....: yield L(m(n)) - ....: n += 1 - - :: - - sage: def rhs_gen(): - ....: n = 1 - ....: while True: - ....: yield L(s(n)) - ....: n += 1 - sage: lhs = L.product_generator(lhs_gen()) - sage: rhs = L.sum_generator(rhs_gen()).exponential() - sage: lhs.coefficients(10) - [1, 1, 2, 3, 5, 7, 11, 15, 22, 30] - sage: rhs.coefficients(10) - [1, 1, 2, 3, 5, 7, 11, 15, 22, 30] - """ - return self(self._product_generator_gen(g)) - - - -class LazyPowerSeries(AlgebraElement): - def __init__(self, A, stream=None, order=None, aorder=None, aorder_changed=True, is_initialized=False, name=None): - """ - EXAMPLES:: - - sage: L = LazyPowerSeriesRing(QQ) - sage: f = L() - sage: loads(dumps(f)) - Uninitialized lazy power series - """ - AlgebraElement.__init__(self, A) - self._stream = stream - self.order = unk if order is None else order - self.aorder = unk if aorder is None else aorder - if self.aorder == inf: - self.order = inf - self.aorder_changed = aorder_changed - self.is_initialized = is_initialized - self._name = name - - def compute_aorder(*args, **kwargs): - """ - The default compute_aorder does nothing. - - EXAMPLES:: - - sage: L = LazyPowerSeriesRing(QQ) - sage: a = L(1) - sage: a.compute_aorder() is None - True - """ - return None - - def _get_repr_info(self, x): - """ - EXAMPLES:: - - sage: L = LazyPowerSeriesRing(QQ) - sage: a = L([1,2,3]) - sage: a.compute_coefficients(5) - sage: a._get_repr_info('x') - [('1', 1), ('x', 2), ('x^2', 3)] - """ - n = len(self._stream) - m = ['1', x] - m += [x + "^" + str(i) for i in range(2, n)] - c = [self._stream[i] for i in range(n)] - return [(mo, co) for mo, co in zip(m, c) if co != 0] - - def __repr__(self): - """ - EXAMPLES:: - - sage: L = LazyPowerSeriesRing(QQ) - sage: s = L(); s._name = 's'; s - s - - :: - - sage: L() - Uninitialized lazy power series - - :: - - sage: a = L([1,2,3]) - sage: a - O(1) - sage: a.compute_coefficients(2) - sage: a - 1 + 2*x + 3*x^2 + O(x^3) - sage: a.compute_coefficients(4) - sage: a - 1 + 2*x + 3*x^2 + 3*x^3 + 3*x^4 + 3*x^5 + ... - - :: - - sage: a = L([1,2,3,0]) - sage: a.compute_coefficients(5) - sage: a - 1 + 2*x + 3*x^2 - """ - if self._name is not None: - return self._name - - if self.is_initialized: - n = len(self._stream) - x = self.parent()._name - baserepr = repr_lincomb(self._get_repr_info(x)) - if self._stream.is_constant(): - if self._stream[n-1] == 0: - l = baserepr - else: - l = baserepr + " + " + repr_lincomb([(x+"^"+str(i), self._stream[n-1]) for i in range(n, n+3)]) + " + ..." - else: - l = baserepr + " + O(x^%s)" % n if n > 0 else "O(1)" - else: - l = 'Uninitialized lazy power series' - return l - - - def refine_aorder(self): - """ - Refines the approximate order of self as much as possible without - computing any coefficients. - - EXAMPLES:: - - sage: L = LazyPowerSeriesRing(QQ) - sage: a = L([0,0,0,0,1]) - sage: a.aorder - 0 - sage: a.coefficient(2) - 0 - sage: a.aorder - 0 - sage: a.refine_aorder() - sage: a.aorder - 3 - - :: - - sage: a = L([0,0]) - sage: a.aorder - 0 - sage: a.coefficient(5) - 0 - sage: a.refine_aorder() - sage: a.aorder - Infinite series order - - :: - - sage: a = L([0,0,1,0,0,0]) - sage: a[4] - 0 - sage: a.refine_aorder() - sage: a.aorder - 2 - """ - #If we already know the order, then we don't have - #to worry about the approximate order - if self.order != unk: - return - - #aorder can never be infinity since order would have to - #be infinity as well - assert self.aorder != inf - - if self.aorder == unk or not self.is_initialized: - self.compute_aorder() - else: - #Try to improve the approximate order - ao = self.aorder - c = self._stream - n = c.number_computed() - - - if ao == 0 and n > 0: - while ao < n: - if self._stream[ao] == 0: - self.aorder += 1 - ao += 1 - else: - break - - #Try to recognize the zero series - if ao == n: - #For non-constant series, we cannot do anything - if not c.is_constant(): - return - if c[n-1] == 0: - self.aorder = inf - self.order = inf - return - - if ao < n: - self.order = ao - - - if hasattr(self, '_reference') and self._reference is not None: - self._reference._copy(self) - - def initialize_coefficient_stream(self, compute_coefficients): - """ - Initializes the coefficient stream. - - INPUT: compute_coefficients - - TESTS:: - - sage: from sage.combinat.species.series_order import inf, unk - sage: L = LazyPowerSeriesRing(QQ) - sage: f = L() - sage: compute_coefficients = lambda ao: iter(ZZ) - sage: f.order = inf - sage: f.aorder = inf - sage: f.initialize_coefficient_stream(compute_coefficients) - sage: f.coefficients(5) - [0, 0, 0, 0, 0] - - :: - - sage: f = L() - sage: compute_coefficients = lambda ao: iter(ZZ) - sage: f.order = 1 - sage: f.aorder = 1 - sage: f.initialize_coefficient_stream(compute_coefficients) - sage: f.coefficients(5) - [0, 1, -1, 2, -2] - """ - ao = self.aorder - assert ao != unk - - if ao == inf: - self.order = inf - self._stream = Stream(0) - else: - self._stream = Stream(compute_coefficients(ao)) - - self.is_initialized = True - - def compute_coefficients(self, i): - """ - Computes all the coefficients of self up to i. - - EXAMPLES:: - - sage: L = LazyPowerSeriesRing(QQ) - sage: a = L([1,2,3]) - sage: a.compute_coefficients(5) - sage: a - 1 + 2*x + 3*x^2 + 3*x^3 + 3*x^4 + 3*x^5 + ... - """ - self.coefficient(i) - - def coefficients(self, n): - """ - Return the first n coefficients of self. - - EXAMPLES:: - - sage: L = LazyPowerSeriesRing(QQ) - sage: f = L([1,2,3,0]) - sage: f.coefficients(5) - [1, 2, 3, 0, 0] - """ - return [self.coefficient(i) for i in range(n)] - - def is_zero(self): - """ - Return True if and only if self is zero. - - EXAMPLES:: - - sage: L = LazyPowerSeriesRing(QQ) - sage: s = L([0,2,3,0]) - sage: s.is_zero() - False - - :: - - sage: s = L(0) - sage: s.is_zero() - True - - :: - - sage: s = L([0]) - sage: s.is_zero() - False - sage: s.coefficient(0) - 0 - sage: s.coefficient(1) - 0 - sage: s.is_zero() - True - """ - self.refine_aorder() - return self.order == inf - - def set_approximate_order(self, new_order): - """ - Sets the approximate order of self and returns True if the - approximate order has changed otherwise it will return False. - - EXAMPLES:: - - sage: L = LazyPowerSeriesRing(QQ) - sage: f = L([0,0,0,3,2,1,0]) - sage: f.get_aorder() - 0 - sage: f.set_approximate_order(3) - True - sage: f.set_approximate_order(3) - False - """ - self.aorder_changed = ( self.aorder != new_order ) - self.aorder = new_order - return self.aorder_changed - - def _copy(self, x): - """ - EXAMPLES:: - - sage: L = LazyPowerSeriesRing(QQ) - sage: f = L.term(2, 2) - sage: g = L() - sage: g._copy(f) - sage: g.order - 2 - sage: g.aorder - 2 - sage: g.is_initialized - True - sage: g.coefficients(4) - [0, 0, 2, 0] - """ - self.order = x.order - self.aorder = x.aorder - self.aorder_changed = x.aorder_changed - self.compute_aorder = x.compute_aorder - self.is_initialized = x.is_initialized - self._stream = x._stream - - def define(self, x): - """ - EXAMPLES: Test Recursive 0 - - :: - - sage: L = LazyPowerSeriesRing(QQ) - sage: one = L(1) - sage: monom = L.gen() - sage: s = L() - sage: s._name = 's' - sage: s.define(one+monom*s) - sage: s.aorder - 0 - sage: s.order - Unknown series order - sage: [s.coefficient(i) for i in range(6)] - [1, 1, 1, 1, 1, 1] - - Test Recursive 1 - - :: - - sage: s = L() - sage: s._name = 's' - sage: s.define(one+monom*s*s) - sage: s.aorder - 0 - sage: s.order - Unknown series order - sage: [s.coefficient(i) for i in range(6)] - [1, 1, 2, 5, 14, 42] - - Test Recursive 1b - - :: - - sage: s = L() - sage: s._name = 's' - sage: s.define(monom + s*s) - sage: s.aorder - 1 - sage: s.order - Unknown series order - sage: [s.coefficient(i) for i in range(7)] - [0, 1, 1, 2, 5, 14, 42] - - Test Recursive 2 - - :: - - sage: s = L() - sage: s._name = 's' - sage: t = L() - sage: t._name = 't' - sage: s.define(one+monom*t*t*t) - sage: t.define(one+monom*s*s) - sage: [s.coefficient(i) for i in range(9)] - [1, 1, 3, 9, 34, 132, 546, 2327, 10191] - sage: [t.coefficient(i) for i in range(9)] - [1, 1, 2, 7, 24, 95, 386, 1641, 7150] - - Test Recursive 2b - - :: - - sage: s = L() - sage: s._name = 's' - sage: t = L() - sage: t._name = 't' - sage: s.define(monom + t*t*t) - sage: t.define(monom + s*s) - sage: [s.coefficient(i) for i in range(9)] - [0, 1, 0, 1, 3, 3, 7, 30, 63] - sage: [t.coefficient(i) for i in range(9)] - [0, 1, 1, 0, 2, 6, 7, 20, 75] - - Test Recursive 3 - - :: - - sage: s = L() - sage: s._name = 's' - sage: s.define(one+monom*s*s*s) - sage: [s.coefficient(i) for i in range(10)] - [1, 1, 3, 12, 55, 273, 1428, 7752, 43263, 246675] - """ - self._copy(x) - x._reference = self - - def coefficient(self, n): - """ - Return the coefficient of xn in self. - - EXAMPLES:: - - sage: L = LazyPowerSeriesRing(QQ) - sage: f = L(ZZ) - sage: [f.coefficient(i) for i in range(5)] - [0, 1, -1, 2, -2] - """ - # The following line must not be written n < self.get_aorder() - # because comparison of Integer and OnfinityOrder is not implemented. - if self.get_aorder() > n: - return self.parent()._zero_base_ring - - assert self.is_initialized - - return self._stream[n] - - def get_aorder(self): - """ - Return the approximate order of self. - - EXAMPLES:: - - sage: L = LazyPowerSeriesRing(QQ) - sage: a = L.gen() - sage: a.get_aorder() - 1 - """ - self.refine_aorder() - return self.aorder - - def get_order(self): - """ - Return the order of self. - - EXAMPLES:: - - sage: L = LazyPowerSeriesRing(QQ) - sage: a = L.gen() - sage: a.get_order() - 1 - """ - self.refine_aorder() - return self.order - - def get_stream(self): - """ - Return self's underlying Stream object. - - EXAMPLES:: - - sage: L = LazyPowerSeriesRing(QQ) - sage: a = L.gen() - sage: s = a.get_stream() - sage: [s[i] for i in range(5)] - [0, 1, 0, 0, 0] - """ - self.refine_aorder() - return self._stream - - def _approximate_order(self, compute_coefficients, new_order, *series): - if self.is_initialized: - return - - ochanged = self.aorder_changed - - ao = new_order(*[s.aorder for s in series]) - ao = inf if ao == unk else ao - - tchanged = self.set_approximate_order(ao) - - if len(series) == 0: - must_initialize_coefficient_stream = True - tchanged = ochanged = False - elif len(series) == 1 or len(series) == 2: - must_initialize_coefficient_stream = ( self.aorder == unk or self.is_initialized is False) - else: - raise ValueError - - if ochanged or tchanged: - for s in series: - s.compute_aorder() - ao = new_order(*[s.aorder for s in series]) - tchanged = self.set_approximate_order(ao) - - if must_initialize_coefficient_stream: - self.initialize_coefficient_stream(compute_coefficients) - - if hasattr(self, '_reference') and self._reference is not None: - self._reference._copy(self) - - def _new(self, compute_coefficients, order_op, *series, **kwds): - parent = kwds['parent'] if 'parent' in kwds else self.parent() - new_fps = self.__class__(parent, stream=None, order=unk, aorder=self.aorder, - aorder_changed=True, is_initialized=False) - - new_fps.compute_aorder = lambda: new_fps._approximate_order(compute_coefficients, order_op, *series) - return new_fps - - def _add_(self, y): - """ - EXAMPLES: Test Plus 1 - - :: - - sage: from sage.combinat.species.series import * - sage: from sage.combinat.species.stream import Stream - sage: L = LazyPowerSeriesRing(QQ) - sage: gs0 = L([0]) - sage: gs1 = L([1]) - sage: sum1 = gs0 + gs1 - sage: sum2 = gs1 + gs1 - sage: sum3 = gs1 + gs0 - sage: [gs0.coefficient(i) for i in range(11)] - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - sage: [gs1.coefficient(i) for i in range(11)] - [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] - sage: [sum1.coefficient(i) for i in range(11)] - [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] - sage: [sum2.coefficient(i) for i in range(11)] - [2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2] - sage: [sum3.coefficient(i) for i in range(11)] - [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] - - Test Plus 2 - - :: - - sage: gs1 = L([1,2,4,8,0]) - sage: gs2 = L([-1, 0,-1,-9,22,0]) - sage: sum = gs1 + gs2 - sage: sum2 = gs2 + gs1 - sage: [ sum.coefficient(i) for i in range(5) ] - [0, 2, 3, -1, 22] - sage: [ sum.coefficient(i) for i in range(5, 11) ] - [0, 0, 0, 0, 0, 0] - sage: [ sum2.coefficient(i) for i in range(5) ] - [0, 2, 3, -1, 22] - sage: [ sum2.coefficient(i) for i in range(5, 11) ] - [0, 0, 0, 0, 0, 0] - """ - return self._new(partial(self._plus_gen, y), min, self, y) - - add = _add_ - - - - def _plus_gen(self, y, ao): - """ - EXAMPLES:: - - sage: L = LazyPowerSeriesRing(QQ) - sage: gs1 = L([1]) - sage: g = gs1._plus_gen(gs1, 0) - sage: [next(g) for i in range(5)] - [2, 2, 2, 2, 2] - - :: - - sage: g = gs1._plus_gen(gs1, 2) - sage: [next(g) for i in range(5)] - [0, 0, 2, 2, 2] - """ - base_ring = self.parent().base_ring() - zero = base_ring(0) - for n in range(ao): - yield zero - n = ao - while True: - yield self._stream[n] + y._stream[n] - n += 1 - - def _mul_(self, y): - """ - EXAMPLES:: - - sage: L = LazyPowerSeriesRing(QQ) - sage: gs0 = L(0) - sage: gs1 = L([1]) - - :: - - sage: prod0 = gs0 * gs1 - sage: [prod0.coefficient(i) for i in range(11)] - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - - :: - - sage: prod1 = gs1 * gs0 - sage: [prod1.coefficient(i) for i in range(11)] - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - - :: - - sage: prod2 = gs1 * gs1 - sage: [prod2.coefficient(i) for i in range(11)] - [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] - - :: - - sage: gs1 = L([1,2,4,8,0]) - sage: gs2 = L([-1, 0,-1,-9,22,0]) - - :: - - sage: prod1 = gs1 * gs2 - sage: [prod1.coefficient(i) for i in range(11)] - [-1, -2, -5, -19, 0, 0, 16, 176, 0, 0, 0] - - :: - - sage: prod2 = gs2 * gs1 - sage: [prod2.coefficient(i) for i in range(11)] - [-1, -2, -5, -19, 0, 0, 16, 176, 0, 0, 0] - """ - - return self._new(partial(self._times_gen, y), lambda a,b:a+b, self, y) - - times = _mul_ - - def _times_gen(self, y, ao): - r""" - Return an iterator for the coefficients of self \* y. - - EXAMPLES:: - - sage: L = LazyPowerSeriesRing(QQ) - sage: f = L([1,1,0]) - sage: g = f._times_gen(f,0) - sage: [next(g) for i in range(5)] - [1, 2, 1, 0, 0] - """ - base_ring = self.parent().base_ring() - zero = base_ring(0) - - for n in range(ao): - yield zero - - n = ao - while True: - low = self.aorder - high = n - y.aorder - nth_coefficient = zero - - #Handle the zero series - if low == inf or high == inf: - yield zero - n += 1 - continue - - for k in range(low, high+1): - cx = self._stream[k] - if cx == 0: - continue - nth_coefficient += cx * y._stream[n-k] - yield nth_coefficient - n += 1 - - def __pow__(self, n): - """ - EXAMPLES:: - - sage: L = LazyPowerSeriesRing(QQ) - sage: f = L([1,1,0]) # 1+x - sage: g = f^3 - sage: g.coefficients(4) - [1, 3, 3, 1] - - :: - - sage: f^0 - 1 - """ - if not isinstance(n, (int, Integer)) or n < 0: - raise ValueError("n must be a nonnegative integer") - return prod([self]*n, self.parent().identity_element()) - - def __invert__(self): - """ - Return 1 over this power series, i.e. invert this power series. - - EXAMPLES:: - - sage: L = LazyPowerSeriesRing(QQ) - sage: x = L.gen() - - Geometric series:: - - sage: a = ~(1-x); a.compute_coefficients(10); a - 1 + x + x^2 + x^3 + x^4 + x^5 + x^6 + x^7 + x^8 + x^9 + x^10 + O(x^11) - - (Shifted) Fibonacci numbers:: - - sage: b = ~(1-x-x^2); b.compute_coefficients(10); b - 1 + x + 2*x^2 + 3*x^3 + 5*x^4 + 8*x^5 - + 13*x^6 + 21*x^7 + 34*x^8 + 55*x^9 + 89*x^10 + O(x^11) - - Series whose constant coefficient is `0` cannot be inverted:: - - sage: ~x - Traceback (most recent call last): - .... - ZeroDivisionError: cannot invert x because constant coefficient is 0 - """ - if self.get_aorder() > 0: - raise ZeroDivisionError( - 'cannot invert {} because ' - 'constant coefficient is 0'.format(self)) - return self._new(self._invert_gen, lambda a: 0, self) - - invert = __invert__ - - def _invert_gen(self, ao): - r""" - Return an iterator for the coefficients of 1 over this power series. - - TESTS:: - - sage: L = LazyPowerSeriesRing(QQ) - sage: f = L([1, -1, 0]) - sage: g = f._invert_gen(0) - sage: [next(g) for i in range(10)] - [1, 1, 1, 1, 1, 1, 1, 1, 1, 1] - """ - from itertools import count - - assert ao == 0 - - ic0 = ~self.coefficient(0) - yield ic0 - if self.order == 0: - return - - one = self.parent()(1) - base = one - ic0 * self - base.coefficient(0) - ao_base = base.get_aorder() - assert ao_base >= 1 - - current = one + base - k = 1 - for n in count(1): - while ao_base*k < n: - current = one + base * current - k += 1 - current.coefficient(n) # make sure new current is initialized - ao_base = base.get_aorder() # update this so that while above is faster - yield current.coefficient(n) * ic0 - - def _div_(self, other): - """ - Divide this power series by ``other``. - - EXAMPLES:: - - sage: L = LazyPowerSeriesRing(QQ) - sage: x = L.gen() - - Fibonacci numbers:: - - sage: b = x / (1-x-x^2); b.compute_coefficients(10); b - x + x^2 + 2*x^3 + 3*x^4 + 5*x^5 + 8*x^6 - + 13*x^7 + 21*x^8 + 34*x^9 + 55*x^10 + O(x^11) - """ - return self * ~other - - div = _div_ - - def __call__(self, y): - """ - Return the composition of this power series and the power series y. - - EXAMPLES:: - - sage: L = LazyPowerSeriesRing(QQ) - sage: s = L([1]) - sage: t = L([0,0,1]) - sage: u = s(t) - sage: u.coefficients(11) - [1, 0, 1, 1, 2, 3, 5, 8, 13, 21, 34] - - Test Compose 2 - - :: - - sage: s = L([1]) - sage: t = L([0,0,1,0]) - sage: u = s(t) - sage: u.aorder - 0 - sage: u.order - Unknown series order - sage: u.coefficients(10) - [1, 0, 1, 0, 1, 0, 1, 0, 1, 0] - sage: u.aorder - 0 - sage: u.order - 0 - - Test Compose 3 s = 1/(1-x), t = x/(1-x) s(t) = (1-x)/(1-2x) - - :: - - sage: s = L([1]) - sage: t = L([0,1]) - sage: u = s(t) - sage: u.coefficients(14) - [1, 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096] - """ - return self._new(partial(self._compose_gen, y), lambda a,b:a*b, self, y) - - composition = __call__ - - def _compose_gen(self, y, ao): - """ - Return a iterator for the coefficients of the composition of this - power series with the power series y. - - EXAMPLES:: - - sage: L = LazyPowerSeriesRing(QQ) - sage: s = L([1]) - sage: t = L([0,1]) - sage: g = s._compose_gen(t, 0) - sage: [next(g) for i in range(10)] - [1, 1, 2, 4, 8, 16, 32, 64, 128, 256] - """ - assert y.coefficient(0) == 0 - yield self._stream[0] - z = self.tail().compose(y) * y - z.coefficient(1) - n = 1 - while True: - yield z._stream[n] - n += 1 - - def tail(self): - """ - Return the power series whose coefficients obtained by subtracting - the constant term from this series and then dividing by x. - - EXAMPLES:: - - sage: from sage.combinat.species.stream import Stream - sage: L = LazyPowerSeriesRing(QQ) - sage: f = L(range(20)) - sage: g = f.tail() - sage: g.coefficients(10) - [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] - """ - return self._new(lambda a0: self.iterator(1), bounded_decrement, self) - - def iterator(self, n=0, initial=None): - """ - Return an iterator for the coefficients of self starting at n. - - EXAMPLES:: - - sage: from sage.combinat.species.stream import Stream - sage: L = LazyPowerSeriesRing(QQ) - sage: f = L(range(10)) - sage: g = f.iterator(2) - sage: [next(g) for i in range(5)] - [2, 3, 4, 5, 6] - sage: g = f.iterator(2, initial=[0,0]) - sage: [next(g) for i in range(5)] - [0, 0, 2, 3, 4] - """ - if initial is not None: - for x in initial: - yield x - while True: - yield self._stream[n] - n += 1 - - compose = __call__ - - def _power_gen(self): - """ - Return a generator for all the powers self^k starting with k = 1. - - EXAMPLES:: - - sage: L = LazyPowerSeriesRing(QQ) - sage: f = L([1,1,0]) - sage: g = f._power_gen() - sage: next(g).coefficients(5) - [1, 1, 0, 0, 0] - sage: next(g).coefficients(5) - [1, 2, 1, 0, 0] - sage: next(g).coefficients(5) - [1, 3, 3, 1, 0] - """ - z = self - while True: - yield z - z = z*self - - def derivative(self): - """ - EXAMPLES:: - - sage: from sage.combinat.species.stream import Stream - sage: L = LazyPowerSeriesRing(QQ) - sage: one = L(1) - sage: monom = L.gen() - sage: s = L([1]) - sage: u = s.derivative() - sage: u.coefficients(10) - [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] - - :: - - sage: s = L() - sage: s._name = 's' - sage: s.define(one+monom*s*s) - sage: u = s.derivative() - sage: u.coefficients(5) #[1*1, 2*2, 3*5, 4*14, 5*42] - [1, 4, 15, 56, 210] - - :: - - sage: s = L([1]) - sage: t = L([0,1]) - sage: u = s(t).derivative() - sage: v = (s.derivative().compose(t))*t.derivative() - sage: u.coefficients(11) - [1, 4, 12, 32, 80, 192, 448, 1024, 2304, 5120, 11264] - sage: v.coefficients(11) - [1, 4, 12, 32, 80, 192, 448, 1024, 2304, 5120, 11264] - - :: - - sage: s = L(); s._name='s' - sage: t = L(); t._name='t' - sage: s.define(monom+t*t*t) - sage: t.define(monom+s*s) - sage: u = (s*t).derivative() - sage: v = s.derivative()*t + s*t.derivative() - sage: u.coefficients(10) - [0, 2, 3, 4, 30, 72, 133, 552, 1791, 4260] - sage: v.coefficients(10) - [0, 2, 3, 4, 30, 72, 133, 552, 1791, 4260] - sage: u.coefficients(10) == v.coefficients(10) - True - - :: - - sage: f = L._new_initial(2, Stream([0,0,4,5,6,0])) - sage: d = f.derivative() - sage: d.get_aorder() - 1 - sage: d.coefficients(5) - [0, 8, 15, 24, 0] - """ - return self._new(self._diff_gen, bounded_decrement, self) - - def _diff_gen(self, ao): - """ - Return an iterator for the coefficients of the derivative of self. - - EXAMPLES:: - - sage: L = LazyPowerSeriesRing(QQ) - sage: f = L([1]) - sage: g = f._diff_gen(0) - sage: [next(g) for i in range(10)] - [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] - """ - n = 1 - while True: - yield n*self._stream[n] - n += 1 - - ########### - #Integrals# - ########### - def integral(self, integration_constant = 0): - """ - EXAMPLES:: - - sage: L = LazyPowerSeriesRing(QQ) - sage: zero = L(0) - sage: s = zero - sage: t = s.integral() - sage: t.is_zero() - True - - :: - - sage: s = zero - sage: t = s.integral(1) - sage: t.coefficients(6) - [1, 0, 0, 0, 0, 0] - sage: t._stream.is_constant() - True - - :: - - sage: s = L.term(1, 0) - sage: t = s.integral() - sage: t.coefficients(6) - [0, 1, 0, 0, 0, 0] - sage: t._stream.is_constant() - True - - :: - - sage: s = L.term(1,0) - sage: t = s.integral(1) - sage: t.coefficients(6) - [1, 1, 0, 0, 0, 0] - sage: t._stream.is_constant() - True - - :: - - sage: s = L.term(1, 4) - sage: t = s.integral() - sage: t.coefficients(10) - [0, 0, 0, 0, 0, 1/5, 0, 0, 0, 0] - - :: - - sage: s = L.term(1,4) - sage: t = s.integral(1) - sage: t.coefficients(10) - [1, 0, 0, 0, 0, 1/5, 0, 0, 0, 0] - - TESTS:: - - sage: from sage.combinat.species.stream import Stream - sage: f = L._new_initial(2, Stream([0,0,4,5,6,0])) - sage: i = f.derivative().integral() - sage: i.get_aorder() - 2 - sage: i.coefficients(5) - [0, 0, 4, 5, 6] - sage: i = f.derivative().integral(1) - sage: i.get_aorder() - 0 - sage: i.coefficients(5) - [1, 0, 4, 5, 6] - """ - if integration_constant == 0: - return self._new(self._integral_zero_gen, increment, self) - else: - L = self.parent() - return L._new_initial(0, Stream(self._integral_nonzero_gen(integration_constant))) - - def _integral_zero_gen(self, ao): - """ - EXAMPLES:: - - sage: L = LazyPowerSeriesRing(QQ) - sage: s = L.gen() - sage: g = s._integral_zero_gen(1) - sage: [next(g) for i in range(5)] - [0, 0, 1/2, 0, 0] - """ - for n in range(ao): - yield self.parent().zero() - n = ao - while True: - #Check to see if the stream is finite - if self.is_finite(n-1): - yield self._stream[n-1] - break - else: - yield (Integer(1)/Integer(n))*self._stream[n-1] - n += 1 - - - def _integral_nonzero_gen(self, integration_constant): - """ - EXAMPLES:: - - sage: from sage.combinat.species.stream import Stream - sage: L = LazyPowerSeriesRing(QQ) - sage: f = L._new_initial(2, Stream([0,0,4,5,6,0])).derivative() - sage: g = f._integral_nonzero_gen(1) - sage: [next(g) for i in range(5)] - [1, 0, 4, 5, 6] - """ - yield integration_constant - ao = self.aorder - assert ao != unk - - if ao == inf: - yield self.parent()._zero_base_ring - else: - for _ in range(ao-1): - yield self.parent()._zero_base_ring - - n = max(1, ao) - while True: - self.coefficient(n - 1) - - #Check to see if the stream is finite - if self.is_finite(n-1): - yield self.coefficient(n-1) - break - else: - yield (Integer(1)/Integer(n))*self.coefficient(n-1) - n += 1 - - def is_finite(self, n=None): - """ - EXAMPLES:: - - sage: L = LazyPowerSeriesRing(QQ) - sage: a = L([0,0,1,0,0]); a - O(1) - sage: a.is_finite() - False - sage: c = a[4] - sage: a.is_finite() - False - sage: a.is_finite(4) - False - sage: c = a[5] - sage: a.is_finite() - True - sage: a.is_finite(4) - True - """ - if self.order is inf: - return True - - s = self._stream - - if n is None: - n = len(s) - - if s.is_constant() and all(s[i] == 0 for i in range(n-1, max(n,len(s)))): - return True - - return False - - def exponential(self): - """ - TESTS:: - - sage: def inv_factorial(): - ....: q = 1 - ....: yield 0 - ....: yield q - ....: n = 2 - ....: while True: - ....: q = q / n - ....: yield q - ....: n += 1 - sage: L = LazyPowerSeriesRing(QQ) - sage: f = L(inv_factorial()) #e^(x)-1 - sage: u = f.exponential() - sage: g = inv_factorial() - sage: z1 = [1,1,2,5,15,52,203,877,4140,21147,115975] - sage: l1 = [z*next(g) for z in z1] - sage: l1 = [1] + l1[1:] - sage: u.coefficients(11) - [1, 1, 1, 5/6, 5/8, 13/30, 203/720, 877/5040, 23/224, 1007/17280, 4639/145152] - sage: l1 == u.coefficients(11) - True - """ - base_ring = self.parent().base_ring() - s = self.parent()() - s.define( (self.derivative()*s).integral(base_ring(1)) ) - return s - - def __getitem__(self, i): - """ - Return the ith coefficient of self. - - EXAMPLES:: - - sage: L = LazyPowerSeriesRing(QQ) - sage: f = L([1,2,3,0]) - sage: [f[i] for i in range(5)] - [1, 2, 3, 0, 0] - """ - return self.coefficient(i) - - - ######################### - #Min and max restriction# - ######################### - def restricted(self, min=None, max=None): - """ - Return the power series restricted to the coefficients starting at - ``min`` and going up to, but not including ``max``. - - If ``min`` is not specified, then it is assumed to be zero. If - ``max`` is not specified, then it is assumed to be infinity. - - EXAMPLES:: - - sage: L = LazyPowerSeriesRing(QQ) - sage: a = L([1]) - sage: a.restricted().coefficients(10) - [1, 1, 1, 1, 1, 1, 1, 1, 1, 1] - sage: a.restricted(min=2).coefficients(10) - [0, 0, 1, 1, 1, 1, 1, 1, 1, 1] - sage: a.restricted(max=5).coefficients(10) - [1, 1, 1, 1, 1, 0, 0, 0, 0, 0] - sage: a.restricted(min=2, max=6).coefficients(10) - [0, 0, 1, 1, 1, 1, 0, 0, 0, 0] - """ - - if ((min is None and max is None) or - (max is None and self.get_aorder() >= min)): - return self - - if min is None: - min = 0 - return self._new(partial(self._restricted_gen, min, max), - lambda ao: builtins.max(ao, min), self) - - def _restricted_gen(self, mn, mx, ao): - """ - EXAMPLES:: - - sage: L = LazyPowerSeriesRing(QQ) - sage: a = L([1]) - sage: g = a._restricted_gen(None, None, 2) - sage: [next(g) for i in range(10)] - [0, 0, 1, 1, 1, 1, 1, 1, 1, 1] - sage: g = a._restricted_gen(1, None, 2) - sage: [next(g) for i in range(10)] - [0, 0, 1, 1, 1, 1, 1, 1, 1, 1] - sage: g = a._restricted_gen(3, None, 2) - sage: [next(g) for i in range(10)] - [0, 0, 0, 1, 1, 1, 1, 1, 1, 1] - - :: - - sage: g = a._restricted_gen(1, 5, 2) - sage: [next(g) for i in range(6)] - [0, 0, 1, 1, 1, 0] - """ - BR = self.parent().base_ring() - for n in range(max(mn,ao)): - yield BR(0) - - n = max(mn, ao) - while True: - if mx is not None and n >= mx: - yield BR(0) - break - else: - yield self._stream[n] - n += 1 - - - ############# - #Change Ring# - ############# - def _change_ring_gen(self, R, ao): - """ - EXAMPLES:: - - sage: L = LazyPowerSeriesRing(QQ) - sage: L2 = LazyPowerSeriesRing(RR) - sage: a = L([1]) - sage: b = L2(a) - sage: b.parent() - Lazy Power Series Ring over Real Field with 53 bits of precision - sage: b.coefficients(3) - [1.00000000000000, 1.00000000000000, 1.00000000000000] - """ - for n in range(ao): - yield R(0) - - n = ao - while True: - yield R(self._stream[n]) - n += 1 - -################################# - - -def uninitialized(): - """ - EXAMPLES:: - - sage: from sage.combinat.species.series import uninitialized - sage: uninitialized() - Traceback (most recent call last): - ... - RuntimeError: we should never be here - """ - raise RuntimeError("we should never be here") diff --git a/src/sage/combinat/species/series_order.py b/src/sage/combinat/species/series_order.py deleted file mode 100644 index f4c42d032bd..00000000000 --- a/src/sage/combinat/species/series_order.py +++ /dev/null @@ -1,294 +0,0 @@ -""" -Series Order - -This file provides some utility classes which are useful when -working with unknown, known, and infinite series orders for -univariate power series. - -This code is based on the work of Ralf Hemmecke and Martin Rubey's -Aldor-Combinat, which can be found at -http://www.risc.uni-linz.ac.at/people/hemmecke/aldor/combinat/index.html. -In particular, the relevant section for this file can be found at -http://www.risc.uni-linz.ac.at/people/hemmecke/AldorCombinat/combinatsu30.html. -""" -from sage.rings.integer import Integer - -class SeriesOrderElement: - pass - -class InfiniteSeriesOrder(SeriesOrderElement): - def __repr__(self): - """ - EXAMPLES:: - - sage: from sage.combinat.species.series_order import * - sage: o = InfiniteSeriesOrder(); o - Infinite series order - """ - return "Infinite series order" - - def __add__(self, x): - """ - EXAMPLES:: - - sage: from sage.combinat.species.series_order import * - sage: o = InfiniteSeriesOrder() - sage: o + 2 - Infinite series order - sage: o + o - Infinite series order - - :: - - sage: u = UnknownSeriesOrder() - sage: o + u - Unknown series order - - TESTS:: - - sage: o + -1 - Traceback (most recent call last): - ... - ValueError: x must be a positive integer - """ - if isinstance(x, (int, Integer)): - if x < 0: - raise ValueError("x must be a positive integer") - return self - - if isinstance(x, InfiniteSeriesOrder): - return self - - if isinstance(x, UnknownSeriesOrder): - return x - - raise TypeError("x must be a positive integer or a SeriesOrderElement") - - __radd__ = __add__ - - - def __mul__(self, x): - """ - EXAMPLES:: - - sage: from sage.combinat.species.series_order import * - sage: o = InfiniteSeriesOrder() - sage: o * 2 - Infinite series order - sage: o * o - Infinite series order - - :: - - sage: u = UnknownSeriesOrder() - sage: o * u - Unknown series order - - TESTS:: - - sage: o * -1 - Traceback (most recent call last): - ... - ValueError: x must be a positive integer - """ - if isinstance(x, (int, Integer)): - if x < 0: - raise ValueError("x must be a positive integer") - elif x == 0: - return x - return self - - if isinstance(x, InfiniteSeriesOrder): - return self - - if isinstance(x, UnknownSeriesOrder): - return x - - raise TypeError("x must be a positive integer or a SeriesOrderElement") - - __rmul__ = __mul__ - - def __lt__(self, x): - """ - EXAMPLES:: - - sage: from sage.combinat.species.series_order import * - sage: o = InfiniteSeriesOrder() - sage: o < 2 - False - sage: o < o - False - - :: - - sage: u = UnknownSeriesOrder() - sage: o < u - False - sage: 2 < o # TODO: Not Implemented - True - """ - if isinstance(x, (int, Integer)): - if x < 0: - raise ValueError("x must be a positive integer") - return False - - if isinstance(x, InfiniteSeriesOrder): - return False - - if isinstance(x, UnknownSeriesOrder): - return False - - - raise TypeError("x must be a positive integer or a SeriesOrderElement") - - - def __gt__(self, x): - """ - EXAMPLES:: - - sage: from sage.combinat.species.series_order import * - sage: o = InfiniteSeriesOrder() - sage: o > 2 - True - """ - return True - -class UnknownSeriesOrder(SeriesOrderElement): - def __repr__(self): - """ - EXAMPLES:: - - sage: from sage.combinat.species.series_order import * - sage: u = UnknownSeriesOrder(); u - Unknown series order - """ - return "Unknown series order" - - def __add__(self, x): - """ - EXAMPLES:: - - sage: from sage.combinat.species.series_order import * - sage: u = UnknownSeriesOrder() - sage: u + 2 - Unknown series order - sage: u + u - Unknown series order - """ - - if isinstance(x, (int, Integer)): - if x < 0: - raise ValueError("x must be a positive integer") - return self - - if isinstance(x, SeriesOrderElement): - return self - - raise TypeError("x must be a positive integer or a SeriesOrderElement") - - __radd__ = __add__ - - - def __mul__(self, x): - """ - EXAMPLES:: - - sage: from sage.combinat.species.series_order import * - sage: u = UnknownSeriesOrder() - sage: u * 2 - Unknown series order - sage: u * u - Unknown series order - """ - if isinstance(x, (int, Integer)): - if x < 0: - raise ValueError("x must be a positive integer") - return self - - if isinstance(x, SeriesOrderElement): - return self - - raise TypeError("x must be a positive integer or a SeriesOrderElement") - - __rmul__ = __mul__ - - def __lt__(self, x): - """ - EXAMPLES:: - - sage: from sage.combinat.species.series_order import * - sage: u = UnknownSeriesOrder() - sage: u < 2 - True - sage: o = InfiniteSeriesOrder() - sage: u < o - True - """ - if isinstance(x, (int, Integer)): - if x < 0: - raise ValueError("x must be a positive integer") - return True - - if isinstance(x, SeriesOrderElement): - return True - - raise TypeError("x must be a positive integer or a SeriesOrderElement") - - def __gt__(self, x): - """ - EXAMPLES:: - - sage: from sage.combinat.species.series_order import * - sage: u = UnknownSeriesOrder() - sage: u > 2 - False - """ - return False - -def bounded_decrement(x): - """ - EXAMPLES:: - - sage: from sage.combinat.species.series_order import * - sage: u = UnknownSeriesOrder() - sage: bounded_decrement(u) - Unknown series order - sage: bounded_decrement(4) - 3 - sage: bounded_decrement(0) - 0 - """ - if isinstance(x, SeriesOrderElement): - return x - - if isinstance(x, (int, Integer)): - if x < 0: - raise ValueError("x must be a positive integer") - return max(0, x - 1) - - raise TypeError("x must be a positive integer or a SeriesOrderElement") - - -def increment(x): - """ - EXAMPLES:: - - sage: from sage.combinat.species.series_order import * - sage: u = UnknownSeriesOrder() - sage: increment(u) - Unknown series order - sage: increment(2) - 3 - """ - if isinstance(x, SeriesOrderElement): - return x + 1 - - if isinstance(x, (int, Integer)): - if x < 0: - raise ValueError("x must be a positive integer") - return x+1 - - raise TypeError("x must be a positive integer or a SeriesOrderElement") - -inf = InfiniteSeriesOrder() -unk = UnknownSeriesOrder() diff --git a/src/sage/combinat/species/set_species.py b/src/sage/combinat/species/set_species.py index 6bf21d4c462..64313334a2f 100644 --- a/src/sage/combinat/species/set_species.py +++ b/src/sage/combinat/species/set_species.py @@ -17,7 +17,6 @@ #***************************************************************************** from .species import GenericCombinatorialSpecies -from .generating_series import _integers_from from sage.combinat.species.structure import GenericSpeciesStructure from sage.combinat.species.misc import accept_size from sage.structure.unique_representation import UniqueRepresentation @@ -102,11 +101,11 @@ def __init__(self, min=None, max=None, weight=None): sage: E = species.SetSpecies() sage: E.structures([1,2,3]).list() [{1, 2, 3}] - sage: E.isotype_generating_series().coefficients(4) + sage: E.isotype_generating_series()[0:4] [1, 1, 1, 1] sage: S = species.SetSpecies() - sage: c = S.generating_series().coefficients(3) + sage: c = S.generating_series()[0:3] sage: S._check() True sage: S == loads(dumps(S)) @@ -130,7 +129,7 @@ def _structures(self, structure_class, labels): _isotypes = _structures - def _gs_iterator(self, base_ring): + def _gs_callable(self, base_ring, n): r""" The generating series for the species of sets is given by `e^x`. @@ -139,15 +138,14 @@ def _gs_iterator(self, base_ring): sage: S = species.SetSpecies() sage: g = S.generating_series() - sage: g.coefficients(10) + sage: [g.coefficient(i) for i in range(10)] [1, 1, 1/2, 1/6, 1/24, 1/120, 1/720, 1/5040, 1/40320, 1/362880] sage: [g.count(i) for i in range(10)] [1, 1, 1, 1, 1, 1, 1, 1, 1, 1] """ - for n in _integers_from(0): - yield base_ring(self._weight / factorial(n)) + return base_ring(self._weight / factorial(n)) - def _itgs_list(self, base_ring): + def _itgs_list(self, base_ring, n): r""" The isomorphism type generating series for the species of sets is `\frac{1}{1-x}`. @@ -156,12 +154,12 @@ def _itgs_list(self, base_ring): sage: S = species.SetSpecies() sage: g = S.isotype_generating_series() - sage: g.coefficients(10) + sage: g[0:10] [1, 1, 1, 1, 1, 1, 1, 1, 1, 1] sage: [g.count(i) for i in range(10)] [1, 1, 1, 1, 1, 1, 1, 1, 1, 1] """ - return [base_ring(self._weight)] + return base_ring(self._weight) def _cis(self, series_ring, base_ring): r""" @@ -172,7 +170,7 @@ def _cis(self, series_ring, base_ring): sage: S = species.SetSpecies() sage: g = S.cycle_index_series() - sage: g.coefficients(5) + sage: g[0:5] [p[], p[1], 1/2*p[1, 1] + 1/2*p[2], diff --git a/src/sage/combinat/species/species.py b/src/sage/combinat/species/species.py index 4e82c1b9b39..4cd3403ae61 100644 --- a/src/sage/combinat/species/species.py +++ b/src/sage/combinat/species/species.py @@ -22,9 +22,9 @@ sage: leaf = species.SingletonSpecies() sage: internal_node = species.SingletonSpecies(weight=q) sage: L = species.LinearOrderSpecies(min=1) - sage: T = species.CombinatorialSpecies() + sage: T = species.CombinatorialSpecies(min=1) sage: T.define(leaf + internal_node*L(T)) - sage: T.isotype_generating_series().coefficients(6) + sage: T.isotype_generating_series()[0:6] [0, 1, q, q^2 + q, q^3 + 3*q^2 + q, q^4 + 6*q^3 + 6*q^2 + q] Consider the following:: @@ -335,7 +335,7 @@ def functorial_composition(self, g): sage: WP = species.SubsetSpecies() sage: P2 = E2*E sage: G = WP.functorial_composition(P2) - sage: G.isotype_generating_series().coefficients(5) + sage: G.isotype_generating_series()[0:5] [1, 1, 2, 4, 11] """ from .functorial_composition_species import FunctorialCompositionSpecies @@ -360,7 +360,7 @@ def restricted(self, min=None, max=None): Set species with min=3 sage: S.structures([1,2]).list() [] - sage: S.generating_series().coefficients(5) + sage: S.generating_series()[0:5] [0, 0, 0, 1/6, 1/24] """ kwargs = {'min': self._min if min is None else min, @@ -431,19 +431,19 @@ def __pow__(self, n): (Singleton species) and (Singleton species)) and (Product of (Singleton species) and (Singleton species))) - sage: (X^2).generating_series().coefficients(4) + sage: (X^2).generating_series()[0:4] [0, 0, 1, 0] - sage: (X^3).generating_series().coefficients(4) + sage: (X^3).generating_series()[0:4] [0, 0, 0, 1] - sage: ((One+X)^3).generating_series().coefficients(4) + sage: ((One+X)^3).generating_series()[0:4] [1, 3, 3, 1] - sage: ((One+X)^7).generating_series().coefficients(8) + sage: ((One+X)^7).generating_series()[0:8] [1, 7, 21, 35, 35, 21, 7, 1] sage: x = QQ[['x']].gen() sage: coeffs = ((1+x+x+x**2)**25+O(x**10)).padded_list() sage: T = ((One+X+X+X^2)^25) - sage: T.generating_series().coefficients(10) == coeffs + sage: T.generating_series()[0:10] == coeffs True sage: X^1 is X True @@ -479,21 +479,23 @@ def _get_series(self, series_ring_class, prefix, base_ring=None): EXAMPLES:: sage: P = species.PermutationSpecies(min=2, max=4) - sage: P.generating_series().coefficients(8) #indirect doctest + sage: P.generating_series()[0:8] #indirect doctest [0, 0, 1, 1, 0, 0, 0, 0] """ series = self._series_helper(series_ring_class, prefix, base_ring=base_ring) - # We need to restrict the series based on the min # and max of this species. Note that if min and max # are both None (as in the default case), then the restrict # method will just return series. - return series.restricted(min=self._min, max=self._max) + if self._min is None and self._max is None: + return series + return series.parent()(lambda n: series[n], + valuation=self._min, degree=self._max) def _series_helper(self, series_ring_class, prefix, base_ring=None): """ This code handles much of the common work involved in getting the - generating series for this species (such has determining the + generating series for this species (such as determining the correct base ring to pass down to the subclass, determining which method on the subclass to call to get the series object, etc.) @@ -516,13 +518,13 @@ def _series_helper(self, series_ring_class, prefix, base_ring=None): sage: from sage.combinat.species.generating_series import OrdinaryGeneratingSeriesRing sage: S = species.SetSpecies() sage: itgs = S._series_helper(OrdinaryGeneratingSeriesRing, "itgs") - sage: itgs.coefficients(3) + sage: itgs[:3] [1, 1, 1] :: sage: itgs = S._series_helper(OrdinaryGeneratingSeriesRing, "itgs", base_ring=RDF) - sage: itgs.coefficients(3) + sage: itgs[:3] [1.0, 1.0, 1.0] """ prefix = "_" + prefix @@ -550,16 +552,16 @@ def _series_helper(self, series_ring_class, prefix, base_ring=None): except AttributeError: pass - # Try to return things like self._gs_iterator(base_ring). - # This is used when the subclass just provides an iterator + # Try to return things like self._gs_callable(base_ring). + # This is used when the subclass just provides an callable # for the coefficients of the generating series. Optionally, # the subclass can specify the order of the series. try: - iterator = getattr(self, prefix + "_iterator")(base_ring) + callable = getattr(self, prefix + "_callable") try: - return series_ring(iterator, order=self._order()) + return series_ring(lambda n: callable(base_ring, n), valuation=self._order()) except AttributeError: - return series_ring(iterator) + return series_ring(lambda n: callable(base_ring, n)) except AttributeError: pass @@ -567,7 +569,7 @@ def _series_helper(self, series_ring_class, prefix, base_ring=None): # This is used when the generating series is just a single # term. try: - return series_ring.term(getattr(self, prefix + "_term")(base_ring), + return series_ring(getattr(self, prefix + "_term")(base_ring), self._order()) except AttributeError: pass @@ -578,7 +580,7 @@ def _series_helper(self, series_ring_class, prefix, base_ring=None): # The generating series with all ones coefficients is generated this # way. try: - return series_ring(getattr(self, prefix + "_list")(base_ring)) + return series_ring(lambda n: getattr(self, prefix + "_list")(base_ring, n)) except AttributeError: pass @@ -597,7 +599,7 @@ def generating_series(self, base_ring=None): sage: P = species.PermutationSpecies() sage: g = P.generating_series() - sage: g.coefficients(4) + sage: g[:4] [1, 1, 1, 1] sage: g.counts(4) [1, 1, 2, 6] @@ -620,7 +622,7 @@ def isotype_generating_series(self, base_ring=None): sage: P = species.PermutationSpecies() sage: g = P.isotype_generating_series() - sage: g.coefficients(4) + sage: g[0:4] [1, 1, 2, 3] sage: g.counts(4) [1, 1, 2, 3] @@ -642,7 +644,7 @@ def cycle_index_series(self, base_ring=None): sage: P = species.PermutationSpecies() sage: g = P.cycle_index_series() - sage: g.coefficients(4) + sage: g[0:4] [p[], p[1], p[1, 1] + p[2], p[1, 1, 1] + p[2, 1] + p[3]] """ return self._get_series(CycleIndexSeriesRing, "cis", base_ring) @@ -774,10 +776,10 @@ def algebraic_equation_system(self): :: sage: sorted(B.digraph().vertex_iterator(), key=str) - [Combinatorial species, - Product of (Combinatorial species) and (Combinatorial species), + [Combinatorial species with min=1, + Product of (Combinatorial species with min=1) and (Combinatorial species with min=1), Singleton species, - Sum of (Singleton species) and (Product of (Combinatorial species) and (Combinatorial species))] + Sum of (Singleton species) and (Product of (Combinatorial species with min=1) and (Combinatorial species with min=1))] :: diff --git a/src/sage/combinat/species/stream.py b/src/sage/combinat/species/stream.py deleted file mode 100644 index 56644b399ef..00000000000 --- a/src/sage/combinat/species/stream.py +++ /dev/null @@ -1,501 +0,0 @@ -""" -Streams or Infinite Arrays - -This code is based on the work of Ralf Hemmecke and Martin Rubey's -Aldor-Combinat, which can be found at -http://www.risc.uni-linz.ac.at/people/hemmecke/aldor/combinat/index.html. -In particular, the relevant section for this file can be found at -http://www.risc.uni-linz.ac.at/people/hemmecke/AldorCombinat/combinatse12.html. -""" -import types -from collections.abc import Iterable -from sage.structure.sage_object import SageObject - - -def _integers_from(n): - """ - Returns a generator for the integers starting at n. - - EXAMPLES:: - - sage: from sage.combinat.species.stream import _integers_from - sage: g = _integers_from(5) - sage: [next(g) for i in range(5)] - [5, 6, 7, 8, 9] - """ - while True: - yield n - n += 1 - -def _apply_function(func, list): - """ - Returns an iterator for func(i) for i in list. - - EXAMPLES:: - - sage: from sage.combinat.species.stream import _apply_function - sage: def square(l): - ....: l.append(l[-1]^2) - ....: return l[-1] - ... - sage: l = [2] - sage: g = _apply_function(square, l) - sage: [next(g) for i in range(5)] - [4, 16, 256, 65536, 4294967296] - """ - while True: - try: - yield func(list) - except Exception: - break - -def Stream(x=None, const=None): - """ - Returns a stream. - - EXAMPLES: We can create a constant stream by just passing a - - :: - - sage: from sage.combinat.species.stream import Stream - sage: s = Stream(const=0) - sage: [s[i] for i in range(10)] - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - """ - if const is not None: - return Stream_class(const=const) - elif isinstance(x, Iterable): - return Stream_class(iter(x)) - elif isinstance(x, (types.FunctionType, types.LambdaType)): - return Stream_class(func=x) - - return Stream_class(iter([x,0])) - -class Stream_class(SageObject): - """ - EXAMPLES:: - - sage: from sage.combinat.species.stream import Stream - sage: from builtins import zip - sage: s = Stream(const=0) - sage: len(s) - 1 - sage: [x for (x,i) in zip(s, range(4))] - [0, 0, 0, 0] - sage: len(s) - 1 - - :: - - sage: s = Stream(const=4) - sage: g = iter(s) - sage: l1 = [x for (x,i) in zip(g, range(10))] - sage: l = [4 for k in range(10)] - sage: l == l1 - True - - :: - - sage: h = lambda l: 1 if len(l) < 2 else l[-1] + l[-2] - sage: fib = Stream(h) - sage: [x for (x,i) in zip(fib, range(11))] - [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89] - - :: - - sage: r = [4, 3, 5, 2, 6, 1, 1, 1, 1, 1] - sage: l = [4, 3, 5, 2, 6, 1] - sage: s = Stream(l) - sage: s[3] = -1 - sage: [x for (x,i) in zip(s, r)] - [4, 3, 5, -1, 6, 1, 1, 1, 1, 1] - sage: s[5] = -2 - sage: [x for (x,i) in zip(s, r)] - [4, 3, 5, -1, 6, -2, 1, 1, 1, 1] - sage: s[6] = -3 - sage: [x for (x,i) in zip(s, r)] - [4, 3, 5, -1, 6, -2, -3, 1, 1, 1] - sage: s[8] = -4 - sage: [x for (x,i) in zip(s, r)] - [4, 3, 5, -1, 6, -2, -3, 1, -4, 1] - sage: a = Stream(const=0) - sage: a[2] = 3 - sage: [x for (x,i) in zip(a, range(4))] - [0, 0, 3, 0] - """ - - def __init__(self, gen=None, const=None, func=None): - """ - EXAMPLES:: - - sage: from sage.combinat.species.stream import Stream_class, Stream - sage: s = Stream_class(const=4) - sage: loads(dumps(s)) - - - :: - - sage: sorted(s.__dict__.items()) - [('_constant', 4), - ('_gen', None), - ('_last_index', 0), - ('_list', [4]), - ('end_reached', True)] - - :: - - sage: s = Stream(ZZ) - sage: sorted(s.__dict__.items()) - [('_constant', None), - ('_gen', ), - ('_last_index', -1), - ('_list', []), - ('end_reached', False)] - """ - #We define self._list up here so that - #_apply_function can make use of it if - #it needs to. - self._list = [] - - - if func is not None: - if gen is not None: - raise ValueError("you cannot specify both a function and a generator") - gen = _apply_function(func, self._list) - - #Constant stream - if const is not None: - self._list = [const] - self._last_index = 0 # last_index == len(self._list) - 1 - self._gen = None - self._constant = const - self.end_reached = True - else: - self._last_index = -1 # last_index == len(self._list) - 1 - self._gen = gen - self._constant = const - self.end_reached = False - - def __setitem__(self, i, t): - """ - Set the i-th entry of self to t. - - EXAMPLES:: - - sage: from sage.combinat.species.stream import Stream - - :: - - sage: s = Stream(const=0) - sage: s[5] - 0 - sage: s.data() - [0] - sage: s[5] = 5 - sage: s[5] - 5 - sage: s.data() - [0, 0, 0, 0, 0, 5] - - :: - - sage: s = Stream(ZZ) - sage: s[10] - -5 - sage: s.data() - [0, 1, -1, 2, -2, 3, -3, 4, -4, 5, -5] - sage: s[10] = 10 - sage: s.data() - [0, 1, -1, 2, -2, 3, -3, 4, -4, 5, 10] - """ - # Compute all of the coefficients up to (and including) the ith one - self[i] - - if i < len(self._list): - #If we are here, we can just change the entry in self._list - self._list[i] = t - else: - #If we are here, then the stream has become constant. We just - #extend self._list with self._constant and then change the - #last entry. - self._list += [ self._constant ] * (i+1 - len(self._list)) - self._last_index = i - self._list[i] = t - - def set_gen(self, gen): - """ - EXAMPLES:: - - sage: from sage.combinat.species.stream import Stream - sage: from builtins import zip - sage: fib = Stream() - sage: def g(): - ....: yield 1 - ....: yield 1 - ....: n = 0 - ....: while True: - ....: yield fib[n] + fib[n+1] - ....: n += 1 - - :: - - sage: fib.set_gen(g()) - sage: [x for (x,i) in zip(fib, range(11))] - [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89] - - :: - - sage: l = [4,3,5,2,6,1] - sage: s = Stream(l) - sage: s[3] - 2 - sage: len(s) - 4 - sage: g = iter(l) - sage: s.set_gen(g) - sage: s[5] - 3 - sage: len(s) - 6 - """ - self._gen = gen - self.end_reached = False - - def map(self, f): - """ - EXAMPLES:: - - sage: from sage.combinat.species.stream import Stream - sage: s = Stream(ZZ) - sage: square = lambda x: x^2 - sage: ss = s.map(square) - sage: [ss[i] for i in range(10)] - [0, 1, 1, 4, 4, 9, 9, 16, 16, 25] - - TESTS:: - - sage: from builtins import zip - sage: f = lambda l: 0 if len(l) == 0 else l[-1] + 1 - sage: o = Stream(f) - sage: [x for (x,i) in zip(o, range(10))] - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - sage: double = lambda z: 2*z - sage: t = o.map(double) - sage: [x for (x,i) in zip(t, range(10))] - [0, 2, 4, 6, 8, 10, 12, 14, 16, 18] - - :: - - sage: double = lambda z: 2*z - sage: o = Stream([0,1,2,3]) - sage: [x for (x,i) in zip(o, range(6))] - [0, 1, 2, 3, 3, 3] - sage: t = o.map(double) - sage: [x for (x,i) in zip(t, range(6))] - [0, 2, 4, 6, 6, 6] - """ - return Stream((f(x) for x in self)) - - def __getitem__(self, i): - """ - Returns the ith entry of self. - - EXAMPLES:: - - sage: from sage.combinat.species.stream import Stream - sage: s = Stream(ZZ) - sage: [s[i] for i in range(10)] - [0, 1, -1, 2, -2, 3, -3, 4, -4, 5] - sage: s[1] - 1 - - :: - - sage: s = Stream([1,2,3]) - sage: [s[i] for i in range(10)] - [1, 2, 3, 3, 3, 3, 3, 3, 3, 3] - - :: - - sage: s = Stream(QQ) - sage: s[10] - -3 - """ - if i <= self._last_index: - return self._list[i] - elif self.end_reached: - if self._constant is not False: - return self._constant - else: - raise IndexError("out of position") - else: - while self._last_index < i: - try: - self._list.append(next(self._gen)) - self._last_index += 1 - except StopIteration: - self.end_reached = True - self._constant = self._list[-1] - return self[i] - - return self._list[i] - - - def __iter__(self): - """ - EXAMPLES:: - - sage: from sage.combinat.species.stream import Stream - sage: s = Stream([1,2,3]) - sage: g = iter(s) - sage: [next(g) for i in range(5)] - [1, 2, 3, 3, 3] - """ - i = 0 - while True: - try: - yield self[i] - except IndexError: - break - i += 1 - - def __len__(self): - """ - Returns the number of coefficients computed so far. - - EXAMPLES:: - - sage: from sage.combinat.species.stream import Stream - sage: l = [4,3,5,7,4,1,9,7] - sage: s = Stream(l) - sage: s[3] - 7 - sage: len(s) - 4 - sage: s[3] - 7 - sage: len(s) - 4 - sage: s[1] - 3 - sage: len(s) - 4 - sage: s[4] - 4 - sage: len(s) - 5 - - TESTS:: - - sage: l = ['Hello', ' ', 'World', '!'] - sage: s = Stream(l) - sage: len(s) - 0 - sage: s[2] - 'World' - sage: len(s) - 3 - sage: u = "" - sage: for i in range(len(s)): u += s[i] - sage: u - 'Hello World' - sage: v = "" - sage: for i in range(10): v += s[i] - sage: v - 'Hello World!!!!!!!' - sage: len(s) - 4 - """ - return len(self._list) - - number_computed = __len__ - - def data(self): - """ - Returns a list of all the coefficients computed so far. - - EXAMPLES:: - - sage: from sage.combinat.species.stream import Stream, _integers_from - sage: s = Stream(_integers_from(3)) - sage: s.data() - [] - sage: s[5] - 8 - sage: s.data() - [3, 4, 5, 6, 7, 8] - """ - return self._list - - def is_constant(self): - """ - Returns True if and only if - - EXAMPLES:: - - sage: from sage.combinat.species.stream import Stream - sage: s = Stream([1,2,3]) - sage: s.is_constant() - False - sage: s[3] - 3 - sage: s.data() - [1, 2, 3] - sage: s.is_constant() - True - - TESTS:: - - sage: l = [2,3,5,7,11,0] - sage: s = Stream(l) - sage: s.is_constant() - False - sage: s[3] - 7 - sage: s.is_constant() - False - sage: s[5] - 0 - sage: s.is_constant() - False - sage: s[6] - 0 - sage: s.is_constant() - True - - :: - - sage: s = Stream(const='I am constant.') - sage: s.is_constant() - True - """ - return self.end_reached - - - def stretch(self, k): - """ - EXAMPLES:: - - sage: from sage.combinat.species.stream import Stream - sage: s = Stream(range(1, 10)) - sage: s2 = s.stretch(2) - sage: [s2[i] for i in range(10)] - [1, 0, 2, 0, 3, 0, 4, 0, 5, 0] - """ - return Stream(self._stretch_gen(k)) - - def _stretch_gen(self, k): - """ - EXAMPLES:: - - sage: from sage.combinat.species.stream import Stream - sage: s = Stream(range(1, 10)) - sage: g = s._stretch_gen(2) - sage: [next(g) for i in range(10)] - [1, 0, 2, 0, 3, 0, 4, 0, 5, 0] - """ - yield self[0] - for i in _integers_from(1): - for j in range(k-1): - yield 0 - yield self[i] diff --git a/src/sage/combinat/species/structure.py b/src/sage/combinat/species/structure.py index bdd6f710600..b37f0837ded 100644 --- a/src/sage/combinat/species/structure.py +++ b/src/sage/combinat/species/structure.py @@ -390,7 +390,7 @@ def __iter__(self): try: if self.cardinality() == 0: return iter([]) - except RuntimeError: + except TypeError: raise NotImplementedError return getattr(self._species, self._iterator)(self._structure_class, self._labels) diff --git a/src/sage/combinat/species/subset_species.py b/src/sage/combinat/species/subset_species.py index 0a41c7dbc9a..763b8bc8053 100644 --- a/src/sage/combinat/species/subset_species.py +++ b/src/sage/combinat/species/subset_species.py @@ -19,7 +19,6 @@ from .species import GenericCombinatorialSpecies from .set_species import SetSpecies -from .generating_series import _integers_from from .structure import GenericSpeciesStructure from sage.combinat.species.misc import accept_size from sage.structure.unique_representation import UniqueRepresentation @@ -143,13 +142,13 @@ def __init__(self, min=None, max=None, weight=None): EXAMPLES:: sage: S = species.SubsetSpecies() - sage: S.generating_series().coefficients(5) + sage: S.generating_series()[0:5] [1, 2, 2, 4/3, 2/3] - sage: S.isotype_generating_series().coefficients(5) + sage: S.isotype_generating_series()[0:5] [1, 2, 3, 4, 5] sage: S = species.SubsetSpecies() - sage: c = S.generating_series().coefficients(3) + sage: c = S.generating_series()[0:3] sage: S._check() True sage: S == loads(dumps(S)) @@ -187,7 +186,7 @@ def _isotypes(self, structure_class, labels): for i in range(len(labels)+1): yield structure_class(self, labels, range(1, i+1)) - def _gs_iterator(self, base_ring): + def _gs_callable(self, base_ring, n): """ The generating series for the species of subsets is `e^{2x}`. @@ -195,13 +194,12 @@ def _gs_iterator(self, base_ring): EXAMPLES:: sage: S = species.SubsetSpecies() - sage: S.generating_series().coefficients(5) + sage: [S.generating_series().coefficient(i) for i in range(5)] [1, 2, 2, 4/3, 2/3] """ - for n in _integers_from(0): - yield base_ring(2)**n / base_ring(factorial(n)) + return base_ring(2)**n / base_ring(factorial(n)) - def _itgs_iterator(self, base_ring): + def _itgs_callable(self, base_ring, n): r""" The generating series for the species of subsets is `e^{2x}`. @@ -209,11 +207,10 @@ def _itgs_iterator(self, base_ring): EXAMPLES:: sage: S = species.SubsetSpecies() - sage: S.isotype_generating_series().coefficients(5) + sage: S.isotype_generating_series()[0:5] [1, 2, 3, 4, 5] """ - for n in _integers_from(1): - yield base_ring(n) + return base_ring(n + 1) def _cis(self, series_ring, base_ring): r""" @@ -226,7 +223,7 @@ def _cis(self, series_ring, base_ring): EXAMPLES:: sage: S = species.SubsetSpecies() - sage: S.cycle_index_series().coefficients(5) + sage: S.cycle_index_series()[0:5] [p[], 2*p[1], 2*p[1, 1] + p[2], diff --git a/src/sage/combinat/species/sum_species.py b/src/sage/combinat/species/sum_species.py index 05950420dbb..e6644bdfa29 100644 --- a/src/sage/combinat/species/sum_species.py +++ b/src/sage/combinat/species/sum_species.py @@ -32,7 +32,7 @@ def __init__(self, F, G, min=None, max=None, weight=None): sage: S = species.PermutationSpecies() sage: A = S+S - sage: A.generating_series().coefficients(5) + sage: A.generating_series()[:5] [2, 2, 2, 2, 2] sage: P = species.PermutationSpecies() @@ -142,13 +142,12 @@ def _gs(self, series_ring, base_ring): sage: P = species.PermutationSpecies() sage: F = P + P - sage: F.generating_series().coefficients(5) + sage: F.generating_series()[:5] [2, 2, 2, 2, 2] """ return (self.left_summand().generating_series(base_ring) + self.right_summand().generating_series(base_ring)) - def _itgs(self, series_ring, base_ring): """ Returns the isomorphism type generating series of this species. @@ -157,7 +156,7 @@ def _itgs(self, series_ring, base_ring): sage: P = species.PermutationSpecies() sage: F = P + P - sage: F.isotype_generating_series().coefficients(5) + sage: F.isotype_generating_series()[:5] [2, 2, 4, 6, 10] """ return (self.left_summand().isotype_generating_series(base_ring) + @@ -171,7 +170,7 @@ def _cis(self, series_ring, base_ring): sage: P = species.PermutationSpecies() sage: F = P + P - sage: F.cycle_index_series().coefficients(5) + sage: F.cycle_index_series()[:5] [2*p[], 2*p[1], 2*p[1, 1] + 2*p[2], diff --git a/src/sage/combinat/tutorial.py b/src/sage/combinat/tutorial.py index e9df13c746c..90ec8eea713 100644 --- a/src/sage/combinat/tutorial.py +++ b/src/sage/combinat/tutorial.py @@ -320,8 +320,7 @@ Then we create a “free” power series, which we name, and which we then define by a recursive equation:: - sage: C = L() - sage: C._name = 'C' + sage: C = L.undefined(valuation=1) sage: C.define( z + C * C ) :: @@ -1672,7 +1671,7 @@ We begin by redefining the complete binary trees; to do so, we stipulate the recurrence relation directly on the sets:: - sage: BT = CombinatorialSpecies() + sage: BT = CombinatorialSpecies(min=1) sage: Leaf = SingletonSpecies() sage: BT.define( Leaf + (BT*BT) ) @@ -1697,7 +1696,7 @@ We recover the generating function for the Catalan numbers:: sage: g = BT.isotype_generating_series(); g - x + x^2 + 2*x^3 + 5*x^4 + 14*x^5 + O(x^6) + z + z^2 + 2*z^3 + 5*z^4 + 14*z^5 + 42*z^6 + 132*z^7 + O(z^8) which is returned in the form of a lazy power series:: @@ -1715,7 +1714,7 @@ The Fibonacci sequence is easily recognized here, hence the name:: - sage: L = FW.isotype_generating_series().coefficients(15); L + sage: L = FW.isotype_generating_series()[:15]; L [1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987] :: diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index e349708c1ed..a8118dd4746 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -21,10 +21,10 @@ example, we can add two streams:: sage: from sage.data_structures.stream import * - sage: f = Stream_function(lambda n: n, QQ, True, 0) + sage: f = Stream_function(lambda n: n, True, 0) sage: [f[i] for i in range(10)] [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - sage: g = Stream_function(lambda n: 1, QQ, True, 0) + sage: g = Stream_function(lambda n: 1, True, 0) sage: [g[i] for i in range(10)] [1, 1, 1, 1, 1, 1, 1, 1, 1, 1] sage: h = Stream_add(f, g) @@ -52,7 +52,7 @@ Two streams can be composed:: - sage: g = Stream_function(lambda n: n, QQ, True, 1) + sage: g = Stream_function(lambda n: n, True, 1) sage: h = Stream_cauchy_compose(f, g) sage: [h[i] for i in range(10)] [0, 1, 4, 14, 46, 145, 444, 1331, 3926, 11434] @@ -71,7 +71,7 @@ Finally, we can apply an arbitrary functions to the elements of a stream:: - sage: h = Stream_map_coefficients(f, lambda n: n^2, QQ) + sage: h = Stream_map_coefficients(f, lambda n: n^2) sage: [h[i] for i in range(10)] [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] @@ -80,7 +80,6 @@ - Kwankyu Lee (2019-02-24): initial version - Tejasvi Chebrolu, Martin Rubey, Travis Scrimshaw (2021-08): refactored and expanded functionality - """ # **************************************************************************** @@ -98,7 +97,11 @@ from sage.rings.integer_ring import ZZ from sage.rings.infinity import infinity from sage.arith.misc import divisors +from sage.misc.misc_c import prod from sage.combinat.integer_vector_weighted import iterator_fast as wt_int_vec_iter +from sage.combinat.sf.sfa import _variables_recursive, _raise_variables +from sage.categories.hopf_algebras_with_basis import HopfAlgebrasWithBasis + class Stream(): """ @@ -168,6 +171,15 @@ class Stream_inexact(Stream): - ``sparse`` -- boolean; whether the implementation of the stream is sparse - ``approximate_order`` -- integer; a lower bound for the order of the stream + + .. TODO:: + + The ``approximate_order`` is currently only updated when + invoking :meth:`order`. It might make sense to update it + whenever the coefficient one larger than the current + ``approximate_order`` is computed, since in some methods this + will allow shortcuts. + """ def __init__(self, is_sparse, approximate_order): """ @@ -178,7 +190,7 @@ def __init__(self, is_sparse, approximate_order): sage: from sage.data_structures.stream import Stream_inexact sage: from sage.data_structures.stream import Stream_function - sage: g = Stream_function(lambda n: n, QQ, False, 0) + sage: g = Stream_function(lambda n: n, False, 0) sage: isinstance(g, Stream_inexact) True """ @@ -198,7 +210,7 @@ def is_nonzero(self): EXAMPLES:: sage: from sage.data_structures.stream import Stream_function - sage: CS = Stream_function(lambda n: 1/n, ZZ, False, 1) + sage: CS = Stream_function(lambda n: 1/n, False, 1) sage: CS.is_nonzero() False sage: CS[1] @@ -291,7 +303,7 @@ def __getitem__(self, n): EXAMPLES:: sage: from sage.data_structures.stream import Stream_function - sage: f = Stream_function(lambda n: n^2, QQ, True, 0) + sage: f = Stream_function(lambda n: n^2, True, 0) sage: f[3] 9 sage: f._cache @@ -301,7 +313,7 @@ def __getitem__(self, n): sage: f._cache {0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81} - sage: f = Stream_function(lambda n: n^2, QQ, False, 0) + sage: f = Stream_function(lambda n: n^2, False, 0) sage: f[3] 9 sage: f._cache @@ -339,14 +351,14 @@ def iterate_coefficients(self): EXAMPLES:: sage: from sage.data_structures.stream import Stream_function, Stream_cauchy_compose - sage: f = Stream_function(lambda n: 1, ZZ, False, 1) - sage: g = Stream_function(lambda n: n^3, ZZ, False, 1) + sage: f = Stream_function(lambda n: 1, False, 1) + sage: g = Stream_function(lambda n: n^3, False, 1) sage: h = Stream_cauchy_compose(f, g) sage: n = h.iterate_coefficients() sage: [next(n) for i in range(10)] [1, 9, 44, 207, 991, 4752, 22769, 109089, 522676, 2504295] """ - n = self._approximate_order + n = self._approximate_order # TODO: shouldn't this be self._offset? while True: yield self.get_coefficient(n) n += 1 @@ -359,7 +371,7 @@ def order(self): EXAMPLES:: sage: from sage.data_structures.stream import Stream_function - sage: f = Stream_function(lambda n: n, QQ, True, 0) + sage: f = Stream_function(lambda n: n, True, 0) sage: f.order() 1 """ @@ -402,8 +414,8 @@ def __ne__(self, other): EXAMPLES:: sage: from sage.data_structures.stream import Stream_function - sage: f = Stream_function(lambda n: n, QQ, True, 0) - sage: g = Stream_function(lambda n: n^2, QQ, True, 0) + sage: f = Stream_function(lambda n: n, True, 0) + sage: g = Stream_function(lambda n: n^2, True, 0) sage: f != g False sage: f[1], g[1] @@ -421,8 +433,8 @@ def __ne__(self, other): Checking the dense implementation:: - sage: f = Stream_function(lambda n: n if n > 0 else 0, QQ, False, -3) - sage: g = Stream_function(lambda n: n^2, QQ, False, 0) + sage: f = Stream_function(lambda n: n if n > 0 else 0, False, -3) + sage: g = Stream_function(lambda n: n^2, False, 0) sage: f != g False sage: g != f @@ -438,8 +450,8 @@ def __ne__(self, other): sage: g != f True - sage: f = Stream_function(lambda n: n if n > 0 else 0, QQ, False, -3) - sage: g = Stream_function(lambda n: n^2, QQ, False, 0) + sage: f = Stream_function(lambda n: n if n > 0 else 0, False, -3) + sage: g = Stream_function(lambda n: n^2, False, 0) sage: _ = f[5], g[1] sage: f != g False @@ -451,8 +463,8 @@ def __ne__(self, other): sage: g != f True - sage: f = Stream_function(lambda n: n if n > 0 else 0, QQ, False, -3) - sage: g = Stream_function(lambda n: n^2, QQ, False, 0) + sage: f = Stream_function(lambda n: n if n > 0 else 0, False, -3) + sage: g = Stream_function(lambda n: n^2, False, 0) sage: _ = g[5], f[1] sage: f != g False @@ -471,7 +483,7 @@ def __ne__(self, other): for i in self._cache: if i in other._cache and other._cache[i] != self._cache[i]: return True - else: # they are dense + else: # they are dense # Make ``self`` have the smaller approximate order. if self._approximate_order > other._approximate_order: self, other = other, self @@ -491,6 +503,7 @@ def __ne__(self, other): return False + class Stream_exact(Stream): r""" A stream of eventually constant coefficients. @@ -533,7 +546,8 @@ def __init__(self, initial_coefficients, is_sparse, constant=None, degree=None, # We do not insist that the last entry of # initial_coefficients is different from constant in case - # comparisons can be expensive such as in the symbolic ring + # comparisons can be expensive such as in the symbolic ring, + # but we remove zeros for i, v in enumerate(initial_coefficients): if v: order += i @@ -686,7 +700,7 @@ def __ne__(self, other): return ``False``:: sage: from sage.data_structures.stream import Stream_function - sage: f = Stream_function(lambda n: 2 if n == 0 else 1, ZZ, False, 0) + sage: f = Stream_function(lambda n: 2 if n == 0 else 1, False, 0) sage: s == f False sage: s != f @@ -735,77 +749,87 @@ def _polynomial_part(self, R): return R(self._initial_coefficients).shift(v) -class Stream_function(Stream_inexact): +class Stream_iterator(Stream_inexact): r""" - Class that creates a stream from a function on the integers. + Class that creates a stream from an iterator. INPUT: - - ``function`` -- a function that generates the - coefficients of the stream - - ``ring`` -- the base ring - - ``is_sparse`` -- boolean; specifies whether the stream is sparse + - ``iter`` -- a function that generates the coefficients of the + stream - ``approximate_order`` -- integer; a lower bound for the order of the stream + Instances of this class are always dense. + EXAMPLES:: - sage: from sage.data_structures.stream import Stream_function - sage: f = Stream_function(lambda n: n^2, ZZ, False, 1) - sage: f[3] - 9 + sage: from sage.data_structures.stream import Stream_iterator + sage: f = Stream_iterator(iter(NonNegativeIntegers()), 0) sage: [f[i] for i in range(10)] - [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] - """ + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - def __init__(self, function, ring, is_sparse, approximate_order): + sage: f = Stream_iterator(iter(NonNegativeIntegers()), 1) + sage: [f[i] for i in range(10)] + [0, 0, 1, 2, 3, 4, 5, 6, 7, 8] + + """ + def __init__(self, iter, approximate_order): """ Initialize. TESTS:: - sage: from sage.data_structures.stream import Stream_function - sage: f = Stream_function(lambda n: 1, ZZ, False, 1) + sage: from sage.data_structures.stream import Stream_iterator + sage: f = Stream_iterator(iter(NonNegativeIntegers()), 0) sage: TestSuite(f).run(skip="_test_pickling") """ - self._function = function - self._ring = ring - super().__init__(is_sparse, approximate_order) + self.iterate_coefficients = lambda: iter + super().__init__(False, approximate_order) - def get_coefficient(self, n): - """ - Return the ``n``-th coefficient of ``self``. - INPUT: +class Stream_function(Stream_inexact): + r""" + Class that creates a stream from a function on the integers. - - ``n`` -- integer; the degree for the coefficient + INPUT: - EXAMPLES:: + - ``function`` -- a function that generates the + coefficients of the stream + - ``is_sparse`` -- boolean; specifies whether the stream is sparse + - ``approximate_order`` -- integer; a lower bound for the order + of the stream - sage: from sage.data_structures.stream import Stream_function - sage: f = Stream_function(lambda n: n, QQ, True, 0) - sage: f.get_coefficient(4) - 4 - """ - return self._ring(self._function(n)) + EXAMPLES:: - def iterate_coefficients(self): + sage: from sage.data_structures.stream import Stream_function + sage: f = Stream_function(lambda n: n^2, False, 1) + sage: f[3] + 9 + sage: [f[i] for i in range(10)] + [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] + + sage: f = Stream_function(lambda n: 1, False, 0) + sage: n = f.iterate_coefficients() + sage: [next(n) for _ in range(10)] + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1] + + sage: f = Stream_function(lambda n: n, True, 0) + sage: f.get_coefficient(4) + 4 + """ + def __init__(self, function, is_sparse, approximate_order): """ - A generator for the coefficients of ``self``. + Initialize. - EXAMPLES:: + TESTS:: sage: from sage.data_structures.stream import Stream_function - sage: f = Stream_function(lambda n: 1, QQ, False, 0) - sage: n = f.iterate_coefficients() - sage: [next(n) for _ in range(10)] - [1, 1, 1, 1, 1, 1, 1, 1, 1, 1] + sage: f = Stream_function(lambda n: 1, False, 1) + sage: TestSuite(f).run(skip="_test_pickling") """ - n = self._offset - ring = self._ring - while True: - yield ring(self._function(n)) - n += 1 + self.get_coefficient = function + super().__init__(is_sparse, approximate_order) class Stream_uninitialized(Stream_inexact): @@ -897,7 +921,7 @@ class Stream_unary(Stream_inexact): EXAMPLES:: sage: from sage.data_structures.stream import (Stream_function, Stream_cauchy_invert, Stream_lmul) - sage: f = Stream_function(lambda n: 2*n, ZZ, False, 1) + sage: f = Stream_function(lambda n: 2*n, False, 1) sage: g = Stream_cauchy_invert(f) sage: [g[i] for i in range(10)] [-1, 1/2, 0, 0, 0, 0, 0, 0, 0, 0] @@ -931,7 +955,7 @@ def __hash__(self): sage: from sage.data_structures.stream import Stream_unary sage: from sage.data_structures.stream import Stream_function - sage: M = Stream_unary(Stream_function(lambda n: 1, ZZ, False, 1), True, 0) + sage: M = Stream_unary(Stream_function(lambda n: 1, False, 1), True, 0) sage: hash(M) == hash(M) True """ @@ -948,8 +972,8 @@ def __eq__(self, other): EXAMPLES:: sage: from sage.data_structures.stream import (Stream_function, Stream_rmul) - sage: f = Stream_function(lambda n: 2*n, ZZ, False, 1) - sage: g = Stream_function(lambda n: n, ZZ, False, 1) + sage: f = Stream_function(lambda n: 2*n, False, 1) + sage: g = Stream_function(lambda n: n, False, 1) sage: h = Stream_rmul(f, 2) sage: n = Stream_rmul(g, 2) sage: h == n @@ -974,8 +998,8 @@ class Stream_binary(Stream_inexact): EXAMPLES:: sage: from sage.data_structures.stream import (Stream_function, Stream_add, Stream_sub) - sage: f = Stream_function(lambda n: 2*n, ZZ, True, 0) - sage: g = Stream_function(lambda n: n, ZZ, True, 1) + sage: f = Stream_function(lambda n: 2*n, True, 0) + sage: g = Stream_function(lambda n: n, True, 1) sage: h = Stream_add(f, g) sage: [h[i] for i in range(10)] [0, 3, 6, 9, 12, 15, 18, 21, 24, 27] @@ -1013,8 +1037,8 @@ def __hash__(self): sage: from sage.data_structures.stream import Stream_binary sage: from sage.data_structures.stream import Stream_function - sage: M = Stream_function(lambda n: n, ZZ, True, 0) - sage: N = Stream_function(lambda n: -2*n, ZZ, True, 0) + sage: M = Stream_function(lambda n: n, True, 0) + sage: N = Stream_function(lambda n: -2*n, True, 0) sage: O = Stream_binary(M, N, True, 0) sage: hash(O) == hash(O) True @@ -1032,9 +1056,9 @@ def __eq__(self, other): EXAMPLES:: sage: from sage.data_structures.stream import (Stream_function, Stream_cauchy_mul) - sage: f = Stream_function(lambda n: 2*n, ZZ, False, 1) - sage: g = Stream_function(lambda n: n, ZZ, False, 1) - sage: h = Stream_function(lambda n: 1, ZZ, False, 1) + sage: f = Stream_function(lambda n: 2*n, False, 1) + sage: g = Stream_function(lambda n: n, False, 1) + sage: h = Stream_function(lambda n: 1, False, 1) sage: t = Stream_cauchy_mul(f, g) sage: u = Stream_cauchy_mul(g, h) sage: v = Stream_cauchy_mul(h, f) @@ -1057,8 +1081,8 @@ class Stream_binaryCommutative(Stream_binary): EXAMPLES:: sage: from sage.data_structures.stream import (Stream_function, Stream_add) - sage: f = Stream_function(lambda n: 2*n, ZZ, True, 0) - sage: g = Stream_function(lambda n: n, ZZ, True, 1) + sage: f = Stream_function(lambda n: 2*n, True, 0) + sage: g = Stream_function(lambda n: n, True, 1) sage: h = Stream_add(f, g) sage: [h[i] for i in range(10)] [0, 3, 6, 9, 12, 15, 18, 21, 24, 27] @@ -1075,8 +1099,8 @@ def __hash__(self): EXAMPLES:: sage: from sage.data_structures.stream import (Stream_function, Stream_add) - sage: f = Stream_function(lambda n: 2*n, ZZ, True, 0) - sage: g = Stream_function(lambda n: n, ZZ, True, 1) + sage: f = Stream_function(lambda n: 2*n, True, 0) + sage: g = Stream_function(lambda n: n, True, 1) sage: h = Stream_add(f, g) sage: u = Stream_add(g, f) sage: hash(h) == hash(u) @@ -1095,8 +1119,8 @@ def __eq__(self, other): EXAMPLES:: sage: from sage.data_structures.stream import (Stream_function, Stream_add) - sage: f = Stream_function(lambda n: 2*n, ZZ, True, 0) - sage: g = Stream_function(lambda n: n, ZZ, True, 1) + sage: f = Stream_function(lambda n: 2*n, True, 0) + sage: g = Stream_function(lambda n: n, True, 1) sage: h = Stream_add(f, g) sage: [h[i] for i in range(10)] [0, 3, 6, 9, 12, 15, 18, 21, 24, 27] @@ -1221,8 +1245,8 @@ class Stream_add(Stream_binaryCommutative): EXAMPLES:: sage: from sage.data_structures.stream import (Stream_add, Stream_function) - sage: f = Stream_function(lambda n: n, ZZ, True, 0) - sage: g = Stream_function(lambda n: 1, ZZ, True, 0) + sage: f = Stream_function(lambda n: n, True, 0) + sage: g = Stream_function(lambda n: 1, True, 0) sage: h = Stream_add(f, g) sage: [h[i] for i in range(10)] [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] @@ -1237,8 +1261,8 @@ def __init__(self, left, right): TESTS:: sage: from sage.data_structures.stream import (Stream_function, Stream_add) - sage: f = Stream_function(lambda n: 1, ZZ, True, 0) - sage: g = Stream_function(lambda n: n^2, ZZ, True, 0) + sage: f = Stream_function(lambda n: 1, True, 0) + sage: g = Stream_function(lambda n: n^2, True, 0) sage: h = Stream_add(f, g) """ if left._is_sparse != right._is_sparse: @@ -1258,8 +1282,8 @@ def get_coefficient(self, n): EXAMPLES:: sage: from sage.data_structures.stream import (Stream_function, Stream_add) - sage: f = Stream_function(lambda n: n, ZZ, True, 0) - sage: g = Stream_function(lambda n: n^2, ZZ, True, 0) + sage: f = Stream_function(lambda n: n, True, 0) + sage: g = Stream_function(lambda n: n^2, True, 0) sage: h = Stream_add(f, g) sage: h.get_coefficient(5) 30 @@ -1281,8 +1305,8 @@ class Stream_sub(Stream_binary): EXAMPLES:: sage: from sage.data_structures.stream import (Stream_sub, Stream_function) - sage: f = Stream_function(lambda n: n, ZZ, True, 0) - sage: g = Stream_function(lambda n: 1, ZZ, True, 0) + sage: f = Stream_function(lambda n: n, True, 0) + sage: g = Stream_function(lambda n: 1, True, 0) sage: h = Stream_sub(f, g) sage: [h[i] for i in range(10)] [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8] @@ -1298,8 +1322,8 @@ def __init__(self, left, right): TESTS:: sage: from sage.data_structures.stream import (Stream_function, Stream_sub) - sage: f = Stream_function(lambda n: 1, ZZ, True, 0) - sage: g = Stream_function(lambda n: n^2, ZZ, True, 0) + sage: f = Stream_function(lambda n: 1, True, 0) + sage: g = Stream_function(lambda n: n^2, True, 0) sage: h = Stream_sub(f, g) """ if left._is_sparse != right._is_sparse: @@ -1319,8 +1343,8 @@ def get_coefficient(self, n): EXAMPLES:: sage: from sage.data_structures.stream import (Stream_function, Stream_sub) - sage: f = Stream_function(lambda n: n, ZZ, True, 0) - sage: g = Stream_function(lambda n: n^2, ZZ, True, 0) + sage: f = Stream_function(lambda n: n, True, 0) + sage: g = Stream_function(lambda n: n^2, True, 0) sage: h = Stream_sub(f, g) sage: h.get_coefficient(5) -20 @@ -1346,8 +1370,8 @@ class Stream_cauchy_mul(Stream_binary): EXAMPLES:: sage: from sage.data_structures.stream import (Stream_cauchy_mul, Stream_function) - sage: f = Stream_function(lambda n: n, ZZ, True, 0) - sage: g = Stream_function(lambda n: 1, ZZ, True, 0) + sage: f = Stream_function(lambda n: n, True, 0) + sage: g = Stream_function(lambda n: 1, True, 0) sage: h = Stream_cauchy_mul(f, g) sage: [h[i] for i in range(10)] [0, 1, 3, 6, 10, 15, 21, 28, 36, 45] @@ -1362,8 +1386,8 @@ def __init__(self, left, right): TESTS:: sage: from sage.data_structures.stream import (Stream_function, Stream_cauchy_mul) - sage: f = Stream_function(lambda n: 1, ZZ, True, 0) - sage: g = Stream_function(lambda n: n^2, ZZ, True, 0) + sage: f = Stream_function(lambda n: 1, True, 0) + sage: g = Stream_function(lambda n: n^2, True, 0) sage: h = Stream_cauchy_mul(f, g) """ if left._is_sparse != right._is_sparse: @@ -1383,8 +1407,8 @@ def get_coefficient(self, n): EXAMPLES:: sage: from sage.data_structures.stream import (Stream_function, Stream_cauchy_mul) - sage: f = Stream_function(lambda n: n, ZZ, True, 0) - sage: g = Stream_function(lambda n: n^2, ZZ, True, 0) + sage: f = Stream_function(lambda n: n, True, 0) + sage: g = Stream_function(lambda n: n^2, True, 0) sage: h = Stream_cauchy_mul(f, g) sage: h.get_coefficient(5) 50 @@ -1408,7 +1432,7 @@ def is_nonzero(self): sage: from sage.data_structures.stream import (Stream_function, ....: Stream_cauchy_mul, Stream_cauchy_invert) - sage: f = Stream_function(lambda n: n, ZZ, True, 1) + sage: f = Stream_function(lambda n: n, True, 1) sage: g = Stream_cauchy_mul(f, f) sage: g.is_nonzero() False @@ -1435,7 +1459,7 @@ class Stream_dirichlet_convolve(Stream_binary): EXAMPLES:: sage: from sage.data_structures.stream import (Stream_dirichlet_convolve, Stream_function, Stream_exact) - sage: f = Stream_function(lambda n: n, ZZ, True, 1) + sage: f = Stream_function(lambda n: n, True, 1) sage: g = Stream_exact([0], True, constant=1) sage: h = Stream_dirichlet_convolve(f, g) sage: [h[i] for i in range(1, 10)] @@ -1449,10 +1473,10 @@ class Stream_dirichlet_convolve(Stream_binary): """ def __init__(self, left, right): """ - Initalize ``self``. + Initialize ``self``. sage: from sage.data_structures.stream import (Stream_dirichlet_convolve, Stream_function, Stream_exact) - sage: f = Stream_function(lambda n: n, ZZ, True, 1) + sage: f = Stream_function(lambda n: n, True, 1) sage: g = Stream_exact([1], True, constant=0) sage: Stream_dirichlet_convolve(f, g) Traceback (most recent call last): @@ -1484,7 +1508,7 @@ def get_coefficient(self, n): EXAMPLES:: sage: from sage.data_structures.stream import (Stream_dirichlet_convolve, Stream_function, Stream_exact) - sage: f = Stream_function(lambda n: n, ZZ, True, 1) + sage: f = Stream_function(lambda n: n, True, 1) sage: g = Stream_exact([0], True, constant=1) sage: h = Stream_dirichlet_convolve(f, g) sage: h.get_coefficient(7) @@ -1513,7 +1537,7 @@ class Stream_dirichlet_invert(Stream_unary): EXAMPLES:: sage: from sage.data_structures.stream import (Stream_dirichlet_invert, Stream_function) - sage: f = Stream_function(lambda n: 1, ZZ, True, 1) + sage: f = Stream_function(lambda n: 1, True, 1) sage: g = Stream_dirichlet_invert(f) sage: [g[i] for i in range(10)] [0, 1, -1, -1, 0, -1, 1, -1, 0, 0] @@ -1582,8 +1606,8 @@ class Stream_cauchy_compose(Stream_binary): EXAMPLES:: sage: from sage.data_structures.stream import Stream_cauchy_compose, Stream_function - sage: f = Stream_function(lambda n: n, ZZ, True, 1) - sage: g = Stream_function(lambda n: 1, ZZ, True, 1) + sage: f = Stream_function(lambda n: n, True, 1) + sage: g = Stream_function(lambda n: 1, True, 1) sage: h = Stream_cauchy_compose(f, g) sage: [h[i] for i in range(10)] [0, 1, 3, 8, 20, 48, 112, 256, 576, 1280] @@ -1598,23 +1622,21 @@ def __init__(self, f, g): TESTS:: sage: from sage.data_structures.stream import Stream_function, Stream_cauchy_compose - sage: f = Stream_function(lambda n: 1, ZZ, True, 1) - sage: g = Stream_function(lambda n: n^2, ZZ, True, 1) + sage: f = Stream_function(lambda n: 1, True, 1) + sage: g = Stream_function(lambda n: n^2, True, 1) sage: h = Stream_cauchy_compose(f, g) """ #assert g._approximate_order > 0 - self._fv = f._approximate_order - self._gv = g._approximate_order - if self._fv < 0: + if f._approximate_order < 0: ginv = Stream_cauchy_invert(g) # The constant part makes no contribution to the negative. # We need this for the case so self._neg_powers[0][n] => 0. self._neg_powers = [Stream_zero(f._is_sparse), ginv] - for i in range(1, -self._fv): + for i in range(1, -f._approximate_order): self._neg_powers.append(Stream_cauchy_mul(self._neg_powers[-1], ginv)) # Placeholder None to make this 1-based. self._pos_powers = [None, g] - val = self._fv * self._gv + val = f._approximate_order * g._approximate_order super().__init__(f, g, f._is_sparse, val) def get_coefficient(self, n): @@ -1628,23 +1650,26 @@ def get_coefficient(self, n): EXAMPLES:: sage: from sage.data_structures.stream import Stream_function, Stream_cauchy_compose - sage: f = Stream_function(lambda n: n, ZZ, True, 1) - sage: g = Stream_function(lambda n: n^2, ZZ, True, 1) + sage: f = Stream_function(lambda n: n, True, 1) + sage: g = Stream_function(lambda n: n^2, True, 1) sage: h = Stream_cauchy_compose(f, g) sage: h.get_coefficient(5) 527 sage: [h.get_coefficient(i) for i in range(10)] [0, 1, 6, 28, 124, 527, 2172, 8755, 34704, 135772] """ + fv = self._left._approximate_order + gv = self._right._approximate_order if n < 0: - return sum(self._left[i] * self._neg_powers[-i][n] for i in range(self._fv, n // self._gv + 1)) + return sum(self._left[i] * self._neg_powers[-i][n] + for i in range(fv, n // gv + 1)) # n > 0 - while len(self._pos_powers) <= n // self._gv: + while len(self._pos_powers) <= n // gv: self._pos_powers.append(Stream_cauchy_mul(self._pos_powers[-1], self._right)) - ret = sum(self._left[i] * self._neg_powers[-i][n] for i in range(self._fv, 0)) + ret = sum(self._left[i] * self._neg_powers[-i][n] for i in range(fv, 0)) if n == 0: ret += self._left[0] - return ret + sum(self._left[i] * self._pos_powers[i][n] for i in range(1, n // self._gv+1)) + return ret + sum(self._left[i] * self._pos_powers[i][n] for i in range(1, n // gv+1)) class Stream_plethysm(Stream_binary): @@ -1652,37 +1677,89 @@ class Stream_plethysm(Stream_binary): Return the plethysm of ``f`` composed by ``g``. This is the plethysm `f \circ g = f(g)` when `g` is an element of - the ring of symmetric functions. + a ring of symmetric functions. INPUT: - ``f`` -- a :class:`Stream` - - ``g`` -- a :class:`Stream` with positive order - - ``p`` -- the powersum symmetric functions + - ``g`` -- a :class:`Stream` with positive order, unless ``f`` is + of :class:`Stream_exact`. + - ``p`` -- the ring of powersum symmetric functions containing ``g`` + - ``ring`` (optional, default ``None``) -- the ring the result + should be in, by default ``p`` + - ``include`` -- a list of variables to be treated as degree one + elements instead of the default degree one elements + - ``exclude`` -- a list of variables to be excluded from the + default degree one elements EXAMPLES:: - sage: from sage.data_structures.stream import Stream_function, Stream_plethysm + sage: from sage.data_structures.stream import Stream_function, Stream_plethysm sage: s = SymmetricFunctions(QQ).s() sage: p = SymmetricFunctions(QQ).p() - sage: f = Stream_function(lambda n: s[n], s, True, 1) - sage: g = Stream_function(lambda n: s[[1]*n], s, True, 1) - sage: h = Stream_plethysm(f, g, p) - sage: [s(h[i]) for i in range(5)] + sage: f = Stream_function(lambda n: s[n], True, 1) + sage: g = Stream_function(lambda n: s[[1]*n], True, 1) + sage: h = Stream_plethysm(f, g, p, s) + sage: [h[i] for i in range(5)] [0, s[1], s[1, 1] + s[2], 2*s[1, 1, 1] + s[2, 1] + s[3], 3*s[1, 1, 1, 1] + 2*s[2, 1, 1] + s[2, 2] + s[3, 1] + s[4]] - sage: u = Stream_plethysm(g, f, p) - sage: [s(u[i]) for i in range(5)] + sage: u = Stream_plethysm(g, f, p, s) + sage: [u[i] for i in range(5)] [0, s[1], s[1, 1] + s[2], s[1, 1, 1] + s[2, 1] + 2*s[3], s[1, 1, 1, 1] + s[2, 1, 1] + 3*s[3, 1] + 2*s[4]] + + This class also handles the plethysm of an exact stream with a + stream of order `0`:: + + sage: from sage.data_structures.stream import Stream_exact + sage: f = Stream_exact([s[1]], True, order=1) + sage: g = Stream_function(lambda n: s[n], True, 0) + sage: r = Stream_plethysm(f, g, p, s) + sage: [r[n] for n in range(3)] + [s[], s[1], s[2]] + + TESTS: + + Check corner cases:: + + sage: f0 = Stream_exact([p([])], True) + sage: f1 = Stream_exact([p[1]], True, order=1) + sage: f2 = Stream_exact([p[2]], True, order=2 ) + sage: f11 = Stream_exact([p[1,1]], True, order=2 ) + sage: r = Stream_plethysm(f0, f1, p); [r[n] for n in range(3)] + [p[], 0, 0] + sage: r = Stream_plethysm(f0, f2, p); [r[n] for n in range(3)] + [p[], 0, 0] + sage: r = Stream_plethysm(f0, f11, p); [r[n] for n in range(3)] + [p[], 0, 0] + + Check that degree one elements are treated in the correct way:: + + sage: R. = QQ[]; p = SymmetricFunctions(R).p() + sage: f_s = a1*p[1] + a2*p[2] + a11*p[1,1] + sage: g_s = b1*p[1] + b21*p[2,1] + b111*p[1,1,1] + sage: r_s = f_s(g_s) + sage: f = Stream_exact([f_s.restrict_degree(k) for k in range(f_s.degree()+1)], True) + sage: g = Stream_exact([g_s.restrict_degree(k) for k in range(g_s.degree()+1)], True) + sage: r = Stream_plethysm(f, g, p) + sage: r_s == sum(r[n] for n in range(2*(r_s.degree()+1))) + True + + sage: r_s - f_s(g_s, include=[]) + (a2*b1^2-a2*b1)*p[2] + (a2*b111^2-a2*b111)*p[2, 2, 2] + (a2*b21^2-a2*b21)*p[4, 2] + + sage: r2 = Stream_plethysm(f, g, p, include=[]) + sage: r_s - sum(r2[n] for n in range(2*(r_s.degree()+1))) + (a2*b1^2-a2*b1)*p[2] + (a2*b111^2-a2*b111)*p[2, 2, 2] + (a2*b21^2-a2*b21)*p[4, 2] + """ - def __init__(self, f, g, p): + def __init__(self, f, g, p, ring=None, include=None, exclude=None): r""" Initialize ``self``. @@ -1691,15 +1768,32 @@ def __init__(self, f, g, p): sage: from sage.data_structures.stream import Stream_function, Stream_plethysm sage: s = SymmetricFunctions(QQ).s() sage: p = SymmetricFunctions(QQ).p() - sage: f = Stream_function(lambda n: s[n], s, True, 1) - sage: g = Stream_function(lambda n: s[n-1,1], s, True, 2) + sage: f = Stream_function(lambda n: s[n], True, 1) + sage: g = Stream_function(lambda n: s[n-1,1], True, 2) sage: h = Stream_plethysm(f, g, p) """ - #assert g._approximate_order > 0 - self._fv = f._approximate_order - self._gv = g._approximate_order + # assert g._approximate_order > 0 + val = f._approximate_order * g._approximate_order + if ring is None: + self._basis = p + else: + self._basis = ring self._p = p - val = self._fv * self._gv + g = Stream_map_coefficients(g, lambda x: p(x)) + self._powers = [g] # a cache for the powers of g + R = self._basis.base_ring() + self._degree_one = _variables_recursive(R, include=include, exclude=exclude) + if isinstance(f, Stream_exact) and not f._constant: + self._degree_f = f._degree + else: + self._degree_f = None + if HopfAlgebrasWithBasis(R).TensorProducts() in p.categories(): + self._tensor_power = len(p._sets) + p_f = p._sets[0] + f = Stream_map_coefficients(f, lambda x: p_f(x)) + else: + self._tensor_power = None + f = Stream_map_coefficients(f, lambda x: p(x)) super().__init__(f, g, f._is_sparse, val) def get_coefficient(self, n): @@ -1715,8 +1809,8 @@ def get_coefficient(self, n): sage: from sage.data_structures.stream import Stream_function, Stream_plethysm sage: s = SymmetricFunctions(QQ).s() sage: p = SymmetricFunctions(QQ).p() - sage: f = Stream_function(lambda n: s[n], s, True, 1) - sage: g = Stream_function(lambda n: s[[1]*n], s, True, 1) + sage: f = Stream_function(lambda n: s[n], True, 1) + sage: g = Stream_function(lambda n: s[[1]*n], True, 1) sage: h = Stream_plethysm(f, g, p) sage: s(h.get_coefficient(5)) 4*s[1, 1, 1, 1, 1] + 4*s[2, 1, 1, 1] + 2*s[2, 2, 1] + 2*s[3, 1, 1] + s[3, 2] + s[4, 1] + s[5] @@ -1728,51 +1822,127 @@ def get_coefficient(self, n): 3*s[1, 1, 1, 1] + 2*s[2, 1, 1] + s[2, 2] + s[3, 1] + s[4], 4*s[1, 1, 1, 1, 1] + 4*s[2, 1, 1, 1] + 2*s[2, 2, 1] + 2*s[3, 1, 1] + s[3, 2] + s[4, 1] + s[5]] """ - if not n: # special case of 0 - return self._left[0] - - # We assume n > 0 - p = self._p - ret = p.zero() - for k in range(n+1): - temp = p(self._left[k]) - for la, c in temp: - inner = self._compute_product(n, la, c) - if inner is not None: - ret += inner - return ret + if not n: # special case of 0 + if self._right[0]: + assert self._degree_f is not None, "the plethysm with a lazy symmetric function of valuation 0 is defined only for symmetric functions of finite support" - def _compute_product(self, n, la, c): - """ + return sum((c * self.compute_product(n, la) + for k in range(self._left._approximate_order, self._degree_f) + if self._left[k] + for la, c in self._left[k]), + self._basis.zero()) + + res = sum((c * self.compute_product(n, la) + for k in range(self._left._approximate_order, n+1) + if self._left[k] + for la, c in self._left[k]), + self._basis.zero()) + return res + + def compute_product(self, n, la): + r""" Compute the product ``c * p[la](self._right)`` in degree ``n``. EXAMPLES:: - sage: from sage.data_structures.stream import Stream_plethysm, Stream_exact, Stream_function + sage: from sage.data_structures.stream import Stream_plethysm, Stream_exact, Stream_function, Stream_zero sage: s = SymmetricFunctions(QQ).s() sage: p = SymmetricFunctions(QQ).p() - sage: f = Stream_function(lambda n: s[n], s, True, 1) + sage: f = Stream_zero(True) # irrelevant for this test sage: g = Stream_exact([s[2], s[3]], False, 0, 4, 2) sage: h = Stream_plethysm(f, g, p) - sage: ret = h._compute_product(7, [2, 1], 1); ret + sage: A = h.compute_product(7, Partition([2, 1])); A 1/12*p[2, 2, 1, 1, 1] + 1/4*p[2, 2, 2, 1] + 1/6*p[3, 2, 2] + 1/12*p[4, 1, 1, 1] + 1/4*p[4, 2, 1] + 1/6*p[4, 3] - sage: ret == p[2,1](s[2] + s[3]).homogeneous_component(7) + sage: A == p[2, 1](s[2] + s[3]).homogeneous_component(7) + True + + sage: p2 = tensor([p, p]) + sage: f = Stream_zero(True) # irrelevant for this test + sage: g = Stream_function(lambda n: sum(tensor([p[k], p[n-k]]) for k in range(n+1)), True, 1) + sage: h = Stream_plethysm(f, g, p2) + sage: A = h.compute_product(7, Partition([2, 1])) + sage: B = p[2, 1](sum(g[n] for n in range(7))) + sage: B = p2.element_class(p2, {m: c for m, c in B if sum(mu.size() for mu in m) == 7}) + sage: A == B + True + + sage: f = Stream_zero(True) # irrelevant for this test + sage: g = Stream_function(lambda n: s[n], True, 0) + sage: h = Stream_plethysm(f, g, p) + sage: B = p[2, 2, 1](sum(s[i] for i in range(7))) + sage: all(h.compute_product(k, Partition([2, 2, 1])) == B.restrict_degree(k) for k in range(7)) True """ - p = self._p - ret = p.zero() - for mu in wt_int_vec_iter(n, la): - temp = c - for i, j in zip(la, mu): - gs = self._right[j] - if not gs: - temp = p.zero() - break - temp *= p[i](gs) - ret += temp + # This is the approximate order of the result + rao = self._right._approximate_order + ret_approx_order = rao * sum(la) + ret = self._basis.zero() + if n < ret_approx_order: + return ret + + la_exp = la.to_exp() + wgt = [i for i, m in enumerate(la_exp, 1) if m] + exp = [m for m in la_exp if m] + # the docstring of wt_int_vec_iter, i.e., iterator_fast, + # states that the weights should be weakly decreasing + wgt.reverse() + exp.reverse() + for k in wt_int_vec_iter(n - ret_approx_order, wgt): + # TODO: it may make a big difference here if the + # approximate order would be updated. + # The test below is based on not removing the fixed block + #if any(d < self._right._approximate_order * m + # for m, d in zip(exp, k)): + # continue + ret += prod(self.stretched_power_restrict_degree(i, m, rao * m + d) + for i, m, d in zip(wgt, exp, k)) return ret + def stretched_power_restrict_degree(self, i, m, d): + r""" + Return the degree ``d*i`` part of ``p([i]*m)(g)``. + + EXAMPLES:: + + sage: from sage.data_structures.stream import Stream_plethysm, Stream_exact, Stream_function, Stream_zero + sage: s = SymmetricFunctions(QQ).s() + sage: p = SymmetricFunctions(QQ).p() + sage: f = Stream_zero(False) # irrelevant for this test + sage: g = Stream_exact([s[2], s[3]], False, 0, 4, 2) + sage: h = Stream_plethysm(f, g, p) + sage: A = h.stretched_power_restrict_degree(2, 3, 6) + sage: A == p[2,2,2](s[2] + s[3]).homogeneous_component(12) + True + + sage: p2 = tensor([p, p]) + sage: f = Stream_zero(True) # irrelevant for this test + sage: g = Stream_function(lambda n: sum(tensor([p[k], p[n-k]]) for k in range(n+1)), True, 1) + sage: h = Stream_plethysm(f, g, p2) + sage: A = h.stretched_power_restrict_degree(2, 3, 6) + sage: B = p[2,2,2](sum(g[n] for n in range(7))) + sage: B = p2.element_class(p2, {m: c for m, c in B if sum(mu.size() for mu in m) == 12}) + sage: A == B + True + """ + while len(self._powers) < m: + self._powers.append(Stream_cauchy_mul(self._powers[-1], self._powers[0])) + power_d = self._powers[m-1][d] + # we have to check power_d for zero because it might be an + # integer and not a symmetric function + if power_d: + if self._tensor_power is None: + terms = {mon.stretch(i): raised_c for mon, c in power_d + if (raised_c := _raise_variables(c, i, self._degree_one))} + else: + terms = {tuple((mu.stretch(i) for mu in mon)): raised_c + for mon, c in power_d + if (raised_c := _raise_variables(c, i, self._degree_one))} + return self._p.element_class(self._p, terms) + + return self._p.zero() + + ##################################################################### # Unary operations @@ -1787,7 +1957,7 @@ def __init__(self, series, scalar): TESTS:: sage: from sage.data_structures.stream import (Stream_rmul, Stream_function) - sage: f = Stream_function(lambda n: -1, ZZ, True, 0) + sage: f = Stream_function(lambda n: -1, True, 0) sage: g = Stream_rmul(f, 3) """ self._series = series @@ -1803,7 +1973,7 @@ def __hash__(self): sage: from sage.data_structures.stream import Stream_function sage: from sage.data_structures.stream import Stream_rmul - sage: a = Stream_function(lambda n: 2*n, ZZ, False, 1) + sage: a = Stream_function(lambda n: 2*n, False, 1) sage: f = Stream_rmul(a, 2) sage: hash(f) == hash(f) True @@ -1822,8 +1992,8 @@ def __eq__(self, other): sage: from sage.data_structures.stream import Stream_function sage: from sage.data_structures.stream import Stream_rmul, Stream_lmul - sage: a = Stream_function(lambda n: 2*n, ZZ, False, 1) - sage: b = Stream_function(lambda n: n, ZZ, False, 1) + sage: a = Stream_function(lambda n: 2*n, False, 1) + sage: b = Stream_function(lambda n: n, False, 1) sage: f = Stream_rmul(a, 2) sage: f == Stream_rmul(b, 2) False @@ -1845,7 +2015,7 @@ def is_nonzero(self): EXAMPLES:: sage: from sage.data_structures.stream import (Stream_rmul, Stream_function) - sage: f = Stream_function(lambda n: n, ZZ, True, 1) + sage: f = Stream_function(lambda n: n, True, 1) sage: g = Stream_rmul(f, 2) sage: g.is_nonzero() False @@ -1874,7 +2044,7 @@ class Stream_rmul(Stream_scalar): sage: from sage.data_structures.stream import (Stream_rmul, Stream_function) sage: W = algebras.DifferentialWeyl(QQ, names=('x',)) sage: x, dx = W.gens() - sage: f = Stream_function(lambda n: x^n, W, True, 1) + sage: f = Stream_function(lambda n: x^n, True, 1) sage: g = Stream_rmul(f, dx) sage: [g[i] for i in range(5)] [0, x*dx + 1, x^2*dx + 2*x, x^3*dx + 3*x^2, x^4*dx + 4*x^3] @@ -1890,7 +2060,7 @@ def get_coefficient(self, n): EXAMPLES:: sage: from sage.data_structures.stream import (Stream_rmul, Stream_function) - sage: f = Stream_function(lambda n: n, ZZ, True, 1) + sage: f = Stream_function(lambda n: n, True, 1) sage: g = Stream_rmul(f, 3) sage: g.get_coefficient(5) 15 @@ -1915,7 +2085,7 @@ class Stream_lmul(Stream_scalar): sage: from sage.data_structures.stream import (Stream_lmul, Stream_function) sage: W = algebras.DifferentialWeyl(QQ, names=('x',)) sage: x, dx = W.gens() - sage: f = Stream_function(lambda n: x^n, W, True, 1) + sage: f = Stream_function(lambda n: x^n, True, 1) sage: g = Stream_lmul(f, dx) sage: [g[i] for i in range(5)] [0, x*dx, x^2*dx, x^3*dx, x^4*dx] @@ -1931,7 +2101,7 @@ def get_coefficient(self, n): EXAMPLES:: sage: from sage.data_structures.stream import (Stream_lmul, Stream_function) - sage: f = Stream_function(lambda n: n, ZZ, True, 1) + sage: f = Stream_function(lambda n: n, True, 1) sage: g = Stream_lmul(f, 3) sage: g.get_coefficient(5) 15 @@ -1952,7 +2122,7 @@ class Stream_neg(Stream_unary): EXAMPLES:: sage: from sage.data_structures.stream import (Stream_neg, Stream_function) - sage: f = Stream_function(lambda n: 1, ZZ, True, 1) + sage: f = Stream_function(lambda n: 1, True, 1) sage: g = Stream_neg(f) sage: [g[i] for i in range(10)] [0, -1, -1, -1, -1, -1, -1, -1, -1, -1] @@ -1964,7 +2134,7 @@ def __init__(self, series): TESTS:: sage: from sage.data_structures.stream import (Stream_neg, Stream_function) - sage: f = Stream_function(lambda n: -1, ZZ, True, 0) + sage: f = Stream_function(lambda n: -1, True, 0) sage: g = Stream_neg(f) """ super().__init__(series, series._is_sparse, series._approximate_order) @@ -1980,7 +2150,7 @@ def get_coefficient(self, n): EXAMPLES:: sage: from sage.data_structures.stream import (Stream_neg, Stream_function) - sage: f = Stream_function(lambda n: n, ZZ, True, 1) + sage: f = Stream_function(lambda n: n, True, 1) sage: g = Stream_neg(f) sage: g.get_coefficient(5) -5 @@ -1997,7 +2167,7 @@ def is_nonzero(self): EXAMPLES:: sage: from sage.data_structures.stream import (Stream_neg, Stream_function) - sage: f = Stream_function(lambda n: n, ZZ, True, 1) + sage: f = Stream_function(lambda n: n, True, 1) sage: g = Stream_neg(f) sage: g.is_nonzero() False @@ -2010,6 +2180,7 @@ def is_nonzero(self): """ return self._series.is_nonzero() + class Stream_cauchy_invert(Stream_unary): """ Operator for multiplicative inverse of the stream. @@ -2021,7 +2192,7 @@ class Stream_cauchy_invert(Stream_unary): EXAMPLES:: sage: from sage.data_structures.stream import (Stream_cauchy_invert, Stream_function) - sage: f = Stream_function(lambda n: 1, ZZ, True, 1) + sage: f = Stream_function(lambda n: 1, True, 1) sage: g = Stream_cauchy_invert(f) sage: [g[i] for i in range(10)] [-1, 0, 0, 0, 0, 0, 0, 0, 0, 0] @@ -2053,7 +2224,7 @@ def get_coefficient(self, n): EXAMPLES:: sage: from sage.data_structures.stream import (Stream_cauchy_invert, Stream_function) - sage: f = Stream_function(lambda n: n, ZZ, True, 1) + sage: f = Stream_function(lambda n: n, True, 1) sage: g = Stream_cauchy_invert(f) sage: g.get_coefficient(5) 0 @@ -2077,7 +2248,7 @@ def iterate_coefficients(self): EXAMPLES:: sage: from sage.data_structures.stream import (Stream_cauchy_invert, Stream_function) - sage: f = Stream_function(lambda n: n^2, ZZ, False, 1) + sage: f = Stream_function(lambda n: n^2, False, 1) sage: g = Stream_cauchy_invert(f) sage: n = g.iterate_coefficients() sage: [next(n) for i in range(10)] @@ -2112,45 +2283,45 @@ def is_nonzero(self): EXAMPLES:: sage: from sage.data_structures.stream import (Stream_cauchy_invert, Stream_function) - sage: f = Stream_function(lambda n: n^2, ZZ, False, 1) + sage: f = Stream_function(lambda n: n^2, False, 1) sage: g = Stream_cauchy_invert(f) sage: g.is_nonzero() True """ return True + class Stream_map_coefficients(Stream_inexact): r""" - The stream with ``function`` applied to each nonzero - coefficient of ``series``. + The stream with ``function`` applied to each nonzero coefficient + of ``series``. INPUT: - ``series`` -- a :class:`Stream` - ``function`` -- a function that modifies the elements of the stream - - ``ring`` -- the base ring of the stream EXAMPLES:: sage: from sage.data_structures.stream import (Stream_map_coefficients, Stream_function) - sage: f = Stream_function(lambda n: 1, ZZ, True, 1) - sage: g = Stream_map_coefficients(f, lambda n: -n, ZZ) + sage: f = Stream_function(lambda n: 1, True, 1) + sage: g = Stream_map_coefficients(f, lambda n: -n) sage: [g[i] for i in range(10)] [0, -1, -1, -1, -1, -1, -1, -1, -1, -1] + """ - def __init__(self, series, function, ring): + def __init__(self, series, function): """ Initialize ``self``. TESTS:: sage: from sage.data_structures.stream import (Stream_map_coefficients, Stream_function) - sage: f = Stream_function(lambda n: -1, ZZ, True, 0) - sage: g = Stream_map_coefficients(f, lambda n: n + 1, ZZ) + sage: f = Stream_function(lambda n: -1, True, 0) + sage: g = Stream_map_coefficients(f, lambda n: n + 1) sage: TestSuite(g).run(skip="_test_pickling") """ self._function = function - self._ring = ring self._series = series super().__init__(series._is_sparse, series._approximate_order) @@ -2165,25 +2336,20 @@ def get_coefficient(self, n): EXAMPLES:: sage: from sage.data_structures.stream import (Stream_map_coefficients, Stream_function) - sage: f = Stream_function(lambda n: n, ZZ, True, -1) - sage: g = Stream_map_coefficients(f, lambda n: n^2 + 1, ZZ) + sage: f = Stream_function(lambda n: n, True, -1) + sage: g = Stream_map_coefficients(f, lambda n: n^2 + 1) sage: g.get_coefficient(5) 26 sage: [g.get_coefficient(i) for i in range(-1, 10)] [2, 0, 2, 5, 10, 17, 26, 37, 50, 65, 82] sage: R. = ZZ[] - sage: f = Stream_function(lambda n: n, ZZ, True, -1) - sage: g = Stream_map_coefficients(f, lambda n: n.degree() + 1, R) + sage: f = Stream_function(lambda n: n, True, -1) + sage: g = Stream_map_coefficients(f, lambda n: R(n).degree() + 1) sage: [g.get_coefficient(i) for i in range(-1, 3)] [1, 0, 1, 1] - - sage: f = Stream_function(lambda n: n, ZZ, True, 0) - sage: g = Stream_map_coefficients(f, lambda n: 5, GF(3)) - sage: [g.get_coefficient(i) for i in range(10)] - [0, 5, 5, 0, 5, 5, 0, 5, 5, 0] """ - c = self._ring(self._series[n]) + c = self._series[n] if c: return self._function(c) return c @@ -2195,13 +2361,13 @@ def __hash__(self): EXAMPLES:: sage: from sage.data_structures.stream import (Stream_map_coefficients, Stream_function) - sage: f = Stream_function(lambda n: -1, ZZ, True, 0) - sage: g = Stream_map_coefficients(f, lambda n: n + 1, ZZ) + sage: f = Stream_function(lambda n: -1, True, 0) + sage: g = Stream_map_coefficients(f, lambda n: n + 1) sage: hash(g) == hash(g) True """ # We don't hash the function as it might not be hashable. - return hash((type(self), self._series, self._ring)) + return hash((type(self), self._series)) def __eq__(self, other): """ @@ -2214,18 +2380,17 @@ def __eq__(self, other): EXAMPLES:: sage: from sage.data_structures.stream import (Stream_map_coefficients, Stream_function) - sage: f = Stream_function(lambda n: -1, ZZ, True, 0) + sage: f = Stream_function(lambda n: -1, True, 0) sage: def plus_one(n): return n + 1 - sage: g = Stream_map_coefficients(f, plus_one, ZZ) + sage: g = Stream_map_coefficients(f, plus_one) sage: g == f False - sage: g == Stream_map_coefficients(f, plus_one, QQ) + sage: g == Stream_map_coefficients(f, lambda n: n + 1) False - sage: g == Stream_map_coefficients(f, plus_one, ZZ) - True """ return (isinstance(other, type(self)) and self._series == other._series - and self._ring == other._ring and self._function == other._function) + and self._function == other._function) + class Stream_shift(Stream_inexact): """ @@ -2246,7 +2411,7 @@ def __init__(self, series, shift): sage: from sage.data_structures.stream import Stream_exact sage: h = Stream_exact([1], False, constant=3) sage: M = Stream_shift(h, 2) - sage: TestSuite(M).run() + sage: TestSuite(M).run(skip="_test_pickling") """ self._series = series self._shift = shift @@ -2260,7 +2425,7 @@ def __getitem__(self, n): sage: from sage.data_structures.stream import Stream_shift sage: from sage.data_structures.stream import Stream_function - sage: F = Stream_function(lambda n: n, ZZ, False, 1) + sage: F = Stream_function(lambda n: n, False, 1) sage: M = Stream_shift(F, 2) sage: [F[i] for i in range(6)] [0, 1, 2, 3, 4, 5] @@ -2277,7 +2442,7 @@ def __hash__(self): sage: from sage.data_structures.stream import Stream_shift sage: from sage.data_structures.stream import Stream_function - sage: F = Stream_function(lambda n: n, ZZ, False, 1) + sage: F = Stream_function(lambda n: n, False, 1) sage: M = Stream_shift(F, 2) sage: hash(M) == hash(M) True @@ -2296,7 +2461,7 @@ def __eq__(self, other): sage: from sage.data_structures.stream import Stream_shift sage: from sage.data_structures.stream import Stream_function - sage: F = Stream_function(lambda n: 1, ZZ, False, 1) + sage: F = Stream_function(lambda n: 1, False, 1) sage: M2 = Stream_shift(F, 2) sage: M3 = Stream_shift(F, 3) sage: M2 == M3 @@ -2317,10 +2482,120 @@ def is_nonzero(self): EXAMPLES:: sage: from sage.data_structures.stream import (Stream_cauchy_invert, Stream_function) - sage: f = Stream_function(lambda n: n^2, ZZ, False, 1) + sage: f = Stream_function(lambda n: n^2, False, 1) sage: g = Stream_cauchy_invert(f) sage: g.is_nonzero() True """ return self._series.is_nonzero() + +class Stream_derivative(Stream_inexact): + """ + Operator for taking derivatives of a stream. + + INPUT: + + - ``series`` -- a :class:`Stream` + - ``shift`` -- a positive integer + """ + def __init__(self, series, shift): + """ + Initialize ``self``. + + EXAMPLES:: + + sage: from sage.data_structures.stream import Stream_exact, Stream_derivative + sage: f = Stream_exact([1,2,3], False) + sage: f2 = Stream_derivative(f, 2) + sage: TestSuite(f2).run() + """ + self._series = series + self._shift = shift + if 0 <= series._approximate_order <= shift: + aorder = 0 + else: + aorder = series._approximate_order - shift + super().__init__(series._is_sparse, aorder) + + def __getitem__(self, n): + """ + Return the ``n``-th coefficient of ``self``. + + EXAMPLES:: + + sage: from sage.data_structures.stream import Stream_function, Stream_derivative + sage: f = Stream_function(lambda n: 1/n if n else 0, True, -2) + sage: [f[i] for i in range(-5, 3)] + [0, 0, 0, -1/2, -1, 0, 1, 1/2] + sage: f2 = Stream_derivative(f, 2) + sage: [f2[i] for i in range(-5, 3)] + [0, -3, -2, 0, 0, 1, 2, 3] + + sage: f = Stream_function(lambda n: 1/n, True, 2) + sage: [f[i] for i in range(-1, 4)] + [0, 0, 0, 1/2, 1/3] + sage: f2 = Stream_derivative(f, 3) + sage: [f2[i] for i in range(-1, 4)] + [0, 2, 6, 12, 20] + """ + return (prod(n+k for k in range(1, self._shift + 1)) + * self._series[n + self._shift]) + + def __hash__(self): + """ + Return the hash of ``self``. + + EXAMPLES:: + + sage: from sage.data_structures.stream import Stream_function + sage: from sage.data_structures.stream import Stream_derivative + sage: a = Stream_function(lambda n: 2*n, False, 1) + sage: f = Stream_derivative(a, 1) + sage: g = Stream_derivative(a, 2) + sage: hash(f) == hash(f) + True + sage: hash(f) == hash(g) + False + + """ + return hash((type(self), self._series, self._shift)) + + def __eq__(self, other): + """ + Test equality. + + INPUT: + + - ``other`` -- a stream of coefficients + + EXAMPLES:: + + sage: from sage.data_structures.stream import Stream_function + sage: from sage.data_structures.stream import Stream_derivative + sage: a = Stream_function(lambda n: 2*n, False, 1) + sage: f = Stream_derivative(a, 1) + sage: g = Stream_derivative(a, 2) + sage: f == g + False + sage: f == Stream_derivative(a, 1) + True + """ + return (isinstance(other, type(self)) and self._shift == other._shift + and self._series == other._series) + + def is_nonzero(self): + r""" + Return ``True`` if and only if this stream is known + to be nonzero. + + EXAMPLES:: + + sage: from sage.data_structures.stream import Stream_exact, Stream_derivative + sage: f = Stream_exact([1,2], False) + sage: Stream_derivative(f, 1).is_nonzero() + True + sage: Stream_derivative(f, 2).is_nonzero() # it might be nice if this gave False + True + """ + return self._series.is_nonzero() diff --git a/src/sage/rings/all.py b/src/sage/rings/all.py index 500ab0b615a..a6090039db2 100644 --- a/src/sage/rings/all.py +++ b/src/sage/rings/all.py @@ -146,7 +146,7 @@ lazy_import('sage.rings.laurent_series_ring_element', 'LaurentSeries', deprecation=33602) # Lazy Laurent series ring -lazy_import('sage.rings.lazy_series_ring', ['LazyLaurentSeriesRing', 'LazyTaylorSeriesRing', +lazy_import('sage.rings.lazy_series_ring', ['LazyLaurentSeriesRing', 'LazyPowerSeriesRing', 'LazySymmetricFunctions', 'LazyDirichletSeriesRing']) # Tate algebras diff --git a/src/sage/rings/lazy_series.py b/src/sage/rings/lazy_series.py index 9f4cd1b0f39..e7869d6e8b5 100644 --- a/src/sage/rings/lazy_series.py +++ b/src/sage/rings/lazy_series.py @@ -68,7 +68,7 @@ series are much faster. Moreover, these are the series where equality can be decided. For example:: - sage: L. = LazyTaylorSeriesRing(ZZ) + sage: L. = LazyPowerSeriesRing(ZZ) sage: f = 1 + 2*z^2 / (1 - z) sage: f - 2 / (1 - z) + 1 + 2*z 0 @@ -77,7 +77,7 @@ streams of multivariate polynomials. Therefore, the only exact series in this case are polynomials:: - sage: L. = LazyTaylorSeriesRing(ZZ) + sage: L. = LazyPowerSeriesRing(ZZ) sage: 1 / (1-x) 1 + x + x^2 + x^3 + x^4 + x^5 + x^6 + O(x,y)^7 @@ -100,6 +100,50 @@ sage: hinv.valuation() -1 +TESTS:: + + sage: def check(L, z, verbose=False): + ....: # division + ....: lf = [0, L(0), 1, L(1), z, 1 + z, 2 + z + z^2] + ....: lg = [3, L(3), 1 + z, 2 + z + z^2] + ....: for f in lf: + ....: for g in lg: + ....: try: + ....: h = f / g + ....: if verbose: print("(%s) / (%s) = %s" % (f, g, h)) + ....: except Exception as e: + ....: print("%s in (%s) / (%s)" % (e, f, g)) + ....: # composition + ....: f = L(0) + ....: l = [(f, 0), (f, L(0)), (f, 2), (f, L(2)), (f, 2 + z + z^2), (f, 3/(1 - 2*z))] + ....: f = L(1) + ....: l.extend([(f, 0), (f, L(0)), (f, 2), (f, L(2)), (f, 2 + z + z^2), (f, 3/(1 - 2*z))]) + ....: f = 2 + z + z^2 + ....: l.extend([(f, 0), (f, L(0)), (f, 2), (f, L(2)), (f, 2 + z + z^2), (f, 3/(1 - 2*z))]) + ....: f = 3/(2 - 3*z) + ....: l.extend([(f, 0), (f, L(0)), (f, 3*z/(1 - 2*z))]) + ....: for f, g in l: + ....: try: + ....: h = f(g) + ....: if verbose: print("(%s)(%s) = %s" % (f, g, h)) + ....: except Exception as e: + ....: print("%s in (%s)(%s)" % (e, f, g)) + ....: # reversion + ....: l = [2 + 3*z, 3*z + 2*z^2, 3*z/(1 - 2*z - 3*z^2)] + ....: for f in l: + ....: try: + ....: h = f.revert() + ....: if verbose: print("(%s)^{(-1)} = %s" % (f, h)) + ....: except Exception as e: + ....: print("%s in (%s).revert()" % (e, f)) + + sage: L. = LazyLaurentSeriesRing(QQ) + sage: check(L, z) + sage: L. = LazyPowerSeriesRing(QQ) + sage: check(L, z) + sage: p = SymmetricFunctions(QQ).p() + sage: L = LazySymmetricFunctions(p) + sage: check(L, L(p[1])) """ # **************************************************************************** @@ -118,10 +162,16 @@ from sage.structure.richcmp import op_EQ, op_NE from sage.functions.other import factorial from sage.arith.power import generic_power +from sage.arith.functions import lcm +from sage.arith.misc import divisors, moebius +from sage.combinat.partition import Partition, Partitions +from sage.misc.misc_c import prod +from sage.misc.derivative import derivative_parse from sage.rings.infinity import infinity from sage.rings.integer_ring import ZZ from sage.rings.polynomial.laurent_polynomial_ring import LaurentPolynomialRing from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing +from sage.categories.tensor import tensor from sage.data_structures.stream import ( Stream_add, Stream_cauchy_mul, @@ -137,11 +187,13 @@ Stream_uninitialized, Stream_shift, Stream_function, + Stream_derivative, Stream_dirichlet_convolve, Stream_dirichlet_invert, Stream_plethysm ) + class LazyModuleElement(Element): r""" A lazy sequence with a module structure given by term-wise @@ -242,18 +294,24 @@ def __getitem__(self, n): [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] """ - R = self.parent()._internal_poly_ring.base_ring() + L = self.parent() + R = L._internal_poly_ring.base_ring() if isinstance(n, slice): if n.stop is None: raise NotImplementedError("cannot list an infinite set") - start = n.start if n.start is not None else self._coeff_stream._approximate_order + if n.start is not None: + start = n.start + elif L._minimal_valuation is not None: + start = L._minimal_valuation + else: + start = self._coeff_stream._approximate_order step = n.step if n.step is not None else 1 return [R(self._coeff_stream[k]) for k in range(start, n.stop, step)] return R(self._coeff_stream[n]) coefficient = __getitem__ - def map_coefficients(self, func, ring=None): + def map_coefficients(self, func): r""" Return the series with ``func`` applied to each nonzero coefficient of ``self``. @@ -323,13 +381,12 @@ def map_coefficients(self, func, ring=None): if not any(initial_coefficients) and not c: return P.zero() coeff_stream = Stream_exact(initial_coefficients, - self._coeff_stream._is_sparse, - order=coeff_stream._approximate_order, - degree=coeff_stream._degree, - constant=BR(c)) + self._coeff_stream._is_sparse, + order=coeff_stream._approximate_order, + degree=coeff_stream._degree, + constant=BR(c)) return P.element_class(P, coeff_stream) - R = P._internal_poly_ring.base_ring() - coeff_stream = Stream_map_coefficients(self._coeff_stream, func, R) + coeff_stream = Stream_map_coefficients(self._coeff_stream, func) return P.element_class(P, coeff_stream) def truncate(self, d): @@ -567,6 +624,10 @@ def __bool__(self): """ Test whether ``self`` is not zero. + An uninitialized series returns ``True`` as it is considered + as a formal variable, such as a generator of a polynomial + ring. + TESTS:: sage: L. = LazyLaurentSeriesRing(GF(2)) @@ -601,11 +662,41 @@ def __bool__(self): 1 sage: bool(M) True + + Uninitialized series:: + + sage: g = L(None, valuation=0) + sage: bool(g) + True + sage: g.define(0) + sage: bool(g) + False + + sage: g = L(None, valuation=0) + sage: bool(g) + True + sage: g.define(1 + z) + sage: bool(g) + True + + sage: g = L(None, valuation=0) + sage: bool(g) + True + sage: g.define(1 + z*g) + sage: bool(g) + True """ if isinstance(self._coeff_stream, Stream_zero): return False if isinstance(self._coeff_stream, Stream_exact): return True + if isinstance(self._coeff_stream, Stream_uninitialized): + if self._coeff_stream._target is None: + return True + if isinstance(self._coeff_stream._target, Stream_zero): + return False + if isinstance(self._coeff_stream._target, Stream_exact): + return True if self.parent()._sparse: cache = self._coeff_stream._cache if any(cache[a] for a in cache): @@ -623,7 +714,7 @@ def define(self, s): INPUT: - - ``s`` -- a Laurent polynomial + - ``s`` -- a lazy series EXAMPLES: @@ -754,6 +845,16 @@ def define(self, s): ... ValueError: series already defined + sage: e = L(None, valuation=0) + sage: e.define(1) + sage: e + 1 + + sage: e = L(None, valuation=0) + sage: e.define((1 + z).polynomial()) + sage: e + 1 + z + sage: D = LazyDirichletSeriesRing(QQ, "s") sage: L. = LazyLaurentSeriesRing(QQ) sage: e = L(lambda n: 1/factorial(n), 0) @@ -765,6 +866,15 @@ def define(self, s): """ if not isinstance(self._coeff_stream, Stream_uninitialized) or self._coeff_stream._target is not None: raise ValueError("series already defined") + + if not isinstance(s, LazyModuleElement): + s = self.parent()(s) + + # Special case when it has a trivial definition + if isinstance(s._coeff_stream, (Stream_zero, Stream_exact)): + self._coeff_stream = s._coeff_stream + return + self._coeff_stream._target = s._coeff_stream # an alias for compatibility with padics @@ -956,7 +1066,7 @@ def change_ring(self, ring): A Taylor series example:: - sage: L. = LazyTaylorSeriesRing(ZZ) + sage: L. = LazyPowerSeriesRing(ZZ) sage: s = 2 + z sage: t = s.change_ring(QQ) sage: t^-1 @@ -965,7 +1075,10 @@ def change_ring(self, ring): Lazy Taylor Series Ring in z over Rational Field """ P = self.parent() - Q = type(P)(ring, names=P.variable_names(), sparse=P._sparse) + if P._names is None: + Q = type(P)(ring, sparse=P._sparse) + else: + Q = type(P)(ring, names=P.variable_names(), sparse=P._sparse) return Q.element_class(Q, self._coeff_stream) # === module structure === @@ -1366,7 +1479,7 @@ def exp(self): ... ValueError: can only compose with a positive valuation series - sage: L. = LazyTaylorSeriesRing(QQ) + sage: L. = LazyPowerSeriesRing(QQ) sage: exp(x+y)[4].factor() (1/24) * (x + y)^4 sage: exp(x/(1-y)).polynomial(3) @@ -1393,7 +1506,7 @@ def log(self): sage: log(1/(1-z)) z + 1/2*z^2 + 1/3*z^3 + 1/4*z^4 + 1/5*z^5 + 1/6*z^6 + 1/7*z^7 + O(z^8) - sage: L. = LazyTaylorSeriesRing(QQ) + sage: L. = LazyPowerSeriesRing(QQ) sage: log((1 + x/(1-y))).polynomial(3) 1/3*x^3 - x^2*y + x*y^2 - 1/2*x^2 + x*y + x @@ -1430,7 +1543,7 @@ def sin(self): ... ValueError: can only compose with a positive valuation series - sage: L. = LazyTaylorSeriesRing(QQ) + sage: L. = LazyPowerSeriesRing(QQ) sage: sin(x/(1-y)).polynomial(3) -1/6*x^3 + x*y^2 + x*y + x @@ -1456,7 +1569,7 @@ def cos(self): sage: cos(z) 1 - 1/2*z^2 + 1/24*z^4 - 1/720*z^6 + O(z^7) - sage: L. = LazyTaylorSeriesRing(QQ) + sage: L. = LazyPowerSeriesRing(QQ) sage: cos(x/(1-y)).polynomial(4) 1/24*x^4 - 3/2*x^2*y^2 - x^2*y - 1/2*x^2 + 1 @@ -1482,7 +1595,7 @@ def tan(self): sage: tan(z) z + 1/3*z^3 + 2/15*z^5 + 17/315*z^7 + O(z^8) - sage: L. = LazyTaylorSeriesRing(QQ) + sage: L. = LazyPowerSeriesRing(QQ) sage: tan(x/(1-y)).polynomial(5) 2/15*x^5 + 2*x^3*y^2 + x*y^4 + x^3*y + x*y^3 + 1/3*x^3 + x*y^2 + x*y + x @@ -1548,7 +1661,7 @@ def sec(self): sage: sec(z) 1 + 1/2*z^2 + 5/24*z^4 + 61/720*z^6 + O(z^7) - sage: L. = LazyTaylorSeriesRing(QQ) + sage: L. = LazyPowerSeriesRing(QQ) sage: sec(x/(1-y)).polynomial(4) 5/24*x^4 + 3/2*x^2*y^2 + x^2*y + 1/2*x^2 + 1 @@ -1572,7 +1685,7 @@ def arcsin(self): sage: arcsin(z) z + 1/6*z^3 + 3/40*z^5 + 5/112*z^7 + O(z^8) - sage: L. = LazyTaylorSeriesRing(QQ) + sage: L. = LazyPowerSeriesRing(QQ) sage: asin(x/(1-y)) x + x*y + (1/6*x^3+x*y^2) + (1/2*x^3*y+x*y^3) + (3/40*x^5+x^3*y^2+x*y^4) + (3/8*x^5*y+5/3*x^3*y^3+x*y^5) @@ -1610,7 +1723,7 @@ def arccos(self): sage: arccos(z/(1-z)) 1/2*pi - z - z^2 - 7/6*z^3 - 3/2*z^4 - 83/40*z^5 - 73/24*z^6 + O(z^7) - sage: L. = LazyTaylorSeriesRing(SR) + sage: L. = LazyPowerSeriesRing(SR) sage: arccos(x/(1-y)) 1/2*pi + (-x) + (-x*y) + ((-1/6)*x^3-x*y^2) + ((-1/2)*x^3*y-x*y^3) + ((-3/40)*x^5-x^3*y^2-x*y^4) + ((-3/8)*x^5*y+(-5/3)*x^3*y^3-x*y^5) + O(x,y)^7 @@ -1634,7 +1747,7 @@ def arctan(self): sage: arctan(z) z - 1/3*z^3 + 1/5*z^5 - 1/7*z^7 + O(z^8) - sage: L. = LazyTaylorSeriesRing(QQ) + sage: L. = LazyPowerSeriesRing(QQ) sage: atan(x/(1-y)) x + x*y + (-1/3*x^3+x*y^2) + (-x^3*y+x*y^3) + (1/5*x^5-2*x^3*y^2+x*y^4) + (x^5*y-10/3*x^3*y^3+x*y^5) + (-1/7*x^7+3*x^5*y^2-5*x^3*y^4+x*y^6) + O(x,y)^8 @@ -1673,7 +1786,7 @@ def arccot(self): sage: arccot(z/(1-z)) 1/2*pi - z - z^2 - 2/3*z^3 + 4/5*z^5 + 4/3*z^6 + O(z^7) - sage: L. = LazyTaylorSeriesRing(SR) + sage: L. = LazyPowerSeriesRing(SR) sage: acot(x/(1-y)) 1/2*pi + (-x) + (-x*y) + (1/3*x^3-x*y^2) + (x^3*y-x*y^3) + ((-1/5)*x^5+2*x^3*y^2-x*y^4) + (-x^5*y+10/3*x^3*y^3-x*y^5) + O(x,y)^7 @@ -1699,7 +1812,7 @@ def sinh(self): sage: sinh(z) z + 1/6*z^3 + 1/120*z^5 + 1/5040*z^7 + O(z^8) - sage: L. = LazyTaylorSeriesRing(QQ) + sage: L. = LazyPowerSeriesRing(QQ) sage: sinh(x/(1-y)) x + x*y + (1/6*x^3+x*y^2) + (1/2*x^3*y+x*y^3) + (1/120*x^5+x^3*y^2+x*y^4) + (1/24*x^5*y+5/3*x^3*y^3+x*y^5) @@ -1727,7 +1840,7 @@ def cosh(self): sage: cosh(z) 1 + 1/2*z^2 + 1/24*z^4 + 1/720*z^6 + O(z^7) - sage: L. = LazyTaylorSeriesRing(QQ) + sage: L. = LazyPowerSeriesRing(QQ) sage: cosh(x/(1-y)) 1 + 1/2*x^2 + x^2*y + (1/24*x^4+3/2*x^2*y^2) + (1/6*x^4*y+2*x^2*y^3) + (1/720*x^6+5/12*x^4*y^2+5/2*x^2*y^4) + O(x,y)^7 @@ -1754,7 +1867,7 @@ def tanh(self): sage: tanh(z) z - 1/3*z^3 + 2/15*z^5 - 17/315*z^7 + O(z^8) - sage: L. = LazyTaylorSeriesRing(QQ) + sage: L. = LazyPowerSeriesRing(QQ) sage: tanh(x/(1-y)) x + x*y + (-1/3*x^3+x*y^2) + (-x^3*y+x*y^3) + (2/15*x^5-2*x^3*y^2+x*y^4) + (2/3*x^5*y-10/3*x^3*y^3+x*y^5) + (-17/315*x^7+2*x^5*y^2-5*x^3*y^4+x*y^6) + O(x,y)^8 @@ -1773,7 +1886,7 @@ def f(n): n = ZZ(n) if n % 2: h = 4 ** ((n + 1) // 2) - return bernoulli(n + 1) * h * (h -1) / factorial(n + 1) + return bernoulli(n + 1) * h * (h - 1) / factorial(n + 1) return ZZ.zero() return P(f, valuation=1)(self) @@ -1817,7 +1930,7 @@ def sech(self): sage: sech(z) 1 - 1/2*z^2 + 5/24*z^4 - 61/720*z^6 + O(z^7) - sage: L. = LazyTaylorSeriesRing(QQ) + sage: L. = LazyPowerSeriesRing(QQ) sage: sech(x/(1-y)) 1 + (-1/2*x^2) + (-x^2*y) + (5/24*x^4-3/2*x^2*y^2) + (5/6*x^4*y-2*x^2*y^3) + (-61/720*x^6+25/12*x^4*y^2-5/2*x^2*y^4) + O(x,y)^7 @@ -1887,7 +2000,7 @@ def arcsinh(self): sage: arcsinh(z) z - 1/6*z^3 + 3/40*z^5 - 5/112*z^7 + O(z^8) - sage: L. = LazyTaylorSeriesRing(QQ) + sage: L. = LazyPowerSeriesRing(QQ) sage: asinh(x/(1-y)) x + x*y + (-1/6*x^3+x*y^2) + (-1/2*x^3*y+x*y^3) + (3/40*x^5-x^3*y^2+x*y^4) + (3/8*x^5*y-5/3*x^3*y^3+x*y^5) + (-5/112*x^7+9/8*x^5*y^2-5/2*x^3*y^4+x*y^6) + O(x,y)^8 @@ -1925,7 +2038,7 @@ def arctanh(self): sage: arctanh(z) z + 1/3*z^3 + 1/5*z^5 + 1/7*z^7 + O(z^8) - sage: L. = LazyTaylorSeriesRing(QQ) + sage: L. = LazyPowerSeriesRing(QQ) sage: atanh(x/(1-y)) x + x*y + (1/3*x^3+x*y^2) + (x^3*y+x*y^3) + (1/5*x^5+2*x^3*y^2+x*y^4) + (x^5*y+10/3*x^3*y^3+x*y^5) + (1/7*x^7+3*x^5*y^2+5*x^3*y^4+x*y^6) + O(x,y)^8 @@ -1960,7 +2073,7 @@ def hypergeometric(self, a, b): sage: z.hypergeometric([], []) - exp(z) O(z^7) - sage: L. = LazyTaylorSeriesRing(QQ) + sage: L. = LazyPowerSeriesRing(QQ) sage: (x+y).hypergeometric([1, 1], [1]).polynomial(4) x^4 + 4*x^3*y + 6*x^2*y^2 + 4*x*y^3 + y^4 + x^3 + 3*x^2*y + 3*x*y^2 + y^3 + x^2 + 2*x*y + y^2 + x + y + 1 @@ -2027,7 +2140,7 @@ def sqrt(self): sage: sqrt(1+z) 1 + 1/2*z - 1/8*z^2 + 1/16*z^3 - 5/128*z^4 + 7/256*z^5 - 21/1024*z^6 + O(z^7) - sage: L. = LazyTaylorSeriesRing(QQ) + sage: L. = LazyPowerSeriesRing(QQ) sage: sqrt(1+x/(1-y)) 1 + 1/2*x + (-1/8*x^2+1/2*x*y) + (1/16*x^3-1/4*x^2*y+1/2*x*y^2) + (-5/128*x^4+3/16*x^3*y-3/8*x^2*y^2+1/2*x*y^3) @@ -2160,6 +2273,11 @@ def _mul_(self, other): sage: t1 = t.approximate_series(44) sage: s1 * t1 - (s * t).approximate_series(42) O(z^42) + + Check products with exact series:: + + sage: L([1], constant=3)^2 + 1 + 6*z + 15*z^2 + 24*z^3 + 33*z^4 + 42*z^5 + 51*z^6 + O(z^7) """ P = self.parent() left = self._coeff_stream @@ -2168,9 +2286,15 @@ def _mul_(self, other): # Check some trivial products if isinstance(left, Stream_zero) or isinstance(right, Stream_zero): return P.zero() - if isinstance(left, Stream_exact) and left._initial_coefficients == (P._internal_poly_ring.base_ring().one(),) and left.order() == 0: + if (isinstance(left, Stream_exact) + and left._initial_coefficients == (P._internal_poly_ring.base_ring().one(),) + and left.order() == 0 + and not left._constant): return other # self == 1 - if isinstance(right, Stream_exact) and right._initial_coefficients == (P._internal_poly_ring.base_ring().one(),) and right.order() == 0: + if (isinstance(right, Stream_exact) + and right._initial_coefficients == (P._internal_poly_ring.base_ring().one(),) + and right.order() == 0 + and not right._constant): return self # right == 1 # The product is exact if and only if both factors are exact @@ -2194,7 +2318,7 @@ def _mul_(self, other): # coefficients of q. if right._constant: d = right._degree - c = left._constant # this is zero + c = left._constant # this is zero # left._constant must be 0 and thus len(il) >= 1 for k in range(len(il)-1): c += il[k] * right._constant @@ -2202,14 +2326,14 @@ def _mul_(self, other): c += il[-1] * right._constant elif left._constant: d = left._degree - c = right._constant # this is zero + c = right._constant # this is zero # left._constant must be 0 and thus len(il) >= 1 for k in range(len(ir)-1): c += left._constant * ir[k] initial_coefficients[d - lv + k] += c c += left._constant * ir[-1] else: - c = left._constant # this is zero + c = left._constant # this is zero coeff_stream = Stream_exact(initial_coefficients, P._sparse, order=lv + rv, @@ -2331,12 +2455,25 @@ def __invert__(self): sage: ~z z^-1 + We can also compute the multiplicative inverse of a symmetric + function:: + + sage: h = SymmetricFunctions(QQ).h() + sage: p = SymmetricFunctions(QQ).p() + sage: L = LazySymmetricFunctions(p) + sage: E = L(lambda n: h[n]) + sage: (~E)[:4] + [p[], -p[1], 1/2*p[1, 1] - 1/2*p[2], -1/6*p[1, 1, 1] + 1/2*p[2, 1] - 1/3*p[3]] + + sage: (E * ~E)[:6] + [p[], 0, 0, 0, 0, 0] + TESTS:: sage: L. = LazyLaurentSeriesRing(QQ) sage: g = L([2], valuation=-1, constant=1); g 2*x^-1 + 1 + x + x^2 + O(x^3) - sage: g*g^-1 + sage: g * g^-1 1 + O(x^7) """ @@ -2368,7 +2505,7 @@ def __invert__(self): return P.element_class(P, coeff_stream) if (len(initial_coefficients) == 2 and not (initial_coefficients[0] + initial_coefficients[1]) - and not coeff_stream._constant): + and not coeff_stream._constant): v = -coeff_stream.order() c = ~initial_coefficients[0] coeff_stream = Stream_exact((), @@ -2439,7 +2576,7 @@ def _div_(self, other): Examples for multivariate Taylor series:: - sage: L. = LazyTaylorSeriesRing(QQ) + sage: L. = LazyPowerSeriesRing(QQ) sage: 1 / (1 - y) 1 + y + y^2 + y^3 + y^4 + y^5 + y^6 + O(x,y)^7 @@ -2592,7 +2729,7 @@ def __call__(self, g, *, check=True): r""" Return the composition of ``self`` with ``g``. - Given two Laurent Series `f` and `g` over the same base ring, the + Given two Laurent series `f` and `g` over the same base ring, the composition `(f \circ g)(z) = f(g(z))` is defined if and only if: - `g = 0` and `val(f) >= 0`, @@ -2793,7 +2930,7 @@ def __call__(self, g, *, check=True): ZeroDivisionError: the valuation of the series must be nonnegative `g \neq 0` and `val(g) \leq 0` and `f` has infinitely many - non-zero coefficients`:: + non-zero coefficients:: sage: g = z^-1 + z^-2 sage: g.valuation() <= 0 @@ -2862,8 +2999,17 @@ def __call__(self, g, *, check=True): 3 sage: parent(three) Univariate Polynomial Ring in x over Rational Field + + Consistency check when `g` is an uninitialized series between a + polynomial `f` as both a polynomial and a lazy series:: + + sage: L. = LazyLaurentSeriesRing(QQ) + sage: f = 1 + z + sage: g = L(None, valuation=0) + sage: f(g) == f.polynomial()(g) + True """ - # f = self and compute f(g) + # Find a good parent for the result from sage.structure.element import get_coercion_model cm = get_coercion_model() P = cm.common_parent(self.base_ring(), parent(g)) @@ -2975,7 +3121,7 @@ def __call__(self, g, *, check=True): def coefficient(n): return sum(self[i] * (g**i)[n] for i in range(n+1)) R = P._internal_poly_ring.base_ring() - coeff_stream = Stream_function(coefficient, R, P._sparse, 1) + coeff_stream = Stream_function(coefficient, P._sparse, 1) return P.element_class(P, coeff_stream) coeff_stream = Stream_cauchy_compose(self._coeff_stream, g._coeff_stream) @@ -2987,35 +3133,43 @@ def revert(self): r""" Return the compositional inverse of ``self``. - Given a Laurent Series `f`. the compositional inverse is a - Laurent Series `g` over the same base ring, such that + Given a Laurent series `f`, the compositional inverse is a + Laurent series `g` over the same base ring, such that `(f \circ g)(z) = f(g(z)) = z`. The compositional inverse exists if and only if: - `val(f) = 1`, or - - `f = a + b z` with `a b \neq 0`, or + - `f = a + b z` with `a, b \neq 0`, or - `f = a/z` with `a \neq 0` EXAMPLES:: - sage: L. = LazyLaurentSeriesRing(ZZ) - sage: z.revert() - z + O(z^8) - sage: (1/z).revert() - z^-1 + sage: L. = LazyLaurentSeriesRing(QQ) + sage: (2*z).revert() + 1/2*z + sage: (2/z).revert() + 2*z^-1 sage: (z-z^2).revert() z + z^2 + 2*z^3 + 5*z^4 + 14*z^5 + 42*z^6 + 132*z^7 + O(z^8) + sage: s = L(degree=1, constant=-1) + sage: s.revert() + -z - z^2 - z^3 + O(z^4) + + sage: s = L(degree=1, constant=1) + sage: s.revert() + z - z^2 + z^3 - z^4 + z^5 - z^6 + z^7 + O(z^8) + TESTS:: sage: L. = LazyLaurentSeriesRing(QQ) - sage: s = L(lambda n: 1 if n == 1 else 0, valuation=1); s - z + O(z^8) + sage: s = L(lambda n: 2 if n == 1 else 0, valuation=1); s + 2*z + O(z^8) sage: s.revert() - z + O(z^8) + 1/2*z + O(z^8) sage: (2+3*z).revert() -2/3 + 1/3*z @@ -3027,6 +3181,22 @@ def revert(self): ... ValueError: cannot determine whether the compositional inverse exists + sage: s = L(lambda n: 1, valuation=-2); s + z^-2 + z^-1 + 1 + z + z^2 + z^3 + z^4 + O(z^5) + sage: s.revert() + Traceback (most recent call last): + ... + ValueError: compositional inverse does not exist + + sage: R. = QQ[] + sage: L. = LazyLaurentSeriesRing(R.fraction_field()) + sage: s = L([q], valuation=0, constant=t); s + q + t*z + t*z^2 + t*z^3 + O(z^4) + sage: s.revert() + Traceback (most recent call last): + ... + ValueError: compositional inverse does not exist + We look at some cases where the compositional inverse does not exist: `f = 0`:: @@ -3040,7 +3210,7 @@ def revert(self): ... ValueError: compositional inverse does not exist - `val(f) ! = 1` and `f(0) * f(1) = 0`:: + `val(f) != 1` and `f(0) * f(1) = 0`:: sage: (z^2).revert() Traceback (most recent call last): @@ -3051,30 +3221,187 @@ def revert(self): Traceback (most recent call last): ... ValueError: compositional inverse does not exist + + Reversion of exact series:: + + sage: f = L([2], valuation=-1, constant=2) + sage: f.revert() + Traceback (most recent call last): + ... + ValueError: compositional inverse does not exist + + sage: f = L([1, 2], valuation=0, constant=1) + sage: f.revert() + Traceback (most recent call last): + ... + ValueError: compositional inverse does not exist + + sage: f = L([-1, -1], valuation=1, constant=-1) + sage: f.revert() + -z - z^2 - z^3 - z^4 - z^5 + O(z^6) + + sage: f = L([-1, 0, -1], valuation=1, constant=-1) + sage: f.revert() + -z + z^3 - z^4 - 2*z^5 + 6*z^6 + z^7 + O(z^8) + + sage: f = L([-1], valuation=1, degree=3, constant=-1) + sage: f.revert() + -z + z^3 - z^4 - 2*z^5 + 6*z^6 + z^7 + O(z^8) """ P = self.parent() - if self.valuation() == 1: - z = P.gen() - g = P(None, valuation=1) - g.define(z/((self/z)(g))) - return g - if self.valuation() not in [-1, 0]: - raise ValueError("compositional inverse does not exist") coeff_stream = self._coeff_stream + if isinstance(coeff_stream, Stream_zero): + raise ValueError("compositional inverse does not exist") if isinstance(coeff_stream, Stream_exact): - if (coeff_stream.order() == 0 - and coeff_stream._degree == 2): - a = coeff_stream[0] - b = coeff_stream[1] - coeff_stream = Stream_exact((-a/b, 1/b), - coeff_stream._is_sparse, - order=0) - return P.element_class(P, coeff_stream) - if (coeff_stream.order() == -1 - and coeff_stream._degree == 0): - return self + if coeff_stream._constant: + if coeff_stream.order() == 1: + R = P.base_ring() + # we cannot assume that the last initial coefficient + # and the constant differ, see stream.Stream_exact + if (coeff_stream._degree == 1 + len(coeff_stream._initial_coefficients) + and coeff_stream._constant == -R.one() + and all(c == -R.one() for c in coeff_stream._initial_coefficients)): + # self = -z/(1-z); self.revert() = -z/(1-z) + return self + else: + raise ValueError("compositional inverse does not exist") + else: + if (coeff_stream.order() == -1 + and coeff_stream._degree == 0): + # self = a/z; self.revert() = a/z + return self + + if (coeff_stream.order() >= 0 + and coeff_stream._degree == 2): + # self = a + b*z; self.revert() = -a/b + 1/b * z + a = coeff_stream[0] + b = coeff_stream[1] + coeff_stream = Stream_exact((-a/b, 1/b), + coeff_stream._is_sparse, + order=0) + return P.element_class(P, coeff_stream) + + if coeff_stream.order() != 1: + raise ValueError("compositional inverse does not exist") + + if any(coeff_stream[i] for i in range(coeff_stream._approximate_order, -1)): + raise ValueError("compositional inverse does not exist") + + if coeff_stream[-1]: + if coeff_stream[0] or coeff_stream[1]: + raise ValueError("compositional inverse does not exist") + raise ValueError("cannot determine whether the compositional inverse exists") + + if not coeff_stream[1]: raise ValueError("compositional inverse does not exist") - raise ValueError("cannot determine whether the compositional inverse exists") + + if coeff_stream[0]: + raise ValueError("cannot determine whether the compositional inverse exists") + + z = P.gen() + g = P(None, valuation=1) + g.define(z / ((self / z)(g))) + return g + + compositional_inverse = revert + + def derivative(self, *args): + """ + Return the derivative of the Laurent series. + + Multiple variables and iteration counts may be supplied; see + the documentation of + :func:`sage.calculus.functional.derivative` function for + details. + + EXAMPLES:: + + sage: L. = LazyLaurentSeriesRing(ZZ) + sage: z.derivative() + 1 + sage: (1+z+z^2).derivative(3) + 0 + sage: (1/z).derivative() + -z^-2 + sage: (1/(1-z)).derivative(z) + 1 + 2*z + 3*z^2 + 4*z^3 + 5*z^4 + 6*z^5 + 7*z^6 + O(z^7) + + TESTS: + + Check the derivative of the logarithm: + + sage: L. = LazyLaurentSeriesRing(QQ) + sage: -log(1-z).derivative() + 1 + z + z^2 + z^3 + z^4 + z^5 + z^6 + O(z^7) + + Check that differentiation of 'exact' series with nonzero + constant works:: + + sage: L. = LazyLaurentSeriesRing(ZZ) + sage: f = L([1,2], valuation=-2, constant=1) + sage: f + z^-2 + 2*z^-1 + 1 + z + z^2 + O(z^3) + sage: f.derivative() + -2*z^-3 - 2*z^-2 + 1 + 2*z + 3*z^2 + 4*z^3 + O(z^4) + + Check that differentiation with respect to a variable other + than the series variable works:: + + sage: R. = QQ[] + sage: L. = LazyLaurentSeriesRing(R) + sage: (z*q).derivative() + q + + sage: (z*q).derivative(q) + z + + sage: (z*q).derivative(q, z) + 1 + + sage: f = 1/(1-q*z+z^2) + sage: f + 1 + q*z + (q^2 - 1)*z^2 + (q^3 - 2*q)*z^3 + (q^4 - 3*q^2 + 1)*z^4 + (q^5 - 4*q^3 + 3*q)*z^5 + (q^6 - 5*q^4 + 6*q^2 - 1)*z^6 + O(z^7) + sage: f.derivative(q)[3] + 3*q^2 - 2 + + """ + P = self.parent() + R = P._laurent_poly_ring + v = R.gen() + order = 0 + vars = [] + for x in derivative_parse(args): + if x is None or x == v: + order += 1 + else: + vars.append(x) + + coeff_stream = self._coeff_stream + if isinstance(coeff_stream, Stream_zero): + return self + if (isinstance(coeff_stream, Stream_exact) + and not coeff_stream._constant): + if coeff_stream._approximate_order >= 0 and coeff_stream._degree <= order: + return P.zero() + if vars: + coeffs = [prod(i-k for k in range(order)) * c.derivative(vars) + for i, c in enumerate(coeff_stream._initial_coefficients, + coeff_stream._approximate_order)] + else: + coeffs = [prod(i-k for k in range(order)) * c + for i, c in enumerate(coeff_stream._initial_coefficients, + coeff_stream._approximate_order)] + coeff_stream = Stream_exact(coeffs, + self._coeff_stream._is_sparse, + order=coeff_stream._approximate_order - order, + constant=coeff_stream._constant) + return P.element_class(P, coeff_stream) + + coeff_stream = Stream_derivative(self._coeff_stream, order) + if vars: + coeff_stream = Stream_map_coefficients(coeff_stream, + lambda c: c.derivative(vars)) + return P.element_class(P, coeff_stream) def approximate_series(self, prec, name=None): r""" @@ -3137,10 +3464,10 @@ def polynomial(self, degree=None, name=None): A Laurent polynomial if the valuation of the series is negative or a polynomial otherwise. - If ``degree`` is not ``None``, the terms of the series of degree - greater than ``degree`` are truncated first. If ``degree`` is ``None`` - and the series is not a polynomial or a Laurent polynomial, a - ``ValueError`` is raised. + If ``degree`` is not ``None``, the terms of the series of + degree greater than ``degree`` are first truncated. If + ``degree`` is ``None`` and the series is not a polynomial or + a Laurent polynomial, a ``ValueError`` is raised. EXAMPLES:: @@ -3181,6 +3508,7 @@ def polynomial(self, degree=None, name=None): sage: L.zero().polynomial() 0 + """ S = self.parent() @@ -3246,13 +3574,14 @@ def _format_series(self, formatter, format_strings=False): return strformat("O({})".format(formatter(z**m))) return formatter(poly) + strformat(" + O({})".format(formatter(z**m))) -class LazyTaylorSeries(LazyCauchyProductSeries): + +class LazyPowerSeries(LazyCauchyProductSeries): r""" A Taylor series where the coefficients are computed lazily. EXAMPLES:: - sage: L. = LazyTaylorSeriesRing(ZZ) + sage: L. = LazyPowerSeriesRing(ZZ) sage: f = 1 / (1 - x^2 + y^3); f 1 + x^2 + (-y^3) + x^4 + (-2*x^2*y^3) + (x^6+y^6) + O(x,y)^7 sage: P. = PowerSeriesRing(ZZ, default_prec=101) @@ -3267,6 +3596,44 @@ class LazyTaylorSeries(LazyCauchyProductSeries): sage: g == f True """ + def exponential(self): + r""" + Return the exponential series of ``self``. + + This method is deprecated, use :meth:`exp` instead. + + TESTS:: + + sage: L. = LazyPowerSeriesRing(QQ) + sage: lazy_exp = x.exponential(); lazy_exp + doctest:...: DeprecationWarning: the method exponential is deprecated. Use exp instead. + See https://trac.sagemath.org/32367 for details. + 1 + x + 1/2*x^2 + 1/6*x^3 + 1/24*x^4 + 1/120*x^5 + 1/720*x^6 + O(x^7) + """ + from sage.misc.superseded import deprecation + deprecation(32367, 'the method exponential is deprecated. Use exp instead.') + return self.exp() + + def compute_coefficients(self, i): + r""" + Computes all the coefficients of self up to i. + + This method is deprecated, it has no effect anymore. + + TESTS:: + + sage: L. = LazyPowerSeriesRing(QQ) + sage: a = L([1,2,3], constant=3) + sage: a.compute_coefficients(5) + doctest:...: DeprecationWarning: the method compute_coefficients obsolete and has no effect. + See https://trac.sagemath.org/32367 for details. + sage: a + 1 + 2*z + 3*z^2 + 3*z^3 + 3*z^4 + 3*z^5 + O(z^6) + """ + from sage.misc.superseded import deprecation + deprecation(32367, "the method compute_coefficients obsolete and has no effect.") + return + def __call__(self, *g, check=True): r""" Return the composition of ``self`` with ``g``. @@ -3274,12 +3641,21 @@ def __call__(self, *g, check=True): The arity of ``self`` must be equal to the number of arguments provided. - Given two Taylor Series `f` and `g` over the same base ring, the - composition `(f \circ g)(z) = f(g(z))` is defined if and only if: + Given a Taylor series `f` of arity `n` and a tuple of Taylor + series `g = (g_1,\dots, g_n)` over the same base ring, the + composition `f \circ g` is defined if and only if for each + `1\leq k\leq n`: - - `g = 0` and `val(f) >= 0`, - - `g` is non-zero and `f` has only finitely many non-zero coefficients, - - `g` is non-zero and `val(g) > 0`. + - `g_i` is zero, or + - setting all variables except the `i`th in `f` to zero + yields a polynomial, or + - `val(g_i) > 0`. + + If `f` is a univariate 'exact' series, we can check whether + `f` is a actually a polynomial. However, if `f` is a + multivariate series, we have no way to test whether setting + all but one variable of `f` to zero yields a polynomial, + except if `f` itself is 'exact' and therefore a polynomial. INPUT: @@ -3287,8 +3663,8 @@ def __call__(self, *g, check=True): EXAMPLES:: - sage: L. = LazyTaylorSeriesRing(QQ) - sage: M. = LazyTaylorSeriesRing(ZZ) + sage: L. = LazyPowerSeriesRing(QQ) + sage: M. = LazyPowerSeriesRing(ZZ) sage: g1 = 1 / (1 - x) sage: g2 = x + y^2 sage: p = a^2 + b + 1 @@ -3298,7 +3674,7 @@ def __call__(self, *g, check=True): The number of mappings from a set with `m` elements to a set with `n` elements:: - sage: M. = LazyTaylorSeriesRing(QQ) + sage: M. = LazyPowerSeriesRing(QQ) sage: Ea = M(lambda n: 1/factorial(n)) sage: Ex = L(lambda n: 1/factorial(n)*x^n) sage: Ea(Ex*y)[5] @@ -3342,7 +3718,7 @@ def __call__(self, *g, check=True): of `f` and the parent of `g` or extended to the corresponding lazy series:: - sage: T. = LazyTaylorSeriesRing(QQ) + sage: T. = LazyPowerSeriesRing(QQ) sage: R. = ZZ[] sage: S. = R[] sage: L. = LaurentPolynomialRing(ZZ) @@ -3371,7 +3747,7 @@ def __call__(self, *g, check=True): TESTS:: - sage: L. = LazyTaylorSeriesRing(ZZ) + sage: L. = LazyPowerSeriesRing(ZZ) sage: f = 1 / (1 - x - y) sage: f(f) Traceback (most recent call last): @@ -3391,8 +3767,50 @@ def __call__(self, *g, check=True): ... TypeError: no common canonical parent for objects with parents: ... + Consistency check when `g` is an uninitialized series between a + polynomial `f` as both a polynomial and a lazy series:: + + sage: L. = LazyPowerSeriesRing(QQ) + sage: f = 1 - z + sage: g = L(None, valuation=1) + sage: f(g) == f.polynomial()(g) + True + + sage: g = L(None, valuation=1) + sage: g.define(z / (1 - g)) + sage: g + z + z^2 + 2*z^3 + 5*z^4 + 14*z^5 + 42*z^6 + 132*z^7 + O(z^8) + sage: gp = L(None, valuation=1) + sage: gp.define(z / f(gp)) + sage: gp + z + z^2 + 2*z^3 + 5*z^4 + 14*z^5 + 42*z^6 + 132*z^7 + O(z^8) + + Check that composing the zero series with anything yields zero:: + + sage: T. = LazyPowerSeriesRing(QQ) + sage: M. = LazyPowerSeriesRing(QQ) + sage: T(0)(1/(1-a), a+b) + 0 + + Check that composing `f` with zero series yields the constant term of `f`:: + + sage: T(3/(1-x-2*y))(0, 0) + 3 + + Check that we can compose a polynomial with anything:: + + sage: T(1-x-2*y + x*y^2)(1, 3) + 3 + + sage: T(1-x-2*y + x*y^2)(1 + a, 3) + 3 + 8*a + + sage: T(1-x-2*y + x*y^2)(1/(1-a), 3) + 3 + 8*a + 8*a^2 + 8*a^3 + 8*a^4 + 8*a^5 + 8*a^6 + O(a,b)^7 + """ - if len(g) != len(self.parent().variable_names()): + fP = parent(self) + if len(g) != fP._arity: raise ValueError("arity of must be equal to the number of arguments provided") # Find a good parent for the result @@ -3400,8 +3818,20 @@ def __call__(self, *g, check=True): cm = get_coercion_model() P = cm.common_parent(self.base_ring(), *[parent(h) for h in g]) - # f has finite length - if isinstance(self._coeff_stream, Stream_exact) and not self._coeff_stream._constant: + # f = 0 + if isinstance(self._coeff_stream, Stream_zero): + return P.zero() + + # g = (0, ..., 0) + if all((not isinstance(h, LazyModuleElement) and not h) + or (isinstance(h, LazyModuleElement) + and isinstance(h._coeff_stream, Stream_zero)) + for h in g): + return P(self[0]) + + # f has finite length and f != 0 + if (isinstance(self._coeff_stream, Stream_exact) + and not self._coeff_stream._constant): # constant polynomial poly = self.polynomial() if poly.is_constant(): @@ -3416,18 +3846,17 @@ def __call__(self, *g, check=True): from sage.rings.polynomial.laurent_polynomial_ring import LaurentPolynomialRing_univariate from sage.rings.lazy_series_ring import LazySeriesRing if not isinstance(P, LazySeriesRing): - fP = parent(self) if fP._laurent_poly_ring.has_coerce_map_from(P): S = fP._laurent_poly_ring P = fP if isinstance(P, (PolynomialRing_general, MPolynomialRing_base)): - from sage.rings.lazy_series_ring import LazyTaylorSeriesRing + from sage.rings.lazy_series_ring import LazyPowerSeriesRing S = P try: sparse = S.is_sparse() except AttributeError: sparse = fP.is_sparse() - P = LazyTaylorSeriesRing(S.base_ring(), S.variable_names(), sparse) + P = LazyPowerSeriesRing(S.base_ring(), S.variable_names(), sparse) elif isinstance(P, LaurentPolynomialRing_univariate): from sage.rings.lazy_series_ring import LazyLaurentSeriesRing S = P @@ -3452,8 +3881,7 @@ def __call__(self, *g, check=True): raise ValueError("can only compose with a positive valuation series") h._coeff_stream._approximate_order = 2 - - # We now ahave that every element of g has a _coeff_stream + # We now have that every element of g has a _coeff_stream sorder = self._coeff_stream._approximate_order if len(g) == 1: g0 = g[0] @@ -3461,7 +3889,7 @@ def __call__(self, *g, check=True): # we assume that the valuation of self[i](g) is at least i def coefficient(n): return sum(self[i] * (g0**i)[n] for i in range(n+1)) - coeff_stream = Stream_function(coefficient, R, P._sparse, 1) + coeff_stream = Stream_function(coefficient, P._sparse, 1) return P.element_class(P, coeff_stream) coeff_stream = Stream_cauchy_compose(self._coeff_stream, g0._coeff_stream) @@ -3476,99 +3904,329 @@ def coefficient(n): # Make sure the element returned from the composition is in P r += P(self[i](g))[n] return r - coeff_stream = Stream_function(coefficient, R, P._sparse, sorder * gv) + coeff_stream = Stream_function(coefficient, P._sparse, sorder * gv) return P.element_class(P, coeff_stream) compose = __call__ - def _format_series(self, formatter, format_strings=False): - """ - Return nonzero ``self`` formatted by ``formatter``. + def revert(self): + r""" + Return the compositional inverse of ``self``. - TESTS:: + Given a Taylor series `f`, the compositional inverse is a + Laurent series `g` over the same base ring, such that + `(f \circ g)(z) = f(g(z)) = z`. - sage: L. = LazyTaylorSeriesRing(QQ) - sage: f = 1 / (2 - x^2 + y) - sage: f._format_series(repr) - '1/2 + (-1/4*y) + (1/4*x^2+1/8*y^2) + (-1/4*x^2*y-1/16*y^3) + (1/8*x^4+3/16*x^2*y^2+1/32*y^4) + (-3/16*x^4*y-1/8*x^2*y^3-1/64*y^5) + (1/16*x^6+3/16*x^4*y^2+5/64*x^2*y^4+1/128*y^6) + O(x,y)^7' + The compositional inverse exists if and only if: - sage: f = (2 - x^2 + y) - sage: f._format_series(repr) - '2 + y + (-x^2)' - """ - P = self.parent() - cs = self._coeff_stream - v = cs._approximate_order - if isinstance(cs, Stream_exact): - if not cs._constant: - m = cs._degree - else: - m = cs._degree + P.options.constant_length - else: - m = v + P.options.display_length + - `val(f) = 1`, or - atomic_repr = P._internal_poly_ring.base_ring()._repr_option('element_is_atomic') - mons = [P._monomial(self[i], i) for i in range(v, m) if self[i]] - if not isinstance(cs, Stream_exact) or cs._constant: - if P._internal_poly_ring.base_ring() is P.base_ring(): - bigO = ["O(%s)" % P._monomial(1, m)] - else: - bigO = ["O(%s)^%s" % (', '.join(str(g) for g in P._names), m)] - else: - bigO = [] + - `f = a + b z` with `a, b \neq 0`, or - from sage.misc.latex import latex - from sage.typeset.unicode_art import unicode_art - from sage.typeset.ascii_art import ascii_art - from sage.misc.repr import repr_lincomb - from sage.typeset.symbols import ascii_left_parenthesis, ascii_right_parenthesis - from sage.typeset.symbols import unicode_left_parenthesis, unicode_right_parenthesis - if formatter == repr: - poly = repr_lincomb([(1, m) for m in mons + bigO], strip_one=True) - elif formatter == latex: - poly = repr_lincomb([(1, m) for m in mons + bigO], is_latex=True, strip_one=True) - elif formatter == ascii_art: - if atomic_repr: - poly = ascii_art(*(mons + bigO), sep = " + ") - else: - def parenthesize(m): - a = ascii_art(m) - h = a.height() - return ascii_art(ascii_left_parenthesis.character_art(h), - a, ascii_right_parenthesis.character_art(h)) - poly = ascii_art(*([parenthesize(m) for m in mons] + bigO), sep = " + ") - elif formatter == unicode_art: - if atomic_repr: - poly = unicode_art(*(mons + bigO), sep = " + ") - else: - def parenthesize(m): - a = unicode_art(m) - h = a.height() - return unicode_art(unicode_left_parenthesis.character_art(h), - a, unicode_right_parenthesis.character_art(h)) - poly = unicode_art(*([parenthesize(m) for m in mons] + bigO), sep = " + ") + EXAMPLES:: - return poly + sage: L. = LazyPowerSeriesRing(QQ) + sage: (2*z).revert() + 1/2*z + sage: (z-z^2).revert() + z + z^2 + 2*z^3 + 5*z^4 + 14*z^5 + 42*z^6 + 132*z^7 + O(z^8) - def polynomial(self, degree=None, names=None): - r""" - Return ``self`` as a polynomial if ``self`` is actually so. + sage: s = L(degree=1, constant=-1) + sage: s.revert() + -z - z^2 - z^3 + O(z^4) - INPUT: + sage: s = L(degree=1, constant=1) + sage: s.revert() + z - z^2 + z^3 - z^4 + z^5 - z^6 + z^7 + O(z^8) - - ``degree`` -- ``None`` or an integer - - ``names`` -- names of the variables; if it is ``None``, the name of - the variables of the series is used + TESTS:: - OUTPUT: + sage: L. = LazyPowerSeriesRing(QQ) + sage: s = L(lambda n: 2 if n == 1 else 0, valuation=1); s + 2*z + O(z^8) + sage: s.revert() + 1/2*z + O(z^8) - If ``degree`` is not ``None``, the terms of the series of degree greater - than ``degree`` are truncated first. If ``degree`` is ``None`` and the - series is not a polynomial polynomial, a ``ValueError`` is raised. + sage: (2+3*z).revert() + -2/3 + 1/3*z + + sage: s = L(lambda n: 2 if n == 0 else 3 if n == 1 else 0, valuation=0); s + 2 + 3*z + O(z^7) + sage: s.revert() + Traceback (most recent call last): + ... + ValueError: cannot determine whether the compositional inverse exists + + sage: R. = QQ[] + sage: L. = LazyPowerSeriesRing(R.fraction_field()) + sage: s = L([q], valuation=0, constant=t); s + q + t*z + t*z^2 + t*z^3 + O(z^4) + sage: s.revert() + Traceback (most recent call last): + ... + ValueError: compositional inverse does not exist + + We look at some cases where the compositional inverse does not exist: + + `f = 0`:: + + sage: L(0).revert() + Traceback (most recent call last): + ... + ValueError: compositional inverse does not exist + sage: (z - z).revert() + Traceback (most recent call last): + ... + ValueError: compositional inverse does not exist + + `val(f) != 1` and `f(0) * f(1) = 0`:: + + sage: (z^2).revert() + Traceback (most recent call last): + ... + ValueError: compositional inverse does not exist + + sage: L(1).revert() + Traceback (most recent call last): + ... + ValueError: compositional inverse does not exist + + Reversion of exact series:: + + sage: f = L([1, 2], valuation=0, constant=1) + sage: f.revert() + Traceback (most recent call last): + ... + ValueError: compositional inverse does not exist + + sage: f = L([-1, -1], valuation=1, constant=-1) + sage: f.revert() + (-z) + (-z^2) + (-z^3) + (-z^4) + (-z^5) + O(z^6) + + sage: f = L([-1, 0, -1], valuation=1, constant=-1) + sage: f.revert() + (-z) + z^3 + (-z^4) + (-2*z^5) + 6*z^6 + z^7 + O(z^8) + + sage: f = L([-1], valuation=1, degree=3, constant=-1) + sage: f.revert() + (-z) + z^3 + (-z^4) + (-2*z^5) + 6*z^6 + z^7 + O(z^8) + """ + P = self.parent() + if P._arity != 1: + raise ValueError("arity must be equal to 1") + coeff_stream = self._coeff_stream + if isinstance(coeff_stream, Stream_zero): + raise ValueError("compositional inverse does not exist") + if isinstance(coeff_stream, Stream_exact): + if coeff_stream._constant: + if coeff_stream.order() == 1: + R = P.base_ring() + # we cannot assume that the last initial coefficient + # and the constant differ, see stream.Stream_exact + if (coeff_stream._degree == 1 + len(coeff_stream._initial_coefficients) + and coeff_stream._constant == -R.one() + and all(c == -R.one() for c in coeff_stream._initial_coefficients)): + # self = -z/(1-z); self.revert() = -z/(1-z) + return self + else: + raise ValueError("compositional inverse does not exist") + else: + if coeff_stream._degree == 2: + # self = a + b*z; self.revert() = -a/b + 1/b * z + a = coeff_stream[0] + b = coeff_stream[1] + coeff_stream = Stream_exact((-a/b, 1/b), + coeff_stream._is_sparse, + order=0) + return P.element_class(P, coeff_stream) + + if coeff_stream.order() != 1: + raise ValueError("compositional inverse does not exist") + + if not coeff_stream[1]: + raise ValueError("compositional inverse does not exist") + + if coeff_stream[0]: + raise ValueError("cannot determine whether the compositional inverse exists") + + z = P.gen() + g = P(None, valuation=1) + g.define(z / ((self / z)(g))) + return g + + compositional_inverse = revert + + def derivative(self, *args): + """ + Return the derivative of the Taylor series. + + Multiple variables and iteration counts may be supplied; see + the documentation of + :func:`sage.calculus.functional.derivative` function for + details. + + EXAMPLES:: + + sage: T. = LazyPowerSeriesRing(ZZ) + sage: z.derivative() + 1 + sage: (1+z+z^2).derivative(3) + 0 + sage: (1/(1-z)).derivative() + 1 + 2*z + 3*z^2 + 4*z^3 + 5*z^4 + 6*z^5 + 7*z^6 + O(z^7) + + sage: R. = QQ[] + sage: L. = LazyPowerSeriesRing(R) + sage: f = 1/(1-q*x+y); f + 1 + (q*x-y) + (q^2*x^2+(-2*q)*x*y+y^2) + (q^3*x^3+(-3*q^2)*x^2*y+3*q*x*y^2-y^3) + (q^4*x^4+(-4*q^3)*x^3*y+6*q^2*x^2*y^2+(-4*q)*x*y^3+y^4) + (q^5*x^5+(-5*q^4)*x^4*y+10*q^3*x^3*y^2+(-10*q^2)*x^2*y^3+5*q*x*y^4-y^5) + (q^6*x^6+(-6*q^5)*x^5*y+15*q^4*x^4*y^2+(-20*q^3)*x^3*y^3+15*q^2*x^2*y^4+(-6*q)*x*y^5+y^6) + O(x,y)^7 + sage: f.derivative(q) + x + (2*q*x^2+(-2)*x*y) + (3*q^2*x^3+(-6*q)*x^2*y+3*x*y^2) + (4*q^3*x^4+(-12*q^2)*x^3*y+12*q*x^2*y^2+(-4)*x*y^3) + (5*q^4*x^5+(-20*q^3)*x^4*y+30*q^2*x^3*y^2+(-20*q)*x^2*y^3+5*x*y^4) + (6*q^5*x^6+(-30*q^4)*x^5*y+60*q^3*x^4*y^2+(-60*q^2)*x^3*y^3+30*q*x^2*y^4+(-6)*x*y^5) + O(x,y)^7 + + """ + P = self.parent() + R = P._laurent_poly_ring + V = R.gens() + order = 0 + vars = [] + gen_vars = [] + for x in derivative_parse(args): + if x is None: + order += 1 + elif x in V: + gen_vars.append(x) + else: + vars.append(x) + + if P._arity > 1 and order: + raise ValueError("for multivariate series you have to specify the variable with respect to which the derivative should be taken") + else: + order += len(gen_vars) + + coeff_stream = self._coeff_stream + if isinstance(coeff_stream, Stream_zero): + return self + + if P._arity > 1: + v = gen_vars + vars + d = -len(gen_vars) + coeff_stream = Stream_map_coefficients(coeff_stream, + lambda c: R(c).derivative(v)) + coeff_stream = Stream_shift(coeff_stream, d) + return P.element_class(P, coeff_stream) + + if (isinstance(coeff_stream, Stream_exact) + and not coeff_stream._constant): + if coeff_stream._degree <= order: + return P.zero() + if vars: + coeffs = [prod(i-k for k in range(order)) * c.derivative(vars) + for i, c in enumerate(coeff_stream._initial_coefficients, + coeff_stream._approximate_order)] + else: + coeffs = [prod(i-k for k in range(order)) * c + for i, c in enumerate(coeff_stream._initial_coefficients, + coeff_stream._approximate_order)] + coeff_stream = Stream_exact(coeffs, + self._coeff_stream._is_sparse, + order=coeff_stream._approximate_order - order, + constant=coeff_stream._constant) + return P.element_class(P, coeff_stream) + + coeff_stream = Stream_derivative(self._coeff_stream, order) + if vars: + coeff_stream = Stream_map_coefficients(coeff_stream, + lambda c: c.derivative(vars)) + return P.element_class(P, coeff_stream) + + def _format_series(self, formatter, format_strings=False): + """ + Return nonzero ``self`` formatted by ``formatter``. + + TESTS:: + + sage: L. = LazyPowerSeriesRing(QQ) + sage: f = 1 / (2 - x^2 + y) + sage: f._format_series(repr) + '1/2 + (-1/4*y) + (1/4*x^2+1/8*y^2) + (-1/4*x^2*y-1/16*y^3) + (1/8*x^4+3/16*x^2*y^2+1/32*y^4) + (-3/16*x^4*y-1/8*x^2*y^3-1/64*y^5) + (1/16*x^6+3/16*x^4*y^2+5/64*x^2*y^4+1/128*y^6) + O(x,y)^7' + + sage: f = (2 - x^2 + y) + sage: f._format_series(repr) + '2 + y + (-x^2)' + """ + P = self.parent() + cs = self._coeff_stream + v = cs._approximate_order + if isinstance(cs, Stream_exact): + if not cs._constant: + m = cs._degree + else: + m = cs._degree + P.options.constant_length + else: + m = v + P.options.display_length + + atomic_repr = P._internal_poly_ring.base_ring()._repr_option('element_is_atomic') + mons = [P._monomial(self[i], i) for i in range(v, m) if self[i]] + if not isinstance(cs, Stream_exact) or cs._constant: + if P._internal_poly_ring.base_ring() is P.base_ring(): + bigO = ["O(%s)" % P._monomial(1, m)] + else: + bigO = ["O(%s)^%s" % (', '.join(str(g) for g in P._names), m)] + else: + bigO = [] + + from sage.misc.latex import latex + from sage.typeset.unicode_art import unicode_art + from sage.typeset.ascii_art import ascii_art + from sage.misc.repr import repr_lincomb + from sage.typeset.symbols import ascii_left_parenthesis, ascii_right_parenthesis + from sage.typeset.symbols import unicode_left_parenthesis, unicode_right_parenthesis + if formatter == repr: + poly = repr_lincomb([(1, m) for m in mons + bigO], strip_one=True) + elif formatter == latex: + poly = repr_lincomb([(1, m) for m in mons + bigO], is_latex=True, strip_one=True) + elif formatter == ascii_art: + if atomic_repr: + poly = ascii_art(*(mons + bigO), sep = " + ") + else: + def parenthesize(m): + a = ascii_art(m) + h = a.height() + return ascii_art(ascii_left_parenthesis.character_art(h), + a, ascii_right_parenthesis.character_art(h)) + poly = ascii_art(*([parenthesize(m) for m in mons] + bigO), sep = " + ") + elif formatter == unicode_art: + if atomic_repr: + poly = unicode_art(*(mons + bigO), sep = " + ") + else: + def parenthesize(m): + a = unicode_art(m) + h = a.height() + return unicode_art(unicode_left_parenthesis.character_art(h), + a, unicode_right_parenthesis.character_art(h)) + poly = unicode_art(*([parenthesize(m) for m in mons] + bigO), sep = " + ") + + return poly + + def polynomial(self, degree=None, names=None): + r""" + Return ``self`` as a polynomial if ``self`` is actually so. + + INPUT: + + - ``degree`` -- ``None`` or an integer + - ``names`` -- names of the variables; if it is ``None``, the name of + the variables of the series is used + + OUTPUT: + + If ``degree`` is not ``None``, the terms of the series of + degree greater than ``degree`` are first truncated. If + ``degree`` is ``None`` and the series is not a polynomial + polynomial, a ``ValueError`` is raised. EXAMPLES:: - sage: L. = LazyTaylorSeriesRing(ZZ) + sage: L. = LazyPowerSeriesRing(ZZ) sage: f = x^2 + y*x - x + 2; f 2 + (-x) + (x^2+x*y) sage: f.polynomial() @@ -3591,6 +4249,12 @@ def polynomial(self, degree=None, names=None): True sage: g3.polynomial(0) 1 + + sage: L. = LazyPowerSeriesRing(ZZ) + sage: f = z-z^2 + sage: f.polynomial() + -z^2 + z + """ from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing S = self.parent() @@ -3602,17 +4266,16 @@ def polynomial(self, degree=None, names=None): if degree is None: if (isinstance(self._coeff_stream, Stream_exact) - and not self._coeff_stream._constant): + and not self._coeff_stream._constant): m = self._coeff_stream._degree else: raise ValueError("not a polynomial") else: m = degree + 1 - if names is None: - names = S.variable_names() - - return R.sum(self[:m]) + if S._arity == 1: + return R(self[0:m]) + return R.sum(self[0:m]) class LazyCompletionGradedAlgebraElement(LazyCauchyProductSeries): @@ -3705,22 +4368,37 @@ class LazySymmetricFunction(LazyCompletionGradedAlgebraElement): """ def __call__(self, *args, check=True): r""" - Return the composition of ``self`` with ``args``. + Return the composition of ``self`` with ``g``. The arity of ``self`` must be equal to the number of arguments provided. - Given two lazy symmetric functions `f` and `g` over the same - base ring, the composition (or plethysm) `(f \circ g)` is - defined if and only if: - - - `g = 0`, - - `g` is non-zero and `f` has only finitely many non-zero coefficients, - - `g` is non-zero and `val(g) > 0`. + Given a lazy symmetric function `f` of arity `n` and a tuple + of lazy symmetric functions `g = (g_1,\dots, g_n)` over the + same base ring, the composition (or plethysm) `(f \circ g)` + is defined if and only if for each `1\leq k\leq n`: + + - `g_i = 0`, or + - setting all alphabets except the `i`th in `f` to zero + yields a symmetric function with only finitely many + non-zero coefficients, or + - `val(g) > 0`. + + If `f` is a univariate 'exact' lazy symmetric function, we + can check whether `f` has only finitely many non-zero + coefficients. However, if `f` has larger arity, we have no + way to test whether setting all but one alphabets of `f` to + zero yields a polynomial, except if `f` itself is 'exact' and + therefore a symmetric function with only finitely many + non-zero coefficients. INPUT: - - ``args`` -- other (lazy) symmetric functions + - ``g`` -- other (lazy) symmetric functions + + .. TODO:: + + allow specification of degree one elements EXAMPLES:: @@ -3753,9 +4431,27 @@ def __call__(self, *args, check=True): sage: E1 = S(lambda n: s[n], valuation=1) sage: E = 1 + E1 sage: P = E(E1) - sage: [s(x) for x in P[:5]] + sage: P[:5] [s[], s[1], 2*s[2], s[2, 1] + 3*s[3], 2*s[2, 2] + 2*s[3, 1] + 5*s[4]] + The plethysm with a tensor product is also implemented:: + + sage: s = SymmetricFunctions(QQ).s() + sage: X = tensor([s[1],s[[]]]) + sage: Y = tensor([s[[]],s[1]]) + sage: S = LazySymmetricFunctions(s) + sage: S2 = LazySymmetricFunctions(tensor([s, s])) + sage: A = S(s[1,1,1]) + sage: B = S2(X+Y) + sage: A(B) + (s[]#s[1,1,1]+s[1]#s[1,1]+s[1,1]#s[1]+s[1,1,1]#s[]) + + sage: H = S(lambda n: s[n]) + sage: H(S2(X*Y)) + (s[]#s[]) + (s[1]#s[1]) + (s[1,1]#s[1,1]+s[2]#s[2]) + (s[1,1,1]#s[1,1,1]+s[2,1]#s[2,1]+s[3]#s[3]) + O^7 + sage: H(S2(X+Y)) + (s[]#s[]) + (s[]#s[1]+s[1]#s[]) + (s[]#s[2]+s[1]#s[1]+s[2]#s[]) + (s[]#s[3]+s[1]#s[2]+s[2]#s[1]+s[3]#s[]) + (s[]#s[4]+s[1]#s[3]+s[2]#s[2]+s[3]#s[1]+s[4]#s[]) + (s[]#s[5]+s[1]#s[4]+s[2]#s[3]+s[3]#s[2]+s[4]#s[1]+s[5]#s[]) + (s[]#s[6]+s[1]#s[5]+s[2]#s[4]+s[3]#s[3]+s[4]#s[2]+s[5]#s[1]+s[6]#s[]) + O^7 + TESTS:: sage: s = SymmetricFunctions(QQ).s() @@ -3767,8 +4463,7 @@ def __call__(self, *args, check=True): True sage: f = 1 / (1 - S(s[2])) sage: g = S(s[1]) / (1 - S(s[1])) - sage: h = f(g) - sage: h + sage: f(g) s[] + s[2] + (s[1,1,1]+2*s[2,1]+s[3]) + (2*s[1,1,1,1]+4*s[2,1,1]+5*s[2,2]+5*s[3,1]+3*s[4]) + (2*s[1,1,1,1,1]+10*s[2,1,1,1]+14*s[2,2,1]+18*s[3,1,1]+16*s[3,2]+14*s[4,1]+4*s[5]) @@ -3780,30 +4475,64 @@ def __call__(self, *args, check=True): Traceback (most recent call last): ... ValueError: can only compose with a positive valuation series + + Check that composing the zero series with anything yields + zero in the correct parent:: + + sage: e = SymmetricFunctions(QQ).e() + sage: h = SymmetricFunctions(QQ).h() + sage: s = SymmetricFunctions(QQ).s() + sage: p = SymmetricFunctions(QQ).p() + sage: L = LazySymmetricFunctions(tensor([e, h])) + sage: r = (L(0)(s[1], p[1])); r + 0 + sage: r.parent() + Symmetric Functions over Rational Field in the Schur basis + + Check that composing `f` with zero series yields the constant term of `f`:: + + sage: f = 3*L(tensor([s[1], s[1]])) + sage: f(0, 0) + 0 + sage: (3+f)(0, 0) + 3 """ - if len(args) != self.parent()._arity: + fP = parent(self) + if len(args) != fP._arity: raise ValueError("arity must be equal to the number of arguments provided") - from sage.combinat.sf.sfa import is_SymmetricFunction - if not all(isinstance(g, LazySymmetricFunction) or is_SymmetricFunction(g) or not g for g in args): - raise ValueError("all arguments must be (possibly lazy) symmetric functions") + # Find a good parent for the result + from sage.structure.element import get_coercion_model + cm = get_coercion_model() + P = cm.common_parent(self.base_ring(), *[parent(h) for h in args]) + + # f = 0 if isinstance(self._coeff_stream, Stream_zero): - return self + return P.zero() + + # g = (0, ..., 0) + if all((not isinstance(h, LazyModuleElement) and not h) + or (isinstance(h, LazyModuleElement) + and isinstance(h._coeff_stream, Stream_zero)) + for h in args): + f = self[0] + # FIXME: TypeError: unable to convert 0 to a rational + if f: + return P(f.leading_coefficient()) + return P.zero() if len(args) == 1: g = args[0] - P = g.parent() - - # Handle other types of 0s - if not isinstance(g, LazySymmetricFunction) and not g: - return P(self[0].leading_coefficient()) + if (isinstance(self._coeff_stream, Stream_exact) + and not self._coeff_stream._constant): - if isinstance(self._coeff_stream, Stream_exact) and not self._coeff_stream._constant: - f = self.symmetric_function() - if is_SymmetricFunction(g): + if not isinstance(g, LazySymmetricFunction): + f = self.symmetric_function() return f(g) - # g must be a LazySymmetricFunction - if isinstance(g._coeff_stream, Stream_exact) and not g._coeff_stream._constant: + + if (isinstance(g._coeff_stream, Stream_exact) + and not g._coeff_stream._constant): + f = self.symmetric_function() gs = g.symmetric_function() return P(f(gs)) @@ -3815,21 +4544,575 @@ def __call__(self, *args, check=True): P = LazySymmetricFunctions(R) g = P(g) - # self has (potentially) infinitely many terms - if check: + if check and not (isinstance(self._coeff_stream, Stream_exact) + and not self._coeff_stream._constant): if g._coeff_stream._approximate_order == 0: if g[0]: raise ValueError("can only compose with a positive valuation series") g._coeff_stream._approximate_order = 1 - ps = P._laurent_poly_ring.realization_of().p() - coeff_stream = Stream_plethysm(self._coeff_stream, g._coeff_stream, ps) + if P._arity == 1: + ps = R.realization_of().p() + else: + ps = tensor([R._sets[0].realization_of().p()]*P._arity) + coeff_stream = Stream_plethysm(self._coeff_stream, g._coeff_stream, + ps, R) + return P.element_class(P, coeff_stream) + else: raise NotImplementedError("only implemented for arity 1") + plethysm = __call__ + + def revert(self): + r""" + Return the compositional inverse of ``self``. + + Given a symmetric function `f`, the compositional inverse is + a symmetric function `g` over the same base ring, such that + `f \circ g = p_1`. Thus, it is the inverse with respect to + plethystic substitution. + + The compositional inverse exists if and only if: + + - `val(f) = 1`, or + + - `f = a + b p_1` with `a, b \neq 0`. + + EXAMPLES:: + + sage: h = SymmetricFunctions(QQ).h() + sage: L = LazySymmetricFunctions(h) + sage: f = L(lambda n: h[n]) - 1 + sage: f(f.revert()) + h[1] + O^7 + + TESTS:: + + sage: f = L(lambda n: h[n]) - 1 - h[1] + sage: f.compositional_inverse() + Traceback (most recent call last): + ... + ValueError: compositional inverse does not exist + + sage: R. = QQ[] + sage: p = SymmetricFunctions(R.fraction_field()).p() + sage: L = LazySymmetricFunctions(p) + sage: f = L(a + b*p[1]) + sage: f.revert() + (((-a)/b)*p[]) + 1/b*p[1] + + sage: f = L(2*p[1]) + sage: f.revert() + 1/2*p[1] + + sage: f = L(2*p[1] + p[2] + p[1,1]) + sage: f.revert() + 1/2*p[1] + (-1/4*p[1,1]-1/2*p[2]) + (1/4*p[1,1,1]+1/2*p[2,1]) + (-5/16*p[1,1,1,1]-3/4*p[2,1,1]+1/2*p[4]) + (7/16*p[1,1,1,1,1]+5/4*p[2,1,1,1]+1/2*p[2,2,1]-1/2*p[4,1]) + (-21/32*p[1,1,1,1,1,1]-35/16*p[2,1,1,1,1]-3/2*p[2,2,1,1]-1/4*p[2,2,2]+3/4*p[4,1,1]) + (33/32*p[1,1,1,1,1,1,1]+63/16*p[2,1,1,1,1,1]+15/4*p[2,2,1,1,1]+3/4*p[2,2,2,1]-5/4*p[4,1,1,1]-p[4,2,1]) + O^8 + + ALGORITHM: + + Let `F` be a symmetric function with valuation `1`, i.e., + whose constant term vanishes and whose degree one term equals + `b p_1`. Then + + .. MATH:: + + (F - b p_1) \circ G = F \circ G - b p_1 \circ G = p_1 - b G, + + and therefore `G = (p_1 - (F - b p_1) \circ G) / b`, which + allows recursive computation of `G`. + + .. SEEALSO:: + + The compositional inverse `\Omega` of the symmetric + function `h_1 + h_2 + \dots` can be handled much more + efficiently using specialized methods. See + :func:`~sage.combinat.species.generating_series.LogarithmCycleIndexSeries` + + AUTHORS: + + - Andrew Gainer-Dewar + - Martin Rubey + + """ + P = self.parent() + if P._arity != 1: + raise ValueError("arity must be equal to 1") + coeff_stream = self._coeff_stream + if isinstance(coeff_stream, Stream_zero): + raise ValueError("compositional inverse does not exist") + R = P._laurent_poly_ring + if (isinstance(coeff_stream, Stream_exact) + and coeff_stream.order() >= 0 + and coeff_stream._degree == 2): + # self = a + b * p_1; self.revert() = -a/b + 1/b * p_1 + a = coeff_stream[0] + b = coeff_stream[1][Partition([1])] + X = R(Partition([1])) + coeff_stream = Stream_exact((-a/b, 1/b * X), + coeff_stream._is_sparse, + order=0) + return P.element_class(P, coeff_stream) + + if not coeff_stream[1]: + raise ValueError("compositional inverse does not exist") + + if coeff_stream[0]: + raise ValueError("cannot determine whether the compositional inverse exists") + + X = R(Partition([1])) + b = coeff_stream[1][Partition([1])] + g = P(None, valuation=1) + g.define(~b * X - (self - b * X)(g)) + return g + + plethystic_inverse = revert + + compositional_inverse = revert + + def derivative_with_respect_to_p1(self, n=1): + r""" + Return the symmetric function obtained by taking the + derivative of ``self`` with respect to the power-sum + symmetric function `p_1` when the expansion of ``self`` in + the power-sum basis is considered as a polynomial in `p_k`'s + (with `k \geq 1`). + + This is the same as skewing ``self`` by the first power-sum + symmetric function `p_1`. + + INPUT: + + - ``n`` -- (default: 1) nonnegative integer which determines + which power of the derivative is taken + + EXAMPLES: + + The species `E` of sets satisfies the relationship `E' = E`:: + + sage: h = SymmetricFunctions(QQ).h() + sage: T = LazySymmetricFunctions(h) + sage: E = T(lambda n: h[n]) + sage: E - E.derivative_with_respect_to_p1() + O^6 + + The species `C` of cyclic orderings and the species `L` of linear + orderings satisfy the relationship `C' = L`:: + + sage: p = SymmetricFunctions(QQ).p() + sage: C = T(lambda n: (sum(euler_phi(k)*p([k])**(n//k) for k in divisors(n))/n if n > 0 else 0)) + sage: L = T(lambda n: p([1]*n)) + sage: L - C.derivative_with_respect_to_p1() + O^6 + + TESTS:: + + sage: T = LazySymmetricFunctions(p) + sage: a = T(p([1,1,1])) + sage: a.derivative_with_respect_to_p1() + (3*p[1,1]) + O^9 + sage: a.derivative_with_respect_to_p1(1) + (3*p[1,1]) + O^9 + sage: a.derivative_with_respect_to_p1(2) + 6*p[1] + O^8 + sage: a.derivative_with_respect_to_p1(3) + 6*p[] + O^7 + """ + P = self.parent() + if P._arity != 1: + raise ValueError("arity must be equal to 1") + + coeff_stream = Stream_map_coefficients(self._coeff_stream, + lambda c: c.derivative_with_respect_to_p1(n)) + coeff_stream = Stream_shift(coeff_stream, -n) return P.element_class(P, coeff_stream) - plethysm = __call__ + def functorial_composition(self, *args): + r"""Return the functorial composition of ``self`` and ``g``. + + Let `X` be a finite set of cardinality `m`. For a group + action of the symmetric group `g: S_n \to S_X` and a + (possibly virtual) representation of the symmetric group on + `X`, `f: S_X \to GL(V)`, the functorial composition is the + (virtual) representation of the symmetric group `f \Box g: + S_n \to GL(V)` given by `\sigma \mapsto f(g(\sigma))`. + + This is more naturally phrased in the language of + combinatorial species. Let `F` and `G` be species, then + their functorial composition is the species `F \Box G` with + `(F \Box G) [A] = F[ G[A] ]`. In other words, an `(F \Box + G)`-structure on a set `A` of labels is an `F`-structure + whose labels are the set of all `G`-structures on `A`. + + The Frobenius character (or cycle index series) of `F \Box G` + can be computed as follows, see section 2.2 of [BLL]_): + + .. MATH:: + + \sum_{n \geq 0} \frac{1}{n!} \sum_{\sigma \in + \mathfrak{S}_{n}} \operatorname{fix} F[ (G[\sigma])_{1}, + (G[\sigma])_{2}, \ldots ] \, p_{1}^{\sigma_{1}} + p_{2}^{\sigma_{2}} \cdots. + + .. WARNING:: + + The operation `f \Box g` only makes sense when `g` + corresponds to a permutation representation, i.e., a + group action. + + EXAMPLES: + + The species `G` of simple graphs can be expressed in terms of + a functorial composition: `G = \mathfrak{p} \Box + \mathfrak{p}_{2}`, where `\mathfrak{p}` is the + :class:`~sage.combinat.species.subset_species.SubsetSpecies`.:: + + sage: R. = QQ[] + sage: h = SymmetricFunctions(R).h() + sage: m = SymmetricFunctions(R).m() + sage: L = LazySymmetricFunctions(m) + sage: P = L(lambda n: sum(q^k*h[n-k]*h[k] for k in range(n+1))) + sage: P2 = L(lambda n: h[2]*h[n-2], valuation=2) + sage: P.functorial_composition(P2)[:4] + [m[], + m[1], + (q+1)*m[1, 1] + (q+1)*m[2], + (q^3+3*q^2+3*q+1)*m[1, 1, 1] + (q^3+2*q^2+2*q+1)*m[2, 1] + (q^3+q^2+q+1)*m[3]] + + For example, there are:: + + sage: P.functorial_composition(P2)[4].coefficient([4])[3] + 3 + + unlabelled graphs on 4 vertices and 3 edges, and:: + + sage: P.functorial_composition(P2)[4].coefficient([2,2])[3] + 8 + + labellings of their vertices with two 1's and two 2's. + + The symmetric function `h_1 \sum_n h_n` is the neutral + element with respect to functorial composition:: + + sage: p = SymmetricFunctions(QQ).p() + sage: h = SymmetricFunctions(QQ).h() + sage: e = SymmetricFunctions(QQ).e() + sage: L = LazySymmetricFunctions(h) + sage: E = L(lambda n: h[n]) + sage: Ep = p[1]*E.derivative_with_respect_to_p1(); Ep + h[1] + (h[1,1]) + (h[2,1]) + (h[3,1]) + (h[4,1]) + (h[5,1]) + O^7 + sage: f = L(lambda n: h[n-n//2, n//2]) + sage: f - Ep.functorial_composition(f) + O^7 + + The functorial composition distributes over the sum:: + + sage: F1 = L(lambda n: h[n]) + sage: F2 = L(lambda n: e[n]) + sage: f1 = F1.functorial_composition(f) + sage: f2 = F2.functorial_composition(f) + sage: (F1 + F2).functorial_composition(f) - f1 - f2 + O^7 + + TESTS: + + Check a corner case:: + + sage: h = SymmetricFunctions(QQ).h() + sage: L = LazySymmetricFunctions(h) + sage: L(h[2,1]).functorial_composition(3*h[0]) + 3*h[] + O^7 + + Check an instance of a non-group action:: + + sage: s = SymmetricFunctions(QQ).s() + sage: p = SymmetricFunctions(QQ).p() + sage: L = LazySymmetricFunctions(p) + sage: f = L(lambda n: s[n]) + sage: g = 2*s[2, 1, 1] + s[2, 2] + 3*s[4] + sage: r = f.functorial_composition(g); r[4] + Traceback (most recent call last): + ... + ValueError: the argument is not the Frobenius character of a permutation representation + + """ + if len(args) != self.parent()._arity: + raise ValueError("arity must be equal to the number of arguments provided") + from sage.combinat.sf.sfa import is_SymmetricFunction + if not all(isinstance(g, LazySymmetricFunction) + or is_SymmetricFunction(g) + or not g for g in args): + raise ValueError("all arguments must be (possibly lazy) symmetric functions") + + if len(args) == 1: + g = args[0] + P = g.parent() + if isinstance(g, LazySymmetricFunction): + R = P._laurent_poly_ring + else: + from sage.rings.lazy_series_ring import LazySymmetricFunctions + R = g.parent() + P = LazySymmetricFunctions(R) + g = P(g) + + p = R.realization_of().p() + # TODO: does the following introduce a memory leak? + g = Stream_map_coefficients(g._coeff_stream, p) + f = Stream_map_coefficients(self._coeff_stream, p) + + def g_cycle_type(s, n): + # the cycle type of G[sigma] of any permutation sigma + # with cycle type s, which is a partition of n + if not n: + if g[0]: + return Partition([1]*ZZ(g[0].coefficient([]))) + return Partition([]) + res = [] + # in the species case, k is at most + # factorial(n) * g[n].coefficient([1]*n) + for k in range(1, lcm(s) + 1): + e = 0 + for d in divisors(k): + m = moebius(d) + if not m: + continue + u = s.power(k // d) + # it could be, that we never need to compute + # g[n], so we only do this here + g_u = g[n] + if g_u: + e += m * u.aut() * g_u.coefficient(u) + # e / k might not be an integer if g is not a + # group action, so it is good to check + res.extend([k] * ZZ(e / k)) + res.reverse() + return Partition(res) + + def coefficient(n): + terms = {} + t_size = None + for s in Partitions(n): + t = g_cycle_type(s, n) + if t_size is None: + t_size = sum(t) + f_t = f[t_size] + if not f_t: + break + elif t_size != sum(t): + raise ValueError("the argument is not the Frobenius character of a permutation representation") + + terms[s] = t.aut() * f_t.coefficient(t) / s.aut() + return R(p.element_class(p, terms)) + + coeff_stream = Stream_function(coefficient, P._sparse, 0) + return P.element_class(P, coeff_stream) + else: + raise NotImplementedError("only implemented for arity 1") + + def arithmetic_product(self, *args, check=True): + r""" + Return the arithmetic product of ``self`` with ``g``. + + The arithmetic product is a binary operation `\boxdot` on the + ring of symmetric functions which is bilinear in its two + arguments and satisfies + + .. MATH:: + + p_{\lambda} \boxdot p_{\mu} = \prod\limits_{i \geq 1, j \geq 1} + p_{\mathrm{lcm}(\lambda_i, \mu_j)}^{\mathrm{gcd}(\lambda_i, \mu_j)} + + for any two partitions `\lambda = (\lambda_1, \lambda_2, \lambda_3, + \dots )` and `\mu = (\mu_1, \mu_2, \mu_3, \dots )` (where `p_{\nu}` + denotes the power-sum symmetric function indexed by the partition + `\nu`, and `p_i` denotes the `i`-th power-sum symmetric function). + This is enough to define the arithmetic product if the base ring + is torsion-free as a `\ZZ`-module; for all other cases the + arithmetic product is uniquely determined by requiring it to be + functorial in the base ring. See + http://mathoverflow.net/questions/138148/ for a discussion of + this arithmetic product. + + If `f` and `g` are two symmetric functions which are homogeneous + of degrees `a` and `b`, respectively, then `f \boxdot g` is + homogeneous of degree `ab`. + + The arithmetic product is commutative and associative and has + unity `e_1 = p_1 = h_1`. + + For species `M` and `N` such that `M[\varnothing] = + N[\varnothing] = \varnothing`, their arithmetic product is + the species `M \boxdot N` of "`M`-assemblies of cloned + `N`-structures". This operation is defined and several + examples are given in [MM2008]_. + + INPUT: + + - ``g`` -- a cycle index series having the same parent as ``self`` + + - ``check`` -- (default: ``True``) a Boolean which, when set + to ``False``, will cause input checks to be skipped + + OUTPUT: + + The arithmetic product of ``self`` with ``g``. + + .. SEEALSO:: + + :meth:`sage.combinat.sf.sfa.SymmetricFunctionAlgebra_generic_Element.arithmetic_product` + + EXAMPLES: + + For `C` the species of (oriented) cycles and `L_{+}` the + species of nonempty linear orders, `C \boxdot L_{+}` + corresponds to the species of "regular octopuses"; a `(C + \boxdot L_{+})`-structure is a cycle of some length, each of + whose elements is an ordered list of a length which is + consistent for all the lists in the structure. :: + + sage: R. = QQ[] + sage: p = SymmetricFunctions(R).p() + sage: m = SymmetricFunctions(R).m() + sage: L = LazySymmetricFunctions(m) + + sage: C = species.CycleSpecies().cycle_index_series() + sage: c = L(lambda n: C[n]) + sage: Lplus = L(lambda n: p([1]*n), valuation=1) + sage: r = c.arithmetic_product(Lplus); r + m[1] + (3*m[1,1]+2*m[2]) + + (8*m[1,1,1]+4*m[2,1]+2*m[3]) + + (42*m[1,1,1,1]+21*m[2,1,1]+12*m[2,2]+7*m[3,1]+3*m[4]) + + (144*m[1,1,1,1,1]+72*m[2,1,1,1]+36*m[2,2,1]+24*m[3,1,1]+12*m[3,2]+6*m[4,1]+2*m[5]) + + ... + + O^7 + + In particular, the number of regular octopuses is:: + + sage: [r[n].coefficient([1]*n) for n in range(8)] + [0, 1, 3, 8, 42, 144, 1440, 5760] + + It is shown in [MM2008]_ that the exponential generating + function for regular octopuses satisfies `(C \boxdot L_{+}) + (x) = \sum_{n \geq 1} \sigma (n) (n - 1)! \frac{x^{n}}{n!}` + (where `\sigma (n)` is the sum of the divisors of `n`). :: + + sage: [sum(divisors(i))*factorial(i-1) for i in range(1,8)] + [1, 3, 8, 42, 144, 1440, 5760] + + AUTHORS: + + - Andrew Gainer-Dewar (2013) + + REFERENCES: + + - [MM2008]_ + + TESTS: + + Check that the product with zero works:: + + sage: s = SymmetricFunctions(QQ).s() + sage: L = LazySymmetricFunctions(s) + sage: L(0).arithmetic_product(s[2]) + 0 + sage: L(s[2]).arithmetic_product(0) + 0 + + Check that the arithmetic product of symmetric functions of + finite support works:: + + sage: L(s([2])).arithmetic_product(s([1,1,1])) + s[2, 2, 1, 1] + s[3, 1, 1, 1] + s[3, 2, 1] + s[3, 3] + 2*s[4, 1, 1] + + sage: f = 1/(1-L(s[1])) + sage: f.arithmetic_product(s[1]) - f + O^7 + + Check the arithmetic product of symmetric functions over a + finite field works:: + + sage: s = SymmetricFunctions(FiniteField(2)).s() + sage: L = LazySymmetricFunctions(s) + sage: L(s([2])).arithmetic_product(s([1,1,1])) + s[2, 2, 1, 1] + s[3, 1, 1, 1] + s[3, 2, 1] + s[3, 3] + + """ + if len(args) != self.parent()._arity: + raise ValueError("arity must be equal to the number of arguments provided") + from sage.combinat.sf.sfa import is_SymmetricFunction + if not all(isinstance(g, LazySymmetricFunction) + or is_SymmetricFunction(g) + or not g for g in args): + raise ValueError("all arguments must be (possibly lazy) symmetric functions") + + if len(args) == 1: + g = args[0] + P = g.parent() + + # f = 0 or g = (0, ..., 0) + if (isinstance(self._coeff_stream, Stream_zero) + or (not isinstance(g, LazyModuleElement) and not g) + or (isinstance(g, LazyModuleElement) + and isinstance(g._coeff_stream, Stream_zero))): + return P.zero() + + if (isinstance(self._coeff_stream, Stream_exact) + and not self._coeff_stream._constant): + + if not isinstance(g, LazySymmetricFunction): + f = self.symmetric_function() + return f.arithmetic_product(g) + + if (isinstance(g._coeff_stream, Stream_exact) + and not g._coeff_stream._constant): + f = self.symmetric_function() + gs = g.symmetric_function() + return P(f.arithmetic_product(gs)) + + if isinstance(g, LazySymmetricFunction): + R = P._laurent_poly_ring + else: + from sage.rings.lazy_series_ring import LazySymmetricFunctions + R = g.parent() + P = LazySymmetricFunctions(R) + g = P(g) + + # compute the constant term in the case where not both f + # and g have finite support + # TODO: this should be done lazily if possible + c = R.zero() + if self[0]: + if (isinstance(g._coeff_stream, Stream_exact) + and not g._coeff_stream._constant): + gs = g.symmetric_function() + c += self[0].arithmetic_product(gs) + elif check: + raise ValueError("can only take the arithmetic product with a positive valuation series") + if g[0]: + if (isinstance(self._coeff_stream, Stream_exact) + and not self._coeff_stream._constant): + fs = self.symmetric_function() + c += fs.arithmetic_product(g[0]) + elif check: + raise ValueError("can only take the arithmetic product with a positive valuation series") + + p = R.realization_of().p() + # TODO: does the following introduce a memory leak? + g = Stream_map_coefficients(g._coeff_stream, p) + f = Stream_map_coefficients(self._coeff_stream, p) + + def coefficient(n): + if not n: + return c + index_set = ((d, n // d) for d in divisors(n)) + return sum(f[i].arithmetic_product(g[j]) + for i, j in index_set if f[i] and g[j]) + + coeff_stream = Stream_function(coefficient, P._sparse, 0) + return P.element_class(P, coeff_stream) + else: + raise NotImplementedError("only implemented for arity 1") def symmetric_function(self, degree=None): r""" @@ -3841,9 +5124,10 @@ def symmetric_function(self, degree=None): OUTPUT: - If ``degree`` is not ``None``, the terms of the series of degree greater - than ``degree`` are truncated first. If ``degree`` is ``None`` and the - series is not a polynomial polynomial, a ``ValueError`` is raised. + If ``degree`` is not ``None``, the terms of the series of + degree greater than ``degree`` are first truncated. If + ``degree`` is ``None`` and the series is not a polynomial + polynomial, a ``ValueError`` is raised. EXAMPLES:: @@ -3878,6 +5162,7 @@ def symmetric_function(self, degree=None): 0 sage: f4.symmetric_function(0) s[] + """ S = self.parent() R = S._laurent_poly_ring @@ -3887,7 +5172,7 @@ def symmetric_function(self, degree=None): if degree is None: if (isinstance(self._coeff_stream, Stream_exact) - and not self._coeff_stream._constant): + and not self._coeff_stream._constant): m = self._coeff_stream._degree else: raise ValueError("not a symmetric function") @@ -3896,6 +5181,7 @@ def symmetric_function(self, degree=None): return R.sum(self[:m]) + class LazyDirichletSeries(LazyModuleElement): r""" A Dirichlet series where the coefficients are computed lazily. @@ -4002,12 +5288,12 @@ def _mul_(self, other): and not left._constant and left._initial_coefficients == (P._internal_poly_ring.base_ring().one(),) and left.order() == 1): - return other # self == 1 + return other # self == 1 if (isinstance(right, Stream_exact) and not right._constant and right._initial_coefficients == (P._internal_poly_ring.base_ring().one(),) and right.order() == 1): - return self # other == 1 + return self # other == 1 coeff = Stream_dirichlet_convolve(left, right) # Performing exact arithmetic is slow because the series grow large # very quickly as we are multiplying the degree @@ -4139,7 +5425,7 @@ def coefficient(m): except ValueError: return ZZ.zero() R = P._internal_poly_ring.base_ring() - return P.element_class(P, Stream_function(coefficient, R, P._sparse, 1)) + return P.element_class(P, Stream_function(coefficient, P._sparse, 1)) def _format_series(self, formatter, format_strings=False): """ diff --git a/src/sage/rings/lazy_series_ring.py b/src/sage/rings/lazy_series_ring.py index 94a3c4ac465..c29a41fcd1c 100644 --- a/src/sage/rings/lazy_series_ring.py +++ b/src/sage/rings/lazy_series_ring.py @@ -9,8 +9,8 @@ :delim: | :class:`LazyLaurentSeriesRing` | The ring of lazy Laurent series. - :class:`LazyTaylorSeriesRing` | The ring of (possibly multivariate) lazy Taylor series. - :class:`LazyCompletionGradedAlgebra` | The completion of a graded alebra consisting of formal series. + :class:`LazyPowerSeriesRing` | The ring of (possibly multivariate) lazy Taylor series. + :class:`LazyCompletionGradedAlgebra` | The completion of a graded algebra consisting of formal series. :class:`LazySymmetricFunctions` | The ring of (possibly multivariate) lazy symmetric functions. :class:`LazyDirichletSeriesRing` | The ring of lazy Dirichlet series. @@ -51,7 +51,7 @@ from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.rings.lazy_series import (LazyModuleElement, LazyLaurentSeries, - LazyTaylorSeries, + LazyPowerSeries, LazyCompletionGradedAlgebraElement, LazySymmetricFunction, LazyDirichletSeries) @@ -61,10 +61,13 @@ from sage.data_structures.stream import ( Stream_zero, Stream_function, + Stream_iterator, Stream_exact, Stream_uninitialized ) +from types import GeneratorType + class LazySeriesRing(UniqueRepresentation, Parent): """ Abstract base class for lazy series. @@ -328,6 +331,11 @@ def _element_constructor_(self, x=None, valuation=None, degree=None, constant=No sage: f == g True + We support passing a generator:: + + sage: L(filter(is_odd, NN), -3) + z^-3 + 3*z^-2 + 5*z^-1 + 7 + 9*z + 11*z^2 + 13*z^3 + O(z^4) + .. TODO:: Add a method to change the sparse/dense implementation. @@ -341,6 +349,8 @@ def _element_constructor_(self, x=None, valuation=None, degree=None, constant=No raise ValueError("the valuation must be specified") return self.element_class(self, Stream_uninitialized(self._sparse, valuation)) + # WARNING: if x is not explicitly specified as None, it is + # set to 0 by Parent.__call__ if coefficients is not None and (x is not None and (not isinstance(x, int) or x)): raise ValueError("coefficients must be None if x is provided") @@ -375,7 +385,7 @@ def _element_constructor_(self, x=None, valuation=None, degree=None, constant=No return self.element_class(self, coeff_stream) initial_coefficients = [x[i] for i in range(x.valuation(), x.degree() + 1)] coeff_stream = Stream_exact(initial_coefficients, self._sparse, - order=x.valuation(), constant=constant, degree=degree) + order=x.valuation(), degree=degree, constant=constant) return self.element_class(self, coeff_stream) # Handle when it is a lazy series @@ -412,7 +422,7 @@ def _element_constructor_(self, x=None, valuation=None, degree=None, constant=No x._coeff_stream._approximate_order += len(initial_coefficients) initial_coefficients = [] coeff_stream = Stream_exact(initial_coefficients, self._sparse, - order=valuation, constant=constant, degree=degree) + order=valuation, degree=degree, constant=constant) return self.element_class(self, coeff_stream) # We are just possibly shifting the result @@ -424,19 +434,25 @@ def _element_constructor_(self, x=None, valuation=None, degree=None, constant=No else: x = coefficients - if callable(x): + if callable(x) or isinstance(x, (GeneratorType, map, filter)): if valuation is None: raise ValueError("the valuation must be specified") if degree is None: if constant is not None: raise ValueError("constant may only be specified if the degree is specified") - coeff_stream = Stream_function(x, self.base_ring(), self._sparse, valuation) + if callable(x): + coeff_stream = Stream_function(lambda i: BR(x(i)), self._sparse, valuation) + else: + coeff_stream = Stream_iterator(map(BR, _skip_leading_zeros(x)), valuation) return self.element_class(self, coeff_stream) # degree is not None if constant is None: constant = BR.zero() - p = [BR(x(i)) for i in range(valuation, degree)] + if callable(x): + p = [BR(x(i)) for i in range(valuation, degree)] + else: + p = [BR(c) for c, _ in zip(_skip_leading_zeros(x), range(valuation, degree))] if not any(p) and not constant: return self.zero() coeff_stream = Stream_exact(p, self._sparse, order=valuation, @@ -459,13 +475,14 @@ def undefined(self, valuation=None): EXAMPLES:: - sage: L. = LazyTaylorSeriesRing(QQ) + sage: L. = LazyPowerSeriesRing(QQ) sage: s = L.undefined(1) sage: s.define(z + (s^2+s(z^2))/2) sage: s z + z^2 + z^3 + 2*z^4 + 3*z^5 + 6*z^6 + 11*z^7 + O(z^8) """ - return self(None, valuation=valuation) + coeff_stream = Stream_uninitialized(self._sparse, valuation) + return self.element_class(self, coeff_stream) unknown = undefined @@ -532,7 +549,7 @@ def one(self): sage: L.one() 1 - sage: L = LazyTaylorSeriesRing(ZZ, 'z') + sage: L = LazyPowerSeriesRing(ZZ, 'z') sage: L.one() 1 @@ -566,7 +583,7 @@ def zero(self): sage: L.zero() 0 - sage: L = LazyTaylorSeriesRing(ZZ, 'z') + sage: L = LazyPowerSeriesRing(ZZ, 'z') sage: L.zero() 0 """ @@ -588,7 +605,7 @@ def characteristic(self): sage: R.characteristic() 11 - sage: R. = LazyTaylorSeriesRing(GF(7)); R + sage: R. = LazyPowerSeriesRing(GF(7)); R Multivariate Lazy Taylor Series Ring in x, y over Finite Field of size 7 sage: R.characteristic() 7 @@ -611,7 +628,7 @@ def _coerce_map_from_(self, S): sage: L.has_coerce_map_from(GF(2)) True - sage: L = LazyTaylorSeriesRing(GF(2), 'z') + sage: L = LazyPowerSeriesRing(GF(2), 'z') sage: L.has_coerce_map_from(ZZ) True sage: L.has_coerce_map_from(GF(2)) @@ -690,6 +707,7 @@ def is_exact(self): """ return self.base_ring().is_exact() + class LazyLaurentSeriesRing(LazySeriesRing): """ The ring of lazy Laurent series. @@ -806,11 +824,11 @@ class LazyLaurentSeriesRing(LazySeriesRing): sage: s 1 + z + 2*z^2 + 5*z^3 + 14*z^4 + 42*z^5 + 132*z^6 + O(z^7) - If we do not explcitly know the exact value of every coefficient, - then equality checking will depend on the computed coefficients. - If at a certain point we cannot prove two series are different - (which involves the coefficients we have computed), then we will - raise an error:: + If we do not explicitly know the exact value of every + coefficient, then equality checking will depend on the computed + coefficients. If at a certain point we cannot prove two series + are different (which involves the coefficients we have computed), + then we will raise an error:: sage: f = 1 / (z + z^2); f z^-1 - 1 + z - z^2 + z^3 - z^4 + z^5 + O(z^6) @@ -868,6 +886,8 @@ def __init__(self, base_ring, names, sparse=True, category=None): sage: L = LazyLaurentSeriesRing(E, 't') # not tested """ self._sparse = sparse + self._arity = 1 + self._minimal_valuation = None # We always use the dense because our CS_exact is implemented densely self._laurent_poly_ring = LaurentPolynomialRing(base_ring, names) self._internal_poly_ring = self._laurent_poly_ring @@ -1116,7 +1136,8 @@ def _monomial(self, c, n): ###################################################################### -class LazyTaylorSeriesRing(LazySeriesRing): + +class LazyPowerSeriesRing(LazySeriesRing): """ The ring of (possibly multivariate) lazy Taylor series. @@ -1128,13 +1149,13 @@ class LazyTaylorSeriesRing(LazySeriesRing): EXAMPLES:: - sage: LazyTaylorSeriesRing(ZZ, 't') + sage: LazyPowerSeriesRing(ZZ, 't') Lazy Taylor Series Ring in t over Integer Ring - sage: L. = LazyTaylorSeriesRing(QQ); L + sage: L. = LazyPowerSeriesRing(QQ); L Multivariate Lazy Taylor Series Ring in x, y over Rational Field """ - Element = LazyTaylorSeries + Element = LazyPowerSeries def __init__(self, base_ring, names, sparse=True, category=None): """ @@ -1142,18 +1163,19 @@ def __init__(self, base_ring, names, sparse=True, category=None): TESTS:: - sage: L = LazyTaylorSeriesRing(ZZ, 't') + sage: L = LazyPowerSeriesRing(ZZ, 't') sage: TestSuite(L).run(skip=['_test_elements', '_test_associativity', '_test_distributivity', '_test_zero']) """ from sage.structure.category_object import normalize_names names = normalize_names(-1, names) self._sparse = sparse + self._minimal_valuation = 0 self._laurent_poly_ring = PolynomialRing(base_ring, names) - if len(names) == 1: + self._arity = len(names) + if self._arity == 1: self._internal_poly_ring = self._laurent_poly_ring else: - coeff_ring = PolynomialRing(base_ring, names) - self._internal_poly_ring = PolynomialRing(coeff_ring, "DUMMY_VARIABLE") + self._internal_poly_ring = PolynomialRing(self._laurent_poly_ring, "DUMMY_VARIABLE") category = Algebras(base_ring.category()) if base_ring in Fields(): category &= CompleteDiscreteValuationRings() @@ -1175,7 +1197,7 @@ def _repr_(self): EXAMPLES:: - sage: LazyTaylorSeriesRing(GF(2), 'z') + sage: LazyPowerSeriesRing(GF(2), 'z') Lazy Taylor Series Ring in z over Finite Field of size 2 """ BR = self.base_ring() @@ -1190,7 +1212,7 @@ def _latex_(self): EXAMPLES:: - sage: L = LazyTaylorSeriesRing(GF(2), 'z') + sage: L = LazyPowerSeriesRing(GF(2), 'z') sage: latex(L) \Bold{F}_{2} [\![z]\!] """ @@ -1204,7 +1226,7 @@ def _monomial(self, c, n): EXAMPLES:: - sage: L = LazyTaylorSeriesRing(ZZ, 'z') + sage: L = LazyPowerSeriesRing(ZZ, 'z') sage: L._monomial(2, 3) 2*z^3 """ @@ -1221,7 +1243,7 @@ def gen(self, n=0): EXAMPLES:: - sage: L = LazyTaylorSeriesRing(ZZ, 'z') + sage: L = LazyPowerSeriesRing(ZZ, 'z') sage: L.gen() z sage: L.gen(3) @@ -1249,7 +1271,7 @@ def ngens(self): EXAMPLES:: - sage: L. = LazyTaylorSeriesRing(ZZ) + sage: L. = LazyPowerSeriesRing(ZZ) sage: L.ngens() 1 """ @@ -1262,13 +1284,13 @@ def gens(self): EXAMPLES:: - sage: L = LazyTaylorSeriesRing(ZZ, 'x,y') + sage: L = LazyPowerSeriesRing(ZZ, 'x,y') sage: L.gens() (x, y) """ return tuple([self.gen(n) for n in range(self.ngens())]) - def _element_constructor_(self, x=None, valuation=None, constant=None, degree=None, check=True): + def _element_constructor_(self, x=None, valuation=None, constant=None, degree=None, coefficients=None, check=True): """ Construct a Taylor series from ``x``. @@ -1282,13 +1304,13 @@ def _element_constructor_(self, x=None, valuation=None, constant=None, degree=No EXAMPLES:: - sage: L = LazyTaylorSeriesRing(GF(2), 'z') + sage: L = LazyPowerSeriesRing(GF(2), 'z') sage: L(2) 0 sage: L(3) 1 - sage: L = LazyTaylorSeriesRing(ZZ, 'z') + sage: L = LazyPowerSeriesRing(ZZ, 'z') sage: L(lambda i: i, 5, 1, 10) 5*z^5 + 6*z^6 + 7*z^7 + 8*z^8 + 9*z^9 + z^10 + z^11 + z^12 + O(z^13) sage: L(lambda i: i, 5, (1, 10)) @@ -1333,7 +1355,7 @@ def _element_constructor_(self, x=None, valuation=None, constant=None, degree=No sage: P. = QQ[] sage: p = x + 3*x^2 + x^5 - sage: L. = LazyTaylorSeriesRing(ZZ) + sage: L. = LazyPowerSeriesRing(ZZ) sage: L(p) x + 3*x^2 + x^5 @@ -1342,13 +1364,13 @@ def _element_constructor_(self, x=None, valuation=None, constant=None, degree=No sage: P. = QQ[] sage: p = x + y^2 + x*y - sage: L. = LazyTaylorSeriesRing(ZZ) + sage: L. = LazyPowerSeriesRing(ZZ) sage: L(p) x + (x*y+y^2) TESTS:: - sage: L. = LazyTaylorSeriesRing(ZZ) + sage: L. = LazyPowerSeriesRing(ZZ) sage: L(constant=1) Traceback (most recent call last): ... @@ -1375,10 +1397,11 @@ def _element_constructor_(self, x=None, valuation=None, constant=None, degree=No """ if valuation is not None: if valuation < 0: - raise ValueError("the valuation of a Taylor series must be positive") - if len(self.variable_names()) > 1: + raise ValueError("the valuation of a Taylor series must be non-negative") + # TODO: the following is nonsense, think of an iterator + if self._arity > 1: raise ValueError("valuation must not be specified for multivariate Taylor series") - if len(self.variable_names()) > 1: + if self._arity > 1: valuation = 0 R = self._laurent_poly_ring @@ -1395,7 +1418,7 @@ def _element_constructor_(self, x=None, valuation=None, constant=None, degree=No if isinstance(constant, (tuple, list)): constant, degree = constant if constant is not None: - if len(self.variable_names()) > 1 and constant: + if self._arity > 1 and constant: raise ValueError("constant must be zero for multivariate Taylor series") constant = BR(constant) if x in R: @@ -1409,7 +1432,7 @@ def _element_constructor_(self, x=None, valuation=None, constant=None, degree=No constant=constant) return self.element_class(self, coeff_stream) - if len(self.variable_names()) == 1: + if self._arity == 1: v = x.valuation() d = x.degree() p_list = [x[i] for i in range(v, d + 1)] @@ -1427,21 +1450,25 @@ def _element_constructor_(self, x=None, valuation=None, constant=None, degree=No degree=degree) return self.element_class(self, coeff_stream) - if isinstance(x, LazyTaylorSeries): + if isinstance(x, LazyPowerSeries): if x._coeff_stream._is_sparse is self._sparse: return self.element_class(self, x._coeff_stream) # TODO: Implement a way to make a self._sparse copy raise NotImplementedError("cannot convert between sparse and dense") - if callable(x): + if callable(x) or isinstance(x, (GeneratorType, map, filter)): if valuation is None: valuation = 0 if degree is not None: if constant is None: constant = ZZ.zero() - if len(self.variable_names()) == 1: - p = [BR(x(i)) for i in range(valuation, degree)] + if callable(x): + p = [x(i) for i in range(valuation, degree)] else: - p = [R(x(i)) for i in range(valuation, degree)] + p = [c for c, _ in zip(_skip_leading_zeros(x), range(valuation, degree))] + if self._arity == 1: + p = [BR(c) for c in p] + else: + p = [R(c) for c in p] if not all(e.is_homogeneous() and e.degree() == i for i, e in enumerate(p, valuation)): raise ValueError("coefficients must be homogeneous polynomials of the correct degree") @@ -1450,16 +1477,21 @@ def _element_constructor_(self, x=None, valuation=None, constant=None, degree=No constant=constant, degree=degree) return self.element_class(self, coeff_stream) - coeff_ring = self._internal_poly_ring.base_ring() - if check and len(self.variable_names()) > 1: - def y(n): - e = R(x(n)) - if not e or e.is_homogeneous() and e.degree() == n: - return e - raise ValueError("coefficient %s at degree %s is not a homogeneous polynomial" % (e, n)) - coeff_stream = Stream_function(y, coeff_ring, self._sparse, valuation) + if check and self._arity > 1: + if callable(x): + def y(n): + e = R(x(n)) + if not e or e.is_homogeneous() and e.degree() == n: + return e + raise ValueError("coefficient %s at degree %s is not a homogeneous polynomial" % (e, n)) + coeff_stream = Stream_function(y, self._sparse, valuation) + else: + coeff_stream = Stream_iterator(map(R, _skip_leading_zeros(x)), valuation) else: - coeff_stream = Stream_function(x, coeff_ring, self._sparse, valuation) + if callable(x): + coeff_stream = Stream_function(x, self._sparse, valuation) + else: + coeff_stream = Stream_iterator(map(BR, _skip_leading_zeros(x)), valuation) return self.element_class(self, coeff_stream) raise ValueError(f"unable to convert {x} into a lazy Taylor series") @@ -1469,7 +1501,7 @@ def _an_element_(self): EXAMPLES:: - sage: L = LazyTaylorSeriesRing(ZZ, 'z') + sage: L = LazyPowerSeriesRing(ZZ, 'z') sage: L.an_element() z + z^2 + z^3 + z^4 + O(z^5) """ @@ -1478,12 +1510,12 @@ def _an_element_(self): coeff_stream = Stream_exact([R.one()], self._sparse, order=1, constant=c) return self.element_class(self, coeff_stream) - ###################################################################### + class LazyCompletionGradedAlgebra(LazySeriesRing): r""" - The completion of a graded alebra consisting of formal series. + The completion of a graded algebra consisting of formal series. For a graded algebra `A`, we can form a completion of `A` consisting of all formal series of `A` such that each homogeneous component is @@ -1536,6 +1568,7 @@ def __init__(self, basis, sparse=True, category=None): sage: TestSuite(L).run(skip=['_test_elements', '_test_associativity', '_test_distributivity', '_test_zero']) """ base_ring = basis.base_ring() + self._minimal_valuation = 0 if basis in Algebras.TensorProducts: self._arity = len(basis._sets) else: @@ -1712,7 +1745,7 @@ def _element_constructor_(self, x=None, valuation=None, degree=None, check=True) d = f.degree() except (TypeError, ValueError, AttributeError): # FIXME: Fallback for symmetric functions in multiple variables - d = sum(p.size() for p in f.support()) + d = sum(sum(mu.size() for mu in p) for p in f.support()) p_dict[d] = p_dict.get(d, 0) + f v = min(p_dict) d = max(p_dict) @@ -1780,16 +1813,15 @@ def check_homogeneous_of_degree(f, d): constant=0, degree=degree) return self.element_class(self, coeff_stream) - coeff_ring = self._internal_poly_ring.base_ring() if check: def y(n): e = R(x(n)) check_homogeneous_of_degree(e, n) return e - coeff_stream = Stream_function(y, coeff_ring, self._sparse, valuation) + coeff_stream = Stream_function(y, self._sparse, valuation) else: - coeff_stream = Stream_function(x, coeff_ring, self._sparse, valuation) + coeff_stream = Stream_function(x, self._sparse, valuation) return self.element_class(self, coeff_stream) raise ValueError(f"unable to convert {x} into a lazy completion element") @@ -1866,8 +1898,9 @@ def __init__(self, base_ring, names, sparse=True, category=None): raise ValueError("positive characteristic not allowed for Dirichlet series") self._sparse = sparse - # TODO: it would be good to have something better than the symbolic ring - self._laurent_poly_ring = SR + self._minimal_valuation = 1 + self._arity = 1 + self._laurent_poly_ring = SR # TODO: it would be good to have something better than the symbolic ring self._internal_poly_ring = PolynomialRing(base_ring, names, sparse=True) category = Algebras(base_ring.category()) @@ -2066,3 +2099,27 @@ def _monomial(self, c, n): except (ValueError, TypeError): return '({})/{}^{}'.format(self.base_ring()(c), n, self.variable_name()) +def _skip_leading_zeros(iterator): + """ + Return an iterator which discards all leading zeros. + + EXAMPLES:: + + sage: from sage.rings.lazy_series_ring import _skip_leading_zeros + sage: it = map(lambda x: 0 if x < 10 else x, NN) + sage: [x for x, _ in zip(_skip_leading_zeros(it), range(10))] + [10, 11, 12, 13, 14, 15, 16, 17, 18, 19] + + sage: it = map(GF(3), NN) + sage: [x for x, _ in zip(it, range(10))] + [0, 1, 2, 0, 1, 2, 0, 1, 2, 0] + sage: it = map(GF(3), NN) + sage: [x for x, _ in zip(_skip_leading_zeros(it), range(10))] + [1, 2, 0, 1, 2, 0, 1, 2, 0, 1] + """ + while True: + c = next(iterator) + if c: + yield c + break + yield from iterator diff --git a/src/sage/tests/books/computational-mathematics-with-sagemath/combinat_doctest.py b/src/sage/tests/books/computational-mathematics-with-sagemath/combinat_doctest.py index 9dc4f6e430a..e964bcd4b70 100644 --- a/src/sage/tests/books/computational-mathematics-with-sagemath/combinat_doctest.py +++ b/src/sage/tests/books/computational-mathematics-with-sagemath/combinat_doctest.py @@ -121,8 +121,7 @@ Sage example in ./combinat.tex, line 661:: - sage: C = L() - sage: C._name = 'C' + sage: C = L.undefined(valuation=1) sage: C.define( z + C * C ) Sage example in ./combinat.tex, line 666:: @@ -889,7 +888,7 @@ Sage example in ./combinat.tex, line 2697:: - sage: BT = CombinatorialSpecies() + sage: BT = CombinatorialSpecies(min=1) sage: Leaf = SingletonSpecies() sage: BT.define( Leaf + (BT*BT) ) @@ -906,7 +905,7 @@ Sage example in ./combinat.tex, line 2727:: sage: g = BT.isotype_generating_series(); g - x + x^2 + 2*x^3 + 5*x^4 + 14*x^5 + O(x^6) + z + z^2 + 2*z^3 + 5*z^4 + 14*z^5 + 42*z^6 + 132*z^7 + O(z^8) Sage example in ./combinat.tex, line 2733:: @@ -922,7 +921,7 @@ Sage example in ./combinat.tex, line 2752:: - sage: L = FW.isotype_generating_series().coefficients(15); L + sage: L = FW.isotype_generating_series()[:15]; L [1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987] Sage example in ./combinat.tex, line 2769:: diff --git a/src/sage/tests/books/computational-mathematics-with-sagemath/polynomes_doctest.py b/src/sage/tests/books/computational-mathematics-with-sagemath/polynomes_doctest.py index 452a116401d..2115f8edb3c 100644 --- a/src/sage/tests/books/computational-mathematics-with-sagemath/polynomes_doctest.py +++ b/src/sage/tests/books/computational-mathematics-with-sagemath/polynomes_doctest.py @@ -363,28 +363,27 @@ Sage example in ./polynomes.tex, line 2028:: sage: L. = LazyPowerSeriesRing(QQ) - sage: lazy_exp = x.exponential(); lazy_exp - O(1) + sage: lazy_exp = x.exp(); lazy_exp + 1 + x + 1/2*x^2 + 1/6*x^3 + 1/24*x^4 + 1/120*x^5 + 1/720*x^6 + O(x^7) Sage example in ./polynomes.tex, line 2039:: sage: lazy_exp[5] 1/120 sage: lazy_exp - 1 + x + 1/2*x^2 + 1/6*x^3 + 1/24*x^4 + 1/120*x^5 + O(x^6) + 1 + x + 1/2*x^2 + 1/6*x^3 + 1/24*x^4 + 1/120*x^5 + 1/720*x^6 + O(x^7) Sage example in ./polynomes.tex, line 2062:: sage: f = L(1) # the constant lazy series 1 sage: for i in range(5): - ....: f = (x*f).exponential() - ....: f.compute_coefficients(5) # forces the computation + ....: f = (x*f).exp() ....: print(f) # of the first coefficients - 1 + x + 1/2*x^2 + 1/6*x^3 + 1/24*x^4 + 1/120*x^5 + O(x^6) - 1 + x + 3/2*x^2 + 5/3*x^3 + 41/24*x^4 + 49/30*x^5 + O(x^6) - 1 + x + 3/2*x^2 + 8/3*x^3 + 101/24*x^4 + 63/10*x^5 + O(x^6) - 1 + x + 3/2*x^2 + 8/3*x^3 + 125/24*x^4 + 49/5*x^5 + O(x^6) - 1 + x + 3/2*x^2 + 8/3*x^3 + 125/24*x^4 + 54/5*x^5 + O(x^6) + 1 + x + 1/2*x^2 + 1/6*x^3 + 1/24*x^4 + 1/120*x^5 + 1/720*x^6 + O(x^7) + 1 + x + 3/2*x^2 + 5/3*x^3 + 41/24*x^4 + 49/30*x^5 + 1057/720*x^6 + O(x^7) + 1 + x + 3/2*x^2 + 8/3*x^3 + 101/24*x^4 + 63/10*x^5 + 6607/720*x^6 + O(x^7) + 1 + x + 3/2*x^2 + 8/3*x^3 + 125/24*x^4 + 49/5*x^5 + 12847/720*x^6 + O(x^7) + 1 + x + 3/2*x^2 + 8/3*x^3 + 125/24*x^4 + 54/5*x^5 + 16087/720*x^6 + O(x^7) Sage example in ./polynomes.tex, line 2091:: @@ -393,10 +392,9 @@ Sage example in ./polynomes.tex, line 2105:: - sage: from sage.combinat.species.series import LazyPowerSeries - sage: f = LazyPowerSeries(L, name='f') - sage: f.define((x*f).exponential()) - sage: f.coefficients(8) + sage: f = L.undefined(valuation=0) + sage: f.define((x*f).exp()) + sage: f[:8] [1, 1, 3/2, 8/3, 125/24, 54/5, 16807/720, 16384/315] Sage example in ./polynomes.tex, line 2158::