From 344b5fdd8b5acbc5568b2815b57b6c98896d3c37 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Mon, 19 Sep 2022 01:49:24 +0200 Subject: [PATCH 1/8] special case Stream_exact, use _approximate_order, use := in sum --- src/sage/data_structures/stream.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index c2a9b90296d..933ceed2772 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -659,7 +659,7 @@ def __getitem__(self, n): if n >= self._degree: return self._constant i = n - self._approximate_order - if i < 0 or i >= len(self._initial_coefficients): + if i < 0 or i >= len(self._initial_coefficients): # TODO: i < 0 should not happen return ZZ.zero() return self._initial_coefficients[i] @@ -2335,11 +2335,18 @@ def get_coefficient(self, n): if n == v: return self._ainv - c = self._zero - for k in range(v, n): - l = self[k] - if l: - c += l * self._series[n - v - k] + # if self._series is exact with self._series._constant == 0 + # then self._series[n - v - k] == 0 unless + # k > n - v - self._series._degree + # in general, we have self._series[n - v - k] == 0 unless + # n - v - self._series._approximate_order < k + if isinstance(self._series, Stream_exact) and not self._series._constant: + a = max(v, n - v - self._series._degree + 1) + else: + a = v + b = min(n, n - v - self._series._approximate_order) + c = sum(l * self._series[n - v - k] for k in range(a, b) + if (l := self[k])) return -c * self._ainv def iterate_coefficients(self): From 436887a3d10b21e6303470838bd2118388abc306 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Mon, 19 Sep 2022 23:35:09 +0200 Subject: [PATCH 2/8] consistently use sum with := --- src/sage/data_structures/stream.py | 74 +++++++++++++++--------------- 1 file changed, 36 insertions(+), 38 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index 1e8399a2f5b..f1bb6722ce0 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -1504,13 +1504,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] """ - c = ZZ.zero() - for k in range(self._left._approximate_order, - n - self._right._approximate_order + 1): - val = self._left[k] - if val: - c += val * self._right[n-k] - return c + 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])) def is_nonzero(self): r""" @@ -1581,9 +1578,7 @@ def __init__(self, left, right): assert left._approximate_order > 0 and right._approximate_order > 0, "Dirichlet convolution is only defined for coefficient streams with minimal index of nonzero coefficient at least 1" - vl = left._approximate_order - vr = right._approximate_order - a = vl * vr + a = left._approximate_order * right._approximate_order super().__init__(left, right, left._is_sparse, a) def get_coefficient(self, n): @@ -1605,15 +1600,10 @@ def get_coefficient(self, n): sage: [h[i] for i in range(1, 10)] [1, 3, 4, 7, 6, 12, 8, 15, 13] """ - c = ZZ.zero() - for k in divisors(n): - if k < self._left._approximate_order or n // k < self._right._approximate_order: - continue - val = self._left[k] - if val: - c += val * self._right[n//k] - return c - + 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]))) class Stream_dirichlet_invert(Stream_unary): r""" @@ -1678,12 +1668,10 @@ def get_coefficient(self, n): self._ainv = self._series[1].inverse_of_unit() if n == 1: return self._ainv - c = self._zero - for k in divisors(n): - if k < n: - val = self._series[n//k] - if val: - c += self[k] * val + # 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]))) return -c * self._ainv @@ -1757,15 +1745,22 @@ def get_coefficient(self, n): fv = self._left._approximate_order gv = self._right._approximate_order if n < 0: - return sum(self._left[i] * self._neg_powers[-i][n] - for i in range(fv, n // gv + 1)) + return 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: - self._pos_powers.append(Stream_cauchy_mul(self._pos_powers[-1], self._right)) - ret = sum(self._left[i] * self._neg_powers[-i][n] for i in range(fv, 0)) - if n == 0: + self._pos_powers.append(Stream_cauchy_mul(self._pos_powers[-1], + self._right)) + + ret = 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(self._left[i] * self._pos_powers[i][n] for i in range(1, n // gv+1)) + + return ret + 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): @@ -2025,7 +2020,8 @@ def stretched_power_restrict_degree(self, i, m, d): True """ while len(self._powers) < m: - self._powers.append(Stream_cauchy_mul(self._powers[-1], self._powers[0])) + self._powers.append(Stream_cauchy_mul(self._powers[-1], + self._powers[0])) power_d = self._powers[m-1][d] # we have to check power_d for zero because it might be an # integer and not a symmetric function @@ -2136,7 +2132,7 @@ class Stream_rmul(Stream_scalar): INPUT: - ``series`` -- a :class:`Stream` - - ``scalar`` -- a scalar + - ``scalar`` -- a non-zero scalar EXAMPLES:: @@ -2177,7 +2173,7 @@ class Stream_lmul(Stream_scalar): INPUT: - ``series`` -- a :class:`Stream` - - ``scalar`` -- a scalar + - ``scalar`` -- a non-zero scalar EXAMPLES:: @@ -2570,7 +2566,7 @@ def __getitem__(self, n): sage: [M[i] for i in range(6)] [0, 0, 0, 1, 2, 3] """ - return self._series[n-self._shift] + return self._series[n - self._shift] def __hash__(self): """ @@ -2607,7 +2603,8 @@ def __eq__(self, other): sage: M2 == Stream_shift(F, 2) True """ - return (isinstance(other, type(self)) and self._shift == other._shift + return (isinstance(other, type(self)) + and self._shift == other._shift and self._series == other._series) def is_nonzero(self): @@ -2677,7 +2674,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 (prod(n + k for k in range(1, self._shift + 1)) * self._series[n + self._shift]) def __hash__(self): @@ -2719,7 +2716,8 @@ def __eq__(self, other): sage: f == Stream_derivative(a, 1) True """ - return (isinstance(other, type(self)) and self._shift == other._shift + return (isinstance(other, type(self)) + and self._shift == other._shift and self._series == other._series) def is_nonzero(self): From 275b6505ddbcf96309e945bc6a7f0d6ddc107acd Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Fri, 23 Sep 2022 10:18:55 +0200 Subject: [PATCH 3/8] improve performance of Stream_plethysm --- src/sage/data_structures/stream.py | 40 ++++++++++++++++++------------ 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index f1bb6722ce0..d30ccfc0260 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -101,7 +101,7 @@ from sage.combinat.integer_vector_weighted import iterator_fast as wt_int_vec_iter from sage.combinat.sf.sfa import _variables_recursive, _raise_variables from sage.categories.hopf_algebras_with_basis import HopfAlgebrasWithBasis - +from sage.misc.cachefunc import cached_method class Stream(): """ @@ -1877,7 +1877,7 @@ def __init__(self, f, g, p, ring=None, include=None, exclude=None): self._basis = ring self._p = p g = Stream_map_coefficients(g, lambda x: p(x)) - self._powers = [g] # a cache for the powers of g + self._powers = [g] # a cache for the powers of g in the powersum basis R = self._basis.base_ring() self._degree_one = _variables_recursive(R, include=include, exclude=exclude) @@ -1922,20 +1922,20 @@ def get_coefficient(self, n): return sum((c * self.compute_product(n, la) for k in range(self._left._approximate_order, self._degree_f) - if self._left[k] + if self._left[k] # necessary, because it might be int(0) for la, c in self._left[k]), self._basis.zero()) res = sum((c * self.compute_product(n, la) for k in range(self._left._approximate_order, n+1) - if self._left[k] + if self._left[k] # necessary, because it might be int(0) for la, c in self._left[k]), self._basis.zero()) return res def compute_product(self, n, la): r""" - Compute the product ``c * p[la](self._right)`` in degree ``n``. + Compute the product ``p[la](self._right)`` in degree ``n``. EXAMPLES:: @@ -1985,17 +1985,23 @@ def compute_product(self, n, la): for k in wt_int_vec_iter(n - ret_approx_order, wgt): # TODO: it may make a big difference here if the # approximate order would be updated. - # The test below is based on not removing the fixed block - #if any(d < self._right._approximate_order * m - # for m, d in zip(exp, k)): - # continue - ret += prod(self.stretched_power_restrict_degree(i, m, rao * m + d) - for i, m, d in zip(wgt, exp, k)) + lf = [] + for i, m, d in zip(wgt, exp, k): + f = self.stretched_power_restrict_degree(i, m, rao * m + d) + if f: + lf.append(f) + else: + break + else: + ret += prod(lf) + return ret + @cached_method def stretched_power_restrict_degree(self, i, m, d): r""" - Return the degree ``d*i`` part of ``p([i]*m)(g)``. + Return the degree ``d*i`` part of ``p([i]*m)(g)`` in + terms of ``self._basis``. EXAMPLES:: @@ -2018,7 +2024,9 @@ def stretched_power_restrict_degree(self, i, m, d): sage: B = p2.element_class(p2, {m: c for m, c in B if sum(mu.size() for mu in m) == 12}) # long time sage: A == B # long time True + """ + # TODO: we should do lazy binary powering here while len(self._powers) < m: self._powers.append(Stream_cauchy_mul(self._powers[-1], self._powers[0])) @@ -2028,14 +2036,14 @@ def stretched_power_restrict_degree(self, i, m, d): if power_d: if self._tensor_power is None: terms = {mon.stretch(i): raised_c for mon, c in power_d - if (raised_c := _raise_variables(c, i, self._degree_one))} + if (raised_c := _raise_variables(c, i, self._degree_one))} # TODO: could this ever be 0? else: terms = {tuple((mu.stretch(i) for mu in mon)): raised_c for mon, c in power_d - if (raised_c := _raise_variables(c, i, self._degree_one))} - return self._p.element_class(self._p, terms) + if (raised_c := _raise_variables(c, i, self._degree_one))} # TODO: could this ever be 0? + return self._basis(self._p.element_class(self._p, terms)) - return self._p.zero() + return self._basis.zero() ##################################################################### From 2e63bf9db6370d7b0c0e280abf1b4d4348abe1a2 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Fri, 23 Sep 2022 12:13:08 +0200 Subject: [PATCH 4/8] add explanations --- src/sage/data_structures/stream.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index d30ccfc0260..ddfef084054 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -1926,6 +1926,8 @@ def get_coefficient(self, n): for la, c in self._left[k]), self._basis.zero()) + # this also needs to be executed if not n and not + # self._right[0] res = sum((c * self.compute_product(n, la) for k in range(self._left._approximate_order, n+1) if self._left[k] # necessary, because it might be int(0) @@ -1985,6 +1987,11 @@ def compute_product(self, n, la): for k in wt_int_vec_iter(n - ret_approx_order, wgt): # TODO: it may make a big difference here if the # approximate order would be updated. + + # prod does not short-cut zero, therefore + # ret += prod(self.stretched_power_restrict_degree(i, m, rao * m + d) + # for i, m, d in zip(wgt, exp, k)) + # is expensive lf = [] for i, m, d in zip(wgt, exp, k): f = self.stretched_power_restrict_degree(i, m, rao * m + d) From 73216e3b979de1984427d338b040cab1dea43cb2 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Sat, 25 Mar 2023 11:01:01 +0100 Subject: [PATCH 5/8] remove Stream_cauchy_invert.get_coefficient because it is always dense, see 814aa7cd53e8ca569ddfd87ba1cfa498c564531d --- src/sage/data_structures/stream.py | 43 ++---------------------------- 1 file changed, 2 insertions(+), 41 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index 542b748f953..72f7d359131 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -2480,7 +2480,8 @@ class Stream_cauchy_invert(Stream_unary): - ``approximate_order`` -- ``None``, or a lower bound on the order of the resulting stream - Instances of this class are always dense. + Instances of this class are always dense, because of mathematical + necessities. EXAMPLES:: @@ -2552,46 +2553,6 @@ def _ainv(self): except TypeError: return self._series[v].inverse_of_unit() - 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_cauchy_invert, Stream_function) - sage: f = Stream_function(lambda n: n, True, 1) - sage: g = Stream_cauchy_invert(f) - sage: g.get_coefficient(5) - 0 - sage: [g.get_coefficient(i) for i in range(10)] - [-2, 1, 0, 0, 0, 0, 0, 0, 0, 0] - """ - if not self._series._true_order: - self._ainv # this computes the true order of ``self`` - v = self._approximate_order - if n < v: - return ZZ.zero() - if n == v: - return self._ainv - - # if self._series is exact with self._series._constant == 0 - # then self._series[n - v - k] == 0 unless - # k > n - v - self._series._degree - # in general, we have self._series[n - v - k] == 0 unless - # n - v - self._series._approximate_order < k - if isinstance(self._series, Stream_exact) and not self._series._constant: - a = max(v, n - v - self._series._degree + 1) - else: - a = v - b = min(n, n - v - self._series._approximate_order) - c = sum(l * self._series[n - v - k] for k in range(a, b) - if (l := self[k])) - return -c * self._ainv - def iterate_coefficients(self): """ A generator for the coefficients of ``self``. From 3cb1a179be87f0deac9ae282d1681e81ffa59380 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Sat, 25 Mar 2023 11:16:25 +0100 Subject: [PATCH 6/8] remove non-sensical TODO, simplify logic in loop --- src/sage/data_structures/stream.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index 72f7d359131..8dfef6f1e51 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -722,7 +722,7 @@ def __getitem__(self, n): if n >= self._degree: return self._constant i = n - self._approximate_order - if i < 0 or i >= len(self._initial_coefficients): # TODO: i < 0 should not happen + if i < 0 or i >= len(self._initial_coefficients): return ZZ.zero() return self._initial_coefficients[i] @@ -2123,10 +2123,9 @@ def compute_product(self, n, la): lf = [] for i, m, d in zip(wgt, exp, k): f = self.stretched_power_restrict_degree(i, m, rao * m + d) - if f: - lf.append(f) - else: + if not f: break + lf.append(f) else: ret += prod(lf) From 48563063eb26c02d5d7ff58eac307645d087b990 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Sun, 26 Mar 2023 11:38:51 +0200 Subject: [PATCH 7/8] remove obsolete TODO, remove useless lazy assignment --- src/sage/data_structures/stream.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index 8dfef6f1e51..04ba84d9701 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -2113,9 +2113,6 @@ def compute_product(self, n, la): wgt.reverse() exp.reverse() for k in wt_int_vec_iter(n - ret_approx_order, wgt): - # TODO: it may make a big difference here if the - # approximate order would be updated. - # prod does not short-cut zero, therefore # ret += prod(self.stretched_power_restrict_degree(i, m, rao * m + d) # for i, m, d in zip(wgt, exp, k)) @@ -2137,6 +2134,11 @@ def stretched_power_restrict_degree(self, i, m, d): Return the degree ``d*i`` part of ``p([i]*m)(g)`` in terms of ``self._basis``. + INPUT: + + - ``i``, ``m`` -- positive integers; + - ``d`` -- integer; + EXAMPLES:: sage: from sage.data_structures.stream import Stream_plethysm, Stream_exact, Stream_function, Stream_zero @@ -2170,13 +2172,16 @@ def stretched_power_restrict_degree(self, i, m, d): # we have to check power_d for zero because it might be an # integer and not a symmetric function if power_d: + # _raise_variables(c, i, self._degree_one) cannot vanish + # because i is positive and c is non-zero if self._tensor_power is None: - terms = {mon.stretch(i): raised_c for mon, c in power_d - if (raised_c := _raise_variables(c, i, self._degree_one))} # TODO: could this ever be 0? + terms = {mon.stretch(i): + _raise_variables(c, i, self._degree_one) + for mon, c in power_d} else: - terms = {tuple((mu.stretch(i) for mu in mon)): raised_c - for mon, c in power_d - if (raised_c := _raise_variables(c, i, self._degree_one))} # TODO: could this ever be 0? + terms = {tuple((mu.stretch(i) for mu in mon)): + _raise_variables(c, i, self._degree_one) + for mon, c in power_d} return self._basis(self._p.element_class(self._p, terms)) return self._basis.zero() From 1d523282ccb30613acbd60befd8a2c7c26aaa18f Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Tue, 22 Aug 2023 10:25:21 +0200 Subject: [PATCH 8/8] simplify Stream_plethysm.get_coefficient, remove trailing semicolon --- src/sage/data_structures/stream.py | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index dc83d6c7cc7..9b4b22d3177 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -2049,21 +2049,17 @@ def get_coefficient(self, n): if not n: # special case of 0 if self._right[0]: assert self._degree_f is not None, "the plethysm with a lazy symmetric function of valuation 0 is defined only for symmetric functions of finite support" + K = self._degree_f + else: + K = 1 + else: + K = n + 1 - return sum((c * self.compute_product(n, la) - for k in range(self._left._approximate_order, self._degree_f) - if self._left[k] # necessary, because it might be int(0) - for la, c in self._left[k]), - self._basis.zero()) - - # this also needs to be executed if not n and not - # self._right[0] - res = sum((c * self.compute_product(n, la) - for k in range(self._left._approximate_order, n+1) - if self._left[k] # necessary, because it might be int(0) - for la, c in self._left[k]), - self._basis.zero()) - return res + 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) + for la, c in self._left[k]), + self._basis.zero()) def compute_product(self, n, la): r""" @@ -2138,8 +2134,8 @@ def stretched_power_restrict_degree(self, i, m, d): INPUT: - - ``i``, ``m`` -- positive integers; - - ``d`` -- integer; + - ``i``, ``m`` -- positive integers + - ``d`` -- integer EXAMPLES::