diff --git a/src/sage/combinat/sf/orthotriang.py b/src/sage/combinat/sf/orthotriang.py index 17c636ce5ea..153dada4eb0 100644 --- a/src/sage/combinat/sf/orthotriang.py +++ b/src/sage/combinat/sf/orthotriang.py @@ -102,7 +102,7 @@ def __init__(self, Sym, base, scalar, prefix, basis_name, leading_coeff): TESTS:: sage: TestSuite(s).run(elements=[s[1,1]+2*s[2], s[1]+3*s[1,1]]) - sage: TestSuite(s).run(skip=["_test_associativity", "_test_prod", '_test_construction']) # long time (7s on sage.math, 2011) + sage: TestSuite(s).run(skip=["_test_associativity", "_test_prod"]) # long time (7s on sage.math, 2011) Note: ``s.an_element()`` is of degree 4; so we skip ``_test_associativity`` and ``_test_prod`` which involve diff --git a/src/sage/combinat/sf/sf.py b/src/sage/combinat/sf/sf.py index fbf1ee165f7..35568ca4a05 100644 --- a/src/sage/combinat/sf/sf.py +++ b/src/sage/combinat/sf/sf.py @@ -845,7 +845,6 @@ class function on the symmetric group where the elements - Devise a mechanism so that pickling bases of symmetric functions pickles the coercions which have a cache. """ - def __init__(self, R): r""" Initialization of ``self``. diff --git a/src/sage/combinat/sf/sfa.py b/src/sage/combinat/sf/sfa.py index 033a753f076..9292fd709e7 100644 --- a/src/sage/combinat/sf/sfa.py +++ b/src/sage/combinat/sf/sfa.py @@ -417,6 +417,17 @@ def is_integral_domain(self, proof=True): """ return self.base_ring().is_integral_domain() + def fraction_field(self): + if not self.is_integral_domain(): + raise TypeError("self must be an integral domain.") + if hasattr(self, "__fraction_field") and self.__fraction_field is not None: + return self.__fraction_field + else: + import sage.rings.fraction_field + K = sage.rings.fraction_field.FractionField_generic(self) + self.__fraction_field = K + return self.__fraction_field + def is_field(self, proof=True): """ Return whether ``self`` is a field. (It is not.) @@ -1853,6 +1864,9 @@ def __init__(self, Sym, basis_name=None, prefix=None, graded=True): _print_style = 'lex' + def is_prime_field(self): + return False + # Todo: share this with ncsf and over algebras with basis indexed by word-like elements def __getitem__(self, c): r""" diff --git a/src/sage/combinat/species/generating_series.py b/src/sage/combinat/species/generating_series.py index 7622efd279b..914126371b4 100644 --- a/src/sage/combinat/species/generating_series.py +++ b/src/sage/combinat/species/generating_series.py @@ -141,7 +141,7 @@ def __init__(self, base_ring): sage: from sage.combinat.species.generating_series import OrdinaryGeneratingSeriesRing sage: OrdinaryGeneratingSeriesRing.options.halting_precision(15) sage: R = OrdinaryGeneratingSeriesRing(QQ) - sage: TestSuite(R).run() + sage: TestSuite(R).run(skip="_test_construction") sage: OrdinaryGeneratingSeriesRing.options._reset() # reset options """ @@ -268,7 +268,7 @@ def __init__(self, base_ring): sage: from sage.combinat.species.generating_series import ExponentialGeneratingSeriesRing sage: ExponentialGeneratingSeriesRing.options.halting_precision(15) sage: R = ExponentialGeneratingSeriesRing(QQ) - sage: TestSuite(R).run() + sage: TestSuite(R).run(skip="_test_construction") sage: ExponentialGeneratingSeriesRing.options._reset() # reset options """ diff --git a/src/sage/combinat/species/species.py b/src/sage/combinat/species/species.py index 61d70666665..472cb934e37 100644 --- a/src/sage/combinat/species/species.py +++ b/src/sage/combinat/species/species.py @@ -376,7 +376,7 @@ def structures(self, labels, structure_class=None): sage: F.structures([1,2,3]).list() Traceback (most recent call last): ... - NotImplementedError + ValueError: Stream is not yet defined """ return StructuresWrapper(self, labels, structure_class) @@ -388,7 +388,7 @@ def isotypes(self, labels, structure_class=None): sage: F.isotypes([1,2,3]).list() Traceback (most recent call last): ... - NotImplementedError + ValueError: Stream is not yet defined """ return IsotypesWrapper(self, labels, structure_class=structure_class) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index 251deff8b96..a2f91678468 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -102,8 +102,10 @@ from sage.misc.lazy_import import lazy_import from sage.combinat.integer_vector_weighted import iterator_fast as wt_int_vec_iter from sage.categories.hopf_algebras_with_basis import HopfAlgebrasWithBasis +from sage.structure.unique_representation import UniqueRepresentation +from sage.rings.polynomial.infinite_polynomial_ring import InfinitePolynomialRing +from sage.rings.polynomial.infinite_polynomial_element import InfinitePolynomial from sage.misc.cachefunc import cached_method - lazy_import('sage.combinat.sf.sfa', ['_variables_recursive', '_raise_variables']) @@ -133,7 +135,6 @@ class Stream(): However, keep in mind that (trivially) this initialization code is not executed if ``_approximate_order`` is set to a value before it is accessed. - """ def __init__(self, true_order): """ @@ -160,6 +161,39 @@ def _approximate_order(self): """ raise NotImplementedError + def order(self): + r""" + Return the order of ``self``, which is the minimum index ``n`` such + that ``self[n]`` is non-zero. + + EXAMPLES:: + + sage: from sage.data_structures.stream import Stream_function + sage: f = Stream_function(lambda n: n, True, 0) + sage: f.order() + 1 + + TESTS:: + + sage: f = Stream_function(lambda n: n*(n+1), False, -1) + sage: f.order() + 1 + sage: f._true_order + True + + sage: f = Stream_function(lambda n: n*(n+1), True, -1) + sage: f.order() + 1 + sage: f._true_order + True + """ + if self._true_order: + return self._approximate_order + n = self._approximate_order + while not self[n]: + n += 1 + return n + def __ne__(self, other): """ Return whether ``self`` and ``other`` are known to be different. @@ -213,6 +247,20 @@ def is_uninitialized(self): """ return False + def input_streams(self): + r""" + Return the list of streams which are used to compute the + coefficients of ``self``. + + EXAMPLES:: + + sage: from sage.data_structures.stream import Stream_zero + sage: z = Stream_zero() + sage: z.input_streams() + [] + """ + return [] + class Stream_inexact(Stream): """ @@ -246,7 +294,7 @@ def __init__(self, is_sparse, true_order): if self._is_sparse: self._cache = dict() # cache of known coefficients else: - self._cache = list() + self._cache = [] self._iter = self.iterate_coefficients() def is_nonzero(self): @@ -335,8 +383,8 @@ def __setstate__(self, d): """ self.__dict__ = d if not self._is_sparse: - self._iter = self.iterate_coefficients() self._cache = [] + self._iter = self.iterate_coefficients() def __getitem__(self, n): """ @@ -438,39 +486,6 @@ def iterate_coefficients(self): yield self.get_coefficient(n) n += 1 - def order(self): - r""" - Return the order of ``self``, which is the minimum index ``n`` such - that ``self[n]`` is non-zero. - - EXAMPLES:: - - sage: from sage.data_structures.stream import Stream_function - sage: f = Stream_function(lambda n: n, True, 0) - sage: f.order() - 1 - - TESTS:: - - sage: f = Stream_function(lambda n: n*(n+1), False, -1) - sage: f.order() - 1 - sage: f._true_order - True - - sage: f = Stream_function(lambda n: n*(n+1), True, -1) - sage: f.order() - 1 - sage: f._true_order - True - """ - if self._true_order: - return self._approximate_order - n = self._approximate_order - while not self[n]: - n += 1 - return n - def __ne__(self, other): """ Return whether ``self`` and ``other`` are known to be different. @@ -545,7 +560,6 @@ def __ne__(self, other): True sage: g != f True - """ # TODO: more cases, in particular mixed implementations, # could be detected @@ -808,7 +822,6 @@ def __eq__(self, other): sage: t = Stream_exact([2], order=-1, degree=5, constant=1) sage: s == t False - """ return (isinstance(other, type(self)) and self._degree == other._degree @@ -967,6 +980,14 @@ class Stream_function(Stream_inexact): We assume for equality that ``function`` is a function in the mathematical sense. + .. WARNING:: + + To make + :meth:`sage.rings.lazy_series_ring.LazySeriesRing.define_implicitly` + work any streams used in ``function`` must appear in its + ``__closure__`` as instances of :class:`Stream`, as opposed + to, for example, as instances of :class:`LazyPowerSeries`. + EXAMPLES:: sage: from sage.data_structures.stream import Stream_function @@ -999,6 +1020,34 @@ def __init__(self, function, is_sparse, approximate_order, true_order=False): super().__init__(is_sparse, true_order) self._approximate_order = approximate_order + def input_streams(self): + r""" + Return the list of streams which are used to compute the + coefficients of ``self``, as provided. + + EXAMPLES: + + Only streams that appear in the closure are detected:: + + sage: from sage.data_structures.stream import Stream_function, Stream_exact + sage: f = Stream_exact([1,3,5], constant=7) + sage: g = Stream_function(lambda n: f[n]^2, False, 0) + sage: g.input_streams() + [] + + sage: def fun(): + ....: f = Stream_exact([1,3,5], constant=7) + ....: g = Stream_function(lambda n: f[n]^2, False, 0) + ....: return g.input_streams() + sage: fun() + [] + """ + closure = self.get_coefficient.__closure__ + if closure is None: + return [] + return [cell.cell_contents for cell in closure + if isinstance(cell.cell_contents, Stream)] + def __hash__(self): """ Return the hash of ``self``. @@ -1206,7 +1255,93 @@ def iterate_coefficients(self): denom *= n -class Stream_uninitialized(Stream_inexact): +class VariablePool(UniqueRepresentation): + """ + A class to keep track of used and unused variables in an + :class:`InfinitePolynomialRing`. + + INPUT: + + - ``ring`` -- :class:`InfinitePolynomialRing` + """ + def __init__(self, ring): + """ + Initialize the pool. + + EXAMPLES:: + + sage: from sage.data_structures.stream import VariablePool + sage: R. = InfinitePolynomialRing(QQ) + sage: P = VariablePool(R) + sage: TestSuite(P).run() + """ + self._gen = ring.gen(0) # alternatively, make :class:`InfinitePolynomialGen` inherit from `UniqueRepresentation`. + self._pool = dict() # dict of variables actually used to names + + def new_variable(self, data=None): + """ + Return an unused variable. + + EXAMPLES:: + + sage: from sage.data_structures.stream import VariablePool + sage: R. = InfinitePolynomialRing(QQ) + sage: P = VariablePool(R) + sage: P.new_variable() + a_0 + + TESTS: + + Check, that we get a new pool for each + :class:`InfinitePolynomialRing`:: + + sage: R0. = InfinitePolynomialRing(QQ) + sage: P0 = VariablePool(R0) + sage: P0.new_variable() + b_0 + """ + for i in range(len(self._pool) + 1): + v = self._gen[i] + if v not in self._pool: + self._pool[v] = data + return v + + def del_variable(self, v): + """ + Remove ``v`` from the pool. + + EXAMPLES:: + + sage: from sage.data_structures.stream import VariablePool + sage: R. = InfinitePolynomialRing(QQ) + sage: P = VariablePool(R) + sage: v = P.new_variable(); v + a_1 + + sage: P.del_variable(v) + sage: v = P.new_variable(); v + a_1 + """ + del self._pool[v] + + def variables(self): + """ + Return the dictionary mapping variables to data. + + EXAMPLES:: + + sage: from sage.data_structures.stream import VariablePool + sage: R. = InfinitePolynomialRing(QQ) + sage: P = VariablePool(R) + sage: P.new_variable("my new variable") + a_2 + sage: P.variables() + {a_0: None, a_1: None, a_2: 'my new variable'} + """ + return self._pool + + +class Stream_uninitialized(Stream): r""" Coefficient stream for an uninitialized series. @@ -1214,6 +1349,9 @@ class Stream_uninitialized(Stream_inexact): - ``approximate_order`` -- integer; a lower bound for the order of the stream + - ``true_order`` -- boolean; if the approximate order is the actual order + - ``name`` -- string; a name that refers to the undefined + stream in error messages Instances of this class are always dense. @@ -1229,11 +1367,11 @@ class Stream_uninitialized(Stream_inexact): sage: one = Stream_exact([1]) sage: C = Stream_uninitialized(0) sage: C._target - sage: C._target = one + sage: C.define(one) sage: C[4] 0 """ - def __init__(self, approximate_order, true_order=False): + def __init__(self, approximate_order, true_order=False, name=None): """ Initialize ``self``. @@ -1244,11 +1382,639 @@ def __init__(self, approximate_order, true_order=False): sage: TestSuite(C).run(skip="_test_pickling") """ self._target = None + self._eqs = None if approximate_order is None: raise ValueError("the valuation must be specified for undefined series") - super().__init__(False, true_order) + super().__init__(true_order) self._approximate_order = approximate_order self._initializing = False + self._is_sparse = False + self._name = name + + def __del__(self): + """ + Remove variables from the pool on destruction. + + TESTS:: + + sage: import gc + sage: L. = LazyPowerSeriesRing(ZZ) + sage: A = L.undefined(name="A") + sage: B = L.undefined(name="B") + sage: eq0 = t*x*y*B(0, 0, t) + (t - x*y)*A(x, y, t) + x*y - t*A(0, y, t) + sage: eq1 = (t*x-t)*B(0, y, t) + (t - x*y)*B(x, y, t) + sage: L.define_implicitly([A, B], [eq0, eq1], max_lookahead=2) + sage: A[1] + 0 + sage: pool = A._coeff_stream._pool + sage: len(pool.variables()) + 17 + sage: del A + sage: del B + sage: del eq0 + sage: del eq1 + sage: gc.collect() + ... + sage: len(pool.variables()) + 0 + """ + if hasattr(self, '_pool'): + # self._good_cache[0] is a lower bound + if self._coefficient_ring == self._base_ring: + for c in self._cache[self._good_cache[0]:]: + if c.parent() is self._PF: + self._pool.del_variable(c.numerator()) + else: + for c in self._cache[self._good_cache[0]:]: + for c0 in c.coefficients(): + if c0.parent() is self._PF: + self._pool.del_variable(c0.numerator()) + + def input_streams(self): + r""" + Return the list of streams which are used to compute the + coefficients of ``self``. + + EXAMPLES:: + + sage: from sage.data_structures.stream import Stream_uninitialized, Stream_function + sage: h = Stream_function(lambda n: n, False, 1) + sage: M = Stream_uninitialized(0) + sage: M.input_streams() + [] + sage: M._target = h + sage: [h[i] for i in range(5)] + [0, 1, 2, 3, 4] + sage: M.input_streams() + [] + """ + if self._target is not None: + return [self._target] + if self._eqs is not None: + return self._eqs + return [] + + def define(self, target): + r""" + Define ``self`` via ``self = target``. + + INPUT: + + - ``target`` -- a stream + + EXAMPLES:: + + sage: from sage.data_structures.stream import Stream_uninitialized, Stream_exact, Stream_cauchy_mul, Stream_add + sage: x = Stream_exact([1], order=1) + sage: C = Stream_uninitialized(1) + sage: C.define(Stream_add(x, Stream_cauchy_mul(C, C, True), True)) + sage: C[6] + 42 + """ + self._target = target + self._n = self._approximate_order - 1 # the largest index of a coefficient we know + # we only need this if target does not have a dense cache + self._cache = [] + self._iter = self.iterate_coefficients() + + def define_implicitly(self, series, initial_values, equations, + base_ring, coefficient_ring, terms_of_degree, + max_lookahead=1): + r""" + Define ``self`` via ``equations == 0``. + + INPUT: + + - ``series`` -- a list of series + - ``equations`` -- a list of equations defining the series + - ``initial_values`` -- a list specifying ``self[0], self[1], ...`` + - ``base_ring`` -- the base ring + - ``coefficient_ring`` -- the ring containing the elements of + the stream (after substitution) + - ``terms_of_degree`` -- a function returning the list of + terms of a given degree + - ``max_lookahead`` -- a positive integer specifying how many + elements beyond the approximate order of each equation to + extract linear equations from + + EXAMPLES:: + + sage: from sage.data_structures.stream import Stream_uninitialized, Stream_exact, Stream_cauchy_mul, Stream_add, Stream_sub + sage: terms_of_degree = lambda n, R: [R.one()] + sage: x = Stream_exact([1], order=1) + sage: C = Stream_uninitialized(1) + sage: D = Stream_add(x, Stream_cauchy_mul(C, C, True), True) + sage: eq = Stream_sub(C, D, True) + sage: C.define_implicitly([C], [], [eq], QQ, QQ, terms_of_degree) + sage: C[6] + 42 + """ + assert self._target is None + + for i, val in enumerate(initial_values): + if val: + self._approximate_order += i + self._true_order = True + self._cache = initial_values[i:] + break + else: + self._approximate_order += len(initial_values) + self._cache = [] + + self._coefficient_ring = coefficient_ring + self._base_ring = base_ring + self._P = InfinitePolynomialRing(self._base_ring, names=["FESDUMMY"]) + self._PF = self._P.fraction_field() + if self._coefficient_ring != self._base_ring: + self._U = self._coefficient_ring.change_ring(self._PF) + self._pool = VariablePool(self._P) + self._uncomputed = True + self._eqs = equations + self._series = series + self._terms_of_degree = terms_of_degree + # currently name is used only for error messages + if self._name is None: + # if used for something global (e.g. an expression tree) + # need to make this unique + self._name = "series" + if len(self._series) > 1: + self._name += str(self._series.index(self)) + self._max_lookahead = max_lookahead + + @lazy_attribute + def _input_streams(self): + r""" + Return the list of streams which have a cache and an implicitly + defined ``self`` depends on. + + ``self`` is the first stream in this list. + + .. WARNING:: + + All caches must have been created before this is called. + Currently, the first invocation is via + :meth:`_good_cache` in :meth:`__getitem__`. + + EXAMPLES:: + + sage: from sage.data_structures.stream import Stream_uninitialized, Stream_exact, Stream_cauchy_mul, Stream_add, Stream_sub + sage: terms_of_degree = lambda n, R: [R.one()] + sage: x = Stream_exact([1], order=1) + sage: C = Stream_uninitialized(1) + sage: D = Stream_add(x, Stream_cauchy_mul(C, C, True), True) + sage: eq = Stream_sub(C, D, True) + sage: C.define_implicitly([C], [], [eq], QQ, QQ, terms_of_degree) + sage: C._input_streams + [, + , + , + ] + """ + known = [self] + todo = [self] + while todo: + x = todo.pop() + for y in x.input_streams(): + if hasattr(y, "_cache") and not any(y is z for z in known): + todo.append(y) + known.append(y) + return known + + @lazy_attribute + def _good_cache(self): + r""" + The number of coefficients in each :meth:`_input_streams` - in + the same order - that are free of undetermined coefficients. + + This is used in :meth:`_subs_in_caches` to only substitute + items that may contain undetermined coefficients. + + .. TODO:: + + It might be an better to share this across all uninitialized + series in one system. + + EXAMPLES:: + + sage: from sage.data_structures.stream import Stream_uninitialized, Stream_exact, Stream_cauchy_mul, Stream_add, Stream_sub + sage: terms_of_degree = lambda n, R: [R.one()] + sage: x = Stream_exact([1], order=1) + sage: C = Stream_uninitialized(1) + sage: D = Stream_add(x, Stream_cauchy_mul(C, C, True), True) + sage: eq = Stream_sub(C, D, True) + sage: C.define_implicitly([C], [], [eq], QQ, QQ, terms_of_degree) + sage: C._good_cache + [0, 0, 0, 0] + sage: C[1] + 1 + sage: C._good_cache + [1, 0, 1, 0] + """ + g = [] + for c in self._input_streams: + if c._is_sparse: + vals = c._cache.values() + else: + vals = c._cache + i = 0 + for val in vals: + if val not in self._coefficient_ring: + break + i += 1 + g.append(i) + return g + + def __getitem__(self, n): + """ + Return the ``n``-th coefficient of ``self``. + + INPUT: + + - ``n`` -- integer; the index + + This method handles uninitialized streams no matter whether + they are defined using :meth:`define` or + :meth:`define_implicitly`. + + In the first case, we rely on coefficients being computed + lazily. More precisely, the value of the requested + coefficient must only depend on the preceding coefficients. + + In the second case, we use a variable to represent the + undetermined coefficient. Let us consider the simplest case + where each term of the stream corresponds to an element of + the series, i.e., ``self._coefficient_ring == + self._base_ring``, and suppose that we are requesting the + first coefficient which has not yet been determined. + + The logic of this method is such that, when called while + ``self._uncomputed == True``, it only returns (without error) + once the value of the variable representing the coefficient + has been successfully determined. In this case the variable + is replaced by its value in all caches of the + :meth:`input_streams`. + + When retrieving the next non-vanishing terms of the given + equations (in :meth:`_compute`), this method + (:meth:`__getitem__`) will be called again (possibly also for + other coefficients), however with the flag ``self._uncomputed + == False``. This entails that a variable is created for the + requested coefficients. + + Note that, only when ``self._uncomputed == False``, elements + from the cache which contain undetermined coefficients (i.e., + variables) are returned. This is achieved by storing the + number of valid coefficients in the cache in + :meth:`_good_cache`. + + From these equations we select the linear ones (in + :meth:`_compute`). We then solve this system of linear + equations, and substitute back any uniquely determined + coefficients in the caches of all input streams (in + :meth:`_subs_in_caches`). We repeat, until the requested + coefficient has been determined. + + EXAMPLES:: + + sage: from sage.data_structures.stream import Stream_uninitialized, Stream_exact, Stream_cauchy_mul, Stream_add, Stream_cauchy_compose + sage: x = Stream_exact([1], order=1) + sage: A = Stream_uninitialized(1) + sage: A.define(Stream_add(x, Stream_cauchy_mul(x, Stream_cauchy_compose(A, A, True), True), True)) + sage: [A[n] for n in range(10)] + [0, 1, 1, 2, 6, 23, 104, 531, 2982, 18109] + """ + if n < self._approximate_order: + return ZZ.zero() + + # define + if self._target is not None: + while not self._true_order and n >= self._approximate_order: + c = next(self._iter) + if c: + self._true_order = True + self._cache.append(c) + else: + self._approximate_order += 1 + + if self._true_order: + # It is important to extend by generator: + # self._iter might recurse, and thereby extend the + # cache itself, too. + i = n - self._approximate_order + self._cache.extend(next(self._iter) + for _ in range(i - len(self._cache) + 1)) + return self._cache[i] + + return ZZ.zero() + + # define_implicitly + if self._eqs is None: + raise ValueError("Stream is not yet defined") + if self._good_cache[0] > n - self._approximate_order: + return self._cache[n - self._approximate_order] + + if self._uncomputed: + for f in self._series: + f._uncomputed = False + while self._good_cache[0] < n - self._approximate_order + 1: + self._compute() + for f in self._series: + f._uncomputed = True + + if n < self._approximate_order: + return ZZ.zero() + if len(self._cache) > n - self._approximate_order: + return self._cache[n - self._approximate_order] + + # it may happen, that a variable for a coefficient of higher + # degree is requested, so we have to fill in all the degrees + for n0 in range(len(self._cache) + self._approximate_order, n+1): + # WARNING: coercing the new variable to self._PF slows + # down the multiplication enormously + if self._coefficient_ring == self._base_ring: + x = (self._pool.new_variable(self._name + "[%s]" % n0) + * self._terms_of_degree(n0, self._PF)[0]) + else: + x = sum(self._pool.new_variable(self._name + "[%s]" % m) * m + for m in self._terms_of_degree(n0, self._PF)) + self._cache.append(x) + + return x + + def _subs_in_caches(self, var, val): + r""" + Substitute ``val`` for ``var`` in the caches of the input + streams and update ``self._good_cache``. + + INPUT: + + - ``var``, a variable in ``self._P`` + - ``val``, the value that should replace the variable + + EXAMPLES:: + + sage: from sage.data_structures.stream import Stream_uninitialized, Stream_exact, Stream_cauchy_mul, Stream_add, Stream_sub + sage: terms_of_degree = lambda n, R: [R.one()] + sage: x = Stream_exact([1], order=1) + sage: C = Stream_uninitialized(1) + sage: D = Stream_add(x, Stream_cauchy_mul(C, C, True), True) + sage: eq = Stream_sub(C, D, True) + sage: C.define_implicitly([C], [], [eq], QQ, QQ, terms_of_degree) + sage: C[3] # indirect doctest + 2 + """ + def subs(c, var, val): + P = self._P.polynomial_ring() + num = P(c.numerator()._p).subs({P(var._p): val}) + den = P(c.denominator()._p).subs({P(var._p): val}) + return self._PF(InfinitePolynomial(self._P, num), + InfinitePolynomial(self._P, den)) + + def retract(c): + num = c.numerator() + den = c.denominator() + if num.is_constant() and den.is_constant(): + return num.constant_coefficient() / den.constant_coefficient() + return c + + def fix_cache(j, s, ao): + if s._cache[ao]: + if s._cache[ao] in self._coefficient_ring: + s._true_order = True + return False + del s._cache[ao] + self._good_cache[j] -= 1 + return True + + for j, s in enumerate(self._input_streams): + m = len(s._cache) - self._good_cache[j] + if s._is_sparse: + # we traverse the cache beginning with the last + # element added, because only the last m elements + # added can contain variables + indices = reversed(s._cache) + else: + indices = range(-1, -m-1, -1) + # substitute variable and determine last good element + good = m + for i0, i in enumerate(indices): + c = s._cache[i] + if self._coefficient_ring == self._base_ring: + if c.parent() == self._PF: + c = retract(subs(c, var, val)) + if c.parent() is not self._base_ring: + good = m - i0 - 1 + elif c.parent() == self._U: + c = c.map_coefficients(lambda e: subs(e, var, val)) + try: + c = c.map_coefficients(lambda e: retract(e), self._base_ring) + except TypeError: + good = m - i0 - 1 + s._cache[i] = c + self._good_cache[j] += good + # fix approximate_order and true_order + ao = s._approximate_order + if s._is_sparse: + while ao in s._cache and fix_cache(j, s, ao): + ao += 1 + else: + while s._cache and fix_cache(j, s, 0): + ao += 1 + s._approximate_order = ao + + self._pool.del_variable(var) + + def _collect_equations(self, offset): + """ + Return the equations obtained by setting the elements + ``eq._approximate_order + offset`` equal to zero, for each + ``eq`` in ``self._eqs``. + + EXAMPLES:: + + sage: from sage.data_structures.stream import Stream_uninitialized, Stream_cauchy_mul, Stream_sub + sage: terms_of_degree = lambda n, R: [R.one()] + sage: C = Stream_uninitialized(0) + sage: eq = Stream_sub(C, Stream_cauchy_mul(C, C, True), True) + sage: C.define_implicitly([C], [], [eq], QQ, QQ, terms_of_degree) + sage: C._uncomputed = False + sage: C._collect_equations(0) + ([], [[(0, -FESDUMMY_0^2 + FESDUMMY_0)]]) + """ + lin_coeffs = [] + all_coeffs = [] # only for the error message + for i, eq in enumerate(self._eqs): + while True: + deg = eq._approximate_order + offset + elt = eq[deg] + if elt: + break + # it may or may not be the case that the + # _approximate_order is advanced by __getitem__ + # still, the following might be unnecessary + for d in range(eq._approximate_order, deg+1): + if not eq[d]: + eq._approximate_order += 1 + + if self._base_ring == self._coefficient_ring: + elt_coeffs = [(deg, elt)] + else: + # TODO: it is a coincidence that `coefficients` + # currently exists in all examples; + # the monomials are only needed for the error messages + elt_coeffs = list(zip(elt.monomials(), elt.coefficients())) + + all_coeffs.append(elt_coeffs) + for idx, coeff in elt_coeffs: + coeff_num = self._PF(coeff).numerator() + V = coeff_num.variables() + if not V: + if len(self._eqs) == 1: + if self._base_ring == self._coefficient_ring: + raise ValueError(f"no solution as the coefficient in degree {idx} of the equation is {elt} != 0") + raise ValueError(f"no solution as the coefficient of {idx} of the equation is {elt} != 0") + raise ValueError(f"no solution as the coefficient of {idx} in equation {i} is {elt} != 0") + if coeff_num.degree() <= 1: + lin_coeffs.append(coeff_num) + elif coeff_num.is_monomial() and sum(1 for d in coeff_num.degrees() if d): + # if we have a single variable, we can remove the + # exponent - maybe we could also remove the + # coefficient - are we computing in an integral + # domain? + c1 = coeff_num.coefficients()[0] + v = self._P(coeff_num.variables()[0]) + lin_coeffs.append(c1 * v) + return lin_coeffs, all_coeffs + + def _solve_linear_equations_and_subs(self, lin_coeffs): + """ + Return whether any of the variables is determined uniquely when + setting ``lin_coeffs`` equal to zero. + + EXAMPLES:: + + sage: from sage.data_structures.stream import Stream_uninitialized, Stream_exact, Stream_cauchy_mul, Stream_add, Stream_sub + sage: terms_of_degree = lambda n, R: [R.one()] + sage: x = Stream_exact([1], order=1) + sage: C = Stream_uninitialized(1) + sage: D = Stream_add(x, Stream_cauchy_mul(C, C, True), True) + sage: eq = Stream_sub(C, D, True) + sage: C.define_implicitly([C], [], [eq], QQ, QQ, terms_of_degree) + sage: C._uncomputed = False + sage: lin_coeffs, all_coeffs = C._collect_equations(0) + sage: C._cache + [FESDUMMY_1] + sage: lin_coeffs + [FESDUMMY_1 - 1] + sage: C._solve_linear_equations_and_subs(lin_coeffs) + True + sage: C._cache + [1] + """ + from sage.rings.polynomial.multi_polynomial_sequence import PolynomialSequence + eqs = PolynomialSequence(lin_coeffs) + m1, v1 = eqs.coefficients_monomials() + # there should be at most one entry in v1 of degree 0 + for j, c in enumerate(v1): + if c.degree() == 0: + b = -m1.column(j) + m = m1.matrix_from_columns(i for i in range(len(v1)) if i != j) + v = [c for i, c in enumerate(v1) if i != j] + break + else: + from sage.modules.free_module_element import zero_vector + b = zero_vector(m1.nrows()) + m = m1 + v = list(v1) + x = m.solve_right(b) + k = m.right_kernel_matrix(basis="computed").transpose() + good = False + for sol, row, var in zip(x, k, v): + if row.is_zero(): + val = self._base_ring(sol) + self._subs_in_caches(var, val) + good = True + return good + + def _compute(self): + r""" + Determine the next equations by comparing coefficients, and solve + those which are linear. + + For each of the equations in the given list of equations + ``self._eqs``, we determine the first coefficient which is + non-zero. Among these, we only keep the coefficients which + are linear, i.e., whose total degree is one, and those which + are a single variable raised to a positive power, implying + that the variable itself must be zero. We then solve the + resulting linear system. If the system does not determine + any variable, we raise an error. + + EXAMPLES:: + + sage: from sage.data_structures.stream import Stream_uninitialized, Stream_exact, Stream_cauchy_mul, Stream_add, Stream_sub + sage: terms_of_degree = lambda n, R: [R.one()] + sage: x = Stream_exact([1], order=1) + sage: C = Stream_uninitialized(1) + sage: D = Stream_add(x, Stream_cauchy_mul(C, C, True), True) + sage: eq = Stream_sub(C, D, True) + sage: C.define_implicitly([C], [], [eq], QQ, QQ, terms_of_degree) + sage: C[3] # indirect doctest + 2 + """ + # determine the next linear equations + lin_coeffs = [] + all_coeffs = [] # only for the error message + for offset in range(self._max_lookahead): + new_lin_coeffs, new_all_coeffs = self._collect_equations(offset) + lin_coeffs.extend(new_lin_coeffs) + all_coeffs.extend(new_all_coeffs) + if lin_coeffs and self._solve_linear_equations_and_subs(lin_coeffs): + return + + if len(self._eqs) == 1: + eq_str = "\n ".join(self._eq_str(idx, eq) + for idx, eq in all_coeffs[0]) + if lin_coeffs: + raise ValueError("could not determine any coefficients:\n " + + eq_str) + raise ValueError("there are no linear equations:\n " + + eq_str) + + eqs_str = "\n".join(f"equation {i}:\n " + + "\n ".join(self._eq_str(idx, eq) + for idx, eq in eqs) + for i, eqs in enumerate(all_coeffs)) + if lin_coeffs: + raise ValueError("could not determine any coefficients:\n" + + eqs_str) + raise ValueError("there are no linear equations:\n" + + eqs_str) + + def _eq_str(self, idx, eq): + """ + Return a string describing the equation ``eq`` obtained by setting + the coefficient ``idx`` to zero. + + EXAMPLES:: + + sage: from sage.data_structures.stream import Stream_uninitialized, Stream_cauchy_mul, Stream_sub + sage: terms_of_degree = lambda n, R: [R.one()] + sage: C = Stream_uninitialized(0) + sage: eq = Stream_sub(C, Stream_cauchy_mul(C, C, True), True) + sage: C.define_implicitly([C], [], [eq], QQ, QQ, terms_of_degree) + sage: C[3] # indirect doctest + Traceback (most recent call last): + ... + ValueError: there are no linear equations: + coefficient [0]: -series[0]^2 + series[0] == 0 + """ + s = repr(eq) + p = self._pool.variables() + # we have to replace variables with longer names first + for v in sorted(p, key=lambda v: -len(str(v))): + s = s.replace(repr(v), p[v]) + return "coefficient " + repr([idx]) + ": " + s + " == 0" def iterate_coefficients(self): """ @@ -1291,13 +2057,16 @@ def is_uninitialized(self): sage: T._coeff_stream.is_uninitialized() True """ - if self._target is None: + if self._target is None and self._eqs is None: return True if self._initializing: return False # We implement semaphore-like behavior for coupled (undefined) series self._initializing = True - result = self._target.is_uninitialized() + if self._target is None: + result = False + else: + result = self._target.is_uninitialized() self._initializing = False return result @@ -1393,6 +2162,21 @@ def is_uninitialized(self): """ return self._series.is_uninitialized() + def input_streams(self): + r""" + Return the list of streams which are used to compute the + coefficients of ``self``. + + EXAMPLES:: + + sage: from sage.data_structures.stream import Stream_function, Stream_neg + sage: h = Stream_function(lambda n: n, False, 1) + sage: M = Stream_neg(h, False) + sage: M.input_streams() + [] + """ + return [self._series] + class Stream_binary(Stream_inexact): """ @@ -1498,6 +2282,23 @@ def is_uninitialized(self): """ return self._left.is_uninitialized() or self._right.is_uninitialized() + def input_streams(self): + r""" + Return the list of streams which are used to compute the + coefficients of ``self``. + + EXAMPLES:: + + sage: from sage.data_structures.stream import Stream_function, Stream_add + sage: l = Stream_function(lambda n: n, False, 1) + sage: r = Stream_function(lambda n: n^2, False, 1) + sage: M = Stream_add(l, r, False) + sage: M.input_streams() + [, + ] + """ + return [self._left, self._right] + class Stream_binaryCommutative(Stream_binary): r""" @@ -1618,7 +2419,7 @@ def order(self): sage: s.order() +Infinity """ - return self._approximate_order # == infinity + return self._approximate_order # == infinity def __eq__(self, other): """ @@ -1688,6 +2489,7 @@ class Stream_add(Stream_binaryCommutative): - ``left`` -- :class:`Stream` of coefficients on the left side of the operator - ``right`` -- :class:`Stream` of coefficients on the right side of the operator + - ``is_sparse`` -- boolean; whether the implementation of the stream is sparse EXAMPLES:: @@ -1746,6 +2548,7 @@ class Stream_sub(Stream_binary): - ``left`` -- :class:`Stream` of coefficients on the left side of the operator - ``right`` -- :class:`Stream` of coefficients on the right side of the operator + - ``is_sparse`` -- boolean; whether the implementation of the stream is sparse EXAMPLES:: @@ -1812,6 +2615,7 @@ class Stream_cauchy_mul(Stream_binary): - ``left`` -- :class:`Stream` of coefficients on the left side of the operator - ``right`` -- :class:`Stream` of coefficients on the right side of the operator + - ``is_sparse`` -- boolean; whether the implementation of the stream is sparse EXAMPLES:: @@ -1863,10 +2667,10 @@ def get_coefficient(self, n): sage: [h.get_coefficient(i) for i in range(10)] [0, 0, 1, 6, 20, 50, 105, 196, 336, 540] """ - return sum(l * self._right[n - k] - for k in range(self._left._approximate_order, - n - self._right._approximate_order + 1) - if (l := self._left[k])) + return ZZ.sum(l * self._right[n - k] + for k in range(self._left._approximate_order, + n - self._right._approximate_order + 1) + if (l := self._left[k])) def is_nonzero(self): r""" @@ -1967,10 +2771,10 @@ def get_coefficient(self, n): sage: [h[i] for i in range(1, 10)] [1, 3, 4, 7, 6, 12, 8, 15, 13] """ - return sum(l * self._right[n//k] for k in divisors(n) - if (k >= self._left._approximate_order - and n // k >= self._right._approximate_order - and (l := self._left[k]))) + return ZZ.sum(l * self._right[n//k] for k in divisors(n) + if (k >= self._left._approximate_order + and n // k >= self._right._approximate_order + and (l := self._left[k]))) class Stream_cauchy_compose(Stream_binary): @@ -2070,23 +2874,23 @@ def get_coefficient(self, n): fv = self._left._approximate_order gv = self._right._approximate_order if n < 0: - return sum(l * self._neg_powers[-k][n] - for k in range(fv, n // gv + 1) - if (l := self._left[k])) + return ZZ.sum(l * self._neg_powers[-k][n] + for k in range(fv, n // gv + 1) + if (l := self._left[k])) # n > 0 while len(self._pos_powers) <= n // gv: # TODO: possibly we always want a dense cache here? self._pos_powers.append(Stream_cauchy_mul(self._pos_powers[-1], self._right, self._is_sparse)) - ret = sum(l * self._neg_powers[-k][n] for k in range(fv, 0) - if (l := self._left[k])) + ret = ZZ.sum(l * self._neg_powers[-k][n] for k in range(fv, 0) + if (l := self._left[k])) if not n: ret += self._left[0] - return ret + sum(l * self._pos_powers[k][n] for k in range(1, n // gv + 1) - if (l := self._left[k])) + return ret + ZZ.sum(l * self._pos_powers[k][n] for k in range(1, n // gv + 1) + if (l := self._left[k])) class Stream_plethysm(Stream_binary): @@ -2180,7 +2984,6 @@ class Stream_plethysm(Stream_binary): sage: r2 = Stream_plethysm(f, g, True, p, include=[]) # needs sage.modules sage: r_s - sum(r2[n] for n in range(2*(r_s.degree()+1))) # needs sage.modules (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, is_sparse, p, ring=None, include=None, exclude=None): r""" @@ -2285,7 +3088,7 @@ def get_coefficient(self, n): return sum((c * self.compute_product(n, la) for k in range(self._left._approximate_order, K) - if self._left[k] # necessary, because it might be int(0) + if self._left[k] # necessary, because it might be int(0) for la, c in self._left[k]), self._basis.zero()) @@ -2396,7 +3199,6 @@ def stretched_power_restrict_degree(self, i, m, d): ....: if sum(mu.size() for mu in m) == 12}) sage: A == B # long time True - """ # TODO: we should do lazy binary powering here while len(self._powers) < m: @@ -2422,6 +3224,32 @@ def stretched_power_restrict_degree(self, i, m, d): return self._basis.zero() + def input_streams(self): + r""" + Return the list of streams which are used to compute the + coefficients of ``self``. + + EXAMPLES:: + + 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], True, 1) + sage: g = Stream_function(lambda n: s[n-1,1], True, 2) + sage: h = Stream_plethysm(f, g, True, p) + sage: h.input_streams() + [] + sage: [h[i] for i in range(1, 5)] + [0, + 1/2*p[1, 1] - 1/2*p[2], + 1/3*p[1, 1, 1] - 1/3*p[3], + 1/4*p[1, 1, 1, 1] + 1/4*p[2, 2] - 1/2*p[4]] + sage: h.input_streams() + [, + ] + """ + return self._powers + ##################################################################### # Unary operations @@ -2730,7 +3558,6 @@ class Stream_cauchy_invert(Stream_unary): sage: g = Stream_cauchy_invert(f) sage: [g[i] for i in range(10)] [-1, 0, 0, 0, 0, 0, 0, 0, 0, 0] - """ def __init__(self, series, approximate_order=None): """ @@ -2789,9 +3616,9 @@ def _ainv(self): """ v = self._series.order() try: - return ~self._series[v] - except TypeError: return self._series[v].inverse_of_unit() + except ArithmeticError: + return ~self._series[v] def iterate_coefficients(self): """ @@ -2888,12 +3715,12 @@ def _approximate_order(self): EXAMPLES:: sage: from sage.data_structures.stream import Stream_function, Stream_dirichlet_invert - sage: f = Stream_function(lambda n: n, True, 1) + sage: f = Stream_function(lambda n: QQ(n), True, 1) sage: h = Stream_dirichlet_invert(f, True) sage: h._approximate_order 1 sage: [h[i] for i in range(5)] - [0, -2, -8, -12, -48] + [0, 1, -2, -3, 0] """ # this is the true order, but we want to check first if self._series._approximate_order > 1: @@ -2921,9 +3748,9 @@ def _ainv(self): 5 """ try: - return ~self._series[1] - except TypeError: return self._series[1].inverse_of_unit() + except ArithmeticError: + return ~self._series[1] def get_coefficient(self, n): """ @@ -2946,9 +3773,9 @@ def get_coefficient(self, n): if n == 1: return self._ainv # TODO: isn't self[k] * l and l * self[k] the same here? - c = sum(self[k] * l for k in divisors(n) - if (k < n - and (l := self._series[n // k]))) + c = ZZ.sum(self[k] * l for k in divisors(n) + if (k < n + and (l := self._series[n // k]))) return -c * self._ainv @@ -2974,7 +3801,6 @@ class Stream_map_coefficients(Stream_unary): sage: g = Stream_map_coefficients(f, lambda n: -n, True) sage: [g[i] for i in range(10)] [0, -1, -1, -1, -1, -1, -1, -1, -1, -1] - """ def __init__(self, series, function, is_sparse, approximate_order=None, true_order=False): """ @@ -3476,6 +4302,8 @@ class Stream_derivative(Stream_unary): """ Operator for taking derivatives of a non-exact stream. + Instances of this class share the cache with its input stream. + INPUT: - ``series`` -- a :class:`Stream` @@ -3708,8 +4536,7 @@ def __eq__(self, other): True """ return (isinstance(other, type(self)) - and self._integration_constants == other._integration_constants - and self._series == other._series) + and self._integration_constants == other._integration_constants) def is_nonzero(self): r""" diff --git a/src/sage/rings/lazy_series.py b/src/sage/rings/lazy_series.py index 74744c311da..a23c1a25303 100644 --- a/src/sage/rings/lazy_series.py +++ b/src/sage/rings/lazy_series.py @@ -574,7 +574,6 @@ def map_coefficients(self, f): sage: f = z + z^2 + z^3 sage: f.map_coefficients(lambda c: c + 1) 2*z + 2*z^2 + 2*z^3 - """ P = self.parent() coeff_stream = self._coeff_stream @@ -850,12 +849,11 @@ def shift(self, n): coeff_stream = Stream_exact(init_coeff, constant=self._coeff_stream._constant, order=valuation, degree=degree) + elif (P._minimal_valuation is not None + and P._minimal_valuation > self._coeff_stream._approximate_order + n): + coeff_stream = Stream_truncated(self._coeff_stream, n, P._minimal_valuation) else: - if (P._minimal_valuation is not None - and P._minimal_valuation > self._coeff_stream._approximate_order + n): - coeff_stream = Stream_truncated(self._coeff_stream, n, P._minimal_valuation) - else: - coeff_stream = Stream_shift(self._coeff_stream, n) + coeff_stream = Stream_shift(self._coeff_stream, n) return P.element_class(P, coeff_stream) @@ -1530,7 +1528,9 @@ def define(self, s): sage: f 1 + 3*x + 16*x^2 + 87*x^3 + 607*x^4 + 4518*x^5 + 30549*x^6 + O(x^7) """ - if not isinstance(self._coeff_stream, Stream_uninitialized) or self._coeff_stream._target is not None: + if (not isinstance(self._coeff_stream, Stream_uninitialized) + or self._coeff_stream._target is not None + or self._coeff_stream._eqs is not None): raise ValueError("series already defined") if not isinstance(s, LazyModuleElement): @@ -1542,7 +1542,7 @@ def define(self, s): self._coeff_stream = coeff_stream return - self._coeff_stream._target = coeff_stream + self._coeff_stream.define(coeff_stream) # an alias for compatibility with padics set = define @@ -2697,7 +2697,6 @@ def arcsinh(self): sage: L. = LazyLaurentSeriesRing(SR); x = var("x") # needs sage.symbolic sage: asinh(z)[0:6] == asinh(x).series(x, 6).coefficients(sparse=False) # needs sage.symbolic True - """ from .lazy_series_ring import LazyLaurentSeriesRing P = LazyLaurentSeriesRing(self.base_ring(), "z", sparse=self.parent()._sparse) @@ -2735,7 +2734,6 @@ def arctanh(self): sage: L. = LazyLaurentSeriesRing(SR); x = var("x") # needs sage.symbolic sage: atanh(z)[0:6] == atanh(x).series(x, 6).coefficients(sparse=False) # needs sage.symbolic True - """ from .lazy_series_ring import LazyLaurentSeriesRing P = LazyLaurentSeriesRing(self.base_ring(), "z", sparse=self.parent()._sparse) @@ -2770,7 +2768,6 @@ def hypergeometric(self, a, b): sage: L. = LazyLaurentSeriesRing(SR); x = var("x") # needs sage.symbolic sage: z.hypergeometric([1,1],[1])[0:6] == hypergeometric([1,1],[1], x).series(x, 6).coefficients(sparse=False) # needs sage.symbolic True - """ from .lazy_series_ring import LazyLaurentSeriesRing from sage.arith.misc import rising_factorial @@ -3647,7 +3644,7 @@ def exp(self): d_self_f = Stream_cauchy_mul_commutative(d_self, f._coeff_stream, False) int_d_self_f = Stream_function(lambda n: d_self_f[n-1] / R(n) if n else R.one(), False, 0) - f._coeff_stream._target = int_d_self_f + f._coeff_stream.define(int_d_self_f) return f def log(self): @@ -4122,7 +4119,6 @@ def __call__(self, g): sage: g = L([2]) sage: f(g) 0 - """ # Find a good parent for the result from sage.structure.element import get_coercion_model @@ -4543,7 +4539,7 @@ def integral(self, variable=None, *, constants=None): constants for the integrals of ``self`` (the last constant corresponds to the first integral) - If the first argument is a list, then this method iterprets it as + If the first argument is a list, then this method interprets it as integration constants. If it is a positive integer, the method interprets it as the number of times to integrate the function. If ``variable`` is not the variable of the Laurent series, then @@ -4806,7 +4802,6 @@ def polynomial(self, degree=None, name=None): sage: L.zero().polynomial() 0 - """ S = self.parent() @@ -4976,7 +4971,6 @@ def _im_gens_(self, codomain, im_gens, base_map=None): sage: f = 1/(1+x*q-t) sage: f._im_gens_(S, [s, x*s], base_map=cc) 1 + 2*x*s + 4*x^2*s^2 + 8*x^3*s^3 + 16*x^4*s^4 + 32*x^5*s^5 + 64*x^6*s^6 + O(s^7) - """ if base_map is None: return codomain(self(*im_gens)) @@ -5186,8 +5180,9 @@ def __call__(self, *g): cm = get_coercion_model() P = cm.common_parent(self.base_ring(), *[parent(h) for h in g]) + coeff_stream = self._coeff_stream # f = 0 - if isinstance(self._coeff_stream, Stream_zero): + if isinstance(coeff_stream, Stream_zero): return P.zero() # g = (0, ..., 0) @@ -5198,8 +5193,8 @@ def __call__(self, *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): + if (isinstance(coeff_stream, Stream_exact) + and not coeff_stream._constant): # constant polynomial poly = self.polynomial() if poly.is_constant(): @@ -5249,7 +5244,7 @@ def __call__(self, *g): h._coeff_stream._approximate_order = 2 # We now have that every element of g has a _coeff_stream - sorder = self._coeff_stream._approximate_order + sorder = coeff_stream._approximate_order if len(g) == 1: g0 = g[0] if isinstance(g0, LazyDirichletSeries): @@ -5257,25 +5252,37 @@ def __call__(self, *g): def coefficient(n): return sum(self[i] * (g0**i)[n] for i in range(n+1)) - coeff_stream = Stream_function(coefficient, P._sparse, 1) - return P.element_class(P, coeff_stream) + return P.element_class(P, Stream_function(coefficient, + P._sparse, 1)) - coeff_stream = Stream_cauchy_compose(self._coeff_stream, - g0._coeff_stream, - P.is_sparse()) - return P.element_class(P, coeff_stream) + return P.element_class(P, Stream_cauchy_compose(coeff_stream, + g0._coeff_stream, + P.is_sparse())) # The arity is at least 2 gv = min(h._coeff_stream._approximate_order for h in g) + gR = None def coefficient(n): + nonlocal gR r = R.zero() for i in range(n // gv + 1): - # Make sure the element returned from the composition is in P - r += P(self[i](g))[n] + c = coeff_stream[i] + if c.parent() == self.base_ring(): + c = P(c) + r += c[n] + elif c.parent().base_ring() is self.base_ring(): + r += c(g)[n] + else: + if gR is None: + S = c.parent().base_ring() + gR = [h.change_ring(S).map_coefficients(S) for h in g] + s = c(gR)[n] + r += s return r - coeff_stream = Stream_function(coefficient, P._sparse, sorder * gv) - return P.element_class(P, coeff_stream) + + return P.element_class(P, Stream_function(coefficient, + P._sparse, sorder * gv)) compose = __call__ @@ -5699,7 +5706,7 @@ def integral(self, variable=None, *, constants=None): specified; the integration constant is taken to be `0`. Now we assume the series is univariate. If the first argument is a - list, then this method iterprets it as integration constants. If it + list, then this method interprets it as integration constants. If it is a positive integer, the method interprets it as the number of times to integrate the function. If ``variable`` is not the variable of the power series, then the coefficients are integrated with respect @@ -6669,7 +6676,6 @@ def revert(self): - Andrew Gainer-Dewar - Martin Rubey - """ P = self.parent() if P._arity != 1: @@ -7196,7 +7202,6 @@ def arithmetic_product(self, *args): sage: L = LazySymmetricFunctions(s) # needs sage.modules sage: L(s([2])).arithmetic_product(s([1,1,1])) # needs sage.modules 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") @@ -7321,7 +7326,6 @@ def symmetric_function(self, degree=None): 0 sage: f4.symmetric_function(0) # needs lrcalc_python s[] - """ S = self.parent() R = S._laurent_poly_ring diff --git a/src/sage/rings/lazy_series_ring.py b/src/sage/rings/lazy_series_ring.py index 6f96ffc839d..cbb41861301 100644 --- a/src/sage/rings/lazy_series_ring.py +++ b/src/sage/rings/lazy_series_ring.py @@ -93,6 +93,7 @@ from types import GeneratorType + class LazySeriesRing(UniqueRepresentation, Parent): """ Abstract base class for lazy series. @@ -620,13 +621,16 @@ def _element_constructor_(self, x=None, valuation=None, degree=None, constant=No raise ValueError(f"unable to convert {x} into {self}") - def undefined(self, valuation=None): + def undefined(self, valuation=None, name=None): r""" Return an uninitialized series. INPUT: - - ``valuation`` -- integer; a lower bound for the valuation of the series + - ``valuation`` -- integer; a lower bound for the valuation + of the series + - ``name`` -- string; a name that refers to the undefined + stream in error messages Power series can be defined recursively (see :meth:`sage.rings.lazy_series.LazyModuleElement.define` for @@ -654,11 +658,415 @@ def undefined(self, valuation=None): """ if valuation is None: valuation = self._minimal_valuation - coeff_stream = Stream_uninitialized(valuation) + coeff_stream = Stream_uninitialized(valuation, name=name) return self.element_class(self, coeff_stream) unknown = undefined + def _terms_of_degree(self, n, R): + r""" + Return the list of terms occurring in a coefficient of degree + ``n`` such that coefficients are in the ring ``R``. + + If ``self`` is a univariate Laurent, power, or Dirichlet + series, this is the list containing the one of the base ring. + + If ``self`` is a multivariate power series, this is the list + of monomials of total degree ``n``. + + If ``self`` is a lazy symmetric function, this is the list + of basis elements of total degree ``n``. + + EXAMPLES:: + + sage: # needs sage.modules + sage: s = SymmetricFunctions(ZZ).s() + sage: L = LazySymmetricFunctions(s) + sage: m = L._terms_of_degree(3, ZZ); m + [s[3], s[2, 1], s[1, 1, 1]] + """ + raise NotImplementedError + + def define_implicitly(self, series, equations, max_lookahead=1): + r""" + Define series by solving functional equations. + + INPUT: + + - ``series`` -- list of undefined series or pairs each + consisting of a series and its initial values + - ``equations`` -- list of equations defining the series + - ``max_lookahead``-- (default: ``1``); a positive integer + specifying how many elements beyond the currently known + (i.e., approximate) order of each equation to extract + linear equations from + + EXAMPLES:: + + sage: L. = LazyPowerSeriesRing(QQ) + sage: f = L.undefined(0) + sage: F = diff(f, 2) + sage: L.define_implicitly([(f, [1, 0])], [F + f]) + sage: f + 1 - 1/2*z^2 + 1/24*z^4 - 1/720*z^6 + O(z^7) + sage: cos(z) + 1 - 1/2*z^2 + 1/24*z^4 - 1/720*z^6 + O(z^7) + sage: F + -1 + 1/2*z^2 - 1/24*z^4 + 1/720*z^6 + O(z^7) + + sage: L. = LazyPowerSeriesRing(QQ) + sage: f = L.undefined(0) + sage: L.define_implicitly([f], [2*z*f(z^3) + z*f^3 - 3*f + 3]) + sage: f + 1 + z + z^2 + 2*z^3 + 5*z^4 + 11*z^5 + 28*z^6 + O(z^7) + + From Exercise 6.63b in [EnumComb2]_:: + + sage: g = L.undefined() + sage: z1 = z*diff(g, z) + sage: z2 = z1 + z^2 * diff(g, z, 2) + sage: z3 = z1 + 3 * z^2 * diff(g, z, 2) + z^3 * diff(g, z, 3) + sage: e1 = g^2 * z3 - 15*g*z1*z2 + 30*z1^3 + sage: e2 = g * z2 - 3 * z1^2 + sage: e3 = g * z2 - 3 * z1^2 + sage: e = e1^2 + 32 * e2^3 - g^10 * e3^2 + sage: L.define_implicitly([(g, [1, 2])], [e]) + + sage: sol = L(lambda n: 1 if not n else (2 if is_square(n) else 0)); sol + 1 + 2*z + 2*z^4 + O(z^7) + sage: all(g[i] == sol[i] for i in range(50)) + True + + Some more examples over different rings:: + + sage: # needs sage.symbolic + sage: L. = LazyPowerSeriesRing(SR) + sage: G = L.undefined(0) + sage: L.define_implicitly([(G, [ln(2)])], [diff(G) - exp(-G(-z))]) + sage: G + log(2) + z + 1/2*z^2 + (-1/12*z^4) + 1/45*z^6 + O(z^7) + + sage: L. = LazyPowerSeriesRing(RR) + sage: G = L.undefined(0) + sage: L.define_implicitly([(G, [log(2)])], [diff(G) - exp(-G(-z))]) + sage: G + 0.693147180559945 + 1.00000000000000*z + 0.500000000000000*z^2 + - 0.0833333333333333*z^4 + 0.0222222222222222*z^6 + O(1.00000000000000*z^7) + + We solve the recurrence relation in (3.12) of Prellberg and Brak + :doi:`10.1007/BF02183685`:: + + sage: q, y = QQ['q,y'].fraction_field().gens() + sage: L. = LazyPowerSeriesRing(q.parent()) + sage: R = L.undefined() + sage: L.define_implicitly([(R, [0])], [(1-q*x)*R - (y*q*x+y)*R(q*x) - q*x*R*R(q*x) - x*y*q]) + sage: R[0] + 0 + sage: R[1] + (-q*y)/(q*y - 1) + sage: R[2] + (q^3*y^2 + q^2*y)/(q^3*y^2 - q^2*y - q*y + 1) + sage: R[3].factor() + (-1) * y * q^3 * (q*y - 1)^-2 * (q^2*y - 1)^-1 * (q^3*y - 1)^-1 + * (q^4*y^3 + q^3*y^2 + q^2*y^2 - q^2*y - q*y - 1) + + sage: Rp = L.undefined(1) + sage: L.define_implicitly([Rp], [(y*q*x+y)*Rp(q*x) + q*x*Rp*Rp(q*x) + x*y*q - (1-q*x)*Rp]) + sage: all(R[n] == Rp[n] for n in range(7)) + True + + Another example:: + + sage: L. = LazyPowerSeriesRing(QQ["x,y,f1,f2"].fraction_field()) + sage: L.base_ring().inject_variables() + Defining x, y, f1, f2 + sage: F = L.undefined() + sage: L.define_implicitly([(F, [0, f1, f2])], [F(2*z) - (1+exp(x*z)+exp(y*z))*F - exp((x+y)*z)*F(-z)]) + sage: F + f1*z + f2*z^2 + ((-1/6*x*y*f1+1/3*x*f2+1/3*y*f2)*z^3) + + ((-1/24*x^2*y*f1-1/24*x*y^2*f1+1/12*x^2*f2+1/12*x*y*f2+1/12*y^2*f2)*z^4) + + ... + O(z^8) + sage: sol = 1/(x-y)*((2*f2-y*f1)*(exp(x*z)-1)/x - (2*f2-x*f1)*(exp(y*z)-1)/y) + sage: F - sol + O(z^7) + + We need to specify the initial values for the degree 1 and 2 + components to get a unique solution in the previous example:: + + sage: L. = LazyPowerSeriesRing(QQ["x,y,f1"].fraction_field()) + sage: L.base_ring().inject_variables() + Defining x, y, f1 + sage: F = L.undefined() + sage: L.define_implicitly([F], [F(2*z) - (1+exp(x*z)+exp(y*z))*F - exp((x+y)*z)*F(-z)]) + sage: F + + + sage: F = L.undefined() + sage: L.define_implicitly([(F, [0, f1])], [F(2*z) - (1+exp(x*z)+exp(y*z))*F - exp((x+y)*z)*F(-z)]) + sage: F + + + Laurent series examples:: + + sage: L. = LazyLaurentSeriesRing(QQ) + sage: f = L.undefined(-1) + sage: L.define_implicitly([(f, [5])], [2+z*f(z^2) - f]) + sage: f + 5*z^-1 + 2 + 2*z + 2*z^3 + O(z^6) + sage: 2 + z*f(z^2) - f + O(z^6) + + sage: g = L.undefined(-2) + sage: L.define_implicitly([(g, [5])], [2+z*g(z^2) - g]) + sage: g + + + A bivariate example:: + + sage: L. = LazyPowerSeriesRing(QQ) + sage: B = L.undefined() + sage: eq = y*B^2 + 1 - B(x, x-y) + sage: L.define_implicitly([B], [eq]) + sage: B + 1 + (x-y) + (2*x*y-2*y^2) + (4*x^2*y-7*x*y^2+3*y^3) + + (2*x^3*y+6*x^2*y^2-18*x*y^3+10*y^4) + + (30*x^3*y^2-78*x^2*y^3+66*x*y^4-18*y^5) + + (28*x^4*y^2-12*x^3*y^3-128*x^2*y^4+180*x*y^5-68*y^6) + O(x,y)^7 + + Knödel walks:: + + sage: L. = LazyPowerSeriesRing(QQ) + sage: F = L.undefined() + sage: eq = F(z, x)*(x^2*z-x+z) - (z - x*z^2 - x^2*z^2)*F(z, 0) + x + sage: L.define_implicitly([F], [eq]) + sage: F + 1 + (2*z^2+z*x) + (z^3+z^2*x) + (5*z^4+3*z^3*x+z^2*x^2) + + (5*z^5+4*z^4*x+z^3*x^2) + (15*z^6+10*z^5*x+4*z^4*x^2+z^3*x^3) + + O(z,x)^7 + + Bicolored rooted trees with black and white roots:: + + sage: L. = LazyPowerSeriesRing(QQ) + sage: A = L.undefined() + sage: B = L.undefined() + sage: L.define_implicitly([A, B], [A - x*exp(B), B - y*exp(A)]) + sage: A + x + x*y + (x^2*y+1/2*x*y^2) + (1/2*x^3*y+2*x^2*y^2+1/6*x*y^3) + + (1/6*x^4*y+3*x^3*y^2+2*x^2*y^3+1/24*x*y^4) + + (1/24*x^5*y+8/3*x^4*y^2+27/4*x^3*y^3+4/3*x^2*y^4+1/120*x*y^5) + + O(x,y)^7 + + sage: h = SymmetricFunctions(QQ).h() + sage: S = LazySymmetricFunctions(h) + sage: E = S(lambda n: h[n]) + sage: T = LazySymmetricFunctions(tensor([h, h])) + sage: X = tensor([h[1],h[[]]]) + sage: Y = tensor([h[[]],h[1]]) + sage: A = T.undefined() + sage: B = T.undefined() + sage: T.define_implicitly([A, B], [A - X*E(B), B - Y*E(A)]) + sage: A[:3] + [h[1] # h[], h[1] # h[1]] + + Permutations with two kinds of labels such that each cycle + contains at least one element of each kind (defined + implicitly to have a test):: + + sage: p = SymmetricFunctions(QQ).p() + sage: S = LazySymmetricFunctions(p) + sage: P = S(lambda n: sum(p[la] for la in Partitions(n))) + sage: T = LazySymmetricFunctions(tensor([p, p])) + sage: X = tensor([p[1],p[[]]]) + sage: Y = tensor([p[[]],p[1]]) + sage: A = T.undefined() + sage: T.define_implicitly([A], [P(X)*P(Y)*A - P(X+Y)]) + sage: A[:4] + [p[] # p[], 0, p[1] # p[1], p[1] # p[1, 1] + p[1, 1] # p[1]] + + The Frobenius character of labelled Dyck words:: + + sage: h = SymmetricFunctions(QQ).h() + sage: L. = LazyPowerSeriesRing(h.fraction_field()) + sage: D = L.undefined() + sage: s1 = L.sum(lambda n: h[n]*t^(n+1)*u^(n-1), 1) + sage: L.define_implicitly([D], [u*D - u - u*s1*D - t*(D - D(t, 0))]) + sage: D + h[] + h[1]*t^2 + ((h[1,1]+h[2])*t^4+h[2]*t^3*u) + + ((h[1,1,1]+3*h[2,1]+h[3])*t^6+(2*h[2,1]+h[3])*t^5*u+h[3]*t^4*u^2) + + O(t,u)^7 + + TESTS:: + + sage: L. = LazyPowerSeriesRing(QQ) + sage: f = L.undefined(1) + sage: L.define_implicitly([f], [log(1+f) - ~(1 + f) + 1]) + sage: f + O(z^8) + + sage: f = L.undefined(0) + sage: fp = f.derivative() + sage: g = L(lambda n: 0 if n < 10 else 1, 0) + sage: L.define_implicitly([f], [f.derivative() * g + f]) + sage: f[0] + 0 + sage: fp[0] + 0 + sage: fp[1] + 0 + sage: fp[2] + 0 + sage: f[1] + 0 + + Some systems of coupled functional equations:: + + sage: L. = LazyPowerSeriesRing(QQ) + sage: A = L.undefined() + sage: B = L.undefined() + sage: L.define_implicitly([A, B], [A - B, A + B + 2]) + sage: A + -1 + O(z^7) + sage: B + -1 + O(z^7) + + sage: L. = LazyPowerSeriesRing(QQ) + sage: A = L.undefined() + sage: B = L.undefined() + sage: FA = A^2 + B - 2 - z*B + sage: FB = B^2 - A + sage: L.define_implicitly([(A, [1]), (B, [1])], [FA, FB]) + sage: A^2 + B - 2 - z*B + O(z^7) + sage: B^2 - A + O(z^7) + + sage: L. = LazyPowerSeriesRing(QQ) + sage: A = L.undefined() + sage: B = L.undefined() + sage: FA = A^2 + B^2 - 2 - z*B + sage: FB = B^3 + 2*A^3 - 3 - z*(A + B) + sage: L.define_implicitly([(A, [1]), (B, [1])], [FA, FB]) + sage: A^2 + B^2 - 2 - z*B + O(z^7) + sage: B^3 + 2*A^3 - 3 - z*(A + B) + O(z^7) + + sage: L. = LazyPowerSeriesRing(QQ) + sage: A = L.undefined(valuation=3) + sage: B = L.undefined(valuation=2) + sage: C = L.undefined(valuation=2) + sage: FA = (A^2 + B^2)*z^2 + sage: FB = A*B*z + sage: FC = (A + B + C)*z^2 + sage: L.define_implicitly([A, B, C], [FA, FB, FC]) + sage: A + O(z^10) + sage: B + O(z^16) + sage: C + O(z^23) + + sage: L. = LazyPowerSeriesRing(QQ) + sage: A = L.undefined() + sage: B = L.undefined() + sage: C = L.undefined() + sage: L.define_implicitly([A, B, C], [B - C - 1, B*z + 2*C + 1, A + 2*C + 1]) + sage: A + 2*C + 1 + O(z^7) + + The following system does not determine `B`, but the solver + will inductively discover that each coefficient of `A` must + be zero. Therefore, asking for a coefficient of `B` will + loop forever:: + + sage: L. = LazyPowerSeriesRing(QQ) + sage: A = L.undefined() + sage: B = L.undefined() + sage: C = L.undefined() + sage: D = L.undefined() + sage: L.define_implicitly([(A, [0,0,0]), (B, [0,0]), (C, [0,0]), (D, [0,0])], [C^2 + D^2, A + B + C + D, A*D]) + sage: B[2] # not tested + + A bivariate example involving composition of series:: + + sage: R. = LazyPowerSeriesRing(QQ) + sage: g = R.undefined() + sage: R.define_implicitly([g], [g - (z*q + z*g*~(1-g))]) + sage: g + z*q + z^2*q + z^3*q + (z^4*q+z^3*q^2) + (z^5*q+3*z^4*q^2) + O(z,q)^7 + + The following does not work, because the equations + determining the coefficients come in bad order:: + + sage: L. = LazyPowerSeriesRing(QQ) + sage: A = L.undefined(name="A") + sage: B = L.undefined(name="B") + sage: eq0 = t*x*y*B(0, 0, t) + (t - x*y)*A(x, y, t) + x*y - t*A(0, y, t) + sage: eq1 = (t*x-t)*B(0, y, t) + (t - x*y)*B(x, y, t) + sage: L.define_implicitly([A, B], [eq0, eq1]) + sage: A[1] + Traceback (most recent call last): + ... + ValueError: could not determine any coefficients: + equation 0: + coefficient [x*y*t]: A[x*y] - A[t] == 0 + equation 1: + coefficient [x*y*t]: B[x*y] - B[t] == 0 + coefficient [x*t^2]: B[x*t] + B[t] == 0 + + Check the error message in the case of symmetric functions:: + + sage: p = SymmetricFunctions(QQ).p() + sage: T = LazySymmetricFunctions(tensor([p, p])) + sage: X = tensor([p[1],p[[]]]) + sage: Y = tensor([p[[]],p[1]]) + sage: A = T.undefined(name="A") + sage: B = T.undefined(name="B") + sage: T.define_implicitly([A, B], [X*A - Y*B]) + sage: A + + + An example we cannot solve because we only look at the next + non-vanishing equations:: + + sage: L. = LazyPowerSeriesRing(QQ) + sage: A = L.undefined() + sage: eq1 = diff(A, x) + diff(A, x, 2) + sage: eq2 = A + diff(A, x) + diff(A, x, 2) + sage: L.define_implicitly([A], [eq1, eq2]) + sage: A[1] + Traceback (most recent call last): + ... + ValueError: could not determine any coefficients: + equation 0: + coefficient [0]: 2*series[2] + series[1] == 0 + equation 1: + coefficient [0]: 2*series[2] + series[1] == 0 + + sage: A = L.undefined() + sage: eq1 = diff(A, x) + diff(A, x, 2) + sage: eq2 = A + diff(A, x) + diff(A, x, 2) + sage: L.define_implicitly([A], [eq1, eq2], max_lookahead=2) + sage: A + O(x^7) + """ + s = [a[0]._coeff_stream if isinstance(a, (tuple, list)) + else a._coeff_stream + for a in series] + ics = [a[1] if isinstance(a, (tuple, list)) + else [] + for a in series] + eqs = [eq._coeff_stream for eq in equations] + for f, ic in zip(s, ics): + f.define_implicitly(s, ic, eqs, + self.base_ring(), + self._internal_poly_ring.base_ring(), + self._terms_of_degree, + max_lookahead=max_lookahead) + class options(GlobalOptions): r""" Set and display the options for lazy series. @@ -738,7 +1146,6 @@ def one(self): sage: L = LazySymmetricFunctions(m) # needs sage.modules sage: L.one() # needs sage.modules m[] - """ R = self.base_ring() coeff_stream = Stream_exact([R.one()], constant=R.zero(), order=0) @@ -1234,6 +1641,7 @@ def _test_revert(self, **options): # we want to test at least 2 elements tester.assertGreater(count, 1, msg="only %s elements in %s.some_elements() have a compositional inverse" % (count, self)) + class LazyLaurentSeriesRing(LazySeriesRing): r""" The ring of lazy Laurent series. @@ -1767,6 +2175,21 @@ def _monomial(self, c, n): """ return self._laurent_poly_ring(c).shift(n) + def _terms_of_degree(self, n, R): + r""" + Return the list consisting of a single element ``1`` in the given + ring. + + EXAMPLES:: + + sage: L = LazyLaurentSeriesRing(ZZ, 'z') + sage: t = L._terms_of_degree(3, ZZ['x']); t + [1] + sage: t[0].parent() + Univariate Polynomial Ring in x over Integer Ring + """ + return [R.one()] + def uniformizer(self): """ Return a uniformizer of ``self``.. @@ -2076,6 +2499,26 @@ def __init__(self, base_ring, names, sparse=True, category=None): Parent.__init__(self, base=base_ring, names=names, category=category) + def construction(self): + """ + Return a pair ``(F, R)``, where ``F`` is a + :class:`CompletionFunctor` and `R` is a ring, such that + ``F(R)`` returns ``self``. + + EXAMPLES:: + + sage: L = LazyPowerSeriesRing(ZZ, 't') + sage: L.construction() + (Completion[t, prec=+Infinity], + Sparse Univariate Polynomial Ring in t over Integer Ring) + """ + from sage.categories.pushout import CompletionFunctor + if self._arity == 1: + return (CompletionFunctor(self._names[0], infinity), + self._laurent_poly_ring) + return (CompletionFunctor(self._names, infinity), + self._laurent_poly_ring) + def _repr_(self): """ String representation of this Taylor series ring. @@ -2121,6 +2564,30 @@ def _monomial(self, c, n): return L(c) * L.gen() ** n return L(c) + @cached_method + def _terms_of_degree(self, n, R): + r""" + Return the list of monomials of degree ``n`` in the polynomial + ring with base ring ``R``. + + EXAMPLES:: + + sage: L. = LazyPowerSeriesRing(ZZ) + sage: m = L._terms_of_degree(3, QQ["z"]); m + [1] + sage: m[0].parent() + Univariate Polynomial Ring in z over Rational Field + sage: L. = LazyPowerSeriesRing(ZZ) + sage: m = L._terms_of_degree(3, QQ["z"]); m + [y^3, x*y^2, x^2*y, x^3] + sage: m[0].parent() + Multivariate Polynomial Ring in x, y over Univariate Polynomial Ring in z over Rational Field + """ + if self._arity == 1: + return [R.one()] + return [m.change_ring(R) + for m in self._internal_poly_ring.base_ring().monomials_of_degree(n)] + @cached_method def gen(self, n=0): """ @@ -2147,7 +2614,7 @@ def gen(self, n=0): if len(self.variable_names()) == 1: coeff_stream = Stream_exact([BR.one()], constant=BR.zero(), order=1) else: - coeff_stream = Stream_exact([R.gen(n)], constant=BR.zero(), order=1) + coeff_stream = Stream_exact([R.gen(n)], constant=R.zero(), order=1) return self.element_class(self, coeff_stream) def ngens(self): @@ -2297,14 +2764,13 @@ def _element_constructor_(self, x=None, valuation=None, constant=None, degree=No Traceback (most recent call last): ... ValueError: coefficients must be homogeneous polynomials of the correct degree - """ if valuation is not None: if valuation < 0: 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 self._arity > 1 and valuation != 0: +# raise ValueError(f"valuation must not be specified for multivariate Taylor series (for {x}), but was set to {valuation}") if self._arity > 1: valuation = 0 @@ -2420,11 +2886,10 @@ def y(n): coeff_stream = Stream_function(y, self._sparse, valuation) else: coeff_stream = Stream_iterator(map(R, _skip_leading_zeros(x)), valuation) + elif callable(x): + coeff_stream = Stream_function(lambda i: BR(x(i)), self._sparse, valuation) else: - 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) + 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") @@ -2608,7 +3073,6 @@ def taylor(self, f): BR = R.base_ring() args = f.arguments() subs = {str(va): ZZ.zero() for va in args} - gens = R.gens() ell = len(subs) from sage.combinat.integer_vector import integer_vectors_nk_fast_iter from sage.arith.misc import factorial @@ -2791,6 +3255,50 @@ def _monomial(self, c, n): L = self._laurent_poly_ring return L(c) + @cached_method + def _terms_of_degree(self, n, R): + r""" + Return the list of basis elements of degree ``n``. + + EXAMPLES:: + + sage: # needs sage.modules + sage: s = SymmetricFunctions(ZZ).s() + sage: L = LazySymmetricFunctions(s) + sage: m = L._terms_of_degree(3, QQ["x"]); m + [s[3], s[2, 1], s[1, 1, 1]] + sage: m[0].parent() + Symmetric Functions over Univariate Polynomial Ring in x over Rational Field in the Schur basis + sage: L = LazySymmetricFunctions(tensor([s, s])) + sage: m = L._terms_of_degree(3, QQ["x"]); m + [s[3] # s[], + s[2, 1] # s[], + s[1, 1, 1] # s[], + s[2] # s[1], + s[1, 1] # s[1], + s[1] # s[2], + s[1] # s[1, 1], + s[] # s[3], + s[] # s[2, 1], + s[] # s[1, 1, 1]] + sage: m[0].parent() + Symmetric Functions over Univariate Polynomial Ring in x over Rational Field in the Schur basis + # Symmetric Functions over Univariate Polynomial Ring in x over Rational Field in the Schur basis + """ + from sage.combinat.integer_vector import IntegerVectors + from sage.misc.mrange import cartesian_product_iterator + from sage.categories.tensor import tensor + B = self._internal_poly_ring.base_ring() + B = B.change_ring(R) + if self._arity == 1: + return list(B.homogeneous_component_basis(n)) + l = [] + for c in IntegerVectors(n, self._arity): + for m in cartesian_product_iterator([F.homogeneous_component_basis(p) + for F, p in zip(B.tensor_factors(), c)]): + l.append(tensor(m)) + return l + def _element_constructor_(self, x=None, valuation=None, degree=None, constant=None, check=True): r""" Construct a lazy element in ``self`` from ``x``. @@ -2907,7 +3415,7 @@ def _element_constructor_(self, x=None, valuation=None, degree=None, constant=No coeff_stream = Stream_exact(p_list, order=v, - constant=0, + constant=self.base_ring().zero(), degree=degree) return self.element_class(self, coeff_stream) @@ -2951,7 +3459,7 @@ def check_homogeneous_of_degree(f, d): check_homogeneous_of_degree(e, i) coeff_stream = Stream_exact(p, order=valuation, - constant=0, + constant=self._laurent_poly_ring.zero(), degree=degree) return self.element_class(self, coeff_stream) if callable(x): @@ -2961,7 +3469,7 @@ def check_homogeneous_of_degree(f, d): check_homogeneous_of_degree(e, i) coeff_stream = Stream_exact(p, order=valuation, - constant=0, + constant=self._laurent_poly_ring.zero(), degree=degree) return self.element_class(self, coeff_stream) if check: @@ -3012,7 +3520,6 @@ def some_elements(self): sage: L = S.formal_series_ring() sage: L.some_elements()[:4] [0, S[], 2*S[] + 2*S[1] + (3*S[1,1]), 2*S[1] + (3*S[1,1])] - """ elt = self.an_element() elts = [self.zero(), self.one(), elt] @@ -3037,6 +3544,7 @@ def some_elements(self): ###################################################################### + class LazySymmetricFunctions(LazyCompletionGradedAlgebra): """ The ring of lazy symmetric functions. @@ -3164,7 +3672,6 @@ def __init__(self, base_ring, names, sparse=True, category=None): sage: TestSuite(L).run() # needs sage.symbolic sage: LazyDirichletSeriesRing.options._reset() # reset the options - """ if base_ring.characteristic() > 0: raise ValueError("positive characteristic not allowed for Dirichlet series") @@ -3414,6 +3921,21 @@ def _monomial(self, c, n): except (ValueError, TypeError): return '({})/{}^{}'.format(self.base_ring()(c), n, self.variable_name()) + def _terms_of_degree(self, n, R): + r""" + Return the list consisting of a single element 1 in the base ring. + + EXAMPLES:: + + sage: L = LazyDirichletSeriesRing(ZZ, 'z') + sage: t = L._terms_of_degree(3, ZZ['x']); t + [1] + sage: t[0].parent() + Univariate Polynomial Ring in x over Integer Ring + """ + return [R.one()] + + def _skip_leading_zeros(iterator): """ Return an iterator which discards all leading zeros. diff --git a/src/sage/rings/polynomial/infinite_polynomial_element.py b/src/sage/rings/polynomial/infinite_polynomial_element.py index bb263b21a51..15ff9a97182 100644 --- a/src/sage/rings/polynomial/infinite_polynomial_element.py +++ b/src/sage/rings/polynomial/infinite_polynomial_element.py @@ -559,6 +559,41 @@ def is_nilpotent(self): """ return self._p.is_nilpotent() + def numerator(self): + r""" + Return a numerator of ``self``, computed as ``self * self.denominator()``. + + .. WARNING:: + + This is not the numerator of the rational function + defined by ``self``, which would always be ``self`` since it is a + polynomial. + + EXAMPLES:: + + sage: X. = InfinitePolynomialRing(QQ) + sage: p = 2/3*x[1] + 4/9*x[2] - 2*x[1]*x[3] + sage: num = p.numerator(); num + -18*x_3*x_1 + 4*x_2 + 6*x_1 + + TESTS:: + + sage: num.parent() + Infinite polynomial ring in x over Rational Field + + Check that :issue:`37756` is fixed:: + + sage: R. = InfinitePolynomialRing(QQ) + sage: P. = QQ[] + sage: FF = P.fraction_field() + sage: FF(a[0]) + Traceback (most recent call last): + ... + TypeError: Could not find a mapping of the passed element to this ring. + """ + P = self.parent() + return InfinitePolynomial(P, self._p.numerator()) + @cached_method def variables(self): """ @@ -577,9 +612,70 @@ def variables(self): """ if hasattr(self._p, 'variables'): - return tuple(self._p.variables()) + P = self.parent() + return tuple(InfinitePolynomial(P, v) for v in self._p.variables()) return () + def monomials(self): + """ + Return the list of monomials in ``self``. + + The returned list is decreasingly ordered by the term ordering of + ``self.parent()``. + + EXAMPLES:: + + sage: X. = InfinitePolynomialRing(QQ) + sage: p = x[1]^3 + x[2] - 2*x[1]*x[3] + sage: p.monomials() + [x_3*x_1, x_2, x_1^3] + + sage: X. = InfinitePolynomialRing(QQ, order='deglex') + sage: p = x[1]^3 + x[2] - 2*x[1]*x[3] + sage: p.monomials() + [x_1^3, x_3*x_1, x_2] + """ + P = self.parent() + return [InfinitePolynomial(P, m) for m in self._p.monomials()] + + def monomial_coefficient(self, mon): + """ + Return the base ring element that is the coefficient of ``mon`` + in ``self``. + + This function contrasts with the function :meth:`coefficient`, + which returns the coefficient of a monomial viewing this + polynomial in a polynomial ring over a base ring having fewer + variables. + + INPUT: + + - ``mon`` -- a monomial in the parent of ``self`` + + OUTPUT: coefficient in base ring + + .. SEEALSO:: + + For coefficients in a base ring of fewer variables, + look at :meth:`coefficient`. + + EXAMPLES:: + + sage: X. = InfinitePolynomialRing(QQ) + sage: f = 2*x[0]*x[2] + 3*x[1]^2 + sage: c = f.monomial_coefficient(x[1]^2); c + 3 + sage: c.parent() + Rational Field + + sage: c = f.coefficient(x[2]); c + 2*x_0 + sage: c.parent() + Infinite polynomial ring in x over Rational Field + + """ + return self._p.monomial_coefficient(mon._p) + @cached_method def max_index(self): r""" @@ -1017,42 +1113,42 @@ def coefficient(self, monomial): 2 """ + P = self.parent() if self._p == 0: - res = 0 - elif isinstance(monomial, self.__class__): - if not (self.parent().has_coerce_map_from(monomial.parent())): - res = 0 + return P.zero() + if isinstance(monomial, self.__class__): + if not P.has_coerce_map_from(monomial.parent()): + return P.zero() + if hasattr(self._p, 'variables'): + VarList = [str(X) for X in self._p.variables()] else: - if hasattr(self._p, 'variables'): - VarList = [str(X) for X in self._p.variables()] - else: - VarList = [] - if hasattr(monomial._p, 'variables'): - VarList.extend([str(X) for X in monomial._p.variables()]) - VarList = list(set(VarList)) - VarList.sort(key=self.parent().varname_key, reverse=True) - from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing - if len(VarList) == 1: - # 'xx' is guaranteed to be no variable - # name of monomial, since coercions - # were tested before - R = PolynomialRing(self._p.base_ring(), VarList + ['xx'], order=self.parent()._order) - - res = PolynomialRing(self._p.base_ring(), VarList, order=self.parent()._order)(R(self._p).coefficient(R(monomial._p))) - else: - R = PolynomialRing(self._p.base_ring(), VarList, order=self.parent()._order) - res = R(self._p).coefficient(R(monomial._p)) - elif isinstance(monomial, dict): + VarList = [] + if hasattr(monomial._p, 'variables'): + VarList.extend([str(X) for X in monomial._p.variables()]) + VarList = list(set(VarList)) + VarList.sort(key=P.varname_key, reverse=True) + from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing + if len(VarList) == 1: + # 'xx' is guaranteed to be no variable + # name of monomial, since coercions + # were tested before + R = PolynomialRing(self._p.base_ring(), VarList + ['xx'], order=P._order) + S = PolynomialRing(self._p.base_ring(), VarList, order=P._order) + res = S(R(self._p).coefficient(R(monomial._p))) + return InfinitePolynomial(P, res) + + R = PolynomialRing(self._p.base_ring(), VarList, order=P._order) + res = R(self._p).coefficient(R(monomial._p)) + return InfinitePolynomial(P, res) + + if isinstance(monomial, dict): if monomial: I = iter(monomial) K = next(I) del monomial[K] - res = self.coefficient(K).coefficient(monomial) - else: - return self - else: - raise TypeError("Objects of type %s have no coefficients in InfinitePolynomials" % (type(monomial))) - return self.parent()(res) + return self.coefficient(K).coefficient(monomial) + return self + raise TypeError("Objects of type %s have no coefficients in InfinitePolynomials" % (type(monomial))) # Essentials for Buchberger def reduce(self, I, tailreduce=False, report=None): diff --git a/src/sage/rings/polynomial/infinite_polynomial_ring.py b/src/sage/rings/polynomial/infinite_polynomial_ring.py index 75ca69f979a..99ce577b416 100644 --- a/src/sage/rings/polynomial/infinite_polynomial_ring.py +++ b/src/sage/rings/polynomial/infinite_polynomial_ring.py @@ -19,7 +19,7 @@ - ``R``, the base ring. It has to be a commutative ring, and in some applications it must even be a field -- ``names``, a finite list of generator names. Generator names must be alpha-numeric. +- ``names``, a finite list of generator names. Generator names must be alphanumeric. - ``order`` (optional string). The default order is ``'lex'`` (lexicographic). ``'deglex'`` is degree lexicographic, and ``'degrevlex'`` (degree reverse lexicographic) is possible but discouraged. @@ -279,8 +279,8 @@ class InfinitePolynomialRingFactory(UniqueFactory): """ A factory for creating infinite polynomial ring elements. It - handles making sure that they are unique as well as handling - pickling. For more details, see + makes sure that they are unique as well as handling pickling. + For more details, see :class:`~sage.structure.factory.UniqueFactory` and :mod:`~sage.rings.polynomial.infinite_polynomial_ring`. @@ -566,7 +566,7 @@ def __init__(self, parent, start): def __next__(self): """ - Return a dictionary that can be used to interprete strings in the base ring of ``self``. + Return a dictionary that can be used to interpret strings in the base ring of ``self``. EXAMPLES:: @@ -704,7 +704,7 @@ def __init__(self, R, names, order): names = ['x'] for n in names: if not (isinstance(n, str) and n.isalnum() and (not n[0].isdigit())): - raise ValueError("generator names must be alpha-numeric strings not starting with a digit, but %s is not" % n) + raise ValueError("generator names must be alphanumeric strings not starting with a digit, but %s is not" % n) if len(names) != len(set(names)): raise ValueError("generator names must be pairwise different") self._names = tuple(names) @@ -888,6 +888,15 @@ def _element_constructor_(self, x): Traceback (most recent call last): ... ValueError: cannot convert 1/3 into an element of Infinite polynomial ring in x over Integer Ring + + Check that :issue:`37756` is fixed:: + + sage: L. = QQ[] + sage: R. = InfinitePolynomialRing(QQ) + sage: M = InfinitePolynomialRing(L, names=["a"]) + sage: c = a[0] + sage: M(c) + a_0 """ from sage.rings.polynomial.infinite_polynomial_element import InfinitePolynomial # In many cases, the easiest solution is to "simply" evaluate @@ -922,7 +931,7 @@ def _element_constructor_(self, x): if isinstance(self._base, MPolynomialRing_polydict): x = sage_eval(repr(), next(self.gens_dict())) else: - x = self._base(x) + x = self._base.coerce(x) # remark: Conversion to self._P (if applicable) # is done in InfinitePolynomial() return InfinitePolynomial(self, x) diff --git a/src/sage/rings/polynomial/multi_polynomial.pyx b/src/sage/rings/polynomial/multi_polynomial.pyx index 2f4a33dc358..cf5c8c74b88 100644 --- a/src/sage/rings/polynomial/multi_polynomial.pyx +++ b/src/sage/rings/polynomial/multi_polynomial.pyx @@ -418,7 +418,7 @@ cdef class MPolynomial(CommutativePolynomial): del Z[ind] # Make polynomial ring over all variables except var. - S = R.base_ring()[tuple(Z)] + S = PolynomialRing(R.base_ring(), Z) ring = S[var] if not self: return ring(0) diff --git a/src/sage/rings/polynomial/multi_polynomial_sequence.py b/src/sage/rings/polynomial/multi_polynomial_sequence.py index b559993d39f..15ae43e070b 100644 --- a/src/sage/rings/polynomial/multi_polynomial_sequence.py +++ b/src/sage/rings/polynomial/multi_polynomial_sequence.py @@ -170,6 +170,7 @@ from sage.rings.polynomial.multi_polynomial_ideal import MPolynomialIdeal from sage.rings.polynomial.multi_polynomial_ring import is_MPolynomialRing from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing +from sage.rings.polynomial.infinite_polynomial_ring import InfinitePolynomialRing_sparse from sage.rings.quotient_ring import is_QuotientRing from sage.structure.sequence import Sequence_generic @@ -297,7 +298,7 @@ def PolynomialSequence(arg1, arg2=None, immutable=False, cr=False, cr_str=None): except ImportError: BooleanMonomialMonoid = () - is_ring = lambda r: is_MPolynomialRing(r) or isinstance(r, BooleanMonomialMonoid) or (is_QuotientRing(r) and is_MPolynomialRing(r.cover_ring())) + is_ring = lambda r: is_MPolynomialRing(r) or isinstance(r, BooleanMonomialMonoid) or (is_QuotientRing(r) and is_MPolynomialRing(r.cover_ring())) or isinstance(r, InfinitePolynomialRing_sparse) if is_ring(arg1): ring, gens = arg1, arg2 @@ -414,7 +415,7 @@ def __init__(self, parts, ring, immutable=False, cr=False, cr_str=None): 2*a*b + 2*b*c + 2*c*d - b, b^2 + 2*a*c + 2*b*d - c] """ - Sequence_generic.__init__(self, sum(parts,tuple()), ring, check=False, immutable=immutable, + Sequence_generic.__init__(self, sum(parts, tuple()), ring, check=False, immutable=immutable, cr=cr, cr_str=cr_str, use_sage_types=True) self._ring = ring self._parts = parts