From 55066e13a5da72641f1a9cc024744293d05e386b Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Sun, 10 Sep 2023 11:57:40 +0900 Subject: [PATCH 001/117] Initial implementation of integral() for lazy series; improvements to derivative. --- src/sage/data_structures/stream.py | 137 ++++++++- src/sage/rings/lazy_series.py | 478 ++++++++++++++++++++++++++++- 2 files changed, 610 insertions(+), 5 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index 8838a4f1754..b1ae06cbcee 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -3371,7 +3371,7 @@ def __getitem__(self, n): 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)) + return (ZZ.prod(range(n + 1, n + self._shift + 1)) * self._series[n + self._shift]) def __hash__(self): @@ -3433,6 +3433,141 @@ def is_nonzero(self): return self._series.is_nonzero() +class Stream_integral(Stream_unary): + """ + Operator for taking integrals of a non-exact stream. + + INPUT: + + - ``series`` -- a :class:`Stream` + - ``integration_constants`` -- a list of integration constants + - ``is_sparse`` -- boolean + """ + def __init__(self, series, integration_constants, is_sparse): + """ + Initialize ``self``. + + EXAMPLES:: + + sage: from sage.data_structures.stream import Stream_exact, Stream_integral + sage: f = Stream_exact([1, 2, 3]) + sage: f2 = Stream_integral(f, [-2], True) + sage: TestSuite(f2).run() + """ + self._shift = len(integration_constants) + self._int_consts = tuple(integration_constants) + super().__init__(series, is_sparse, False) + + @lazy_attribute + def _approximate_order(self): + """ + Compute and return the approximate order of ``self``. + + EXAMPLES:: + + sage: from sage.data_structures.stream import Stream_function, Stream_integral + sage: f = Stream_function(lambda n: (n+1)*(n+2), True, 2) + sage: h = Stream_integral(f, [0, 0], True) + sage: h._approximate_order + 0 + sage: [h[i] for i in range(10)] + [0, 0, 0, 0, 1, 1, 1, 1, 1, 1] + sage: h._approximate_order + 4 + """ + # this is generally not the true order + return min(self._series._approximate_order + self._shift, 0) + + def get_coefficient(self, n): + """ + Return the ``n``-th coefficient of ``self``. + + EXAMPLES:: + + sage: from sage.data_structures.stream import Stream_function, Stream_integral + sage: f = Stream_function(lambda n: n + 1, True, -3) + sage: [f[i] for i in range(-3, 4)] + [-2, -1, 0, 1, 2, 3, 4] + sage: f2 = Stream_integral(f, [0], True) + sage: [f2[i] for i in range(-3, 5)] + [0, 1, 1, 0, 1, 1, 1, 1] + + sage: f = Stream_function(lambda n: (n + 1)*(n+2), True, 2) + sage: [f[i] for i in range(-1, 4)] + [0, 0, 0, 12, 20] + sage: f2 = Stream_integral(f, [-1, -1, -1], True) + sage: [f2[i] for i in range(-1, 7)] + [0, -1, -1, -1/2, 0, 0, 1/5, 1/6] + """ + if 0 <= n < self._shift: + return (self._int_consts[n] / ZZ.prod(range(2, n + 1))) + return (self._series[n - self._shift] / + ZZ.prod(range(n - self._shift + 1, n + 1))) + + def __hash__(self): + """ + Return the hash of ``self``. + + EXAMPLES:: + + sage: from sage.data_structures.stream import Stream_function, Stream_integral + sage: a = Stream_function(lambda n: 2*n, False, 1) + sage: f = Stream_integral(a, [0, 1], True) + sage: g = Stream_integral(a, [0, 2], True) + sage: hash(f) == hash(f) + True + sage: hash(f) == hash(g) + False + """ + return hash((type(self), self._series, self._int_consts)) + + def __eq__(self, other): + """ + Return whether ``self`` and ``other`` are known to be equal. + + INPUT: + + - ``other`` -- a stream + + EXAMPLES:: + + sage: from sage.data_structures.stream import Stream_function, Stream_integral + sage: a = Stream_function(lambda n: 2*n, False, 1) + sage: f = Stream_integral(a, [1], True) + sage: g = Stream_integral(a, [2], True) + sage: f == g + False + sage: f == Stream_integral(a, [1], True) + True + """ + return (isinstance(other, type(self)) + and self._int_consts == other._int_consts + and self._series == other._series) + + def is_nonzero(self): + r""" + Return ``True`` if and only if this stream is known + to be non-zero. + + EXAMPLES:: + + sage: from sage.data_structures.stream import Stream_function, Stream_integral + sage: f = Stream_function(lambda n: 2*n, True, 1) + sage: f[1] + 2 + sage: f.is_nonzero() + True + sage: Stream_integral(f, [0], True).is_nonzero() + True + sage: f = Stream_function(lambda n: 0, False, 1) + sage: Stream_integral(f, [0, 0, 0], False).is_nonzero() + False + sage: Stream_integral(f, [0, 2], False).is_nonzero() + True + """ + return self._series.is_nonzero() or any(self._int_consts) + + class Stream_infinite_operator(Stream): r""" Stream defined by applying an operator an infinite number of times. diff --git a/src/sage/rings/lazy_series.py b/src/sage/rings/lazy_series.py index 8086e15f9ef..7124419c847 100644 --- a/src/sage/rings/lazy_series.py +++ b/src/sage/rings/lazy_series.py @@ -257,6 +257,7 @@ Stream_truncated, Stream_function, Stream_derivative, + Stream_integral, Stream_dirichlet_convolve, Stream_dirichlet_invert, Stream_plethysm @@ -4533,6 +4534,172 @@ def derivative(self, *args): P.is_sparse()) return P.element_class(P, coeff_stream) + def integral(self, variable=None, integration_constants=None): + r""" + Return the integral of ``self`` with respect to ``variable``. + + INPUT: + + - ``variable`` -- (optional) the variable to integrate + - ``integration_constants`` -- (optional) list of integration + 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 + 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 + the coefficients are integrated with respect to ``variable``. + + If the integration constants are not specified, they are considered + to be `0`. + + EXAMPLES:: + + sage: L. = LazyLaurentSeriesRing(QQ) + sage: f = t^-3 + 2 + 3*t + t^5 + sage: f.integral() + -1/2*t^-2 + 2*t + 3/2*t^2 + 1/6*t^6 + sage: f.integral([-2, -2]) + 1/2*t^-1 - 2 - 2*t + t^2 + 1/2*t^3 + 1/42*t^7 + sage: f.integral(t) + -1/2*t^-2 + 2*t + 3/2*t^2 + 1/6*t^6 + sage: f.integral(2) + 1/2*t^-1 + t^2 + 1/2*t^3 + 1/42*t^7 + sage: L.zero().integral() + 0 + sage: L.zero().integral([0, 1, 2, 3]) + t + t^2 + 1/2*t^3 + + We solve the ODE `f' = a f` by integrating both sides and + the recursive definition:: + + sage: R. = QQ[] + sage: L. = LazyLaurentSeriesRing(R) + sage: f = L.undefined(0) + sage: f.define((a*f).integral([C])) + sage: f + C + a*C*x + 1/2*a^2*C*x^2 + 1/6*a^3*C*x^3 + 1/24*a^4*C*x^4 + + 1/120*a^5*C*x^5 + 1/720*a^6*C*x^6 + O(x^7) + sage: C * exp(a*x) + C + a*C*x + 1/2*a^2*C*x^2 + 1/6*a^3*C*x^3 + 1/24*a^4*C*x^4 + + 1/120*a^5*C*x^5 + 1/720*a^6*C*x^6 + O(x^7) + + We can integrate both the series and coefficients:: + + sage: R. = QQ[] + sage: L. = LazyLaurentSeriesRing(R) + sage: f = (x*t^2 + y*t^-2 + z)^2; f + y^2*t^-4 + 2*y*z*t^-2 + (2*x*y + z^2) + 2*x*z*t^2 + x^2*t^4 + sage: f.integral(x) + x*y^2*t^-4 + 2*x*y*z*t^-2 + (x^2*y + x*z^2) + x^2*z*t^2 + 1/3*x^3*t^4 + sage: f.integral(t) + -1/3*y^2*t^-3 - 2*y*z*t^-1 + (2*x*y + z^2)*t + 2/3*x*z*t^3 + 1/5*x^2*t^5 + sage: f.integral(y, [x*y*z]) + -1/9*y^3*t^-3 - y^2*z*t^-1 + x*y*z + (x*y^2 + y*z^2)*t + 2/3*x*y*z*t^3 + 1/5*x^2*y*t^5 + + TESTS:: + + sage: L. = LazyLaurentSeriesRing(QQ) + sage: f = t^-2 + sage: f.integral(t, [0, 0, 0]) + Traceback (most recent call last): + ... + ValueError: cannot integrate 3 times the series t^-2 + sage: f = t^-5 + t^-2 + sage: f.integral(3) + Traceback (most recent call last): + ... + ValueError: cannot integrate 3 times the series t^-5 + t^-2 + sage: f.integral([0, 1], [0, 1]) + Traceback (most recent call last): + ... + ValueError: integration constants given twice + sage: f.integral(4, [0, 1]) + Traceback (most recent call last): + ... + ValueError: the number of integrations does not match the number of integration constants + """ + P = self.parent() + zero = P.base_ring().zero() + if variable is None: + if integration_constants is None: + integration_constants = [zero] + elif variable != P.gen(): + if isinstance(variable, (list, tuple)): + if integration_constants is not None: + raise ValueError("integration constants given twice") + integration_constants = tuple(variable) + variable = None + elif variable in ZZ and ZZ(variable) >= 0: + if integration_constants is None: + integration_constants = [zero] * ZZ(variable) + elif ZZ(variable) != len(integration_constants): + raise ValueError("the number of integrations does not match" + " the number of integration constants") + variable = None + if integration_constants is None: + integration_constants = [] + else: + if integration_constants is None: + integration_constants = [zero] + variable = None + + nints = len(integration_constants) + + coeff_stream = self._coeff_stream + if isinstance(coeff_stream, Stream_zero): + if any(integration_constants): + coeff_stream = Stream_exact([c / ZZ.prod(k for k in range(1, i+1)) + for i, c in enumerate(integration_constants)], + order=0, + constant=zero) + return P.element_class(P, coeff_stream) + return self + + if (isinstance(coeff_stream, Stream_exact) and not coeff_stream._constant): + coeffs = [c / ZZ.prod(k for k in range(1, i+1)) + for i, c in enumerate(integration_constants)] + if coeff_stream._approximate_order < 0: + ic = coeff_stream._initial_coefficients + ao = coeff_stream._approximate_order + if nints > -ao or any(ic[-ao-nints:-ao]): + raise ValueError(f"cannot integrate {nints} times the series {self}") + if variable is not None: + coeffs = [c.integral(variable) / ZZ.prod(i+k for k in range(1, nints+1)) + for i, c in enumerate(ic[:-ao-nints], ao)] + coeffs + else: + coeffs = [c / ZZ.prod(i+k for k in range(1, nints+1)) + for i, c in enumerate(ic[:-ao-nints], ao)] + coeffs + + ic = ic[-ao:] + val = ao + nints + ao = 0 + else: + coeffs += [zero] * coeff_stream._approximate_order + ic = coeff_stream._initial_coefficients + val = 0 + ao = coeff_stream._approximate_order + if variable: + coeffs += [c.integral(variable) / ZZ.prod(i+k for k in range(1, nints+1)) + for i, c in enumerate(ic, ao)] + else: + coeffs += [c / ZZ.prod(i+k for k in range(1, nints+1)) + for i, c in enumerate(ic, ao)] + if not any(coeffs): + return P.zero() + coeff_stream = Stream_exact(coeffs, order=val, constant=zero) + return P.element_class(P, coeff_stream) + + if nints: + coeff_stream = Stream_integral(coeff_stream, integration_constants, P.is_sparse()) + + if variable is not None: + coeff_stream = Stream_map_coefficients(coeff_stream, + lambda c: c.integral(variable), + P.is_sparse()) + return P.element_class(P, coeff_stream) + def approximate_series(self, prec, name=None): r""" Return the Laurent series with absolute precision ``prec`` approximated @@ -5301,14 +5468,18 @@ def derivative(self, *args): sage: T. = LazyPowerSeriesRing(ZZ) sage: z.derivative() 1 - sage: (1+z+z^2).derivative(3) + sage: (1 + z + z^2).derivative(3) 0 - sage: (1/(1-z)).derivative() + sage: (z^2 + z^4 + z^10).derivative(3) + 24*z + 720*z^7 + 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: T([1, 1, 1], constant=4).derivative() + 1 + 2*z + 12*z^2 + 16*z^3 + 20*z^4 + 24*z^5 + 28*z^6 + O(z^7) sage: R. = QQ[] sage: L. = LazyPowerSeriesRing(R) - sage: f = 1/(1-q*x+y); f + 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) @@ -5322,6 +5493,53 @@ def derivative(self, *args): + (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 + Multivariate:: + + sage: L. = LazyPowerSeriesRing(QQ) + sage: f = (x + y^2 + z)^3; f + (x^3+3*x^2*z+3*x*z^2+z^3) + (3*x^2*y^2+6*x*y^2*z+3*y^2*z^2) + (3*x*y^4+3*y^4*z) + y^6 + sage: f.derivative(x) + (3*x^2+6*x*z+3*z^2) + (6*x*y^2+6*y^2*z) + 3*y^4 + sage: f.derivative(y, 5) + 720*y + sage: f.derivative(z, 5) + 0 + sage: f.derivative(x, y, z) + 12*y + + sage: f = (1 + x + y^2 + z)^-1 + sage: f.derivative(x) + -1 + (2*x+2*z) + (-3*x^2+2*y^2-6*x*z-3*z^2) + ... + O(x,y,z)^6 + sage: f.derivative(y, 2) + -2 + (4*x+4*z) + (-6*x^2+12*y^2-12*x*z-6*z^2) + ... + O(x,y,z)^5 + sage: f.derivative(x, y) + 4*y + (-12*x*y-12*y*z) + (24*x^2*y-12*y^3+48*x*y*z+24*y*z^2) + + (-40*x^3*y+48*x*y^3-120*x^2*y*z+48*y^3*z-120*x*y*z^2-40*y*z^3) + O(x,y,z)^5 + sage: f.derivative(x, y, z) + (-12*y) + (48*x*y+48*y*z) + (-120*x^2*y+48*y^3-240*x*y*z-120*y*z^2) + O(x,y,z)^4 + + sage: R. = QQ[] + sage: L. = LazyPowerSeriesRing(R) + sage: f = ((t^2-3)*x + t*y^2 - t*z)^2 + sage: f.derivative(t,x,t,y) + 24*t*y + sage: f.derivative(t, 2) + ((12*t^2-12)*x^2+(-12*t)*x*z+2*z^2) + (12*t*x*y^2+(-4)*y^2*z) + 2*y^4 + sage: f.derivative(z, t) + ((-6*t^2+6)*x+4*t*z) + ((-4*t)*y^2) + sage: f.derivative(t, 10) + 0 + + sage: f = (1 + t*(x + y + z))^-1 + sage: f.derivative(x, t, y) + 4*t + ((-18*t^2)*x+(-18*t^2)*y+(-18*t^2)*z) + + (48*t^3*x^2+96*t^3*x*y+48*t^3*y^2+96*t^3*x*z+96*t^3*y*z+48*t^3*z^2) + + ... + O(x,y,z)^5 + sage: f.derivative(t, 2) + (2*x^2+4*x*y+2*y^2+4*x*z+4*y*z+2*z^2) + ... + O(x,y,z)^7 + sage: f.derivative(x, y, z, t) + (-18*t^2) + (96*t^3*x+96*t^3*y+96*t^3*z) + ... + O(x,y,z)^4 + TESTS: Check that :issue:`36154` is fixed:: @@ -5341,7 +5559,7 @@ def derivative(self, *args): if x is None: order += 1 elif x in V: - gen_vars.append(x) + gen_vars.append(x._coeff_stream[1]) else: vars.append(x) @@ -5357,6 +5575,16 @@ def derivative(self, *args): if P._arity > 1: v = gen_vars + vars d = -len(gen_vars) + + if isinstance(coeff_stream, Stream_exact): # the constant should be 0 + ao = coeff_stream._approximate_order + val = max(ao + d, 0) + coeffs = [R(c).derivative(v) for c in coeff_stream._initial_coefficients[val-(ao+d):]] + if any(coeffs): + coeff_stream = Stream_exact(coeffs, order=val, constant=coeff_stream._constant) + return P.element_class(P, coeff_stream) + return P.zero() + coeff_stream = Stream_map_coefficients(coeff_stream, lambda c: R(c).derivative(v), P.is_sparse()) @@ -5390,6 +5618,248 @@ def derivative(self, *args): P.is_sparse()) return P.element_class(P, coeff_stream) + def integral(self, variable=None, integration_constants=None): + r""" + Return the integral of ``self`` with respect to ``variable``. + + INPUT: + + - ``variable`` -- (optional) the variable to integrate + - ``integration_constants`` -- (optional) list of integration + constants for the integrals of ``self`` (the last constant + corresponds to the first integral) + + For multivariable series, then only ``variable`` should be + 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 + 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 + to ``variable``. If the integration constants are not specified, + they are considered to be `0`. + + EXAMPLES:: + + sage: L. = LazyPowerSeriesRing(QQ) + sage: f = 2 + 3*t + t^5 + sage: f.integral() + 2*t + 3/2*t^2 + 1/6*t^6 + sage: f.integral([-2, -2]) + -2 - 2*t + t^2 + 1/2*t^3 + 1/42*t^7 + sage: f.integral(t) + 2*t + 3/2*t^2 + 1/6*t^6 + sage: f.integral(2) + t^2 + 1/2*t^3 + 1/42*t^7 + sage: (t^3 + t^5).integral() + 1/4*t^4 + 1/6*t^6 + sage: L.zero().integral() + 0 + sage: L.zero().integral([0, 1, 2, 3]) + t + t^2 + 1/2*t^3 + sage: L([1, 2 ,3], constant=4).integral() + t + t^2 + t^3 + t^4 + 4/5*t^5 + 2/3*t^6 + O(t^7) + + We solve the ODE `f'' - f' - 2 f = 0` by solving for `f''`, then + integrating and applying a recursive definition:: + + sage: R. = QQ[] + sage: L. = LazyPowerSeriesRing(R) + sage: f = L.undefined() + sage: f.define((f.derivative() + 2*f).integral([C, D])) + sage: f + C + D*x + ((C+1/2*D)*x^2) + ((1/3*C+1/2*D)*x^3) + + ((1/4*C+5/24*D)*x^4) + ((1/12*C+11/120*D)*x^5) + + ((11/360*C+7/240*D)*x^6) + O(x^7) + sage: f.derivative(2) - f.derivative() - 2*f + O(x^7) + + We compare this with the answer we get from the + characteristic polynomial:: + + sage: g = C * exp(-x) + D * exp(2*x); g + (C+D) + ((-C+2*D)*x) + ((1/2*C+2*D)*x^2) + ((-1/6*C+4/3*D)*x^3) + + ((1/24*C+2/3*D)*x^4) + ((-1/120*C+4/15*D)*x^5) + + ((1/720*C+4/45*D)*x^6) + O(x^7) + sage: g.derivative(2) - g.derivative() - 2*g + O(x^7) + + Note that ``C`` and ``D`` are playing different roles, so we need + to perform a substitution to the coefficients of ``f`` to recover + the solution ``g``:: + + sage: fp = f.map_coefficients(lambda c: c(C=C+D, D=2*D-C)); fp + (C+D) + ((-C+2*D)*x) + ((1/2*C+2*D)*x^2) + ((-1/6*C+4/3*D)*x^3) + + ((1/24*C+2/3*D)*x^4) + ((-1/120*C+4/15*D)*x^5) + + ((1/720*C+4/45*D)*x^6) + O(x^7) + sage: fp - g + O(x^7) + + We can integrate both the series and coefficients:: + + sage: R. = QQ[] + sage: L. = LazyPowerSeriesRing(R) + sage: f = (x*t^2 + y*t + z)^2; f + z^2 + 2*y*z*t + ((y^2+2*x*z)*t^2) + 2*x*y*t^3 + x^2*t^4 + sage: f.integral(x) + x*z^2 + 2*x*y*z*t + ((x*y^2+x^2*z)*t^2) + x^2*y*t^3 + 1/3*x^3*t^4 + sage: f.integral(t) + z^2*t + y*z*t^2 + ((1/3*y^2+2/3*x*z)*t^3) + 1/2*x*y*t^4 + 1/5*x^2*t^5 + sage: f.integral(y, [x*y*z]) + x*y*z + y*z^2*t + 1/2*y^2*z*t^2 + ((1/9*y^3+2/3*x*y*z)*t^3) + 1/4*x*y^2*t^4 + 1/5*x^2*y*t^5 + + We can integrate multivariate power series:: + + sage: R. = QQ[] + sage: L. = LazyPowerSeriesRing(R) + sage: f = ((t^2 + t) - t * y^2 + t^2 * (y + z))^2; f + (t^4+2*t^3+t^2) + ((2*t^4+2*t^3)*y+(2*t^4+2*t^3)*z) + + ((t^4-2*t^3-2*t^2)*y^2+2*t^4*y*z+t^4*z^2) + + ((-2*t^3)*y^3+(-2*t^3)*y^2*z) + t^2*y^4 + sage: g = f.integral(x); g + ((t^4+2*t^3+t^2)*x) + ((2*t^4+2*t^3)*x*y+(2*t^4+2*t^3)*x*z) + + ((t^4-2*t^3-2*t^2)*x*y^2+2*t^4*x*y*z+t^4*x*z^2) + + ((-2*t^3)*x*y^3+(-2*t^3)*x*y^2*z) + t^2*x*y^4 + sage: g[0] + 0 + sage: g[1] + (t^4 + 2*t^3 + t^2)*x + sage: g[2] + (2*t^4 + 2*t^3)*x*y + (2*t^4 + 2*t^3)*x*z + sage: f.integral(z) + ((t^4+2*t^3+t^2)*z) + ((2*t^4+2*t^3)*y*z+(t^4+t^3)*z^2) + + ((t^4-2*t^3-2*t^2)*y^2*z+t^4*y*z^2+1/3*t^4*z^3) + + ((-2*t^3)*y^3*z+(-t^3)*y^2*z^2) + t^2*y^4*z + sage: f.integral(t) + (1/5*t^5+1/2*t^4+1/3*t^3) + ((2/5*t^5+1/2*t^4)*y+(2/5*t^5+1/2*t^4)*z) + + ((1/5*t^5-1/2*t^4-2/3*t^3)*y^2+2/5*t^5*y*z+1/5*t^5*z^2) + + ((-1/2*t^4)*y^3+(-1/2*t^4)*y^2*z) + 1/3*t^3*y^4 + + sage: L. = LazyPowerSeriesRing(QQ) + sage: (x + y - z^2).integral(z) + (x*z+y*z) + (-1/3*z^3) + + TESTS:: + + sage: L. = LazyPowerSeriesRing(QQ) + sage: f = t^2 + sage: f.integral([0, 1], [0, 1]) + Traceback (most recent call last): + ... + ValueError: integration constants given twice + sage: f.integral(4, [0, 1]) + Traceback (most recent call last): + ... + ValueError: the number of integrations does not match the number of integration constants + + sage: L. = LazyPowerSeriesRing(QQ) + sage: x.integral(y, [2]) + Traceback (most recent call last): + ... + ValueError: integration constants must not be given for multivariate series + sage: x.integral() + Traceback (most recent call last): + ... + ValueError: the integration variable must be specified + """ + P = self.parent() + coeff_stream = self._coeff_stream + R = P._laurent_poly_ring + + if P._arity > 1: + if integration_constants is not None: + raise ValueError("integration constants must not be given for multivariate series") + if variable is None: + raise ValueError("the integration variable must be specified") + + if isinstance(coeff_stream, Stream_zero): + return self + + if variable in P.gens(): + variable = variable._coeff_stream[1] + shift = 1 + else: + shift = 0 + + if isinstance(coeff_stream, Stream_exact): # the constant should be 0 + ao = coeff_stream._approximate_order + coeffs = [R(c).integral(variable) for c in coeff_stream._initial_coefficients] + coeff_stream = Stream_exact(coeffs, order=ao+shift, constant=coeff_stream._constant) + return P.element_class(P, coeff_stream) + + coeff_stream = Stream_map_coefficients(coeff_stream, + lambda c: c.integral(variable), + P.is_sparse()) + if shift: + coeff_stream = Stream_shift(coeff_stream, 1) + return P.element_class(P, coeff_stream) + + # the univariate case + + zero = P.base_ring().zero() + # This is copied from the LazyLaurentSeries.integral + if variable is None: + if integration_constants is None: + integration_constants = [zero] + elif variable != P.gen(): + if isinstance(variable, (list, tuple)): + if integration_constants is not None: + raise ValueError("integration constants given twice") + integration_constants = tuple(variable) + variable = None + elif variable in ZZ and ZZ(variable) >= 0: + if integration_constants is None: + integration_constants = [zero] * ZZ(variable) + elif ZZ(variable) != len(integration_constants): + raise ValueError("the number of integrations does not match" + " the number of integration constants") + variable = None + if integration_constants is None: + integration_constants = [] + else: + if integration_constants is None: + integration_constants = [zero] + variable = None + + nints = len(integration_constants) + + if isinstance(coeff_stream, Stream_zero): + if any(integration_constants): + coeff_stream = Stream_exact([c / ZZ.prod(k for k in range(1, i+1)) + for i, c in enumerate(integration_constants)], + order=0, + constant=zero) + return P.element_class(P, coeff_stream) + + return self + + if (isinstance(coeff_stream, Stream_exact) and not coeff_stream._constant): + coeffs = [c / ZZ.prod(k for k in range(1, i+1)) + for i, c in enumerate(integration_constants)] + coeffs += [zero] * coeff_stream._approximate_order + ic = coeff_stream._initial_coefficients + ao = coeff_stream._approximate_order + if variable: + coeffs += [c.integral(variable) / ZZ.prod(i+k for k in range(1, nints+1)) + for i, c in enumerate(ic, ao)] + else: + coeffs += [c / ZZ.prod(i+k for k in range(1, nints+1)) + for i, c in enumerate(ic, ao)] + if not any(coeffs): + return P.zero() + coeff_stream = Stream_exact(coeffs, order=0, constant=zero) + return P.element_class(P, coeff_stream) + + if nints: + coeff_stream = Stream_integral(coeff_stream, integration_constants, P.is_sparse()) + + if variable is not None: + coeff_stream = Stream_map_coefficients(coeff_stream, + lambda c: c.integral(variable), + P.is_sparse()) + return P.element_class(P, coeff_stream) + def _format_series(self, formatter, format_strings=False): """ Return nonzero ``self`` formatted by ``formatter``. From 419701c10f4c10c8bc072b3655b3d39c5501ae14 Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Tue, 10 Oct 2023 12:20:10 +0900 Subject: [PATCH 002/117] Adding functional equation and taylor series constructions. --- src/sage/data_structures/stream.py | 338 ++++++++++++++++++++++++++++- src/sage/rings/lazy_series_ring.py | 99 ++++++++- 2 files changed, 434 insertions(+), 3 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index b1ae06cbcee..16224579f4f 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -103,6 +103,7 @@ 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.misc.cachefunc import cached_method +from copy import copy lazy_import('sage.combinat.sf.sfa', ['_variables_recursive', '_raise_variables']) @@ -213,6 +214,14 @@ def is_uninitialized(self): """ return False + def replace(self, stream, sub): + """ + Return ``self`` except with ``stream`` replaced by ``sub``. + + The default is to return ``self``. + """ + return self + class Stream_inexact(Stream): """ @@ -596,7 +605,6 @@ class Stream_exact(Stream): The convention for ``order`` is different to the one in :class:`sage.rings.lazy_series_ring.LazySeriesRing`, where the input is shifted to have the prescribed order. - """ def __init__(self, initial_coefficients, constant=None, degree=None, order=None): """ @@ -1038,6 +1046,142 @@ def __eq__(self, other): return isinstance(other, type(self)) and self.get_coefficient == other.get_coefficient +class Stream_taylor(Stream_inexact): + r""" + Class that returns a stream for the Taylor series of a function. + + INPUT: + + - ``function`` -- a function that has a ``derivative`` method and takes + a single input + - ``is_sparse`` -- boolean; specifies whether the stream is sparse + + EXAMPLES:: + + sage: from sage.data_structures.stream import Stream_taylor + sage: g(x) = sin(x) + sage: f = Stream_taylor(g, False) + sage: f[3] + -1/6 + sage: [f[i] for i in range(10)] + [0, 1, 0, -1/6, 0, 1/120, 0, -1/5040, 0, 1/362880] + + sage: g(y) = cos(y) + sage: f = Stream_taylor(g, False) + sage: n = f.iterate_coefficients() + sage: [next(n) for _ in range(10)] + [1, 0, -1/2, 0, 1/24, 0, -1/720, 0, 1/40320, 0] + + sage: g(z) = 1 / (1 - 2*z) + sage: f = Stream_taylor(g, True) + sage: [f[i] for i in range(4)] + [1, 2, 4, 8] + """ + def __init__(self, function, is_sparse): + """ + Initialize. + + TESTS:: + + sage: from sage.data_structures.stream import Stream_taylor + sage: f = Stream_taylor(polygen(QQ, 'x')^3, False) + sage: TestSuite(f).run(skip="_test_pickling") + """ + self._func = function + super().__init__(is_sparse, False) + self._approximate_order = 0 + + def __hash__(self): + """ + Return the hash of ``self``. + + EXAMPLES:: + + sage: from sage.data_structures.stream import Stream_taylor + sage: x = polygen(QQ, 'x') + sage: f = Stream_taylor(x^3 + x - 2, True) + sage: g = Stream_taylor(x^3 + x - 2, False) + sage: hash(f) == hash(g) + True + """ + # We don't hash the function as it might not be hashable. + return hash(type(self)) + + def __eq__(self, other): + r""" + Return whether ``self`` and ``other`` are known to be equal. + + INPUT: + + - ``other`` -- a stream + + EXAMPLES:: + + sage: from sage.data_structures.stream import Stream_taylor + sage: x = polygen(QQ, 'x') + sage: fun = x^2 + sage: f = Stream_taylor(x^2, True) + sage: g = Stream_taylor(x^2, False) + sage: h = Stream_taylor((x^3 + x^2) / (x + 1), False) + sage: f == g + True + sage: f == h + True + + sage: F(y) = cos(y)^2 + sin(y)^2 + y + sage: G(y) = y + 1 + sage: f = Stream_taylor(F, True) + sage: g = Stream_taylor(G, False) + sage: f == g + True + """ + # The bool call is needed when passing functions in SR + return isinstance(other, type(self)) and bool(self._func == other._func) + + def get_coefficient(self, n): + """ + Return the ``n``-th coefficient of ``self``. + + INPUT: + + - ``n`` -- integer; the degree for the coefficient + + EXAMPLES:: + + sage: from sage.data_structures.stream import Stream_taylor + sage: g(x) = exp(x) + sage: f = Stream_taylor(g, True) + sage: f.get_coefficient(5) + 1/120 + """ + if n == 0: + return self._func(ZZ.zero()) + from sage.functions.other import factorial + return self._func.derivative(n)(ZZ.zero()) / factorial(n) + + def iterate_coefficients(self): + """ + A generator for the coefficients of ``self``. + + EXAMPLES:: + + sage: from sage.data_structures.stream import Stream_taylor + sage: x = polygen(QQ, 'x') + sage: f = Stream_taylor(x^3, False) + sage: it = f.iterate_coefficients() + sage: [next(it) for _ in range(10)] + [0, 0, 0, 1, 0, 0, 0, 0, 0, 0] + """ + cur = self._func + n = 0 + denom = 1 + while True: + yield cur(ZZ.zero()) / denom + cur = cur.derivative() + n += 1 + denom *= n + + class Stream_uninitialized(Stream_inexact): r""" Coefficient stream for an uninitialized series. @@ -1134,6 +1278,128 @@ def is_uninitialized(self): return result +class Stream_functional_equation(Stream_inexact): + r""" + Coefficient stream defined by a functional equation `F = 0`. + + INPUT: + + - ``approximate_order`` -- integer; a lower bound for the order + of the stream + - ``F`` -- the stream for the equation using ``uninitialized`` + - ``uninitialized`` -- the uninitialized stream + - ``initial_values`` -- the initial values + + Instances of this class are always dense. + + EXAMPLES:: + """ + def __init__(self, approximate_order, F, uninitialized, initial_values, true_order=False): + """ + Initialize ``self``. + + TESTS:: + + sage: from sage.data_structures.stream import Stream_uninitialized + sage: C = Stream_uninitialized(0) + sage: TestSuite(C).run(skip="_test_pickling") + """ + if approximate_order is None: + raise ValueError("the valuation must be specified for undefined series") + if initial_values is None: + initial_values = [] + self._start = approximate_order + for i, val in enumerate(initial_values): + if val: + approximate_order += i + true_order = True + break + else: + approximate_order += len(initial_values) + super().__init__(False, true_order) + self._F = F + self._initial_values = initial_values + self._approximate_order = approximate_order + self._uninitialized = uninitialized + self._uninitialized._approximate_order = approximate_order + self._uninitialized._target = self + + def iterate_coefficients(self): + """ + A generator for the coefficients of ``self``. + + EXAMPLES:: + + sage: from sage.data_structures.stream import Stream_uninitialized + sage: from sage.data_structures.stream import Stream_exact + sage: z = Stream_exact([1], order=1) + sage: C = Stream_uninitialized(0) + sage: C._target + sage: C._target = z + sage: n = C.iterate_coefficients() + sage: [next(n) for _ in range(10)] + [0, 1, 0, 0, 0, 0, 0, 0, 0, 0] + """ + yield from self._initial_values + + from sage.rings.polynomial.infinite_polynomial_ring import InfinitePolynomialRing + P = InfinitePolynomialRing(ZZ, 'x') + x = P.gen() + PFF = P.fraction_field() + + def get_coeff(n): + # Check against the initial values first + i = n - self._start + if n < len(self._initial_values): + return P(self._initial_values[n]) + + # We are always a dense implementation + # Check to see if we have already computed the value + if n < self._approximate_order: + return P.zero() + if self._true_order: + i = n - self._approximate_order + if i < len(self._cache): + return P(self._cache[i]) + + return x[i] + + sf = Stream_function(get_coeff, is_sparse=False, approximate_order=self._start, true_order=True) + self._F = self._F.replace(self._uninitialized, sf) + + n = self._start + offset = len(self._initial_values) - self._start + while True: + coeff = self._F[n] + if coeff.parent() is PFF: + coeff = coeff.numerator() + + V = coeff.variables() + if not V: + if coeff: + raise ValueError(f"no solution from degree {n} as {coeff} == 0") + yield ZZ.zero() + n += 1 + continue + + if len(V) > 1: + # Substitute for known variables + coeff = coeff.subs({x[i]: val for i, val in enumerate(sf._cache)}) + V = coeff.variables() + if len(V) > 1: + raise ValueError(f"unable to determine a unique solution from degree {n}") + + # single variable to solve for + hc = coeff.homogeneous_components() + if not set(hc).issubset([0,1]): + raise ValueError(f"unable to determine a unique solution from degree {n}") + val = -hc.get(0, P.zero()).lc() / hc[1].lc() + # Update the cache + sf._cache[n + offset] = val + yield val + n += 1 + + class Stream_unary(Stream_inexact): r""" Base class for unary operators on coefficient streams. @@ -1225,6 +1491,26 @@ def is_uninitialized(self): """ return self._series.is_uninitialized() + def replace(self, stream, sub): + r""" + Return ``self`` except with ``stream`` replaced by ``sub``. + + .. WARNING:: + + This does not update the approximate order or the cache. + """ + if self._series == stream: + ret = copy(self) + ret._series = sub + else: + temp = self._series.replace(stream, sub) + if temp == self._series: + ret = self + else: + ret = copy(self) + ret._series = temp + return ret + class Stream_binary(Stream_inexact): """ @@ -1247,7 +1533,6 @@ class Stream_binary(Stream_inexact): sage: [h[i] for i in range(10)] [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] """ - def __init__(self, left, right, is_sparse): """ Initialize ``self``. @@ -1331,6 +1616,35 @@ def is_uninitialized(self): """ return self._left.is_uninitialized() or self._right.is_uninitialized() + def replace(self, stream, sub): + r""" + Return ``self`` except with ``stream`` replaced by ``sub``. + + .. WARNING:: + + This does not update the approximate order or the cache. + """ + if self._left == stream: + ret = copy(self) + ret._left = sub + else: + temp = self._left.replace(stream, sub) + if temp == self._left: + ret = self + else: + ret = copy(self) + ret._left = temp + # It is possible that both the left and right are the same stream + if self._right == stream: + ret = copy(ret) + ret._right = sub + else: + temp = ret._right.replace(stream, sub) + if not (temp == self._right): + ret = copy(ret) + ret._right = temp + return ret + class Stream_binaryCommutative(Stream_binary): r""" @@ -3056,6 +3370,26 @@ def is_uninitialized(self): """ return self._series.is_uninitialized() + def replace(self, stream, sub): + r""" + Return ``self`` except with ``stream`` replaced by ``sub``. + + .. WARNING:: + + This does not update the approximate order. + """ + if self._series == stream: + ret = copy(self) + ret._series = sub + else: + temp = self._series.replace(stream, sub) + if temp == self._series: + ret = self + else: + ret = copy(self) + ret._series = temp + return ret + class Stream_truncated(Stream_unary): """ diff --git a/src/sage/rings/lazy_series_ring.py b/src/sage/rings/lazy_series_ring.py index 07e08938696..4a3e3afb1cb 100644 --- a/src/sage/rings/lazy_series_ring.py +++ b/src/sage/rings/lazy_series_ring.py @@ -87,7 +87,10 @@ Stream_function, Stream_iterator, Stream_exact, - Stream_uninitialized + Stream_uninitialized, + Stream_sub, + Stream_taylor, + Stream_functional_equation ) from types import GeneratorType @@ -1792,6 +1795,43 @@ def residue_field(self): raise TypeError("the base ring is not a field") return R + def taylor(self, f): + r""" + Return the Taylor expansion of the function ``f``. + + INPUT: + + - ``f`` -- a function such that one of the following works: + + * the substitution `f(z)`, where `z` is generator of ``self`` + * `f` is a function of a single variable with no poles and has a + ``derivative`` method + """ + try: + return f(self.gen()) + except (ValueError, TypeError): + pass + stream = Stream_taylor(f, self.is_sparse()) + return self.element_class(self, stream) + + def functional_equation(self, left, right, series, initial_values): + r""" + Define the lazy undefined ``series`` that solves the functional + equation ``left == right`` with ``initial_values``. + """ + if not isinstance(series._coeff_stream, Stream_uninitialized) or series._coeff_stream._target is not None: + raise ValueError("series already defined") + + left = self(left) + right = self(right) + cs = series._coeff_stream + ao = cs._approximate_order + R = self.base_ring() + initial_values = [R(val) for val in initial_values] + F = Stream_sub(left._coeff_stream, right._coeff_stream, self.is_sparse()) + ret = Stream_functional_equation(ao, F, cs, initial_values) + series._coeff_stream = ret + # === special functions === def q_pochhammer(self, q=None): @@ -2503,6 +2543,63 @@ def some_elements(self): elts.extend([(z-3)*(2+z)**2, (1 - 2*z**3)/(1 - z + 3*z**2), self(lambda n: sum_gens**n)]) return elts + def taylor(self, f): + r""" + Return the Taylor expansion of the function ``f``. + + INPUT: + + - ``f`` -- a function such that one of the following works: + + * the substitution `f(z_1, \ldots, z_n)`, where `(z_1, \ldots, z_n)` + are the generators of ``self`` + * `f` is a function of a single variable with no poles and has a + ``derivative`` method + """ + try: + return f(*self.gens()) + except (ValueError, TypeError): + pass + if self._arity != 1: + raise NotImplementedError("only implemented generically for one variable") + stream = Stream_taylor(f, self.is_sparse()) + return self.element_class(self, stream) + + def functional_equation(self, left, right, series, initial_values): + r""" + Define the lazy undefined ``series`` that solves the functional + equation ``left == right`` with ``initial_values``. + + EXAMPLES:: + + sage: L. = LazyPowerSeriesRing(QQ) + sage: f = L.undefined(0) + sage: F = diff(f, 2) + sage: L.functional_equation(-F, f, f, [1, 0]) + 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) + """ + if self._arity != 1: + raise NotImplementedError("only implemented for one variable") + + if not isinstance(series._coeff_stream, Stream_uninitialized) or series._coeff_stream._target is not None: + raise ValueError("series already defined") + + left = self(left) + right = self(right) + cs = series._coeff_stream + ao = cs._approximate_order + R = self.base_ring() + initial_values = [R(val) for val in initial_values] + F = Stream_sub(left._coeff_stream, right._coeff_stream, self.is_sparse()) + ret = Stream_functional_equation(ao, F, cs, initial_values) + series._coeff_stream = ret + + ###################################################################### From b9790074d73d1d89cf29df5fd7ff87713582b36b Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Thu, 12 Oct 2023 18:11:31 +0900 Subject: [PATCH 003/117] Adding more examples, fixing bugs, streamlining the implementation. --- src/sage/data_structures/stream.py | 64 +++++++++++++++--------------- src/sage/rings/lazy_series_ring.py | 57 +++++++++++++++++++++++++- 2 files changed, 87 insertions(+), 34 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index 16224579f4f..e5de010fbad 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -1289,12 +1289,13 @@ class Stream_functional_equation(Stream_inexact): - ``F`` -- the stream for the equation using ``uninitialized`` - ``uninitialized`` -- the uninitialized stream - ``initial_values`` -- the initial values + - ``R`` -- the base ring Instances of this class are always dense. EXAMPLES:: """ - def __init__(self, approximate_order, F, uninitialized, initial_values, true_order=False): + def __init__(self, approximate_order, F, uninitialized, initial_values, R, true_order=False): """ Initialize ``self``. @@ -1313,11 +1314,14 @@ def __init__(self, approximate_order, F, uninitialized, initial_values, true_ord if val: approximate_order += i true_order = True + initial_values = initial_values[i:] break else: approximate_order += len(initial_values) + initial_values = [] super().__init__(False, true_order) self._F = F + self._base = R self._initial_values = initial_values self._approximate_order = approximate_order self._uninitialized = uninitialized @@ -1343,59 +1347,55 @@ def iterate_coefficients(self): yield from self._initial_values from sage.rings.polynomial.infinite_polynomial_ring import InfinitePolynomialRing - P = InfinitePolynomialRing(ZZ, 'x') + P = InfinitePolynomialRing(self._base, 'x') x = P.gen() PFF = P.fraction_field() + offset = self._approximate_order def get_coeff(n): - # Check against the initial values first - i = n - self._start - if n < len(self._initial_values): - return P(self._initial_values[n]) - - # We are always a dense implementation - # Check to see if we have already computed the value - if n < self._approximate_order: - return P.zero() - if self._true_order: - i = n - self._approximate_order - if i < len(self._cache): - return P(self._cache[i]) - - return x[i] - - sf = Stream_function(get_coeff, is_sparse=False, approximate_order=self._start, true_order=True) + return x[n-offset] + + sf = Stream_function(get_coeff, is_sparse=False, approximate_order=offset, true_order=True) self._F = self._F.replace(self._uninitialized, sf) - n = self._start - offset = len(self._initial_values) - self._start + n = self._F._approximate_order + data = list(self._initial_values) while True: coeff = self._F[n] if coeff.parent() is PFF: coeff = coeff.numerator() - + else: + coeff = P(coeff) V = coeff.variables() - if not V: - if coeff: - raise ValueError(f"no solution from degree {n} as {coeff} == 0") - yield ZZ.zero() - n += 1 - continue if len(V) > 1: # Substitute for known variables - coeff = coeff.subs({x[i]: val for i, val in enumerate(sf._cache)}) + coeff = coeff.subs({str(x[i]): val for i, val in enumerate(data)}) V = coeff.variables() if len(V) > 1: - raise ValueError(f"unable to determine a unique solution from degree {n}") + raise ValueError(f"unable to determine a unique solution in degree {n}") + + if not V: + if coeff: + raise ValueError(f"no solution in degree {n} as {coeff} == 0") + i = n - self._start + #print(i, len(data)) + if i < len(data): + # We already know this coefficient + yield data[n - self._start] + else: + yield self._base.zero() + data.append(self._base.zero()) + n += 1 + continue # single variable to solve for hc = coeff.homogeneous_components() if not set(hc).issubset([0,1]): - raise ValueError(f"unable to determine a unique solution from degree {n}") + raise ValueError(f"unable to determine a unique solution in degree {n}") val = -hc.get(0, P.zero()).lc() / hc[1].lc() # Update the cache - sf._cache[n + offset] = val + data.append(val) yield val n += 1 diff --git a/src/sage/rings/lazy_series_ring.py b/src/sage/rings/lazy_series_ring.py index 4a3e3afb1cb..36f9f564b05 100644 --- a/src/sage/rings/lazy_series_ring.py +++ b/src/sage/rings/lazy_series_ring.py @@ -1818,6 +1818,19 @@ def functional_equation(self, left, right, series, initial_values): r""" Define the lazy undefined ``series`` that solves the functional equation ``left == right`` with ``initial_values``. + + EXAMPLES:: + + sage: L. = LazyLaurentSeriesRing(QQ) + sage: f = L.undefined(-1) + sage: L.functional_equation(f, 2+z*f(z^2), f, [5]) + sage: f + 5*z^-1 + 5 + 2*z + 2*z^2 + 2*z^4 + O(z^6) + + sage: f = L.undefined(-2) + sage: L.functional_equation(f, 2+z*f(z^2), f, [5]) + sage: f + """ if not isinstance(series._coeff_stream, Stream_uninitialized) or series._coeff_stream._target is not None: raise ValueError("series already defined") @@ -1829,7 +1842,7 @@ def functional_equation(self, left, right, series, initial_values): R = self.base_ring() initial_values = [R(val) for val in initial_values] F = Stream_sub(left._coeff_stream, right._coeff_stream, self.is_sparse()) - ret = Stream_functional_equation(ao, F, cs, initial_values) + ret = Stream_functional_equation(ao, F, cs, initial_values, R) series._coeff_stream = ret # === special functions === @@ -2582,6 +2595,46 @@ def functional_equation(self, left, right, series, initial_values): 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.functional_equation(2*z*f(z^3) + z*f^3 - 3*f + 3, 0, f, []) + sage: f + 1 + z + z^2 + 2*z^3 + 5*z^4 + 11*z^5 + 28*z^6 + O(z^7) + + sage: L. = LazyPowerSeriesRing(SR) + sage: G = L.undefined(0) + sage: L.functional_equation(diff(G) - exp(-G(-z)), 0, G, [ln(2)]) + 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.functional_equation(diff(G) - exp(-G(-z)), 0, G, [log(2)]) + 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.functional_equation((1-q*x)*R - (y*q*x+y)*R(q*x) - q*x*R*R(q*x) - x*y*q, 0, R, [0]) + 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.functional_equation((1-q*x)*Rp, (y*q*x+y)*Rp(q*x) + q*x*Rp*Rp(q*x) + x*y*q, Rp, []) + sage: all(R[n] == Rp[n] for n in range(10)) + True """ if self._arity != 1: raise NotImplementedError("only implemented for one variable") @@ -2596,7 +2649,7 @@ def functional_equation(self, left, right, series, initial_values): R = self.base_ring() initial_values = [R(val) for val in initial_values] F = Stream_sub(left._coeff_stream, right._coeff_stream, self.is_sparse()) - ret = Stream_functional_equation(ao, F, cs, initial_values) + ret = Stream_functional_equation(ao, F, cs, initial_values, R) series._coeff_stream = ret From 9de8c8f7cc5bf9476f19ba9f0c4ee63935be2a9c Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Fri, 13 Oct 2023 18:08:45 +0900 Subject: [PATCH 004/117] Fixing more bugs, updating the inputs, other touchups. --- src/sage/data_structures/stream.py | 52 ++++++++++++----- src/sage/rings/lazy_series_ring.py | 90 ++++++++++++++++++++++-------- 2 files changed, 105 insertions(+), 37 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index e5de010fbad..88c765569b9 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -1277,6 +1277,28 @@ def is_uninitialized(self): self._initializing = False return result + def replace(self, stream, sub): + r""" + Return ``self`` except with ``stream`` replaced by ``sub``. + + .. WARNING:: + + This does not update the approximate order or the cache. + """ + if self._target is None: + return self + if self._target == stream: + ret = copy(self) + ret._target = sub + else: + temp = self._target.replace(stream, sub) + if temp == self._target: + ret = self + else: + ret = copy(self) + ret._target = temp + return ret + class Stream_functional_equation(Stream_inexact): r""" @@ -1347,7 +1369,7 @@ def iterate_coefficients(self): yield from self._initial_values from sage.rings.polynomial.infinite_polynomial_ring import InfinitePolynomialRing - P = InfinitePolynomialRing(self._base, 'x') + P = InfinitePolynomialRing(self._base, names=('FESDUMMY',)) x = P.gen() PFF = P.fraction_field() offset = self._approximate_order @@ -1368,24 +1390,22 @@ def get_coeff(n): coeff = P(coeff) V = coeff.variables() + # Substitute for known variables + if V: + # The infinite polynomial ring is very brittle with substitutions + # and variable comparisons + sub = {str(x[i]): val for i, val in enumerate(data) + if any(str(x[i]) == str(va) for va in V)} + if sub: + coeff = coeff.subs(sub) + V = P(coeff).variables() + if len(V) > 1: - # Substitute for known variables - coeff = coeff.subs({str(x[i]): val for i, val in enumerate(data)}) - V = coeff.variables() - if len(V) > 1: - raise ValueError(f"unable to determine a unique solution in degree {n}") + raise ValueError(f"unable to determine a unique solution in degree {n}") if not V: if coeff: - raise ValueError(f"no solution in degree {n} as {coeff} == 0") - i = n - self._start - #print(i, len(data)) - if i < len(data): - # We already know this coefficient - yield data[n - self._start] - else: - yield self._base.zero() - data.append(self._base.zero()) + raise ValueError(f"no solution in degree {n} as {coeff} != 0") n += 1 continue @@ -1393,6 +1413,8 @@ def get_coeff(n): hc = coeff.homogeneous_components() if not set(hc).issubset([0,1]): raise ValueError(f"unable to determine a unique solution in degree {n}") + if str(hc[1].lm()) != str(x[len(data)]): + raise ValueError(f"the solutions to the coefficients must be computed in order") val = -hc.get(0, P.zero()).lc() / hc[1].lc() # Update the cache data.append(val) diff --git a/src/sage/rings/lazy_series_ring.py b/src/sage/rings/lazy_series_ring.py index 36f9f564b05..e0405229961 100644 --- a/src/sage/rings/lazy_series_ring.py +++ b/src/sage/rings/lazy_series_ring.py @@ -1814,35 +1814,38 @@ def taylor(self, f): stream = Stream_taylor(f, self.is_sparse()) return self.element_class(self, stream) - def functional_equation(self, left, right, series, initial_values): + def functional_equation(self, eqn, series, initial_values=None): r""" Define the lazy undefined ``series`` that solves the functional - equation ``left == right`` with ``initial_values``. + equation ``eqn == 0`` with ``initial_values``. EXAMPLES:: sage: L. = LazyLaurentSeriesRing(QQ) sage: f = L.undefined(-1) - sage: L.functional_equation(f, 2+z*f(z^2), f, [5]) + sage: L.functional_equation(2+z*f(z^2) - f, f, [5]) sage: f - 5*z^-1 + 5 + 2*z + 2*z^2 + 2*z^4 + O(z^6) + 5*z^-1 + 2 + 2*z + 2*z^3 + O(z^6) + sage: 2 + z*f(z^2) - f + O(z^6) sage: f = L.undefined(-2) - sage: L.functional_equation(f, 2+z*f(z^2), f, [5]) + sage: L.functional_equation(2+z*f(z^2) - f, f, [5]) sage: f - + """ if not isinstance(series._coeff_stream, Stream_uninitialized) or series._coeff_stream._target is not None: raise ValueError("series already defined") - left = self(left) - right = self(right) + if initial_values is None: + initial_values = [] + + eqn = self(eqn) cs = series._coeff_stream ao = cs._approximate_order R = self.base_ring() initial_values = [R(val) for val in initial_values] - F = Stream_sub(left._coeff_stream, right._coeff_stream, self.is_sparse()) - ret = Stream_functional_equation(ao, F, cs, initial_values, R) + ret = Stream_functional_equation(ao, eqn._coeff_stream, cs, initial_values, R) series._coeff_stream = ret # === special functions === @@ -2578,17 +2581,17 @@ def taylor(self, f): stream = Stream_taylor(f, self.is_sparse()) return self.element_class(self, stream) - def functional_equation(self, left, right, series, initial_values): + def functional_equation(self, eqn, series, initial_values=None): r""" Define the lazy undefined ``series`` that solves the functional - equation ``left == right`` with ``initial_values``. + equation ``eqn == 0`` with ``initial_values``. EXAMPLES:: sage: L. = LazyPowerSeriesRing(QQ) sage: f = L.undefined(0) sage: F = diff(f, 2) - sage: L.functional_equation(-F, f, f, [1, 0]) + sage: L.functional_equation(F + f, f, [1, 0]) sage: f 1 - 1/2*z^2 + 1/24*z^4 - 1/720*z^6 + O(z^7) sage: cos(z) @@ -2598,19 +2601,38 @@ def functional_equation(self, left, right, series, initial_values): sage: L. = LazyPowerSeriesRing(QQ) sage: f = L.undefined(0) - sage: L.functional_equation(2*z*f(z^3) + z*f^3 - 3*f + 3, 0, f, []) + sage: L.functional_equation(2*z*f(z^3) + z*f^3 - 3*f + 3, f) 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 [ECII]_:: + + 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.functional_equation(e, g, [1, 2]) + + 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(20)) + True + + Some more examples over different rings:: + sage: L. = LazyPowerSeriesRing(SR) sage: G = L.undefined(0) - sage: L.functional_equation(diff(G) - exp(-G(-z)), 0, G, [ln(2)]) + sage: L.functional_equation(diff(G) - exp(-G(-z)), G, [ln(2)]) 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.functional_equation(diff(G) - exp(-G(-z)), 0, G, [log(2)]) + sage: L.functional_equation(diff(G) - exp(-G(-z)), G, [log(2)]) sage: G 0.693147180559945 + 1.00000000000000*z + 0.500000000000000*z^2 - 0.0833333333333333*z^4 + 0.0222222222222222*z^6 + O(1.00000000000000*z^7) @@ -2620,7 +2642,7 @@ def functional_equation(self, left, right, series, initial_values): sage: q,y = QQ['q,y'].fraction_field().gens() sage: L. = LazyPowerSeriesRing(q.parent()) sage: R = L.undefined() - sage: L.functional_equation((1-q*x)*R - (y*q*x+y)*R(q*x) - q*x*R*R(q*x) - x*y*q, 0, R, [0]) + sage: L.functional_equation((1-q*x)*R - (y*q*x+y)*R(q*x) - q*x*R*R(q*x) - x*y*q, R, [0]) sage: R[0] 0 sage: R[1] @@ -2632,9 +2654,32 @@ def functional_equation(self, left, right, series, initial_values): * (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.functional_equation((1-q*x)*Rp, (y*q*x+y)*Rp(q*x) + q*x*Rp*Rp(q*x) + x*y*q, Rp, []) + sage: L.functional_equation((y*q*x+y)*Rp(q*x) + q*x*Rp*Rp(q*x) + x*y*q - (1-q*x)*Rp, Rp, []) sage: all(R[n] == Rp[n] for n in range(10)) 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.functional_equation(F(2*z) - (1+exp(x*z)+exp(y*z))*F - exp((x+y)*z)*F(-z), F, [0, f1, f2]) + 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 value for the degree 1 component to + get a unique solution in the previous example:: + + sage: F = L.undefined() + sage: L.functional_equation(F(2*z) - (1+exp(x*z)+exp(y*z))*F - exp((x+y)*z)*F(-z), F, []) + sage: F + """ if self._arity != 1: raise NotImplementedError("only implemented for one variable") @@ -2642,14 +2687,15 @@ def functional_equation(self, left, right, series, initial_values): if not isinstance(series._coeff_stream, Stream_uninitialized) or series._coeff_stream._target is not None: raise ValueError("series already defined") - left = self(left) - right = self(right) + if initial_values is None: + initial_values = [] + + eqn = self(eqn) cs = series._coeff_stream ao = cs._approximate_order R = self.base_ring() initial_values = [R(val) for val in initial_values] - F = Stream_sub(left._coeff_stream, right._coeff_stream, self.is_sparse()) - ret = Stream_functional_equation(ao, F, cs, initial_values, R) + ret = Stream_functional_equation(ao, eqn._coeff_stream, cs, initial_values, R) series._coeff_stream = ret From 071a10a3cc01028a80083bc0c4a23edb989f60ab Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Tue, 17 Oct 2023 13:12:10 +0900 Subject: [PATCH 005/117] Improving the functional defintion; changing the method name/location; moving towards full coverage. --- src/sage/data_structures/stream.py | 56 +++++++- src/sage/rings/lazy_series.py | 136 +++++++++++++++++++ src/sage/rings/lazy_series_ring.py | 205 +++++++---------------------- 3 files changed, 234 insertions(+), 163 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index 88c765569b9..ea2c2825497 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -219,6 +219,13 @@ def replace(self, stream, sub): Return ``self`` except with ``stream`` replaced by ``sub``. The default is to return ``self``. + + EXAMPLES:: + + sage: from sage.data_structures.stream import Stream_zero + sage: zero = Stream_zero() + sage: zero.replace(zero, zero) is zero + True """ return self @@ -1087,6 +1094,15 @@ def __init__(self, function, is_sparse): sage: f = Stream_taylor(polygen(QQ, 'x')^3, False) sage: TestSuite(f).run(skip="_test_pickling") """ + from sage.symbolic.ring import SR + from sage.structure.element import parent + if parent(function) is SR: + self._is_symbolic = True + if function.number_of_arguments() != 1: + raise NotImplementedError("the function can only take a single input") + self._arg = function.arguments()[0] + else: + self._is_symbolic = False self._func = function super().__init__(is_sparse, False) self._approximate_order = 0 @@ -1135,7 +1151,7 @@ def __eq__(self, other): sage: f == g True """ - # The bool call is needed when passing functions in SR + # The bool call is needed when the functions are in SR return isinstance(other, type(self)) and bool(self._func == other._func) def get_coefficient(self, n): @@ -1153,11 +1169,24 @@ def get_coefficient(self, n): sage: f = Stream_taylor(g, True) sage: f.get_coefficient(5) 1/120 + + sage: from sage.data_structures.stream import Stream_taylor + sage: y = SR.var('y') + sage: f = Stream_taylor(sin(y), True) + sage: f.get_coefficient(5) + 1/120 """ if n == 0: + if self._is_symbolic: + return self._func.subs({self._arg: ZZ.zero()}) return self._func(ZZ.zero()) + from sage.functions.other import factorial - return self._func.derivative(n)(ZZ.zero()) / factorial(n) + if self._is_symbolic: + num = self._func.derivative(n).subs({self._arg: ZZ.zero()}) + else: + num = self._func.derivative(n)(ZZ.zero()) + return num / factorial(n) def iterate_coefficients(self): """ @@ -1171,12 +1200,21 @@ def iterate_coefficients(self): sage: it = f.iterate_coefficients() sage: [next(it) for _ in range(10)] [0, 0, 0, 1, 0, 0, 0, 0, 0, 0] + + sage: y = SR.var('y') + sage: f = Stream_taylor(y^3, False) + sage: it = f.iterate_coefficients() + sage: [next(it) for _ in range(10)] + [0, 0, 0, 1, 0, 0, 0, 0, 0, 0] """ cur = self._func n = 0 denom = 1 while True: - yield cur(ZZ.zero()) / denom + if self._is_symbolic: + yield cur({self._arg: ZZ.zero()}) / denom + else: + yield cur(ZZ.zero()) / denom cur = cur.derivative() n += 1 denom *= n @@ -1375,7 +1413,10 @@ def iterate_coefficients(self): offset = self._approximate_order def get_coeff(n): - return x[n-offset] + n -= offset + if n < len(self._initial_values): + return self._initial_values[n] + return x[n] sf = Stream_function(get_coeff, is_sparse=False, approximate_order=offset, true_order=True) self._F = self._F.replace(self._uninitialized, sf) @@ -1417,6 +1458,7 @@ def get_coeff(n): raise ValueError(f"the solutions to the coefficients must be computed in order") val = -hc.get(0, P.zero()).lc() / hc[1].lc() # Update the cache + sf._cache[len(data)] = val data.append(val) yield val n += 1 @@ -3835,7 +3877,7 @@ def _approximate_order(self): return min(self._series._approximate_order + self._shift, 0) def get_coefficient(self, n): - """ + r""" Return the ``n``-th coefficient of ``self``. EXAMPLES:: @@ -3845,14 +3887,14 @@ def get_coefficient(self, n): sage: [f[i] for i in range(-3, 4)] [-2, -1, 0, 1, 2, 3, 4] sage: f2 = Stream_integral(f, [0], True) - sage: [f2[i] for i in range(-3, 5)] + sage: [f2.get_coefficient(i) for i in range(-3, 5)] [0, 1, 1, 0, 1, 1, 1, 1] sage: f = Stream_function(lambda n: (n + 1)*(n+2), True, 2) sage: [f[i] for i in range(-1, 4)] [0, 0, 0, 12, 20] sage: f2 = Stream_integral(f, [-1, -1, -1], True) - sage: [f2[i] for i in range(-1, 7)] + sage: [f2.get_coefficient(i) for i in range(-1, 7)] [0, -1, -1, -1/2, 0, 0, 1/5, 1/6] """ if 0 <= n < self._shift: diff --git a/src/sage/rings/lazy_series.py b/src/sage/rings/lazy_series.py index 7124419c847..2024d295bd5 100644 --- a/src/sage/rings/lazy_series.py +++ b/src/sage/rings/lazy_series.py @@ -253,6 +253,7 @@ Stream_zero, Stream_exact, Stream_uninitialized, + Stream_functional_equation, Stream_shift, Stream_truncated, Stream_function, @@ -1549,6 +1550,141 @@ def define(self, s): # an alias for compatibility with padics set = define + def define_implicity(self, eqn, initial_values=None): + r""" + Define ``self`` as the series that solves the functional + equation ``eqn == 0`` with ``initial_values``. + + EXAMPLES:: + + sage: L. = LazyPowerSeriesRing(QQ) + sage: f = L.undefined(0) + sage: F = diff(f, 2) + sage: f.define_implicity(F + f, [1, 0]) + 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: f.define_implicity(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: g.define_implicity(e, [1, 2]) + + 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(20)) + True + + Some more examples over different rings:: + + sage: L. = LazyPowerSeriesRing(SR) + sage: G = L.undefined(0) + sage: G.define_implicity(diff(G) - exp(-G(-z)), [ln(2)]) + 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: G.define_implicity(diff(G) - exp(-G(-z)), [log(2)]) + 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: R.define_implicity((1-q*x)*R - (y*q*x+y)*R(q*x) - q*x*R*R(q*x) - x*y*q, [0]) + 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: Rp.define_implicity((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(10)) + 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: F.define_implicity(F(2*z) - (1+exp(x*z)+exp(y*z))*F - exp((x+y)*z)*F(-z), [0, f1, f2]) + 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: F = L.undefined() + sage: F.define_implicity(F(2*z) - (1+exp(x*z)+exp(y*z))*F - exp((x+y)*z)*F(-z)) + sage: F + + + sage: F = L.undefined() + sage: F.define_implicity(F(2*z) - (1+exp(x*z)+exp(y*z))*F - exp((x+y)*z)*F(-z), [0, f1]) + sage: F + + + Laurent series examples:: + + sage: L. = LazyLaurentSeriesRing(QQ) + sage: f = L.undefined(-1) + sage: f.define_implicity(2+z*f(z^2) - f, [5]) + 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: g.define_implicity(2+z*g(z^2) - g, [5]) + sage: g + + """ + if not isinstance(self._coeff_stream, Stream_uninitialized) or self._coeff_stream._target is not None: + raise ValueError("series already defined") + + if initial_values is None: + initial_values = [] + + P = self.parent() + eqn = P(eqn) + cs = self._coeff_stream + ao = cs._approximate_order + R = P.base_ring() + initial_values = [R(val) for val in initial_values] + ret = Stream_functional_equation(ao, eqn._coeff_stream, cs, initial_values, R) + self._coeff_stream = ret + def _repr_(self): r""" Return a string representation of ``self``. diff --git a/src/sage/rings/lazy_series_ring.py b/src/sage/rings/lazy_series_ring.py index e0405229961..c3059bf936a 100644 --- a/src/sage/rings/lazy_series_ring.py +++ b/src/sage/rings/lazy_series_ring.py @@ -88,9 +88,7 @@ Stream_iterator, Stream_exact, Stream_uninitialized, - Stream_sub, - Stream_taylor, - Stream_functional_equation + Stream_taylor ) from types import GeneratorType @@ -1797,7 +1795,7 @@ def residue_field(self): def taylor(self, f): r""" - Return the Taylor expansion of the function ``f``. + Return the Taylor expansion around `0` of the function ``f``. INPUT: @@ -1806,6 +1804,24 @@ def taylor(self, f): * the substitution `f(z)`, where `z` is generator of ``self`` * `f` is a function of a single variable with no poles and has a ``derivative`` method + + EXAMPLES:: + + sage: L. = LazyLaurentSeriesRing(QQ) + sage: x = SR.var('x') + sage: f(x) = (1 + x)/(1 - x^2) + sage: L.taylor(f) + 1 + z + z^2 + z^3 + z^4 + z^5 + z^6 + O(z^7) + + For inputs as symbolic functions, this uses the generic implementation, + and so the function cannot have any poles at `0`:: + + sage: f(x) = (1 + x^2) / sin(x^2) + sage: L.taylor(f) + + sage: def g(a): return (1 + a^2) / sin(a^2) + sage: L.taylor(g) + z^-2 + 1 + 1/6*z^2 + 1/6*z^4 + O(z^5) """ try: return f(self.gen()) @@ -1814,40 +1830,6 @@ def taylor(self, f): stream = Stream_taylor(f, self.is_sparse()) return self.element_class(self, stream) - def functional_equation(self, eqn, series, initial_values=None): - r""" - Define the lazy undefined ``series`` that solves the functional - equation ``eqn == 0`` with ``initial_values``. - - EXAMPLES:: - - sage: L. = LazyLaurentSeriesRing(QQ) - sage: f = L.undefined(-1) - sage: L.functional_equation(2+z*f(z^2) - f, f, [5]) - 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: f = L.undefined(-2) - sage: L.functional_equation(2+z*f(z^2) - f, f, [5]) - sage: f - - """ - if not isinstance(series._coeff_stream, Stream_uninitialized) or series._coeff_stream._target is not None: - raise ValueError("series already defined") - - if initial_values is None: - initial_values = [] - - eqn = self(eqn) - cs = series._coeff_stream - ao = cs._approximate_order - R = self.base_ring() - initial_values = [R(val) for val in initial_values] - ret = Stream_functional_equation(ao, eqn._coeff_stream, cs, initial_values, R) - series._coeff_stream = ret - # === special functions === def q_pochhammer(self, q=None): @@ -2561,7 +2543,7 @@ def some_elements(self): def taylor(self, f): r""" - Return the Taylor expansion of the function ``f``. + Return the Taylor expansion around `0` of the function ``f``. INPUT: @@ -2571,6 +2553,34 @@ def taylor(self, f): are the generators of ``self`` * `f` is a function of a single variable with no poles and has a ``derivative`` method + + .. WARNING:: + + For inputs as symbolic functions/expressions + + EXAMPLES:: + + sage: L. = LazyPowerSeriesRing(QQ) + sage: x = SR.var('x') + sage: f(x) = (1 + x) / (1 - x^3) + sage: L.taylor(f) + 1 + z + z^3 + z^4 + z^6 + O(z^7) + sage: (1 + z) / (1 - z^3) + 1 + z + z^3 + z^4 + z^6 + O(z^7) + sage: f(x) = cos(x + pi/2) + sage: L.taylor(f) + -z + 1/6*z^3 - 1/120*z^5 + O(z^7) + + sage: L. = LazyPowerSeriesRing(QQ) + sage: def f(x, y): return (1 + x) / (1 + y) + sage: L.taylor(f) + 1 + (a-b) + (-a*b+b^2) + (a*b^2-b^3) + (-a*b^3+b^4) + (a*b^4-b^5) + (-a*b^5+b^6) + O(a,b)^7 + sage: y = SR.var('y') + sage: g(x, y) = (1 + x) / (1 + y) + sage: L.taylor(g) + Traceback (most recent call last): + ... + NotImplementedError: only implemented generically for one variable """ try: return f(*self.gens()) @@ -2581,123 +2591,6 @@ def taylor(self, f): stream = Stream_taylor(f, self.is_sparse()) return self.element_class(self, stream) - def functional_equation(self, eqn, series, initial_values=None): - r""" - Define the lazy undefined ``series`` that solves the functional - equation ``eqn == 0`` with ``initial_values``. - - EXAMPLES:: - - sage: L. = LazyPowerSeriesRing(QQ) - sage: f = L.undefined(0) - sage: F = diff(f, 2) - sage: L.functional_equation(F + f, f, [1, 0]) - 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.functional_equation(2*z*f(z^3) + z*f^3 - 3*f + 3, f) - 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 [ECII]_:: - - 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.functional_equation(e, g, [1, 2]) - - 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(20)) - True - - Some more examples over different rings:: - - sage: L. = LazyPowerSeriesRing(SR) - sage: G = L.undefined(0) - sage: L.functional_equation(diff(G) - exp(-G(-z)), G, [ln(2)]) - 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.functional_equation(diff(G) - exp(-G(-z)), G, [log(2)]) - 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.functional_equation((1-q*x)*R - (y*q*x+y)*R(q*x) - q*x*R*R(q*x) - x*y*q, R, [0]) - 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.functional_equation((y*q*x+y)*Rp(q*x) + q*x*Rp*Rp(q*x) + x*y*q - (1-q*x)*Rp, Rp, []) - sage: all(R[n] == Rp[n] for n in range(10)) - 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.functional_equation(F(2*z) - (1+exp(x*z)+exp(y*z))*F - exp((x+y)*z)*F(-z), F, [0, f1, f2]) - 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 value for the degree 1 component to - get a unique solution in the previous example:: - - sage: F = L.undefined() - sage: L.functional_equation(F(2*z) - (1+exp(x*z)+exp(y*z))*F - exp((x+y)*z)*F(-z), F, []) - sage: F - - """ - if self._arity != 1: - raise NotImplementedError("only implemented for one variable") - - if not isinstance(series._coeff_stream, Stream_uninitialized) or series._coeff_stream._target is not None: - raise ValueError("series already defined") - - if initial_values is None: - initial_values = [] - - eqn = self(eqn) - cs = series._coeff_stream - ao = cs._approximate_order - R = self.base_ring() - initial_values = [R(val) for val in initial_values] - ret = Stream_functional_equation(ao, eqn._coeff_stream, cs, initial_values, R) - series._coeff_stream = ret - ###################################################################### From 743cad65f0690b32a0b48f5fde3f88e8d0c87565 Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Tue, 17 Oct 2023 15:45:28 +0900 Subject: [PATCH 006/117] Full doctest coverage for the PR. --- src/sage/data_structures/stream.py | 114 +++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index ea2c2825497..d8ead2bd6b1 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -1322,6 +1322,29 @@ def replace(self, stream, sub): .. WARNING:: This does not update the approximate order or the cache. + + EXAMPLES:: + + sage: from sage.data_structures.stream import Stream_uninitialized, Stream_shift, Stream_function + sage: U = Stream_uninitialized(0) + sage: F = Stream_function(lambda n: 1, False, 0) + sage: X = Stream_function(lambda n: n, False, 0) + sage: S = Stream_shift(F, -3) + sage: U.replace(X, F) is U + True + sage: U._target = S + sage: Up = U.replace(S, X) + sage: Up == U + False + sage: [Up[i] for i in range(5)] + [0, 1, 2, 3, 4] + sage: Upp = U.replace(F, X) + sage: Upp == U + False + sage: [Upp[i] for i in range(5)] + [3, 4, 5, 6, 7] + sage: [U[i] for i in range(5)] + [1, 1, 1, 1, 1] """ if self._target is None: return self @@ -1562,6 +1585,28 @@ def replace(self, stream, sub): .. WARNING:: This does not update the approximate order or the cache. + + EXAMPLES:: + + sage: from sage.data_structures.stream import Stream_shift, Stream_neg, Stream_function + sage: F = Stream_function(lambda n: 1, False, 0) + sage: X = Stream_function(lambda n: n, False, 0) + sage: S = Stream_shift(F, -3) + sage: N = Stream_neg(S, False) + sage: N.replace(X, F) is N + True + sage: Np = N.replace(F, X) + sage: Np == N + False + sage: [Np[i] for i in range(5)] + [-3, -4, -5, -6, -7] + sage: Npp = N.replace(S, X) + sage: Npp == N + False + sage: [Npp[i] for i in range(5)] + [0, -1, -2, -3, -4] + sage: [N[i] for i in range(5)] + [-1, -1, -1, -1, -1] """ if self._series == stream: ret = copy(self) @@ -1687,6 +1732,53 @@ def replace(self, stream, sub): .. WARNING:: This does not update the approximate order or the cache. + + EXAMPLES:: + + sage: from sage.data_structures.stream import Stream_neg, Stream_sub, Stream_function + sage: L = Stream_function(lambda n: 1, False, 0) + sage: R = Stream_function(lambda n: n, False, 0) + sage: NL = Stream_neg(L, False) + sage: NR = Stream_neg(R, False) + sage: S = Stream_sub(NL, NR, False) + sage: S.replace(Stream_function(lambda n: n^2, False, 0), R) is S + True + sage: Sp = S.replace(L, R) + sage: Sp == S + False + sage: [Sp[i] for i in range(5)] + [0, 0, 0, 0, 0] + + Because we have computed some values of the cache for ``NR`` (which + is copied), we get the following wrong result:: + + sage: Sp = S.replace(R, L) + sage: Sp == S + False + sage: [Sp[i] for i in range(5)] + [-1, 0, 0, 0, 0] + + With fresh caches:: + + sage: NL = Stream_neg(L, False) + sage: NR = Stream_neg(R, False) + sage: S = Stream_sub(NL, NR, False) + sage: Sp = S.replace(R, L) + sage: [Sp[i] for i in range(5)] + [0, 0, 0, 0, 0] + + The replacements here do not affect the relevant caches:: + + sage: Sp = S.replace(NL, L) + sage: Sp == S + False + sage: [Sp[i] for i in range(5)] + [1, 2, 3, 4, 5] + sage: Sp = S.replace(NR, R) + sage: Sp == S + False + sage: [Sp[i] for i in range(5)] + [-1, -2, -3, -4, -5] """ if self._left == stream: ret = copy(self) @@ -3441,6 +3533,28 @@ def replace(self, stream, sub): .. WARNING:: This does not update the approximate order. + + EXAMPLES:: + + sage: from sage.data_structures.stream import Stream_uninitialized, Stream_shift, Stream_function + sage: F = Stream_function(lambda n: 1, False, 0) + sage: X = Stream_function(lambda n: n, False, 0) + sage: S = Stream_shift(F, -3) + sage: S.replace(X, F) is S + True + sage: Sp = S.replace(F, X) + sage: Sp == S + False + sage: [Sp[i] for i in range(5)] + [3, 4, 5, 6, 7] + sage: U = Stream_uninitialized(0) + sage: U._target = F + sage: S = Stream_shift(U, -3) + sage: Sp = S.replace(F, X) + sage: Sp == S + False + sage: [Sp[i] for i in range(5)] + [3, 4, 5, 6, 7] """ if self._series == stream: ret = copy(self) From 971c44b754956ff1434004e5d431b8790a0599f7 Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Tue, 24 Oct 2023 15:56:02 +0900 Subject: [PATCH 007/117] Adding Martin's patch and updating a few doctests reflecting the speed increase. --- src/sage/data_structures/stream.py | 45 ++++++++++++++++++++++++++++-- src/sage/rings/lazy_series.py | 9 +++--- 2 files changed, 48 insertions(+), 6 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index d8ead2bd6b1..7b75eb613fb 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -147,6 +147,9 @@ def __init__(self, true_order): """ self._true_order = true_order + def map_cache(self, function): + pass + @lazy_attribute def _approximate_order(self): """ @@ -454,6 +457,14 @@ def iterate_coefficients(self): yield self.get_coefficient(n) n += 1 + def map_cache(self, function): + if self._cache: + if self._is_sparse: + i = max(self._cache) + self._cache[i] = function(self._cache[i]) + else: + self._cache[-1] = function(self._cache[-1]) + def order(self): r""" Return the order of ``self``, which is the minimum index ``n`` such @@ -1360,6 +1371,11 @@ def replace(self, stream, sub): ret._target = temp return ret + def map_cache(self, function): + super().map_cache(function) + if self._target is not None: + self._target.map_cache(function) + class Stream_functional_equation(Stream_inexact): r""" @@ -1430,7 +1446,7 @@ def iterate_coefficients(self): yield from self._initial_values from sage.rings.polynomial.infinite_polynomial_ring import InfinitePolynomialRing - P = InfinitePolynomialRing(self._base, names=('FESDUMMY',)) + P = InfinitePolynomialRing(self._base, names=('FESDUMMY',), implementation='sparse') x = P.gen() PFF = P.fraction_field() offset = self._approximate_order @@ -1481,7 +1497,16 @@ def get_coeff(n): raise ValueError(f"the solutions to the coefficients must be computed in order") val = -hc.get(0, P.zero()).lc() / hc[1].lc() # Update the cache - sf._cache[len(data)] = val + def sub(c): + if c not in self._base: + # print(c.polynomial().parent()) + return self._base(c.subs({V[0]: val})) + return c + # print("sf._cache", sf._cache) + sf.map_cache(sub) + # print("F._cache", self._F._cache) + self._F.map_cache(sub) + # sf._cache[len(data)] = val data.append(val) yield val n += 1 @@ -1526,6 +1551,10 @@ def __init__(self, series, is_sparse, true_order=False): self._series = series super().__init__(is_sparse, true_order) + def map_cache(self, function): + super().map_cache(function) + self._series.map_cache(function) + def __hash__(self): """ Return the hash of ``self``. @@ -1663,6 +1692,11 @@ def __init__(self, left, right, is_sparse): self._right = right super().__init__(is_sparse, False) + def map_cache(self, function): + super().map_cache(function) + self._left.map_cache(function) + self._right.map_cache(function) + def __hash__(self): """ Return the hash of ``self``. @@ -2526,6 +2560,10 @@ def __init__(self, f, g, is_sparse, p, ring=None, include=None, exclude=None): f = Stream_map_coefficients(f, lambda x: p(x), is_sparse) super().__init__(f, g, is_sparse) + def map_cache(self, function): + super().map_cache(function) + self._powers = [g.map_cache(function) for g in self._powers] + @lazy_attribute def _approximate_order(self): """ @@ -3407,6 +3445,9 @@ def __init__(self, series, shift): self._shift = shift super().__init__(series._true_order) + def map_cache(self, function): + self._series.map_cache(function) + @lazy_attribute def _approximate_order(self): """ diff --git a/src/sage/rings/lazy_series.py b/src/sage/rings/lazy_series.py index 2024d295bd5..6f8920ed63d 100644 --- a/src/sage/rings/lazy_series.py +++ b/src/sage/rings/lazy_series.py @@ -1588,7 +1588,7 @@ def define_implicity(self, eqn, initial_values=None): 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(20)) + sage: all(g[i] == sol[i] for i in range(50)) True Some more examples over different rings:: @@ -1603,12 +1603,13 @@ def define_implicity(self, eqn, initial_values=None): sage: G = L.undefined(0) sage: G.define_implicity(diff(G) - exp(-G(-z)), [log(2)]) sage: G - 0.693147180559945 + 1.00000000000000*z + 0.500000000000000*z^2 - 0.0833333333333333*z^4 + 0.0222222222222222*z^6 + O(1.00000000000000*z^7) + 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: q, y = QQ['q,y'].fraction_field().gens() sage: L. = LazyPowerSeriesRing(q.parent()) sage: R = L.undefined() sage: R.define_implicity((1-q*x)*R - (y*q*x+y)*R(q*x) - q*x*R*R(q*x) - x*y*q, [0]) @@ -1624,7 +1625,7 @@ def define_implicity(self, eqn, initial_values=None): sage: Rp = L.undefined(1) sage: Rp.define_implicity((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(10)) + sage: all(R[n] == Rp[n] for n in range(7)) True Another example:: From c62ee41657d3ef6492becc458357015de3c9a6f5 Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Tue, 24 Oct 2023 16:31:14 +0900 Subject: [PATCH 008/117] Fixing some remaining details and polishing. --- src/sage/data_structures/stream.py | 262 ++++++++++++++++++++++++----- 1 file changed, 217 insertions(+), 45 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index 7b75eb613fb..362495177c9 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -147,9 +147,6 @@ def __init__(self, true_order): """ self._true_order = true_order - def map_cache(self, function): - pass - @lazy_attribute def _approximate_order(self): """ @@ -232,6 +229,21 @@ def replace(self, stream, sub): """ return self + def recursive_map_largest_cached(self, function): + r""" + Update the largest indexed entry in the cache by applying ``function`` + and proceed recursively through any dependent streams. + + The default is to do nothing (as there is no cache). + + EXAMPLES:: + + sage: from sage.data_structures.stream import Stream_zero + sage: z = Stream_zero() + sage: z.recursive_map_largest_cached(lambda x: x + 10) + """ + pass + class Stream_inexact(Stream): """ @@ -457,14 +469,6 @@ def iterate_coefficients(self): yield self.get_coefficient(n) n += 1 - def map_cache(self, function): - if self._cache: - if self._is_sparse: - i = max(self._cache) - self._cache[i] = function(self._cache[i]) - else: - self._cache[-1] = function(self._cache[-1]) - def order(self): r""" Return the order of ``self``, which is the minimum index ``n`` such @@ -602,6 +606,39 @@ def __ne__(self, other): return False + def recursive_map_largest_cached(self, function): + r""" + Update the largest indexed entry in the cache by applying ``function`` + and proceed recursively through any dependent streams. + + EXAMPLES:: + + sage: from sage.data_structures.stream import Stream_function + sage: f = Stream_function(lambda n: n, False, 1) + sage: [f[i] for i in range(5)] + [0, 1, 2, 3, 4] + sage: f._cache + [1, 2, 3, 4] + sage: f.recursive_map_largest_cached(lambda x: x + 10) + sage: f._cache + [1, 2, 3, 14] + + sage: f = Stream_function(lambda n: n, True, 1) + sage: [f[i] for i in range(0,10,3)] + [0, 3, 6, 9] + sage: f._cache + {3: 3, 6: 6, 9: 9} + sage: f.recursive_map_largest_cached(lambda x: x + 10) + sage: f._cache + {3: 3, 6: 6, 9: 19} + """ + if self._cache: + if self._is_sparse: + i = max(self._cache) + self._cache[i] = function(self._cache[i]) + else: + self._cache[-1] = function(self._cache[-1]) + class Stream_exact(Stream): r""" @@ -1244,7 +1281,7 @@ class Stream_uninitialized(Stream_inexact): .. TODO:: - shouldn't instances of this class share the cache with its + Should instances of this class share the cache with its ``_target``? EXAMPLES:: @@ -1371,10 +1408,28 @@ def replace(self, stream, sub): ret._target = temp return ret - def map_cache(self, function): - super().map_cache(function) + def recursive_map_largest_cached(self, function): + r""" + Update the largest indexed entry in the cache by applying ``function`` + and proceed recursively through any dependent streams. + + 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._target = h + sage: [h[i] for i in range(5)] + [0, 1, 2, 3, 4] + sage: h._cache + [1, 2, 3, 4] + sage: M.recursive_map_largest_cached(lambda x: x + 10) + sage: h._cache + [1, 2, 3, 14] + """ + super().recursive_map_largest_cached(function) if self._target is not None: - self._target.map_cache(function) + self._target.recursive_map_largest_cached(function) class Stream_functional_equation(Stream_inexact): @@ -1393,6 +1448,16 @@ class Stream_functional_equation(Stream_inexact): Instances of this class are always dense. EXAMPLES:: + + sage: from sage.data_structures.stream import Stream_uninitialized + sage: from sage.data_structures.stream import Stream_functional_equation + sage: from sage.data_structures.stream import Stream_derivative, Stream_sub + sage: C = Stream_uninitialized(0) + sage: D = Stream_derivative(C, 1, False) + sage: F = Stream_sub(D, C, False) + sage: S = Stream_functional_equation(0, F, C, [1], QQ) + sage: [S[i] for i in range(10)] + [1, 1, 1/2, 1/6, 1/24, 1/120, 1/720, 1/5040, 1/40320, 1/362880] """ def __init__(self, approximate_order, F, uninitialized, initial_values, R, true_order=False): """ @@ -1401,8 +1466,13 @@ def __init__(self, approximate_order, F, uninitialized, initial_values, R, true_ TESTS:: sage: from sage.data_structures.stream import Stream_uninitialized + sage: from sage.data_structures.stream import Stream_functional_equation + sage: from sage.data_structures.stream import Stream_derivative, Stream_sub sage: C = Stream_uninitialized(0) - sage: TestSuite(C).run(skip="_test_pickling") + sage: D = Stream_derivative(C, 1, False) + sage: F = Stream_sub(D, C, False) + sage: S = Stream_functional_equation(0, F, C, [1], QQ) + sage: TestSuite(S).run(skip="_test_pickling") """ if approximate_order is None: raise ValueError("the valuation must be specified for undefined series") @@ -1434,14 +1504,15 @@ def iterate_coefficients(self): EXAMPLES:: sage: from sage.data_structures.stream import Stream_uninitialized - sage: from sage.data_structures.stream import Stream_exact - sage: z = Stream_exact([1], order=1) + sage: from sage.data_structures.stream import Stream_functional_equation + sage: from sage.data_structures.stream import Stream_derivative, Stream_sub sage: C = Stream_uninitialized(0) - sage: C._target - sage: C._target = z - sage: n = C.iterate_coefficients() + sage: D = Stream_derivative(C, 1, False) + sage: F = Stream_sub(D, C, False) + sage: S = Stream_functional_equation(0, F, C, [1], QQ) + sage: n = S.iterate_coefficients() sage: [next(n) for _ in range(10)] - [0, 1, 0, 0, 0, 0, 0, 0, 0, 0] + [1, 1, 1/2, 1/6, 1/24, 1/120, 1/720, 1/5040, 1/40320, 1/362880] """ yield from self._initial_values @@ -1496,17 +1567,13 @@ def get_coeff(n): if str(hc[1].lm()) != str(x[len(data)]): raise ValueError(f"the solutions to the coefficients must be computed in order") val = -hc.get(0, P.zero()).lc() / hc[1].lc() - # Update the cache + # Update the caches def sub(c): if c not in self._base: - # print(c.polynomial().parent()) return self._base(c.subs({V[0]: val})) return c - # print("sf._cache", sf._cache) - sf.map_cache(sub) - # print("F._cache", self._F._cache) - self._F.map_cache(sub) - # sf._cache[len(data)] = val + sf.recursive_map_largest_cached(sub) + self._F.recursive_map_largest_cached(sub) data.append(val) yield val n += 1 @@ -1551,10 +1618,6 @@ def __init__(self, series, is_sparse, true_order=False): self._series = series super().__init__(is_sparse, true_order) - def map_cache(self, function): - super().map_cache(function) - self._series.map_cache(function) - def __hash__(self): """ Return the hash of ``self``. @@ -1649,6 +1712,35 @@ def replace(self, stream, sub): ret._series = temp return ret + def recursive_map_largest_cached(self, function): + r""" + Update the largest indexed entry in the cache by applying ``function`` + and proceed recursively through any dependent streams. + + .. WARNING:: + + This might make the output inconsistent. + + 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[i] for i in range(5)] + [0, -1, -2, -3, -4] + sage: M._cache + [-1, -2, -3, -4] + sage: h._cache + [1, 2, 3, 4] + sage: M.recursive_map_largest_cached(lambda x: x + 10) + sage: M._cache + [-1, -2, -3, 6] + sage: h._cache + [1, 2, 3, 14] + """ + super().recursive_map_largest_cached(function) + self._series.recursive_map_largest_cached(function) + class Stream_binary(Stream_inexact): """ @@ -1692,11 +1784,6 @@ def __init__(self, left, right, is_sparse): self._right = right super().__init__(is_sparse, False) - def map_cache(self, function): - super().map_cache(function) - self._left.map_cache(function) - self._right.map_cache(function) - def __hash__(self): """ Return the hash of ``self``. @@ -1835,6 +1922,37 @@ def replace(self, stream, sub): ret._right = temp return ret + def recursive_map_largest_cached(self, function): + r""" + Update the largest indexed entry in the cache by applying ``function`` + and proceed recursively through any dependent streams. + + 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[i] for i in range(5)] + [0, 2, 6, 12, 20] + sage: M._cache + [2, 6, 12, 20] + sage: l._cache + [1, 2, 3, 4] + sage: r._cache + [1, 4, 9, 16] + sage: M.recursive_map_largest_cached(lambda x: x + 10) + sage: M._cache + [2, 6, 12, 30] + sage: l._cache + [1, 2, 3, 14] + sage: r._cache + [1, 4, 9, 26] + """ + super().recursive_map_largest_cached(function) + self._left.recursive_map_largest_cached(function) + self._right.recursive_map_largest_cached(function) + class Stream_binaryCommutative(Stream_binary): r""" @@ -2560,10 +2678,6 @@ def __init__(self, f, g, is_sparse, p, ring=None, include=None, exclude=None): f = Stream_map_coefficients(f, lambda x: p(x), is_sparse) super().__init__(f, g, is_sparse) - def map_cache(self, function): - super().map_cache(function) - self._powers = [g.map_cache(function) for g in self._powers] - @lazy_attribute def _approximate_order(self): """ @@ -2763,6 +2877,46 @@ def stretched_power_restrict_degree(self, i, m, d): return self._basis.zero() + def recursive_map_largest_cached(self, function): + r""" + Update the largest indexed entry in the cache by applying ``function`` + and proceed recursively through any dependent streams. + + 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[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._cache + {2: 1/2*p[1, 1] - 1/2*p[2], + 3: 1/3*p[1, 1, 1] - 1/3*p[3], + 4: 1/4*p[1, 1, 1, 1] + 1/4*p[2, 2] - 1/2*p[4]} + sage: [hp._cache for hp in h._powers] + [{2: 1/2*p[1, 1] - 1/2*p[2], + 3: 1/3*p[1, 1, 1] - 1/3*p[3], + 4: 1/8*p[1, 1, 1, 1] + 1/4*p[2, 1, 1] - 1/8*p[2, 2] - 1/4*p[4]}, + {4: 1/4*p[1, 1, 1, 1] - 1/2*p[2, 1, 1] + 1/4*p[2, 2]}] + sage: h.recursive_map_largest_cached(lambda x: x/2) + sage: h._cache + {2: 1/2*p[1, 1] - 1/2*p[2], + 3: 1/3*p[1, 1, 1] - 1/3*p[3], + 4: 1/8*p[1, 1, 1, 1] + 1/8*p[2, 2] - 1/4*p[4]} + sage: [hp._cache for hp in h._powers] + [{2: 1/2*p[1, 1] - 1/2*p[2], + 3: 1/3*p[1, 1, 1] - 1/3*p[3], + 4: 1/128*p[1, 1, 1, 1] + 1/64*p[2, 1, 1] - 1/128*p[2, 2] - 1/64*p[4]}, + {4: 1/8*p[1, 1, 1, 1] - 1/4*p[2, 1, 1] + 1/8*p[2, 2]}] + """ + super().recursive_map_largest_cached(function) + for g in self._powers: + g.recursive_map_largest_cached(function) + ##################################################################### # Unary operations @@ -3445,9 +3599,6 @@ def __init__(self, series, shift): self._shift = shift super().__init__(series._true_order) - def map_cache(self, function): - self._series.map_cache(function) - @lazy_attribute def _approximate_order(self): """ @@ -3609,6 +3760,27 @@ def replace(self, stream, sub): ret._series = temp return ret + def recursive_map_largest_cached(self, function): + r""" + Update the largest indexed entry in the cache by applying ``function`` + and proceed recursively through any dependent streams. + + EXAMPLES:: + + sage: from sage.data_structures.stream import Stream_shift + sage: from sage.data_structures.stream import Stream_function + sage: h = Stream_function(lambda n: n, False, -5) + sage: M = Stream_shift(h, 2) + sage: [M[i] for i in range(-5, 5)] + [0, 0, -5, -4, -3, -2, -1, 0, 1, 2] + sage: h._cache + [-5, -4, -3, -2, -1, 0, 1, 2] + sage: M.recursive_map_largest_cached(lambda x: x + 10) + sage: h._cache + [-5, -4, -3, -2, -1, 0, 1, 12] + """ + self._series.recursive_map_largest_cached(function) + class Stream_truncated(Stream_unary): """ From a4ae02ccf0c1aa23066f00c12b54b4a328742356 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Sun, 29 Oct 2023 14:02:46 +0100 Subject: [PATCH 009/117] provide method to access parent streams, trust that only last element of cache may contain variable, add failing test --- src/sage/data_structures/stream.py | 230 +++++++++-------------------- src/sage/rings/lazy_series.py | 8 + 2 files changed, 75 insertions(+), 163 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index 362495177c9..d4de5aff3b3 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -229,20 +229,19 @@ def replace(self, stream, sub): """ return self - def recursive_map_largest_cached(self, function): + def parent_streams(self): r""" - Update the largest indexed entry in the cache by applying ``function`` - and proceed recursively through any dependent streams. - - The default is to do nothing (as there is no cache). + 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.recursive_map_largest_cached(lambda x: x + 10) + sage: z.parent_streams() + [] """ - pass + return [] class Stream_inexact(Stream): @@ -606,39 +605,6 @@ def __ne__(self, other): return False - def recursive_map_largest_cached(self, function): - r""" - Update the largest indexed entry in the cache by applying ``function`` - and proceed recursively through any dependent streams. - - EXAMPLES:: - - sage: from sage.data_structures.stream import Stream_function - sage: f = Stream_function(lambda n: n, False, 1) - sage: [f[i] for i in range(5)] - [0, 1, 2, 3, 4] - sage: f._cache - [1, 2, 3, 4] - sage: f.recursive_map_largest_cached(lambda x: x + 10) - sage: f._cache - [1, 2, 3, 14] - - sage: f = Stream_function(lambda n: n, True, 1) - sage: [f[i] for i in range(0,10,3)] - [0, 3, 6, 9] - sage: f._cache - {3: 3, 6: 6, 9: 9} - sage: f.recursive_map_largest_cached(lambda x: x + 10) - sage: f._cache - {3: 3, 6: 6, 9: 19} - """ - if self._cache: - if self._is_sparse: - i = max(self._cache) - self._cache[i] = function(self._cache[i]) - else: - self._cache[-1] = function(self._cache[-1]) - class Stream_exact(Stream): r""" @@ -1408,28 +1374,27 @@ def replace(self, stream, sub): ret._target = temp return ret - def recursive_map_largest_cached(self, function): + def parent_streams(self): r""" - Update the largest indexed entry in the cache by applying ``function`` - and proceed recursively through any dependent streams. + 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.parent_streams() + [] sage: M._target = h sage: [h[i] for i in range(5)] [0, 1, 2, 3, 4] - sage: h._cache - [1, 2, 3, 4] - sage: M.recursive_map_largest_cached(lambda x: x + 10) - sage: h._cache - [1, 2, 3, 14] + sage: M.parent_streams() + [] """ - super().recursive_map_largest_cached(function) if self._target is not None: - self._target.recursive_map_largest_cached(function) + return [self._target] + return [] class Stream_functional_equation(Stream_inexact): @@ -1497,6 +1462,19 @@ def __init__(self, approximate_order, F, uninitialized, initial_values, R, true_ self._uninitialized._approximate_order = approximate_order self._uninitialized._target = self + def _subs_in_caches(self, s, var, val): + if hasattr(s, "_cache"): + if s._cache: + if s._is_sparse: + i = max(s._cache) + else: + i = -1 + c = s._cache[i] + if c not in self._base: + s._cache[i] = self._base(c.subs({var: val})) + for t in s.parent_streams(): + self._subs_in_caches(t, var, val) + def iterate_coefficients(self): """ A generator for the coefficients of ``self``. @@ -1532,7 +1510,7 @@ def get_coeff(n): self._F = self._F.replace(self._uninitialized, sf) n = self._F._approximate_order - data = list(self._initial_values) + m = len(self._initial_values) while True: coeff = self._F[n] if coeff.parent() is PFF: @@ -1541,16 +1519,6 @@ def get_coeff(n): coeff = P(coeff) V = coeff.variables() - # Substitute for known variables - if V: - # The infinite polynomial ring is very brittle with substitutions - # and variable comparisons - sub = {str(x[i]): val for i, val in enumerate(data) - if any(str(x[i]) == str(va) for va in V)} - if sub: - coeff = coeff.subs(sub) - V = P(coeff).variables() - if len(V) > 1: raise ValueError(f"unable to determine a unique solution in degree {n}") @@ -1562,21 +1530,20 @@ def get_coeff(n): # single variable to solve for hc = coeff.homogeneous_components() - if not set(hc).issubset([0,1]): - raise ValueError(f"unable to determine a unique solution in degree {n}") - if str(hc[1].lm()) != str(x[len(data)]): - raise ValueError(f"the solutions to the coefficients must be computed in order") - val = -hc.get(0, P.zero()).lc() / hc[1].lc() + if len(hc) == 1: + val = self._base.zero() + else: + if set(hc) != set([0, 1]): + raise ValueError(f"unable to determine a unique solution in degree {n}") + if str(hc[1].lm()) != str(x[m]): + raise ValueError(f"the solutions to the coefficients must be computed in order") + val = self._base(-hc[0].lc() / hc[1].lc()) # Update the caches - def sub(c): - if c not in self._base: - return self._base(c.subs({V[0]: val})) - return c - sf.recursive_map_largest_cached(sub) - self._F.recursive_map_largest_cached(sub) - data.append(val) + self._subs_in_caches(sf, V[0], val) + self._subs_in_caches(self._F, V[0], val) yield val n += 1 + m += 1 class Stream_unary(Stream_inexact): @@ -1712,34 +1679,20 @@ def replace(self, stream, sub): ret._series = temp return ret - def recursive_map_largest_cached(self, function): + def parent_streams(self): r""" - Update the largest indexed entry in the cache by applying ``function`` - and proceed recursively through any dependent streams. - - .. WARNING:: - - This might make the output inconsistent. + 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[i] for i in range(5)] - [0, -1, -2, -3, -4] - sage: M._cache - [-1, -2, -3, -4] - sage: h._cache - [1, 2, 3, 4] - sage: M.recursive_map_largest_cached(lambda x: x + 10) - sage: M._cache - [-1, -2, -3, 6] - sage: h._cache - [1, 2, 3, 14] + sage: M.parent_streams() + [] """ - super().recursive_map_largest_cached(function) - self._series.recursive_map_largest_cached(function) + return [self._series] class Stream_binary(Stream_inexact): @@ -1922,10 +1875,10 @@ def replace(self, stream, sub): ret._right = temp return ret - def recursive_map_largest_cached(self, function): + def parent_streams(self): r""" - Update the largest indexed entry in the cache by applying ``function`` - and proceed recursively through any dependent streams. + Return the list of streams which are used to compute the + coefficients of ``self``. EXAMPLES:: @@ -1933,25 +1886,11 @@ def recursive_map_largest_cached(self, function): 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[i] for i in range(5)] - [0, 2, 6, 12, 20] - sage: M._cache - [2, 6, 12, 20] - sage: l._cache - [1, 2, 3, 4] - sage: r._cache - [1, 4, 9, 16] - sage: M.recursive_map_largest_cached(lambda x: x + 10) - sage: M._cache - [2, 6, 12, 30] - sage: l._cache - [1, 2, 3, 14] - sage: r._cache - [1, 4, 9, 26] - """ - super().recursive_map_largest_cached(function) - self._left.recursive_map_largest_cached(function) - self._right.recursive_map_largest_cached(function) + sage: M.parent_streams() + [, + ] + """ + return [self._left, self._right] class Stream_binaryCommutative(Stream_binary): @@ -2877,10 +2816,10 @@ def stretched_power_restrict_degree(self, i, m, d): return self._basis.zero() - def recursive_map_largest_cached(self, function): + def parent_streams(self): r""" - Update the largest indexed entry in the cache by applying ``function`` - and proceed recursively through any dependent streams. + Return the list of streams which are used to compute the + coefficients of ``self``. EXAMPLES:: @@ -2890,32 +2829,18 @@ def recursive_map_largest_cached(self, function): 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.parent_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], + [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._cache - {2: 1/2*p[1, 1] - 1/2*p[2], - 3: 1/3*p[1, 1, 1] - 1/3*p[3], - 4: 1/4*p[1, 1, 1, 1] + 1/4*p[2, 2] - 1/2*p[4]} - sage: [hp._cache for hp in h._powers] - [{2: 1/2*p[1, 1] - 1/2*p[2], - 3: 1/3*p[1, 1, 1] - 1/3*p[3], - 4: 1/8*p[1, 1, 1, 1] + 1/4*p[2, 1, 1] - 1/8*p[2, 2] - 1/4*p[4]}, - {4: 1/4*p[1, 1, 1, 1] - 1/2*p[2, 1, 1] + 1/4*p[2, 2]}] - sage: h.recursive_map_largest_cached(lambda x: x/2) - sage: h._cache - {2: 1/2*p[1, 1] - 1/2*p[2], - 3: 1/3*p[1, 1, 1] - 1/3*p[3], - 4: 1/8*p[1, 1, 1, 1] + 1/8*p[2, 2] - 1/4*p[4]} - sage: [hp._cache for hp in h._powers] - [{2: 1/2*p[1, 1] - 1/2*p[2], - 3: 1/3*p[1, 1, 1] - 1/3*p[3], - 4: 1/128*p[1, 1, 1, 1] + 1/64*p[2, 1, 1] - 1/128*p[2, 2] - 1/64*p[4]}, - {4: 1/8*p[1, 1, 1, 1] - 1/4*p[2, 1, 1] + 1/8*p[2, 2]}] - """ - super().recursive_map_largest_cached(function) - for g in self._powers: - g.recursive_map_largest_cached(function) + sage: h.parent_streams() + [, + ] + """ + return self._powers ##################################################################### @@ -3760,27 +3685,6 @@ def replace(self, stream, sub): ret._series = temp return ret - def recursive_map_largest_cached(self, function): - r""" - Update the largest indexed entry in the cache by applying ``function`` - and proceed recursively through any dependent streams. - - EXAMPLES:: - - sage: from sage.data_structures.stream import Stream_shift - sage: from sage.data_structures.stream import Stream_function - sage: h = Stream_function(lambda n: n, False, -5) - sage: M = Stream_shift(h, 2) - sage: [M[i] for i in range(-5, 5)] - [0, 0, -5, -4, -3, -2, -1, 0, 1, 2] - sage: h._cache - [-5, -4, -3, -2, -1, 0, 1, 2] - sage: M.recursive_map_largest_cached(lambda x: x + 10) - sage: h._cache - [-5, -4, -3, -2, -1, 0, 1, 12] - """ - self._series.recursive_map_largest_cached(function) - class Stream_truncated(Stream_unary): """ diff --git a/src/sage/rings/lazy_series.py b/src/sage/rings/lazy_series.py index 6f8920ed63d..6593a8bc260 100644 --- a/src/sage/rings/lazy_series.py +++ b/src/sage/rings/lazy_series.py @@ -1670,6 +1670,14 @@ def define_implicity(self, eqn, initial_values=None): sage: g.define_implicity(2+z*g(z^2) - g, [5]) sage: g + + TESTS:: + + sage: L. = LazyPowerSeriesRing(QQ) + sage: f = L.undefined(0) + sage: f.define_implicity(log(1+f) - ~(1 + f) + 1, []) + sage: f + """ if not isinstance(self._coeff_stream, Stream_uninitialized) or self._coeff_stream._target is not None: raise ValueError("series already defined") From aaf978fb640f0dd535c3a02cb96177919b679492 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Sun, 29 Oct 2023 20:07:51 +0100 Subject: [PATCH 010/117] slightly improve substitution, test is failing elsewhere --- src/sage/data_structures/stream.py | 23 +++++++++++++---------- src/sage/rings/lazy_series.py | 3 ++- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index d4de5aff3b3..0de0ba4a96f 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -1456,6 +1456,9 @@ def __init__(self, approximate_order, F, uninitialized, initial_values, R, true_ super().__init__(False, true_order) self._F = F self._base = R + from sage.rings.polynomial.infinite_polynomial_ring import InfinitePolynomialRing + self._P = InfinitePolynomialRing(self._base, names=('FESDUMMY',), implementation='sparse') + self._PFF = self._P.fraction_field() self._initial_values = initial_values self._approximate_order = approximate_order self._uninitialized = uninitialized @@ -1470,8 +1473,13 @@ def _subs_in_caches(self, s, var, val): else: i = -1 c = s._cache[i] - if c not in self._base: - s._cache[i] = self._base(c.subs({var: val})) + if hasattr(c, "parent"): + if c.parent() is self._PFF: + num = c.numerator().subs({var: val}) + den = c.denominator().subs({var: val}) + s._cache[i] = self._base(num/den) + elif c.parent() is self._P: + s._cache[i] = self._base(c.subs({var: val})) for t in s.parent_streams(): self._subs_in_caches(t, var, val) @@ -1493,13 +1501,8 @@ def iterate_coefficients(self): [1, 1, 1/2, 1/6, 1/24, 1/120, 1/720, 1/5040, 1/40320, 1/362880] """ yield from self._initial_values - - from sage.rings.polynomial.infinite_polynomial_ring import InfinitePolynomialRing - P = InfinitePolynomialRing(self._base, names=('FESDUMMY',), implementation='sparse') - x = P.gen() - PFF = P.fraction_field() + x = self._P.gen() offset = self._approximate_order - def get_coeff(n): n -= offset if n < len(self._initial_values): @@ -1513,10 +1516,10 @@ def get_coeff(n): m = len(self._initial_values) while True: coeff = self._F[n] - if coeff.parent() is PFF: + if coeff.parent() is self._PFF: coeff = coeff.numerator() else: - coeff = P(coeff) + coeff = self._P(coeff) V = coeff.variables() if len(V) > 1: diff --git a/src/sage/rings/lazy_series.py b/src/sage/rings/lazy_series.py index 6593a8bc260..22c90512fb4 100644 --- a/src/sage/rings/lazy_series.py +++ b/src/sage/rings/lazy_series.py @@ -1674,9 +1674,10 @@ def define_implicity(self, eqn, initial_values=None): TESTS:: sage: L. = LazyPowerSeriesRing(QQ) - sage: f = L.undefined(0) + sage: f = L.undefined(1) sage: f.define_implicity(log(1+f) - ~(1 + f) + 1, []) sage: f + 0 """ if not isinstance(self._coeff_stream, Stream_uninitialized) or self._coeff_stream._target is not None: From 95f00b0af042c7827c920f795047fdd8ce7bc281 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Mon, 30 Oct 2023 10:20:04 +0100 Subject: [PATCH 011/117] fix a typo and make the linter happy --- src/sage/data_structures/stream.py | 1 + src/sage/rings/lazy_series.py | 28 ++++++++++++++-------------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index 0de0ba4a96f..4b456fbb57f 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -1503,6 +1503,7 @@ def iterate_coefficients(self): yield from self._initial_values x = self._P.gen() offset = self._approximate_order + def get_coeff(n): n -= offset if n < len(self._initial_values): diff --git a/src/sage/rings/lazy_series.py b/src/sage/rings/lazy_series.py index 22c90512fb4..46e44d363f4 100644 --- a/src/sage/rings/lazy_series.py +++ b/src/sage/rings/lazy_series.py @@ -1550,7 +1550,7 @@ def define(self, s): # an alias for compatibility with padics set = define - def define_implicity(self, eqn, initial_values=None): + def define_implicitly(self, eqn, initial_values=None): r""" Define ``self`` as the series that solves the functional equation ``eqn == 0`` with ``initial_values``. @@ -1560,7 +1560,7 @@ def define_implicity(self, eqn, initial_values=None): sage: L. = LazyPowerSeriesRing(QQ) sage: f = L.undefined(0) sage: F = diff(f, 2) - sage: f.define_implicity(F + f, [1, 0]) + sage: f.define_implicitly(F + f, [1, 0]) sage: f 1 - 1/2*z^2 + 1/24*z^4 - 1/720*z^6 + O(z^7) sage: cos(z) @@ -1570,7 +1570,7 @@ def define_implicity(self, eqn, initial_values=None): sage: L. = LazyPowerSeriesRing(QQ) sage: f = L.undefined(0) - sage: f.define_implicity(2*z*f(z^3) + z*f^3 - 3*f + 3) + sage: f.define_implicitly(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) @@ -1584,7 +1584,7 @@ def define_implicity(self, eqn, initial_values=None): 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: g.define_implicity(e, [1, 2]) + sage: g.define_implicitly(e, [1, 2]) 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) @@ -1595,13 +1595,13 @@ def define_implicity(self, eqn, initial_values=None): sage: L. = LazyPowerSeriesRing(SR) sage: G = L.undefined(0) - sage: G.define_implicity(diff(G) - exp(-G(-z)), [ln(2)]) + sage: G.define_implicitly(diff(G) - exp(-G(-z)), [ln(2)]) 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: G.define_implicity(diff(G) - exp(-G(-z)), [log(2)]) + sage: G.define_implicitly(diff(G) - exp(-G(-z)), [log(2)]) sage: G 0.693147180559945 + 1.00000000000000*z + 0.500000000000000*z^2 - 0.0833333333333333*z^4 + 0.0222222222222222*z^6 + O(1.00000000000000*z^7) @@ -1612,7 +1612,7 @@ def define_implicity(self, eqn, initial_values=None): sage: q, y = QQ['q,y'].fraction_field().gens() sage: L. = LazyPowerSeriesRing(q.parent()) sage: R = L.undefined() - sage: R.define_implicity((1-q*x)*R - (y*q*x+y)*R(q*x) - q*x*R*R(q*x) - x*y*q, [0]) + sage: R.define_implicitly((1-q*x)*R - (y*q*x+y)*R(q*x) - q*x*R*R(q*x) - x*y*q, [0]) sage: R[0] 0 sage: R[1] @@ -1624,7 +1624,7 @@ def define_implicity(self, eqn, initial_values=None): * (q^4*y^3 + q^3*y^2 + q^2*y^2 - q^2*y - q*y - 1) sage: Rp = L.undefined(1) - sage: Rp.define_implicity((y*q*x+y)*Rp(q*x) + q*x*Rp*Rp(q*x) + x*y*q - (1-q*x)*Rp) + sage: Rp.define_implicitly((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 @@ -1634,7 +1634,7 @@ def define_implicity(self, eqn, initial_values=None): sage: L.base_ring().inject_variables() Defining x, y, f1, f2 sage: F = L.undefined() - sage: F.define_implicity(F(2*z) - (1+exp(x*z)+exp(y*z))*F - exp((x+y)*z)*F(-z), [0, f1, f2]) + sage: F.define_implicitly(F(2*z) - (1+exp(x*z)+exp(y*z))*F - exp((x+y)*z)*F(-z), [0, f1, f2]) 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) @@ -1647,12 +1647,12 @@ def define_implicity(self, eqn, initial_values=None): components to get a unique solution in the previous example:: sage: F = L.undefined() - sage: F.define_implicity(F(2*z) - (1+exp(x*z)+exp(y*z))*F - exp((x+y)*z)*F(-z)) + sage: F.define_implicitly(F(2*z) - (1+exp(x*z)+exp(y*z))*F - exp((x+y)*z)*F(-z)) sage: F sage: F = L.undefined() - sage: F.define_implicity(F(2*z) - (1+exp(x*z)+exp(y*z))*F - exp((x+y)*z)*F(-z), [0, f1]) + sage: F.define_implicitly(F(2*z) - (1+exp(x*z)+exp(y*z))*F - exp((x+y)*z)*F(-z), [0, f1]) sage: F @@ -1660,14 +1660,14 @@ def define_implicity(self, eqn, initial_values=None): sage: L. = LazyLaurentSeriesRing(QQ) sage: f = L.undefined(-1) - sage: f.define_implicity(2+z*f(z^2) - f, [5]) + sage: f.define_implicitly(2+z*f(z^2) - f, [5]) 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: g.define_implicity(2+z*g(z^2) - g, [5]) + sage: g.define_implicitly(2+z*g(z^2) - g, [5]) sage: g @@ -1675,7 +1675,7 @@ def define_implicity(self, eqn, initial_values=None): sage: L. = LazyPowerSeriesRing(QQ) sage: f = L.undefined(1) - sage: f.define_implicity(log(1+f) - ~(1 + f) + 1, []) + sage: f.define_implicitly(log(1+f) - ~(1 + f) + 1, []) sage: f 0 From 9f116487563c5f407f5c24c3f8493db5ab3ed347 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Thu, 2 Nov 2023 10:26:15 +0100 Subject: [PATCH 012/117] use a custom getitem method --- src/sage/data_structures/stream.py | 125 +++++++++++++++++------------ src/sage/rings/lazy_series.py | 11 +++ 2 files changed, 83 insertions(+), 53 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index 4b456fbb57f..52575e9eddb 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -365,8 +365,8 @@ def __setstate__(self, d): """ self.__dict__ = d if not self._is_sparse: + self._cache = list() self._iter = self.iterate_coefficients() - self._cache = [] def __getitem__(self, n): """ @@ -1397,7 +1397,7 @@ def parent_streams(self): return [] -class Stream_functional_equation(Stream_inexact): +class Stream_functional_equation(Stream): r""" Coefficient stream defined by a functional equation `F = 0`. @@ -1443,7 +1443,7 @@ def __init__(self, approximate_order, F, uninitialized, initial_values, R, true_ raise ValueError("the valuation must be specified for undefined series") if initial_values is None: initial_values = [] - self._start = approximate_order + for i, val in enumerate(initial_values): if val: approximate_order += i @@ -1453,17 +1453,72 @@ def __init__(self, approximate_order, F, uninitialized, initial_values, R, true_ else: approximate_order += len(initial_values) initial_values = [] - super().__init__(False, true_order) + super().__init__(true_order) + self._is_sparse = False self._F = F self._base = R from sage.rings.polynomial.infinite_polynomial_ring import InfinitePolynomialRing - self._P = InfinitePolynomialRing(self._base, names=('FESDUMMY',), implementation='sparse') + self._P = InfinitePolynomialRing(self._base, names=('FESDUMMY',), + implementation='sparse') + self._x = self._P.gen() self._PFF = self._P.fraction_field() - self._initial_values = initial_values + for i, v in enumerate(initial_values): + if v: + self._cache = initial_values[i:] + self._true_order = True + break + approximate_order += 1 + else: + self._cache = [] self._approximate_order = approximate_order - self._uninitialized = uninitialized - self._uninitialized._approximate_order = approximate_order - self._uninitialized._target = self + self._n = approximate_order + len(self._cache) - 1 # the largest index of a coefficient we know + self._uncomputed = True + self._last_eq_n = self._F._approximate_order - 1 + uninitialized._target = self + + def parent_streams(self): + r""" + Return the list of streams which are used to compute the + coefficients of ``self``. + """ + return [] + + def __getitem__(self, n): + if n < self._approximate_order: + return ZZ.zero() + + if self._n >= n: + return self._cache[n - self._approximate_order] + + if self._uncomputed: + self._uncomputed = False + while not self._true_order and n >= self._approximate_order: + for k in range(self._n+1, n+1): + v, val = self._compute() + if val: + self._true_order = True + self._cache[-1] = val + else: + self._approximate_order += 1 + del self._cache[-1] + self._subs_in_caches(self._F, v, val) + self._n += 1 + + if self._true_order: + for k in range(self._n+1, n+1): + v, val = self._compute() + self._cache[-1] = val + self._subs_in_caches(self._F, v, val) + self._n += 1 + self._uncomputed = True + + if len(self._cache) == n - self._approximate_order + 1: + if n >= self._approximate_order: + return self._cache[n - self._approximate_order] + return ZZ.zero() + + self._cache.append(self._x[n]) + return self._cache[-1] def _subs_in_caches(self, s, var, val): if hasattr(s, "_cache"): @@ -1483,40 +1538,10 @@ def _subs_in_caches(self, s, var, val): for t in s.parent_streams(): self._subs_in_caches(t, var, val) - def iterate_coefficients(self): - """ - A generator for the coefficients of ``self``. - - EXAMPLES:: - - sage: from sage.data_structures.stream import Stream_uninitialized - sage: from sage.data_structures.stream import Stream_functional_equation - sage: from sage.data_structures.stream import Stream_derivative, Stream_sub - sage: C = Stream_uninitialized(0) - sage: D = Stream_derivative(C, 1, False) - sage: F = Stream_sub(D, C, False) - sage: S = Stream_functional_equation(0, F, C, [1], QQ) - sage: n = S.iterate_coefficients() - sage: [next(n) for _ in range(10)] - [1, 1, 1/2, 1/6, 1/24, 1/120, 1/720, 1/5040, 1/40320, 1/362880] - """ - yield from self._initial_values - x = self._P.gen() - offset = self._approximate_order - - def get_coeff(n): - n -= offset - if n < len(self._initial_values): - return self._initial_values[n] - return x[n] - - sf = Stream_function(get_coeff, is_sparse=False, approximate_order=offset, true_order=True) - self._F = self._F.replace(self._uninitialized, sf) - - n = self._F._approximate_order - m = len(self._initial_values) + def _compute(self): while True: - coeff = self._F[n] + self._last_eq_n += 1 + coeff = self._F[self._last_eq_n] if coeff.parent() is self._PFF: coeff = coeff.numerator() else: @@ -1524,12 +1549,11 @@ def get_coeff(n): V = coeff.variables() if len(V) > 1: - raise ValueError(f"unable to determine a unique solution in degree {n}") + raise ValueError(f"unable to determine a unique solution in degree {self._last_eq_n}") if not V: if coeff: - raise ValueError(f"no solution in degree {n} as {coeff} != 0") - n += 1 + raise ValueError(f"no solution in degree {self._last_eq_n} as {coeff} != 0") continue # single variable to solve for @@ -1539,16 +1563,11 @@ def get_coeff(n): else: if set(hc) != set([0, 1]): raise ValueError(f"unable to determine a unique solution in degree {n}") - if str(hc[1].lm()) != str(x[m]): - raise ValueError(f"the solutions to the coefficients must be computed in order") +# if str(hc[1].lm()) != str(self._x[m]): +# raise ValueError(f"the solutions to the coefficients must be computed in order") val = self._base(-hc[0].lc() / hc[1].lc()) - # Update the caches - self._subs_in_caches(sf, V[0], val) - self._subs_in_caches(self._F, V[0], val) - yield val - n += 1 - m += 1 + return V[0], val class Stream_unary(Stream_inexact): r""" diff --git a/src/sage/rings/lazy_series.py b/src/sage/rings/lazy_series.py index 46e44d363f4..75becfc25d5 100644 --- a/src/sage/rings/lazy_series.py +++ b/src/sage/rings/lazy_series.py @@ -1679,6 +1679,17 @@ def define_implicitly(self, eqn, initial_values=None): sage: f 0 + We run into the same problem:: + + sage: f[1] + sage: f._coeff_stream._F._left._left.get_coefficient.__closure__[1].cell_contents.__dict__ + {'_left': , + '_right': , + '_true_order': True, + '_is_sparse': True, + '_cache': {0: FESDUMMY_1}, + '_approximate_order': 0} + """ if not isinstance(self._coeff_stream, Stream_uninitialized) or self._coeff_stream._target is not None: raise ValueError("series already defined") From 185788e03dc873aa036b8ddf1c449da19376301d Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Thu, 9 Nov 2023 22:49:36 +0100 Subject: [PATCH 013/117] add optional argument input_streams to Stream_function --- src/sage/data_structures/stream.py | 266 +++-------------------------- src/sage/rings/lazy_series.py | 29 ++-- 2 files changed, 42 insertions(+), 253 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index 52575e9eddb..8910a738c4f 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -214,22 +214,7 @@ def is_uninitialized(self): """ return False - def replace(self, stream, sub): - """ - Return ``self`` except with ``stream`` replaced by ``sub``. - - The default is to return ``self``. - - EXAMPLES:: - - sage: from sage.data_structures.stream import Stream_zero - sage: zero = Stream_zero() - sage: zero.replace(zero, zero) is zero - True - """ - return self - - def parent_streams(self): + def input_streams(self): r""" Return the list of streams which are used to compute the coefficients of ``self``. @@ -238,7 +223,7 @@ def parent_streams(self): sage: from sage.data_structures.stream import Stream_zero sage: z = Stream_zero() - sage: z.parent_streams() + sage: z.input_streams() [] """ return [] @@ -991,6 +976,8 @@ class Stream_function(Stream_inexact): - ``is_sparse`` -- boolean; specifies whether the stream is sparse - ``approximate_order`` -- integer; a lower bound for the order of the stream + - ``input_streams`` -- optional, a list of streams that are + involved in the computation of the coefficients of ``self`` .. NOTE:: @@ -1014,8 +1001,9 @@ class Stream_function(Stream_inexact): sage: f = Stream_function(lambda n: n, True, 0) sage: f[4] 4 + """ - def __init__(self, function, is_sparse, approximate_order, true_order=False): + def __init__(self, function, is_sparse, approximate_order, true_order=False, input_streams=[]): """ Initialize. @@ -1028,6 +1016,14 @@ def __init__(self, function, is_sparse, approximate_order, true_order=False): self.get_coefficient = function super().__init__(is_sparse, true_order) self._approximate_order = approximate_order + self._input_streams = input_streams + + def input_streams(self): + r""" + Return the list of streams which are used to compute the + coefficients of ``self``, as provided. + """ + return self._input_streams def __hash__(self): """ @@ -1329,52 +1325,7 @@ def is_uninitialized(self): self._initializing = False return result - def replace(self, stream, sub): - r""" - Return ``self`` except with ``stream`` replaced by ``sub``. - - .. WARNING:: - - This does not update the approximate order or the cache. - - EXAMPLES:: - - sage: from sage.data_structures.stream import Stream_uninitialized, Stream_shift, Stream_function - sage: U = Stream_uninitialized(0) - sage: F = Stream_function(lambda n: 1, False, 0) - sage: X = Stream_function(lambda n: n, False, 0) - sage: S = Stream_shift(F, -3) - sage: U.replace(X, F) is U - True - sage: U._target = S - sage: Up = U.replace(S, X) - sage: Up == U - False - sage: [Up[i] for i in range(5)] - [0, 1, 2, 3, 4] - sage: Upp = U.replace(F, X) - sage: Upp == U - False - sage: [Upp[i] for i in range(5)] - [3, 4, 5, 6, 7] - sage: [U[i] for i in range(5)] - [1, 1, 1, 1, 1] - """ - if self._target is None: - return self - if self._target == stream: - ret = copy(self) - ret._target = sub - else: - temp = self._target.replace(stream, sub) - if temp == self._target: - ret = self - else: - ret = copy(self) - ret._target = temp - return ret - - def parent_streams(self): + def input_streams(self): r""" Return the list of streams which are used to compute the coefficients of ``self``. @@ -1384,12 +1335,12 @@ def parent_streams(self): 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.parent_streams() + sage: M.input_streams() [] sage: M._target = h sage: [h[i] for i in range(5)] [0, 1, 2, 3, 4] - sage: M.parent_streams() + sage: M.input_streams() [] """ if self._target is not None: @@ -1476,13 +1427,6 @@ def __init__(self, approximate_order, F, uninitialized, initial_values, R, true_ self._last_eq_n = self._F._approximate_order - 1 uninitialized._target = self - def parent_streams(self): - r""" - Return the list of streams which are used to compute the - coefficients of ``self``. - """ - return [] - def __getitem__(self, n): if n < self._approximate_order: return ZZ.zero() @@ -1535,7 +1479,7 @@ def _subs_in_caches(self, s, var, val): s._cache[i] = self._base(num/den) elif c.parent() is self._P: s._cache[i] = self._base(c.subs({var: val})) - for t in s.parent_streams(): + for t in s.input_streams(): self._subs_in_caches(t, var, val) def _compute(self): @@ -1660,49 +1604,7 @@ def is_uninitialized(self): """ return self._series.is_uninitialized() - def replace(self, stream, sub): - r""" - Return ``self`` except with ``stream`` replaced by ``sub``. - - .. WARNING:: - - This does not update the approximate order or the cache. - - EXAMPLES:: - - sage: from sage.data_structures.stream import Stream_shift, Stream_neg, Stream_function - sage: F = Stream_function(lambda n: 1, False, 0) - sage: X = Stream_function(lambda n: n, False, 0) - sage: S = Stream_shift(F, -3) - sage: N = Stream_neg(S, False) - sage: N.replace(X, F) is N - True - sage: Np = N.replace(F, X) - sage: Np == N - False - sage: [Np[i] for i in range(5)] - [-3, -4, -5, -6, -7] - sage: Npp = N.replace(S, X) - sage: Npp == N - False - sage: [Npp[i] for i in range(5)] - [0, -1, -2, -3, -4] - sage: [N[i] for i in range(5)] - [-1, -1, -1, -1, -1] - """ - if self._series == stream: - ret = copy(self) - ret._series = sub - else: - temp = self._series.replace(stream, sub) - if temp == self._series: - ret = self - else: - ret = copy(self) - ret._series = temp - return ret - - def parent_streams(self): + def input_streams(self): r""" Return the list of streams which are used to compute the coefficients of ``self``. @@ -1712,7 +1614,7 @@ def parent_streams(self): 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.parent_streams() + sage: M.input_streams() [] """ return [self._series] @@ -1822,83 +1724,7 @@ def is_uninitialized(self): """ return self._left.is_uninitialized() or self._right.is_uninitialized() - def replace(self, stream, sub): - r""" - Return ``self`` except with ``stream`` replaced by ``sub``. - - .. WARNING:: - - This does not update the approximate order or the cache. - - EXAMPLES:: - - sage: from sage.data_structures.stream import Stream_neg, Stream_sub, Stream_function - sage: L = Stream_function(lambda n: 1, False, 0) - sage: R = Stream_function(lambda n: n, False, 0) - sage: NL = Stream_neg(L, False) - sage: NR = Stream_neg(R, False) - sage: S = Stream_sub(NL, NR, False) - sage: S.replace(Stream_function(lambda n: n^2, False, 0), R) is S - True - sage: Sp = S.replace(L, R) - sage: Sp == S - False - sage: [Sp[i] for i in range(5)] - [0, 0, 0, 0, 0] - - Because we have computed some values of the cache for ``NR`` (which - is copied), we get the following wrong result:: - - sage: Sp = S.replace(R, L) - sage: Sp == S - False - sage: [Sp[i] for i in range(5)] - [-1, 0, 0, 0, 0] - - With fresh caches:: - - sage: NL = Stream_neg(L, False) - sage: NR = Stream_neg(R, False) - sage: S = Stream_sub(NL, NR, False) - sage: Sp = S.replace(R, L) - sage: [Sp[i] for i in range(5)] - [0, 0, 0, 0, 0] - - The replacements here do not affect the relevant caches:: - - sage: Sp = S.replace(NL, L) - sage: Sp == S - False - sage: [Sp[i] for i in range(5)] - [1, 2, 3, 4, 5] - sage: Sp = S.replace(NR, R) - sage: Sp == S - False - sage: [Sp[i] for i in range(5)] - [-1, -2, -3, -4, -5] - """ - if self._left == stream: - ret = copy(self) - ret._left = sub - else: - temp = self._left.replace(stream, sub) - if temp == self._left: - ret = self - else: - ret = copy(self) - ret._left = temp - # It is possible that both the left and right are the same stream - if self._right == stream: - ret = copy(ret) - ret._right = sub - else: - temp = ret._right.replace(stream, sub) - if not (temp == self._right): - ret = copy(ret) - ret._right = temp - return ret - - def parent_streams(self): + def input_streams(self): r""" Return the list of streams which are used to compute the coefficients of ``self``. @@ -1909,7 +1735,7 @@ def parent_streams(self): 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.parent_streams() + sage: M.input_streams() [, ] """ @@ -2839,7 +2665,7 @@ def stretched_power_restrict_degree(self, i, m, d): return self._basis.zero() - def parent_streams(self): + def input_streams(self): r""" Return the list of streams which are used to compute the coefficients of ``self``. @@ -2852,14 +2678,14 @@ def parent_streams(self): 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.parent_streams() + 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.parent_streams() + sage: h.input_streams() [, ] """ @@ -3666,48 +3492,6 @@ def is_uninitialized(self): """ return self._series.is_uninitialized() - def replace(self, stream, sub): - r""" - Return ``self`` except with ``stream`` replaced by ``sub``. - - .. WARNING:: - - This does not update the approximate order. - - EXAMPLES:: - - sage: from sage.data_structures.stream import Stream_uninitialized, Stream_shift, Stream_function - sage: F = Stream_function(lambda n: 1, False, 0) - sage: X = Stream_function(lambda n: n, False, 0) - sage: S = Stream_shift(F, -3) - sage: S.replace(X, F) is S - True - sage: Sp = S.replace(F, X) - sage: Sp == S - False - sage: [Sp[i] for i in range(5)] - [3, 4, 5, 6, 7] - sage: U = Stream_uninitialized(0) - sage: U._target = F - sage: S = Stream_shift(U, -3) - sage: Sp = S.replace(F, X) - sage: Sp == S - False - sage: [Sp[i] for i in range(5)] - [3, 4, 5, 6, 7] - """ - if self._series == stream: - ret = copy(self) - ret._series = sub - else: - temp = self._series.replace(stream, sub) - if temp == self._series: - ret = self - else: - ret = copy(self) - ret._series = temp - return ret - class Stream_truncated(Stream_unary): """ diff --git a/src/sage/rings/lazy_series.py b/src/sage/rings/lazy_series.py index 75becfc25d5..1c11089ebdc 100644 --- a/src/sage/rings/lazy_series.py +++ b/src/sage/rings/lazy_series.py @@ -1679,17 +1679,20 @@ def define_implicitly(self, eqn, initial_values=None): sage: f 0 - We run into the same problem:: - + sage: f = L.undefined(0) + sage: fp = f.derivative() + sage: g = L(lambda n: 0 if n < 10 else 1, 0) + sage: f.define_implicitly(f.derivative() * g + f) + sage: f[0] + 0 + sage: fp[0] + 0 + sage: fp[1] + 0 + sage: fp[2] + 0 sage: f[1] - sage: f._coeff_stream._F._left._left.get_coefficient.__closure__[1].cell_contents.__dict__ - {'_left': , - '_right': , - '_true_order': True, - '_is_sparse': True, - '_cache': {0: FESDUMMY_1}, - '_approximate_order': 0} - + 0 """ if not isinstance(self._coeff_stream, Stream_uninitialized) or self._coeff_stream._target is not None: raise ValueError("series already defined") @@ -3805,7 +3808,8 @@ def exp(self): # of the product are of the form sum_{k=1}^n a_k a_{n+1-k}. 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) + False, 0, + input_streams=[d_self_f]) f._coeff_stream._target = int_d_self_f return f @@ -3859,7 +3863,8 @@ def log(self): coeff_stream_inverse, P.is_sparse()) int_d_self_quo_self = Stream_function(lambda n: d_self_quo_self[n-1] / R(n), - P.is_sparse(), 1) + P.is_sparse(), 1, + input_streams=[d_self_quo_self]) return P.element_class(P, int_d_self_quo_self) From caea280fd015ab621c8c645cd993341e16691e8a Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Mon, 20 Nov 2023 12:13:02 +0100 Subject: [PATCH 014/117] WIP: merge Stream_functional_equation into Stream_uninitialized --- src/sage/data_structures/stream.py | 328 ++++++++++++++--------------- src/sage/rings/lazy_series.py | 20 +- 2 files changed, 166 insertions(+), 182 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index 8910a738c4f..be1adaec5df 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -161,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. @@ -453,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. @@ -1230,7 +1230,7 @@ def iterate_coefficients(self): denom *= n -class Stream_uninitialized(Stream_inexact): +class Stream_uninitialized(Stream): r""" Coefficient stream for an uninitialized series. @@ -1268,144 +1268,37 @@ def __init__(self, approximate_order, true_order=False): sage: TestSuite(C).run(skip="_test_pickling") """ self._target = None + self._F = 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 - def iterate_coefficients(self): - """ - A generator for the coefficients of ``self``. - - EXAMPLES:: - - sage: from sage.data_structures.stream import Stream_uninitialized - sage: from sage.data_structures.stream import Stream_exact - sage: z = Stream_exact([1], order=1) - sage: C = Stream_uninitialized(0) - sage: C._target - sage: C._target = z - sage: n = C.iterate_coefficients() - sage: [next(n) for _ in range(10)] - [0, 1, 0, 0, 0, 0, 0, 0, 0, 0] - """ - n = self._approximate_order - while True: - yield self._target[n] - n += 1 - - def is_uninitialized(self): - """ - Return ``True`` if ``self`` is an uninitialized stream. - - EXAMPLES:: - - sage: from sage.data_structures.stream import Stream_uninitialized - sage: C = Stream_uninitialized(0) - sage: C.is_uninitialized() - True - - A more subtle uninitialized series:: - - sage: L. = LazyPowerSeriesRing(QQ) - sage: T = L.undefined(1) - sage: D = L.undefined(0) - sage: T.define(z * exp(T) * D) - sage: T._coeff_stream.is_uninitialized() - True - """ - if self._target 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() - self._initializing = False - return result - - 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] - return [] - - -class Stream_functional_equation(Stream): - r""" - Coefficient stream defined by a functional equation `F = 0`. - - INPUT: - - - ``approximate_order`` -- integer; a lower bound for the order - of the stream - - ``F`` -- the stream for the equation using ``uninitialized`` - - ``uninitialized`` -- the uninitialized stream - - ``initial_values`` -- the initial values - - ``R`` -- the base ring - - Instances of this class are always dense. - - EXAMPLES:: - - sage: from sage.data_structures.stream import Stream_uninitialized - sage: from sage.data_structures.stream import Stream_functional_equation - sage: from sage.data_structures.stream import Stream_derivative, Stream_sub - sage: C = Stream_uninitialized(0) - sage: D = Stream_derivative(C, 1, False) - sage: F = Stream_sub(D, C, False) - sage: S = Stream_functional_equation(0, F, C, [1], QQ) - sage: [S[i] for i in range(10)] - [1, 1, 1/2, 1/6, 1/24, 1/120, 1/720, 1/5040, 1/40320, 1/362880] - """ - def __init__(self, approximate_order, F, uninitialized, initial_values, R, true_order=False): - """ - Initialize ``self``. + def define(self, target): + self._target = target + self._n = self._approximate_order - 1 # the largest index of a coefficient we know + # we only need this if target is not dense + self._cache = list() + self._iter = self.iterate_coefficients() - TESTS:: + def define_implicitly(self, F, initial_values, R): + assert self._target is None - sage: from sage.data_structures.stream import Stream_uninitialized - sage: from sage.data_structures.stream import Stream_functional_equation - sage: from sage.data_structures.stream import Stream_derivative, Stream_sub - sage: C = Stream_uninitialized(0) - sage: D = Stream_derivative(C, 1, False) - sage: F = Stream_sub(D, C, False) - sage: S = Stream_functional_equation(0, F, C, [1], QQ) - sage: TestSuite(S).run(skip="_test_pickling") - """ - if approximate_order is None: - raise ValueError("the valuation must be specified for undefined series") if initial_values is None: initial_values = [] for i, val in enumerate(initial_values): if val: - approximate_order += i - true_order = True - initial_values = initial_values[i:] + self._approximate_order += i + self._true_order = True + self._cache = initial_values[i:] break else: - approximate_order += len(initial_values) - initial_values = [] - super().__init__(true_order) - self._is_sparse = False + self._approximate_order += len(initial_values) + self._cache = [] + self._F = F self._base = R from sage.rings.polynomial.infinite_polynomial_ring import InfinitePolynomialRing @@ -1413,24 +1306,42 @@ def __init__(self, approximate_order, F, uninitialized, initial_values, R, true_ implementation='sparse') self._x = self._P.gen() self._PFF = self._P.fraction_field() - for i, v in enumerate(initial_values): - if v: - self._cache = initial_values[i:] - self._true_order = True - break - approximate_order += 1 - else: - self._cache = [] - self._approximate_order = approximate_order - self._n = approximate_order + len(self._cache) - 1 # the largest index of a coefficient we know self._uncomputed = True - self._last_eq_n = self._F._approximate_order - 1 - uninitialized._target = self + self._last_eq_n = self._F._approximate_order - 1 # the index of the last equation we used + self._n = self._approximate_order + len(self._cache) - 1 # the largest index of a coefficient we know def __getitem__(self, n): 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() + +# if target is dense, we do not need to duplicate its cache +# for i in range(self._n+1, n): +# self._target[i] +# self._n = n +# return self._target[n] + + # define_implicitly if self._n >= n: return self._cache[n - self._approximate_order] @@ -1513,6 +1424,83 @@ def _compute(self): return V[0], val + def iterate_coefficients(self): + """ + A generator for the coefficients of ``self``. + + EXAMPLES:: + + sage: from sage.data_structures.stream import Stream_uninitialized + sage: from sage.data_structures.stream import Stream_exact + sage: z = Stream_exact([1], order=1) + sage: C = Stream_uninitialized(0) + sage: C._target + sage: C._target = z + sage: n = C.iterate_coefficients() + sage: [next(n) for _ in range(10)] + [0, 1, 0, 0, 0, 0, 0, 0, 0, 0] + """ + n = self._approximate_order + while True: + yield self._target[n] + n += 1 + + def is_uninitialized(self): + """ + Return ``True`` if ``self`` is an uninitialized stream. + + EXAMPLES:: + + sage: from sage.data_structures.stream import Stream_uninitialized + sage: C = Stream_uninitialized(0) + sage: C.is_uninitialized() + True + + A more subtle uninitialized series:: + + sage: L. = LazyPowerSeriesRing(QQ) + sage: T = L.undefined(1) + sage: D = L.undefined(0) + sage: T.define(z * exp(T) * D) + sage: T._coeff_stream.is_uninitialized() + True + """ + if self._target is None and self._F is None: + return True + if self._initializing: + return False + # We implement semaphore-like behavior for coupled (undefined) series + self._initializing = True + if self._target is None: + result = self._F.is_uninitialized() + else: + result = self._target.is_uninitialized() + self._initializing = False + return result + + 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] + return [] + + class Stream_unary(Stream_inexact): r""" Base class for unary operators on coefficient streams. diff --git a/src/sage/rings/lazy_series.py b/src/sage/rings/lazy_series.py index f18424a991a..32cccb50fa4 100644 --- a/src/sage/rings/lazy_series.py +++ b/src/sage/rings/lazy_series.py @@ -253,7 +253,6 @@ Stream_zero, Stream_exact, Stream_uninitialized, - Stream_functional_equation, Stream_shift, Stream_truncated, Stream_function, @@ -1545,7 +1544,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 @@ -1697,17 +1696,14 @@ def define_implicitly(self, eqn, initial_values=None): if not isinstance(self._coeff_stream, Stream_uninitialized) or self._coeff_stream._target is not None: raise ValueError("series already defined") - if initial_values is None: - initial_values = [] - P = self.parent() - eqn = P(eqn) - cs = self._coeff_stream - ao = cs._approximate_order + F = P(eqn)._coeff_stream R = P.base_ring() - initial_values = [R(val) for val in initial_values] - ret = Stream_functional_equation(ao, eqn._coeff_stream, cs, initial_values, R) - self._coeff_stream = ret + if initial_values is None: + initial_values = [] + else: + initial_values = [R(val) for val in initial_values] + self._coeff_stream.define_implicitly(F, initial_values, R) def _repr_(self): r""" @@ -3810,7 +3806,7 @@ def exp(self): int_d_self_f = Stream_function(lambda n: d_self_f[n-1] / R(n) if n else R.one(), False, 0, input_streams=[d_self_f]) - f._coeff_stream._target = int_d_self_f + f._coeff_stream.define(int_d_self_f) return f def log(self): From 4c6e0894f2d48d494efc8931e6c4adf36cf5c7ac Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Mon, 20 Nov 2023 18:06:55 +0100 Subject: [PATCH 015/117] WIP: find all input streams at setup - bug because there are equal streams which are different --- src/sage/data_structures/stream.py | 97 +++++++++++++++++------------- 1 file changed, 55 insertions(+), 42 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index be1adaec5df..2ca79e54082 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -104,6 +104,7 @@ from sage.categories.hopf_algebras_with_basis import HopfAlgebrasWithBasis from sage.misc.cachefunc import cached_method from copy import copy +from sage.sets.recursively_enumerated_set import RecursivelyEnumeratedSet lazy_import('sage.combinat.sf.sfa', ['_variables_recursive', '_raise_variables']) @@ -1276,6 +1277,30 @@ def __init__(self, approximate_order, true_order=False): self._initializing = False self._is_sparse = 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_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._F is not None: + return [self._F] + return [] + def define(self, target): self._target = target self._n = self._approximate_order - 1 # the largest index of a coefficient we know @@ -1308,7 +1333,13 @@ def define_implicitly(self, F, initial_values, R): self._PFF = self._P.fraction_field() self._uncomputed = True self._last_eq_n = self._F._approximate_order - 1 # the index of the last equation we used - self._n = self._approximate_order + len(self._cache) - 1 # the largest index of a coefficient we know + self._n = self._approximate_order + len(self._cache) - 1 # the index of the last coefficient we know + + def children(c): + return [s for s in c.input_streams() if hasattr(s, "_cache")] + + self._input_streams = list(RecursivelyEnumeratedSet([self], children)) + self._good_cache = [0 for c in self._input_streams] # the number of coefficients that have been substituted already def __getitem__(self, n): if n < self._approximate_order: @@ -1356,14 +1387,14 @@ def __getitem__(self, n): else: self._approximate_order += 1 del self._cache[-1] - self._subs_in_caches(self._F, v, val) + self._subs_in_caches(v, val) self._n += 1 if self._true_order: for k in range(self._n+1, n+1): v, val = self._compute() self._cache[-1] = val - self._subs_in_caches(self._F, v, val) + self._subs_in_caches(v, val) self._n += 1 self._uncomputed = True @@ -1375,23 +1406,27 @@ def __getitem__(self, n): self._cache.append(self._x[n]) return self._cache[-1] - def _subs_in_caches(self, s, var, val): - if hasattr(s, "_cache"): - if s._cache: - if s._is_sparse: - i = max(s._cache) - else: - i = -1 - c = s._cache[i] - if hasattr(c, "parent"): - if c.parent() is self._PFF: - num = c.numerator().subs({var: val}) - den = c.denominator().subs({var: val}) - s._cache[i] = self._base(num/den) - elif c.parent() is self._P: - s._cache[i] = self._base(c.subs({var: val})) - for t in s.input_streams(): - self._subs_in_caches(t, var, val) + def _subs_in_caches(self, var, val): + + def subs(cache, k): + c = cache[k] + if hasattr(c, "parent"): + if c.parent() is self._PFF: + num = c.numerator().subs({var: val}) + den = c.denominator().subs({var: val}) + cache[k] = self._base(num/den) + elif c.parent() is self._P: + cache[k] = self._base(c.subs({var: val})) + + for j, s in enumerate(self._input_streams): + m = len(s._cache) - self._good_cache[j] + if s._is_sparse: + for _, i in zip(range(m), reversed(s._cache)): + subs(s._cache, i) + else: + for i in range(-m, -1): + subs(s._cache, i) + self._good_cache[j] += m def _compute(self): while True: @@ -1478,28 +1513,6 @@ def is_uninitialized(self): self._initializing = False return result - 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] - return [] - class Stream_unary(Stream_inexact): r""" From 4c4b9f3689ed7ac249770e2e5aabb06cac846e05 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Mon, 20 Nov 2023 19:41:15 +0100 Subject: [PATCH 016/117] walk through the closure in Stream_function to determine input streams, use 'is' for equality when finding input streams in Stream_uninitialized --- src/sage/data_structures/stream.py | 34 ++++++++++++++++++++---------- src/sage/rings/lazy_series.py | 8 +++---- 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index 2ca79e54082..05b7e88d6a2 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -104,7 +104,6 @@ from sage.categories.hopf_algebras_with_basis import HopfAlgebrasWithBasis from sage.misc.cachefunc import cached_method from copy import copy -from sage.sets.recursively_enumerated_set import RecursivelyEnumeratedSet lazy_import('sage.combinat.sf.sfa', ['_variables_recursive', '_raise_variables']) @@ -977,8 +976,6 @@ class Stream_function(Stream_inexact): - ``is_sparse`` -- boolean; specifies whether the stream is sparse - ``approximate_order`` -- integer; a lower bound for the order of the stream - - ``input_streams`` -- optional, a list of streams that are - involved in the computation of the coefficients of ``self`` .. NOTE:: @@ -1004,7 +1001,7 @@ class Stream_function(Stream_inexact): 4 """ - def __init__(self, function, is_sparse, approximate_order, true_order=False, input_streams=[]): + def __init__(self, function, is_sparse, approximate_order, true_order=False): """ Initialize. @@ -1017,14 +1014,21 @@ def __init__(self, function, is_sparse, approximate_order, true_order=False, inp self.get_coefficient = function super().__init__(is_sparse, true_order) self._approximate_order = approximate_order - self._input_streams = input_streams def input_streams(self): r""" Return the list of streams which are used to compute the coefficients of ``self``, as provided. """ - return self._input_streams + closure = self.get_coefficient.__closure__ + if closure is None: + return [] + l = [] + for cell in closure: + content = cell.cell_contents + if isinstance(content, Stream): + l.append(content) + return l def __hash__(self): """ @@ -1254,7 +1258,7 @@ class Stream_uninitialized(Stream): 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 """ @@ -1335,12 +1339,20 @@ def define_implicitly(self, F, initial_values, R): self._last_eq_n = self._F._approximate_order - 1 # the index of the last equation we used self._n = self._approximate_order + len(self._cache) - 1 # the index of the last coefficient we know - def children(c): - return [s for s in c.input_streams() if hasattr(s, "_cache")] - - self._input_streams = list(RecursivelyEnumeratedSet([self], children)) + self._input_streams = list(self.input_streams_iterator()) self._good_cache = [0 for c in self._input_streams] # the number of coefficients that have been substituted already + def input_streams_iterator(self): + known = [self] + todo = [self] + while todo: + x = todo.pop() + yield x + 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) + def __getitem__(self, n): if n < self._approximate_order: return ZZ.zero() diff --git a/src/sage/rings/lazy_series.py b/src/sage/rings/lazy_series.py index 32cccb50fa4..ff3e4f5ccc8 100644 --- a/src/sage/rings/lazy_series.py +++ b/src/sage/rings/lazy_series.py @@ -1676,7 +1676,7 @@ def define_implicitly(self, eqn, initial_values=None): sage: f = L.undefined(1) sage: f.define_implicitly(log(1+f) - ~(1 + f) + 1, []) sage: f - 0 + O(z^8) sage: f = L.undefined(0) sage: fp = f.derivative() @@ -3804,8 +3804,7 @@ def exp(self): # of the product are of the form sum_{k=1}^n a_k a_{n+1-k}. 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, - input_streams=[d_self_f]) + False, 0) f._coeff_stream.define(int_d_self_f) return f @@ -3859,8 +3858,7 @@ def log(self): coeff_stream_inverse, P.is_sparse()) int_d_self_quo_self = Stream_function(lambda n: d_self_quo_self[n-1] / R(n), - P.is_sparse(), 1, - input_streams=[d_self_quo_self]) + P.is_sparse(), 1) return P.element_class(P, int_d_self_quo_self) From 036f11fbd84fad2b742f4ac8d179ccb53c12301d Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Mon, 20 Nov 2023 22:40:09 +0100 Subject: [PATCH 017/117] fix bad error message --- src/sage/data_structures/stream.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index 05b7e88d6a2..0a6124286d4 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -103,8 +103,6 @@ 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.misc.cachefunc import cached_method -from copy import copy - lazy_import('sage.combinat.sf.sfa', ['_variables_recursive', '_raise_variables']) @@ -1464,7 +1462,7 @@ def _compute(self): val = self._base.zero() else: if set(hc) != set([0, 1]): - raise ValueError(f"unable to determine a unique solution in degree {n}") + raise ValueError(f"unable to determine a unique solution in degree {self._last_eq_n}") # if str(hc[1].lm()) != str(self._x[m]): # raise ValueError(f"the solutions to the coefficients must be computed in order") val = self._base(-hc[0].lc() / hc[1].lc()) From 1954a2754bc7e74f69379e4c97a4481efe92a0f5 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Tue, 21 Nov 2023 10:05:05 +0100 Subject: [PATCH 018/117] store the list of variables belonging to each implicitly defined stream separately --- src/sage/data_structures/stream.py | 73 ++++++++++++++++++++++++++---- src/sage/rings/lazy_series.py | 4 +- 2 files changed, 66 insertions(+), 11 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index 0a6124286d4..ed4f6b3ce78 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -1232,7 +1232,6 @@ def iterate_coefficients(self): n += 1 denom *= n - class Stream_uninitialized(Stream): r""" Coefficient stream for an uninitialized series. @@ -1304,13 +1303,34 @@ def input_streams(self): return [] def define(self, target): + r""" + Define ``self`` via ``self = target``. + + INPUT: + + - ``target`` -- a stream + + EXAMPLES:: + + """ self._target = target self._n = self._approximate_order - 1 # the largest index of a coefficient we know - # we only need this if target is not dense + # we only need this if target does not have a dense cache self._cache = list() self._iter = self.iterate_coefficients() def define_implicitly(self, F, initial_values, R): + r""" + Define ``self`` via ``F == 0``. + + INPUT: + + - ``F`` -- a stream + - ``initial_values`` -- a list specifying ``self[0], self[1], ...`` + - ``R`` -- the coefficient ring + + EXAMPLES:: + """ assert self._target is None if initial_values is None: @@ -1329,9 +1349,11 @@ def define_implicitly(self, F, initial_values, R): self._F = F self._base = R from sage.rings.polynomial.infinite_polynomial_ring import InfinitePolynomialRing - self._P = InfinitePolynomialRing(self._base, names=('FESDUMMY',), - implementation='sparse') + # we use a silly variable name, because InfinitePolynomialRing is cached + self._P = InfinitePolynomialRing(self._base, names=("FESDUMMY",), + implementation='dense') self._x = self._P.gen() + self._variables = set() self._PFF = self._P.fraction_field() self._uncomputed = True self._last_eq_n = self._F._approximate_order - 1 # the index of the last equation we used @@ -1341,6 +1363,12 @@ def define_implicitly(self, F, initial_values, R): self._good_cache = [0 for c in self._input_streams] # the number of coefficients that have been substituted already def input_streams_iterator(self): + r""" + Return the list of streams which have a cache and ``self`` + depends on. + + EXAMPLES:: + """ known = [self] todo = [self] while todo: @@ -1352,6 +1380,17 @@ def input_streams_iterator(self): known.append(y) def __getitem__(self, n): + """ + Return the ``n``-th coefficient of ``self``. + + INPUT: + + - ``n`` -- integer; the index + + EXAMPLES:: + + sage: + """ if n < self._approximate_order: return ZZ.zero() @@ -1413,11 +1452,24 @@ def __getitem__(self, n): return self._cache[n - self._approximate_order] return ZZ.zero() - self._cache.append(self._x[n]) - return self._cache[-1] + x = self._x[self._P._max + 1] + self._variables.add(x) + 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. + + INPUT: + - ``var``, a variable + - ``val``, the value that should replace the variable + + EXAMPLES:: + + """ def subs(cache, k): c = cache[k] if hasattr(c, "parent"): @@ -1439,6 +1491,9 @@ def subs(cache, k): self._good_cache[j] += m def _compute(self): + """ + Solve the next equations, until the next variable is determined. + """ while True: self._last_eq_n += 1 coeff = self._F[self._last_eq_n] @@ -1446,10 +1501,10 @@ def _compute(self): coeff = coeff.numerator() else: coeff = self._P(coeff) - V = coeff.variables() + V = list(self._variables.intersection([self._P(v) for v in coeff.variables()])) if len(V) > 1: - raise ValueError(f"unable to determine a unique solution in degree {self._last_eq_n}") + raise ValueError(f"unable to determine a unique solution in degree {self._last_eq_n}, the equation is {coeff} == 0") if not V: if coeff: @@ -1462,7 +1517,7 @@ def _compute(self): val = self._base.zero() else: if set(hc) != set([0, 1]): - raise ValueError(f"unable to determine a unique solution in degree {self._last_eq_n}") + raise ValueError(f"unable to determine a unique solution in degree {self._last_eq_n}, the equation is {coeff} == 0") # if str(hc[1].lm()) != str(self._x[m]): # raise ValueError(f"the solutions to the coefficients must be computed in order") val = self._base(-hc[0].lc() / hc[1].lc()) diff --git a/src/sage/rings/lazy_series.py b/src/sage/rings/lazy_series.py index ff3e4f5ccc8..c70f3c7caf5 100644 --- a/src/sage/rings/lazy_series.py +++ b/src/sage/rings/lazy_series.py @@ -1648,12 +1648,12 @@ def define_implicitly(self, eqn, initial_values=None): sage: F = L.undefined() sage: F.define_implicitly(F(2*z) - (1+exp(x*z)+exp(y*z))*F - exp((x+y)*z)*F(-z)) sage: F - + sage: F = L.undefined() sage: F.define_implicitly(F(2*z) - (1+exp(x*z)+exp(y*z))*F - exp((x+y)*z)*F(-z), [0, f1]) sage: F - + Laurent series examples:: From 616d3b3735a1b93b741caadfae4d8e56bda2a30a Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Tue, 21 Nov 2023 17:34:45 +0100 Subject: [PATCH 019/117] solve systems of functional equations --- src/sage/data_structures/stream.py | 43 +++++++++++++++++++----------- src/sage/rings/lazy_series.py | 39 ++++++++++++++++++++++++--- 2 files changed, 63 insertions(+), 19 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index ed4f6b3ce78..4df9bded83b 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -1359,25 +1359,33 @@ def define_implicitly(self, F, initial_values, R): self._last_eq_n = self._F._approximate_order - 1 # the index of the last equation we used self._n = self._approximate_order + len(self._cache) - 1 # the index of the last coefficient we know - self._input_streams = list(self.input_streams_iterator()) - self._good_cache = [0 for c in self._input_streams] # the number of coefficients that have been substituted already - def input_streams_iterator(self): + @lazy_attribute + def _input_streams(self): r""" Return the list of streams which have a cache and ``self`` depends on. + All caches must have been created before this is called. + EXAMPLES:: """ known = [self] todo = [self] while todo: x = todo.pop() - yield x 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 that have been substituted already. + """ + return [0 for c in self._input_streams] def __getitem__(self, n): """ @@ -1476,9 +1484,15 @@ def subs(cache, k): if c.parent() is self._PFF: num = c.numerator().subs({var: val}) den = c.denominator().subs({var: val}) - cache[k] = self._base(num/den) + new = num/den elif c.parent() is self._P: - cache[k] = self._base(c.subs({var: val})) + new = c.subs({var: val}) + else: + return + if new in self._base: + cache[k] = self._base(new) + else: + cache[k] = new for j, s in enumerate(self._input_streams): m = len(s._cache) - self._good_cache[j] @@ -1486,7 +1500,7 @@ def subs(cache, k): for _, i in zip(range(m), reversed(s._cache)): subs(s._cache, i) else: - for i in range(-m, -1): + for i in range(-m, 0): subs(s._cache, i) self._good_cache[j] += m @@ -1511,16 +1525,15 @@ def _compute(self): raise ValueError(f"no solution in degree {self._last_eq_n} as {coeff} != 0") continue - # single variable to solve for - hc = coeff.homogeneous_components() - if len(hc) == 1: + c = coeff.polynomial() + v = c.parent()(V[0].polynomial()) + d = c.degree(v) + if d == 1: + val = self._PFF(- c.coefficient({v: 0}) / c.coefficient({v: 1})) + elif c.is_monomial() and c.coefficient({v: d}) in self._base: val = self._base.zero() else: - if set(hc) != set([0, 1]): - raise ValueError(f"unable to determine a unique solution in degree {self._last_eq_n}, the equation is {coeff} == 0") -# if str(hc[1].lm()) != str(self._x[m]): -# raise ValueError(f"the solutions to the coefficients must be computed in order") - val = self._base(-hc[0].lc() / hc[1].lc()) + raise ValueError(f"unable to determine a unique solution in degree {self._last_eq_n}, the equation is {coeff} == 0") return V[0], val diff --git a/src/sage/rings/lazy_series.py b/src/sage/rings/lazy_series.py index c70f3c7caf5..60733e8c495 100644 --- a/src/sage/rings/lazy_series.py +++ b/src/sage/rings/lazy_series.py @@ -1532,7 +1532,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._F is not None): raise ValueError("series already defined") if not isinstance(s, LazyModuleElement): @@ -1615,9 +1617,9 @@ def define_implicitly(self, eqn, initial_values=None): sage: R[0] 0 sage: R[1] - q*y/(-q*y + 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) + (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) @@ -1692,8 +1694,37 @@ def define_implicitly(self, eqn, initial_values=None): 0 sage: f[1] 0 + + Some systems of two coupled functional equations:: + + 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: A.define_implicitly(FA, [1]) + sage: B.define_implicitly(FB, [1]) + 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: A.define_implicitly(FA, [1]) + sage: B.define_implicitly(FB, [1]) + sage: A^2 + B^2 - 2 - z*B + O(z^7) + sage: B^3 + 2*A^3 - 3 - z*(A + B) + O(z^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._F is not None): raise ValueError("series already defined") P = self.parent() From 5d0c3a601ffa9957c2a41b6371c87472205b865d Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Wed, 22 Nov 2023 07:34:02 +0100 Subject: [PATCH 020/117] WIP: use no more new variables than necessary --- src/sage/data_structures/stream.py | 22 +++++++++++++++++++--- src/sage/rings/lazy_series.py | 4 ++-- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index 4df9bded83b..6a3fdae27bc 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -1232,6 +1232,21 @@ def iterate_coefficients(self): n += 1 denom *= n +from collections import defaultdict +STREAM_UNINITIALIZED_VARIABLES = defaultdict(set) +def get_variable(P): + r""" + Return the first variable with index not in + ``STREAM_UNINITIALIZED_VARIABLES``. + + """ + vars = STREAM_UNINITIALIZED_VARIABLES[P] + for i in range(P._max+2): + v = P.gen()[i] + if v not in vars: + vars.add(v) + return v + class Stream_uninitialized(Stream): r""" Coefficient stream for an uninitialized series. @@ -1352,9 +1367,9 @@ def define_implicitly(self, F, initial_values, R): # we use a silly variable name, because InfinitePolynomialRing is cached self._P = InfinitePolynomialRing(self._base, names=("FESDUMMY",), implementation='dense') - self._x = self._P.gen() - self._variables = set() self._PFF = self._P.fraction_field() + self._variables = set() # variables used for this stream + self._uncomputed = True self._last_eq_n = self._F._approximate_order - 1 # the index of the last equation we used self._n = self._approximate_order + len(self._cache) - 1 # the index of the last coefficient we know @@ -1460,7 +1475,7 @@ def __getitem__(self, n): return self._cache[n - self._approximate_order] return ZZ.zero() - x = self._x[self._P._max + 1] + x = get_variable(self._P) self._variables.add(x) self._cache.append(x) return x @@ -1503,6 +1518,7 @@ def subs(cache, k): for i in range(-m, 0): subs(s._cache, i) self._good_cache[j] += m + STREAM_UNINITIALIZED_VARIABLES[self._P].remove(var) def _compute(self): """ diff --git a/src/sage/rings/lazy_series.py b/src/sage/rings/lazy_series.py index 60733e8c495..ac4553dce2c 100644 --- a/src/sage/rings/lazy_series.py +++ b/src/sage/rings/lazy_series.py @@ -1650,12 +1650,12 @@ def define_implicitly(self, eqn, initial_values=None): sage: F = L.undefined() sage: F.define_implicitly(F(2*z) - (1+exp(x*z)+exp(y*z))*F - exp((x+y)*z)*F(-z)) sage: F - + sage: F = L.undefined() sage: F.define_implicitly(F(2*z) - (1+exp(x*z)+exp(y*z))*F - exp((x+y)*z)*F(-z), [0, f1]) sage: F - + Laurent series examples:: From fcb3bf9829ac19856df7b0130721934b07827867 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Wed, 22 Nov 2023 12:17:26 +0100 Subject: [PATCH 021/117] fix conversion issue --- src/sage/data_structures/stream.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index 6a3fdae27bc..1c75521bf54 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -103,6 +103,9 @@ 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.misc.cachefunc import cached_method +from sage.rings.polynomial.infinite_polynomial_element import InfinitePolynomial_dense +from sage.rings.polynomial.infinite_polynomial_ring import InfinitePolynomialRing +from collections import defaultdict lazy_import('sage.combinat.sf.sfa', ['_variables_recursive', '_raise_variables']) @@ -1232,7 +1235,6 @@ def iterate_coefficients(self): n += 1 denom *= n -from collections import defaultdict STREAM_UNINITIALIZED_VARIABLES = defaultdict(set) def get_variable(P): r""" @@ -1363,7 +1365,6 @@ def define_implicitly(self, F, initial_values, R): self._F = F self._base = R - from sage.rings.polynomial.infinite_polynomial_ring import InfinitePolynomialRing # we use a silly variable name, because InfinitePolynomialRing is cached self._P = InfinitePolynomialRing(self._base, names=("FESDUMMY",), implementation='dense') @@ -1531,7 +1532,8 @@ def _compute(self): coeff = coeff.numerator() else: coeff = self._P(coeff) - V = list(self._variables.intersection([self._P(v) for v in coeff.variables()])) + V = self._variables.intersection(InfinitePolynomial_dense(self._P, v) + for v in coeff.variables()) if len(V) > 1: raise ValueError(f"unable to determine a unique solution in degree {self._last_eq_n}, the equation is {coeff} == 0") @@ -1541,8 +1543,9 @@ def _compute(self): raise ValueError(f"no solution in degree {self._last_eq_n} as {coeff} != 0") continue + var = V.pop() c = coeff.polynomial() - v = c.parent()(V[0].polynomial()) + v = c.parent()(var.polynomial()) d = c.degree(v) if d == 1: val = self._PFF(- c.coefficient({v: 0}) / c.coefficient({v: 1})) @@ -1551,7 +1554,7 @@ def _compute(self): else: raise ValueError(f"unable to determine a unique solution in degree {self._last_eq_n}, the equation is {coeff} == 0") - return V[0], val + return var, val def iterate_coefficients(self): """ From 378e28d4d93cc27bea109794224853843bc1d55c Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Thu, 23 Nov 2023 14:15:56 +0100 Subject: [PATCH 022/117] better substitution, better error messages --- src/sage/data_structures/stream.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index 1c75521bf54..370c0ea9987 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -1494,14 +1494,19 @@ def _subs_in_caches(self, var, val): EXAMPLES:: """ + var_p = var.polynomial() def subs(cache, k): c = cache[k] if hasattr(c, "parent"): if c.parent() is self._PFF: - num = c.numerator().subs({var: val}) - den = c.denominator().subs({var: val}) + num = c.numerator() + if var_p in num.variables(): + num = num.subs({var: val}) + den = c.denominator() + if var_p in den.variables(): + num = den.subs({var: val}) new = num/den - elif c.parent() is self._P: + elif c.parent() is self._P and var_p in c.variables(): new = c.subs({var: val}) else: return @@ -1536,10 +1541,10 @@ def _compute(self): for v in coeff.variables()) if len(V) > 1: - raise ValueError(f"unable to determine a unique solution in degree {self._last_eq_n}, the equation is {coeff} == 0") + raise ValueError(f"unable to determine a unique solution in degree {self._last_eq_n}, the variables are {V}, the equation is {coeff} == 0") if not V: - if coeff: + if coeff in self._base and coeff: raise ValueError(f"no solution in degree {self._last_eq_n} as {coeff} != 0") continue @@ -1552,7 +1557,7 @@ def _compute(self): elif c.is_monomial() and c.coefficient({v: d}) in self._base: val = self._base.zero() else: - raise ValueError(f"unable to determine a unique solution in degree {self._last_eq_n}, the equation is {coeff} == 0") + raise ValueError(f"unable to determine a unique solution in degree {self._last_eq_n}, the variable is {var}, the equation is {coeff} == 0") return var, val From e91fd918db0a922e465cbef6ef077c33cee46e18 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Fri, 24 Nov 2023 08:45:57 +0100 Subject: [PATCH 023/117] switch to ZZ.sum, add more failing doctests --- src/sage/data_structures/stream.py | 36 +++++++++++++++--------------- src/sage/rings/lazy_series.py | 23 +++++++++++++++++++ 2 files changed, 41 insertions(+), 18 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index 370c0ea9987..fb51931a6ee 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -2209,10 +2209,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""" @@ -2313,10 +2313,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): @@ -2416,23 +2416,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): @@ -3318,9 +3318,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 diff --git a/src/sage/rings/lazy_series.py b/src/sage/rings/lazy_series.py index ac4553dce2c..1269b9ca729 100644 --- a/src/sage/rings/lazy_series.py +++ b/src/sage/rings/lazy_series.py @@ -1721,6 +1721,29 @@ def define_implicitly(self, eqn, initial_values=None): sage: B^3 + 2*A^3 - 3 - z*(A + B) O(z^7) + sage: L. = LazyPowerSeriesRing(QQ) + sage: A = L.undefined() + sage: B = L.undefined() + sage: C = L.undefined() + sage: FA = (A^2 + B^2)*z^4 + sage: FB = A*B*z^3 + sage: FC = (A + B + C)*z^4 + sage: A.define_implicitly(FA, [0,0,0]) + sage: B.define_implicitly(FB, [0,0]) + sage: C.define_implicitly(FC, [0,0]) + sage: B[2] + + sage: L. = LazyPowerSeriesRing(QQ) + sage: A = L.undefined() + sage: B = L.undefined() + sage: C = L.undefined() + sage: D = L.undefined() + sage: A.define_implicitly(C^2 + D^2, [0,0,0]) + sage: B.define_implicitly(A + B + C + D, [0,0]) + sage: C.define_implicitly(A*D, [0,0]) + sage: D.define_implicitly(A + B + C + D, [0,0]) + sage: B[2] + """ if (not isinstance(self._coeff_stream, Stream_uninitialized) or self._coeff_stream._target is not None From 0f449ff5b7255bea88546b047673f85684bfa660 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Sat, 25 Nov 2023 21:15:43 +0100 Subject: [PATCH 024/117] more failing doctests --- src/sage/data_structures/stream.py | 6 ++++-- src/sage/rings/lazy_series.py | 17 +++++++++++++---- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index fb51931a6ee..2c31d86b3ef 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -1544,8 +1544,10 @@ def _compute(self): raise ValueError(f"unable to determine a unique solution in degree {self._last_eq_n}, the variables are {V}, the equation is {coeff} == 0") if not V: - if coeff in self._base and coeff: - raise ValueError(f"no solution in degree {self._last_eq_n} as {coeff} != 0") + if coeff: + if coeff in self._base: + raise ValueError(f"no solution in degree {self._last_eq_n} as {coeff} != 0") + # should do some substitution coeff = 0, but unclear which variable to extract continue var = V.pop() diff --git a/src/sage/rings/lazy_series.py b/src/sage/rings/lazy_series.py index 1269b9ca729..8be6e02613a 100644 --- a/src/sage/rings/lazy_series.py +++ b/src/sage/rings/lazy_series.py @@ -1725,15 +1725,15 @@ def define_implicitly(self, eqn, initial_values=None): sage: A = L.undefined() sage: B = L.undefined() sage: C = L.undefined() - sage: FA = (A^2 + B^2)*z^4 - sage: FB = A*B*z^3 - sage: FC = (A + B + C)*z^4 + sage: FA = (A^2 + B^2)*z^2 + sage: FB = A*B*z + sage: FC = (A + B + C)*z^2 sage: A.define_implicitly(FA, [0,0,0]) sage: B.define_implicitly(FB, [0,0]) sage: C.define_implicitly(FC, [0,0]) sage: B[2] - sage: L. = LazyPowerSeriesRing(QQ) + sage: L. = LazyPowerSeriesRing(QQ) sage: A = L.undefined() sage: B = L.undefined() sage: C = L.undefined() @@ -1744,6 +1744,15 @@ def define_implicitly(self, eqn, initial_values=None): sage: D.define_implicitly(A + B + C + D, [0,0]) sage: B[2] + sage: L. = LazyPowerSeriesRing(QQ) + sage: A = L.undefined() + sage: B = L.undefined() + sage: C = L.undefined() + sage: A.define_implicitly(B - C - 1) + sage: B.define_implicitly(B*z + 2*C + 1) + sage: C.define_implicitly(A + 2*C + 1) + sage: A[0] + """ if (not isinstance(self._coeff_stream, Stream_uninitialized) or self._coeff_stream._target is not None From a046fd0cd10099fd62cef398d4e4ec0acc8116b0 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Sun, 7 Jan 2024 12:10:53 +0100 Subject: [PATCH 025/117] initial commit to solve also systems of functional equations --- src/sage/data_structures/stream.py | 191 +++++++++++++------------ src/sage/rings/lazy_series.py | 219 +--------------------------- src/sage/rings/lazy_series_ring.py | 222 +++++++++++++++++++++++++++++ 3 files changed, 319 insertions(+), 313 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index 2c31d86b3ef..502a3395aa8 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -1241,6 +1241,7 @@ def get_variable(P): Return the first variable with index not in ``STREAM_UNINITIALIZED_VARIABLES``. + We need a different dictionary for each base ring. """ vars = STREAM_UNINITIALIZED_VARIABLES[P] for i in range(P._max+2): @@ -1287,7 +1288,7 @@ def __init__(self, approximate_order, true_order=False): sage: TestSuite(C).run(skip="_test_pickling") """ self._target = None - self._F = None + self._eqs = None if approximate_order is None: raise ValueError("the valuation must be specified for undefined series") super().__init__(true_order) @@ -1315,8 +1316,8 @@ def input_streams(self): """ if self._target is not None: return [self._target] - if self._F is not None: - return [self._F] + if self._eqs is not None: + return self._eqs return [] def define(self, target): @@ -1336,23 +1337,20 @@ def define(self, target): self._cache = list() self._iter = self.iterate_coefficients() - def define_implicitly(self, F, initial_values, R): + def define_implicitly(self, series, initial_values, equations, last_equation_used, R): r""" - Define ``self`` via ``F == 0``. + Define ``self`` via ``equations == 0``. INPUT: - - ``F`` -- a stream + - ``series`` -- a list of series + - ``equations`` -- a list of equations defining the series - ``initial_values`` -- a list specifying ``self[0], self[1], ...`` - ``R`` -- the coefficient ring - EXAMPLES:: """ assert self._target is None - if initial_values is None: - initial_values = [] - for i, val in enumerate(initial_values): if val: self._approximate_order += i @@ -1363,18 +1361,15 @@ def define_implicitly(self, F, initial_values, R): self._approximate_order += len(initial_values) self._cache = [] - self._F = F self._base = R # we use a silly variable name, because InfinitePolynomialRing is cached self._P = InfinitePolynomialRing(self._base, names=("FESDUMMY",), implementation='dense') self._PFF = self._P.fraction_field() - self._variables = set() # variables used for this stream - self._uncomputed = True - self._last_eq_n = self._F._approximate_order - 1 # the index of the last equation we used - self._n = self._approximate_order + len(self._cache) - 1 # the index of the last coefficient we know - + self._eqs = equations + self._last_eqs_n = last_equation_used # the indices of the last equations we used + self._series = series @lazy_attribute def _input_streams(self): @@ -1382,9 +1377,11 @@ def _input_streams(self): Return the list of streams which have a cache and ``self`` depends on. - All caches must have been created before this is called. + ``self`` is the first stream in this list. - EXAMPLES:: + All caches must have been created before this is called. + Does this mean that this should only be called after the + first invocation of `_compute`? """ known = [self] todo = [self] @@ -1399,7 +1396,11 @@ def _input_streams(self): @lazy_attribute def _good_cache(self): r""" - The number of coefficients that have been substituted already. + The number of coefficients in each input stream - 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. """ return [0 for c in self._input_streams] @@ -1439,64 +1440,40 @@ def __getitem__(self, n): return ZZ.zero() -# if target is dense, we do not need to duplicate its cache -# for i in range(self._n+1, n): -# self._target[i] -# self._n = n -# return self._target[n] - # define_implicitly - if self._n >= n: + if self._good_cache[0] >= n - self._approximate_order + 1: return self._cache[n - self._approximate_order] if self._uncomputed: - self._uncomputed = False - while not self._true_order and n >= self._approximate_order: - for k in range(self._n+1, n+1): - v, val = self._compute() - if val: - self._true_order = True - self._cache[-1] = val - else: - self._approximate_order += 1 - del self._cache[-1] - self._subs_in_caches(v, val) - self._n += 1 - - if self._true_order: - for k in range(self._n+1, n+1): - v, val = self._compute() - self._cache[-1] = val - self._subs_in_caches(v, val) - self._n += 1 - self._uncomputed = True - - if len(self._cache) == n - self._approximate_order + 1: - if n >= self._approximate_order: - return self._cache[n - self._approximate_order] - return ZZ.zero() + 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 len(self._cache) >= n - self._approximate_order + 1: + return self._cache[n - self._approximate_order] x = get_variable(self._P) - self._variables.add(x) 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. + streams and update ``self._good_cache``. INPUT: - ``var``, a variable - ``val``, the value that should replace the variable - - EXAMPLES:: - """ var_p = var.polynomial() def subs(cache, k): c = cache[k] + # TODO: we may want to insist that all coefficients of a + # stream have a parent if hasattr(c, "parent"): if c.parent() is self._PFF: num = c.numerator() @@ -1509,59 +1486,81 @@ def subs(cache, k): elif c.parent() is self._P and var_p in c.variables(): new = c.subs({var: val}) else: - return + return c in self._base if new in self._base: cache[k] = self._base(new) + return True else: cache[k] = new + return False for j, s in enumerate(self._input_streams): m = len(s._cache) - self._good_cache[j] + good = m # last index of cache element still containing variables if s._is_sparse: - for _, i in zip(range(m), reversed(s._cache)): - subs(s._cache, i) + for idx, i in zip(range(m), reversed(s._cache)): + if not subs(s._cache, i): + good = m - idx - 1 else: for i in range(-m, 0): - subs(s._cache, i) - self._good_cache[j] += m + if not subs(s._cache, i): + good = -i - 1 + self._good_cache[j] += good STREAM_UNINITIALIZED_VARIABLES[self._P].remove(var) def _compute(self): """ Solve the next equations, until the next variable is determined. """ - while True: - self._last_eq_n += 1 - coeff = self._F[self._last_eq_n] - if coeff.parent() is self._PFF: - coeff = coeff.numerator() - else: - coeff = self._P(coeff) - V = self._variables.intersection(InfinitePolynomial_dense(self._P, v) - for v in coeff.variables()) - - if len(V) > 1: - raise ValueError(f"unable to determine a unique solution in degree {self._last_eq_n}, the variables are {V}, the equation is {coeff} == 0") - - if not V: - if coeff: - if coeff in self._base: - raise ValueError(f"no solution in degree {self._last_eq_n} as {coeff} != 0") - # should do some substitution coeff = 0, but unclear which variable to extract - continue - - var = V.pop() - c = coeff.polynomial() - v = c.parent()(var.polynomial()) - d = c.degree(v) - if d == 1: - val = self._PFF(- c.coefficient({v: 0}) / c.coefficient({v: 1})) - elif c.is_monomial() and c.coefficient({v: d}) in self._base: - val = self._base.zero() - else: - raise ValueError(f"unable to determine a unique solution in degree {self._last_eq_n}, the variable is {var}, the equation is {coeff} == 0") - - return var, val + # determine the next linear equations + coeffs = [] + for i, eq in enumerate(self._eqs): + while True: + self._last_eqs_n[i] += 1 + coeff = eq[self._last_eqs_n[i]] + if coeff.parent() is self._PFF: + coeff = coeff.numerator() + else: + coeff = self._P(coeff) + V = coeff.variables() + if not V: + if coeff: + raise ValueError(f"no solution in degree {self._last_eqs_n} as {coeff} != 0") + else: + continue + if coeff.degree() <= 1: + coeffs.append(coeff) + else: + self._last_eqs_n[i] -= 1 + break + if not coeffs: + raise ValueError("No linear equations") + + # solve + from sage.structure.sequence import Sequence + coeffs = Sequence([coeff.polynomial() for coeff in coeffs]) + m1, v1 = coeffs.coefficient_matrix() + m = m1.matrix_from_columns([i for i, (c,) in enumerate(v1) + if c.degree() == 1]) + b = -m1.matrix_from_columns([i for i, (c,) in enumerate(v1) + if c.degree() == 0]) + + if not b: + from sage.modules.free_module_element import zero_vector + b = zero_vector(m.nrows()) + x = m.solve_right(b) + k = m.right_kernel_matrix() + + # substitute + bad = True + for i, ((c,), (y,)) in enumerate(zip(v1, x)): + if k.column(i).is_zero(): + var = self._P(c) + val = self._base(y) + self._subs_in_caches(var, val) + bad = False + if bad: + raise ValueError("Could not determine any coefficients") def iterate_coefficients(self): """ @@ -1604,14 +1603,14 @@ def is_uninitialized(self): sage: T._coeff_stream.is_uninitialized() True """ - if self._target is None and self._F 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 if self._target is None: - result = self._F.is_uninitialized() + result = False else: result = self._target.is_uninitialized() self._initializing = False @@ -3850,6 +3849,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` diff --git a/src/sage/rings/lazy_series.py b/src/sage/rings/lazy_series.py index 8be6e02613a..fd8de2ac50f 100644 --- a/src/sage/rings/lazy_series.py +++ b/src/sage/rings/lazy_series.py @@ -1534,7 +1534,7 @@ def define(self, s): """ if (not isinstance(self._coeff_stream, Stream_uninitialized) or self._coeff_stream._target is not None - or self._coeff_stream._F is not None): + or self._coeff_stream._eqs is not None): raise ValueError("series already defined") if not isinstance(s, LazyModuleElement): @@ -1551,223 +1551,6 @@ def define(self, s): # an alias for compatibility with padics set = define - def define_implicitly(self, eqn, initial_values=None): - r""" - Define ``self`` as the series that solves the functional - equation ``eqn == 0`` with ``initial_values``. - - EXAMPLES:: - - sage: L. = LazyPowerSeriesRing(QQ) - sage: f = L.undefined(0) - sage: F = diff(f, 2) - sage: f.define_implicitly(F + f, [1, 0]) - 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: f.define_implicitly(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: g.define_implicitly(e, [1, 2]) - - 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: L. = LazyPowerSeriesRing(SR) - sage: G = L.undefined(0) - sage: G.define_implicitly(diff(G) - exp(-G(-z)), [ln(2)]) - 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: G.define_implicitly(diff(G) - exp(-G(-z)), [log(2)]) - 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: R.define_implicitly((1-q*x)*R - (y*q*x+y)*R(q*x) - q*x*R*R(q*x) - x*y*q, [0]) - 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: Rp.define_implicitly((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: F.define_implicitly(F(2*z) - (1+exp(x*z)+exp(y*z))*F - exp((x+y)*z)*F(-z), [0, f1, f2]) - 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: F = L.undefined() - sage: F.define_implicitly(F(2*z) - (1+exp(x*z)+exp(y*z))*F - exp((x+y)*z)*F(-z)) - sage: F - - - sage: F = L.undefined() - sage: F.define_implicitly(F(2*z) - (1+exp(x*z)+exp(y*z))*F - exp((x+y)*z)*F(-z), [0, f1]) - sage: F - - - Laurent series examples:: - - sage: L. = LazyLaurentSeriesRing(QQ) - sage: f = L.undefined(-1) - sage: f.define_implicitly(2+z*f(z^2) - f, [5]) - 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: g.define_implicitly(2+z*g(z^2) - g, [5]) - sage: g - - - TESTS:: - - sage: L. = LazyPowerSeriesRing(QQ) - sage: f = L.undefined(1) - sage: f.define_implicitly(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: f.define_implicitly(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 two coupled functional equations:: - - 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: A.define_implicitly(FA, [1]) - sage: B.define_implicitly(FB, [1]) - 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: A.define_implicitly(FA, [1]) - sage: B.define_implicitly(FB, [1]) - 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() - sage: B = L.undefined() - sage: C = L.undefined() - sage: FA = (A^2 + B^2)*z^2 - sage: FB = A*B*z - sage: FC = (A + B + C)*z^2 - sage: A.define_implicitly(FA, [0,0,0]) - sage: B.define_implicitly(FB, [0,0]) - sage: C.define_implicitly(FC, [0,0]) - sage: B[2] - - sage: L. = LazyPowerSeriesRing(QQ) - sage: A = L.undefined() - sage: B = L.undefined() - sage: C = L.undefined() - sage: D = L.undefined() - sage: A.define_implicitly(C^2 + D^2, [0,0,0]) - sage: B.define_implicitly(A + B + C + D, [0,0]) - sage: C.define_implicitly(A*D, [0,0]) - sage: D.define_implicitly(A + B + C + D, [0,0]) - sage: B[2] - - sage: L. = LazyPowerSeriesRing(QQ) - sage: A = L.undefined() - sage: B = L.undefined() - sage: C = L.undefined() - sage: A.define_implicitly(B - C - 1) - sage: B.define_implicitly(B*z + 2*C + 1) - sage: C.define_implicitly(A + 2*C + 1) - sage: A[0] - - """ - if (not isinstance(self._coeff_stream, Stream_uninitialized) - or self._coeff_stream._target is not None - or self._coeff_stream._F is not None): - raise ValueError("series already defined") - - P = self.parent() - F = P(eqn)._coeff_stream - R = P.base_ring() - if initial_values is None: - initial_values = [] - else: - initial_values = [R(val) for val in initial_values] - self._coeff_stream.define_implicitly(F, initial_values, R) - def _repr_(self): r""" Return a string representation of ``self``. diff --git a/src/sage/rings/lazy_series_ring.py b/src/sage/rings/lazy_series_ring.py index c3059bf936a..1e574c8f374 100644 --- a/src/sage/rings/lazy_series_ring.py +++ b/src/sage/rings/lazy_series_ring.py @@ -659,6 +659,228 @@ def undefined(self, valuation=None): unknown = undefined + def define_implicitly(self, series, equations): + 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 + + 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: 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: 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 + + + 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 two 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() + sage: B = L.undefined() + sage: C = L.undefined() + 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, [0,0,0]), (B, [0,0]), (C, [0,0])], [FA, FB, FC]) + sage: B[2] + + 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] + + 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) + """ + 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] + # common state for all series + eqs = [eq._coeff_stream for eq in equations] + last_eq_used = [eq._approximate_order - 1 for eq in eqs] + for f, ic in zip(s, ics): + f.define_implicitly(s, ic, eqs, last_eq_used, self.base_ring()) + class options(GlobalOptions): r""" Set and display the options for lazy series. From a464a615d11116a67c0432642b345a7b640a9e0a Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Sun, 7 Jan 2024 19:01:38 +0100 Subject: [PATCH 026/117] work around bug in InfinitePolynomialRing, better error messages --- src/sage/data_structures/stream.py | 47 ++++++++++++++++++++---------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index 502a3395aa8..8968f1a4dc1 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -1514,6 +1514,7 @@ def _compute(self): """ # determine the next linear equations coeffs = [] + non_linear_coeffs = [] for i, eq in enumerate(self._eqs): while True: self._last_eqs_n[i] += 1 @@ -1525,42 +1526,56 @@ def _compute(self): V = coeff.variables() if not V: if coeff: - raise ValueError(f"no solution in degree {self._last_eqs_n} as {coeff} != 0") + if len(self._last_eqs_n) == 1: + raise ValueError(f"no solution in degree {self._last_eqs_n[0]} as {coeff} != 0") + raise ValueError(f"no solution in degrees {self._last_eqs_n} as {coeff} != 0") else: continue if coeff.degree() <= 1: coeffs.append(coeff) else: + # nonlinear equations must not be discarded, we + # keep them to improve any error messages + non_linear_coeffs.append(coeff) self._last_eqs_n[i] -= 1 break if not coeffs: - raise ValueError("No linear equations") + if len(self._last_eqs_n) == 1: + raise ValueError(f"no linear equations in degree {self._last_eqs_n[0]}: {non_linear_coeffs}") + raise ValueError(f"no linear equations in degrees {self._last_eqs_n}: {non_linear_coeffs}") # solve - from sage.structure.sequence import Sequence - coeffs = Sequence([coeff.polynomial() for coeff in coeffs]) - m1, v1 = coeffs.coefficient_matrix() - m = m1.matrix_from_columns([i for i, (c,) in enumerate(v1) - if c.degree() == 1]) - b = -m1.matrix_from_columns([i for i, (c,) in enumerate(v1) - if c.degree() == 0]) - - if not b: + from sage.rings.polynomial.multi_polynomial_sequence import PolynomialSequence + eqs = PolynomialSequence([coeff.polynomial() for coeff in coeffs]) + m1, v1 = eqs.coefficient_matrix() + # there should be at most one entry in v1 of degree 0 + # v1 is a matrix, not a vector + for j, (c,) in enumerate(v1): + if c.degree() == 0: + b = -m1.column(j) + m = m1.matrix_from_columns([i for i in range(v1.nrows()) 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(m.nrows()) + b = zero_vector(m1.nrows()) + m = m1 + v = v1.list() x = m.solve_right(b) k = m.right_kernel_matrix() - # substitute + from sage.rings.polynomial.infinite_polynomial_element import InfinitePolynomial_dense bad = True - for i, ((c,), (y,)) in enumerate(zip(v1, x)): + for i, (c, y) in enumerate(zip(v, x)): if k.column(i).is_zero(): - var = self._P(c) + var = InfinitePolynomial_dense(self._P, c) val = self._base(y) self._subs_in_caches(var, val) bad = False if bad: - raise ValueError("Could not determine any coefficients") + if len(self._last_eqs_n) == 1: + raise ValueError(f"could not determine any coefficients in degree {self._last_eqs_n[0]} - equations are {coeffs + non_linear_coeffs}") + raise ValueError(f"could not determine any coefficients in degrees {self._last_eqs_n} - equations are {coeffs + non_linear_coeffs}") def iterate_coefficients(self): """ From b9f29ad5e7bcd4f2a50b0437ff13a4f7a803d76f Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Mon, 8 Jan 2024 11:02:23 +0100 Subject: [PATCH 027/117] fix bad counting in _subs_in_caches --- src/sage/data_structures/stream.py | 44 +++++++++++++++++++++++++----- src/sage/rings/lazy_series_ring.py | 17 ++++++++---- 2 files changed, 48 insertions(+), 13 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index 8968f1a4dc1..92524c86519 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -1368,7 +1368,7 @@ def define_implicitly(self, series, initial_values, equations, last_equation_use self._PFF = self._P.fraction_field() self._uncomputed = True self._eqs = equations - self._last_eqs_n = last_equation_used # the indices of the last equations we used + self._last_eqs_n = last_equation_used # the indices of the last equations we used self._series = series @lazy_attribute @@ -1401,8 +1401,23 @@ def _good_cache(self): This is used in :meth:`_subs_in_caches` to only substitute items that may contain undetermined coefficients. + + It might be better to share this across all uninitialized + series in one system. """ - return [0 for c in self._input_streams] + g = [] + for c in self._input_streams: + if c._is_sparse: + vals = c._cache.values() + else: + vals = c._cache + i = 0 + for v in vals: + if v not in self._base: + break + i += 1 + g.append(i) + return g def __getitem__(self, n): """ @@ -1496,15 +1511,20 @@ def subs(cache, k): for j, s in enumerate(self._input_streams): m = len(s._cache) - self._good_cache[j] - good = m # last index of cache element still containing variables + good = m # last index of cache element still containing + # variables + + # TODO: document why we first substitute in the last + # element of the cache in the sparse case, but not in the + # dense case if s._is_sparse: for idx, i in zip(range(m), reversed(s._cache)): if not subs(s._cache, i): good = m - idx - 1 else: - for i in range(-m, 0): + for i in range(-1, -m-1, -1): if not subs(s._cache, i): - good = -i - 1 + good = m + i self._good_cache[j] += good STREAM_UNINITIALIZED_VARIABLES[self._P].remove(var) @@ -1512,6 +1532,10 @@ def _compute(self): """ Solve the next equations, until the next variable is determined. """ + # this is needed to work around a bug prohibiting conversion + # of variables into the InfinitePolynomialRing over the + # SymbolicRing + from sage.rings.polynomial.infinite_polynomial_element import InfinitePolynomial_dense # determine the next linear equations coeffs = [] non_linear_coeffs = [] @@ -1533,6 +1557,14 @@ def _compute(self): continue if coeff.degree() <= 1: coeffs.append(coeff) + elif coeff.is_monomial() and sum(1 for d in coeff.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? + c = coeff.coefficients()[0] + v = InfinitePolynomial_dense(self._P, coeff.variables()[0]) + coeffs.append(c * v) else: # nonlinear equations must not be discarded, we # keep them to improve any error messages @@ -1543,7 +1575,6 @@ def _compute(self): if len(self._last_eqs_n) == 1: raise ValueError(f"no linear equations in degree {self._last_eqs_n[0]}: {non_linear_coeffs}") raise ValueError(f"no linear equations in degrees {self._last_eqs_n}: {non_linear_coeffs}") - # solve from sage.rings.polynomial.multi_polynomial_sequence import PolynomialSequence eqs = PolynomialSequence([coeff.polynomial() for coeff in coeffs]) @@ -1564,7 +1595,6 @@ def _compute(self): x = m.solve_right(b) k = m.right_kernel_matrix() # substitute - from sage.rings.polynomial.infinite_polynomial_element import InfinitePolynomial_dense bad = True for i, (c, y) in enumerate(zip(v, x)): if k.column(i).is_zero(): diff --git a/src/sage/rings/lazy_series_ring.py b/src/sage/rings/lazy_series_ring.py index 1e574c8f374..127b26184b6 100644 --- a/src/sage/rings/lazy_series_ring.py +++ b/src/sage/rings/lazy_series_ring.py @@ -844,14 +844,19 @@ def define_implicitly(self, series, equations): O(z^7) sage: L. = LazyPowerSeriesRing(QQ) - sage: A = L.undefined() - sage: B = L.undefined() - sage: C = L.undefined() + 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, [0,0,0]), (B, [0,0]), (C, [0,0])], [FA, FB, FC]) - sage: B[2] + sage: L.define_implicitly([A, B, C], [FA, FB, FC]) + sage: A + O(z^10) + sage: B + O(z^9) + sage: C + O(z^9) sage: L. = LazyPowerSeriesRing(QQ) sage: A = L.undefined() @@ -859,7 +864,7 @@ def define_implicitly(self, series, equations): 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] + sage: B[2] # known bug, not tested sage: L. = LazyPowerSeriesRing(QQ) sage: A = L.undefined() From 9ab26c220b89ed342ef5a8b5c58d3d48bb31fe53 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Mon, 8 Jan 2024 14:56:30 +0100 Subject: [PATCH 028/117] fix to erroneous doctests - the bug was in the old version --- src/sage/rings/lazy_series_ring.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/rings/lazy_series_ring.py b/src/sage/rings/lazy_series_ring.py index 127b26184b6..f89a435dac2 100644 --- a/src/sage/rings/lazy_series_ring.py +++ b/src/sage/rings/lazy_series_ring.py @@ -764,12 +764,12 @@ def define_implicitly(self, series, equations): 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:: From 409c21e151bd06779c5bfad0fa9335579b9cdfb8 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Thu, 11 Jan 2024 16:50:37 +0100 Subject: [PATCH 029/117] fix bookkeeping --- src/sage/data_structures/stream.py | 97 +++++++++++++++++++----------- src/sage/rings/lazy_series_ring.py | 13 ++-- 2 files changed, 67 insertions(+), 43 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index 92524c86519..a959fbb4e00 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -1337,7 +1337,7 @@ def define(self, target): self._cache = list() self._iter = self.iterate_coefficients() - def define_implicitly(self, series, initial_values, equations, last_equation_used, R): + def define_implicitly(self, series, initial_values, equations, R): r""" Define ``self`` via ``equations == 0``. @@ -1346,7 +1346,7 @@ def define_implicitly(self, series, initial_values, equations, last_equation_use - ``series`` -- a list of series - ``equations`` -- a list of equations defining the series - ``initial_values`` -- a list specifying ``self[0], self[1], ...`` - - ``R`` -- the coefficient ring + - ``R`` -- the ring containing the coefficients (after substitution) """ assert self._target is None @@ -1368,7 +1368,6 @@ def define_implicitly(self, series, initial_values, equations, last_equation_use self._PFF = self._P.fraction_field() self._uncomputed = True self._eqs = equations - self._last_eqs_n = last_equation_used # the indices of the last equations we used self._series = series @lazy_attribute @@ -1456,7 +1455,7 @@ def __getitem__(self, n): return ZZ.zero() # define_implicitly - if self._good_cache[0] >= n - self._approximate_order + 1: + if self._good_cache[0] > n - self._approximate_order: return self._cache[n - self._approximate_order] if self._uncomputed: @@ -1467,7 +1466,9 @@ def __getitem__(self, n): for f in self._series: f._uncomputed = True - if len(self._cache) >= n - self._approximate_order + 1: + if n < self._approximate_order: + return ZZ.zero() + if len(self._cache) > n - self._approximate_order: return self._cache[n - self._approximate_order] x = get_variable(self._P) @@ -1497,35 +1498,54 @@ def subs(cache, k): den = c.denominator() if var_p in den.variables(): num = den.subs({var: val}) - new = num/den + new = num / den elif c.parent() is self._P and var_p in c.variables(): new = c.subs({var: val}) else: - return c in self._base + return if new in self._base: cache[k] = self._base(new) - return True else: cache[k] = new - return False for j, s in enumerate(self._input_streams): m = len(s._cache) - self._good_cache[j] - good = m # last index of cache element still containing - # variables - - # TODO: document why we first substitute in the last - # element of the cache in the sparse case, but not in the - # dense case if s._is_sparse: - for idx, i in zip(range(m), reversed(s._cache)): - if not subs(s._cache, i): - good = m - idx - 1 + # 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: - for i in range(-1, -m-1, -1): - if not subs(s._cache, i): - good = m + i + indices = range(-1, -m-1, -1) + # determine last good element + good = m + for i0, i in enumerate(indices): + subs(s._cache, i) + if s._cache[i] not in self._base: + good = m - i0 - 1 self._good_cache[j] += good + # fix approximate_order and true_order + ao = s._approximate_order + if s._is_sparse: + while ao in s._cache: + if s._cache[ao]: + if s._cache[ao] in self._base: + s._true_order = True + break + del s._cache[ao] + self._good_cache[j] -= 1 + ao += 1 + else: + while s._cache: + if s._cache[0]: + if s._cache[0] in self._base: + s._true_order = True + break + del s._cache[0] + self._good_cache[j] -= 1 + ao += 1 + s._approximate_order = ao + STREAM_UNINITIALIZED_VARIABLES[self._P].remove(var) def _compute(self): @@ -1541,20 +1561,23 @@ def _compute(self): non_linear_coeffs = [] for i, eq in enumerate(self._eqs): while True: - self._last_eqs_n[i] += 1 - coeff = eq[self._last_eqs_n[i]] + ao = eq._approximate_order + coeff = eq[ao] + if not coeff: + # it may or may not be the case that the + # _approximate_order is advanced by __getitem__ + if eq._approximate_order == ao: + eq._approximate_order += 1 + continue if coeff.parent() is self._PFF: coeff = coeff.numerator() else: coeff = self._P(coeff) V = coeff.variables() if not V: - if coeff: - if len(self._last_eqs_n) == 1: - raise ValueError(f"no solution in degree {self._last_eqs_n[0]} as {coeff} != 0") - raise ValueError(f"no solution in degrees {self._last_eqs_n} as {coeff} != 0") - else: - continue + if len(self._eqs) == 1: + raise ValueError(f"no solution as {coeff} != 0 in the equation at degree {eq._approximate_order}") + raise ValueError(f"no solution as {coeff} != 0 in equation {i} at degree {eq._approximate_order}") if coeff.degree() <= 1: coeffs.append(coeff) elif coeff.is_monomial() and sum(1 for d in coeff.degrees() if d): @@ -1569,12 +1592,12 @@ def _compute(self): # nonlinear equations must not be discarded, we # keep them to improve any error messages non_linear_coeffs.append(coeff) - self._last_eqs_n[i] -= 1 break if not coeffs: - if len(self._last_eqs_n) == 1: - raise ValueError(f"no linear equations in degree {self._last_eqs_n[0]}: {non_linear_coeffs}") - raise ValueError(f"no linear equations in degrees {self._last_eqs_n}: {non_linear_coeffs}") + if len(self._eqs) == 1: + raise ValueError(f"there are no linear equations in degree {self._approximate_order}: {non_linear_coeffs}") + degrees = [eq._approximate_order for eq in self._eqs] + raise ValueError(f"there are no linear equations in degrees {degrees}: {non_linear_coeffs}") # solve from sage.rings.polynomial.multi_polynomial_sequence import PolynomialSequence eqs = PolynomialSequence([coeff.polynomial() for coeff in coeffs]) @@ -1603,9 +1626,11 @@ def _compute(self): self._subs_in_caches(var, val) bad = False if bad: - if len(self._last_eqs_n) == 1: - raise ValueError(f"could not determine any coefficients in degree {self._last_eqs_n[0]} - equations are {coeffs + non_linear_coeffs}") - raise ValueError(f"could not determine any coefficients in degrees {self._last_eqs_n} - equations are {coeffs + non_linear_coeffs}") + if len(self._eqs) == 1: + assert len(coeffs) + len(non_linear_coeffs) == 1 + raise ValueError(f"could not determine any coefficients using the equation in degree {self._eqs[0]._approximate_order}: {(coeffs + non_linear_coeffs)[0]}") + degrees = [eq._approximate_order for eq in self._eqs] + raise ValueError(f"could not determine any coefficients using the equations in degrees {degrees}: {coeffs + non_linear_coeffs}") def iterate_coefficients(self): """ diff --git a/src/sage/rings/lazy_series_ring.py b/src/sage/rings/lazy_series_ring.py index f89a435dac2..f35a2cd7d34 100644 --- a/src/sage/rings/lazy_series_ring.py +++ b/src/sage/rings/lazy_series_ring.py @@ -764,12 +764,12 @@ def define_implicitly(self, series, equations): 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:: @@ -784,7 +784,7 @@ def define_implicitly(self, series, equations): sage: g = L.undefined(-2) sage: L.define_implicitly([(g, [5])], [2+z*g(z^2) - g]) sage: g - + TESTS:: @@ -854,9 +854,9 @@ def define_implicitly(self, series, equations): sage: A O(z^10) sage: B - O(z^9) + O(z^16) sage: C - O(z^9) + O(z^23) sage: L. = LazyPowerSeriesRing(QQ) sage: A = L.undefined() @@ -882,9 +882,8 @@ def define_implicitly(self, series, equations): for a in series] # common state for all series eqs = [eq._coeff_stream for eq in equations] - last_eq_used = [eq._approximate_order - 1 for eq in eqs] for f, ic in zip(s, ics): - f.define_implicitly(s, ic, eqs, last_eq_used, self.base_ring()) + f.define_implicitly(s, ic, eqs, self._internal_poly_ring.base_ring()) class options(GlobalOptions): r""" From 92d0abdcd1815d32ffeda5b96e91279f8dfe323d Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Thu, 11 Jan 2024 20:21:37 +0100 Subject: [PATCH 030/117] slightly simplify logic --- src/sage/data_structures/stream.py | 61 +++++++++++++++--------------- 1 file changed, 31 insertions(+), 30 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index a959fbb4e00..6383cb6e628 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -1563,36 +1563,37 @@ def _compute(self): while True: ao = eq._approximate_order coeff = eq[ao] - if not coeff: - # it may or may not be the case that the - # _approximate_order is advanced by __getitem__ - if eq._approximate_order == ao: - eq._approximate_order += 1 - continue - if coeff.parent() is self._PFF: - coeff = coeff.numerator() - else: - coeff = self._P(coeff) - V = coeff.variables() - if not V: - if len(self._eqs) == 1: - raise ValueError(f"no solution as {coeff} != 0 in the equation at degree {eq._approximate_order}") - raise ValueError(f"no solution as {coeff} != 0 in equation {i} at degree {eq._approximate_order}") - if coeff.degree() <= 1: - coeffs.append(coeff) - elif coeff.is_monomial() and sum(1 for d in coeff.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? - c = coeff.coefficients()[0] - v = InfinitePolynomial_dense(self._P, coeff.variables()[0]) - coeffs.append(c * v) - else: - # nonlinear equations must not be discarded, we - # keep them to improve any error messages - non_linear_coeffs.append(coeff) - break + if coeff: + break + # it may or may not be the case that the + # _approximate_order is advanced by __getitem__ + if eq._approximate_order == ao: + eq._approximate_order += 1 + + if coeff.parent() is self._PFF: + coeff = coeff.numerator() + else: + coeff = self._P(coeff) + V = coeff.variables() + if not V: + if len(self._eqs) == 1: + raise ValueError(f"no solution as {coeff} != 0 in the equation at degree {eq._approximate_order}") + raise ValueError(f"no solution as {coeff} != 0 in equation {i} at degree {eq._approximate_order}") + if coeff.degree() <= 1: + coeffs.append(coeff) + elif coeff.is_monomial() and sum(1 for d in coeff.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? + c = coeff.coefficients()[0] + v = InfinitePolynomial_dense(self._P, coeff.variables()[0]) + coeffs.append(c * v) + else: + # nonlinear equations must not be discarded, we + # collect them to improve any error messages + non_linear_coeffs.append(coeff) + if not coeffs: if len(self._eqs) == 1: raise ValueError(f"there are no linear equations in degree {self._approximate_order}: {non_linear_coeffs}") From bf700ab91c00aa3eb7d80d37b7febdfee39edca5 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Fri, 12 Jan 2024 09:52:51 +0100 Subject: [PATCH 031/117] fix typo --- src/sage/data_structures/stream.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index 6383cb6e628..05258c09a31 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -1497,7 +1497,7 @@ def subs(cache, k): num = num.subs({var: val}) den = c.denominator() if var_p in den.variables(): - num = den.subs({var: val}) + den = den.subs({var: val}) new = num / den elif c.parent() is self._P and var_p in c.variables(): new = c.subs({var: val}) From 62574fe4f4a93a0e50fbb75a1e529f13be83b7de Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Fri, 12 Jan 2024 11:07:55 +0100 Subject: [PATCH 032/117] bypass buggy subs in InfinitePolynomialRing --- src/sage/data_structures/stream.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index 05258c09a31..31f0fd27e01 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -1485,6 +1485,7 @@ def _subs_in_caches(self, var, val): - ``var``, a variable - ``val``, the value that should replace the variable """ + from sage.rings.polynomial.infinite_polynomial_element import InfinitePolynomial_dense var_p = var.polynomial() def subs(cache, k): c = cache[k] @@ -1492,15 +1493,23 @@ def subs(cache, k): # stream have a parent if hasattr(c, "parent"): if c.parent() is self._PFF: + R = self._P.polynomial_ring() num = c.numerator() if var_p in num.variables(): - num = num.subs({var: val}) + d = {R(v): InfinitePolynomial_dense(self._P, v) for v in num.variables()} + d[R(var_p)] = val + num = R(num.polynomial()).subs(d) den = c.denominator() if var_p in den.variables(): - den = den.subs({var: val}) + d = {R(v): InfinitePolynomial_dense(self._P, v) for v in den.variables()} + d[R(var_p)] = val + den = R(den.polynomial()).subs(d) new = num / den elif c.parent() is self._P and var_p in c.variables(): - new = c.subs({var: val}) + R = self._P.polynomial_ring() + d = {R(v): InfinitePolynomial_dense(self._P, v) for v in c.variables()} + d[R(var_p)] = val + new = R(c.polynomial()).subs(d) else: return if new in self._base: From 80769fb50b1679452f4441f45acb5504505be8d3 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Fri, 12 Jan 2024 19:37:41 +0100 Subject: [PATCH 033/117] more work on the multivariate case --- src/sage/data_structures/stream.py | 129 +++++++++--------- src/sage/rings/lazy_series.py | 4 + src/sage/rings/lazy_series_ring.py | 79 ++++++++++- .../polynomial/multi_polynomial_element.py | 5 +- 4 files changed, 152 insertions(+), 65 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index 31f0fd27e01..7366d330e33 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -1328,8 +1328,6 @@ def define(self, target): - ``target`` -- a stream - EXAMPLES:: - """ self._target = target self._n = self._approximate_order - 1 # the largest index of a coefficient we know @@ -1337,7 +1335,8 @@ def define(self, target): self._cache = list() self._iter = self.iterate_coefficients() - def define_implicitly(self, series, initial_values, equations, R): + def define_implicitly(self, series, initial_values, equations, + base_ring, coefficient_ring, terms_of_degree): r""" Define ``self`` via ``equations == 0``. @@ -1347,6 +1346,7 @@ def define_implicitly(self, series, initial_values, equations, R): - ``equations`` -- a list of equations defining the series - ``initial_values`` -- a list specifying ``self[0], self[1], ...`` - ``R`` -- the ring containing the coefficients (after substitution) + - ``terms_of_degree`` -- a function returning the list of terms of a given degree """ assert self._target is None @@ -1361,14 +1361,16 @@ def define_implicitly(self, series, initial_values, equations, R): self._approximate_order += len(initial_values) self._cache = [] - self._base = R + self._coefficient_ring = coefficient_ring + self._base_ring = base_ring # we use a silly variable name, because InfinitePolynomialRing is cached - self._P = InfinitePolynomialRing(self._base, names=("FESDUMMY",), + self._P = InfinitePolynomialRing(self._base_ring, names=("FESDUMMY",), implementation='dense') self._PFF = self._P.fraction_field() self._uncomputed = True self._eqs = equations self._series = series + self._terms_of_degree = terms_of_degree @lazy_attribute def _input_streams(self): @@ -1412,7 +1414,7 @@ def _good_cache(self): vals = c._cache i = 0 for v in vals: - if v not in self._base: + if v not in self._coefficient_ring: break i += 1 g.append(i) @@ -1471,7 +1473,8 @@ def __getitem__(self, n): if len(self._cache) > n - self._approximate_order: return self._cache[n - self._approximate_order] - x = get_variable(self._P) + x = sum(get_variable(self._P) * m + for m in self._terms_of_degree(n, self._P)) self._cache.append(x) return x @@ -1485,37 +1488,30 @@ def _subs_in_caches(self, var, val): - ``var``, a variable - ``val``, the value that should replace the variable """ - from sage.rings.polynomial.infinite_polynomial_element import InfinitePolynomial_dense var_p = var.polynomial() - def subs(cache, k): - c = cache[k] + def subs(poly): + R = self._P.polynomial_ring() + if var_p in poly.variables(): + d = {R(v): InfinitePolynomial_dense(self._P, v) + for v in poly.variables()} + d[R(var_p)] = val + return R(poly.polynomial()).subs(d) + return poly + + def subs_frac(c): # TODO: we may want to insist that all coefficients of a # stream have a parent if hasattr(c, "parent"): if c.parent() is self._PFF: - R = self._P.polynomial_ring() - num = c.numerator() - if var_p in num.variables(): - d = {R(v): InfinitePolynomial_dense(self._P, v) for v in num.variables()} - d[R(var_p)] = val - num = R(num.polynomial()).subs(d) - den = c.denominator() - if var_p in den.variables(): - d = {R(v): InfinitePolynomial_dense(self._P, v) for v in den.variables()} - d[R(var_p)] = val - den = R(den.polynomial()).subs(d) - new = num / den + new = subs(c.numerator()) / subs(c.denominator()) elif c.parent() is self._P and var_p in c.variables(): - R = self._P.polynomial_ring() - d = {R(v): InfinitePolynomial_dense(self._P, v) for v in c.variables()} - d[R(var_p)] = val - new = R(c.polynomial()).subs(d) + new = subs(c) else: - return - if new in self._base: - cache[k] = self._base(new) + return c + if new in self._coefficient_ring: + return self._coefficient_ring(new) else: - cache[k] = new + return new for j, s in enumerate(self._input_streams): m = len(s._cache) - self._good_cache[j] @@ -1529,8 +1525,12 @@ def subs(cache, k): # determine last good element good = m for i0, i in enumerate(indices): - subs(s._cache, i) - if s._cache[i] not in self._base: + if self._base_ring == self._coefficient_ring: + s._cache[i] = subs_frac(s._cache[i]) + else: + s._cache[i] = s._cache[i].map_coefficients(subs_frac) + + if s._cache[i] not in self._coefficient_ring: good = m - i0 - 1 self._good_cache[j] += good # fix approximate_order and true_order @@ -1538,7 +1538,7 @@ def subs(cache, k): if s._is_sparse: while ao in s._cache: if s._cache[ao]: - if s._cache[ao] in self._base: + if s._cache[ao] in self._coefficient_ring: s._true_order = True break del s._cache[ao] @@ -1547,7 +1547,7 @@ def subs(cache, k): else: while s._cache: if s._cache[0]: - if s._cache[0] in self._base: + if s._cache[0] in self._coefficient_ring: s._true_order = True break del s._cache[0] @@ -1561,10 +1561,6 @@ def _compute(self): """ Solve the next equations, until the next variable is determined. """ - # this is needed to work around a bug prohibiting conversion - # of variables into the InfinitePolynomialRing over the - # SymbolicRing - from sage.rings.polynomial.infinite_polynomial_element import InfinitePolynomial_dense # determine the next linear equations coeffs = [] non_linear_coeffs = [] @@ -1579,29 +1575,37 @@ def _compute(self): if eq._approximate_order == ao: eq._approximate_order += 1 - if coeff.parent() is self._PFF: - coeff = coeff.numerator() - else: - coeff = self._P(coeff) - V = coeff.variables() - if not V: - if len(self._eqs) == 1: - raise ValueError(f"no solution as {coeff} != 0 in the equation at degree {eq._approximate_order}") - raise ValueError(f"no solution as {coeff} != 0 in equation {i} at degree {eq._approximate_order}") - if coeff.degree() <= 1: - coeffs.append(coeff) - elif coeff.is_monomial() and sum(1 for d in coeff.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? - c = coeff.coefficients()[0] - v = InfinitePolynomial_dense(self._P, coeff.variables()[0]) - coeffs.append(c * v) + if self._base_ring == self._coefficient_ring: + lcoeff = [coeff] else: - # nonlinear equations must not be discarded, we - # collect them to improve any error messages - non_linear_coeffs.append(coeff) + # TODO: it is a coincidence that this currently + # exists in all examples + lcoeff = coeff.coefficients() + + for c in lcoeff: + if c.parent() is self._PFF: + c = c.numerator() + else: + c = self._P(c) + V = c.variables() + if not V: + if len(self._eqs) == 1: + raise ValueError(f"no solution as {coeff} != 0 in the equation at degree {eq._approximate_order}") + raise ValueError(f"no solution as {coeff} != 0 in equation {i} at degree {eq._approximate_order}") + if c.degree() <= 1: + coeffs.append(c) + elif c.is_monomial() and sum(1 for d in c.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 = c.coefficients()[0] + v = InfinitePolynomial_dense(self._P, c.variables()[0]) + coeffs.append(c1 * v) + else: + # nonlinear equations must not be discarded, we + # collect them to improve any error messages + non_linear_coeffs.append(c) if not coeffs: if len(self._eqs) == 1: @@ -1631,8 +1635,11 @@ def _compute(self): bad = True for i, (c, y) in enumerate(zip(v, x)): if k.column(i).is_zero(): + # work around a bug prohibiting conversion of + # variables into the InfinitePolynomialRing over the + # SymbolicRing var = InfinitePolynomial_dense(self._P, c) - val = self._base(y) + val = self._base_ring(y) self._subs_in_caches(var, val) bad = False if bad: diff --git a/src/sage/rings/lazy_series.py b/src/sage/rings/lazy_series.py index fd8de2ac50f..06639e817e4 100644 --- a/src/sage/rings/lazy_series.py +++ b/src/sage/rings/lazy_series.py @@ -5271,9 +5271,13 @@ def coefficient(n): def coefficient(n): r = R.zero() + P = self._coeff_stream[0].parent().base_ring() + g1 = [a.change_ring(P) for a in g] for i in range(n // gv + 1): # Make sure the element returned from the composition is in P + # NO, we must not do this, because of define_implicitly r += P(self[i](g))[n] +# r += (self._coeff_stream[i](g1))[n] return r coeff_stream = Stream_function(coefficient, P._sparse, sorder * gv) return P.element_class(P, coeff_stream) diff --git a/src/sage/rings/lazy_series_ring.py b/src/sage/rings/lazy_series_ring.py index f35a2cd7d34..cf1d0786151 100644 --- a/src/sage/rings/lazy_series_ring.py +++ b/src/sage/rings/lazy_series_ring.py @@ -659,6 +659,22 @@ def undefined(self, valuation=None): 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``. + """ + raise NotImplementedError + def define_implicitly(self, series, equations): r""" Define series by solving functional equations. @@ -873,6 +889,14 @@ def define_implicitly(self, series, equations): 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) + + A bivariate example:: + + sage: R. = LazyPowerSeriesRing(QQ) + sage: g = R.undefined() + sage: R.define_implicitly([g], [g - (z*q + z*g*~(1-g))]) + sage: g + """ s = [a[0]._coeff_stream if isinstance(a, (tuple, list)) else a._coeff_stream @@ -880,10 +904,12 @@ def define_implicitly(self, series, equations): ics = [a[1] if isinstance(a, (tuple, list)) else [] for a in series] - # common state for all series eqs = [eq._coeff_stream for eq in equations] for f, ic in zip(s, ics): - f.define_implicitly(s, ic, eqs, self._internal_poly_ring.base_ring()) + f.define_implicitly(s, ic, eqs, + self.base_ring(), + self._internal_poly_ring.base_ring(), + self._terms_of_degree) class options(GlobalOptions): r""" @@ -1989,6 +2015,14 @@ 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. + + """ + return [R.one()] + def uniformizer(self): """ Return a uniformizer of ``self``.. @@ -2343,6 +2377,26 @@ def _monomial(self, c, n): return L(c) * L.gen() ** n return L(c) + 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(QQ) + sage: L._terms_of_degree(3, QQ) + [1] + sage: L. = LazyPowerSeriesRing(QQ) + sage: L._terms_of_degree(3, QQ) + [y^3, x*y^2, x^2*y, x^3] + + """ + 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): """ @@ -2980,6 +3034,20 @@ def _monomial(self, c, n): L = self._laurent_poly_ring return L(c) + 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: L._terms_of_degree(3, ZZ) + [s[3], s[2, 1], s[1, 1, 1]] + """ + return list(self._internal_poly_ring.base_ring().homogeneous_component_basis(n)) + def _element_constructor_(self, x=None, valuation=None, degree=None, constant=None, check=True): r""" Construct a lazy element in ``self`` from ``x``. @@ -3603,6 +3671,13 @@ 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. + """ + return [R.one()] + + def _skip_leading_zeros(iterator): """ Return an iterator which discards all leading zeros. diff --git a/src/sage/rings/polynomial/multi_polynomial_element.py b/src/sage/rings/polynomial/multi_polynomial_element.py index f5c1b0e480c..d1e187582f0 100644 --- a/src/sage/rings/polynomial/multi_polynomial_element.py +++ b/src/sage/rings/polynomial/multi_polynomial_element.py @@ -178,8 +178,9 @@ def __call__(self, *x, **kwds): except AttributeError: K = self.parent().base_ring() y = K(0) - for (m,c) in self.element().dict().items(): - y += c*prod([ x[i]**m[i] for i in range(n) if m[i] != 0]) + for m, c in self.element().dict().items(): + mon = prod((x[i]**m[i] for i in range(n) if m[i]), K(1)) + y += c * mon return y def _richcmp_(self, right, op): From 2285de910d99d3a16283e2b9645714f5e20d2819 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Tue, 16 Jan 2024 11:15:23 +0100 Subject: [PATCH 034/117] replace InfinitePolynomialRing, make simple bivariate example work --- src/sage/data_structures/stream.py | 262 ++++++++++++++++++++++------- src/sage/rings/lazy_series_ring.py | 8 +- 2 files changed, 205 insertions(+), 65 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index 7366d330e33..1925d1f7622 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -1235,20 +1235,180 @@ def iterate_coefficients(self): n += 1 denom *= n -STREAM_UNINITIALIZED_VARIABLES = defaultdict(set) -def get_variable(P): - r""" - Return the first variable with index not in - ``STREAM_UNINITIALIZED_VARIABLES``. - We need a different dictionary for each base ring. +from sage.structure.parent import Parent +from sage.structure.element import Element, parent +from sage.structure.unique_representation import UniqueRepresentation +from sage.categories.fields import Fields +from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing + +class UndeterminedCoefficientsRingElement(Element): + def __init__(self, parent, v): + Element.__init__(self, parent) + self._p = v + + def _repr_(self): + return repr(self._p) + + def _richcmp_(self, other, op): + r""" + Compare ``self`` with ``other`` with respect to the comparison + operator ``op``. + """ + return self._p._richcmp_(other._p, op) + + def _add_(self, other): + """ + + EXAMPLES:: + + sage: from sage.data_structures.stream import UndeterminedCoefficientsRing + sage: R = UndeterminedCoefficientsRing(QQ) + sage: R(None) + 1 + FESDUMMY_... + 1 + """ + P = self.parent() + return P.element_class(P, self._p + other._p) + + def _sub_(self, other): + """ + EXAMPLES:: + + sage: from sage.data_structures.stream import UndeterminedCoefficientsRing + sage: R = UndeterminedCoefficientsRing(QQ) + sage: 1 - R(None) + -FESDUMMY_... + 1 + """ + P = self.parent() + return P.element_class(P, self._p - other._p) + + def _neg_(self): + """ + Return the negative of ``self``. + """ + P = self.parent() + return P.element_class(P, -self._p) + + def _mul_(self, other): + """ + EXAMPLES:: + + sage: from sage.data_structures.stream import UndeterminedCoefficientsRing + sage: R = UndeterminedCoefficientsRing(QQ) + sage: R(None) * R(None) + FESDUMMY_...*FESDUMMY_... + """ + P = self.parent() + return P.element_class(P, self._p * other._p) + + def _div_(self, other): + P = self.parent() + return P.element_class(P, self._p / other._p) + + def numerator(self): + return self._p.numerator() + + def variables(self): + """ + EXAMPLES:: + + sage: from sage.data_structures.stream import UndeterminedCoefficientsRing + sage: R = UndeterminedCoefficientsRing(QQ) + sage: R(None) / (R(None) + R(None)) + FESDUMMY_.../(FESDUMMY_... + FESDUMMY_...) + """ + return self._p.numerator().variables() + self._p.denominator().variables() + + def rational_function(self): + return self._p + + def subs(self, d): + """ + EXAMPLES:: + + sage: from sage.data_structures.stream import UndeterminedCoefficientsRing + sage: R = UndeterminedCoefficientsRing(QQ) + sage: p = R(None) + 1 + sage: v = p.variables()[0] + sage: q = R(None) + 1 + sage: (p/q).subs({v: 3}) + 4/(FESDUMMY_... + 1) + """ + P = self.parent() + V = self.variables() + d1 = {v: c for v, c in d.items() + if v in V} + return P.element_class(P, self._p.subs(d1)) + +class UndeterminedCoefficientsRing(UniqueRepresentation, Parent): + """ + Rational functions in unknowns over a base ring. """ - vars = STREAM_UNINITIALIZED_VARIABLES[P] - for i in range(P._max+2): - v = P.gen()[i] - if v not in vars: - vars.add(v) - return v + # must not inherit from UniqueRepresentation, because we want a + # new set of variables for each system of equations + + _PREFIX = "FESDUMMY_" + @staticmethod + def __classcall_private__(cls, base_ring, *args, **kwds): + return super().__classcall__(cls, base_ring, *args, **kwds) + + def __init__(self, base_ring): + self._pool = dict() # dict from variables actually used to indices of gens + # we must start with at least two variables, to make PolynomialSequence work + self._P = PolynomialRing(base_ring, names=[self._PREFIX+str(i) for i in range(2)]) + self._PF = self._P.fraction_field() + Parent.__init__(self, base=base_ring, category=Fields()) + + def polynomial_ring(self): + """ + .. WARNING:: + + This ring changes when new variables are requested. + """ + return self._P + + def _element_constructor_(self, x): + if x is None: + n = self._P.ngens() + for i in range(n): + if i not in self._pool.values(): + break + else: + names = self._P.variable_names() + (self._PREFIX+str(n),) + self._P = PolynomialRing(self._P.base_ring(), names) + self._PF = self._P.fraction_field() + i = n + v = self._P.gen(i) + self._pool[v] = i + return self.element_class(self, self._PF(v)) + + if x in self._PF: + return self.element_class(self, self._PF(x)) + + raise ValueError(f"{x} is not in {self}") + + def delete_variable(self, v): + del self._pool[v] + + def _coerce_map_from_(self, S): + """ + Return ``True`` if a coercion from ``S`` exists. + """ + if self._P.base_ring().has_coerce_map_from(S): + return True + return None + + def _coerce_map_from_base_ring(self): + """ + Return a coercion map from the base ring of ``self``. + """ + return self._generic_coerce_map(self._P.base_ring()) + + def _repr_(self): + return f"Undetermined coefficient ring over {self._P.base_ring()}" + + Element = UndeterminedCoefficientsRingElement + class Stream_uninitialized(Stream): r""" @@ -1363,10 +1523,8 @@ def define_implicitly(self, series, initial_values, equations, self._coefficient_ring = coefficient_ring self._base_ring = base_ring - # we use a silly variable name, because InfinitePolynomialRing is cached - self._P = InfinitePolynomialRing(self._base_ring, names=("FESDUMMY",), - implementation='dense') - self._PFF = self._P.fraction_field() + self._P = UndeterminedCoefficientsRing(self._base_ring) + # elements of the stream have self._P as base ring self._uncomputed = True self._eqs = equations self._series = series @@ -1473,7 +1631,7 @@ def __getitem__(self, n): if len(self._cache) > n - self._approximate_order: return self._cache[n - self._approximate_order] - x = sum(get_variable(self._P) * m + x = sum(self._P(None) * m for m in self._terms_of_degree(n, self._P)) self._cache.append(x) return x @@ -1488,31 +1646,6 @@ def _subs_in_caches(self, var, val): - ``var``, a variable - ``val``, the value that should replace the variable """ - var_p = var.polynomial() - def subs(poly): - R = self._P.polynomial_ring() - if var_p in poly.variables(): - d = {R(v): InfinitePolynomial_dense(self._P, v) - for v in poly.variables()} - d[R(var_p)] = val - return R(poly.polynomial()).subs(d) - return poly - - def subs_frac(c): - # TODO: we may want to insist that all coefficients of a - # stream have a parent - if hasattr(c, "parent"): - if c.parent() is self._PFF: - new = subs(c.numerator()) / subs(c.denominator()) - elif c.parent() is self._P and var_p in c.variables(): - new = subs(c) - else: - return c - if new in self._coefficient_ring: - return self._coefficient_ring(new) - else: - return new - for j, s in enumerate(self._input_streams): m = len(s._cache) - self._good_cache[j] if s._is_sparse: @@ -1522,15 +1655,29 @@ def subs_frac(c): indices = reversed(s._cache) else: indices = range(-1, -m-1, -1) - # determine last good element + # substitute variable and determine last good element good = m for i0, i in enumerate(indices): - if self._base_ring == self._coefficient_ring: - s._cache[i] = subs_frac(s._cache[i]) - else: - s._cache[i] = s._cache[i].map_coefficients(subs_frac) - - if s._cache[i] not in self._coefficient_ring: + # the following looks dangerous - could there be a + # ring that contains the UndeterminedCoefficientRing? + # it is not enough to look at the parent of + # s._cache[i] + c = s._cache[i] + if c not in self._coefficient_ring: + if self._base_ring == self._coefficient_ring: + c = c.subs({var: val}) + f = c.rational_function() + if f in self._coefficient_ring: + c = self._coefficient_ring(f) + else: + c = c.map_coefficients(lambda e: e.subs({var: val})) + try: + c = c.map_coefficients(lambda e: self._base_ring(e.rational_function()), + self._base_ring) + except TypeError: + pass + s._cache[i] = c + if c not in self._coefficient_ring: good = m - i0 - 1 self._good_cache[j] += good # fix approximate_order and true_order @@ -1555,7 +1702,7 @@ def subs_frac(c): ao += 1 s._approximate_order = ao - STREAM_UNINITIALIZED_VARIABLES[self._P].remove(var) + self._P.delete_variable(var) def _compute(self): """ @@ -1583,10 +1730,7 @@ def _compute(self): lcoeff = coeff.coefficients() for c in lcoeff: - if c.parent() is self._PFF: - c = c.numerator() - else: - c = self._P(c) + c = self._P(c).numerator() V = c.variables() if not V: if len(self._eqs) == 1: @@ -1600,7 +1744,7 @@ def _compute(self): # coefficient - are we computing in an integral # domain? c1 = c.coefficients()[0] - v = InfinitePolynomial_dense(self._P, c.variables()[0]) + v = self._P(c.variables()[0]) coeffs.append(c1 * v) else: # nonlinear equations must not be discarded, we @@ -1614,7 +1758,7 @@ def _compute(self): raise ValueError(f"there are no linear equations in degrees {degrees}: {non_linear_coeffs}") # solve from sage.rings.polynomial.multi_polynomial_sequence import PolynomialSequence - eqs = PolynomialSequence([coeff.polynomial() for coeff in coeffs]) + eqs = PolynomialSequence(self._P.polynomial_ring(), coeffs) m1, v1 = eqs.coefficient_matrix() # there should be at most one entry in v1 of degree 0 # v1 is a matrix, not a vector @@ -1633,12 +1777,8 @@ def _compute(self): k = m.right_kernel_matrix() # substitute bad = True - for i, (c, y) in enumerate(zip(v, x)): + for i, (var, y) in enumerate(zip(v, x)): if k.column(i).is_zero(): - # work around a bug prohibiting conversion of - # variables into the InfinitePolynomialRing over the - # SymbolicRing - var = InfinitePolynomial_dense(self._P, c) val = self._base_ring(y) self._subs_in_caches(var, val) bad = False diff --git a/src/sage/rings/lazy_series_ring.py b/src/sage/rings/lazy_series_ring.py index cf1d0786151..3a47facd933 100644 --- a/src/sage/rings/lazy_series_ring.py +++ b/src/sage/rings/lazy_series_ring.py @@ -780,12 +780,12 @@ def define_implicitly(self, series, equations): 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:: @@ -872,7 +872,7 @@ def define_implicitly(self, series, equations): sage: B O(z^16) sage: C - O(z^23) + O(z^22) sage: L. = LazyPowerSeriesRing(QQ) sage: A = L.undefined() @@ -896,7 +896,7 @@ def define_implicitly(self, series, equations): 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 """ s = [a[0]._coeff_stream if isinstance(a, (tuple, list)) else a._coeff_stream From 07f3c079e75cbfbc840018b7883c16f582382de0 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Wed, 17 Jan 2024 18:27:50 +0100 Subject: [PATCH 035/117] stay in the coefficient ring if possible in Stream_cauchy_invert and Stream_dirichlet_invert, add (currently failing) doctest for implicitly defined series involving composition --- src/sage/data_structures/stream.py | 8 ++++---- src/sage/rings/lazy_series.py | 8 ++------ src/sage/rings/lazy_series_ring.py | 11 +++++++++++ 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index 1925d1f7622..a3b7c448b0c 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -3389,9 +3389,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): """ @@ -3521,9 +3521,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): """ diff --git a/src/sage/rings/lazy_series.py b/src/sage/rings/lazy_series.py index 06639e817e4..5d538cc862d 100644 --- a/src/sage/rings/lazy_series.py +++ b/src/sage/rings/lazy_series.py @@ -5271,14 +5271,10 @@ def coefficient(n): def coefficient(n): r = R.zero() - P = self._coeff_stream[0].parent().base_ring() - g1 = [a.change_ring(P) for a in g] for i in range(n // gv + 1): - # Make sure the element returned from the composition is in P - # NO, we must not do this, because of define_implicitly - r += P(self[i](g))[n] -# r += (self._coeff_stream[i](g1))[n] + r += (self._coeff_stream[i](g))[n] return r + coeff_stream = Stream_function(coefficient, P._sparse, sorder * gv) return P.element_class(P, coeff_stream) diff --git a/src/sage/rings/lazy_series_ring.py b/src/sage/rings/lazy_series_ring.py index 3a47facd933..31b8f893c9f 100644 --- a/src/sage/rings/lazy_series_ring.py +++ b/src/sage/rings/lazy_series_ring.py @@ -897,6 +897,17 @@ def define_implicitly(self, series, equations): 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 + + A bivariate example involving composition of series:: + + sage: R. = LazyPowerSeriesRing(QQ) + sage: M1 = R.undefined() + sage: M2 = R.undefined() + sage: eq1 = -t*(x - y)*M1(0, 0, t)*x + t*(x - 1)*(x + 1)*(y^2 + 1)*M1(0, y, t) + (t*x^2*y^2 + t*x*y + t*y^2 + t - x*y)*M1(x, y, t) + t*M2(0, 0, t)*x*y + x*y + sage: eq2 = -t*M1(0, 0, t)*x + t*(x - 1)*(y + 1)*M1(0, y, t) + t*(x*y + y + 1)*M1(x, y, t) - t*M2(0, 0, t)*x + t*(x - 1)*(y^2 + y^2 + y + 1)*M2(0, y, t) + (t*x^2*y^2 + t*x*y^2 + t*x*y + t*y^2 + t*y^2 + t*y + t - x*y)*M2(x, y, t) + sage: R.define_implicitly([M1, M2], [eq1, eq2]) + sage: M1 + """ s = [a[0]._coeff_stream if isinstance(a, (tuple, list)) else a._coeff_stream From 41afaa2ede652460b9e9841aabef46609b9138c3 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Thu, 18 Jan 2024 16:36:10 +0100 Subject: [PATCH 036/117] provide functorial construction for coercion --- src/sage/categories/pushout.py | 17 +++++++++++++++++ src/sage/data_structures/stream.py | 5 +++++ src/sage/rings/lazy_series.py | 6 +++++- src/sage/rings/lazy_series_ring.py | 9 +++++++-- 4 files changed, 34 insertions(+), 3 deletions(-) diff --git a/src/sage/categories/pushout.py b/src/sage/categories/pushout.py index fd2bbe4bcaf..24c984dfe83 100644 --- a/src/sage/categories/pushout.py +++ b/src/sage/categories/pushout.py @@ -1191,6 +1191,23 @@ def _repr_(self): return "MPoly[%s]" % ','.join(self.vars) +class UndeterminedCoefficientsFunctor(ConstructionFunctor): + rank = 0 + + def __init__(self): + from .rings import Rings + Functor.__init__(self, Rings(), Rings()) + + def _apply_functor(self, R): + from sage.data_structures.stream import UndeterminedCoefficientsRing + return UndeterminedCoefficientsRing(R) + + __hash__ = ConstructionFunctor.__hash__ + + def _repr_(self): + return "UndeterminedCoefficients" + + class InfinitePolynomialFunctor(ConstructionFunctor): r""" A Construction Functor for Infinite Polynomial Rings (see :mod:`~sage.rings.polynomial.infinite_polynomial_ring`). diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index a3b7c448b0c..bcce709ddd3 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -1340,6 +1340,8 @@ def subs(self, d): if v in V} return P.element_class(P, self._p.subs(d1)) +from sage.categories.pushout import UndeterminedCoefficientsFunctor + class UndeterminedCoefficientsRing(UniqueRepresentation, Parent): """ Rational functions in unknowns over a base ring. @@ -1359,6 +1361,9 @@ def __init__(self, base_ring): self._PF = self._P.fraction_field() Parent.__init__(self, base=base_ring, category=Fields()) + def construction(self): + return (UndeterminedCoefficientsFunctor(), self.base_ring()) + def polynomial_ring(self): """ .. WARNING:: diff --git a/src/sage/rings/lazy_series.py b/src/sage/rings/lazy_series.py index 5d538cc862d..cacf6c6f18b 100644 --- a/src/sage/rings/lazy_series.py +++ b/src/sage/rings/lazy_series.py @@ -5272,7 +5272,11 @@ def coefficient(n): def coefficient(n): r = R.zero() for i in range(n // gv + 1): - r += (self._coeff_stream[i](g))[n] + c = self._coeff_stream[i] + if c in self.base_ring(): + c = P(c) + return c[n] + r += c(g)[n] return r coeff_stream = Stream_function(coefficient, P._sparse, sorder * gv) diff --git a/src/sage/rings/lazy_series_ring.py b/src/sage/rings/lazy_series_ring.py index 31b8f893c9f..c17f192ff80 100644 --- a/src/sage/rings/lazy_series_ring.py +++ b/src/sage/rings/lazy_series_ring.py @@ -2343,6 +2343,11 @@ def __init__(self, base_ring, names, sparse=True, category=None): Parent.__init__(self, base=base_ring, names=names, category=category) + def construction(self): + from sage.categories.pushout import CompletionFunctor + return (CompletionFunctor(self._names, infinity), + self._laurent_poly_ring) + def _repr_(self): """ String representation of this Taylor series ring. @@ -2590,8 +2595,8 @@ def _element_constructor_(self, x=None, valuation=None, constant=None, degree=No 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 From 44fcaff27d7dbffd61fa34a1988fa37e5b01f628 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Mon, 22 Jan 2024 10:42:02 +0100 Subject: [PATCH 037/117] fix bug in composition, work around performance bug in subs --- src/sage/data_structures/stream.py | 27 +++++++++++++++++++------ src/sage/rings/lazy_series.py | 32 +++++++++++++++--------------- 2 files changed, 37 insertions(+), 22 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index ed0af5b40ef..f52a757c4ec 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -983,6 +983,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 @@ -1337,10 +1345,17 @@ def subs(self, d): 4/(FESDUMMY_... + 1) """ P = self.parent() - V = self.variables() - d1 = {v: c for v, c in d.items() - if v in V} - return P.element_class(P, self._p.subs(d1)) + p_num = P._P(self._p.numerator()) + V_num = p_num.variables() + d_num = {P._P(v): c for v, c in d.items() + if v in V_num} + num = p_num.subs(d_num) + p_den = P._P(self._p.denominator()) + V_den = p_den.variables() + d_den = {P._P(v): c for v, c in d.items() + if v in V_den} + den = p_den.subs(d_den) + return P.element_class(P, P._PF(num / den)) from sage.categories.pushout import UndeterminedCoefficientsFunctor @@ -1381,7 +1396,7 @@ def _element_constructor_(self, x): if i not in self._pool.values(): break else: - names = self._P.variable_names() + (self._PREFIX+str(n),) + names = self._P.variable_names() + (self._PREFIX+str(n),) # tuple(self._PREFIX+str(i) for i in range(n, 2*n)) self._P = PolynomialRing(self._P.base_ring(), names) self._PF = self._P.fraction_field() i = n @@ -4317,7 +4332,7 @@ def __eq__(self, other): True """ return (isinstance(other, type(self)) - and self._integration_constants == other._integration_constants + 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 9621b532757..076f6d13a47 100644 --- a/src/sage/rings/lazy_series.py +++ b/src/sage/rings/lazy_series.py @@ -5187,8 +5187,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) @@ -5199,8 +5200,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(): @@ -5250,7 +5251,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): @@ -5258,29 +5259,28 @@ 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) - def coefficient(n): r = R.zero() for i in range(n // gv + 1): - c = self._coeff_stream[i] + c = coeff_stream[i] if c in self.base_ring(): c = P(c) - return c[n] - r += c(g)[n] + r += c[n] + else: + r += c(g)[n] 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__ From 585a358ff996438a4cb6803e576210942f3b3313 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Mon, 22 Jan 2024 11:46:10 +0100 Subject: [PATCH 038/117] make _terms_of_degree more correct for completions in higher arity --- src/sage/rings/lazy_series_ring.py | 76 ++++++++++++++++++++++++++---- 1 file changed, 67 insertions(+), 9 deletions(-) diff --git a/src/sage/rings/lazy_series_ring.py b/src/sage/rings/lazy_series_ring.py index e21eb638625..8d1d2841310 100644 --- a/src/sage/rings/lazy_series_ring.py +++ b/src/sage/rings/lazy_series_ring.py @@ -900,13 +900,46 @@ def define_implicitly(self, series, equations): A bivariate example involving composition of series:: - sage: R. = LazyPowerSeriesRing(QQ) - sage: M1 = R.undefined() - sage: M2 = R.undefined() - sage: eq1 = -t*(x - y)*M1(0, 0, t)*x + t*(x - 1)*(x + 1)*(y^2 + 1)*M1(0, y, t) + (t*x^2*y^2 + t*x*y + t*y^2 + t - x*y)*M1(x, y, t) + t*M2(0, 0, t)*x*y + x*y - sage: eq2 = -t*M1(0, 0, t)*x + t*(x - 1)*(y + 1)*M1(0, y, t) + t*(x*y + y + 1)*M1(x, y, t) - t*M2(0, 0, t)*x + t*(x - 1)*(y^2 + y^2 + y + 1)*M2(0, y, t) + (t*x^2*y^2 + t*x*y^2 + t*x*y + t*y^2 + t*y^2 + t*y + t - x*y)*M2(x, y, t) - sage: R.define_implicitly([M1, M2], [eq1, eq2]) - sage: M1 + 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 currently, because the equations + determining the coefficients come in bad order:: + + sage: L. = LazyPowerSeriesRing(QQ) + sage: M1 = L.undefined() + sage: M2 = L.undefined() + sage: eq1 = t*x*y*M2(0, 0, t) + (t - x*y)*M1(x, y, t) + x*y - t*M1(0, y, t) + sage: eq2 = (t*x-t)*M2(0, y, t) + (t - x*y)*M2(x, y, t) + sage: L.define_implicitly([M1, M2], [eq1, eq2]) + sage: M1[1] # known bug, not tested + + 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) + + (1/120*x^6*y+5/3*x^5*y^2+12*x^4*y^3+9*x^3*y^4+2/3*x^2*y^5+1/720*x*y^6) + + O(x,y)^8 + + 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[1] # known bug, not tested """ s = [a[0]._coeff_stream if isinstance(a, (tuple, list)) @@ -3091,8 +3124,33 @@ def _terms_of_degree(self, n, R): sage: L = LazySymmetricFunctions(s) sage: L._terms_of_degree(3, ZZ) [s[3], s[2, 1], s[1, 1, 1]] - """ - return list(self._internal_poly_ring.base_ring().homogeneous_component_basis(n)) + + sage: L = LazySymmetricFunctions(tensor([s, s])) + sage: L._terms_of_degree(3, ZZ) + [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]] + """ + 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() + 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""" From 0cc3eaf874ff53e07c7f9d24e221a23796ea3ec5 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Thu, 1 Feb 2024 21:31:02 +0100 Subject: [PATCH 039/117] provide a construction functor --- src/sage/combinat/sf/elementary.py | 1 + src/sage/combinat/sf/homogeneous.py | 1 + src/sage/combinat/sf/macdonald.py | 5 ++ src/sage/combinat/sf/monomial.py | 1 + src/sage/combinat/sf/powersum.py | 1 + src/sage/combinat/sf/schur.py | 1 + src/sage/combinat/sf/sf.py | 1 - src/sage/combinat/sf/sfa.py | 78 ++++++++++++++++++++++++++++- 8 files changed, 86 insertions(+), 3 deletions(-) diff --git a/src/sage/combinat/sf/elementary.py b/src/sage/combinat/sf/elementary.py index f0942314323..878cfe4f73f 100644 --- a/src/sage/combinat/sf/elementary.py +++ b/src/sage/combinat/sf/elementary.py @@ -50,6 +50,7 @@ def __init__(self, Sym): sage: TestSuite(e).run(skip=['_test_associativity', '_test_distributivity', '_test_prod']) sage: TestSuite(e).run(elements = [e[1,1]+e[2], e[1]+2*e[1,1]]) """ + self._descriptor = (("elementary",),) classical.SymmetricFunctionAlgebra_classical.__init__(self, Sym, "elementary", 'e') def _dual_basis_default(self): diff --git a/src/sage/combinat/sf/homogeneous.py b/src/sage/combinat/sf/homogeneous.py index 29cf294ea80..d21dd197c58 100644 --- a/src/sage/combinat/sf/homogeneous.py +++ b/src/sage/combinat/sf/homogeneous.py @@ -52,6 +52,7 @@ def __init__(self, Sym): sage: TestSuite(h).run(skip=['_test_associativity', '_test_distributivity', '_test_prod']) sage: TestSuite(h).run(elements = [h[1,1]+h[2], h[1]+2*h[1,1]]) """ + self._descriptor = (("homogeneous",),) classical.SymmetricFunctionAlgebra_classical.__init__(self, Sym, "homogeneous", 'h') def _dual_basis_default(self): diff --git a/src/sage/combinat/sf/macdonald.py b/src/sage/combinat/sf/macdonald.py index 1358c5779df..a467ad29eef 100644 --- a/src/sage/combinat/sf/macdonald.py +++ b/src/sage/combinat/sf/macdonald.py @@ -1082,6 +1082,7 @@ def __init__(self, macdonald): sage: TestSuite(Q).run(elements = [Q.t*Q[1,1]+Q.q*Q[2], Q[1]+(Q.q+Q.t)*Q[1,1]]) # long time (depends on previous) """ MacdonaldPolynomials_generic.__init__(self, macdonald) + self._descriptor = (("macdonald", {"q": self.q, "t": self.t}), ("Q",)) self._J = macdonald.J() self._P = macdonald.P() @@ -1118,6 +1119,7 @@ def __init__(self, macdonald): self._self_to_s_cache = _j_to_s_cache self._s_to_self_cache = _s_to_j_cache MacdonaldPolynomials_generic.__init__(self, macdonald) + self._descriptor = (("macdonald", {"q": self.q, "t": self.t}), ("J",)) def _s_cache(self, n): r""" @@ -1218,6 +1220,7 @@ def __init__(self, macdonald): """ MacdonaldPolynomials_generic.__init__(self, macdonald) + self._descriptor = (("macdonald", {"q": self.q, "t": self.t}), ("H",)) self._m = self._sym.m() self._Lmunu = macdonald.Ht()._Lmunu if not self.t: @@ -1440,6 +1443,7 @@ def __init__(self, macdonald): """ MacdonaldPolynomials_generic.__init__(self, macdonald) + self._descriptor = (("macdonald", {"q": self.q, "t": self.t}), ("Ht",)) self._self_to_m_cache = _ht_to_m_cache self._m = self._sym.m() category = ModulesWithBasis(self.base_ring()) @@ -1735,6 +1739,7 @@ def __init__(self, macdonald): """ MacdonaldPolynomials_generic.__init__(self, macdonald) + self._descriptor = (("macdonald", {"q": self.q, "t": self.t}), ("S",)) self._s = macdonald._s self._self_to_s_cache = _S_to_s_cache self._s_to_self_cache = _s_to_S_cache diff --git a/src/sage/combinat/sf/monomial.py b/src/sage/combinat/sf/monomial.py index 583008830af..18d25b1af25 100644 --- a/src/sage/combinat/sf/monomial.py +++ b/src/sage/combinat/sf/monomial.py @@ -45,6 +45,7 @@ def __init__(self, Sym): sage: TestSuite(m).run(skip=['_test_associativity', '_test_distributivity', '_test_prod']) sage: TestSuite(m).run(elements = [m[1,1]+m[2], m[1]+2*m[1,1]]) """ + self._descriptor = (("monomial",),) classical.SymmetricFunctionAlgebra_classical.__init__(self, Sym, "monomial", 'm') def _dual_basis_default(self): diff --git a/src/sage/combinat/sf/powersum.py b/src/sage/combinat/sf/powersum.py index 8d7f744e75f..ea7ca6bcc1f 100644 --- a/src/sage/combinat/sf/powersum.py +++ b/src/sage/combinat/sf/powersum.py @@ -44,6 +44,7 @@ def __init__(self, Sym): sage: TestSuite(p).run(skip=['_test_associativity', '_test_distributivity', '_test_prod']) sage: TestSuite(p).run(elements = [p[1,1]+p[2], p[1]+2*p[1,1]]) """ + self._descriptor = (("powersum",),) classical.SymmetricFunctionAlgebra_classical.__init__(self, Sym, "powersum", 'p') def coproduct_on_generators(self, i): diff --git a/src/sage/combinat/sf/schur.py b/src/sage/combinat/sf/schur.py index 40e1de75812..672bac86c91 100644 --- a/src/sage/combinat/sf/schur.py +++ b/src/sage/combinat/sf/schur.py @@ -48,6 +48,7 @@ def __init__(self, Sym): sage: TestSuite(s).run(skip=['_test_associativity', '_test_distributivity', '_test_prod']) sage: TestSuite(s).run(elements = [s[1,1]+s[2], s[1]+2*s[1,1]]) """ + self._descriptor = (("schur",),) classical.SymmetricFunctionAlgebra_classical.__init__(self, Sym, "Schur", 's') def _dual_basis_default(self): diff --git a/src/sage/combinat/sf/sf.py b/src/sage/combinat/sf/sf.py index ad86bfd6c7f..5dc923f8df2 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 2b42ebb7be6..5d985031f7e 100644 --- a/src/sage/combinat/sf/sfa.py +++ b/src/sage/combinat/sf/sfa.py @@ -1786,7 +1786,6 @@ class SymmetricFunctionAlgebra_generic(CombinatorialFreeModule): sage: s(m([2,1])) -2*s[1, 1, 1] + s[2, 1] """ - def __init__(self, Sym, basis_name=None, prefix=None, graded=True): r""" Initializes the symmetric function algebra. @@ -3013,6 +3012,21 @@ def coproduct_by_coercion(self, elt): return self.tensor_square().sum(coeff * tensor([self(s[x]), self(s[y])]) for ((x,y), coeff) in s(elt).coproduct()) + def construction(self): + """ + Return a pair ``(F, R)``, where ``F`` is a + :class:`SymmetricFunctionsFunctor` and `R` is a ring, such + that ``F(R)`` returns ``self``. + + EXAMPLES:: + + sage: s = SymmetricFunctions(ZZ).s() + sage: F, R = s.construction() + sage: F(QQ) + Symmetric Functions over Rational Field in the Schur basis + """ + return SymmetricFunctionsFunctor(self._descriptor), self.base_ring() + class SymmetricFunctionAlgebra_generic_Element(CombinatorialFreeModule.Element): r""" @@ -3033,7 +3047,6 @@ class SymmetricFunctionAlgebra_generic_Element(CombinatorialFreeModule.Element): m[1, 1, 1] + m[2, 1] + m[3] sage: m.set_print_style('lex') """ - def factor(self): """ Return the factorization of this symmetric function. @@ -6375,6 +6388,67 @@ def exponential_specialization(self, t=None, q=1): SymmetricFunctionAlgebra_generic.Element = SymmetricFunctionAlgebra_generic_Element +from sage.categories.pushout import ConstructionFunctor +from sage.categories.commutative_rings import CommutativeRings +from sage.categories.functor import Functor + +class SymmetricFunctionsFunctor(ConstructionFunctor): + rank = 9 + + def __init__(self, descriptor): + self._descriptor = descriptor + Functor.__init__(self, CommutativeRings(), CommutativeRings()) + + def _apply_functor(self, R): + """ + Apply the functor to an object of ``self``'s domain. + + EXAMPLES:: + + sage: s = SymmetricFunctions(ZZ).s() + sage: F, R = s.construction() + sage: F(QQ) + Symmetric Functions over Rational Field in the Schur basis + """ + from sage.combinat.sf.sf import SymmetricFunctions + S = SymmetricFunctions(R) + for method, *params in self._descriptor: + if params: + assert len(params) == 1 + S = S.__getattribute__(method)(**params[0]) + else: + S = S.__getattribute__(method)() + return S + + def _apply_functor_to_morphism(self, f): + """ + Apply the functor ``self`` to the ring morphism `f`. + + """ + dom = self(f.domain()) + codom = self(f.codomain()) + + def action(x): + return codom._from_dict({a: f(b) + for a, b in x.monomial_coefficients().items()}) + return dom.module_morphism(function=action, codomain=codom) + + def __eq__(self, other): + if not isinstance(other, SymmetricFunctionsFunctor): + return False + return self.vars == other.vars + + def _repr_(self): + """ + TESTS:: + + sage: R. = ZZ[] + sage: H = SymmetricFunctions(R).macdonald().H() + sage: F, R = H.construction() + sage: F + (('macdonald', {'q': q, 't': t}), ('H',)) + """ + return repr(self._descriptor) ################### def _lmax(x): From 693c051110c5783433217ad45beed457bb1fdf86 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Tue, 6 Feb 2024 11:04:26 +0100 Subject: [PATCH 040/117] more constructions --- src/sage/combinat/sf/character.py | 2 ++ src/sage/combinat/sf/hall_littlewood.py | 10 +++++++++- src/sage/combinat/sf/hecke.py | 8 ++++++-- src/sage/combinat/sf/jack.py | 12 ++++++++++-- src/sage/combinat/sf/macdonald.py | 11 ++++++++--- src/sage/combinat/sf/orthogonal.py | 2 +- src/sage/combinat/sf/symplectic.py | 1 + src/sage/combinat/sf/witt.py | 1 + 8 files changed, 38 insertions(+), 9 deletions(-) diff --git a/src/sage/combinat/sf/character.py b/src/sage/combinat/sf/character.py index 3bd8ab5e0a1..827e7ad33ae 100644 --- a/src/sage/combinat/sf/character.py +++ b/src/sage/combinat/sf/character.py @@ -224,6 +224,7 @@ def __init__(self, Sym, pfix): SFA_generic.__init__(self, Sym, basis_name="induced trivial symmetric group character", prefix=pfix, graded=False) + self._descriptor = (("ht",),) self._other = Sym.complete() self._p = Sym.powersum() @@ -451,6 +452,7 @@ def __init__(self, Sym, pfix): SFA_generic.__init__(self, Sym, basis_name="irreducible symmetric group character", prefix=pfix, graded=False) + self._descriptor = (("st",),) self._other = Sym.Schur() self._p = Sym.powersum() diff --git a/src/sage/combinat/sf/hall_littlewood.py b/src/sage/combinat/sf/hall_littlewood.py index c3376537a35..cf19404b4eb 100644 --- a/src/sage/combinat/sf/hall_littlewood.py +++ b/src/sage/combinat/sf/hall_littlewood.py @@ -77,7 +77,11 @@ def __repr__(self): """ return self._name + " over %s" % self._sym.base_ring() - def __init__(self, Sym, t='t'): + @staticmethod + def __classcall__(cls, Sym, t='t'): + return super().__classcall__(cls, Sym, Sym.base_ring()(t)) + + def __init__(self, Sym, t): """ Initialize ``self``. @@ -708,6 +712,7 @@ def __init__(self, hall_littlewood): sage: TestSuite(P).run(elements = [P.t*P[1,1]+P[2], P[1]+(1+P.t)*P[1,1]]) """ HallLittlewood_generic.__init__(self, hall_littlewood) + self._descriptor = (("hall_littlewood", {"t": self.t}), ("P",)) self._self_to_s_cache = p_to_s_cache self._s_to_self_cache = s_to_p_cache @@ -846,6 +851,7 @@ def __init__(self, hall_littlewood): (1/(t^2-1))*HLQ[1, 1] - (1/(t-1))*HLQ[2] """ HallLittlewood_generic.__init__(self, hall_littlewood) + self._descriptor = (("hall_littlewood", {"t": self.t}), ("Q",)) self._P = self._hall_littlewood.P() # temporary until Hom(GradedHopfAlgebrasWithBasis work better) @@ -942,6 +948,8 @@ def __init__(self, hall_littlewood): [ 0 0 1] """ HallLittlewood_generic.__init__(self, hall_littlewood) + self._descriptor = (("hall_littlewood", {"t": self.t}), ("Qp",)) + self._self_to_s_cache = qp_to_s_cache self._s_to_self_cache = s_to_qp_cache diff --git a/src/sage/combinat/sf/hecke.py b/src/sage/combinat/sf/hecke.py index 5cea11ccb2c..f2071d5f4b6 100644 --- a/src/sage/combinat/sf/hecke.py +++ b/src/sage/combinat/sf/hecke.py @@ -121,8 +121,11 @@ class HeckeCharacter(SymmetricFunctionAlgebra_multiplicative): - [Ram1991]_ - [RR1997]_ """ + @staticmethod + def __classcall__(cls, Sym, q='q'): + return super().__classcall__(cls, Sym, Sym.base_ring()(q)) - def __init__(self, sym, q='q'): + def __init__(self, sym, q): r""" Initialize ``self``. @@ -156,10 +159,11 @@ def __init__(self, sym, q='q'): ....: for mu in Partitions(n)) True """ - self.q = sym.base_ring()(q) + self.q = q SymmetricFunctionAlgebra_multiplicative.__init__(self, sym, basis_name="Hecke character with q={}".format(self.q), prefix="qbar") + self._descriptor = (("qbar", {"q": self.q}),) self._p = sym.power() # temporary until Hom(GradedHopfAlgebrasWithBasis work better) diff --git a/src/sage/combinat/sf/jack.py b/src/sage/combinat/sf/jack.py index a7a0fec6186..601f6468337 100644 --- a/src/sage/combinat/sf/jack.py +++ b/src/sage/combinat/sf/jack.py @@ -50,8 +50,11 @@ class Jack(UniqueRepresentation): + @staticmethod + def __classcall__(cls, Sym, t='t'): + return super().__classcall__(cls, Sym, Sym.base_ring()(t)) - def __init__(self, Sym, t='t'): + def __init__(self, Sym, t): r""" The family of Jack symmetric functions including the `P`, `Q`, `J`, `Qp` bases. The default parameter is ``t``. @@ -70,7 +73,7 @@ def __init__(self, Sym, t='t'): Jack polynomials with t=1 over Rational Field """ self._sym = Sym - self.t = Sym.base_ring()(t) + self.t = t self._name_suffix = "" if str(t) != 't': self._name_suffix += " with t=%s" % t @@ -874,6 +877,7 @@ def __init__(self, jack): self._m_to_self_cache = m_to_p_cache self._self_to_m_cache = p_to_m_cache JackPolynomials_generic.__init__(self, jack) + self._descriptor = (("jack", {"t": self.t}), ("P",)) def _m_cache(self, n): r""" @@ -1076,6 +1080,7 @@ def __init__(self, jack): self._name = "Jack polynomials in the J basis" self._prefix = "JackJ" JackPolynomials_generic.__init__(self, jack) + self._descriptor = (("jack", {"t": self.t}), ("J",)) # Should be shared with _q (and possibly other bases in Macdo/HL) as BasesByRenormalization self._P = self._jack.P() @@ -1112,6 +1117,7 @@ def __init__(self, jack): self._name = "Jack polynomials in the Q basis" self._prefix = "JackQ" JackPolynomials_generic.__init__(self, jack) + self._descriptor = (("jack", {"t": self.t}), ("Q",)) # Should be shared with _j (and possibly other bases in Macdo/HL) as BasesByRenormalization self._P = self._jack.P() @@ -1150,6 +1156,7 @@ def __init__(self, jack): self._name = "Jack polynomials in the Qp basis" self._prefix = "JackQp" JackPolynomials_generic.__init__(self, jack) + self._descriptor = (("jack", {"t": self.t}), ("Qp",)) self._P = self._jack.P() self._self_to_h_cache = qp_to_h_cache self._h_to_self_cache = h_to_qp_cache @@ -1354,6 +1361,7 @@ def __init__(self, Sym): #self._self_to_m_cache = {} and we don't need to compute it separately for zonals sfa.SymmetricFunctionAlgebra_generic.__init__(self, self._sym, prefix="Z", basis_name="zonal") + self._descriptor = (("zonal",),) category = sage.categories.all.ModulesWithBasis(self._sym.base_ring()) self .register_coercion(SetMorphism(Hom(self._P, self, category), self.sum_of_terms)) self._P.register_coercion(SetMorphism(Hom(self, self._P, category), self._P.sum_of_terms)) diff --git a/src/sage/combinat/sf/macdonald.py b/src/sage/combinat/sf/macdonald.py index a467ad29eef..03ec0bcc5a2 100644 --- a/src/sage/combinat/sf/macdonald.py +++ b/src/sage/combinat/sf/macdonald.py @@ -97,7 +97,11 @@ def __repr__(self): """ return self._name - def __init__(self, Sym, q='q', t='t'): + @staticmethod + def __classcall__(cls, Sym, q='q', t='t'): + return super().__classcall__(cls, Sym, Sym.base_ring()(q), Sym.base_ring()(t)) + + def __init__(self, Sym, q, t): r""" Macdonald Symmetric functions including `P`, `Q`, `J`, `H`, `Ht` bases also including the S basis which is the plethystic transformation @@ -119,8 +123,8 @@ def __init__(self, Sym, q='q', t='t'): """ self._sym = Sym self._s = Sym.s() - self.q = Sym.base_ring()(q) - self.t = Sym.base_ring()(t) + self.q = q + self.t = t self._name_suffix = "" if str(q) != 'q': self._name_suffix += " with q=%s" % q @@ -1012,6 +1016,7 @@ def __init__(self, macdonald): sage: TestSuite(P).run(elements = [P.t*P[1,1]+P.q*P[2], P[1]+(P.q+P.t)*P[1,1]]) # long time (depends on previous) """ MacdonaldPolynomials_generic.__init__(self, macdonald) + self._descriptor = (("macdonald", {"q": self.q, "t": self.t}), ("P",)) self._J = macdonald.J() # temporary until Hom(GradedHopfAlgebrasWithBasis work better) diff --git a/src/sage/combinat/sf/orthogonal.py b/src/sage/combinat/sf/orthogonal.py index 3ab5f56debc..b6c3576a557 100644 --- a/src/sage/combinat/sf/orthogonal.py +++ b/src/sage/combinat/sf/orthogonal.py @@ -170,7 +170,7 @@ def __init__(self, Sym): """ sfa.SymmetricFunctionAlgebra_generic.__init__(self, Sym, "orthogonal", 'o', graded=False) - + self._descriptor = (("o",),) # We make a strong reference since we use it for our computations # and so we can define the coercion below (only codomains have # strong references) diff --git a/src/sage/combinat/sf/symplectic.py b/src/sage/combinat/sf/symplectic.py index f6db1782489..a6eebc438af 100644 --- a/src/sage/combinat/sf/symplectic.py +++ b/src/sage/combinat/sf/symplectic.py @@ -178,6 +178,7 @@ def __init__(self, Sym): """ sfa.SymmetricFunctionAlgebra_generic.__init__(self, Sym, "symplectic", 'sp', graded=False) + self._descriptor = (("sp",),) # We make a strong reference since we use it for our computations # and so we can define the coercion below (only codomains have diff --git a/src/sage/combinat/sf/witt.py b/src/sage/combinat/sf/witt.py index 138b2647826..1f5ed292bd0 100644 --- a/src/sage/combinat/sf/witt.py +++ b/src/sage/combinat/sf/witt.py @@ -407,6 +407,7 @@ def __init__(self, Sym, coerce_h=True, coerce_e=False, coerce_p=False): sage: TestSuite(w).run(skip=['_test_associativity', '_test_distributivity', '_test_prod']) sage: TestSuite(w).run(elements = [w[1,1]+w[2], w[1]+2*w[1,1]]) """ + self._descriptor = (("w",),) self._coerce_h = coerce_h self._coerce_e = coerce_e self._coerce_p = coerce_p From 7d7ccebe07f2b81049b619f3e4b493f45195cca5 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Tue, 6 Feb 2024 14:08:21 +0100 Subject: [PATCH 041/117] fix oversight in factorization of symmetric function: unit should be in the base ring --- src/sage/combinat/sf/sfa.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/combinat/sf/sfa.py b/src/sage/combinat/sf/sfa.py index 8a73014fe6b..0fd2d890b88 100644 --- a/src/sage/combinat/sf/sfa.py +++ b/src/sage/combinat/sf/sfa.py @@ -3103,7 +3103,7 @@ def factor(self): poly = _to_polynomials([self], self.base_ring())[0] factors = poly.factor() - unit = factors.unit() + unit = self.base_ring()(factors.unit()) if factors.universe() == self.base_ring(): return Factorization(factors, unit=unit) factors = [(_from_polynomial(factor, M), exponent) From 7bd1c5b32534a5ae4a520bc1a5c99dcf05a0d3cf Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Tue, 6 Feb 2024 14:31:39 +0100 Subject: [PATCH 042/117] skip construction for dual and orthotriang --- src/sage/combinat/sf/dual.py | 5 ++++- src/sage/combinat/sf/orthotriang.py | 7 +++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/sage/combinat/sf/dual.py b/src/sage/combinat/sf/dual.py index 6a68eb7dfe6..58b69743c7b 100644 --- a/src/sage/combinat/sf/dual.py +++ b/src/sage/combinat/sf/dual.py @@ -74,7 +74,7 @@ def __init__(self, dual_basis, scalar, scalar_name="", basis_name=None, prefix=N sage: e = SymmetricFunctions(QQ).e() sage: f = e.dual_basis(prefix = "m", basis_name="Forgotten symmetric functions"); f Symmetric Functions over Rational Field in the Forgotten symmetric functions basis - sage: TestSuite(f).run(elements = [f[1,1]+2*f[2], f[1]+3*f[1,1]]) + sage: TestSuite(f).run(skip='_test_construction', elements = [f[1,1]+2*f[2], f[1]+3*f[1,1]]) sage: TestSuite(f).run() # long time (11s on sage.math, 2011) This class defines canonical coercions between ``self`` and @@ -157,6 +157,9 @@ def __init__(self, dual_basis, scalar, scalar_name="", basis_name=None, prefix=N self.register_coercion(SetMorphism(Hom(self._dual_basis, self, category), self._dual_to_self)) self._dual_basis.register_coercion(SetMorphism(Hom(self, self._dual_basis, category), self._self_to_dual)) + def construction(self): + raise NotImplementedError + def _dual_to_self(self, x): """ Coerce an element of the dual of ``self`` canonically into ``self``. diff --git a/src/sage/combinat/sf/orthotriang.py b/src/sage/combinat/sf/orthotriang.py index 2e1650e57a7..5f2f01b7f6c 100644 --- a/src/sage/combinat/sf/orthotriang.py +++ b/src/sage/combinat/sf/orthotriang.py @@ -84,8 +84,8 @@ def __init__(self, Sym, base, scalar, prefix, basis_name, leading_coeff=None): 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"]) # long time (7s on sage.math, 2011) + sage: TestSuite(s).run(skip='_test_construction', 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) Note: ``s.an_element()`` is of degree 4; so we skip ``_test_associativity`` and ``_test_prod`` which involve @@ -102,6 +102,9 @@ def __init__(self, Sym, base, scalar, prefix, basis_name, leading_coeff=None): self.register_coercion(SetMorphism(Hom(base, self), self._base_to_self)) base.register_coercion(SetMorphism(Hom(self, base), self._self_to_base)) + def construction(self): + raise NotImplementedError + def _base_to_self(self, x): """ Coerce a symmetric function in base ``x`` into ``self``. From 10f796a442cd6184378ccaad6847a316fcc5be84 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Wed, 7 Feb 2024 20:43:22 +0100 Subject: [PATCH 043/117] adapt to PolynomialSequence.coefficients_monomials --- src/sage/data_structures/stream.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index f52a757c4ec..442b0ddf4e4 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -1781,20 +1781,19 @@ def _compute(self): # solve from sage.rings.polynomial.multi_polynomial_sequence import PolynomialSequence eqs = PolynomialSequence(self._P.polynomial_ring(), coeffs) - m1, v1 = eqs.coefficient_matrix() + m1, v1 = eqs.coefficients_monomials() # there should be at most one entry in v1 of degree 0 - # v1 is a matrix, not a vector - for j, (c,) in enumerate(v1): + for j, c in enumerate(v1): if c.degree() == 0: b = -m1.column(j) - m = m1.matrix_from_columns([i for i in range(v1.nrows()) if i != j]) - v = [c for i, (c,) in enumerate(v1) if i != 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 = v1.list() + v = list(v1) x = m.solve_right(b) k = m.right_kernel_matrix() # substitute From 2d997393036b3387b0bc740a0942da4de04d6e28 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Thu, 8 Feb 2024 20:20:07 +0100 Subject: [PATCH 044/117] add change_ring, add doctests demonstrating that coercions with different base rings work now --- src/sage/combinat/sf/sfa.py | 43 +++++++++++++++++++++++++------------ 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/src/sage/combinat/sf/sfa.py b/src/sage/combinat/sf/sfa.py index 0fd2d890b88..80f2db928e9 100644 --- a/src/sage/combinat/sf/sfa.py +++ b/src/sage/combinat/sf/sfa.py @@ -1,6 +1,5 @@ # sage.doctest: needs sage.combinat sage.modules -r""" -Symmetric Functions +r"""Symmetric Functions For a comprehensive tutorial on how to use symmetric functions in Sage @@ -170,21 +169,22 @@ BACKWARD INCOMPATIBLE CHANGES (:trac:`5457`): -The symmetric functions code has been refactored to take -advantage of the coercion systems. This introduced a couple of glitches: +The symmetric functions code has been refactored to take advantage of +the coercion systems. This introduced a couple of glitches, in +particular, on some bases changes, coefficients in Jack polynomials +are not normalized -- On some bases changes, coefficients in Jack polynomials are not normalized +However, conversions and coercions are now also defined between +symmetric functions over different coefficient rings:: -- Except in a few cases, conversions and coercions are only defined - between symmetric functions over the same coefficient ring. E.g. - the following does not work anymore:: + sage: S = SymmetricFunctions(QQ) + sage: S2 = SymmetricFunctions(QQ['t']) + sage: S3 = SymmetricFunctions(ZZ) + sage: S.m()[1] + S2.m()[2] + m[1] + m[2] - sage: s = SymmetricFunctions(QQ) - sage: s2 = SymmetricFunctions(QQ['t']) - sage: s([1]) + s2([2]) # todo: not implemented - - This feature will probably come back at some point through - improvements to the Sage coercion system. + sage: S.m()(S3.sp()[2,1]) + -m[1] + 2*m[1, 1, 1] + m[2, 1] Backward compatibility should be essentially retained. @@ -3028,6 +3028,21 @@ def construction(self): """ return SymmetricFunctionsFunctor(self._descriptor), self.base_ring() + def change_ring(self, R): + r""" + Return the base change of ``self`` to `R`. + + EXAMPLES:: + + sage: s = SymmetricFunctions(ZZ).s() + sage: s.change_ring(QQ) + Symmetric Functions over Rational Field in the Schur basis + """ + if R is self.base_ring(): + return self + functor, _ = self.construction() + return functor(R) + class SymmetricFunctionAlgebra_generic_Element(CombinatorialFreeModule.Element): r""" From 71c12598d9bfda998499150aaa92055551086a7e Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Fri, 9 Feb 2024 16:51:44 +0100 Subject: [PATCH 045/117] add change_ring, add doctests demonstrating that coercions with different base rings work now --- src/sage/combinat/sf/sfa.py | 40 ++++++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/src/sage/combinat/sf/sfa.py b/src/sage/combinat/sf/sfa.py index 0fd2d890b88..07daa75375a 100644 --- a/src/sage/combinat/sf/sfa.py +++ b/src/sage/combinat/sf/sfa.py @@ -170,21 +170,22 @@ BACKWARD INCOMPATIBLE CHANGES (:trac:`5457`): -The symmetric functions code has been refactored to take -advantage of the coercion systems. This introduced a couple of glitches: +The symmetric functions code has been refactored to take advantage of +the coercion systems. This introduced a couple of glitches, in +particular, on some bases changes, coefficients in Jack polynomials +are not normalized -- On some bases changes, coefficients in Jack polynomials are not normalized +However, conversions and coercions are now also defined between +symmetric functions over different coefficient rings:: -- Except in a few cases, conversions and coercions are only defined - between symmetric functions over the same coefficient ring. E.g. - the following does not work anymore:: + sage: S = SymmetricFunctions(QQ) + sage: S2 = SymmetricFunctions(QQ['t']) + sage: S3 = SymmetricFunctions(ZZ) + sage: S.m()[1] + S2.m()[2] + m[1] + m[2] - sage: s = SymmetricFunctions(QQ) - sage: s2 = SymmetricFunctions(QQ['t']) - sage: s([1]) + s2([2]) # todo: not implemented - - This feature will probably come back at some point through - improvements to the Sage coercion system. + sage: S.m()(S3.sp()[2,1]) + -m[1] + 2*m[1, 1, 1] + m[2, 1] Backward compatibility should be essentially retained. @@ -3028,6 +3029,21 @@ def construction(self): """ return SymmetricFunctionsFunctor(self._descriptor), self.base_ring() + def change_ring(self, R): + r""" + Return the base change of ``self`` to `R`. + + EXAMPLES:: + + sage: s = SymmetricFunctions(ZZ).s() + sage: s.change_ring(QQ) + Symmetric Functions over Rational Field in the Schur basis + """ + if R is self.base_ring(): + return self + functor, _ = self.construction() + return functor(R) + class SymmetricFunctionAlgebra_generic_Element(CombinatorialFreeModule.Element): r""" From 08d4d78120302bfe02ea2489f6e2d7e14ef5bf6a Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Fri, 9 Feb 2024 16:53:36 +0100 Subject: [PATCH 046/117] make map_coefficients more general, in line with polynomials --- src/sage/categories/modules_with_basis.py | 49 +++++++++++++++++++---- 1 file changed, 41 insertions(+), 8 deletions(-) diff --git a/src/sage/categories/modules_with_basis.py b/src/sage/categories/modules_with_basis.py index df7a04edc1f..41236ea3491 100644 --- a/src/sage/categories/modules_with_basis.py +++ b/src/sage/categories/modules_with_basis.py @@ -28,6 +28,7 @@ from sage.categories.fields import Fields from sage.categories.modules import Modules from sage.categories.poor_man_map import PoorManMap +from sage.categories.map import Map from sage.structure.element import Element, parent @@ -2031,17 +2032,25 @@ def trailing_term(self, *args, **kwds): """ return self.parent().term(*self.trailing_item(*args, **kwds)) - def map_coefficients(self, f): + def map_coefficients(self, f, new_base_ring=None): """ - Mapping a function on coefficients. + Return the element obtained by applying ``f`` to the non-zero + coefficients of ``self``. + + If ``f`` is a :class:`sage.categories.map.Map`, then the resulting + polynomial will be defined over the codomain of ``f``. Otherwise, the + resulting polynomial will be over the same ring as ``self``. Set + ``new_base_ring`` to override this behaviour. + + An error is raised if the coefficients are not in the new base ring. INPUT: - - ``f`` -- an endofunction on the coefficient ring of the - free module + - ``f`` -- a callable that will be applied to the + coefficients of ``self``. - Return a new element of ``self.parent()`` obtained by applying the - function ``f`` to all of the coefficients of ``self``. + - ``new_base_ring`` (optional) -- if given, the resulting element + will be defined over this ring. EXAMPLES:: @@ -2065,8 +2074,32 @@ def map_coefficients(self, f): sage: a = s([2,1]) + 2*s([3,2]) # needs sage.combinat sage.modules sage: a.map_coefficients(lambda x: x * 2) # needs sage.combinat sage.modules 2*s[2, 1] + 4*s[3, 2] - """ - return self.parent().sum_of_terms( (m, f(c)) for m,c in self ) + + We can map into a different base ring:: + + sage: # needs sage.combinat + sage: e = SymmetricFunctions(QQ).elementary() + sage: a = 1/2*(e([2,1]) + e([1,1,1])); a + 1/2*e[1, 1, 1] + 1/2*e[2, 1] + sage: b = a.map_coefficients(lambda c: 2*c, ZZ); b + e[1, 1, 1] + e[2, 1] + sage: b.parent() + Symmetric Functions over Integer Ring in the elementary basis + sage: b.map_coefficients(lambda c: 1/2*c, ZZ) + Traceback (most recent call last): + ... + TypeError: no conversion of this rational to integer + """ + R = self.parent() + if new_base_ring is not None: + B = new_base_ring + R = R.change_ring(B) + elif isinstance(f, Map): + B = f.codomain() + R = R.change_ring(B) + else: + B = self.base_ring() + return R.sum_of_terms((m, B(f(c))) for m, c in self) def map_support(self, f): """ From 4c1d4557226a50cfff1dc5facfc44e32517dc798 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Fri, 9 Feb 2024 17:26:00 +0100 Subject: [PATCH 047/117] correct equality --- src/sage/combinat/sf/sfa.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/combinat/sf/sfa.py b/src/sage/combinat/sf/sfa.py index 07daa75375a..8db655bb658 100644 --- a/src/sage/combinat/sf/sfa.py +++ b/src/sage/combinat/sf/sfa.py @@ -6455,7 +6455,7 @@ def action(x): def __eq__(self, other): if not isinstance(other, SymmetricFunctionsFunctor): return False - return self.vars == other.vars + return self._descriptor == other._descriptor def _repr_(self): """ From 3a056bd6458c52b1238444507c0a7326b7c34394 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Fri, 9 Feb 2024 17:29:41 +0100 Subject: [PATCH 048/117] fix equality and subs in UndeterminedCoefficientsRingElement, fix construction of univariate LazyPowerSeriesRing --- src/sage/data_structures/stream.py | 29 +++++++++++++++++------------ src/sage/rings/lazy_series_ring.py | 3 +++ 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index 442b0ddf4e4..783d16fd155 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -1257,16 +1257,12 @@ def __init__(self, parent, v): Element.__init__(self, parent) self._p = v + def __bool__(self): + return bool(self._p) + def _repr_(self): return repr(self._p) - def _richcmp_(self, other, op): - r""" - Compare ``self`` with ``other`` with respect to the comparison - operator ``op``. - """ - return self._p._richcmp_(other._p, op) - def _add_(self, other): """ @@ -1332,7 +1328,7 @@ def variables(self): def rational_function(self): return self._p - def subs(self, d): + def subs(self, in_dict=None, *args, **kwds): """ EXAMPLES:: @@ -1344,19 +1340,26 @@ def subs(self, d): sage: (p/q).subs({v: 3}) 4/(FESDUMMY_... + 1) """ +# if isinstance(in_dict, dict): +# R = self._p.parent() +# in_dict = {ZZ(m) if m in ZZ else R(m): v for m, v in in_dict.items()} +# +# P = self.parent() +# return P.element_class(P, self._p.subs(in_dict, *args, **kwds)) P = self.parent() p_num = P._P(self._p.numerator()) V_num = p_num.variables() - d_num = {P._P(v): c for v, c in d.items() + d_num = {P._P(v): c for v, c in in_dict.items() if v in V_num} num = p_num.subs(d_num) p_den = P._P(self._p.denominator()) V_den = p_den.variables() - d_den = {P._P(v): c for v, c in d.items() + d_den = {P._P(v): c for v, c in in_dict.items() if v in V_den} den = p_den.subs(d_den) return P.element_class(P, P._PF(num / den)) + from sage.categories.pushout import UndeterminedCoefficientsFunctor class UndeterminedCoefficientsRing(UniqueRepresentation, Parent): @@ -1527,7 +1530,8 @@ def define_implicitly(self, series, initial_values, equations, - ``series`` -- a list of series - ``equations`` -- a list of equations defining the series - ``initial_values`` -- a list specifying ``self[0], self[1], ...`` - - ``R`` -- the ring containing the coefficients (after substitution) + - ``base_ring`` -- the base ring + - ``coefficient_ring`` -- the ring containing the coefficients (after substitution) - ``terms_of_degree`` -- a function returning the list of terms of a given degree """ @@ -1694,7 +1698,8 @@ def _subs_in_caches(self, var, val): else: c = c.map_coefficients(lambda e: e.subs({var: val})) try: - c = c.map_coefficients(lambda e: self._base_ring(e.rational_function()), + c = c.map_coefficients(lambda e: (e if e in self._base_ring + else self._base_ring(e.rational_function())), self._base_ring) except TypeError: pass diff --git a/src/sage/rings/lazy_series_ring.py b/src/sage/rings/lazy_series_ring.py index 8d1d2841310..f4aae77c6e5 100644 --- a/src/sage/rings/lazy_series_ring.py +++ b/src/sage/rings/lazy_series_ring.py @@ -2378,6 +2378,9 @@ def __init__(self, base_ring, names, sparse=True, category=None): def construction(self): 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) From 791c63b728e60048740936ad86a3bf3219a727fd Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Sat, 10 Feb 2024 22:59:33 +0100 Subject: [PATCH 049/117] beautify repr, add doctests, deprecate corresponding_basis_over --- src/sage/combinat/sf/sfa.py | 182 +++++++++++++++++++++++++++--------- 1 file changed, 136 insertions(+), 46 deletions(-) diff --git a/src/sage/combinat/sf/sfa.py b/src/sage/combinat/sf/sfa.py index 8db655bb658..92e39be373f 100644 --- a/src/sage/combinat/sf/sfa.py +++ b/src/sage/combinat/sf/sfa.py @@ -625,6 +625,11 @@ def corresponding_basis_over(self, R): sage: Sym = SymmetricFunctions(QQ) sage: m = Sym.monomial() sage: m.corresponding_basis_over(ZZ) + doctest:warning + ... + DeprecationWarning: S.corresponding_basis_over(R) is deprecated. + Use S.change_ring(R) instead. + See https://github.com/sagemath/sage/issues/37220 for details. Symmetric Functions over Integer Ring in the monomial basis sage: Sym = SymmetricFunctions(CyclotomicField()) @@ -635,7 +640,8 @@ def corresponding_basis_over(self, R): sage: P = ZZ['q','t'] sage: Sym = SymmetricFunctions(P) sage: mj = Sym.macdonald().J() - sage: mj.corresponding_basis_over(Integers(13)) + sage: mj.corresponding_basis_over(Integers(13)['q','t']) + Symmetric Functions over Multivariate Polynomial Ring in q, t over Ring of integers modulo 13 in the Macdonald J basis TESTS: @@ -658,23 +664,40 @@ def corresponding_basis_over(self, R): Symmetric Functions over Universal Cyclotomic Field in the forgotten basis sage: Sym.w().corresponding_basis_over(CyclotomicField()) Symmetric Functions over Universal Cyclotomic Field in the Witt basis - sage: Sym.macdonald().P().corresponding_basis_over(CyclotomicField()) - sage: Sym.macdonald().Q().corresponding_basis_over(CyclotomicField()) - sage: Sym.macdonald().J().corresponding_basis_over(CyclotomicField()) - sage: Sym.macdonald().H().corresponding_basis_over(CyclotomicField()) - sage: Sym.macdonald().Ht().corresponding_basis_over(CyclotomicField()) - sage: Sym.macdonald().S().corresponding_basis_over(CyclotomicField()) - sage: Sym.macdonald(q=1).S().corresponding_basis_over(CyclotomicField()) + sage: Sym.macdonald().P().corresponding_basis_over(CyclotomicField()['q', 't']) + Symmetric Functions over Multivariate Polynomial Ring in q, t over Universal Cyclotomic Field in the Macdonald P basis + sage: Sym.macdonald().Q().corresponding_basis_over(CyclotomicField()['q', 't']) + Symmetric Functions over Multivariate Polynomial Ring in q, t over Universal Cyclotomic Field in the Macdonald Q basis + sage: Sym.macdonald().J().corresponding_basis_over(CyclotomicField()['q', 't']) + Symmetric Functions over Multivariate Polynomial Ring in q, t over Universal Cyclotomic Field in the Macdonald J basis + sage: Sym.macdonald().H().corresponding_basis_over(CyclotomicField()['q', 't']) + Symmetric Functions over Multivariate Polynomial Ring in q, t over Universal Cyclotomic Field in the Macdonald H basis + sage: Sym.macdonald().Ht().corresponding_basis_over(CyclotomicField()['q', 't']) + Symmetric Functions over Multivariate Polynomial Ring in q, t over Universal Cyclotomic Field in the Macdonald Ht basis + sage: Sym.macdonald().S().corresponding_basis_over(CyclotomicField()['q', 't']) + Symmetric Functions over Multivariate Polynomial Ring in q, t over Universal Cyclotomic Field in the Macdonald S basis + sage: Sym.macdonald(q=1).S().corresponding_basis_over(CyclotomicField()['t']) + Symmetric Functions over Univariate Polynomial Ring in t over Universal Cyclotomic Field in the Macdonald S with q=1 basis sage: Sym.macdonald(q=1,t=3).P().corresponding_basis_over(CyclotomicField()) - sage: Sym.hall_littlewood().P().corresponding_basis_over(CyclotomicField()) - sage: Sym.hall_littlewood().Q().corresponding_basis_over(CyclotomicField()) - sage: Sym.hall_littlewood().Qp().corresponding_basis_over(CyclotomicField()) + Symmetric Functions over Universal Cyclotomic Field in the Macdonald P with q=1 and t=3 basis + sage: Sym.hall_littlewood().P().corresponding_basis_over(CyclotomicField()['t']) + Symmetric Functions over Univariate Polynomial Ring in t over Universal Cyclotomic Field in the Hall-Littlewood P basis + sage: Sym.hall_littlewood().Q().corresponding_basis_over(CyclotomicField()['t']) + Symmetric Functions over Univariate Polynomial Ring in t over Universal Cyclotomic Field in the Hall-Littlewood Q basis + sage: Sym.hall_littlewood().Qp().corresponding_basis_over(CyclotomicField()['t']) + Symmetric Functions over Univariate Polynomial Ring in t over Universal Cyclotomic Field in the Hall-Littlewood Qp basis sage: Sym.hall_littlewood(t=1).P().corresponding_basis_over(CyclotomicField()) - sage: Sym.jack().J().corresponding_basis_over(CyclotomicField()) - sage: Sym.jack().P().corresponding_basis_over(CyclotomicField()) - sage: Sym.jack().Q().corresponding_basis_over(CyclotomicField()) - sage: Sym.jack().Qp().corresponding_basis_over(CyclotomicField()) + Symmetric Functions over Universal Cyclotomic Field in the Hall-Littlewood P with t=1 basis + sage: Sym.jack().J().corresponding_basis_over(CyclotomicField()['t']) + Symmetric Functions over Univariate Polynomial Ring in t over Universal Cyclotomic Field in the Jack J basis + sage: Sym.jack().P().corresponding_basis_over(CyclotomicField()['t']) + Symmetric Functions over Univariate Polynomial Ring in t over Universal Cyclotomic Field in the Jack P basis + sage: Sym.jack().Q().corresponding_basis_over(CyclotomicField()['t']) + Symmetric Functions over Univariate Polynomial Ring in t over Universal Cyclotomic Field in the Jack Q basis + sage: Sym.jack().Qp().corresponding_basis_over(CyclotomicField()['t']) + Symmetric Functions over Univariate Polynomial Ring in t over Universal Cyclotomic Field in the Jack Qp basis sage: Sym.jack(t=1).J().corresponding_basis_over(CyclotomicField()) + Symmetric Functions over Universal Cyclotomic Field in the Jack J with t=1 basis sage: Sym.zonal().corresponding_basis_over(CyclotomicField()) Symmetric Functions over Universal Cyclotomic Field in the zonal basis sage: Sym.llt(3).hspin().corresponding_basis_over(CyclotomicField()) @@ -688,26 +711,13 @@ def corresponding_basis_over(self, R): rewritten as soon as the bases of ``SymmetricFunctions`` are put on a more robust and systematic footing. """ - from sage.combinat.sf.sf import SymmetricFunctions - from sage.misc.call import attrcall + from sage.misc.superseded import deprecation + deprecation(37220, 'S.corresponding_basis_over(R) is deprecated.' + ' Use S.change_ring(R) instead.') try: - return attrcall(self._basis)(SymmetricFunctions(R)) - except AttributeError: # or except (AttributeError, ValueError): + return self.change_ring(R) + except NotImplementedError: return None - #Alternative code proposed by Florent Hivert, which sadly fails for the - #forgotten basis (which reduces differently than the other ones): - #try: - # parentred1 = self._reduction - # parentred2 = parentred1[1][0]._reduction - # parentred2prime = tuple([parentred2[0], tuple([R]), parentred2[2]]) - # from sage.structure.unique_representation import unreduce - # parent2 = unreduce(*parentred2prime) - # parentred1prime = tuple([parentred1[0], tuple([parent2]), parentred1[2]]) - # return unreduce(*parentred1prime) - #except (AttributeError, ValueError): - # return None - #This code relied heavily on the construction of bases of - #``SymmetricFunctions`` and on their reduction. def skew_schur(self, x): """ @@ -1034,8 +1044,9 @@ def component(i, g): # == h_g[L_i] # Now let's try to find out what basis self is in, and # construct the corresponding basis of symmetric functions # over QQ. - corresponding_parent_over_QQ = self.corresponding_basis_over(QQ) - if corresponding_parent_over_QQ is None: + try: + corresponding_parent_over_QQ = self.change_ring(QQ) + except (NotImplementedError, TypeError): # This is the case where the corresponding basis # over QQ cannot be found. This can have two reasons: # Either the basis depends on variables (like the @@ -1205,8 +1216,9 @@ def component(i, g): # == h_g[L_i] or e_g[L_i] # Now let's try to find out what basis self is in, and # construct the corresponding basis of symmetric functions # over QQ. - corresponding_parent_over_QQ = self.corresponding_basis_over(QQ) - if corresponding_parent_over_QQ is None: + try: + corresponding_parent_over_QQ = self.change_ring(QQ) + except (NotImplementedError, TypeError): # This is the case where the corresponding basis # over QQ cannot be found. This can have two reasons: # Either the basis depends on variables (like the @@ -4115,8 +4127,9 @@ def itensor(self, x): # Now let's try to find out what basis self is in, and # construct the corresponding basis of symmetric functions # over QQ. - corresponding_parent_over_QQ = parent.corresponding_basis_over(QQ) - if corresponding_parent_over_QQ is None: + try: + corresponding_parent_over_QQ = parent.change_ring(QQ) + except (NotImplementedError, TypeError): # This is the case where the corresponding basis # over QQ cannot be found. This can have two reasons: # Either the basis depends on variables (like the @@ -4314,7 +4327,7 @@ def reduced_kronecker_product(self, x): comp_x = comp_parent(x) # Now, comp_self and comp_x are the same as self and x, but in the # Schur basis, which we call comp_parent. - schur_Q = comp_parent.corresponding_basis_over(QQ) + schur_Q = comp_parent.change_ring(QQ) # schur_Q is the Schur basis of the symmetric functions over QQ. result = comp_parent.zero() for lam, a in comp_self: @@ -4756,8 +4769,9 @@ def f(lam, mu): return parent(p._apply_multi_module_morphism(p(self),p(x),f)) comp_parent = parent comp_self = self - corresponding_parent_over_QQ = parent.corresponding_basis_over(QQ) - if corresponding_parent_over_QQ is None: + try: + corresponding_parent_over_QQ = parent.change_ring(QQ) + except (NotImplementedError, TypeError): comp_parent = parent.realization_of().schur() comp_self = comp_parent(self) from sage.combinat.sf.sf import SymmetricFunctions @@ -6412,9 +6426,43 @@ def exponential_specialization(self, t=None, q=1): from sage.categories.functor import Functor class SymmetricFunctionsFunctor(ConstructionFunctor): + """ + A constructor for free Zinbiel algebras. + + EXAMPLES:: + + sage: s = SymmetricFunctions(QQ).s() + sage: s.construction() + (SymmetricFunctionsFunctor[schur], Rational Field) + """ rank = 9 def __init__(self, descriptor): + r""" + Initialise the functor. + + INPUT: + + - ``descriptor`` -- an iterable of pairs ``(family, params)`` + or singletons ``(basis,)``, where ``family`` and ``basis`` + are strings and ``params`` is a dictionary whose keys are + strings. + + .. WARNING: + + Strictly speaking, this is not necessarily a functor on + :class:`CommutativeRings`, but rather a functor on + commutative rings with some distinguished elements. For + example, for the Macdonald polynomials, we have to + specify `q` and `t` in the ring. + + EXAMPLES:: + + sage: from sage.combinat.sf.sfa import SymmetricFunctionsFunctor + sage: R. = ZZ[] + sage: SymmetricFunctionsFunctor((('macdonald', {'q': q, 't': t}), ('H',))) + SymmetricFunctionsFunctor[macdonald(q=q, t=t).H] + """ self._descriptor = descriptor Functor.__init__(self, CommutativeRings(), CommutativeRings()) @@ -6425,7 +6473,7 @@ def _apply_functor(self, R): EXAMPLES:: sage: s = SymmetricFunctions(ZZ).s() - sage: F, R = s.construction() + sage: F, R = s.construction() # indirect doctest sage: F(QQ) Symmetric Functions over Rational Field in the Schur basis """ @@ -6443,6 +6491,27 @@ def _apply_functor_to_morphism(self, f): """ Apply the functor ``self`` to the ring morphism `f`. + EXAMPLES:: + + sage: s = SymmetricFunctions(QQ).s() + sage: F, R = s.construction() + sage: F(ZZ.hom(GF(3))) # indirect doctest + Generic morphism: + From: Symmetric Functions over Integer Ring in the Schur basis + To: Symmetric Functions over Finite Field of size 3 in the Schur basis + + sage: R. = ZZ[] + sage: P = SymmetricFunctions(R).jack().P() + sage: F, R = P.construction() + sage: F(ZZ["t"].hom(GF(3)["t"])) + Generic morphism: + From: Symmetric Functions over Univariate Polynomial Ring in t over Integer Ring in the Jack P basis + To: Symmetric Functions over Univariate Polynomial Ring in t over Finite Field of size 3 in the Jack P basis + + sage: R. = ZZ[] + sage: H = SymmetricFunctions(R).macdonald().H() + sage: F, R = H.construction() + sage: F(ZZ["q", "t"].hom(GF(3)["q", "t"])) # known bug """ dom = self(f.domain()) codom = self(f.codomain()) @@ -6453,6 +6522,20 @@ def action(x): return dom.module_morphism(function=action, codomain=codom) def __eq__(self, other): + """ + EXAMPLES:: + + sage: R. = ZZ[] + sage: S. = QQ[] + sage: T. = QQ[] + sage: PR = SymmetricFunctions(R).jack().P() + sage: PS = SymmetricFunctions(S).jack().P() + sage: PT = SymmetricFunctions(T).jack(t=s).P() + sage: PR.construction()[0] == PS.construction()[0] + True + sage: PR.construction()[0] == PT.construction()[0] + False + """ if not isinstance(other, SymmetricFunctionsFunctor): return False return self._descriptor == other._descriptor @@ -6461,13 +6544,20 @@ def _repr_(self): """ TESTS:: - sage: R. = ZZ[] + sage: R. = ZZ[] sage: H = SymmetricFunctions(R).macdonald().H() sage: F, R = H.construction() sage: F - (('macdonald', {'q': q, 't': t}), ('H',)) + SymmetricFunctionsFunctor[macdonald(q=q, t=t).H] """ - return repr(self._descriptor) + basis = ".".join(method + + ("(" + + ", ".join(key + "=" + repr(param) + for key, param in params[0].items()) + + ")" if params + else "") + for method, *params in self._descriptor) + return "SymmetricFunctionsFunctor[" + basis + "]" ################### def _lmax(x): From 6a209947d608e59fb40a19daaff1df9cf5220e1d Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Sat, 10 Feb 2024 23:44:34 +0100 Subject: [PATCH 050/117] provide _descriptor for llt --- src/sage/combinat/sf/llt.py | 2 ++ src/sage/combinat/sf/sfa.py | 8 ++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/sage/combinat/sf/llt.py b/src/sage/combinat/sf/llt.py index 6e0e20d49ba..cc7bfb3e688 100644 --- a/src/sage/combinat/sf/llt.py +++ b/src/sage/combinat/sf/llt.py @@ -645,6 +645,7 @@ def __init__(self, llt): self._m_to_self_cache = m_to_hsp_cache[level] LLT_generic.__init__(self, llt, prefix="HSp%s" % level) + self._descriptor = (("llt", {"k": self.level(), "t": self.t}), ("hspin",)) def _to_m(self, part): r""" @@ -713,6 +714,7 @@ def __init__(self, llt): self._self_to_m_cache = hcosp_to_m_cache[level] self._m_to_self_cache = m_to_hcosp_cache[level] LLT_generic.__init__(self, llt, prefix="HCosp%s" % level) + self._descriptor = (("llt", {"k": self.level(), "t": self.t}), ("hcospin",)) def _to_m(self, part): r""" diff --git a/src/sage/combinat/sf/sfa.py b/src/sage/combinat/sf/sfa.py index 92e39be373f..79cce5a9292 100644 --- a/src/sage/combinat/sf/sfa.py +++ b/src/sage/combinat/sf/sfa.py @@ -700,10 +700,14 @@ def corresponding_basis_over(self, R): Symmetric Functions over Universal Cyclotomic Field in the Jack J with t=1 basis sage: Sym.zonal().corresponding_basis_over(CyclotomicField()) Symmetric Functions over Universal Cyclotomic Field in the zonal basis - sage: Sym.llt(3).hspin().corresponding_basis_over(CyclotomicField()) - sage: Sym.llt(3).hcospin().corresponding_basis_over(CyclotomicField()) + sage: Sym.llt(3).hspin().corresponding_basis_over(CyclotomicField()['t']) + Symmetric Functions over Univariate Polynomial Ring in t over Universal Cyclotomic Field in the level 3 LLT spin basis + sage: Sym.llt(3).hcospin().corresponding_basis_over(CyclotomicField()['t']) + Symmetric Functions over Univariate Polynomial Ring in t over Universal Cyclotomic Field in the level 3 LLT cospin basis sage: Sym.llt(3, t=1).hspin().corresponding_basis_over(CyclotomicField()) + Symmetric Functions over Universal Cyclotomic Field in the level 3 LLT spin with t=1 basis sage: Sym.llt(3, t=1).hcospin().corresponding_basis_over(CyclotomicField()) + Symmetric Functions over Universal Cyclotomic Field in the level 3 LLT cospin with t=1 basis .. TODO:: From 77b79c74c7b9ab3333d3b1256078a6aac36ffe0e Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Sun, 11 Feb 2024 12:11:45 +0100 Subject: [PATCH 051/117] construction for dual bases --- src/sage/combinat/sf/dual.py | 21 ++++++++++++--------- src/sage/combinat/sf/sfa.py | 2 +- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/sage/combinat/sf/dual.py b/src/sage/combinat/sf/dual.py index 58b69743c7b..69cada9e2ac 100644 --- a/src/sage/combinat/sf/dual.py +++ b/src/sage/combinat/sf/dual.py @@ -26,7 +26,13 @@ class SymmetricFunctionAlgebra_dual(classical.SymmetricFunctionAlgebra_classical): - def __init__(self, dual_basis, scalar, scalar_name="", basis_name=None, prefix=None): + @staticmethod + def __classcall__(cls, dual_basis, scalar, scalar_name="", basis_name=None, prefix=None): + if prefix is None: + prefix = 'd_'+dual_basis.prefix() + return super().__classcall__(cls, dual_basis, scalar, scalar_name, basis_name, prefix) + + def __init__(self, dual_basis, scalar, scalar_name, basis_name, prefix): r""" Generic dual basis of a basis of symmetric functions. @@ -72,9 +78,9 @@ def __init__(self, dual_basis, scalar, scalar_name="", basis_name=None, prefix=N EXAMPLES:: sage: e = SymmetricFunctions(QQ).e() - sage: f = e.dual_basis(prefix = "m", basis_name="Forgotten symmetric functions"); f + sage: f = e.dual_basis(prefix="m", basis_name="Forgotten symmetric functions"); f Symmetric Functions over Rational Field in the Forgotten symmetric functions basis - sage: TestSuite(f).run(skip='_test_construction', elements = [f[1,1]+2*f[2], f[1]+3*f[1,1]]) + sage: TestSuite(f).run(elements=[f[1,1]+2*f[2], f[1]+3*f[1,1]]) sage: TestSuite(f).run() # long time (11s on sage.math, 2011) This class defines canonical coercions between ``self`` and @@ -123,6 +129,9 @@ def __init__(self, dual_basis, scalar, scalar_name="", basis_name=None, prefix=N self._dual_basis = dual_basis self._scalar = scalar self._scalar_name = scalar_name + if self._scalar == sage.combinat.sf.sfa.zee: + self._descriptor = (self._dual_basis._descriptor + + (("dual_basis", {"prefix": prefix, "basis_name": basis_name}),)) # Set up the cache @@ -145,9 +154,6 @@ def __init__(self, dual_basis, scalar, scalar_name="", basis_name=None, prefix=N self._sym = sage.combinat.sf.sf.SymmetricFunctions(scalar_target) self._p = self._sym.power() - if prefix is None: - prefix = 'd_'+dual_basis.prefix() - classical.SymmetricFunctionAlgebra_classical.__init__(self, self._sym, basis_name=basis_name, prefix=prefix) @@ -157,9 +163,6 @@ def __init__(self, dual_basis, scalar, scalar_name="", basis_name=None, prefix=N self.register_coercion(SetMorphism(Hom(self._dual_basis, self, category), self._dual_to_self)) self._dual_basis.register_coercion(SetMorphism(Hom(self, self._dual_basis, category), self._self_to_dual)) - def construction(self): - raise NotImplementedError - def _dual_to_self(self, x): """ Coerce an element of the dual of ``self`` canonically into ``self``. diff --git a/src/sage/combinat/sf/sfa.py b/src/sage/combinat/sf/sfa.py index 79cce5a9292..070ae8f64e8 100644 --- a/src/sage/combinat/sf/sfa.py +++ b/src/sage/combinat/sf/sfa.py @@ -6431,7 +6431,7 @@ def exponential_specialization(self, t=None, q=1): class SymmetricFunctionsFunctor(ConstructionFunctor): """ - A constructor for free Zinbiel algebras. + A constructor for algebras of symmetric functions. EXAMPLES:: From 3752688a112b8b089b3e21f2c3d5f79a7468b952 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Sun, 11 Feb 2024 16:07:24 +0100 Subject: [PATCH 052/117] fix equality for llt --- src/sage/combinat/sf/llt.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/sage/combinat/sf/llt.py b/src/sage/combinat/sf/llt.py index cc7bfb3e688..1e46ddeeae3 100644 --- a/src/sage/combinat/sf/llt.py +++ b/src/sage/combinat/sf/llt.py @@ -91,8 +91,11 @@ class LLT_class(UniqueRepresentation): sage: HS3x(HC3t2[3,1]) 2*HSp3[3, 1] + (-2*x+1)*HSp3[4] """ + @staticmethod + def __classcall__(cls, Sym, k, t='t'): + return super().__classcall__(cls, Sym, k, Sym.base_ring()(t)) - def __init__(self, Sym, k, t='t'): + def __init__(self, Sym, k, t): r""" Class of LLT symmetric function bases @@ -129,7 +132,7 @@ def __init__(self, Sym, k, t='t'): self._k = k self._sym = Sym self._name = "level %s LLT polynomials" % self._k - self.t = Sym.base_ring()(t) + self.t = t self._name_suffix = "" if str(t) != 't': self._name_suffix += " with t=%s" % self.t From 0688f74e84eb85cc6d6a24d925d157f3a63d7bfd Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Sat, 24 Feb 2024 10:50:25 +0100 Subject: [PATCH 053/117] correct ring for _terms_of_degrees in LazySymmetricFunctions --- src/sage/rings/lazy_series_ring.py | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/sage/rings/lazy_series_ring.py b/src/sage/rings/lazy_series_ring.py index 02a477e4e01..67a89a9af80 100644 --- a/src/sage/rings/lazy_series_ring.py +++ b/src/sage/rings/lazy_series_ring.py @@ -939,8 +939,8 @@ def define_implicitly(self, series, equations): sage: A = T.undefined() sage: B = T.undefined() sage: T.define_implicitly([A, B], [A - X*E(B), B - Y*E(A)]) - sage: A[1] # known bug, not tested - + sage: A[:3] + [h[1] # h[], h[1] # h[1]] """ s = [a[0]._coeff_stream if isinstance(a, (tuple, list)) else a._coeff_stream @@ -2438,13 +2438,16 @@ def _terms_of_degree(self, n, R): EXAMPLES:: - sage: L. = LazyPowerSeriesRing(QQ) - sage: L._terms_of_degree(3, QQ) + sage: L. = LazyPowerSeriesRing(ZZ) + sage: m = L._terms_of_degree(3, QQ["z"]); m [1] - sage: L. = LazyPowerSeriesRing(QQ) - sage: L._terms_of_degree(3, QQ) + 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()] @@ -3127,11 +3130,13 @@ def _terms_of_degree(self, n, R): sage: # needs sage.modules sage: s = SymmetricFunctions(ZZ).s() sage: L = LazySymmetricFunctions(s) - sage: L._terms_of_degree(3, ZZ) + 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: L._terms_of_degree(3, ZZ) + sage: m = L._terms_of_degree(3, QQ["x"]); m [s[3] # s[], s[2, 1] # s[], s[1, 1, 1] # s[], @@ -3142,11 +3147,14 @@ def _terms_of_degree(self, n, R): 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 = [] From ffbbe789297a7db18a48004a705270e693840903 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Sat, 24 Feb 2024 18:22:10 +0100 Subject: [PATCH 054/117] fix doctests --- src/sage/combinat/species/generating_series.py | 4 ++-- src/sage/data_structures/stream.py | 4 ++-- src/sage/rings/lazy_series_ring.py | 4 +--- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/sage/combinat/species/generating_series.py b/src/sage/combinat/species/generating_series.py index b7b208c3d87..0764f99beee 100644 --- a/src/sage/combinat/species/generating_series.py +++ b/src/sage/combinat/species/generating_series.py @@ -142,7 +142,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 """ @@ -269,7 +269,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/data_structures/stream.py b/src/sage/data_structures/stream.py index 783d16fd155..4993c0b1ddb 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -3514,12 +3514,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: diff --git a/src/sage/rings/lazy_series_ring.py b/src/sage/rings/lazy_series_ring.py index 67a89a9af80..01139abe8ed 100644 --- a/src/sage/rings/lazy_series_ring.py +++ b/src/sage/rings/lazy_series_ring.py @@ -927,8 +927,7 @@ def define_implicitly(self, series, equations): 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) - + (1/120*x^6*y+5/3*x^5*y^2+12*x^4*y^3+9*x^3*y^4+2/3*x^2*y^5+1/720*x*y^6) - + O(x,y)^8 + + O(x,y)^7 sage: h = SymmetricFunctions(QQ).h() sage: S = LazySymmetricFunctions(h) @@ -3134,7 +3133,6 @@ def _terms_of_degree(self, n, R): [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[], From 32cebca7b091dad75f9f7d6d8afaec2b3c8b804a Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Sat, 24 Feb 2024 18:58:40 +0100 Subject: [PATCH 055/117] fix blank lines --- src/sage/rings/lazy_series.py | 1 + src/sage/rings/lazy_series_ring.py | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/lazy_series.py b/src/sage/rings/lazy_series.py index 15d39d765d1..29c2c5df94c 100644 --- a/src/sage/rings/lazy_series.py +++ b/src/sage/rings/lazy_series.py @@ -5268,6 +5268,7 @@ def coefficient(n): # The arity is at least 2 gv = min(h._coeff_stream._approximate_order for h in g) + def coefficient(n): r = R.zero() for i in range(n // gv + 1): diff --git a/src/sage/rings/lazy_series_ring.py b/src/sage/rings/lazy_series_ring.py index 01139abe8ed..25323bb38c4 100644 --- a/src/sage/rings/lazy_series_ring.py +++ b/src/sage/rings/lazy_series_ring.py @@ -3162,7 +3162,6 @@ def _terms_of_degree(self, n, R): 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``. From 6cd4f94e30508a538de9825a9f3b77fd569aa631 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Sat, 24 Feb 2024 22:31:58 +0100 Subject: [PATCH 056/117] another test, fix type of constant in Stream_exact --- src/sage/rings/lazy_series_ring.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/sage/rings/lazy_series_ring.py b/src/sage/rings/lazy_series_ring.py index 25323bb38c4..2db222d2f15 100644 --- a/src/sage/rings/lazy_series_ring.py +++ b/src/sage/rings/lazy_series_ring.py @@ -940,6 +940,21 @@ def define_implicitly(self, series, equations): 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 + implicitely 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[:3] """ s = [a[0]._coeff_stream if isinstance(a, (tuple, list)) else a._coeff_stream @@ -3278,7 +3293,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) @@ -3322,7 +3337,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.base_ring().zero(), degree=degree) return self.element_class(self, coeff_stream) if callable(x): @@ -3332,7 +3347,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.base_ring().zero(), degree=degree) return self.element_class(self, coeff_stream) if check: From 26781b39e3f043ca0e318b31ac59012efd374349 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Sun, 25 Feb 2024 20:37:08 +0100 Subject: [PATCH 057/117] add output for a doctest --- src/sage/rings/lazy_series_ring.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sage/rings/lazy_series_ring.py b/src/sage/rings/lazy_series_ring.py index 2db222d2f15..86e8aa64a00 100644 --- a/src/sage/rings/lazy_series_ring.py +++ b/src/sage/rings/lazy_series_ring.py @@ -955,6 +955,7 @@ def define_implicitly(self, series, equations): sage: A = T.undefined() sage: T.define_implicitly([A], [P(X)*P(Y)*A - P(X+Y)]) sage: A[:3] + [p[] # p[], 0, p[1] # p[1], p[1] # p[1, 1] + p[1, 1] # p[1]] """ s = [a[0]._coeff_stream if isinstance(a, (tuple, list)) else a._coeff_stream From 57e4b403c4574f54ad1a4c5b553307299338a253 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Tue, 27 Feb 2024 20:36:40 +0100 Subject: [PATCH 058/117] move UndeterminedCoefficientsFunctor to stream.py, improve substitution and retraction of undetermined coefficients --- src/sage/categories/pushout.py | 17 --------- src/sage/data_structures/stream.py | 56 +++++++++++++++++++----------- src/sage/rings/lazy_series_ring.py | 9 +++-- 3 files changed, 39 insertions(+), 43 deletions(-) diff --git a/src/sage/categories/pushout.py b/src/sage/categories/pushout.py index 07bbb5461ac..55cd22d87dd 100644 --- a/src/sage/categories/pushout.py +++ b/src/sage/categories/pushout.py @@ -1191,23 +1191,6 @@ def _repr_(self): return "MPoly[%s]" % ','.join(self.vars) -class UndeterminedCoefficientsFunctor(ConstructionFunctor): - rank = 0 - - def __init__(self): - from .rings import Rings - Functor.__init__(self, Rings(), Rings()) - - def _apply_functor(self, R): - from sage.data_structures.stream import UndeterminedCoefficientsRing - return UndeterminedCoefficientsRing(R) - - __hash__ = ConstructionFunctor.__hash__ - - def _repr_(self): - return "UndeterminedCoefficients" - - class InfinitePolynomialFunctor(ConstructionFunctor): r""" A Construction Functor for Infinite Polynomial Rings (see :mod:`~sage.rings.polynomial.infinite_polynomial_ring`). diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index 4993c0b1ddb..21d863894fc 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -102,9 +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.categories.rings import Rings from sage.misc.cachefunc import cached_method -from sage.rings.polynomial.infinite_polynomial_element import InfinitePolynomial_dense -from sage.rings.polynomial.infinite_polynomial_ring import InfinitePolynomialRing +from sage.categories.functor import Functor +from sage.categories.pushout import ConstructionFunctor from collections import defaultdict lazy_import('sage.combinat.sf.sfa', ['_variables_recursive', '_raise_variables']) @@ -1328,6 +1329,10 @@ def variables(self): def rational_function(self): return self._p + def is_constant(self): + return (self._p.numerator().is_constant() + and self._p.denominator().is_constant()) + def subs(self, in_dict=None, *args, **kwds): """ EXAMPLES:: @@ -1360,7 +1365,20 @@ def subs(self, in_dict=None, *args, **kwds): return P.element_class(P, P._PF(num / den)) -from sage.categories.pushout import UndeterminedCoefficientsFunctor +class UndeterminedCoefficientsFunctor(ConstructionFunctor): + rank = 0 + + def __init__(self): + Functor.__init__(self, Rings(), Rings()) + + def _apply_functor(self, R): + return UndeterminedCoefficientsRing(R) + + __hash__ = ConstructionFunctor.__hash__ + + def _repr_(self): + return "UndeterminedCoefficients" + class UndeterminedCoefficientsRing(UniqueRepresentation, Parent): """ @@ -1531,7 +1549,7 @@ def define_implicitly(self, series, initial_values, equations, - ``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 coefficients (after substitution) + - ``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 """ @@ -1550,7 +1568,8 @@ def define_implicitly(self, series, initial_values, equations, self._coefficient_ring = coefficient_ring self._base_ring = base_ring self._P = UndeterminedCoefficientsRing(self._base_ring) - # elements of the stream have self._P as base ring + if self._coefficient_ring != self._base_ring: + self._U = self._coefficient_ring.change_ring(self._P) self._uncomputed = True self._eqs = equations self._series = series @@ -1684,28 +1703,23 @@ def _subs_in_caches(self, var, val): # substitute variable and determine last good element good = m for i0, i in enumerate(indices): - # the following looks dangerous - could there be a - # ring that contains the UndeterminedCoefficientRing? - # it is not enough to look at the parent of - # s._cache[i] c = s._cache[i] - if c not in self._coefficient_ring: - if self._base_ring == self._coefficient_ring: + if self._base_ring == self._coefficient_ring: + if c.parent() == self._P: c = c.subs({var: val}) - f = c.rational_function() - if f in self._coefficient_ring: - c = self._coefficient_ring(f) - else: + if c.is_constant(): + c = self._base_ring(c.rational_function()) + else: + good = m - i0 - 1 + else: + if c.parent() == self._U: c = c.map_coefficients(lambda e: e.subs({var: val})) try: - c = c.map_coefficients(lambda e: (e if e in self._base_ring - else self._base_ring(e.rational_function())), + c = c.map_coefficients(lambda e: self._base_ring(e.rational_function()), self._base_ring) except TypeError: - pass - s._cache[i] = c - if c not in self._coefficient_ring: - good = m - i0 - 1 + good = m - i0 - 1 + s._cache[i] = c self._good_cache[j] += good # fix approximate_order and true_order ao = s._approximate_order diff --git a/src/sage/rings/lazy_series_ring.py b/src/sage/rings/lazy_series_ring.py index 86e8aa64a00..43fc00b2c15 100644 --- a/src/sage/rings/lazy_series_ring.py +++ b/src/sage/rings/lazy_series_ring.py @@ -941,7 +941,6 @@ def define_implicitly(self, series, equations): 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 implicitely to have a test):: @@ -954,7 +953,7 @@ def define_implicitly(self, series, equations): 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[:3] + sage: A[:4] [p[] # p[], 0, p[1] # p[1], p[1] # p[1, 1] + p[1, 1] # p[1]] """ s = [a[0]._coeff_stream if isinstance(a, (tuple, list)) @@ -2495,7 +2494,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): @@ -3338,7 +3337,7 @@ def check_homogeneous_of_degree(f, d): check_homogeneous_of_degree(e, i) coeff_stream = Stream_exact(p, order=valuation, - constant=self.base_ring().zero(), + constant=self._laurent_poly_ring.zero(), degree=degree) return self.element_class(self, coeff_stream) if callable(x): @@ -3348,7 +3347,7 @@ def check_homogeneous_of_degree(f, d): check_homogeneous_of_degree(e, i) coeff_stream = Stream_exact(p, order=valuation, - constant=self.base_ring().zero(), + constant=self._laurent_poly_ring.zero(), degree=degree) return self.element_class(self, coeff_stream) if check: From ab229656217c073fbebd86a14e5b957b38e8baed Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Tue, 27 Feb 2024 21:55:41 +0100 Subject: [PATCH 059/117] micro-optimization --- src/sage/data_structures/stream.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index 21d863894fc..3836985aaba 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -1362,7 +1362,7 @@ def subs(self, in_dict=None, *args, **kwds): d_den = {P._P(v): c for v, c in in_dict.items() if v in V_den} den = p_den.subs(d_den) - return P.element_class(P, P._PF(num / den)) + return P.element_class(P, P._PF(num, den)) class UndeterminedCoefficientsFunctor(ConstructionFunctor): From 96aea2a02eb780c30eb254a0c5a35b04aae720e8 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Tue, 2 Apr 2024 00:22:00 +0200 Subject: [PATCH 060/117] remove UndeterminedCoefficientsRing --- src/sage/data_structures/stream.py | 255 +++++++---------------------- src/sage/rings/lazy_series_ring.py | 6 +- 2 files changed, 61 insertions(+), 200 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index 3836985aaba..ede362c5d4c 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -1247,211 +1247,70 @@ def iterate_coefficients(self): denom *= n -from sage.structure.parent import Parent -from sage.structure.element import Element, parent from sage.structure.unique_representation import UniqueRepresentation -from sage.categories.fields import Fields -from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing +from sage.rings.polynomial.infinite_polynomial_ring import InfinitePolynomialRing -class UndeterminedCoefficientsRingElement(Element): - def __init__(self, parent, v): - Element.__init__(self, parent) - self._p = v +class VariablePool(UniqueRepresentation): + """ + A class to keep track of used and unused variables in an + :cls:`InfinitePolynomialRing`. - def __bool__(self): - return bool(self._p) + INPUT: - def _repr_(self): - return repr(self._p) + - ``ring``, an :cls:`InfinitePolynomialRing`. + """ + def __init__(self, ring): + self._gen = ring.gen(0) # alternatively, make :cls:`InfinitePolynomialGen` inherit from `UniqueRepresentation`. + self._pool = dict() # dict from variables actually used to indices of gens - def _add_(self, other): + def new_variable(self): """ + Return an unused variable. EXAMPLES:: - sage: from sage.data_structures.stream import UndeterminedCoefficientsRing - sage: R = UndeterminedCoefficientsRing(QQ) - sage: R(None) + 1 - FESDUMMY_... + 1 - """ - P = self.parent() - return P.element_class(P, self._p + other._p) + sage: from sage.data_structures.stream import VariablePool + sage: R. = InfinitePolynomialRing(QQ) + sage: P = VariablePool(R) + sage: P.new_variable() + a_0 - def _sub_(self, other): - """ - EXAMPLES:: + TESTS: - sage: from sage.data_structures.stream import UndeterminedCoefficientsRing - sage: R = UndeterminedCoefficientsRing(QQ) - sage: 1 - R(None) - -FESDUMMY_... + 1 - """ - P = self.parent() - return P.element_class(P, self._p - other._p) + Check, that we get a new pool for each + :cls:`InfinitePolynomialRing`:: - def _neg_(self): - """ - Return the negative of ``self``. - """ - P = self.parent() - return P.element_class(P, -self._p) + sage: R0. = InfinitePolynomialRing(QQ) + sage: P0 = VariablePool(R0) + sage: P0.new_variable() + b_0 - def _mul_(self, other): """ - EXAMPLES:: + for i in range(len(self._pool)+1): + if i not in self._pool.values(): + break + v = self._gen[i] + self._pool[v] = i + return v - sage: from sage.data_structures.stream import UndeterminedCoefficientsRing - sage: R = UndeterminedCoefficientsRing(QQ) - sage: R(None) * R(None) - FESDUMMY_...*FESDUMMY_... + def del_variable(self, v): """ - P = self.parent() - return P.element_class(P, self._p * other._p) - - def _div_(self, other): - P = self.parent() - return P.element_class(P, self._p / other._p) - - def numerator(self): - return self._p.numerator() + Remove ``v`` from the pool. - def variables(self): - """ EXAMPLES:: - sage: from sage.data_structures.stream import UndeterminedCoefficientsRing - sage: R = UndeterminedCoefficientsRing(QQ) - sage: R(None) / (R(None) + R(None)) - FESDUMMY_.../(FESDUMMY_... + FESDUMMY_...) - """ - return self._p.numerator().variables() + self._p.denominator().variables() - - def rational_function(self): - return self._p - - def is_constant(self): - return (self._p.numerator().is_constant() - and self._p.denominator().is_constant()) + sage: from sage.data_structures.stream import VariablePool + sage: R. = InfinitePolynomialRing(QQ) + sage: P = VariablePool(R) + sage: v = P.new_variable(); v + a_0 - def subs(self, in_dict=None, *args, **kwds): + sage: P.del_variable(v) + sage: v = P.new_variable(); v + a_0 """ - EXAMPLES:: - - sage: from sage.data_structures.stream import UndeterminedCoefficientsRing - sage: R = UndeterminedCoefficientsRing(QQ) - sage: p = R(None) + 1 - sage: v = p.variables()[0] - sage: q = R(None) + 1 - sage: (p/q).subs({v: 3}) - 4/(FESDUMMY_... + 1) - """ -# if isinstance(in_dict, dict): -# R = self._p.parent() -# in_dict = {ZZ(m) if m in ZZ else R(m): v for m, v in in_dict.items()} -# -# P = self.parent() -# return P.element_class(P, self._p.subs(in_dict, *args, **kwds)) - P = self.parent() - p_num = P._P(self._p.numerator()) - V_num = p_num.variables() - d_num = {P._P(v): c for v, c in in_dict.items() - if v in V_num} - num = p_num.subs(d_num) - p_den = P._P(self._p.denominator()) - V_den = p_den.variables() - d_den = {P._P(v): c for v, c in in_dict.items() - if v in V_den} - den = p_den.subs(d_den) - return P.element_class(P, P._PF(num, den)) - - -class UndeterminedCoefficientsFunctor(ConstructionFunctor): - rank = 0 - - def __init__(self): - Functor.__init__(self, Rings(), Rings()) - - def _apply_functor(self, R): - return UndeterminedCoefficientsRing(R) - - __hash__ = ConstructionFunctor.__hash__ - - def _repr_(self): - return "UndeterminedCoefficients" - - -class UndeterminedCoefficientsRing(UniqueRepresentation, Parent): - """ - Rational functions in unknowns over a base ring. - """ - # must not inherit from UniqueRepresentation, because we want a - # new set of variables for each system of equations - - _PREFIX = "FESDUMMY_" - @staticmethod - def __classcall_private__(cls, base_ring, *args, **kwds): - return super().__classcall__(cls, base_ring, *args, **kwds) - - def __init__(self, base_ring): - self._pool = dict() # dict from variables actually used to indices of gens - # we must start with at least two variables, to make PolynomialSequence work - self._P = PolynomialRing(base_ring, names=[self._PREFIX+str(i) for i in range(2)]) - self._PF = self._P.fraction_field() - Parent.__init__(self, base=base_ring, category=Fields()) - - def construction(self): - return (UndeterminedCoefficientsFunctor(), self.base_ring()) - - def polynomial_ring(self): - """ - .. WARNING:: - - This ring changes when new variables are requested. - """ - return self._P - - def _element_constructor_(self, x): - if x is None: - n = self._P.ngens() - for i in range(n): - if i not in self._pool.values(): - break - else: - names = self._P.variable_names() + (self._PREFIX+str(n),) # tuple(self._PREFIX+str(i) for i in range(n, 2*n)) - self._P = PolynomialRing(self._P.base_ring(), names) - self._PF = self._P.fraction_field() - i = n - v = self._P.gen(i) - self._pool[v] = i - return self.element_class(self, self._PF(v)) - - if x in self._PF: - return self.element_class(self, self._PF(x)) - - raise ValueError(f"{x} is not in {self}") - - def delete_variable(self, v): del self._pool[v] - def _coerce_map_from_(self, S): - """ - Return ``True`` if a coercion from ``S`` exists. - """ - if self._P.base_ring().has_coerce_map_from(S): - return True - return None - - def _coerce_map_from_base_ring(self): - """ - Return a coercion map from the base ring of ``self``. - """ - return self._generic_coerce_map(self._P.base_ring()) - - def _repr_(self): - return f"Undetermined coefficient ring over {self._P.base_ring()}" - - Element = UndeterminedCoefficientsRingElement - class Stream_uninitialized(Stream): r""" @@ -1567,9 +1426,11 @@ def define_implicitly(self, series, initial_values, equations, self._coefficient_ring = coefficient_ring self._base_ring = base_ring - self._P = UndeterminedCoefficientsRing(self._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._P) + self._U = self._coefficient_ring.change_ring(self._PF) + self._pool = VariablePool(self._P) self._uncomputed = True self._eqs = equations self._series = series @@ -1676,8 +1537,8 @@ def __getitem__(self, n): if len(self._cache) > n - self._approximate_order: return self._cache[n - self._approximate_order] - x = sum(self._P(None) * m - for m in self._terms_of_degree(n, self._P)) + x = sum(self._pool.new_variable() * m + for m in self._terms_of_degree(n, self._PF)) self._cache.append(x) return x @@ -1688,7 +1549,7 @@ def _subs_in_caches(self, var, val): INPUT: - - ``var``, a variable + - ``var``, a variable in ``self._P`` - ``val``, the value that should replace the variable """ for j, s in enumerate(self._input_streams): @@ -1704,18 +1565,18 @@ def _subs_in_caches(self, var, val): good = m for i0, i in enumerate(indices): c = s._cache[i] - if self._base_ring == self._coefficient_ring: - if c.parent() == self._P: - c = c.subs({var: val}) - if c.is_constant(): - c = self._base_ring(c.rational_function()) + if self._coefficient_ring == self._base_ring: + if c.parent() == self._PF: + c = self._PF(c.subs({var: val})) + if c.numerator().is_constant() and c.denominator().is_constant(): + c = self._base_ring(c) else: good = m - i0 - 1 else: if c.parent() == self._U: c = c.map_coefficients(lambda e: e.subs({var: val})) try: - c = c.map_coefficients(lambda e: self._base_ring(e.rational_function()), + c = c.map_coefficients(lambda e: self._base_ring(e), self._base_ring) except TypeError: good = m - i0 - 1 @@ -1743,7 +1604,7 @@ def _subs_in_caches(self, var, val): ao += 1 s._approximate_order = ao - self._P.delete_variable(var) + self._pool.del_variable(var) def _compute(self): """ @@ -1771,7 +1632,7 @@ def _compute(self): lcoeff = coeff.coefficients() for c in lcoeff: - c = self._P(c).numerator() + c = self._PF(c).numerator() V = c.variables() if not V: if len(self._eqs) == 1: @@ -1799,7 +1660,7 @@ def _compute(self): raise ValueError(f"there are no linear equations in degrees {degrees}: {non_linear_coeffs}") # solve from sage.rings.polynomial.multi_polynomial_sequence import PolynomialSequence - eqs = PolynomialSequence(self._P.polynomial_ring(), coeffs) + eqs = PolynomialSequence([c.polynomial() for c in coeffs]) m1, v1 = eqs.coefficients_monomials() # there should be at most one entry in v1 of degree 0 for j, c in enumerate(v1): @@ -1820,7 +1681,7 @@ def _compute(self): for i, (var, y) in enumerate(zip(v, x)): if k.column(i).is_zero(): val = self._base_ring(y) - self._subs_in_caches(var, val) + self._subs_in_caches(self._P(var), val) bad = False if bad: if len(self._eqs) == 1: diff --git a/src/sage/rings/lazy_series_ring.py b/src/sage/rings/lazy_series_ring.py index 516d3a63324..fa5b2540b2c 100644 --- a/src/sage/rings/lazy_series_ring.py +++ b/src/sage/rings/lazy_series_ring.py @@ -780,12 +780,12 @@ def define_implicitly(self, series, equations): 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:: @@ -872,7 +872,7 @@ def define_implicitly(self, series, equations): sage: B O(z^16) sage: C - O(z^22) + O(z^23) sage: L. = LazyPowerSeriesRing(QQ) sage: A = L.undefined() From 60bc31c14d7ca2c25b3c8f86e47285db6c2f311c Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Tue, 2 Apr 2024 14:35:58 +0200 Subject: [PATCH 061/117] use bad workaround for InfinitePolynomialRing bug --- src/sage/data_structures/stream.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index ede362c5d4c..26bc3b4b155 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -1660,7 +1660,7 @@ def _compute(self): raise ValueError(f"there are no linear equations in degrees {degrees}: {non_linear_coeffs}") # solve from sage.rings.polynomial.multi_polynomial_sequence import PolynomialSequence - eqs = PolynomialSequence([c.polynomial() for c in coeffs]) + eqs = PolynomialSequence(c.polynomial() for c in coeffs) m1, v1 = eqs.coefficients_monomials() # there should be at most one entry in v1 of degree 0 for j, c in enumerate(v1): @@ -1681,7 +1681,11 @@ def _compute(self): for i, (var, y) in enumerate(zip(v, x)): if k.column(i).is_zero(): val = self._base_ring(y) - self._subs_in_caches(self._P(var), val) + # workaround for `var = self._P(var)` + var = next(self._P.gen(0)[i] + for i, v in enumerate(reversed(self._P._P.gens())) + if v == var) + self._subs_in_caches(var, val) bad = False if bad: if len(self._eqs) == 1: From df873611920fda448ad57d33c74edeed38f3fe1c Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Tue, 2 Apr 2024 16:32:15 +0200 Subject: [PATCH 062/117] conversion is slower than direct computation --- src/sage/data_structures/stream.py | 6 ++++-- src/sage/rings/lazy_series_ring.py | 1 - 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index 26bc3b4b155..191b37ac710 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -1568,8 +1568,10 @@ def _subs_in_caches(self, var, val): if self._coefficient_ring == self._base_ring: if c.parent() == self._PF: c = self._PF(c.subs({var: val})) - if c.numerator().is_constant() and c.denominator().is_constant(): - c = self._base_ring(c) + num = c.numerator() + den = c.denominator() + if num.is_constant() and den.is_constant(): + c = num.constant_coefficient() / den.constant_coefficient() else: good = m - i0 - 1 else: diff --git a/src/sage/rings/lazy_series_ring.py b/src/sage/rings/lazy_series_ring.py index fa5b2540b2c..e74ede206fa 100644 --- a/src/sage/rings/lazy_series_ring.py +++ b/src/sage/rings/lazy_series_ring.py @@ -836,7 +836,6 @@ def define_implicitly(self, series, equations): sage: B -1 + O(z^7) - sage: L. = LazyPowerSeriesRing(QQ) sage: A = L.undefined() sage: B = L.undefined() From b3fb7718e7120fc3f5972c6cb83418bf6d1cd6a4 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Wed, 3 Apr 2024 08:32:36 +0200 Subject: [PATCH 063/117] fix doctests and error handling for undefined streams --- src/sage/combinat/species/species.py | 4 ++-- src/sage/data_structures/stream.py | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/sage/combinat/species/species.py b/src/sage/combinat/species/species.py index 705dbd22494..ff0e470e829 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 191b37ac710..1384923cc09 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -1303,11 +1303,11 @@ def del_variable(self, v): sage: R. = InfinitePolynomialRing(QQ) sage: P = VariablePool(R) sage: v = P.new_variable(); v - a_0 + a_1 sage: P.del_variable(v) sage: v = P.new_variable(); v - a_0 + a_1 """ del self._pool[v] @@ -1521,6 +1521,8 @@ def __getitem__(self, n): 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] From f70ebf56bd88cc77c173c084e83cb5081fa79c90 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Wed, 3 Apr 2024 11:11:43 +0200 Subject: [PATCH 064/117] fix bad markup (:cls: should be :class:) --- src/sage/data_structures/stream.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index 1384923cc09..c3dd585ec3a 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -1253,14 +1253,14 @@ def iterate_coefficients(self): class VariablePool(UniqueRepresentation): """ A class to keep track of used and unused variables in an - :cls:`InfinitePolynomialRing`. + :class:`InfinitePolynomialRing`. INPUT: - - ``ring``, an :cls:`InfinitePolynomialRing`. + - ``ring``, an :class:`InfinitePolynomialRing`. """ def __init__(self, ring): - self._gen = ring.gen(0) # alternatively, make :cls:`InfinitePolynomialGen` inherit from `UniqueRepresentation`. + self._gen = ring.gen(0) # alternatively, make :class:`InfinitePolynomialGen` inherit from `UniqueRepresentation`. self._pool = dict() # dict from variables actually used to indices of gens def new_variable(self): @@ -1278,7 +1278,7 @@ def new_variable(self): TESTS: Check, that we get a new pool for each - :cls:`InfinitePolynomialRing`:: + :class:`InfinitePolynomialRing`:: sage: R0. = InfinitePolynomialRing(QQ) sage: P0 = VariablePool(R0) From 5524bff2afba37702e53fce22004907450730eae Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Wed, 3 Apr 2024 13:24:32 +0200 Subject: [PATCH 065/117] remove workaround and fix InfinitePolynomialRing instead --- src/sage/data_structures/stream.py | 6 +----- .../rings/polynomial/infinite_polynomial_element.py | 11 ++++++++++- src/sage/rings/polynomial/infinite_polynomial_ring.py | 4 ++-- .../rings/polynomial/multi_polynomial_sequence.py | 7 ++++--- 4 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index c3dd585ec3a..f16c43d6f58 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -1664,7 +1664,7 @@ def _compute(self): raise ValueError(f"there are no linear equations in degrees {degrees}: {non_linear_coeffs}") # solve from sage.rings.polynomial.multi_polynomial_sequence import PolynomialSequence - eqs = PolynomialSequence(c.polynomial() for c in coeffs) + eqs = PolynomialSequence(coeffs) m1, v1 = eqs.coefficients_monomials() # there should be at most one entry in v1 of degree 0 for j, c in enumerate(v1): @@ -1685,10 +1685,6 @@ def _compute(self): for i, (var, y) in enumerate(zip(v, x)): if k.column(i).is_zero(): val = self._base_ring(y) - # workaround for `var = self._P(var)` - var = next(self._P.gen(0)[i] - for i, v in enumerate(reversed(self._P._P.gens())) - if v == var) self._subs_in_caches(var, val) bad = False if bad: diff --git a/src/sage/rings/polynomial/infinite_polynomial_element.py b/src/sage/rings/polynomial/infinite_polynomial_element.py index b135404a020..1c6984aef87 100644 --- a/src/sage/rings/polynomial/infinite_polynomial_element.py +++ b/src/sage/rings/polynomial/infinite_polynomial_element.py @@ -578,9 +578,18 @@ 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 () + @cached_method + def monomials(self): + P = self.parent() + return [InfinitePolynomial(P, m) for m in self._p.monomials()] + + def monomial_coefficient(self, mon): + return self._p.monomial_coefficient(mon._p) + @cached_method def max_index(self): r""" diff --git a/src/sage/rings/polynomial/infinite_polynomial_ring.py b/src/sage/rings/polynomial/infinite_polynomial_ring.py index dbb71289f89..a23b038731f 100644 --- a/src/sage/rings/polynomial/infinite_polynomial_ring.py +++ b/src/sage/rings/polynomial/infinite_polynomial_ring.py @@ -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`. diff --git a/src/sage/rings/polynomial/multi_polynomial_sequence.py b/src/sage/rings/polynomial/multi_polynomial_sequence.py index e9476061b57..97fd0574b4a 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 @@ -380,7 +381,7 @@ def __init__(self, parts, ring, immutable=False, cr=False, cr_str=None): INPUT: - - ``part`` - a list of lists with polynomials + - ``parts`` - a list of lists with polynomials - ``ring`` - a multivariate polynomial ring @@ -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 From 205f3eacfbb5fbb5e5b9efd61fd87308ea596b53 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Wed, 3 Apr 2024 13:48:40 +0200 Subject: [PATCH 066/117] add documentation and tests, remove cached_method --- .../polynomial/infinite_polynomial_element.py | 54 ++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/src/sage/rings/polynomial/infinite_polynomial_element.py b/src/sage/rings/polynomial/infinite_polynomial_element.py index 1c6984aef87..76bb926c176 100644 --- a/src/sage/rings/polynomial/infinite_polynomial_element.py +++ b/src/sage/rings/polynomial/infinite_polynomial_element.py @@ -582,12 +582,64 @@ def variables(self): return tuple(InfinitePolynomial(P, v) for v in self._p.variables()) return () - @cached_method 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 coefficient in the base ring of the monomial mon in + ``self``, where mon must have the same parent as self. + + This function contrasts with the function ``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 + + OUTPUT: + + coefficient in base ring + + .. SEEALSO:: + + For coefficients in a base ring of fewer variables, + look at ``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 From b0b6e62b4dac42314b6aa445fcbf04bcebf19aa7 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Thu, 4 Apr 2024 16:04:46 +0200 Subject: [PATCH 067/117] optimize and clarify substitution --- src/sage/data_structures/stream.py | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index f16c43d6f58..bf9bf3f0278 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -1249,6 +1249,7 @@ def iterate_coefficients(self): 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 class VariablePool(UniqueRepresentation): """ @@ -1554,6 +1555,20 @@ def _subs_in_caches(self, var, val): - ``var``, a variable in ``self._P`` - ``val``, the value that should replace the variable """ + 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 + for j, s in enumerate(self._input_streams): m = len(s._cache) - self._good_cache[j] if s._is_sparse: @@ -1569,19 +1584,14 @@ def _subs_in_caches(self, var, val): c = s._cache[i] if self._coefficient_ring == self._base_ring: if c.parent() == self._PF: - c = self._PF(c.subs({var: val})) - num = c.numerator() - den = c.denominator() - if num.is_constant() and den.is_constant(): - c = num.constant_coefficient() / den.constant_coefficient() - else: + c = retract(subs(c, var, val)) + if not c.parent() is self._base_ring: good = m - i0 - 1 else: if c.parent() == self._U: - c = c.map_coefficients(lambda e: e.subs({var: val})) + c = c.map_coefficients(lambda e: subs(e, var, val)) try: - c = c.map_coefficients(lambda e: self._base_ring(e), - self._base_ring) + c = c.map_coefficients(lambda e: retract(e), self._base_ring) except TypeError: good = m - i0 - 1 s._cache[i] = c From 4bef1dc0db9e38b0e3ffc68c7dc6efcb0477cc2d Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Thu, 4 Apr 2024 17:33:00 +0200 Subject: [PATCH 068/117] simplify VariablePool, add documentation and doctests --- src/sage/data_structures/stream.py | 185 +++++++++++++++++++++++------ src/sage/rings/lazy_series_ring.py | 3 +- 2 files changed, 152 insertions(+), 36 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index bf9bf3f0278..720178a6abf 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -102,11 +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.categories.rings import Rings +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 -from sage.categories.functor import Functor -from sage.categories.pushout import ConstructionFunctor -from collections import defaultdict lazy_import('sage.combinat.sf.sfa', ['_variables_recursive', '_raise_variables']) @@ -1029,6 +1028,23 @@ 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: @@ -1247,10 +1263,6 @@ def iterate_coefficients(self): denom *= n -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 - class VariablePool(UniqueRepresentation): """ A class to keep track of used and unused variables in an @@ -1261,8 +1273,18 @@ class VariablePool(UniqueRepresentation): - ``ring``, an :class:`InfinitePolynomialRing`. """ def __init__(self, ring): - self._gen = ring.gen(0) # alternatively, make :class:`InfinitePolynomialGen` inherit from `UniqueRepresentation`. - self._pool = dict() # dict from variables actually used to indices of gens + """ + Inititialize 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 = [] # list of variables actually used def new_variable(self): """ @@ -1287,12 +1309,11 @@ def new_variable(self): b_0 """ - for i in range(len(self._pool)+1): - if i not in self._pool.values(): - break - v = self._gen[i] - self._pool[v] = i - return v + for i in range(len(self._pool) + 1): + v = self._gen[i] + if v not in self._pool: + self._pool.append(v) + return v def del_variable(self, v): """ @@ -1310,7 +1331,7 @@ def del_variable(self, v): sage: v = P.new_variable(); v a_1 """ - del self._pool[v] + self._pool.remove(v) class Stream_uninitialized(Stream): @@ -1391,9 +1412,17 @@ def define(self, target): - ``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 + 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 = list() self._iter = self.iterate_coefficients() @@ -1412,6 +1441,17 @@ def define_implicitly(self, series, initial_values, equations, - ``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 + 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 @@ -1440,14 +1480,31 @@ def define_implicitly(self, series, initial_values, equations, @lazy_attribute def _input_streams(self): r""" - Return the list of streams which have a cache and ``self`` - depends on. + Return the list of streams which have a cache and an implicitly + defined ``self`` depends on. ``self`` is the first stream in this list. - All caches must have been created before this is called. - Does this mean that this should only be called after the - first invocation of `_compute`? + .. 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] @@ -1462,14 +1519,32 @@ def _input_streams(self): @lazy_attribute def _good_cache(self): r""" - The number of coefficients in each input stream - in the same - order - that are free of undetermined coefficients. + 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. - It might be better to share this across all uninitialized - series in one system. + .. 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: @@ -1495,7 +1570,12 @@ def __getitem__(self, n): EXAMPLES:: - sage: + 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() @@ -1554,6 +1634,18 @@ def _subs_in_caches(self, var, val): - ``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] # implicit doctest + 2 """ def subs(c, var, val): P = self._P.polynomial_ring() @@ -1621,8 +1713,30 @@ def retract(c): self._pool.del_variable(var) def _compute(self): - """ - Solve the next equations, until the next variable is determined. + 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] # implicit doctest + 2 """ # determine the next linear equations coeffs = [] @@ -1641,8 +1755,8 @@ def _compute(self): if self._base_ring == self._coefficient_ring: lcoeff = [coeff] else: - # TODO: it is a coincidence that this currently - # exists in all examples + # TODO: it is a coincidence that `coefficients` + # currently exists in all examples lcoeff = coeff.coefficients() for c in lcoeff: @@ -2107,7 +2221,7 @@ def order(self): sage: s.order() +Infinity """ - return self._approximate_order # == infinity + return self._approximate_order # == infinity def __eq__(self, other): """ @@ -2177,6 +2291,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:: @@ -2235,6 +2350,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:: @@ -2301,6 +2417,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:: @@ -2774,7 +2891,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()) diff --git a/src/sage/rings/lazy_series_ring.py b/src/sage/rings/lazy_series_ring.py index e74ede206fa..d2470cc103f 100644 --- a/src/sage/rings/lazy_series_ring.py +++ b/src/sage/rings/lazy_series_ring.py @@ -942,7 +942,7 @@ def define_implicitly(self, series, equations): Permutations with two kinds of labels such that each cycle contains at least one element of each kind (defined - implicitely to have a test):: + implicitly to have a test):: sage: p = SymmetricFunctions(QQ).p() sage: S = LazySymmetricFunctions(p) @@ -2954,7 +2954,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 From 4c1682b0f1d32542e5dffc5535739f8794b8c86b Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Thu, 4 Apr 2024 20:25:42 +0200 Subject: [PATCH 069/117] improve documentation of doctests --- src/sage/rings/lazy_series_ring.py | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/sage/rings/lazy_series_ring.py b/src/sage/rings/lazy_series_ring.py index d2470cc103f..f053e5a1875 100644 --- a/src/sage/rings/lazy_series_ring.py +++ b/src/sage/rings/lazy_series_ring.py @@ -777,6 +777,9 @@ def define_implicitly(self, series, equations): 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 @@ -825,7 +828,7 @@ def define_implicitly(self, series, equations): sage: f[1] 0 - Some systems of two coupled functional equations:: + Some systems of coupled functional equations:: sage: L. = LazyPowerSeriesRing(QQ) sage: A = L.undefined() @@ -877,17 +880,22 @@ def define_implicitly(self, series, equations): 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] # known bug, not tested + 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: 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) + 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] # known bug, not tested A bivariate example:: @@ -954,6 +962,7 @@ def define_implicitly(self, series, equations): 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]] + """ s = [a[0]._coeff_stream if isinstance(a, (tuple, list)) else a._coeff_stream From 12fc4bca0610fc89dd36c01051ee80f666f70668 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Sat, 6 Apr 2024 23:49:51 +0200 Subject: [PATCH 070/117] return numerator and denominator as elements of self, restrict element_constructor, fix coefficients --- .../polynomial/infinite_polynomial_element.py | 68 +++++++++++-------- .../polynomial/infinite_polynomial_ring.py | 2 +- 2 files changed, 39 insertions(+), 31 deletions(-) diff --git a/src/sage/rings/polynomial/infinite_polynomial_element.py b/src/sage/rings/polynomial/infinite_polynomial_element.py index b135404a020..03f6f83673c 100644 --- a/src/sage/rings/polynomial/infinite_polynomial_element.py +++ b/src/sage/rings/polynomial/infinite_polynomial_element.py @@ -560,6 +560,14 @@ def is_nilpotent(self): """ return self._p.is_nilpotent() + def numerator(self): + P = self.parent() + return InfinitePolynomial(P, self._p.numerator()) + + def denominator(self): + P = self.parent() + return InfinitePolynomial(P, self._p.denominator()) + @cached_method def variables(self): """ @@ -1018,42 +1026,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 dbb71289f89..8e4891613d9 100644 --- a/src/sage/rings/polynomial/infinite_polynomial_ring.py +++ b/src/sage/rings/polynomial/infinite_polynomial_ring.py @@ -922,7 +922,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) From a0deca48a98f424e7372738694daa7768ec24419 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Sun, 7 Apr 2024 11:37:18 +0200 Subject: [PATCH 071/117] add docstring to numerator, remove wrong denominator method (the inherited method is correct) --- .../polynomial/infinite_polynomial_element.py | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/src/sage/rings/polynomial/infinite_polynomial_element.py b/src/sage/rings/polynomial/infinite_polynomial_element.py index 03f6f83673c..8aa310c20de 100644 --- a/src/sage/rings/polynomial/infinite_polynomial_element.py +++ b/src/sage/rings/polynomial/infinite_polynomial_element.py @@ -561,12 +561,32 @@ def is_nilpotent(self): return self._p.is_nilpotent() def numerator(self): - P = self.parent() - return InfinitePolynomial(P, self._p.numerator()) + r""" + Return a numerator of ``self``, computed as ``self * self.denominator()``. + + Note that some subclasses may implement its own numerator + function. + + .. warning:: + + This is not the numerator of the rational function + defined by ``self``, which would always be self since ``self`` is a + polynomial. + + EXAMPLES: - def denominator(self): + 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 + """ P = self.parent() - return InfinitePolynomial(P, self._p.denominator()) + return InfinitePolynomial(P, self._p.numerator()) @cached_method def variables(self): From 690e048d6c846587103591acc56fa478495b747b Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Wed, 3 Apr 2024 13:24:32 +0200 Subject: [PATCH 072/117] remove workaround and fix InfinitePolynomialRing instead --- .../rings/polynomial/infinite_polynomial_element.py | 11 ++++++++++- src/sage/rings/polynomial/infinite_polynomial_ring.py | 4 ++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/sage/rings/polynomial/infinite_polynomial_element.py b/src/sage/rings/polynomial/infinite_polynomial_element.py index 8aa310c20de..b48d22a9f1d 100644 --- a/src/sage/rings/polynomial/infinite_polynomial_element.py +++ b/src/sage/rings/polynomial/infinite_polynomial_element.py @@ -606,9 +606,18 @@ 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 () + @cached_method + def monomials(self): + P = self.parent() + return [InfinitePolynomial(P, m) for m in self._p.monomials()] + + def monomial_coefficient(self, mon): + return self._p.monomial_coefficient(mon._p) + @cached_method def max_index(self): r""" diff --git a/src/sage/rings/polynomial/infinite_polynomial_ring.py b/src/sage/rings/polynomial/infinite_polynomial_ring.py index 8e4891613d9..169978acf95 100644 --- a/src/sage/rings/polynomial/infinite_polynomial_ring.py +++ b/src/sage/rings/polynomial/infinite_polynomial_ring.py @@ -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`. From 6d4659c22512310a2ad69f610c483bc7d33995e2 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Wed, 3 Apr 2024 13:48:40 +0200 Subject: [PATCH 073/117] add documentation and tests, remove cached_method --- .../polynomial/infinite_polynomial_element.py | 54 ++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/src/sage/rings/polynomial/infinite_polynomial_element.py b/src/sage/rings/polynomial/infinite_polynomial_element.py index b48d22a9f1d..fba15b886c2 100644 --- a/src/sage/rings/polynomial/infinite_polynomial_element.py +++ b/src/sage/rings/polynomial/infinite_polynomial_element.py @@ -610,12 +610,64 @@ def variables(self): return tuple(InfinitePolynomial(P, v) for v in self._p.variables()) return () - @cached_method 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 coefficient in the base ring of the monomial mon in + ``self``, where mon must have the same parent as self. + + This function contrasts with the function ``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 + + OUTPUT: + + coefficient in base ring + + .. SEEALSO:: + + For coefficients in a base ring of fewer variables, + look at ``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 From 0fe5270a2c1e3428e61f4c7ba572839de91d6b11 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Sun, 7 Apr 2024 11:54:35 +0200 Subject: [PATCH 074/117] add doctest, fix linter --- .../rings/polynomial/infinite_polynomial_ring.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/sage/rings/polynomial/infinite_polynomial_ring.py b/src/sage/rings/polynomial/infinite_polynomial_ring.py index 169978acf95..0d3af0e9bf4 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. @@ -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 From 064d6ba536d9b998f2d49673abef8982b6763868 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Sun, 7 Apr 2024 11:57:43 +0200 Subject: [PATCH 075/117] add doctest for coercion issue --- .../rings/polynomial/infinite_polynomial_element.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/sage/rings/polynomial/infinite_polynomial_element.py b/src/sage/rings/polynomial/infinite_polynomial_element.py index fba15b886c2..be383e381f5 100644 --- a/src/sage/rings/polynomial/infinite_polynomial_element.py +++ b/src/sage/rings/polynomial/infinite_polynomial_element.py @@ -584,6 +584,16 @@ def numerator(self): 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()) From fdb99dcfa8fd516176ad4cd230d291274bf36f92 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Sun, 7 Apr 2024 20:52:50 +0200 Subject: [PATCH 076/117] Use proper reference role MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Matthias Köppe --- src/sage/combinat/sf/dual.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/combinat/sf/dual.py b/src/sage/combinat/sf/dual.py index aae10602f2d..207e411a2c6 100644 --- a/src/sage/combinat/sf/dual.py +++ b/src/sage/combinat/sf/dual.py @@ -292,7 +292,7 @@ def basis_name(self): Return the name of the basis of ``self``. This is used for output and, for the classical bases of - symmetric functions, to connect this basis with Symmetrica. + symmetric functions, to connect this basis with :ref:`Symmetrica `. EXAMPLES:: From acfc7f2f372f8643663ab66c39644d03462ba25f Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Sun, 7 Apr 2024 20:54:58 +0200 Subject: [PATCH 077/117] Remove wrong indentation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Matthias Köppe --- src/sage/data_structures/stream.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index 720178a6abf..6dc5306015a 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -1434,12 +1434,12 @@ def define_implicitly(self, series, initial_values, equations, 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 + - ``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 EXAMPLES:: From b36cfea96a77082ddb92fae3f484091ac4c85438 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Sun, 7 Apr 2024 20:57:01 +0200 Subject: [PATCH 078/117] Fix missing backticks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Matthias Köppe --- src/sage/rings/polynomial/infinite_polynomial_element.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/sage/rings/polynomial/infinite_polynomial_element.py b/src/sage/rings/polynomial/infinite_polynomial_element.py index be383e381f5..0ca5cdfed55 100644 --- a/src/sage/rings/polynomial/infinite_polynomial_element.py +++ b/src/sage/rings/polynomial/infinite_polynomial_element.py @@ -622,8 +622,9 @@ def variables(self): def monomials(self): """ - Return the list of monomials in self. The returned list is - decreasingly ordered by the term ordering of + Return the list of monomials in ``self``. + + The returned list is decreasingly ordered by the term ordering of ``self.parent()``. EXAMPLES:: From 4e569b5f7e7d8a3b0fabbd6f51cc11d04a9e13ca Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Sun, 7 Apr 2024 20:58:08 +0200 Subject: [PATCH 079/117] Fix missing backticks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Matthias Köppe --- src/sage/rings/polynomial/infinite_polynomial_element.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/polynomial/infinite_polynomial_element.py b/src/sage/rings/polynomial/infinite_polynomial_element.py index 0ca5cdfed55..306ad66de36 100644 --- a/src/sage/rings/polynomial/infinite_polynomial_element.py +++ b/src/sage/rings/polynomial/infinite_polynomial_element.py @@ -570,7 +570,7 @@ def numerator(self): .. warning:: This is not the numerator of the rational function - defined by ``self``, which would always be self since ``self`` is a + defined by ``self``, which would always be ``self`` since it is a polynomial. EXAMPLES: From 891bfe4ecb2d4ce6ea9d3b2db41f4da945a19402 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Sun, 7 Apr 2024 21:14:26 +0200 Subject: [PATCH 080/117] remove misleading sentence and whitespace --- src/sage/rings/polynomial/infinite_polynomial_element.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/sage/rings/polynomial/infinite_polynomial_element.py b/src/sage/rings/polynomial/infinite_polynomial_element.py index 306ad66de36..ac507fc2aa2 100644 --- a/src/sage/rings/polynomial/infinite_polynomial_element.py +++ b/src/sage/rings/polynomial/infinite_polynomial_element.py @@ -564,9 +564,6 @@ def numerator(self): r""" Return a numerator of ``self``, computed as ``self * self.denominator()``. - Note that some subclasses may implement its own numerator - function. - .. warning:: This is not the numerator of the rational function @@ -622,8 +619,8 @@ def variables(self): def monomials(self): """ - Return the list of monomials in ``self``. - + Return the list of monomials in ``self``. + The returned list is decreasingly ordered by the term ordering of ``self.parent()``. From 54b65c03fed045fc0f645401f22b98257d482055 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Sun, 7 Apr 2024 21:47:12 +0200 Subject: [PATCH 081/117] break expected output of doctest MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Matthias Köppe --- src/sage/combinat/sf/sfa.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sage/combinat/sf/sfa.py b/src/sage/combinat/sf/sfa.py index d569b6ce1dd..30ced10112c 100644 --- a/src/sage/combinat/sf/sfa.py +++ b/src/sage/combinat/sf/sfa.py @@ -641,7 +641,8 @@ def corresponding_basis_over(self, R): sage: Sym = SymmetricFunctions(P) sage: mj = Sym.macdonald().J() sage: mj.corresponding_basis_over(Integers(13)['q','t']) - Symmetric Functions over Multivariate Polynomial Ring in q, t over Ring of integers modulo 13 in the Macdonald J basis + Symmetric Functions over Multivariate Polynomial Ring in q, t over + Ring of integers modulo 13 in the Macdonald J basis TESTS: From ba1d8fa7c87abc292c90f4cb96ecb818776773c3 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Sun, 7 Apr 2024 21:54:28 +0200 Subject: [PATCH 082/117] provide missing doctests --- src/sage/rings/lazy_series_ring.py | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src/sage/rings/lazy_series_ring.py b/src/sage/rings/lazy_series_ring.py index f053e5a1875..346f828089d 100644 --- a/src/sage/rings/lazy_series_ring.py +++ b/src/sage/rings/lazy_series_ring.py @@ -962,7 +962,6 @@ def define_implicitly(self, series, equations): 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]] - """ s = [a[0]._coeff_stream if isinstance(a, (tuple, list)) else a._coeff_stream @@ -2088,6 +2087,13 @@ def _terms_of_degree(self, n, 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()] @@ -2401,6 +2407,18 @@ def __init__(self, base_ring, names, sparse=True, category=None): 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), @@ -3813,6 +3831,14 @@ def _monomial(self, c, n): 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()] From c62292d47183f8e7ce23d2c84440a53beb23e2c0 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Sun, 7 Apr 2024 21:55:31 +0200 Subject: [PATCH 083/117] add missing colon MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Matthias Köppe --- src/sage/rings/polynomial/infinite_polynomial_element.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/polynomial/infinite_polynomial_element.py b/src/sage/rings/polynomial/infinite_polynomial_element.py index ac507fc2aa2..6ae25934f21 100644 --- a/src/sage/rings/polynomial/infinite_polynomial_element.py +++ b/src/sage/rings/polynomial/infinite_polynomial_element.py @@ -570,7 +570,7 @@ def numerator(self): defined by ``self``, which would always be ``self`` since it is a polynomial. - EXAMPLES: + EXAMPLES:: sage: X. = InfinitePolynomialRing(QQ) sage: p = 2/3*x[1] + 4/9*x[2] - 2*x[1]*x[3] From e419a73f7c4c4b8ebf6c53e0964ce11554ca37de Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Sun, 7 Apr 2024 21:56:23 +0200 Subject: [PATCH 084/117] break expected output of doctest MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Matthias Köppe --- src/sage/combinat/free_module.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/sage/combinat/free_module.py b/src/sage/combinat/free_module.py index d70f3539cd4..eb5e8797084 100644 --- a/src/sage/combinat/free_module.py +++ b/src/sage/combinat/free_module.py @@ -500,9 +500,11 @@ def change_ring(self, R): True sage: T = F.tensor(F); T - Free module generated by {'a', 'b', 'c'} over Integer Ring # Free module generated by {'a', 'b', 'c'} over Integer Ring + Free module generated by {'a', 'b', 'c'} over Integer Ring + # Free module generated by {'a', 'b', 'c'} over Integer Ring sage: T.change_ring(QQ) - Free module generated by {'a', 'b', 'c'} over Rational Field # Free module generated by {'a', 'b', 'c'} over Rational Field + Free module generated by {'a', 'b', 'c'} over Rational Field + # Free module generated by {'a', 'b', 'c'} over Rational Field """ if R is self.base_ring(): return self From 716fca461196cb0593102160f0edfff9cdd682c8 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Sun, 7 Apr 2024 21:56:56 +0200 Subject: [PATCH 085/117] add missing # needs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Matthias Köppe --- src/sage/rings/lazy_series_ring.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sage/rings/lazy_series_ring.py b/src/sage/rings/lazy_series_ring.py index 346f828089d..f7fd96b3f59 100644 --- a/src/sage/rings/lazy_series_ring.py +++ b/src/sage/rings/lazy_series_ring.py @@ -724,6 +724,7 @@ def define_implicitly(self, series, equations): 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))]) From 6c536b04afab7b410c87e26bb8f3ef0855006d02 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Sun, 7 Apr 2024 21:57:36 +0200 Subject: [PATCH 086/117] Remove wrong indentation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Matthias Köppe --- src/sage/data_structures/stream.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index 6dc5306015a..7f5dc1c3b77 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -1410,7 +1410,7 @@ def define(self, target): INPUT: - - ``target`` -- a stream + - ``target`` -- a stream EXAMPLES:: From bd3eaa12bcea2663d98f3b02b1324c208f30b198 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Sun, 7 Apr 2024 21:58:14 +0200 Subject: [PATCH 087/117] break expected output of doctest MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Matthias Köppe --- src/sage/rings/lazy_series_ring.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sage/rings/lazy_series_ring.py b/src/sage/rings/lazy_series_ring.py index f7fd96b3f59..1554069a845 100644 --- a/src/sage/rings/lazy_series_ring.py +++ b/src/sage/rings/lazy_series_ring.py @@ -3190,7 +3190,8 @@ def _terms_of_degree(self, n, R): 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 + 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 9ec2d3037f665032f1696dda3248b8b5442c4e7c Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Sun, 7 Apr 2024 21:58:53 +0200 Subject: [PATCH 088/117] Remove wrong indentation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Matthias Köppe --- src/sage/data_structures/stream.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index 7f5dc1c3b77..65a6dd23f90 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -1632,8 +1632,8 @@ def _subs_in_caches(self, var, val): INPUT: - - ``var``, a variable in ``self._P`` - - ``val``, the value that should replace the variable + - ``var``, a variable in ``self._P`` + - ``val``, the value that should replace the variable EXAMPLES:: From a782b712592dd1ca7f52fafaa97d2cba60cff22f Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Sun, 7 Apr 2024 22:06:54 +0200 Subject: [PATCH 089/117] use proper sphinx roles MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Matthias Köppe --- .../polynomial/infinite_polynomial_element.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/sage/rings/polynomial/infinite_polynomial_element.py b/src/sage/rings/polynomial/infinite_polynomial_element.py index 6ae25934f21..729f0f88a28 100644 --- a/src/sage/rings/polynomial/infinite_polynomial_element.py +++ b/src/sage/rings/polynomial/infinite_polynomial_element.py @@ -641,26 +641,23 @@ def monomials(self): def monomial_coefficient(self, mon): """ - Return the coefficient in the base ring of the monomial mon in - ``self``, where mon must have the same parent as self. - - This function contrasts with the function ``coefficient`` + 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 - - OUTPUT: + - ``mon`` -- a monomial of the parent of ``self`` - coefficient in base ring + OUTPUT: coefficient in base ring .. SEEALSO:: For coefficients in a base ring of fewer variables, - look at ``coefficient``. + look at :meth:`coefficient`. EXAMPLES:: From 2286ac999da4f86d3ce3675c8fc9010d4dc9911b Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Sun, 7 Apr 2024 22:16:36 +0200 Subject: [PATCH 090/117] remove whitespace --- src/sage/combinat/free_module.py | 4 ++-- src/sage/combinat/sf/sfa.py | 2 +- src/sage/rings/lazy_series_ring.py | 2 +- src/sage/rings/polynomial/infinite_polynomial_element.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/sage/combinat/free_module.py b/src/sage/combinat/free_module.py index eb5e8797084..2f153396fc0 100644 --- a/src/sage/combinat/free_module.py +++ b/src/sage/combinat/free_module.py @@ -500,10 +500,10 @@ def change_ring(self, R): True sage: T = F.tensor(F); T - Free module generated by {'a', 'b', 'c'} over Integer Ring + Free module generated by {'a', 'b', 'c'} over Integer Ring # Free module generated by {'a', 'b', 'c'} over Integer Ring sage: T.change_ring(QQ) - Free module generated by {'a', 'b', 'c'} over Rational Field + Free module generated by {'a', 'b', 'c'} over Rational Field # Free module generated by {'a', 'b', 'c'} over Rational Field """ if R is self.base_ring(): diff --git a/src/sage/combinat/sf/sfa.py b/src/sage/combinat/sf/sfa.py index 30ced10112c..05abd332b08 100644 --- a/src/sage/combinat/sf/sfa.py +++ b/src/sage/combinat/sf/sfa.py @@ -641,7 +641,7 @@ def corresponding_basis_over(self, R): sage: Sym = SymmetricFunctions(P) sage: mj = Sym.macdonald().J() sage: mj.corresponding_basis_over(Integers(13)['q','t']) - Symmetric Functions over Multivariate Polynomial Ring in q, t over + Symmetric Functions over Multivariate Polynomial Ring in q, t over Ring of integers modulo 13 in the Macdonald J basis TESTS: diff --git a/src/sage/rings/lazy_series_ring.py b/src/sage/rings/lazy_series_ring.py index 1554069a845..9bf9b1df7cd 100644 --- a/src/sage/rings/lazy_series_ring.py +++ b/src/sage/rings/lazy_series_ring.py @@ -3190,7 +3190,7 @@ def _terms_of_degree(self, n, R): 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 # Symmetric Functions over Univariate Polynomial Ring in x over Rational Field in the Schur basis """ from sage.combinat.integer_vector import IntegerVectors diff --git a/src/sage/rings/polynomial/infinite_polynomial_element.py b/src/sage/rings/polynomial/infinite_polynomial_element.py index 729f0f88a28..bfa6fd9dbaf 100644 --- a/src/sage/rings/polynomial/infinite_polynomial_element.py +++ b/src/sage/rings/polynomial/infinite_polynomial_element.py @@ -642,7 +642,7 @@ def monomials(self): 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 From 7b49ae12d4f54a1aee47df3ee9dad73b657a9d14 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Sun, 7 Apr 2024 20:57:01 +0200 Subject: [PATCH 091/117] Fix missing backticks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Matthias Köppe --- src/sage/rings/polynomial/infinite_polynomial_element.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/sage/rings/polynomial/infinite_polynomial_element.py b/src/sage/rings/polynomial/infinite_polynomial_element.py index be383e381f5..0ca5cdfed55 100644 --- a/src/sage/rings/polynomial/infinite_polynomial_element.py +++ b/src/sage/rings/polynomial/infinite_polynomial_element.py @@ -622,8 +622,9 @@ def variables(self): def monomials(self): """ - Return the list of monomials in self. The returned list is - decreasingly ordered by the term ordering of + Return the list of monomials in ``self``. + + The returned list is decreasingly ordered by the term ordering of ``self.parent()``. EXAMPLES:: From a36aac5e8b7ff3a6cfd0617a5e93346261d87671 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Sun, 7 Apr 2024 20:58:08 +0200 Subject: [PATCH 092/117] Fix missing backticks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Matthias Köppe --- src/sage/rings/polynomial/infinite_polynomial_element.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/polynomial/infinite_polynomial_element.py b/src/sage/rings/polynomial/infinite_polynomial_element.py index 0ca5cdfed55..306ad66de36 100644 --- a/src/sage/rings/polynomial/infinite_polynomial_element.py +++ b/src/sage/rings/polynomial/infinite_polynomial_element.py @@ -570,7 +570,7 @@ def numerator(self): .. warning:: This is not the numerator of the rational function - defined by ``self``, which would always be self since ``self`` is a + defined by ``self``, which would always be ``self`` since it is a polynomial. EXAMPLES: From b128a60c31ccb62a6db77f374d1f49544967960b Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Sun, 7 Apr 2024 21:14:26 +0200 Subject: [PATCH 093/117] remove misleading sentence and whitespace --- src/sage/rings/polynomial/infinite_polynomial_element.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/sage/rings/polynomial/infinite_polynomial_element.py b/src/sage/rings/polynomial/infinite_polynomial_element.py index 306ad66de36..ac507fc2aa2 100644 --- a/src/sage/rings/polynomial/infinite_polynomial_element.py +++ b/src/sage/rings/polynomial/infinite_polynomial_element.py @@ -564,9 +564,6 @@ def numerator(self): r""" Return a numerator of ``self``, computed as ``self * self.denominator()``. - Note that some subclasses may implement its own numerator - function. - .. warning:: This is not the numerator of the rational function @@ -622,8 +619,8 @@ def variables(self): def monomials(self): """ - Return the list of monomials in ``self``. - + Return the list of monomials in ``self``. + The returned list is decreasingly ordered by the term ordering of ``self.parent()``. From 8015042510adff40bad70a635cb9f6351cf21201 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Sun, 7 Apr 2024 21:55:31 +0200 Subject: [PATCH 094/117] add missing colon MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Matthias Köppe --- src/sage/rings/polynomial/infinite_polynomial_element.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/polynomial/infinite_polynomial_element.py b/src/sage/rings/polynomial/infinite_polynomial_element.py index ac507fc2aa2..6ae25934f21 100644 --- a/src/sage/rings/polynomial/infinite_polynomial_element.py +++ b/src/sage/rings/polynomial/infinite_polynomial_element.py @@ -570,7 +570,7 @@ def numerator(self): defined by ``self``, which would always be ``self`` since it is a polynomial. - EXAMPLES: + EXAMPLES:: sage: X. = InfinitePolynomialRing(QQ) sage: p = 2/3*x[1] + 4/9*x[2] - 2*x[1]*x[3] From 385ee8c3a673b80b5ab2d62d1fcc37f8907989a4 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Sun, 7 Apr 2024 22:06:54 +0200 Subject: [PATCH 095/117] use proper sphinx roles MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Matthias Köppe --- .../polynomial/infinite_polynomial_element.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/sage/rings/polynomial/infinite_polynomial_element.py b/src/sage/rings/polynomial/infinite_polynomial_element.py index 6ae25934f21..729f0f88a28 100644 --- a/src/sage/rings/polynomial/infinite_polynomial_element.py +++ b/src/sage/rings/polynomial/infinite_polynomial_element.py @@ -641,26 +641,23 @@ def monomials(self): def monomial_coefficient(self, mon): """ - Return the coefficient in the base ring of the monomial mon in - ``self``, where mon must have the same parent as self. - - This function contrasts with the function ``coefficient`` + 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 - - OUTPUT: + - ``mon`` -- a monomial of the parent of ``self`` - coefficient in base ring + OUTPUT: coefficient in base ring .. SEEALSO:: For coefficients in a base ring of fewer variables, - look at ``coefficient``. + look at :meth:`coefficient`. EXAMPLES:: From 6706a3925a46925e5627bcdb6458c74f3c2db098 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Mon, 8 Apr 2024 09:28:52 +0200 Subject: [PATCH 096/117] remove whitespace --- src/sage/rings/polynomial/infinite_polynomial_element.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/polynomial/infinite_polynomial_element.py b/src/sage/rings/polynomial/infinite_polynomial_element.py index 729f0f88a28..bfa6fd9dbaf 100644 --- a/src/sage/rings/polynomial/infinite_polynomial_element.py +++ b/src/sage/rings/polynomial/infinite_polynomial_element.py @@ -642,7 +642,7 @@ def monomials(self): 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 From 575731d8ae9d53547e0f51d4ee7987a4feaa8ee8 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Mon, 8 Apr 2024 09:30:37 +0200 Subject: [PATCH 097/117] improve docstring formatting Co-authored-by: Travis Scrimshaw --- src/sage/rings/polynomial/infinite_polynomial_element.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sage/rings/polynomial/infinite_polynomial_element.py b/src/sage/rings/polynomial/infinite_polynomial_element.py index bfa6fd9dbaf..0470186552c 100644 --- a/src/sage/rings/polynomial/infinite_polynomial_element.py +++ b/src/sage/rings/polynomial/infinite_polynomial_element.py @@ -564,7 +564,8 @@ def numerator(self): r""" Return a numerator of ``self``, computed as ``self * self.denominator()``. - .. warning:: + .. WARNING + :: This is not the numerator of the rational function defined by ``self``, which would always be ``self`` since it is a From 97dbbc60822bf4e1cd4919b14e8d80248e2725d3 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Mon, 8 Apr 2024 09:34:32 +0200 Subject: [PATCH 098/117] remove unnecessary parens Co-authored-by: Travis Scrimshaw --- src/sage/rings/polynomial/infinite_polynomial_element.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/polynomial/infinite_polynomial_element.py b/src/sage/rings/polynomial/infinite_polynomial_element.py index 0470186552c..1f6249fec57 100644 --- a/src/sage/rings/polynomial/infinite_polynomial_element.py +++ b/src/sage/rings/polynomial/infinite_polynomial_element.py @@ -1117,7 +1117,7 @@ def coefficient(self, monomial): if self._p == 0: return P.zero() if isinstance(monomial, self.__class__): - if not (P.has_coerce_map_from(monomial.parent())): + 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()] From 9b76cf6ee0e8b1561d04b2271c8a1d809516765c Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Mon, 8 Apr 2024 09:36:41 +0200 Subject: [PATCH 099/117] improve docstring --- src/sage/rings/polynomial/infinite_polynomial_element.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/sage/rings/polynomial/infinite_polynomial_element.py b/src/sage/rings/polynomial/infinite_polynomial_element.py index 1f6249fec57..df9e201c12f 100644 --- a/src/sage/rings/polynomial/infinite_polynomial_element.py +++ b/src/sage/rings/polynomial/infinite_polynomial_element.py @@ -642,7 +642,8 @@ def monomials(self): def monomial_coefficient(self, mon): """ - Return the base ring element that is the coefficient of ``mon`` in ``self``. + 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 @@ -651,7 +652,7 @@ def monomial_coefficient(self, mon): INPUT: - - ``mon`` -- a monomial of the parent of ``self`` + - ``mon`` -- a monomial in the parent of ``self`` OUTPUT: coefficient in base ring @@ -673,6 +674,7 @@ def monomial_coefficient(self, mon): 2*x_0 sage: c.parent() Infinite polynomial ring in x over Rational Field + """ return self._p.monomial_coefficient(mon._p) From 2f607aadcad477051a7fdef0b5ff96eec842198f Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Mon, 8 Apr 2024 10:48:51 +0200 Subject: [PATCH 100/117] fix sphinx role --- src/sage/rings/polynomial/infinite_polynomial_element.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/sage/rings/polynomial/infinite_polynomial_element.py b/src/sage/rings/polynomial/infinite_polynomial_element.py index df9e201c12f..5645c307731 100644 --- a/src/sage/rings/polynomial/infinite_polynomial_element.py +++ b/src/sage/rings/polynomial/infinite_polynomial_element.py @@ -564,8 +564,7 @@ def numerator(self): r""" Return a numerator of ``self``, computed as ``self * self.denominator()``. - .. WARNING - :: + .. WARNING:: This is not the numerator of the rational function defined by ``self``, which would always be ``self`` since it is a From 11c5cd93ff698493ddf35ffb4f705e3283f3d980 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Thu, 18 Apr 2024 14:15:41 +0200 Subject: [PATCH 101/117] dirty fix for composition, add doctests, one works only with rank of InfinitePolynomialFunctor lowered --- src/sage/rings/lazy_series.py | 8 +++++++- src/sage/rings/lazy_series_ring.py | 23 +++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/sage/rings/lazy_series.py b/src/sage/rings/lazy_series.py index 6be1b4ab57d..56aeea83db6 100644 --- a/src/sage/rings/lazy_series.py +++ b/src/sage/rings/lazy_series.py @@ -5268,16 +5268,22 @@ def coefficient(n): # 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): c = coeff_stream[i] if c in self.base_ring(): c = P(c) r += c[n] - else: + elif c.parent().base_ring() is self.base_ring(): r += c(g)[n] + else: + if gR is None: + gR = [h.change_ring(c.parent().base_ring()) for h in g] + r += c(gR)[n] return r return P.element_class(P, Stream_function(coefficient, diff --git a/src/sage/rings/lazy_series_ring.py b/src/sage/rings/lazy_series_ring.py index 6f2ecc6c357..80c333b4d08 100644 --- a/src/sage/rings/lazy_series_ring.py +++ b/src/sage/rings/lazy_series_ring.py @@ -807,6 +807,29 @@ def define_implicitly(self, series, equations): 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 + TESTS:: sage: L. = LazyPowerSeriesRing(QQ) From 8e088caafb66c538f4750fc67017a57c56f13b0f Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Thu, 2 May 2024 12:10:55 +0200 Subject: [PATCH 102/117] improve error messages --- src/sage/data_structures/stream.py | 123 +++++++++++++++++++++-------- src/sage/rings/lazy_series_ring.py | 119 +++++++++++++++++----------- 2 files changed, 163 insertions(+), 79 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index 65a6dd23f90..0c829bcef89 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -1284,9 +1284,9 @@ def __init__(self, ring): sage: TestSuite(P).run() """ self._gen = ring.gen(0) # alternatively, make :class:`InfinitePolynomialGen` inherit from `UniqueRepresentation`. - self._pool = [] # list of variables actually used + self._pool = dict() # dict of variables actually used to names - def new_variable(self): + def new_variable(self, name=None): """ Return an unused variable. @@ -1312,7 +1312,10 @@ def new_variable(self): for i in range(len(self._pool) + 1): v = self._gen[i] if v not in self._pool: - self._pool.append(v) + if name is None: + self._pool[v] = v + else: + self._pool[v] = name return v def del_variable(self, v): @@ -1331,7 +1334,10 @@ def del_variable(self, v): sage: v = P.new_variable(); v a_1 """ - self._pool.remove(v) + del self._pool[v] + + def variables(self): + return self._pool class Stream_uninitialized(Stream): @@ -1361,7 +1367,7 @@ class Stream_uninitialized(Stream): sage: C[4] 0 """ - def __init__(self, approximate_order, true_order=False): + def __init__(self, approximate_order, true_order=False, name=None): """ Initialize ``self``. @@ -1379,6 +1385,7 @@ def __init__(self, approximate_order, true_order=False): self._approximate_order = approximate_order self._initializing = False self._is_sparse = False + self._name = name def input_streams(self): r""" @@ -1476,6 +1483,13 @@ def define_implicitly(self, series, initial_values, equations, 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)) @lazy_attribute def _input_streams(self): @@ -1620,8 +1634,13 @@ def __getitem__(self, n): if len(self._cache) > n - self._approximate_order: return self._cache[n - self._approximate_order] - x = sum(self._pool.new_variable() * m - for m in self._terms_of_degree(n, self._PF)) + if self._coefficient_ring == self._base_ring: + x = sum(self._PF(self._pool.new_variable(self._name + "[%s]" % n)) * m + for m in self._terms_of_degree(n, self._PF)) + else: + x = sum(self._PF(self._pool.new_variable(self._name + "[%s]" % m)) * m + for m in self._terms_of_degree(n, self._PF)) + self._cache.append(x) return x @@ -1740,7 +1759,7 @@ def _compute(self): """ # determine the next linear equations coeffs = [] - non_linear_coeffs = [] + all_coeffs = [] # only for the error message for i, eq in enumerate(self._eqs): while True: ao = eq._approximate_order @@ -1753,39 +1772,46 @@ def _compute(self): eq._approximate_order += 1 if self._base_ring == self._coefficient_ring: - lcoeff = [coeff] + lcoeff = [(ao, coeff)] else: # TODO: it is a coincidence that `coefficients` - # currently exists in all examples - lcoeff = coeff.coefficients() + # currently exists in all examples; + # the monomials are only needed for the error messages + lcoeff = list(zip(coeff.monomials(), coeff.coefficients())) + + all_coeffs.append(lcoeff) - for c in lcoeff: - c = self._PF(c).numerator() - V = c.variables() + for idx, c in lcoeff: + c_num = self._PF(c).numerator() + V = c_num.variables() if not V: if len(self._eqs) == 1: - raise ValueError(f"no solution as {coeff} != 0 in the equation at degree {eq._approximate_order}") - raise ValueError(f"no solution as {coeff} != 0 in equation {i} at degree {eq._approximate_order}") - if c.degree() <= 1: - coeffs.append(c) - elif c.is_monomial() and sum(1 for d in c.degrees() if d): + if self._base_ring == self._coefficient_ring: + raise ValueError(f"no solution as the coefficient in degree {idx} of the equation is {coeff} != 0") + raise ValueError(f"no solution as the coefficient of {idx} of the equation is {coeff} != 0") + raise ValueError(f"no solution as the coefficient of {idx} in equation {i} is {coeff} != 0") + if c_num.degree() <= 1: + coeffs.append(c_num) + elif c_num.is_monomial() and sum(1 for d in c_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 = c.coefficients()[0] - v = self._P(c.variables()[0]) + c1 = c_num.coefficients()[0] + v = self._P(c_num.variables()[0]) coeffs.append(c1 * v) - else: - # nonlinear equations must not be discarded, we - # collect them to improve any error messages - non_linear_coeffs.append(c) if not coeffs: if len(self._eqs) == 1: - raise ValueError(f"there are no linear equations in degree {self._approximate_order}: {non_linear_coeffs}") - degrees = [eq._approximate_order for eq in self._eqs] - raise ValueError(f"there are no linear equations in degrees {degrees}: {non_linear_coeffs}") + raise ValueError(f"there are no linear equations:\n " + + "\n ".join(self._eq_str(idx, eq) + for idx, eq in all_coeffs[0])) + raise ValueError(f"there are no linear equations:\n" + + "\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))) + # solve from sage.rings.polynomial.multi_polynomial_sequence import PolynomialSequence eqs = PolynomialSequence(coeffs) @@ -1805,7 +1831,7 @@ def _compute(self): x = m.solve_right(b) k = m.right_kernel_matrix() # substitute - bad = True + bad = True # indicates whether we could not determine any coefficients for i, (var, y) in enumerate(zip(v, x)): if k.column(i).is_zero(): val = self._base_ring(y) @@ -1813,10 +1839,41 @@ def _compute(self): bad = False if bad: if len(self._eqs) == 1: - assert len(coeffs) + len(non_linear_coeffs) == 1 - raise ValueError(f"could not determine any coefficients using the equation in degree {self._eqs[0]._approximate_order}: {(coeffs + non_linear_coeffs)[0]}") - degrees = [eq._approximate_order for eq in self._eqs] - raise ValueError(f"could not determine any coefficients using the equations in degrees {degrees}: {coeffs + non_linear_coeffs}") + raise ValueError(f"could not determine any coefficients:\n " + + "\n ".join(self._eq_str(idx, eq) + for idx, eq in all_coeffs[0])) + + raise ValueError(f"could not determine any coefficients:\n" + + "\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))) + + 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] # implicit doctest + Traceback (most recent call last): + ... + ValueError: there are no linear equations: + [0]: -series[0]^2 + series[0] == 0 + + """ + s = repr(eq) + d = self._pool.variables() + # we have to replace longer variables first + for v in sorted(d, key=lambda v: -len(str(v))): + s = s.replace(repr(v), d[v]) + return repr([idx]) + ": " + s + " == 0" def iterate_coefficients(self): """ diff --git a/src/sage/rings/lazy_series_ring.py b/src/sage/rings/lazy_series_ring.py index 40d516e8c67..ae6e1951a54 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,7 +621,7 @@ 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. @@ -654,7 +655,7 @@ 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 @@ -784,12 +785,14 @@ def define_implicitly(self, series, equations): 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:: @@ -804,7 +807,7 @@ def define_implicitly(self, series, equations): sage: g = L.undefined(-2) sage: L.define_implicitly([(g, [5])], [2+z*g(z^2) - g]) sage: g - + A bivariate example:: @@ -829,6 +832,45 @@ def define_implicitly(self, series, equations): + (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]] + TESTS:: sage: L. = LazyPowerSeriesRing(QQ) @@ -919,7 +961,7 @@ def define_implicitly(self, series, equations): 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] # known bug, not tested + sage: B[2] # not tested A bivariate example:: @@ -937,55 +979,38 @@ def define_implicitly(self, series, equations): 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 currently, because the equations + The following does not work, because the equations determining the coefficients come in bad order:: sage: L. = LazyPowerSeriesRing(QQ) - sage: M1 = L.undefined() - sage: M2 = L.undefined() - sage: eq1 = t*x*y*M2(0, 0, t) + (t - x*y)*M1(x, y, t) + x*y - t*M1(0, y, t) - sage: eq2 = (t*x-t)*M2(0, y, t) + (t - x*y)*M2(x, y, t) - sage: L.define_implicitly([M1, M2], [eq1, eq2]) - sage: M1[1] # known bug, not tested - - 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]] + 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: + [x*y*t]: A[x*y] - A[t] == 0 + equation 1: + [x*y*t]: B[x*y] - B[t] == 0 + [x*t^2]: B[x*t] + B[t] == 0 - Permutations with two kinds of labels such that each cycle - contains at least one element of each kind (defined - implicitly to have a test):: + Check the error message in the case of symmetric functions:: 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]] + sage: A = T.undefined(name="A") + sage: B = T.undefined(name="B") + sage: T.define_implicitly([A, B], [X*A - Y*B]) + sage: A + + """ s = [a[0]._coeff_stream if isinstance(a, (tuple, list)) else a._coeff_stream @@ -1575,6 +1600,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. @@ -3476,6 +3502,7 @@ def some_elements(self): ###################################################################### + class LazySymmetricFunctions(LazyCompletionGradedAlgebra): """ The ring of lazy symmetric functions. From 85bcdf812011d420f490ab63360b282ec349178e Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Thu, 2 May 2024 18:26:14 +0200 Subject: [PATCH 103/117] fix filling of cache, improve documentation --- src/sage/data_structures/stream.py | 64 +++++++++++++++++++++++++----- src/sage/rings/lazy_series_ring.py | 29 +++++++++++--- 2 files changed, 78 insertions(+), 15 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index 0c829bcef89..43c3a2b5051 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -1582,6 +1582,48 @@ def __getitem__(self, n): - ``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 @@ -1590,6 +1632,7 @@ def __getitem__(self, n): 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() @@ -1634,14 +1677,17 @@ def __getitem__(self, n): if len(self._cache) > n - self._approximate_order: return self._cache[n - self._approximate_order] - if self._coefficient_ring == self._base_ring: - x = sum(self._PF(self._pool.new_variable(self._name + "[%s]" % n)) * m - for m in self._terms_of_degree(n, self._PF)) - else: - x = sum(self._PF(self._pool.new_variable(self._name + "[%s]" % m)) * m - for m in self._terms_of_degree(n, self._PF)) + # 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): + if self._coefficient_ring == self._base_ring: + x = (self._PF(self._pool.new_variable(self._name + "[%s]" % n0)) + * self._terms_of_degree(n0, self._PF)[0]) + else: + x = sum(self._PF(self._pool.new_variable(self._name + "[%s]" % m)) * m + for m in self._terms_of_degree(n0, self._PF)) + self._cache.append(x) - self._cache.append(x) return x def _subs_in_caches(self, var, val): @@ -1865,7 +1911,7 @@ def _eq_str(self, idx, eq): Traceback (most recent call last): ... ValueError: there are no linear equations: - [0]: -series[0]^2 + series[0] == 0 + coefficient [0]: -series[0]^2 + series[0] == 0 """ s = repr(eq) @@ -1873,7 +1919,7 @@ def _eq_str(self, idx, eq): # we have to replace longer variables first for v in sorted(d, key=lambda v: -len(str(v))): s = s.replace(repr(v), d[v]) - return repr([idx]) + ": " + s + " == 0" + return "coefficient " + repr([idx]) + ": " + s + " == 0" def iterate_coefficients(self): """ diff --git a/src/sage/rings/lazy_series_ring.py b/src/sage/rings/lazy_series_ring.py index ae6e1951a54..efb09c3c481 100644 --- a/src/sage/rings/lazy_series_ring.py +++ b/src/sage/rings/lazy_series_ring.py @@ -786,13 +786,13 @@ def define_implicitly(self, series, equations): sage: L.define_implicitly([F], [F(2*z) - (1+exp(x*z)+exp(y*z))*F - exp((x+y)*z)*F(-z)]) sage: F + coefficient [3]: 6*series[3] + (-2*x - 2*y)*series[2] + (x*y)*series[1] == 0> 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 + coefficient [3]: 6*series[3] + (-2*x - 2*y)*series[2] + (x*y*f1) == 0> Laurent series examples:: @@ -993,10 +993,10 @@ def define_implicitly(self, series, equations): ... ValueError: could not determine any coefficients: equation 0: - [x*y*t]: A[x*y] - A[t] == 0 + coefficient [x*y*t]: A[x*y] - A[t] == 0 equation 1: - [x*y*t]: B[x*y] - B[t] == 0 - [x*t^2]: B[x*t] + B[t] == 0 + 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:: @@ -1009,7 +1009,24 @@ def define_implicitly(self, series, equations): sage: T.define_implicitly([A, B], [X*A - Y*B]) sage: A + coefficient [p[1] # p[1]]: -B[p[1] # p[]] + A[p[] # p[1]] == 0> + + 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 """ s = [a[0]._coeff_stream if isinstance(a, (tuple, list)) From a4dbb419b03c70105ab2ddc7c1ae6a9fc85a55c0 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Fri, 3 May 2024 15:19:30 +0200 Subject: [PATCH 104/117] fix typo and implicit doctest -> indirect doctest --- src/sage/data_structures/stream.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index 43c3a2b5051..99fc5647caf 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -1274,7 +1274,7 @@ class VariablePool(UniqueRepresentation): """ def __init__(self, ring): """ - Inititialize the pool. + Initialize the pool. EXAMPLES:: @@ -1709,7 +1709,7 @@ def _subs_in_caches(self, var, val): 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] # implicit doctest + sage: C[3] # indirect doctest 2 """ def subs(c, var, val): @@ -1800,7 +1800,7 @@ def _compute(self): 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] # implicit doctest + sage: C[3] # indirect doctest 2 """ # determine the next linear equations @@ -1907,7 +1907,7 @@ def _eq_str(self, idx, eq): 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] # implicit doctest + sage: C[3] # indirect doctest Traceback (most recent call last): ... ValueError: there are no linear equations: From d855cc35a1bea69cadd6d1a2b60c873bdac73d7e Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Fri, 3 May 2024 15:34:31 +0200 Subject: [PATCH 105/117] fix doctest --- src/sage/rings/lazy_series_ring.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/lazy_series_ring.py b/src/sage/rings/lazy_series_ring.py index efb09c3c481..ea46201ef06 100644 --- a/src/sage/rings/lazy_series_ring.py +++ b/src/sage/rings/lazy_series_ring.py @@ -1001,7 +1001,7 @@ def define_implicitly(self, series, equations): Check the error message in the case of symmetric functions:: sage: p = SymmetricFunctions(QQ).p() - sage: S = LazySymmetricFunctions(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") From e1a26e2407fb7267ebaf61192025e29b8e71596f Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Tue, 7 May 2024 16:15:20 +0200 Subject: [PATCH 106/117] provide an option to look ahead in the equations, refactor --- src/sage/data_structures/stream.py | 260 ++++++++++++++++++----------- src/sage/rings/lazy_series.py | 4 +- src/sage/rings/lazy_series_ring.py | 31 +++- 3 files changed, 194 insertions(+), 101 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index 99fc5647caf..cb81bb13f12 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -1307,7 +1307,6 @@ def new_variable(self, name=None): sage: P0 = VariablePool(R0) sage: P0.new_variable() b_0 - """ for i in range(len(self._pool) + 1): v = self._gen[i] @@ -1337,6 +1336,19 @@ def del_variable(self, v): del self._pool[v] def variables(self): + """ + Return the dictionary mapping variables to names. + + 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: a_0, a_1: a_1, a_2: 'my new variable'} + """ return self._pool @@ -1348,6 +1360,9 @@ class Stream_uninitialized(Stream): - ``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. @@ -1435,7 +1450,8 @@ def define(self, target): self._iter = self.iterate_coefficients() def define_implicitly(self, series, initial_values, equations, - base_ring, coefficient_ring, terms_of_degree): + base_ring, coefficient_ring, terms_of_degree, + max_lookahead=1): r""" Define ``self`` via ``equations == 0``. @@ -1445,8 +1461,13 @@ def define_implicitly(self, series, initial_values, equations, - ``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 + - ``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:: @@ -1459,6 +1480,7 @@ def define_implicitly(self, series, initial_values, equations, sage: C.define_implicitly([C], [], [eq], QQ, QQ, terms_of_degree) sage: C[6] 42 + """ assert self._target is None @@ -1490,6 +1512,7 @@ def define_implicitly(self, series, initial_values, equations, 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): @@ -1680,11 +1703,13 @@ def __getitem__(self, n): # 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._PF(self._pool.new_variable(self._name + "[%s]" % n0)) + x = (self._pool.new_variable(self._name + "[%s]" % n0) * self._terms_of_degree(n0, self._PF)[0]) else: - x = sum(self._PF(self._pool.new_variable(self._name + "[%s]" % m)) * m + 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) @@ -1726,6 +1751,15 @@ def retract(c): 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: @@ -1756,117 +1790,110 @@ def retract(c): # fix approximate_order and true_order ao = s._approximate_order if s._is_sparse: - while ao in s._cache: - if s._cache[ao]: - if s._cache[ao] in self._coefficient_ring: - s._true_order = True - break - del s._cache[ao] - self._good_cache[j] -= 1 + while ao in s._cache and fix_cache(j, s, ao): ao += 1 else: - while s._cache: - if s._cache[0]: - if s._cache[0] in self._coefficient_ring: - s._true_order = True - break - del s._cache[0] - self._good_cache[j] -= 1 + while s._cache and fix_cache(j, s, 0): ao += 1 s._approximate_order = ao self._pool.del_variable(var) - 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. + 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_exact, Stream_cauchy_mul, Stream_add, Stream_sub + sage: from sage.data_structures.stream import Stream_uninitialized, Stream_cauchy_mul, 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 = 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 - 2 + sage: C._uncomputed = False + sage: C._collect_equations(0) + ([], [[(0, -FESDUMMY_0^2 + FESDUMMY_0)]]) """ - # determine the next linear equations - coeffs = [] + lin_coeffs = [] all_coeffs = [] # only for the error message for i, eq in enumerate(self._eqs): while True: - ao = eq._approximate_order - coeff = eq[ao] - if coeff: + 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__ - if eq._approximate_order == ao: - eq._approximate_order += 1 + # 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: - lcoeff = [(ao, coeff)] + 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 - lcoeff = list(zip(coeff.monomials(), coeff.coefficients())) - - all_coeffs.append(lcoeff) + elt_coeffs = list(zip(elt.monomials(), elt.coefficients())) - for idx, c in lcoeff: - c_num = self._PF(c).numerator() - V = c_num.variables() + 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 {coeff} != 0") - raise ValueError(f"no solution as the coefficient of {idx} of the equation is {coeff} != 0") - raise ValueError(f"no solution as the coefficient of {idx} in equation {i} is {coeff} != 0") - if c_num.degree() <= 1: - coeffs.append(c_num) - elif c_num.is_monomial() and sum(1 for d in c_num.degrees() if d): + 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 = c_num.coefficients()[0] - v = self._P(c_num.variables()[0]) - coeffs.append(c1 * v) - - if not coeffs: - if len(self._eqs) == 1: - raise ValueError(f"there are no linear equations:\n " - + "\n ".join(self._eq_str(idx, eq) - for idx, eq in all_coeffs[0])) - raise ValueError(f"there are no linear equations:\n" - + "\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))) - - # solve + 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(coeffs) + 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]) + 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: @@ -1875,25 +1902,70 @@ def _compute(self): m = m1 v = list(v1) x = m.solve_right(b) - k = m.right_kernel_matrix() - # substitute - bad = True # indicates whether we could not determine any coefficients - for i, (var, y) in enumerate(zip(v, x)): - if k.column(i).is_zero(): - val = self._base_ring(y) + 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) - bad = False - if bad: - if len(self._eqs) == 1: - raise ValueError(f"could not determine any coefficients:\n " - + "\n ".join(self._eq_str(idx, eq) - for idx, eq in all_coeffs[0])) + 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 + bad = True # indicates whether we could not determine any coefficients + 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(f"could not determine any coefficients:\n " + + eq_str) + raise ValueError(f"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(f"could not determine any coefficients:\n" - + "\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))) + + eqs_str) + raise ValueError(f"there are no linear equations:\n" + + eqs_str) def _eq_str(self, idx, eq): """ diff --git a/src/sage/rings/lazy_series.py b/src/sage/rings/lazy_series.py index 8665ae4559e..9a15cf75ee6 100644 --- a/src/sage/rings/lazy_series.py +++ b/src/sage/rings/lazy_series.py @@ -4547,7 +4547,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 @@ -5711,7 +5711,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 diff --git a/src/sage/rings/lazy_series_ring.py b/src/sage/rings/lazy_series_ring.py index ea46201ef06..80d9febd025 100644 --- a/src/sage/rings/lazy_series_ring.py +++ b/src/sage/rings/lazy_series_ring.py @@ -627,7 +627,10 @@ def undefined(self, valuation=None, name=None): 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 @@ -652,6 +655,7 @@ def undefined(self, valuation=None, name=None): sage: f.define(z^-1 + z^2*f^2) sage: f z^-1 + 1 + 2*z + 5*z^2 + 14*z^3 + 42*z^4 + 132*z^5 + O(z^6) + """ if valuation is None: valuation = self._minimal_valuation @@ -673,10 +677,18 @@ def _terms_of_degree(self, n, R): 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): + def define_implicitly(self, series, equations, max_lookahead=1): r""" Define series by solving functional equations. @@ -684,8 +696,11 @@ def define_implicitly(self, series, equations): - ``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:: @@ -1028,6 +1043,12 @@ def define_implicitly(self, series, equations): 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 @@ -1040,7 +1061,8 @@ def define_implicitly(self, series, equations): f.define_implicitly(s, ic, eqs, self.base_ring(), self._internal_poly_ring.base_ring(), - self._terms_of_degree) + self._terms_of_degree, + max_lookahead=max_lookahead) class options(GlobalOptions): r""" @@ -1121,7 +1143,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) From 245f32c2de12f53e390bbf99e30b50e3760e9898 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Wed, 8 May 2024 11:07:47 +0200 Subject: [PATCH 107/117] free variables in VariablePool on destruction of a Stream_uninitialized --- src/sage/data_structures/stream.py | 62 +++++++++++++++++++++++------- 1 file changed, 49 insertions(+), 13 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index cb81bb13f12..dae09a85d10 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -1286,7 +1286,7 @@ def __init__(self, ring): 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, name=None): + def new_variable(self, data=None): """ Return an unused variable. @@ -1311,10 +1311,7 @@ def new_variable(self, name=None): for i in range(len(self._pool) + 1): v = self._gen[i] if v not in self._pool: - if name is None: - self._pool[v] = v - else: - self._pool[v] = name + self._pool[v] = data return v def del_variable(self, v): @@ -1337,7 +1334,7 @@ def del_variable(self, v): def variables(self): """ - Return the dictionary mapping variables to names. + Return the dictionary mapping variables to data. EXAMPLES:: @@ -1347,7 +1344,7 @@ def variables(self): sage: P.new_variable("my new variable") a_2 sage: P.variables() - {a_0: a_0, a_1: a_1, a_2: 'my new variable'} + {a_0: None, a_1: None, a_2: 'my new variable'} """ return self._pool @@ -1402,6 +1399,45 @@ def __init__(self, approximate_order, true_order=False, name=None): 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 @@ -1590,8 +1626,8 @@ def _good_cache(self): else: vals = c._cache i = 0 - for v in vals: - if v not in self._coefficient_ring: + for val in vals: + if val not in self._coefficient_ring: break i += 1 g.append(i) @@ -1987,10 +2023,10 @@ def _eq_str(self, idx, eq): """ s = repr(eq) - d = self._pool.variables() - # we have to replace longer variables first - for v in sorted(d, key=lambda v: -len(str(v))): - s = s.replace(repr(v), d[v]) + 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): From d64c9688d1e41d5415c623f0c9c257865a13eaef Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Wed, 8 May 2024 16:00:52 +0200 Subject: [PATCH 108/117] remove duplicate example, cache _terms_of_degree --- src/sage/rings/lazy_series_ring.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/sage/rings/lazy_series_ring.py b/src/sage/rings/lazy_series_ring.py index 80d9febd025..33bc6ae5a8e 100644 --- a/src/sage/rings/lazy_series_ring.py +++ b/src/sage/rings/lazy_series_ring.py @@ -978,14 +978,6 @@ def define_implicitly(self, series, equations, max_lookahead=1): 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:: - - 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 - A bivariate example involving composition of series:: sage: R. = LazyPowerSeriesRing(QQ) @@ -2559,6 +2551,7 @@ 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 @@ -3251,6 +3244,7 @@ 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``. From 4ceac4c462655578d61670813d419aaf67200a93 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Fri, 10 May 2024 08:59:41 +0200 Subject: [PATCH 109/117] replace list() with [] --- src/sage/data_structures/stream.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index dae09a85d10..2f2716349c8 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -295,7 +295,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): @@ -384,7 +384,7 @@ def __setstate__(self, d): """ self.__dict__ = d if not self._is_sparse: - self._cache = list() + self._cache = [] self._iter = self.iterate_coefficients() def __getitem__(self, n): @@ -1482,7 +1482,7 @@ def define(self, target): 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 = list() + self._cache = [] self._iter = self.iterate_coefficients() def define_implicitly(self, series, initial_values, equations, From 87ec8e47f74fcf843c7da4c2d7a931c764d734a9 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Fri, 10 May 2024 09:17:46 +0200 Subject: [PATCH 110/117] improve doc formatting Co-authored-by: Travis Scrimshaw --- src/sage/data_structures/stream.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index 2f2716349c8..0d24d589670 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -1270,7 +1270,7 @@ class VariablePool(UniqueRepresentation): INPUT: - - ``ring``, an :class:`InfinitePolynomialRing`. + - ``ring`` -- :class:`InfinitePolynomialRing` """ def __init__(self, ring): """ From 2355a3263ed7e5bf7f7b316b7eb3fd905d426be2 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Fri, 10 May 2024 09:30:38 +0200 Subject: [PATCH 111/117] remove trailing blank lines in docstrings --- src/sage/data_structures/stream.py | 11 ----------- src/sage/rings/lazy_series.py | 10 ---------- src/sage/rings/lazy_series_ring.py | 4 ---- 3 files changed, 25 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index 0d24d589670..2195d03d3e5 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -135,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): """ @@ -561,7 +560,6 @@ def __ne__(self, other): True sage: g != f True - """ # TODO: more cases, in particular mixed implementations, # could be detected @@ -824,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 @@ -1008,7 +1005,6 @@ class Stream_function(Stream_inexact): sage: f = Stream_function(lambda n: n, True, 0) sage: f[4] 4 - """ def __init__(self, function, is_sparse, approximate_order, true_order=False): """ @@ -1516,7 +1512,6 @@ def define_implicitly(self, series, initial_values, equations, sage: C.define_implicitly([C], [], [eq], QQ, QQ, terms_of_degree) sage: C[6] 42 - """ assert self._target is None @@ -1691,7 +1686,6 @@ def __getitem__(self, n): 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() @@ -2020,7 +2014,6 @@ def _eq_str(self, idx, eq): ... ValueError: there are no linear equations: coefficient [0]: -series[0]^2 + series[0] == 0 - """ s = repr(eq) p = self._pool.variables() @@ -2997,7 +2990,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""" @@ -3213,7 +3205,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: @@ -3573,7 +3564,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): """ @@ -3817,7 +3807,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): """ diff --git a/src/sage/rings/lazy_series.py b/src/sage/rings/lazy_series.py index 9a15cf75ee6..80ec20b26c2 100644 --- a/src/sage/rings/lazy_series.py +++ b/src/sage/rings/lazy_series.py @@ -576,7 +576,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 @@ -2701,7 +2700,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) @@ -2739,7 +2737,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) @@ -2774,7 +2771,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 @@ -4126,7 +4122,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 @@ -4807,7 +4802,6 @@ def polynomial(self, degree=None, name=None): sage: L.zero().polynomial() 0 - """ S = self.parent() @@ -4977,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)) @@ -6642,7 +6635,6 @@ def revert(self): - Andrew Gainer-Dewar - Martin Rubey - """ P = self.parent() if P._arity != 1: @@ -7169,7 +7161,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") @@ -7294,7 +7285,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 33bc6ae5a8e..20b7933d79f 100644 --- a/src/sage/rings/lazy_series_ring.py +++ b/src/sage/rings/lazy_series_ring.py @@ -655,7 +655,6 @@ def undefined(self, valuation=None, name=None): sage: f.define(z^-1 + z^2*f^2) sage: f z^-1 + 1 + 2*z + 5*z^2 + 14*z^3 + 42*z^4 + 132*z^5 + O(z^6) - """ if valuation is None: valuation = self._minimal_valuation @@ -2751,7 +2750,6 @@ 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: @@ -3509,7 +3507,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] @@ -3662,7 +3659,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") From e0cb0f37c66afd36011d443e469ab980580d8ff8 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Fri, 10 May 2024 09:31:30 +0200 Subject: [PATCH 112/117] use list comprehension Co-authored-by: Travis Scrimshaw --- src/sage/data_structures/stream.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index 2195d03d3e5..7ed5602c9b9 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -1045,12 +1045,8 @@ def input_streams(self): closure = self.get_coefficient.__closure__ if closure is None: return [] - l = [] - for cell in closure: - content = cell.cell_contents - if isinstance(content, Stream): - l.append(content) - return l + return [cell.cell_contents for cell in closure + if isinstance(cell.cell_contents, Stream)] def __hash__(self): """ From 688e91a7179e689d6c440486494d5860b8a89dce Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Fri, 10 May 2024 09:54:55 +0200 Subject: [PATCH 113/117] cosmetic fixes and replace else: if...: with elif: --- src/sage/data_structures/stream.py | 24 +++++++++++------------- src/sage/rings/lazy_series.py | 9 ++++----- src/sage/rings/lazy_series_ring.py | 7 +++---- 3 files changed, 18 insertions(+), 22 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index 7ed5602c9b9..6108b7568d9 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -1802,15 +1802,14 @@ def fix_cache(j, s, ao): if self._coefficient_ring == self._base_ring: if c.parent() == self._PF: c = retract(subs(c, var, val)) - if not c.parent() is self._base_ring: - good = m - i0 - 1 - else: - if 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: + 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 @@ -1966,7 +1965,6 @@ def _compute(self): # determine the next linear equations lin_coeffs = [] all_coeffs = [] # only for the error message - bad = True # indicates whether we could not determine any coefficients for offset in range(self._max_lookahead): new_lin_coeffs, new_all_coeffs = self._collect_equations(offset) lin_coeffs.extend(new_lin_coeffs) @@ -1978,9 +1976,9 @@ def _compute(self): eq_str = "\n ".join(self._eq_str(idx, eq) for idx, eq in all_coeffs[0]) if lin_coeffs: - raise ValueError(f"could not determine any coefficients:\n " + raise ValueError("could not determine any coefficients:\n " + eq_str) - raise ValueError(f"there are no linear equations:\n " + raise ValueError("there are no linear equations:\n " + eq_str) eqs_str = "\n".join(f"equation {i}:\n " @@ -1988,9 +1986,9 @@ def _compute(self): for idx, eq in eqs) for i, eqs in enumerate(all_coeffs)) if lin_coeffs: - raise ValueError(f"could not determine any coefficients:\n" + raise ValueError("could not determine any coefficients:\n" + eqs_str) - raise ValueError(f"there are no linear equations:\n" + raise ValueError("there are no linear equations:\n" + eqs_str) def _eq_str(self, idx, eq): diff --git a/src/sage/rings/lazy_series.py b/src/sage/rings/lazy_series.py index 80ec20b26c2..15de66cff25 100644 --- a/src/sage/rings/lazy_series.py +++ b/src/sage/rings/lazy_series.py @@ -851,12 +851,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) diff --git a/src/sage/rings/lazy_series_ring.py b/src/sage/rings/lazy_series_ring.py index 20b7933d79f..d7512dcdd81 100644 --- a/src/sage/rings/lazy_series_ring.py +++ b/src/sage/rings/lazy_series_ring.py @@ -2872,11 +2872,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") From b8ecc2856ce40faf77db705a8dbce5f6f76ae9c6 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Fri, 10 May 2024 13:10:09 +0200 Subject: [PATCH 114/117] use PolynomialRing explicitly --- src/sage/rings/polynomial/multi_polynomial.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/polynomial/multi_polynomial.pyx b/src/sage/rings/polynomial/multi_polynomial.pyx index 37247f36571..0cd77f09e6d 100644 --- a/src/sage/rings/polynomial/multi_polynomial.pyx +++ b/src/sage/rings/polynomial/multi_polynomial.pyx @@ -416,7 +416,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) From b3105a8db43371318d2a62862e11883d443fca05 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Fri, 10 May 2024 13:10:57 +0200 Subject: [PATCH 115/117] naive implementations of is_prime_field and fraction_field for symmetric functions --- src/sage/combinat/sf/sfa.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/sage/combinat/sf/sfa.py b/src/sage/combinat/sf/sfa.py index d2d9d6385e5..e181a71cd7e 100644 --- a/src/sage/combinat/sf/sfa.py +++ b/src/sage/combinat/sf/sfa.py @@ -412,6 +412,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.) @@ -1848,6 +1859,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""" From a814c2098f5a29420099d9a327a172b45b2f6518 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Sun, 12 May 2024 11:29:40 +0200 Subject: [PATCH 116/117] add (currently failing) example --- src/sage/rings/lazy_series_ring.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/sage/rings/lazy_series_ring.py b/src/sage/rings/lazy_series_ring.py index d7512dcdd81..257b7d943dd 100644 --- a/src/sage/rings/lazy_series_ring.py +++ b/src/sage/rings/lazy_series_ring.py @@ -885,6 +885,15 @@ def define_implicitly(self, series, equations, max_lookahead=1): 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 + TESTS:: sage: L. = LazyPowerSeriesRing(QQ) From 124c7df339d13630cfaca5c4ad26feb6bd69cf2a Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Wed, 15 May 2024 15:56:14 +0200 Subject: [PATCH 117/117] work harder putting the coefficients into the correct parent --- src/sage/rings/lazy_series.py | 8 +++++--- src/sage/rings/lazy_series_ring.py | 3 +++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/sage/rings/lazy_series.py b/src/sage/rings/lazy_series.py index 15de66cff25..2a0fc0a0083 100644 --- a/src/sage/rings/lazy_series.py +++ b/src/sage/rings/lazy_series.py @@ -5267,15 +5267,17 @@ def coefficient(n): r = R.zero() for i in range(n // gv + 1): c = coeff_stream[i] - if c in self.base_ring(): + 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: - gR = [h.change_ring(c.parent().base_ring()) for h in g] - r += c(gR)[n] + 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 return P.element_class(P, Stream_function(coefficient, diff --git a/src/sage/rings/lazy_series_ring.py b/src/sage/rings/lazy_series_ring.py index 41b0b22c976..cbb41861301 100644 --- a/src/sage/rings/lazy_series_ring.py +++ b/src/sage/rings/lazy_series_ring.py @@ -893,6 +893,9 @@ def define_implicitly(self, series, equations, max_lookahead=1): 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::