From 55066e13a5da72641f1a9cc024744293d05e386b Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Sun, 10 Sep 2023 11:57:40 +0900 Subject: [PATCH 001/369] 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/369] 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/369] 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/369] 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/369] 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/369] 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/369] 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/369] 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/369] 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/369] 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/369] 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/369] 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/369] 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/369] 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/369] 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/369] 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/369] 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/369] 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/369] 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/369] 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/369] 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/369] 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/369] 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/369] 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/369] 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/369] 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/369] 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/369] 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/369] 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/369] 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/369] 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/369] 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/369] 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/369] 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/369] 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/369] 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/369] 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/369] 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 32bcda310ba8a6ef93c1950245ca19288f5a6c5b Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Sat, 27 Jan 2024 02:52:48 +0100 Subject: [PATCH 039/369] Implemented `.ramified_places` to extend quaternion algebra functionality to number fields - Implemented method `.ramified_places` for quaternion algebras over number fields. Integrated `.ramified_primes()` into it in the process - Rerouted `.discriminant()` through `.ramified_places` - Modified `.is_division_algebra()`, `.is_matrix_ring()` and `.is_isomorphic` to use `.ramified_places` instead of `.discriminant()`, extending them to base number fields - Added `.is_definite()` and `.is_totally_definite()` methods - Added Voight's book "Quaternion Algebras" to the list of references --- src/doc/en/reference/references/index.rst | 3 + .../algebras/quatalg/quaternion_algebra.py | 336 +++++++++++++++--- 2 files changed, 282 insertions(+), 57 deletions(-) diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index 1c075a474d6..4535380e922 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -6342,6 +6342,9 @@ REFERENCES: .. [Voi2012] \J. Voight. Identifying the matrix ring: algorithms for quaternion algebras and quadratic forms, to appear. +.. [Voi2021] \J. Voight. Quaternion Algebras. Graduate Texts in + Mathematics 288. Springer Cham, 2021. + .. [VS06] \G.D. Villa Salvador. Topics in the Theory of Algebraic Function Fields. Birkh\"auser, 2006. diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index 26b02b5d3e8..d479b69a4da 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -401,9 +401,8 @@ def is_commutative(self) -> bool: def is_division_algebra(self) -> bool: """ - Return ``True`` if the quaternion algebra is a division algebra (i.e. - every nonzero element in ``self`` is invertible), and ``False`` if the - quaternion algebra is isomorphic to the 2x2 matrix algebra. + Checks whether the quaternion algebra ``self`` is a division algebra, i.e. whether + every nonzero element in ``self`` is invertible. EXAMPLES:: @@ -411,40 +410,52 @@ def is_division_algebra(self) -> bool: True sage: QuaternionAlgebra(1).is_division_algebra() False - sage: QuaternionAlgebra(2,9).is_division_algebra() - False + + sage: K = QuadraticField(3) + sage: L = QuadraticField(-15) + sage: QuaternionAlgebra(K, -1, -1).is_division_algebra() + True + sage: QuaternionAlgebra(L, -1, -1).is_division_algebra() + True + sage: QuaternionAlgebra(RR(2.),1).is_division_algebra() Traceback (most recent call last): ... - NotImplementedError: base field must be rational numbers + NotImplementedError: base field must be rational numbers or a number field """ - if not is_RationalField(self.base_ring()): - raise NotImplementedError("base field must be rational numbers") - return self.discriminant() != 1 + try: + return self.ramified_places(inf=True) != ([], []) + except ValueError: + raise NotImplementedError("base field must be rational numbers or a number field") def is_matrix_ring(self) -> bool: """ - Return ``True`` if the quaternion algebra is isomorphic to the 2x2 - matrix ring, and ``False`` if ``self`` is a division algebra (i.e. - every nonzero element in ``self`` is invertible). + Checks whether the quaternion algebra ``self`` is isomorphic to the 2x2 matrix + ring over the base field. EXAMPLES:: sage: QuaternionAlgebra(QQ,-5,-2).is_matrix_ring() False - sage: QuaternionAlgebra(1).is_matrix_ring() - True sage: QuaternionAlgebra(2,9).is_matrix_ring() True + + sage: K = QuadraticField(3) + sage: L = QuadraticField(-15) + sage: QuaternionAlgebra(K, -1, -1).is_matrix_ring() + False + sage: QuaternionAlgebra(L, -1, -1).is_matrix_ring() + False + sage: QuaternionAlgebra(RR(2.),1).is_matrix_ring() Traceback (most recent call last): ... - NotImplementedError: base field must be rational numbers - + NotImplementedError: base field must be rational numbers or a number field """ - if not is_RationalField(self.base_ring()): - raise NotImplementedError("base field must be rational numbers") - return self.discriminant() == 1 + try: + return self.ramified_places(inf=True) == ([], []) + except ValueError: + raise NotImplementedError("base field must be rational numbers or a number field") def is_exact(self) -> bool: """ @@ -1029,86 +1040,297 @@ def inner_product_matrix(self): a, b = self._a, self._b return diagonal_matrix(self.base_ring(), [2, -2*a, -2*b, 2*a*b]) - @cached_method - def discriminant(self): + def is_definite(self): """ - Return the discriminant of this quaternion algebra, i.e. the product of the finite - primes it ramifies at. + Checks whether the quaternion algebra ``self`` is definite, i.e. whether it ramifies at the + unique Archimedean place of its base field QQ. This is the case if and only if both + invariants of ``self`` are negative, see [Voi2021, Exercise 2.4(c)]. EXAMPLES:: - sage: QuaternionAlgebra(210,-22).discriminant() - 210 - sage: QuaternionAlgebra(19).discriminant() - 19 + sage: QuaternionAlgebra(QQ,-5,-2).is_definite() + True + sage: QuaternionAlgebra(1).is_definite() + False + + sage: QuaternionAlgebra(RR(2.),1).is_definite() + Traceback (most recent call last): + ... + ValueError: base field must be rational numbers + """ + if not is_RationalField(self.base_ring()): + raise ValueError("base field must be rational numbers") + a, b = self.invariants() + return a < 0 and b < 0 + + def is_totally_definite(self): + """ + Checks whether the quaternion algebra ``self`` is totally definite, i.e. whether it ramifies + at all real Archimedean places of its base number field. + + EXAMPLES:: + + sage: QuaternionAlgebra(QQ, -5, -2).is_totally_definite() + True + + sage: K = QuadraticField(3) + sage: QuaternionAlgebra(K, -1, -1).is_totally_definite() + True + + sage: L = QuadraticField(-15) + sage: QuaternionAlgebra(L, -1, -1).is_totally_definite() + True sage: x = polygen(ZZ, 'x') sage: F. = NumberField(x^2 - x - 1) - sage: B. = QuaternionAlgebra(F, 2*a, F(-1)) - sage: B.discriminant() - Fractional ideal (2) + sage: QuaternionAlgebra(F, 2*a, F(-1)).is_totally_definite() + False - sage: QuaternionAlgebra(QQ[sqrt(2)], 3, 19).discriminant() # needs sage.symbolic - Fractional ideal (1) + sage: QuaternionAlgebra(RR(2.),1).is_definite() + Traceback (most recent call last): + ... + ValueError: base field must be rational numbers or a number field """ - if not is_RationalField(self.base_ring()): - try: - F = self.base_ring() - return F.hilbert_conductor(self._a, self._b) - except NotImplementedError: - raise ValueError("base field must be rational numbers or number field") - else: - return ZZ.prod(self.ramified_primes()) + F = self.base_ring() + if is_RationalField(F): + return self.is_definite() + + try: + E = F.real_embeddings() + return [e for e in E if F.hilbert_symbol(self._a, self._b, e) == -1] == E + except (AttributeError, NotImplementedError): + raise ValueError("base field must be rational numbers or a number field") + + @cached_method + def ramified_places(self, inf=True): + """ + Return the places of the base number field at which the quaternion algebra ``self`` ramifies. + + Note: The initial choice of primes (in the case F = QQ) respectively of prime ideals (in the + number field case) to check ramification for is motivated by [Voi2021, 12.4.12(a)]. The + restriction to real Archimedean embeddings is due to [Voi2021, 14.5.8]. + + INPUT: + + - ``inf`` -- (default: ``True``) + + OUTPUT: + + The non-Archimedean (AKA finite) places at which ``self`` ramifies (given as elements of ZZ if + ``self`` is defined over the rational field QQ, respectively as fractional ideals of the number + field's ring of integers, otherwise) and, if ``inf`` is set to ``True``, also the Archimedean + (AKA infinite) places at which ``self`` ramifies (given by real embeddings of the base field). + + EXAMPLES:: + + sage: QuaternionAlgebra(210,-22).ramified_places() + ([2, 3, 5, 7], []) + + sage: QuaternionAlgebra(-1, -1).ramified_places() + ([2], + [Ring morphism: + From: Rational Field + To: Real Field with 53 bits of precision + Defn: 1 |--> 1.00000000000000]) + + sage: K = QuadraticField(3) + sage: QuaternionAlgebra(K, -1, -1).ramified_places() + ([], + [Ring morphism: + From: Number Field in a with defining polynomial x^2 - 3 with a = 1.732050807568878? + To: Real Field with 53 bits of precision + Defn: a |--> -1.73205080756888, + Ring morphism: + From: Number Field in a with defining polynomial x^2 - 3 with a = 1.732050807568878? + To: Real Field with 53 bits of precision + Defn: a |--> 1.73205080756888]) + + sage: L = QuadraticField(-15) + sage: QuaternionAlgebra(L, -1, -1).ramified_places() + ([Fractional ideal (2, 1/2*a + 1/2), Fractional ideal (2, 1/2*a - 1/2)], []) + + sage: x = polygen(ZZ, 'x') + sage: F. = NumberField(x^2 - x - 1) + sage: QuaternionAlgebra(F, 2*a, F(-1)).ramified_places() + ([Fractional ideal (2)], + [Ring morphism: + From: Number Field in a with defining polynomial x^2 - x - 1 + To: Real Field with 53 bits of precision + Defn: a |--> -0.618033988749895]) + + sage: QuaternionAlgebra(QQ[sqrt(2)], 3, 19).ramified_places() # needs sage.symbolic + ([], []) + sage: QuaternionAlgebra(RR(2.),1).ramified_places() + Traceback (most recent call last) + ... + ValueError: base field must be rational numbers or a number field + """ + if not isinstace(inf, bool): + raise ValueError("inf must be a truth value") + + F = self.base_ring() + + # For efficiency (and to not convert QQ into a number field manually), we handle F = QQ first + if is_RationalField(F): + ram_fin = sorted([p for p in set([2]).union(prime_divisors(self._a.numerator()), + prime_divisors(self._a.denominator()), prime_divisors(self._b.numerator()), + prime_divisors(self._b.denominator())) if hilbert_symbol(self._a, self._b, p) == -1]) + + if not inf: + return ram_fin + + # The given quaternion algebra ramifies at the unique infinite place of QQ, by definition, + # if and only if it is definite + if self.is_definite(): + return ram_fin, QQ.places() + + return ram_fin, [] + + try: + # Over the number field F, first compute the finite ramified places + ram_fin = [p for p in set(F.primes_above(2)).union(F.primes_above(self._a), + F.primes_above(self._b)) if F.hilbert_symbol(self._a, self._b, p) == -1] + + if not inf: + return ram_fin + + # At this point the infinite ramified places also need to be computed + return ram_fin, [e for e in F.real_embeddings() if F.hilbert_symbol(self._a, self._b, e) == -1] + except (AttributeError, NotImplementedError): + raise ValueError("base field must be rational numbers or a number field") + @cached_method def ramified_primes(self): """ - Return the (finite) primes that ramify in this rational quaternion algebra. + Return the (finite) primes of the base number field at which the quaternion algebra ``self`` ramifies. OUTPUT: - The list of prime numbers at which ``self`` ramifies (given as integers), sorted by their - magnitude (small to large). + The list of finite primes at which ``self`` ramifies; given as integers, sorted + small-to-large, if ``self`` is defined over QQ, and as fractional ideals in the + ring of integers of the base number field otherwise. EXAMPLES:: - sage: QuaternionAlgebra(QQ, -1, -1).ramified_primes() + sage: QuaternionAlgebra(-58, -69).ramified_primes() + [3, 23, 29] + + sage: K = QuadraticField(3) + sage: L = QuadraticField(-15) + sage: QuaternionAlgebra(-1, -1).ramified_primes() [2] + sage: QuaternionAlgebra(K, -1, -1).ramified_primes() + [] + sage: QuaternionAlgebra(L, -1, -1).ramified_primes() + [Fractional ideal (2, 1/2*a + 1/2), Fractional ideal (2, 1/2*a - 1/2)] - sage: QuaternionAlgebra(QQ, -58, -69).ramified_primes() - [3, 23, 29] + sage: x = polygen(ZZ, 'x') + sage: F. = NumberField(x^2 - x - 1) + sage: QuaternionAlgebra(F, 2*a, F(-1)).ramified_primes() + [Fractional ideal (2)] + + sage: QuaternionAlgebra(RR(2.),1).ramified_primes() + Traceback (most recent call last) + ... + ValueError: base field must be rational numbers or a number field """ - if not is_RationalField(self.base_ring()): - raise ValueError("base field must be the rational numbers") + return self.ramified_places(inf=False) + + @cached_method + def discriminant(self): + """ + Return the discriminant of the quaternion algebra ``self``, i.e. the product of the + finite places it ramifies at. - return sorted([p for p in set([2]).union(prime_divisors(self._a.numerator()), - prime_divisors(self._a.denominator()), prime_divisors(self._b.numerator()), - prime_divisors(self._b.denominator())) if hilbert_symbol(self._a, self._b, p) == -1]) + OUTPUT: + + The discriminant of this quaternion algebra (which has to be defined over a number field), + as an element of ZZ if ``self`` is defined over QQ, and as a fractional ideal in the + ring of integers of the base number field otherwise. + + EXAMPLES:: + + sage: QuaternionAlgebra(210, -22).discriminant() + 210 + sage: QuaternionAlgebra(19).discriminant() + 19 + sage: QuaternionAlgebra(-1, -1).discriminant() + 2 + + sage: K = QuadraticField(3) + sage: L = QuadraticField(-15) + sage: QuaternionAlgebra(K, -1, -1).discriminant() + Fractional ideal (1) + sage: QuaternionAlgebra(L, -1, -1).discriminant() + Fractional ideal (2) + + sage: x = polygen(ZZ, 'x') + sage: F. = NumberField(x^2 - x - 1) + sage: QuaternionAlgebra(F, 2*a, F(-1)).discriminant() + Fractional ideal (2) + + sage: QuaternionAlgebra(QQ[sqrt(2)], 3, 19).discriminant() # needs sage.symbolic + Fractional ideal (1) + + sage: QuaternionAlgebra(RR(2.),1).discriminant() + Traceback (most recent call last) + ... + ValueError: base field must be rational numbers or a number field + """ + F = self.base_ring() + if is_RationalField(F): + return ZZ.prod(self.ramified_places(inf=False)) + + try: + return F.ideal(F.prod(self.ramified_places(inf=False))) + except NotImplementedError: + raise ValueError("base field must be rational numbers or a number field") def is_isomorphic(self, A) -> bool: """ - Return ``True`` if (and only if) ``self`` and ``A`` are isomorphic quaternion algebras over Q. + Checks whether ``self`` and ``A`` are isomorphic quaternion algebras. + + Currently only implemented over a number field; motivated by + [Voi2021, Main Theorem 14.6.1], noting that QQ has a unique infinite place. INPUT: - - ``A`` -- a quaternion algebra defined over the rationals Q + - ``A`` -- a quaternion algebra defined over a number field EXAMPLES:: sage: B = QuaternionAlgebra(-46, -87) sage: A = QuaternionAlgebra(-58, -69) + sage: A == B + False sage: B.is_isomorphic(A) True - sage: A == B + + sage: K = QuadraticField(3) + sage: A = QuaternionAlgebra(K, -1, -1) + sage: B = QuaternionAlgebra(K, 1, -1) + sage: A.discriminant() == B.discriminant() + True + sage: B.is_isomorphic(A) False """ if not isinstance(A, QuaternionAlgebra_ab): raise TypeError("A must be a quaternion algebra of the form (a,b)_K") - if self.base_ring() != QQ or A.base_ring() != QQ: - raise NotImplementedError("isomorphism check only implemented for rational quaternion algebras") + F = self.base_ring() + if F != A.base_ring(): + raise ValueError("both quaternion algebras must be defined over the same base ring") - return self.ramified_primes() == A.ramified_primes() + try: + if is_RationalField(F): + return self.ramified_places(inf=False) == A.ramified_places(inf=False) + + ram_self = self.ramified_places(inf=True) + ram_A = A.ramified_places(inf=True) + return set(ram_self[0]) == set(ram_A[0]) and ram_self[1] == ram_A[1] + except ValueError: + raise NotImplementedError("base field must be rational numbers or a number field") def _magma_init_(self, magma): """ From 97f1257b5d51bc4ea76c092ee66b240f39af2b32 Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Sat, 27 Jan 2024 03:55:59 +0100 Subject: [PATCH 040/369] Fixed lint issues and refernce formatting --- .../algebras/quatalg/quaternion_algebra.py | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index d479b69a4da..01c84d4e821 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -401,7 +401,7 @@ def is_commutative(self) -> bool: def is_division_algebra(self) -> bool: """ - Checks whether the quaternion algebra ``self`` is a division algebra, i.e. whether + Checks whether the quaternion algebra ``self`` is a division algebra, i.e. whether every nonzero element in ``self`` is invertible. EXAMPLES:: @@ -1044,7 +1044,7 @@ def is_definite(self): """ Checks whether the quaternion algebra ``self`` is definite, i.e. whether it ramifies at the unique Archimedean place of its base field QQ. This is the case if and only if both - invariants of ``self`` are negative, see [Voi2021, Exercise 2.4(c)]. + invariants of ``self`` are negative, see Exercise 2.4(c) in [Voi2021]_. EXAMPLES:: @@ -1107,8 +1107,8 @@ def ramified_places(self, inf=True): Return the places of the base number field at which the quaternion algebra ``self`` ramifies. Note: The initial choice of primes (in the case F = QQ) respectively of prime ideals (in the - number field case) to check ramification for is motivated by [Voi2021, 12.4.12(a)]. The - restriction to real Archimedean embeddings is due to [Voi2021, 14.5.8]. + number field case) to check ramification for is motivated by 12.4.12(a) in [Voi2021]_. The + restriction to real Archimedean embeddings is due to 14.5.8 in [Voi2021]_. INPUT: @@ -1116,9 +1116,9 @@ def ramified_places(self, inf=True): OUTPUT: - The non-Archimedean (AKA finite) places at which ``self`` ramifies (given as elements of ZZ if - ``self`` is defined over the rational field QQ, respectively as fractional ideals of the number - field's ring of integers, otherwise) and, if ``inf`` is set to ``True``, also the Archimedean + The non-Archimedean (AKA finite) places at which ``self`` ramifies (given as elements of ZZ if + ``self`` is defined over the rational field QQ, respectively as fractional ideals of the number + field's ring of integers, otherwise) and, if ``inf`` is set to ``True``, also the Archimedean (AKA infinite) places at which ``self`` ramifies (given by real embeddings of the base field). EXAMPLES:: @@ -1175,7 +1175,7 @@ def ramified_places(self, inf=True): ram_fin = sorted([p for p in set([2]).union(prime_divisors(self._a.numerator()), prime_divisors(self._a.denominator()), prime_divisors(self._b.numerator()), prime_divisors(self._b.denominator())) if hilbert_symbol(self._a, self._b, p) == -1]) - + if not inf: return ram_fin @@ -1199,7 +1199,6 @@ def ramified_places(self, inf=True): except (AttributeError, NotImplementedError): raise ValueError("base field must be rational numbers or a number field") - @cached_method def ramified_primes(self): """ @@ -1241,14 +1240,14 @@ def ramified_primes(self): def discriminant(self): """ Return the discriminant of the quaternion algebra ``self``, i.e. the product of the - finite places it ramifies at. + finite places it ramifies at. OUTPUT: The discriminant of this quaternion algebra (which has to be defined over a number field), as an element of ZZ if ``self`` is defined over QQ, and as a fractional ideal in the ring of integers of the base number field otherwise. - + EXAMPLES:: sage: QuaternionAlgebra(210, -22).discriminant() @@ -1261,7 +1260,7 @@ def discriminant(self): sage: K = QuadraticField(3) sage: L = QuadraticField(-15) sage: QuaternionAlgebra(K, -1, -1).discriminant() - Fractional ideal (1) + Fractional ideal (1) sage: QuaternionAlgebra(L, -1, -1).discriminant() Fractional ideal (2) @@ -1291,8 +1290,8 @@ def is_isomorphic(self, A) -> bool: """ Checks whether ``self`` and ``A`` are isomorphic quaternion algebras. - Currently only implemented over a number field; motivated by - [Voi2021, Main Theorem 14.6.1], noting that QQ has a unique infinite place. + Currently only implemented over a number field; motivated by Main Theorem 14.6.1 + in [Voi2021]_, noting that QQ has a unique infinite place. INPUT: From 0bb5cfe4539e6bb45dc84ac0c0a7cd52b97cafac Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Sun, 28 Jan 2024 00:16:14 +0100 Subject: [PATCH 041/369] Fixed typo and corrected/modified docstrings --- .../algebras/quatalg/quaternion_algebra.py | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index 01c84d4e821..06083f287e0 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -1086,7 +1086,7 @@ def is_totally_definite(self): sage: QuaternionAlgebra(F, 2*a, F(-1)).is_totally_definite() False - sage: QuaternionAlgebra(RR(2.),1).is_definite() + sage: QuaternionAlgebra(RR(2.),1).is_totally_definite() Traceback (most recent call last): ... ValueError: base field must be rational numbers or a number field @@ -1116,10 +1116,11 @@ def ramified_places(self, inf=True): OUTPUT: - The non-Archimedean (AKA finite) places at which ``self`` ramifies (given as elements of ZZ if - ``self`` is defined over the rational field QQ, respectively as fractional ideals of the number - field's ring of integers, otherwise) and, if ``inf`` is set to ``True``, also the Archimedean - (AKA infinite) places at which ``self`` ramifies (given by real embeddings of the base field). + The non-Archimedean (AKA finite) places at which ``self`` ramifies (given as elements of ZZ, + sorted small to large, if ``self`` is defined over the rational field QQ, respectively as + fractional ideals of the number field's ring of integers, otherwise) and, if ``inf`` is set + to ``True``, also the Archimedean (AKA infinite) places at which ``self`` ramifies (given + by real embeddings of the base field). EXAMPLES:: @@ -1161,11 +1162,11 @@ def ramified_places(self, inf=True): sage: QuaternionAlgebra(QQ[sqrt(2)], 3, 19).ramified_places() # needs sage.symbolic ([], []) sage: QuaternionAlgebra(RR(2.),1).ramified_places() - Traceback (most recent call last) + Traceback (most recent call last): ... ValueError: base field must be rational numbers or a number field """ - if not isinstace(inf, bool): + if not isinstance(inf, bool): raise ValueError("inf must be a truth value") F = self.base_ring() @@ -1207,7 +1208,7 @@ def ramified_primes(self): OUTPUT: The list of finite primes at which ``self`` ramifies; given as integers, sorted - small-to-large, if ``self`` is defined over QQ, and as fractional ideals in the + small to large, if ``self`` is defined over QQ, and as fractional ideals in the ring of integers of the base number field otherwise. EXAMPLES:: @@ -1230,7 +1231,7 @@ def ramified_primes(self): [Fractional ideal (2)] sage: QuaternionAlgebra(RR(2.),1).ramified_primes() - Traceback (most recent call last) + Traceback (most recent call last): ... ValueError: base field must be rational numbers or a number field """ @@ -1273,7 +1274,7 @@ def discriminant(self): Fractional ideal (1) sage: QuaternionAlgebra(RR(2.),1).discriminant() - Traceback (most recent call last) + Traceback (most recent call last): ... ValueError: base field must be rational numbers or a number field """ From a02472b3b5c66e9bba62fefbbaa0c638b8c8e842 Mon Sep 17 00:00:00 2001 From: ymusleh Date: Tue, 30 Jan 2024 12:58:00 -0500 Subject: [PATCH 042/369] Added pseudomorphism --- src/sage/modules/free_module.py | 12 ++ .../modules/free_module_pseudomorphism.py | 109 ++++++++++++++++++ 2 files changed, 121 insertions(+) create mode 100644 src/sage/modules/free_module_pseudomorphism.py diff --git a/src/sage/modules/free_module.py b/src/sage/modules/free_module.py index ffd6260764d..32243ee1ad8 100644 --- a/src/sage/modules/free_module.py +++ b/src/sage/modules/free_module.py @@ -3064,6 +3064,18 @@ def hom(self, im_gens, codomain=None, **kwds): codomain = R**n return super().hom(im_gens, codomain, **kwds) + def pseudohom(self, morphism, twist=None, **kwds): + r""" + Created a pseudomorphism defined by a given morphism and twist. + Let A be a ring and M a free module over A. Let \theta: A \to A + + """ + from sage.modules.free_module_pseudomorphism import FreeModulePseudoMorphism + from sage.structure.element import is_Matrix + if is_Matrix(morphism): + return FreeModulePseudoMorphism(self.hom(morphism), twist) + return FreeModulePseudoMorphism(morphism, twist) + def inner_product_matrix(self): """ Return the default identity inner product matrix associated to this diff --git a/src/sage/modules/free_module_pseudomorphism.py b/src/sage/modules/free_module_pseudomorphism.py new file mode 100644 index 00000000000..e489c40cff3 --- /dev/null +++ b/src/sage/modules/free_module_pseudomorphism.py @@ -0,0 +1,109 @@ +""" +Pseudomorphisms of free modules + +AUTHORS: + + +TESTS:: + + sage: V = ZZ^2; f = V.hom([V.1, -2*V.0]) +""" + +#################################################################################### +# Copyright (C) 2009 William Stein +# +# Distributed under the terms of the GNU General Public License (GPL) +# +# This code is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# The full text of the GPL is available at: +# +# http://www.gnu.org/licenses/ +#################################################################################### + +# A matrix morphism is a morphism that is defined by multiplication by a +# matrix. Elements of domain must either have a method "vector()" that +# returns a vector that the defining matrix can hit from the left, or +# be coercible into vector space of appropriate dimension. +import sage.modules.free_module as free_module +from sage.categories.morphism import Morphism +from sage.modules import free_module_homspace, matrix_morphism +from sage.structure.richcmp import rich_to_bool, richcmp +from sage.structure.sequence import Sequence +from sage.structure.all import parent +from sage.misc.lazy_import import lazy_import + +lazy_import('sage.rings.derivation', 'RingDerivation') + +class FreeModulePseudoMorphism(): + def __init__(self, morphism, twist=None, side="left"): + """ + INPUT: + + - ``parent`` - a homspace in a (sub) category of free modules + + - ``A`` - matrix + + - side -- side of the vectors acted on by the matrix (default: ``"left"``) + + EXAMPLES:: + + sage: V = ZZ^3; W = span([[1,2,3],[-1,2,8]], ZZ) + sage: phi = V.hom(matrix(ZZ,3,[1..9])) + sage: type(phi) + + """ + self.derivation = None + if isinstance(twist, Morphism): + self.twist_morphism = twist + elif isinstance(twist, RingDerivation): + self.twist_morphism = twist.parent().twisting_morphism() + if twist: + self.derivation = twist + else: + self.derivation = None + self.side = side + self.base_morphism = morphism + + def __call__(self, x): + if self.twist_morphism is None and self.derivation is None: + return self.base_morphism(x) + else: + try: + if parent(x) is not self.domain(): + x = self.domain()(x) + except TypeError: + raise TypeError("%s must be coercible into %s" % (x,self.domain())) + if self.domain().is_ambient(): + x = x.element() + else: + x = self.domain().coordinate_vector(x) + if self.twist_morphism is None: + x_twistmorphism = x + else: + x_twistmorphism = self.domain()(list(map(self.twist_morphism, x))) + C = self.codomain() + if self.side == "left": + v = x_twistmorphism * self.matrix() + else: + v = self.matrix() * x_twistmorphism + if self.derivation is not None: + v += self.domain()(list(map(self.derivation, x))) + if not C.is_ambient(): + v = C.linear_combination_of_basis(v) + return C._element_constructor_(v) + + def domain(self): + return self.base_morphism.domain() + + def codomain(self): + return self.base_morphism.codomain() + + def matrix(self): + return self.base_morphism.matrix() + + def base_morphism(self): + return self.base_morphism From e1232f15792bf339e6258e4f72b3c27c3cfa608f Mon Sep 17 00:00:00 2001 From: ymusleh Date: Wed, 31 Jan 2024 05:23:34 -0500 Subject: [PATCH 043/369] Adding pseudohomspace --- src/sage/modules/free_module.py | 12 ++-- .../modules/free_module_pseudohomspace.py | 61 +++++++++++++++++++ .../modules/free_module_pseudomorphism.py | 38 ++++++++++-- 3 files changed, 102 insertions(+), 9 deletions(-) create mode 100644 src/sage/modules/free_module_pseudohomspace.py diff --git a/src/sage/modules/free_module.py b/src/sage/modules/free_module.py index 32243ee1ad8..422e5b50394 100644 --- a/src/sage/modules/free_module.py +++ b/src/sage/modules/free_module.py @@ -212,6 +212,8 @@ richcmp_not_equal, ) from sage.structure.sequence import Sequence +from sage.categories.morphism import Morphism +from sage.matrix.constructor import matrix ############################################################################### # @@ -3064,7 +3066,11 @@ def hom(self, im_gens, codomain=None, **kwds): codomain = R**n return super().hom(im_gens, codomain, **kwds) - def pseudohom(self, morphism, twist=None, **kwds): + def PseudoHom(self, codomain=None, twist=None): + from sage.modules.free_module_pseudohomspace import FreeModulePseudoHomspace + return FreeModulePseudoHomspace(self, codomain, twist) + + def pseudohom(self, morphism, twist=None, codomain=None, **kwds): r""" Created a pseudomorphism defined by a given morphism and twist. Let A be a ring and M a free module over A. Let \theta: A \to A @@ -3072,9 +3078,7 @@ def pseudohom(self, morphism, twist=None, **kwds): """ from sage.modules.free_module_pseudomorphism import FreeModulePseudoMorphism from sage.structure.element import is_Matrix - if is_Matrix(morphism): - return FreeModulePseudoMorphism(self.hom(morphism), twist) - return FreeModulePseudoMorphism(morphism, twist) + return FreeModulePseudoMorphism(self, morphism, twist, codomain) def inner_product_matrix(self): """ diff --git a/src/sage/modules/free_module_pseudohomspace.py b/src/sage/modules/free_module_pseudohomspace.py new file mode 100644 index 00000000000..7728b942245 --- /dev/null +++ b/src/sage/modules/free_module_pseudohomspace.py @@ -0,0 +1,61 @@ + +# **************************************************************************** +# Copyright (C) 2005 William Stein +# +# Distributed under the terms of the GNU General Public License (GPL) +# +# This code is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty +# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# +# See the GNU General Public License for more details; the full text +# is available at: +# +# https://www.gnu.org/licenses/ +# **************************************************************************** +import sage.categories.homset +from sage.structure.element import is_Matrix +from sage.matrix.constructor import matrix, identity_matrix +from sage.matrix.matrix_space import MatrixSpace +from sage.misc.cachefunc import cached_method +from sage.categories.morphism import Morphism +from sage.misc.lazy_import import lazy_import + +lazy_import('sage.rings.derivation', 'RingDerivation') + +class FreeModulePseudoHomspace(sage.categories.homset.HomsetWithBase): + def __init__(self, X, Y, twist=None): + self._domain = X + self._codomain = X + if Y is not None: + self._codomain = Y + if (twist.domain() is not self.domain().coordinate_ring() + or twist.codomain() is not self.codomain().coordinate_ring()): + raise TypeError("twisting morphism domain/codomain do not match coordinate rings of the modules") + elif isinstance(twist, Morphism) or isinstance(twist, RingDerivation): + self.twist = twist + else: + raise TypeError("twist is not a ring morphism or derivation") + + def __call__(self, A, **kwds): + from . import free_module_pseudomorphism + side = kwds.get("side", "left") + if not is_Matrix(A): + C = self.codomain() + try: + if callable(A): + v = [C(A(g)) for g in self.domain().gens()] + A = matrix([C.coordinates(a) for a in v], ncols=C.rank()) + if side == "right": + A = A.transpose() + else: + v = [C(a) for a in A] + if side == "right": + A = matrix([C.coordinates(a) for a in v], ncols=C.rank()).transpose() + else: + A = matrix([C.coordinates(a) for a in v], ncols=C.rank()) + except TypeError: + pass + if not self.codomain().base_ring().has_coerce_map_from(self.domain().base_ring()) and not A.is_zero(): + raise TypeError("nontrivial morphisms require a coercion map from the base ring of the domain to the base ring of the codomain") + return free_module_pseudomorphism.FreeModulePseudoMorphism(self.domain(), A, twist=self.twist, codomain = self.codomain()) diff --git a/src/sage/modules/free_module_pseudomorphism.py b/src/sage/modules/free_module_pseudomorphism.py index e489c40cff3..eb86a86f83d 100644 --- a/src/sage/modules/free_module_pseudomorphism.py +++ b/src/sage/modules/free_module_pseudomorphism.py @@ -35,17 +35,23 @@ from sage.structure.sequence import Sequence from sage.structure.all import parent from sage.misc.lazy_import import lazy_import +from sage.modules.free_module_morphism import FreeModuleMorphism +from sage.modules.free_module_homspace import FreeModuleHomspace, is_FreeModuleHomspace +from sage.matrix.constructor import matrix lazy_import('sage.rings.derivation', 'RingDerivation') -class FreeModulePseudoMorphism(): - def __init__(self, morphism, twist=None, side="left"): +class FreeModulePseudoMorphism(Morphism): + def __init__(self, domain, base_morphism, twist=None, codomain=None, side="left"): """ INPUT: + - ``domain`` - the domain of the pseudomorphism; a free module - - ``parent`` - a homspace in a (sub) category of free modules + - ``base_morphism`` - either a morphism or a matrix defining a morphism - - ``A`` - matrix + - ``twist`` - a twisting morphism, this is either a morphism or a derivation (default: None) + + - ``codomain`` - the codomain of the pseudomorphism; a free module (default: None) - side -- side of the vectors acted on by the matrix (default: ``"left"``) @@ -56,7 +62,15 @@ def __init__(self, morphism, twist=None, side="left"): sage: type(phi) """ + from sage.structure.element import is_Matrix + if is_Matrix(base_morphism): + self.base_morphism = domain.hom(base_morphism, codomain) + elif isinstance(base_morphism, Morphism): + self.base_morphism = base_morphism + else: + self.base_morphism = domain.hom(matrix(domain.coordinate_ring(), base_morphism), codomain) self.derivation = None + self.twist_morphism = None if isinstance(twist, Morphism): self.twist_morphism = twist elif isinstance(twist, RingDerivation): @@ -66,7 +80,6 @@ def __init__(self, morphism, twist=None, side="left"): else: self.derivation = None self.side = side - self.base_morphism = morphism def __call__(self, x): if self.twist_morphism is None and self.derivation is None: @@ -96,6 +109,21 @@ def __call__(self, x): v = C.linear_combination_of_basis(v) return C._element_constructor_(v) + def __repr__(self): + r = "Free module pseudomorphism defined {}by the matrix\n{!r}{}{}\nDomain: {}\nCodomain: {}" + act = "" + if self.side == "right": + act = "as left-multiplication " + morph = "" + if self.twist_morphism is not None: + morph = "\nTwisted by the morphism {}" + morph = morph.format(self.twist_morphism.__repr__()) + deriv = "" + if self.derivation is not None: + deriv = "\nTwisted by the derivation {}" + deriv = deriv.format(self.derivation.__repr__()) + return r.format(act, self.matrix(), morph, deriv, self.domain(), self.codomain()) + def domain(self): return self.base_morphism.domain() From 90d836509d35355d183317b9de3983b1c4108fa0 Mon Sep 17 00:00:00 2001 From: ymusleh Date: Wed, 31 Jan 2024 16:00:01 -0500 Subject: [PATCH 044/369] Improve documentation for pseudomorphism --- src/sage/modules/free_module.py | 3 +- .../modules/free_module_pseudohomspace.py | 18 ++- .../modules/free_module_pseudomorphism.py | 110 +++++++++++++++--- 3 files changed, 112 insertions(+), 19 deletions(-) diff --git a/src/sage/modules/free_module.py b/src/sage/modules/free_module.py index 422e5b50394..2e4528abf5b 100644 --- a/src/sage/modules/free_module.py +++ b/src/sage/modules/free_module.py @@ -3078,7 +3078,8 @@ def pseudohom(self, morphism, twist=None, codomain=None, **kwds): """ from sage.modules.free_module_pseudomorphism import FreeModulePseudoMorphism from sage.structure.element import is_Matrix - return FreeModulePseudoMorphism(self, morphism, twist, codomain) + side = kwds.get("side", "left") + return FreeModulePseudoMorphism(self, morphism, twist, codomain, side) def inner_product_matrix(self): """ diff --git a/src/sage/modules/free_module_pseudohomspace.py b/src/sage/modules/free_module_pseudohomspace.py index 7728b942245..8e2682d3b65 100644 --- a/src/sage/modules/free_module_pseudohomspace.py +++ b/src/sage/modules/free_module_pseudohomspace.py @@ -1,6 +1,6 @@ # **************************************************************************** -# Copyright (C) 2005 William Stein +# Copyright (C) 2024 Yossef Musleh # # Distributed under the terms of the GNU General Public License (GPL) # @@ -24,6 +24,17 @@ lazy_import('sage.rings.derivation', 'RingDerivation') class FreeModulePseudoHomspace(sage.categories.homset.HomsetWithBase): + r""" + This class implements the space of Pseudomorphisms with a fixed twist. + + + + EXAMPLES:: + + sage: F = GF(25); M = F^2; twist = F.frobenius_endomorphism(5) + sage: PHS = F.PseudoHom(twist) + + """ def __init__(self, X, Y, twist=None): self._domain = X self._codomain = X @@ -58,4 +69,7 @@ def __call__(self, A, **kwds): pass if not self.codomain().base_ring().has_coerce_map_from(self.domain().base_ring()) and not A.is_zero(): raise TypeError("nontrivial morphisms require a coercion map from the base ring of the domain to the base ring of the codomain") - return free_module_pseudomorphism.FreeModulePseudoMorphism(self.domain(), A, twist=self.twist, codomain = self.codomain()) + return free_module_pseudomorphism.FreeModulePseudoMorphism(self.domain(), A, twist=self.twist, codomain = self.codomain()) + + def __repr__(self): + pass diff --git a/src/sage/modules/free_module_pseudomorphism.py b/src/sage/modules/free_module_pseudomorphism.py index eb86a86f83d..5664410c607 100644 --- a/src/sage/modules/free_module_pseudomorphism.py +++ b/src/sage/modules/free_module_pseudomorphism.py @@ -3,14 +3,54 @@ AUTHORS: + - Yossef Musleh (2024-02): initial version + +Let M be a free module over a ring R, and let $theta, delta: R to R$ +be a morphism and derivation of $R$ respectively such that + +$delta(xy) = theta(x)delta(y) + x$. + +Then a pseudomorphism from $f : M to M$ is a map such that + + $f(x + y) = f(x) + f(y)$ + $f(lambda x) = theta(lambda)f(x) + delta(lambda)x$ + +If $delta$ is the zero morphism, then we can relax the condition that the +codomain is M and consider a free module $M'$ over a ring $R'$. + TESTS:: - sage: V = ZZ^2; f = V.hom([V.1, -2*V.0]) + sage: V = ZZ^2 + sage: f = V.pseudohom([V.1, -2*V.0]); f + Free module pseudomorphism defined by the matrix + [ 0 1] + [-2 0] + Domain: Ambient free module of rank 2 over the principal ideal domain Integer Ring + Codomain: Ambient free module of rank 2 over the principal ideal domain Integer Ring + sage: f(V((1, 2))) + (-4, 1) + + sage: P. = ZZ[]; deriv = P.derivation() + sage: M = P^2 + sage: f = M.pseudohom([[1, 2*x], [x, 1]], deriv, side="right"); f + Free module pseudomorphism defined as left-multiplication by the matrix + [ 1 2*x] + [ x 1] + Twisted by the derivation d/dx + Domain: Ambient free module of rank 2 over the integral domain Univariate Polynomial Ring in x over Integer Ring + Codomain: Ambient free module of rank 2 over the integral domain Univariate Polynomial Ring in x over Integer Ring + sage: e = M((2*x^2 + 3*x + 1, x^3 + 7*x + 4)) + sage: f(e) + (2*x^4 + 16*x^2 + 15*x + 4, 3*x^3 + 6*x^2 + 8*x + 11) + sage: f = M.pseudohom([[1, 2], [1, 1]], deriv) + sage: f(e) + (x^3 + 2*x^2 + 14*x + 8, x^3 + 7*x^2 + 13*x + 13) + """ #################################################################################### -# Copyright (C) 2009 William Stein +# Copyright (C) 2024 Yossef Musleh # # Distributed under the terms of the GNU General Public License (GPL) # @@ -24,10 +64,6 @@ # http://www.gnu.org/licenses/ #################################################################################### -# A matrix morphism is a morphism that is defined by multiplication by a -# matrix. Elements of domain must either have a method "vector()" that -# returns a vector that the defining matrix can hit from the left, or -# be coercible into vector space of appropriate dimension. import sage.modules.free_module as free_module from sage.categories.morphism import Morphism from sage.modules import free_module_homspace, matrix_morphism @@ -44,6 +80,8 @@ class FreeModulePseudoMorphism(Morphism): def __init__(self, domain, base_morphism, twist=None, codomain=None, side="left"): """ + Constructs a pseudomorphism of free modules. + INPUT: - ``domain`` - the domain of the pseudomorphism; a free module @@ -57,10 +95,10 @@ def __init__(self, domain, base_morphism, twist=None, codomain=None, side="left" EXAMPLES:: - sage: V = ZZ^3; W = span([[1,2,3],[-1,2,8]], ZZ) - sage: phi = V.hom(matrix(ZZ,3,[1..9])) + sage: F = GF(25); V = F^3; twist = F.frobenius_endomorphism(5) + sage: phi = V.pseudohom(matrix(F,3,[1..9]), twist) sage: type(phi) - + """ from sage.structure.element import is_Matrix if is_Matrix(base_morphism): @@ -80,8 +118,20 @@ def __init__(self, domain, base_morphism, twist=None, codomain=None, side="left" else: self.derivation = None self.side = side - + def __call__(self, x): + r""" + Return the result of applying a pseudomorphism to an element of the + free module. + + TESTS:: + + sage: Fq = GF(25); M = Fq^2; frob = Fq.frobenius_endomorphism(5) + sage: ph = M.pseudohom([[1, 2], [0, 1]], frob, side="right") + sage: e = M((3*Fq.gen() + 2, 2*Fq.gen() + 2)) + sage: ph(e) + (z2 + 2, 1) + """ if self.twist_morphism is None and self.derivation is None: return self.base_morphism(x) else: @@ -94,15 +144,14 @@ def __call__(self, x): x = x.element() else: x = self.domain().coordinate_vector(x) - if self.twist_morphism is None: - x_twistmorphism = x - else: - x_twistmorphism = self.domain()(list(map(self.twist_morphism, x))) C = self.codomain() if self.side == "left": - v = x_twistmorphism * self.matrix() + v = x * self.matrix() else: - v = self.matrix() * x_twistmorphism + v = self.matrix() * x + if self.twist_morphism is not None: + for i in range(len(v)): + v[i] *= self.twist_morphism(x[i]) if self.derivation is not None: v += self.domain()(list(map(self.derivation, x))) if not C.is_ambient(): @@ -110,6 +159,11 @@ def __call__(self, x): return C._element_constructor_(v) def __repr__(self): + r""" + Return the string representation of a pseudomorphism. + + TESTS:: + """ r = "Free module pseudomorphism defined {}by the matrix\n{!r}{}{}\nDomain: {}\nCodomain: {}" act = "" if self.side == "right": @@ -125,13 +179,37 @@ def __repr__(self): return r.format(act, self.matrix(), morph, deriv, self.domain(), self.codomain()) def domain(self): + r""" + Return the domain of the pseudomorphism. + """ return self.base_morphism.domain() def codomain(self): + r""" + Return the codomain of the pseudomorphism. + """ return self.base_morphism.codomain() def matrix(self): + r""" + Return the underlying matrix of a pseudomorphism. + """ return self.base_morphism.matrix() def base_morphism(self): + r""" + Return the underlying morphism of a pseudomorphism. This is an element + of the Hom space of the free module. + """ return self.base_morphism + + def twisting_morphism(self): + r""" + Return the twisting homomorphism of the pseudomorphism. + """ + return self.twist_morphism + + def derivation(self): + r""" + """ + return self.derivation From eab0a12feea62822b9bc58bfc92918e65cc09aa2 Mon Sep 17 00:00:00 2001 From: ymusleh Date: Thu, 1 Feb 2024 03:27:21 -0500 Subject: [PATCH 045/369] Finishing documentation --- src/sage/modules/free_module.py | 8 +- .../modules/free_module_pseudohomspace.py | 75 +++++++++-- .../modules/free_module_pseudomorphism.py | 119 ++++++++++-------- 3 files changed, 135 insertions(+), 67 deletions(-) diff --git a/src/sage/modules/free_module.py b/src/sage/modules/free_module.py index 2e4528abf5b..d34fb91c6fc 100644 --- a/src/sage/modules/free_module.py +++ b/src/sage/modules/free_module.py @@ -3066,15 +3066,19 @@ def hom(self, im_gens, codomain=None, **kwds): codomain = R**n return super().hom(im_gens, codomain, **kwds) - def PseudoHom(self, codomain=None, twist=None): + def PseudoHom(self, twist=None, codomain=None): from sage.modules.free_module_pseudohomspace import FreeModulePseudoHomspace return FreeModulePseudoHomspace(self, codomain, twist) def pseudohom(self, morphism, twist=None, codomain=None, **kwds): r""" - Created a pseudomorphism defined by a given morphism and twist. + Create a pseudomorphism defined by a given morphism and twist. Let A be a ring and M a free module over A. Let \theta: A \to A + EXAMPLE:: + + sage: F = GF(25); M = F^2; twist = F.frobenius_endomorphism(5) + sage: """ from sage.modules.free_module_pseudomorphism import FreeModulePseudoMorphism from sage.structure.element import is_Matrix diff --git a/src/sage/modules/free_module_pseudohomspace.py b/src/sage/modules/free_module_pseudohomspace.py index 8e2682d3b65..09dd12934f7 100644 --- a/src/sage/modules/free_module_pseudohomspace.py +++ b/src/sage/modules/free_module_pseudohomspace.py @@ -27,12 +27,17 @@ class FreeModulePseudoHomspace(sage.categories.homset.HomsetWithBase): r""" This class implements the space of Pseudomorphisms with a fixed twist. - + For free modules, the elements of a pseudomorphism correspond to matrices + which define the mapping on elements of a basis. EXAMPLES:: - sage: F = GF(25); M = F^2; twist = F.frobenius_endomorphism(5) - sage: PHS = F.PseudoHom(twist) + sage: F = GF(125); M = F^2; twist = F.frobenius_endomorphism() + sage: PHS = M.PseudoHom(twist) + sage: h = PHS([[1, 2], [1, 1]]) + sage: e = M((4*F.gen()^2 + F.gen() + 2, 4*F.gen()^2 + 4*F.gen() + 4)) + sage: h(e) + (3*z3^2 + z3, 4*z3^2 + 3*z3 + 3) """ def __init__(self, X, Y, twist=None): @@ -40,15 +45,22 @@ def __init__(self, X, Y, twist=None): self._codomain = X if Y is not None: self._codomain = Y + self.base_homspace = self._domain.Hom(self._codomain) + if twist is None: + return if (twist.domain() is not self.domain().coordinate_ring() or twist.codomain() is not self.codomain().coordinate_ring()): - raise TypeError("twisting morphism domain/codomain do not match coordinate rings of the modules") + raise TypeError("twisting morphism domain/codomain do not match\ + coordinate rings of the modules") elif isinstance(twist, Morphism) or isinstance(twist, RingDerivation): self.twist = twist else: raise TypeError("twist is not a ring morphism or derivation") def __call__(self, A, **kwds): + r""" + Coerce a matrix or free module homomorphism into a pseudomorphism. + """ from . import free_module_pseudomorphism side = kwds.get("side", "left") if not is_Matrix(A): @@ -62,14 +74,57 @@ def __call__(self, A, **kwds): else: v = [C(a) for a in A] if side == "right": - A = matrix([C.coordinates(a) for a in v], ncols=C.rank()).transpose() + A = matrix([C.coordinates(a) for a in v], \ + ncols=C.rank()).transpose() else: - A = matrix([C.coordinates(a) for a in v], ncols=C.rank()) + A = matrix([C.coordinates(a) for a in v], \ + ncols=C.rank()) except TypeError: pass - if not self.codomain().base_ring().has_coerce_map_from(self.domain().base_ring()) and not A.is_zero(): - raise TypeError("nontrivial morphisms require a coercion map from the base ring of the domain to the base ring of the codomain") - return free_module_pseudomorphism.FreeModulePseudoMorphism(self.domain(), A, twist=self.twist, codomain = self.codomain()) + if not self.codomain().base_ring().has_coerce_map_from(\ + self.domain().base_ring()) and not A.is_zero(): + raise TypeError("nontrivial morphisms require a coercion map \ + from the base ring of the domain to the base ring of the \ + codomain") + return free_module_pseudomorphism.FreeModulePseudoMorphism(\ + self.domain(), A, twist=self.twist, \ + codomain = self.codomain()) def __repr__(self): - pass + r""" + Returns the string representation of the pseudomorphism space. + + EXAMPLE:: + + """ + r = "Set of Pseudomorphisms from {} to {} {} {}" + morph = "" + if self.twist_morphism is not None: + morph = "\nTwisted by the morphism {}" + morph = morph.format(self.twist_morphism.__repr__()) + deriv = "" + if self.derivation is not None: + deriv = "\nTwisted by the derivation {}" + deriv = deriv.format(self.derivation.__repr__()) + return r.format(self.domain(), self.codomain(), morph, deriv) + + def zero(self): + r""" + Return the zero pseudomorphism. + """ + return self.base_homspace.zero() + + def _matrix_space(self): + return self.base_homspace._matrix_space() + + def basis(self, side="left"): + r""" + Return a basis for the underlying matrix space. + """ + return self.base_homspace.basis(side) + + def identity(self): + r""" + Return the pseudomorphism corresponding to the identity transformation + """ + return self.base_homspace.identity() diff --git a/src/sage/modules/free_module_pseudomorphism.py b/src/sage/modules/free_module_pseudomorphism.py index 5664410c607..44145b2b247 100644 --- a/src/sage/modules/free_module_pseudomorphism.py +++ b/src/sage/modules/free_module_pseudomorphism.py @@ -5,48 +5,6 @@ - Yossef Musleh (2024-02): initial version -Let M be a free module over a ring R, and let $theta, delta: R to R$ -be a morphism and derivation of $R$ respectively such that - -$delta(xy) = theta(x)delta(y) + x$. - -Then a pseudomorphism from $f : M to M$ is a map such that - - $f(x + y) = f(x) + f(y)$ - $f(lambda x) = theta(lambda)f(x) + delta(lambda)x$ - -If $delta$ is the zero morphism, then we can relax the condition that the -codomain is M and consider a free module $M'$ over a ring $R'$. - - -TESTS:: - - sage: V = ZZ^2 - sage: f = V.pseudohom([V.1, -2*V.0]); f - Free module pseudomorphism defined by the matrix - [ 0 1] - [-2 0] - Domain: Ambient free module of rank 2 over the principal ideal domain Integer Ring - Codomain: Ambient free module of rank 2 over the principal ideal domain Integer Ring - sage: f(V((1, 2))) - (-4, 1) - - sage: P. = ZZ[]; deriv = P.derivation() - sage: M = P^2 - sage: f = M.pseudohom([[1, 2*x], [x, 1]], deriv, side="right"); f - Free module pseudomorphism defined as left-multiplication by the matrix - [ 1 2*x] - [ x 1] - Twisted by the derivation d/dx - Domain: Ambient free module of rank 2 over the integral domain Univariate Polynomial Ring in x over Integer Ring - Codomain: Ambient free module of rank 2 over the integral domain Univariate Polynomial Ring in x over Integer Ring - sage: e = M((2*x^2 + 3*x + 1, x^3 + 7*x + 4)) - sage: f(e) - (2*x^4 + 16*x^2 + 15*x + 4, 3*x^3 + 6*x^2 + 8*x + 11) - sage: f = M.pseudohom([[1, 2], [1, 1]], deriv) - sage: f(e) - (x^3 + 2*x^2 + 14*x + 8, x^3 + 7*x^2 + 13*x + 13) - """ #################################################################################### @@ -78,6 +36,49 @@ lazy_import('sage.rings.derivation', 'RingDerivation') class FreeModulePseudoMorphism(Morphism): + r""" + Let `M, M'` be free modules over a ring `R`, `\theta: R \to R` a ring + homomorphism, and `\theta: R \to R` a derivation i.e. an additive + map such that + + `\delta(xy) = x\delta(y) + \delta(x)y`. + + Then a pseudomorphism `f : M to M` is a map such that + + `f(x + y) = f(x) + f(y)` + `f(\lambda x) = `\theta(\lambda)f(x) + \delta(\lambda)x` + + The pair `(\theta, \delta)` may be referred to as the *twist* of + the morphism. + + TESTS:: + + sage: V = ZZ^2 + sage: f = V.pseudohom([V.1, -2*V.0]); f + Free module pseudomorphism defined by the matrix + [ 0 1] + [-2 0] + Domain: Ambient free module of rank 2 over the principal ideal domain Integer Ring + Codomain: Ambient free module of rank 2 over the principal ideal domain Integer Ring + sage: f(V((1, 2))) + (-4, 1) + + sage: P. = ZZ[]; deriv = P.derivation() + sage: M = P^2 + sage: f = M.pseudohom([[1, 2*x], [x, 1]], deriv, side="right"); f + Free module pseudomorphism defined as left-multiplication by the matrix + [ 1 2*x] + [ x 1] + twisted by the derivation d/dx + Domain: Ambient free module of rank 2 over the integral domain Univariate Polynomial Ring in x over Integer Ring + Codomain: Ambient free module of rank 2 over the integral domain Univariate Polynomial Ring in x over Integer Ring + sage: e = M((2*x^2 + 3*x + 1, x^3 + 7*x + 4)) + sage: f(e) + (2*x^4 + 16*x^2 + 15*x + 4, 3*x^3 + 6*x^2 + 8*x + 11) + sage: f = M.pseudohom([[1, 2], [1, 1]], deriv) + sage: f(e) + (x^3 + 2*x^2 + 14*x + 8, x^3 + 7*x^2 + 13*x + 13) + """ def __init__(self, domain, base_morphism, twist=None, codomain=None, side="left"): """ Constructs a pseudomorphism of free modules. @@ -85,13 +86,17 @@ def __init__(self, domain, base_morphism, twist=None, codomain=None, side="left" INPUT: - ``domain`` - the domain of the pseudomorphism; a free module - - ``base_morphism`` - either a morphism or a matrix defining a morphism + - ``base_morphism`` - either a morphism or a matrix defining a + morphism - - ``twist`` - a twisting morphism, this is either a morphism or a derivation (default: None) + - ``twist`` - a twisting morphism, this is either a morphism or + a derivation (default: None) - - ``codomain`` - the codomain of the pseudomorphism; a free module (default: None) + - ``codomain`` - the codomain of the pseudomorphism; a free + module (default: None) - - side -- side of the vectors acted on by the matrix (default: ``"left"``) + - side -- side of the vectors acted on by the matrix + (default: ``"left"``) EXAMPLES:: @@ -106,7 +111,8 @@ def __init__(self, domain, base_morphism, twist=None, codomain=None, side="left" elif isinstance(base_morphism, Morphism): self.base_morphism = base_morphism else: - self.base_morphism = domain.hom(matrix(domain.coordinate_ring(), base_morphism), codomain) + self.base_morphism = domain.hom(matrix(domain.coordinate_ring(), \ + base_morphism), codomain) self.derivation = None self.twist_morphism = None if isinstance(twist, Morphism): @@ -126,11 +132,11 @@ def __call__(self, x): TESTS:: - sage: Fq = GF(25); M = Fq^2; frob = Fq.frobenius_endomorphism(5) - sage: ph = M.pseudohom([[1, 2], [0, 1]], frob, side="right") - sage: e = M((3*Fq.gen() + 2, 2*Fq.gen() + 2)) + sage: Fq = GF(343); M = Fq^3; frob = Fq.frobenius_endomorphism() + sage: ph = M.pseudohom([[1, 2, 3], [0, 1, 1], [2, 1, 1]], frob, side="right") + sage: e = M((3*Fq.gen()^2 + 5*Fq.gen() + 2, 6*Fq.gen()^2 + 2*Fq.gen() + 2, Fq.gen() + 4)) sage: ph(e) - (z2 + 2, 1) + (3*z3 + 3, 2*z3^2, 5*z3^2 + 4) """ if self.twist_morphism is None and self.derivation is None: return self.base_morphism(x) @@ -164,19 +170,21 @@ def __repr__(self): TESTS:: """ - r = "Free module pseudomorphism defined {}by the matrix\n{!r}{}{}\nDomain: {}\nCodomain: {}" + r = "Free module pseudomorphism defined {}by the \ + matrix\n{!r}{}{}\nDomain: {}\nCodomain: {}" act = "" if self.side == "right": act = "as left-multiplication " morph = "" if self.twist_morphism is not None: - morph = "\nTwisted by the morphism {}" + morph = "\ntwisted by the morphism {}" morph = morph.format(self.twist_morphism.__repr__()) deriv = "" if self.derivation is not None: - deriv = "\nTwisted by the derivation {}" + deriv = "\ntwisted by the derivation {}" deriv = deriv.format(self.derivation.__repr__()) - return r.format(act, self.matrix(), morph, deriv, self.domain(), self.codomain()) + return r.format(act, self.matrix(), morph, deriv, \ + self.domain(), self.codomain()) def domain(self): r""" @@ -211,5 +219,6 @@ def twisting_morphism(self): def derivation(self): r""" + Return the twisting derivation of the pseudomorphism. """ return self.derivation From 0af5e3be1e8f21f815a5d7cad29ac4b57b9f0bc6 Mon Sep 17 00:00:00 2001 From: ymusleh Date: Thu, 1 Feb 2024 03:49:57 -0500 Subject: [PATCH 046/369] finishing docs --- .../modules/free_module_pseudohomspace.py | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/sage/modules/free_module_pseudohomspace.py b/src/sage/modules/free_module_pseudohomspace.py index 09dd12934f7..8b22700d892 100644 --- a/src/sage/modules/free_module_pseudohomspace.py +++ b/src/sage/modules/free_module_pseudohomspace.py @@ -60,6 +60,18 @@ def __init__(self, X, Y, twist=None): def __call__(self, A, **kwds): r""" Coerce a matrix or free module homomorphism into a pseudomorphism. + + EXAMPLES:: + + sage: F = GF(125); M = F^2; twist = F.frobenius_endomorphism() + sage: PHS = M.PseudoHom(twist) + sage: h = PHS([[1, 2], [1, 1]]); h + Free module pseudomorphism defined by the matrix + [1 2] + [1 1] + twisted by the morphism Frobenius endomorphism z3 |--> z3^5 on Finite Field in z3 of size 5^3 + Domain: Vector space of dimension 2 over Finite Field in z3 of size 5^3 + Codomain: Vector space of dimension 2 over Finite Field in z3 of size 5^3 """ from . import free_module_pseudomorphism side = kwds.get("side", "left") @@ -110,7 +122,15 @@ def __repr__(self): def zero(self): r""" - Return the zero pseudomorphism. + Return the zero pseudomorphism. This corresponds to the zero matrix. + + EXAMPLES:: + + sage: F = GF(125); M = F^2; twist = F.frobenius_endomorphism() + sage: PHS = M.PseudoHom(twist) + sage: PHS.zero().matrix() + [0 0] + [0 0] """ return self.base_homspace.zero() @@ -126,5 +146,13 @@ def basis(self, side="left"): def identity(self): r""" Return the pseudomorphism corresponding to the identity transformation + EXAMPLES:: + + sage: F = GF(125); M = F^2; twist = F.frobenius_endomorphism() + sage: PHS = M.PseudoHom(twist) + sage: PHS.zero().matrix() + [1 0] + [0 1] + """ return self.base_homspace.identity() From b15fa0590cf623285bb96cf9f91fd78e00ce3bc2 Mon Sep 17 00:00:00 2001 From: ymusleh Date: Thu, 1 Feb 2024 04:29:24 -0500 Subject: [PATCH 047/369] fixed pseudo action --- src/sage/modules/free_module.py | 9 +++++++-- src/sage/modules/free_module_pseudohomspace.py | 5 +++-- src/sage/modules/free_module_pseudomorphism.py | 13 +++++++------ 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/sage/modules/free_module.py b/src/sage/modules/free_module.py index d34fb91c6fc..0530fe2b0f4 100644 --- a/src/sage/modules/free_module.py +++ b/src/sage/modules/free_module.py @@ -3067,6 +3067,11 @@ def hom(self, im_gens, codomain=None, **kwds): return super().hom(im_gens, codomain, **kwds) def PseudoHom(self, twist=None, codomain=None): + r""" + Create the Pseudohomspace corresponding to given twist data. + + + """ from sage.modules.free_module_pseudohomspace import FreeModulePseudoHomspace return FreeModulePseudoHomspace(self, codomain, twist) @@ -3075,9 +3080,9 @@ def pseudohom(self, morphism, twist=None, codomain=None, **kwds): Create a pseudomorphism defined by a given morphism and twist. Let A be a ring and M a free module over A. Let \theta: A \to A - EXAMPLE:: + EXAMPLES:: - sage: F = GF(25); M = F^2; twist = F.frobenius_endomorphism(5) + sage: F = GF(25); M = F^2; twist = F.frobenius_endomorphism() sage: """ from sage.modules.free_module_pseudomorphism import FreeModulePseudoMorphism diff --git a/src/sage/modules/free_module_pseudohomspace.py b/src/sage/modules/free_module_pseudohomspace.py index 8b22700d892..f95aeb2fb9d 100644 --- a/src/sage/modules/free_module_pseudohomspace.py +++ b/src/sage/modules/free_module_pseudohomspace.py @@ -37,7 +37,7 @@ class FreeModulePseudoHomspace(sage.categories.homset.HomsetWithBase): sage: h = PHS([[1, 2], [1, 1]]) sage: e = M((4*F.gen()^2 + F.gen() + 2, 4*F.gen()^2 + 4*F.gen() + 4)) sage: h(e) - (3*z3^2 + z3, 4*z3^2 + 3*z3 + 3) + (z3, 2*z3^2 + 3*z3 + 3) """ def __init__(self, X, Y, twist=None): @@ -146,11 +146,12 @@ def basis(self, side="left"): def identity(self): r""" Return the pseudomorphism corresponding to the identity transformation + EXAMPLES:: sage: F = GF(125); M = F^2; twist = F.frobenius_endomorphism() sage: PHS = M.PseudoHom(twist) - sage: PHS.zero().matrix() + sage: PHS.identity().matrix() [1 0] [0 1] diff --git a/src/sage/modules/free_module_pseudomorphism.py b/src/sage/modules/free_module_pseudomorphism.py index 44145b2b247..d707dc44d6a 100644 --- a/src/sage/modules/free_module_pseudomorphism.py +++ b/src/sage/modules/free_module_pseudomorphism.py @@ -136,7 +136,7 @@ def __call__(self, x): sage: ph = M.pseudohom([[1, 2, 3], [0, 1, 1], [2, 1, 1]], frob, side="right") sage: e = M((3*Fq.gen()^2 + 5*Fq.gen() + 2, 6*Fq.gen()^2 + 2*Fq.gen() + 2, Fq.gen() + 4)) sage: ph(e) - (3*z3 + 3, 2*z3^2, 5*z3^2 + 4) + (2*z3^2 + 3*z3 + 2, z3^2 + 2*z3 + 1, 2*z3^2 + 4*z3) """ if self.twist_morphism is None and self.derivation is None: return self.base_morphism(x) @@ -151,13 +151,14 @@ def __call__(self, x): else: x = self.domain().coordinate_vector(x) C = self.codomain() + if self.twist_morphism is None: + x_twist = x + else: + x_twist = self.domain()(list(map(self.twist_morphism, x))) if self.side == "left": - v = x * self.matrix() + v = x_twist * self.matrix() else: - v = self.matrix() * x - if self.twist_morphism is not None: - for i in range(len(v)): - v[i] *= self.twist_morphism(x[i]) + v = self.matrix() * x_twist if self.derivation is not None: v += self.domain()(list(map(self.derivation, x))) if not C.is_ambient(): From 1dbb2b791748f77523810e158a91039f0f4e6501 Mon Sep 17 00:00:00 2001 From: ymusleh Date: Thu, 1 Feb 2024 08:41:25 -0500 Subject: [PATCH 048/369] Added examples for all pseudomorphism methods --- src/sage/modules/free_module.py | 20 ++- .../modules/free_module_pseudohomspace.py | 10 +- .../modules/free_module_pseudomorphism.py | 130 ++++++++++++------ 3 files changed, 109 insertions(+), 51 deletions(-) diff --git a/src/sage/modules/free_module.py b/src/sage/modules/free_module.py index 0530fe2b0f4..6fe6d516124 100644 --- a/src/sage/modules/free_module.py +++ b/src/sage/modules/free_module.py @@ -3068,12 +3068,18 @@ def hom(self, im_gens, codomain=None, **kwds): def PseudoHom(self, twist=None, codomain=None): r""" - Create the Pseudohomspace corresponding to given twist data. + Create the Pseudo Hom space corresponding to given twist data. + EXAMPLES:: + + sage: F = GF(25); M = F^2; twist = F.frobenius_endomorphism() + sage: PHS = M.PseudoHom(twist); PHS + Set of Pseudomorphisms from Vector space of dimension 2 over Finite Field in z2 of size 5^2 to Vector space of dimension 2 over Finite Field in z2 of size 5^2 + Twisted by the morphism Frobenius endomorphism z2 |--> z2^5 on Finite Field in z2 of size 5^2 """ from sage.modules.free_module_pseudohomspace import FreeModulePseudoHomspace - return FreeModulePseudoHomspace(self, codomain, twist) + return FreeModulePseudoHomspace(self, codomain, twist=twist) def pseudohom(self, morphism, twist=None, codomain=None, **kwds): r""" @@ -3082,8 +3088,14 @@ def pseudohom(self, morphism, twist=None, codomain=None, **kwds): EXAMPLES:: - sage: F = GF(25); M = F^2; twist = F.frobenius_endomorphism() - sage: + sage: F = GF(25); M = F^2; twist = F.frobenius_endomorphism() + sage: ph = M.pseudohom([[1, 2], [0, 1]], twist, side="right"); ph + Free module pseudomorphism defined as left-multiplication by the matrix + [1 2] + [0 1] + twisted by the morphism Frobenius endomorphism z2 |--> z2^5 on Finite Field in z2 of size 5^2 + Domain: Vector space of dimension 2 over Finite Field in z2 of size 5^2 + Codomain: Vector space of dimension 2 over Finite Field in z2 of size 5^2 """ from sage.modules.free_module_pseudomorphism import FreeModulePseudoMorphism from sage.structure.element import is_Matrix diff --git a/src/sage/modules/free_module_pseudohomspace.py b/src/sage/modules/free_module_pseudohomspace.py index f95aeb2fb9d..790b8002d75 100644 --- a/src/sage/modules/free_module_pseudohomspace.py +++ b/src/sage/modules/free_module_pseudohomspace.py @@ -45,15 +45,21 @@ def __init__(self, X, Y, twist=None): self._codomain = X if Y is not None: self._codomain = Y + super().__init__(self._domain, self._codomain, category=None) self.base_homspace = self._domain.Hom(self._codomain) + self.twist = twist + self.twist_morphism = None + self.derivation = None if twist is None: return if (twist.domain() is not self.domain().coordinate_ring() or twist.codomain() is not self.codomain().coordinate_ring()): raise TypeError("twisting morphism domain/codomain do not match\ coordinate rings of the modules") - elif isinstance(twist, Morphism) or isinstance(twist, RingDerivation): - self.twist = twist + elif isinstance(twist, Morphism): + self.twist_morphism = twist + elif isinstance(twist, RingDerivation): + self.derivation = twist else: raise TypeError("twist is not a ring morphism or derivation") diff --git a/src/sage/modules/free_module_pseudomorphism.py b/src/sage/modules/free_module_pseudomorphism.py index d707dc44d6a..8055867d0cb 100644 --- a/src/sage/modules/free_module_pseudomorphism.py +++ b/src/sage/modules/free_module_pseudomorphism.py @@ -106,6 +106,7 @@ def __init__(self, domain, base_morphism, twist=None, codomain=None, side="left" """ from sage.structure.element import is_Matrix + Morphism.__init__(self, domain.PseudoHom(twist, codomain)) if is_Matrix(base_morphism): self.base_morphism = domain.hom(base_morphism, codomain) elif isinstance(base_morphism, Morphism): @@ -125,54 +126,58 @@ def __init__(self, domain, base_morphism, twist=None, codomain=None, side="left" self.derivation = None self.side = side - def __call__(self, x): + def _call_(self, x): r""" Return the result of applying a pseudomorphism to an element of the free module. TESTS:: - sage: Fq = GF(343); M = Fq^3; frob = Fq.frobenius_endomorphism() - sage: ph = M.pseudohom([[1, 2, 3], [0, 1, 1], [2, 1, 1]], frob, side="right") - sage: e = M((3*Fq.gen()^2 + 5*Fq.gen() + 2, 6*Fq.gen()^2 + 2*Fq.gen() + 2, Fq.gen() + 4)) - sage: ph(e) - (2*z3^2 + 3*z3 + 2, z3^2 + 2*z3 + 1, 2*z3^2 + 4*z3) + sage: Fq = GF(343); M = Fq^3; frob = Fq.frobenius_endomorphism() + sage: ph = M.pseudohom([[1, Fq.gen(), 3], [0, 1, 1], [2, 1, 1]], frob, side="right") + sage: e = M((3*Fq.gen()^2 + 5*Fq.gen() + 2, 6*Fq.gen()^2 + 2*Fq.gen() + 2, Fq.gen() + 4)) + sage: ph(e) + (z3^2 + 6*z3 + 2, z3^2 + 2*z3 + 1, 2*z3^2 + 4*z3) """ if self.twist_morphism is None and self.derivation is None: return self.base_morphism(x) + if self.domain().is_ambient(): + x = x.element() else: - try: - if parent(x) is not self.domain(): - x = self.domain()(x) - except TypeError: - raise TypeError("%s must be coercible into %s" % (x,self.domain())) - if self.domain().is_ambient(): - x = x.element() - else: - x = self.domain().coordinate_vector(x) - C = self.codomain() - if self.twist_morphism is None: - x_twist = x - else: - x_twist = self.domain()(list(map(self.twist_morphism, x))) - if self.side == "left": - v = x_twist * self.matrix() - else: - v = self.matrix() * x_twist - if self.derivation is not None: - v += self.domain()(list(map(self.derivation, x))) - if not C.is_ambient(): - v = C.linear_combination_of_basis(v) - return C._element_constructor_(v) + x = self.domain().coordinate_vector(x) + C = self.codomain() + if self.twist_morphism is None: + x_twist = x + else: + x_twist = self.domain()(list(map(self.twist_morphism, x))) + if self.side == "left": + v = x_twist * self.matrix() + else: + v = self.matrix() * x_twist + if self.derivation is not None: + v += self.domain()(list(map(self.derivation, x))) + if not C.is_ambient(): + v = C.linear_combination_of_basis(v) + return C._element_constructor_(v) def __repr__(self): r""" Return the string representation of a pseudomorphism. TESTS:: + + sage: Fq = GF(343); M = Fq^3; frob = Fq.frobenius_endomorphism() + sage: ph = M.pseudohom([[1,1,1],[2,2,2],[3,3,3]], frob); ph + Free module pseudomorphism defined by the matrix + [1 1 1] + [2 2 2] + [3 3 3] + twisted by the morphism Frobenius endomorphism z3 |--> z3^7 on Finite Field in z3 of size 7^3 + Domain: Vector space of dimension 3 over Finite Field in z3 of size 7^3 + Codomain: Vector space of dimension 3 over Finite Field in z3 of size 7^3 """ - r = "Free module pseudomorphism defined {}by the \ - matrix\n{!r}{}{}\nDomain: {}\nCodomain: {}" + r = "Free module pseudomorphism defined {}by the "\ + "matrix\n{!r}{}{}\nDomain: {}\nCodomain: {}" act = "" if self.side == "right": act = "as left-multiplication " @@ -187,39 +192,74 @@ def __repr__(self): return r.format(act, self.matrix(), morph, deriv, \ self.domain(), self.codomain()) - def domain(self): - r""" - Return the domain of the pseudomorphism. - """ - return self.base_morphism.domain() - - def codomain(self): - r""" - Return the codomain of the pseudomorphism. - """ - return self.base_morphism.codomain() - def matrix(self): r""" Return the underlying matrix of a pseudomorphism. + + If a pseudomorphism `f` on free module `M` has matrix m acting on + the left on elements `v \in M`, with twisting morphism `\theta`. + Then we have + + `f(v) = m*\theta(v)` + + where `\theta` acts of the coefficients of `v` in terms of the basis + for `m`. + + EXAMPLES:: + + sage: Fq = GF(343); M = Fq^3; frob = Fq.frobenius_endomorphism() + sage: ph = M.pseudohom([[1, 2, 3], [0, 1, 1], [2, 1, 1]], frob, side="right") + sage: e = M((3*Fq.gen()^2 + 5*Fq.gen() + 2, 6*Fq.gen()^2 + 2*Fq.gen() + 2, Fq.gen() + 4)) + sage: ph.matrix() + [1 2 3] + [0 1 1] + [2 1 1] + sage: ph(e) == ph.matrix()*vector([frob(c) for c in e]) + True """ return self.base_morphism.matrix() - def base_morphism(self): + def _base_morphism(self): r""" Return the underlying morphism of a pseudomorphism. This is an element of the Hom space of the free module. + + EXAMPLES:: + + sage: Fq = GF(343); M = Fq^3; frob = Fq.frobenius_endomorphism() + sage: ph = M.pseudohom([[1, 2, 3], [0, 1, 1], [2, 1, 1]], frob, side="right") + sage: ph._base_morphism() + Vector space morphism represented by the matrix: + [1 2 3] + [0 1 1] + [2 1 1] + Domain: Vector space of dimension 3 over Finite Field in z3 of size 7^3 + Codomain: Vector space of dimension 3 over Finite Field in z3 of size 7^3 """ return self.base_morphism def twisting_morphism(self): r""" Return the twisting homomorphism of the pseudomorphism. + + EXAMPLES:: + + sage: Fq = GF(343); M = Fq^3; frob = Fq.frobenius_endomorphism() + sage: ph = M.pseudohom([[1, 2, 3], [0, 1, 1], [2, 1, 1]], frob, side="right") + sage: ph.twisting_morphism() + Frobenius endomorphism z3 |--> z3^7 on Finite Field in z3 of size 7^3 """ return self.twist_morphism - def derivation(self): + def twisting_derivation(self): r""" Return the twisting derivation of the pseudomorphism. + + EXAMPLES:: + + sage: P. = ZZ[]; deriv = P.derivation(); M = P^2 + sage: f = M.pseudohom([[1, 2*x], [x, 1]], deriv, side="right") + sage: f.twisting_derivation() + d/dx """ return self.derivation From 120d7a119d73c1a552e89c79b549700ce7472beb Mon Sep 17 00:00:00 2001 From: ymusleh Date: Thu, 1 Feb 2024 09:10:52 -0500 Subject: [PATCH 049/369] Completed documentation for pseudomorphism and pseudohomspace --- src/sage/modules/free_module.py | 2 +- .../modules/free_module_pseudohomspace.py | 50 ++++++++++++++++++- .../modules/free_module_pseudomorphism.py | 1 - 3 files changed, 49 insertions(+), 4 deletions(-) diff --git a/src/sage/modules/free_module.py b/src/sage/modules/free_module.py index 6fe6d516124..ece22120141 100644 --- a/src/sage/modules/free_module.py +++ b/src/sage/modules/free_module.py @@ -3071,7 +3071,7 @@ def PseudoHom(self, twist=None, codomain=None): Create the Pseudo Hom space corresponding to given twist data. EXAMPLES:: - + sage: F = GF(25); M = F^2; twist = F.frobenius_endomorphism() sage: PHS = M.PseudoHom(twist); PHS Set of Pseudomorphisms from Vector space of dimension 2 over Finite Field in z2 of size 5^2 to Vector space of dimension 2 over Finite Field in z2 of size 5^2 diff --git a/src/sage/modules/free_module_pseudohomspace.py b/src/sage/modules/free_module_pseudohomspace.py index 790b8002d75..ecd644f642e 100644 --- a/src/sage/modules/free_module_pseudohomspace.py +++ b/src/sage/modules/free_module_pseudohomspace.py @@ -1,4 +1,11 @@ +""" +Space of Pseudomorphisms of free modules +AUTHORS: + + - Yossef Musleh (2024-02): initial version + +""" # **************************************************************************** # Copyright (C) 2024 Yossef Musleh # @@ -72,7 +79,7 @@ def __call__(self, A, **kwds): sage: F = GF(125); M = F^2; twist = F.frobenius_endomorphism() sage: PHS = M.PseudoHom(twist) sage: h = PHS([[1, 2], [1, 1]]); h - Free module pseudomorphism defined by the matrix + Free module pseudomorphism defined by the matrix [1 2] [1 1] twisted by the morphism Frobenius endomorphism z3 |--> z3^5 on Finite Field in z3 of size 5^3 @@ -114,6 +121,10 @@ def __repr__(self): EXAMPLE:: + sage: Fq = GF(343); M = Fq^2; frob = Fq.frobenius_endomorphism() + sage: PHS = M.PseudoHom(frob); PHS + Set of Pseudomorphisms from Vector space of dimension 2 over Finite Field in z3 of size 7^3 to Vector space of dimension 2 over Finite Field in z3 of size 7^3 + Twisted by the morphism Frobenius endomorphism z3 |--> z3^7 on Finite Field in z3 of size 7^3 """ r = "Set of Pseudomorphisms from {} to {} {} {}" morph = "" @@ -141,11 +152,47 @@ def zero(self): return self.base_homspace.zero() def _matrix_space(self): + r""" + Return the full matrix space of the underlying morphisms. + + EXAMPLES:: + + sage: Fq = GF(343); M = Fq^2; frob = Fq.frobenius_endomorphism() + sage: PHS = M.PseudoHom(frob) + sage: PHS._matrix_space() + Full MatrixSpace of 2 by 2 dense matrices over Finite Field in z3 of size 7^3 + """ return self.base_homspace._matrix_space() def basis(self, side="left"): r""" Return a basis for the underlying matrix space. + + EXAMPLES:: + + sage: Fq = GF(343); M = Fq^2; frob = Fq.frobenius_endomorphism() + sage: PHS = M.PseudoHom(frob) + sage: PHS.basis() + (Vector space morphism represented by the matrix: + [1 0] + [0 0] + Domain: Vector space of dimension 2 over Finite Field in z3 of size 7^3 + Codomain: Vector space of dimension 2 over Finite Field in z3 of size 7^3, + Vector space morphism represented by the matrix: + [0 1] + [0 0] + Domain: Vector space of dimension 2 over Finite Field in z3 of size 7^3 + Codomain: Vector space of dimension 2 over Finite Field in z3 of size 7^3, + Vector space morphism represented by the matrix: + [0 0] + [1 0] + Domain: Vector space of dimension 2 over Finite Field in z3 of size 7^3 + Codomain: Vector space of dimension 2 over Finite Field in z3 of size 7^3, + Vector space morphism represented by the matrix: + [0 0] + [0 1] + Domain: Vector space of dimension 2 over Finite Field in z3 of size 7^3 + Codomain: Vector space of dimension 2 over Finite Field in z3 of size 7^3) """ return self.base_homspace.basis(side) @@ -160,6 +207,5 @@ def identity(self): sage: PHS.identity().matrix() [1 0] [0 1] - """ return self.base_homspace.identity() diff --git a/src/sage/modules/free_module_pseudomorphism.py b/src/sage/modules/free_module_pseudomorphism.py index 8055867d0cb..00c56e21e1f 100644 --- a/src/sage/modules/free_module_pseudomorphism.py +++ b/src/sage/modules/free_module_pseudomorphism.py @@ -6,7 +6,6 @@ - Yossef Musleh (2024-02): initial version """ - #################################################################################### # Copyright (C) 2024 Yossef Musleh # From 957e414dcc863935245007fafa2f4b97ecaa6127 Mon Sep 17 00:00:00 2001 From: ymusleh Date: Thu, 1 Feb 2024 12:19:17 -0500 Subject: [PATCH 050/369] Wrap identity/zero morphism --- src/sage/modules/free_module_pseudohomspace.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/sage/modules/free_module_pseudohomspace.py b/src/sage/modules/free_module_pseudohomspace.py index ecd644f642e..ffca84bafbb 100644 --- a/src/sage/modules/free_module_pseudohomspace.py +++ b/src/sage/modules/free_module_pseudohomspace.py @@ -145,11 +145,15 @@ def zero(self): sage: F = GF(125); M = F^2; twist = F.frobenius_endomorphism() sage: PHS = M.PseudoHom(twist) - sage: PHS.zero().matrix() + sage: PHS.zero() + Free module pseudomorphism defined by the matrix [0 0] [0 0] + twisted by the morphism Frobenius endomorphism z3 |--> z3^5 on Finite Field in z3 of size 5^3 + Domain: Vector space of dimension 2 over Finite Field in z3 of size 5^3 + Codomain: Vector space of dimension 2 over Finite Field in z3 of size 5^3 """ - return self.base_homspace.zero() + return self(self.base_homspace.zero()) def _matrix_space(self): r""" @@ -204,8 +208,12 @@ def identity(self): sage: F = GF(125); M = F^2; twist = F.frobenius_endomorphism() sage: PHS = M.PseudoHom(twist) - sage: PHS.identity().matrix() + sage: PHS.identity() + Free module pseudomorphism defined by the matrix [1 0] [0 1] + twisted by the morphism Frobenius endomorphism z3 |--> z3^5 on Finite Field in z3 of size 5^3 + Domain: Vector space of dimension 2 over Finite Field in z3 of size 5^3 + Codomain: Vector space of dimension 2 over Finite Field in z3 of size 5^3 """ - return self.base_homspace.identity() + return self(self.base_homspace.identity()) From 051b0f3acd18b929c33620b8d7672445c87a35a5 Mon Sep 17 00:00:00 2001 From: ymusleh Date: Thu, 1 Feb 2024 12:27:45 -0500 Subject: [PATCH 051/369] Removed intermediate morphism --- .../modules/free_module_pseudomorphism.py | 35 ++++--------------- 1 file changed, 7 insertions(+), 28 deletions(-) diff --git a/src/sage/modules/free_module_pseudomorphism.py b/src/sage/modules/free_module_pseudomorphism.py index 00c56e21e1f..de442ff670f 100644 --- a/src/sage/modules/free_module_pseudomorphism.py +++ b/src/sage/modules/free_module_pseudomorphism.py @@ -107,12 +107,12 @@ def __init__(self, domain, base_morphism, twist=None, codomain=None, side="left" from sage.structure.element import is_Matrix Morphism.__init__(self, domain.PseudoHom(twist, codomain)) if is_Matrix(base_morphism): - self.base_morphism = domain.hom(base_morphism, codomain) + self._base_matrix = base_morphism elif isinstance(base_morphism, Morphism): - self.base_morphism = base_morphism + self._base_matrix = base_morphism.matrix() else: - self.base_morphism = domain.hom(matrix(domain.coordinate_ring(), \ - base_morphism), codomain) + self._base_matrix = matrix(domain.coordinate_ring(), \ + base_morphism) self.derivation = None self.twist_morphism = None if isinstance(twist, Morphism): @@ -138,8 +138,6 @@ def _call_(self, x): sage: ph(e) (z3^2 + 6*z3 + 2, z3^2 + 2*z3 + 1, 2*z3^2 + 4*z3) """ - if self.twist_morphism is None and self.derivation is None: - return self.base_morphism(x) if self.domain().is_ambient(): x = x.element() else: @@ -150,9 +148,9 @@ def _call_(self, x): else: x_twist = self.domain()(list(map(self.twist_morphism, x))) if self.side == "left": - v = x_twist * self.matrix() + v = x_twist * self._base_matrix else: - v = self.matrix() * x_twist + v = self._base_matrix * x_twist if self.derivation is not None: v += self.domain()(list(map(self.derivation, x))) if not C.is_ambient(): @@ -216,26 +214,7 @@ def matrix(self): sage: ph(e) == ph.matrix()*vector([frob(c) for c in e]) True """ - return self.base_morphism.matrix() - - def _base_morphism(self): - r""" - Return the underlying morphism of a pseudomorphism. This is an element - of the Hom space of the free module. - - EXAMPLES:: - - sage: Fq = GF(343); M = Fq^3; frob = Fq.frobenius_endomorphism() - sage: ph = M.pseudohom([[1, 2, 3], [0, 1, 1], [2, 1, 1]], frob, side="right") - sage: ph._base_morphism() - Vector space morphism represented by the matrix: - [1 2 3] - [0 1 1] - [2 1 1] - Domain: Vector space of dimension 3 over Finite Field in z3 of size 7^3 - Codomain: Vector space of dimension 3 over Finite Field in z3 of size 7^3 - """ - return self.base_morphism + return self._base_matrix def twisting_morphism(self): r""" From 11dd611bdc5645d76a7dea37d84c84a37c503cd1 Mon Sep 17 00:00:00 2001 From: ymusleh Date: Thu, 1 Feb 2024 12:43:01 -0500 Subject: [PATCH 052/369] Improve documentation --- .../modules/free_module_pseudohomspace.py | 20 +++++++++---------- .../modules/free_module_pseudomorphism.py | 10 +++++----- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/sage/modules/free_module_pseudohomspace.py b/src/sage/modules/free_module_pseudohomspace.py index ffca84bafbb..f696be346f6 100644 --- a/src/sage/modules/free_module_pseudohomspace.py +++ b/src/sage/modules/free_module_pseudohomspace.py @@ -99,27 +99,27 @@ def __call__(self, A, **kwds): else: v = [C(a) for a in A] if side == "right": - A = matrix([C.coordinates(a) for a in v], \ + A = matrix([C.coordinates(a) for a in v], ncols=C.rank()).transpose() else: - A = matrix([C.coordinates(a) for a in v], \ + A = matrix([C.coordinates(a) for a in v], ncols=C.rank()) except TypeError: pass - if not self.codomain().base_ring().has_coerce_map_from(\ + if not self.codomain().base_ring().has_coerce_map_from( self.domain().base_ring()) and not A.is_zero(): - raise TypeError("nontrivial morphisms require a coercion map \ - from the base ring of the domain to the base ring of the \ - codomain") - return free_module_pseudomorphism.FreeModulePseudoMorphism(\ - self.domain(), A, twist=self.twist, \ - codomain = self.codomain()) + raise TypeError("nontrivial morphisms require a coercion map" + "from the base ring of the domain to the base ring of the" + "codomain") + return free_module_pseudomorphism.FreeModulePseudoMorphism( + self.domain(), A, twist=self.twist, + codomain=self.codomain()) def __repr__(self): r""" Returns the string representation of the pseudomorphism space. - EXAMPLE:: + EXAMPLES:: sage: Fq = GF(343); M = Fq^2; frob = Fq.frobenius_endomorphism() sage: PHS = M.PseudoHom(frob); PHS diff --git a/src/sage/modules/free_module_pseudomorphism.py b/src/sage/modules/free_module_pseudomorphism.py index de442ff670f..b5d894a7ac2 100644 --- a/src/sage/modules/free_module_pseudomorphism.py +++ b/src/sage/modules/free_module_pseudomorphism.py @@ -83,18 +83,18 @@ def __init__(self, domain, base_morphism, twist=None, codomain=None, side="left" Constructs a pseudomorphism of free modules. INPUT: - - ``domain`` - the domain of the pseudomorphism; a free module + - ``domain`` - the domain of the pseudomorphism; a free module - ``base_morphism`` - either a morphism or a matrix defining a morphism - - ``twist`` - a twisting morphism, this is either a morphism or + - ``twist`` - a twisting morphism, this is either a morphism or a derivation (default: None) - ``codomain`` - the codomain of the pseudomorphism; a free module (default: None) - - side -- side of the vectors acted on by the matrix + - side -- side of the vectors acted on by the matrix (default: ``"left"``) EXAMPLES:: @@ -111,7 +111,7 @@ def __init__(self, domain, base_morphism, twist=None, codomain=None, side="left" elif isinstance(base_morphism, Morphism): self._base_matrix = base_morphism.matrix() else: - self._base_matrix = matrix(domain.coordinate_ring(), \ + self._base_matrix = matrix(domain.coordinate_ring(), base_morphism) self.derivation = None self.twist_morphism = None @@ -186,7 +186,7 @@ def __repr__(self): if self.derivation is not None: deriv = "\ntwisted by the derivation {}" deriv = deriv.format(self.derivation.__repr__()) - return r.format(act, self.matrix(), morph, deriv, \ + return r.format(act, self.matrix(), morph, deriv, self.domain(), self.codomain()) def matrix(self): From 0cc3eaf874ff53e07c7f9d24e221a23796ea3ec5 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Thu, 1 Feb 2024 21:31:02 +0100 Subject: [PATCH 053/369] 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 6e95539541983ff74871d87400b0b5de5254c5fb Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Fri, 2 Feb 2024 22:52:08 +0100 Subject: [PATCH 054/369] Moved code in `.is_isomorphic`, edited some docstrings --- .../algebras/quatalg/quaternion_algebra.py | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index 04fd2e866d7..71ee1022336 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -1045,7 +1045,7 @@ def is_definite(self): """ Checks whether the quaternion algebra ``self`` is definite, i.e. whether it ramifies at the unique Archimedean place of its base field QQ. This is the case if and only if both - invariants of ``self`` are negative, see Exercise 2.4(c) in [Voi2021]_. + invariants of ``self`` are negative; see Exercise 2.4(c) in [Voi2021]_. EXAMPLES:: @@ -1323,10 +1323,10 @@ def is_isomorphic(self, A) -> bool: if F != A.base_ring(): raise ValueError("both quaternion algebras must be defined over the same base ring") - try: - if is_RationalField(F): - return self.ramified_places(inf=False) == A.ramified_places(inf=False) + if is_RationalField(F): + return self.ramified_places(inf=False) == A.ramified_places(inf=False) + try: ram_self = self.ramified_places(inf=True) ram_A = A.ramified_places(inf=True) return set(ram_self[0]) == set(ram_A[0]) and ram_self[1] == ram_A[1] @@ -2021,7 +2021,8 @@ def is_maximal(self): r""" Check whether the order of ``self`` is maximal in the ambient quaternion algebra. - Only works in quaternion algebras over number fields + Only implemented for quaternion algebras over number fields; for reference, + see Theorem 15.5.5 in [Voi2021]_. OUTPUT: Boolean @@ -3268,12 +3269,12 @@ def cyclic_right_subideals(self, p, alpha=None): def is_integral(self): r""" - Check if a quaternion fractional ideal is integral. An ideal in a quaternion algebra is - said integral if it is contained in its left order. If the left order is already defined it just - check the definition, otherwise it uses one of the alternative definition of Lemma 16.2.8 of - [Voi2021]_. + Checks whether a quaternion fractional ideal is integral. An ideal in a quaternion algebra + is integral if and only if it is contained in its left order. If the left order is already + defined this method just checks this definition, otherwise it uses one of the alternative + definitions from Lemma 16.2.8 of [Voi2021]_. - OUTPUT: a boolean. + OUTPUT: A boolean. EXAMPLES:: From 693c051110c5783433217ad45beed457bb1fdf86 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Tue, 6 Feb 2024 11:04:26 +0100 Subject: [PATCH 055/369] 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 056/369] 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 057/369] 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 c87409e13afbe222f9fa75e240e34be30f3e9137 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Wed, 24 Jan 2024 18:10:11 +0100 Subject: [PATCH 058/369] use Parent in quotient rings too --- src/sage/rings/quotient_ring.py | 36 ++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/src/sage/rings/quotient_ring.py b/src/sage/rings/quotient_ring.py index a6a0a8ce7f8..5b8013dbf26 100644 --- a/src/sage/rings/quotient_ring.py +++ b/src/sage/rings/quotient_ring.py @@ -110,17 +110,20 @@ # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. -# http://www.gnu.org/licenses/ +# https://www.gnu.org/licenses/ # **************************************************************************** import sage.interfaces.abc import sage.misc.latex as latex import sage.structure.parent_gens + +from sage.structure.parent import Parent from sage.categories.commutative_rings import CommutativeRings from sage.categories.rings import Rings from sage.misc.cachefunc import cached_method from sage.rings import ideal, quotient_ring_element, ring from sage.structure.category_object import normalize_names from sage.structure.richcmp import richcmp, richcmp_method +from sage.structure.category_object import check_default_category _Rings = Rings() _CommRings = CommutativeRings() @@ -334,7 +337,7 @@ def QuotientRing(R, I, names=None, **kwds): I_lift = S.ideal(G) J = R.defining_ideal() if S == ZZ: - return Integers((I_lift+J).gen(), **kwds) + return Integers((I_lift + J).gen(), **kwds) return R.__class__(S, I_lift + J, names=names) if R in _CommRings: return QuotientRing_generic(R, I, names, **kwds) @@ -372,16 +375,15 @@ def is_QuotientRing(x): _RingsQuotients = _Rings.Quotients() _CommutativeRingsQuotients = _CommRings.Quotients() -from sage.structure.category_object import check_default_category @richcmp_method -class QuotientRing_nc(ring.Ring, sage.structure.parent_gens.ParentWithGens): +class QuotientRing_nc(Parent): """ The quotient ring of `R` by a twosided ideal `I`. - This class is for rings that do not inherit from - :class:`~sage.rings.ring.CommutativeRing`. + This class is for rings that are not in the category + ``Rings().Commutative()``. EXAMPLES: @@ -489,8 +491,8 @@ def __init__(self, R, I, names, category=None): raise TypeError("The second argument must be an ideal of the given ring, but %s is not" % I) self.__R = R self.__I = I - #sage.structure.parent_gens.ParentWithGens.__init__(self, R.base_ring(), names) - ## + # sage.structure.parent_gens.ParentWithGens.__init__(self, R.base_ring(), names) + # Unfortunately, computing the join of categories, which is done in # check_default_category, is very expensive. # However, we don't just want to use the given category without mixing in @@ -504,10 +506,10 @@ def __init__(self, R, I, names, category=None): except (AttributeError, NotImplementedError): commutative = False if commutative: - category = check_default_category(_CommutativeRingsQuotients,category) + category = check_default_category(_CommutativeRingsQuotients, category) else: - category = check_default_category(_RingsQuotients,category) - ring.Ring.__init__(self, R.base_ring(), names=names, category=category) + category = check_default_category(_RingsQuotients, category) + Parent.__init__(self, base=R.base_ring(), names=names, category=category) # self._populate_coercion_lists_([R]) # we don't want to do this, since subclasses will often implement improved coercion maps. def construction(self): @@ -790,7 +792,7 @@ def lift(self, x=None): return self.lifting_map() return self.lifting_map()(x) - def retract(self,x): + def retract(self, x): """ The image of an element of the cover ring under the quotient map. @@ -1146,7 +1148,7 @@ def _coerce_map_from_(self, R): try: if R.defining_ideal().change_ring(C) <= self.defining_ideal(): return True - except AttributeError: # Not all ideals have a change_ring + except AttributeError: # Not all ideals have a change_ring pass return C.has_coerce_map_from(R) @@ -1246,6 +1248,10 @@ def gen(self, i=0): """ return self(self.__R.gen(i)) + def gens(self) -> tuple: + return tuple(self(self.__R.gen(i)) + for i in range(self.cover_ring().ngens())) + def _singular_(self, singular=None): """ Returns the Singular quotient ring of ``self`` if the base ring is @@ -1310,7 +1316,7 @@ def _singular_init_(self, singular=None): if singular is None: from sage.interfaces.singular import singular self.__R._singular_().set_ring() - self.__singular = singular("%s" % self.__I._singular_().name(),"qring") + self.__singular = singular("%s" % self.__I._singular_().name(), "qring") return self.__singular def _magma_init_(self, magma): @@ -1468,6 +1474,7 @@ def _ideal_class_(self, num_gens): return QuotientRingIdeal_principal return QuotientRingIdeal_generic + class QuotientRingIdeal_generic(ideal.Ideal_generic): r""" Specialized class for quotient-ring ideals. @@ -1517,6 +1524,7 @@ def _contains_(self, other): J = R.cover_ring().ideal(Igens) return other.lift() in J + class QuotientRingIdeal_principal(ideal.Ideal_principal, QuotientRingIdeal_generic): r""" Specialized class for principal quotient-ring ideals. From a7c11be09ae0bd4f840cf14a6530e99494fdd29e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Thu, 25 Jan 2024 15:10:19 +0100 Subject: [PATCH 059/369] moving ideals to the categories --- src/sage/categories/commutative_rings.py | 32 +++++++ src/sage/categories/rings.py | 80 +++++++++++++++- src/sage/rings/morphism.pyx | 4 +- src/sage/rings/ring.pyx | 117 ----------------------- 4 files changed, 114 insertions(+), 119 deletions(-) diff --git a/src/sage/categories/commutative_rings.py b/src/sage/categories/commutative_rings.py index 1601ca3d3e3..c2f6c1ba683 100644 --- a/src/sage/categories/commutative_rings.py +++ b/src/sage/categories/commutative_rings.py @@ -216,6 +216,38 @@ def over(self, base=None, gen=None, gens=None, name=None, names=None): gens = (gen,) return RingExtension(self, base, gens, names) + def _ideal_class_(self, n=0): + r""" + Return a callable object that can be used to create ideals in this + ring. + + This class can depend on `n`, the number of generators of the ideal. + The default input of `n=0` indicates an unspecified number of generators, + in which case a class that works for any number of generators is returned. + + EXAMPLES:: + + sage: ZZ._ideal_class_() + + sage: RR._ideal_class_() + + sage: R. = GF(5)[] + sage: R._ideal_class_(1) + + sage: S = R.quo(x^3 - y^2) + sage: S._ideal_class_(1) + + sage: S._ideal_class_(2) + + sage: T. = S[] # needs sage.libs.singular + sage: T._ideal_class_(5) # needs sage.libs.singular + + sage: T._ideal_class_(1) # needs sage.libs.singular + + """ + from sage.rings.ideal import Ideal_generic, Ideal_principal + return Ideal_principal if n == 1 else Ideal_generic + class ElementMethods: pass diff --git a/src/sage/categories/rings.py b/src/sage/categories/rings.py index a162fdb0def..f6f66245dff 100644 --- a/src/sage/categories/rings.py +++ b/src/sage/categories/rings.py @@ -562,6 +562,84 @@ def ideal_monoid(self): from sage.rings.noncommutative_ideals import IdealMonoid_nc return IdealMonoid_nc(self) + def _ideal_class_(self, n=0): + r""" + Return a callable object that can be used to create ideals in this + ring. + + The argument `n`, standing for the number of generators + of the ideal, is ignored. + + EXAMPLES: + + Since :trac:`7797`, non-commutative rings have ideals as well:: + + sage: A = SteenrodAlgebra(2) # needs sage.combinat sage.modules + sage: A._ideal_class_() # needs sage.combinat sage.modules + + """ + from sage.rings.noncommutative_ideals import Ideal_nc + return Ideal_nc + + def principal_ideal(self, gen, coerce=True): + """ + Return the principal ideal generated by ``gen``. + + EXAMPLES:: + + sage: R. = ZZ[] + sage: R.principal_ideal(x+2*y) + Ideal (x + 2*y) of Multivariate Polynomial Ring in x, y over Integer Ring + """ + C = self._ideal_class_(1) + if coerce: + gen = self(gen) + return C(self, [gen]) + + @cached_method + def unit_ideal(self): + """ + Return the unit ideal of this ring. + + EXAMPLES:: + + sage: Zp(7).unit_ideal() # needs sage.rings.padics + Principal ideal (1 + O(7^20)) of 7-adic Ring with capped relative precision 20 + """ + return self.principal_ideal(self.one(), coerce=False) + + @cached_method + def zero_ideal(self): + """ + Return the zero ideal of this ring (cached). + + EXAMPLES:: + + sage: ZZ.zero_ideal() + Principal ideal (0) of Integer Ring + sage: QQ.zero_ideal() + Principal ideal (0) of Rational Field + sage: QQ['x'].zero_ideal() + Principal ideal (0) of Univariate Polynomial Ring in x over Rational Field + + The result is cached:: + + sage: ZZ.zero_ideal() is ZZ.zero_ideal() + True + + TESTS: + + Make sure that :trac:`13644` is fixed:: + + sage: # needs sage.rings.padics + sage: K = Qp(3) + sage: R. = K[] + sage: L. = K.extension(a^2-3) + sage: L.ideal(a) + Principal ideal (1 + O(a^40)) of 3-adic Eisenstein Extension Field in a defined by a^2 - 3 + """ + return self.principal_ideal(self.zero(), coerce=False) + def characteristic(self): """ Return the characteristic of this ring. @@ -753,7 +831,7 @@ def _ideal_class_(self, n=0): example:: sage: super(Ring,QQ)._ideal_class_.__module__ - 'sage.categories.rings' + 'sage.categories.commutative_rings' sage: super(Ring,QQ)._ideal_class_() sage: super(Ring,QQ)._ideal_class_(1) diff --git a/src/sage/rings/morphism.pyx b/src/sage/rings/morphism.pyx index c65f75a248c..59e0fca9781 100644 --- a/src/sage/rings/morphism.pyx +++ b/src/sage/rings/morphism.pyx @@ -410,6 +410,7 @@ from sage.rings import ideal import sage.structure.all from sage.structure.richcmp cimport (richcmp, rich_to_bool) from sage.misc.cachefunc import cached_method +from sage.categories.rings import Rings cdef class RingMap(Morphism): @@ -1291,7 +1292,8 @@ cdef class RingHomomorphism(RingMap): from sage.rings.ideal import Ideal_generic A = self.domain() B = self.codomain() - if not (A.is_commutative() and B.is_commutative()): + Comm = Rings().Commutative() + if not (A in Comm and B in Comm): raise NotImplementedError("rings are not commutative") if A.base_ring() != B.base_ring(): raise NotImplementedError("base rings must be equal") diff --git a/src/sage/rings/ring.pyx b/src/sage/rings/ring.pyx index 650fb387fa0..dcd95e2660f 100644 --- a/src/sage/rings/ring.pyx +++ b/src/sage/rings/ring.pyx @@ -506,123 +506,6 @@ cdef class Ring(ParentWithGens): else: raise TypeError("Don't know how to transform %s into an ideal of %s" % (self, x)) - def _ideal_class_(self, n=0): - r""" - Return a callable object that can be used to create ideals in this - ring. For generic rings, this returns the factory function - :func:`sage.rings.ideal.Ideal`, which does its best to be clever about - what is required. - - This class can depend on `n`, the number of generators of the ideal. - The default input of `n=0` indicates an unspecified number of generators, - in which case a class that works for any number of generators is returned. - - EXAMPLES:: - - sage: ZZ._ideal_class_() - - sage: RR._ideal_class_() - - sage: R. = GF(5)[] - sage: R._ideal_class_(1) - - sage: S = R.quo(x^3 - y^2) - sage: S._ideal_class_(1) - - sage: S._ideal_class_(2) - - sage: T. = S[] # needs sage.libs.singular - sage: T._ideal_class_(5) # needs sage.libs.singular - - sage: T._ideal_class_(1) # needs sage.libs.singular - - - Since :trac:`7797`, non-commutative rings have ideals as well:: - - sage: A = SteenrodAlgebra(2) # needs sage.combinat sage.modules - sage: A._ideal_class_() # needs sage.combinat sage.modules - - - """ - # One might need more than just n, but I can't think of an example. - from sage.rings.noncommutative_ideals import Ideal_nc - try: - if not self.is_commutative(): - return Ideal_nc - except (NotImplementedError, AttributeError): - return Ideal_nc - from sage.rings.ideal import Ideal_generic, Ideal_principal - if n == 1: - return Ideal_principal - else: - return Ideal_generic - - def principal_ideal(self, gen, coerce=True): - """ - Return the principal ideal generated by gen. - - EXAMPLES:: - - sage: R. = ZZ[] - sage: R.principal_ideal(x+2*y) - Ideal (x + 2*y) of Multivariate Polynomial Ring in x, y over Integer Ring - """ - C = self._ideal_class_(1) - if coerce: - gen = self(gen) - return C(self, [gen]) - - def unit_ideal(self): - """ - Return the unit ideal of this ring. - - EXAMPLES:: - - sage: Zp(7).unit_ideal() # needs sage.rings.padics - Principal ideal (1 + O(7^20)) of 7-adic Ring with capped relative precision 20 - """ - if self._unit_ideal is None: - I = Ring.ideal(self, [self(1)], coerce=False) - self._unit_ideal = I - return I - return self._unit_ideal - - def zero_ideal(self): - """ - Return the zero ideal of this ring (cached). - - EXAMPLES:: - - sage: ZZ.zero_ideal() - Principal ideal (0) of Integer Ring - sage: QQ.zero_ideal() - Principal ideal (0) of Rational Field - sage: QQ['x'].zero_ideal() - Principal ideal (0) of Univariate Polynomial Ring in x over Rational Field - - The result is cached:: - - sage: ZZ.zero_ideal() is ZZ.zero_ideal() - True - - TESTS: - - Make sure that :trac:`13644` is fixed:: - - sage: # needs sage.rings.padics - sage: K = Qp(3) - sage: R. = K[] - sage: L. = K.extension(a^2-3) - sage: L.ideal(a) - Principal ideal (1 + O(a^40)) of 3-adic Eisenstein Extension Field in a defined by a^2 - 3 - - """ - if self._zero_ideal is None: - I = Ring.ideal(self, [self.zero()], coerce=False) - self._zero_ideal = I - return I - return self._zero_ideal - def zero(self): """ Return the zero element of this ring (cached). From 55226d6ae0025bf65ecaf9f0eec19efbd521b48b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Fri, 26 Jan 2024 09:32:22 +0100 Subject: [PATCH 060/369] some fixes in function fields --- src/sage/categories/rings.py | 55 -------------------------- src/sage/rings/function_field/ideal.py | 2 +- src/sage/rings/function_field/order.py | 2 +- 3 files changed, 2 insertions(+), 57 deletions(-) diff --git a/src/sage/categories/rings.py b/src/sage/categories/rings.py index f6f66245dff..c39a29b66af 100644 --- a/src/sage/categories/rings.py +++ b/src/sage/categories/rings.py @@ -795,61 +795,6 @@ def ideal(self, *args, **kwds): gens = gens[0] return C(self, gens, **kwds) - def _ideal_class_(self, n=0): - """ - Return the class that is used to implement ideals of this ring. - - .. NOTE:: - - We copy the code from :class:`~sage.rings.ring.Ring`. This is - necessary because not all rings inherit from that class, such - as matrix algebras. - - INPUT: - - - ``n`` (optional integer, default 0): The number of generators - of the ideal to be created. - - OUTPUT: - - The class that is used to implement ideals of this ring with - ``n`` generators. - - .. NOTE:: - - Often principal ideals (``n==1``) are implemented via - a different class. - - EXAMPLES:: - - sage: MS = MatrixSpace(QQ, 2, 2) # needs sage.modules - sage: MS._ideal_class_() # needs sage.modules - - - We do not know of a commutative ring in Sage that does not inherit - from the base class of rings. So, we need to cheat in the next - example:: - - sage: super(Ring,QQ)._ideal_class_.__module__ - 'sage.categories.commutative_rings' - sage: super(Ring,QQ)._ideal_class_() - - sage: super(Ring,QQ)._ideal_class_(1) - - sage: super(Ring,QQ)._ideal_class_(2) - - """ - from sage.rings.noncommutative_ideals import Ideal_nc - try: - if not self.is_commutative(): - return Ideal_nc - except (NotImplementedError, AttributeError): - return Ideal_nc - from sage.rings.ideal import Ideal_generic, Ideal_principal - if n == 1: - return Ideal_principal - return Ideal_generic - ## # Quotient rings def quotient(self, I, names=None, **kwds): diff --git a/src/sage/rings/function_field/ideal.py b/src/sage/rings/function_field/ideal.py index 22aa15299b1..21ccf2b4f4c 100644 --- a/src/sage/rings/function_field/ideal.py +++ b/src/sage/rings/function_field/ideal.py @@ -1070,7 +1070,7 @@ def __init__(self, R): sage: M = O.ideal_monoid() sage: TestSuite(M).run() """ - self.Element = R._ideal_class + self.Element = R._ideal_class_ Parent.__init__(self, category=Monoids()) self.__R = R diff --git a/src/sage/rings/function_field/order.py b/src/sage/rings/function_field/order.py index 615cbab3690..a1cb369ee8e 100644 --- a/src/sage/rings/function_field/order.py +++ b/src/sage/rings/function_field/order.py @@ -146,7 +146,7 @@ def __init__(self, field, ideal_class=FunctionFieldIdeal, category=None): category = IntegralDomains().or_subcategory(category).Infinite() Parent.__init__(self, category=category, facade=field) - self._ideal_class = ideal_class # element class for parent ideal monoid + self._ideal_class_ = ideal_class # element class for parent ideal monoid self._field = field def is_field(self, proof=True): From 3a1f0e00912927d4ac2b81ec23af82493aac9537 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Tue, 6 Feb 2024 15:18:12 +0100 Subject: [PATCH 061/369] little detail --- src/sage/rings/function_field/order_polymod.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sage/rings/function_field/order_polymod.py b/src/sage/rings/function_field/order_polymod.py index 97f5a625819..7c75a72f51d 100644 --- a/src/sage/rings/function_field/order_polymod.py +++ b/src/sage/rings/function_field/order_polymod.py @@ -649,7 +649,8 @@ def decomposition(self, ideal): # matrices_reduced give the multiplication matrices used to form the # algebra O mod pO. matrices_reduced = list(map(lambda M: M.mod(p), matrices)) - A = FiniteDimensionalAlgebra(k, matrices_reduced) + A = FiniteDimensionalAlgebra(k, matrices_reduced, + assume_associative=True) # Each prime ideal of the algebra A corresponds to a prime ideal of O, # and since the algebra is an Artinian ring, all of its prime ideals From 296d7e3a996101af6cb87d270e8231bb5d392d5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Tue, 6 Feb 2024 16:28:27 +0100 Subject: [PATCH 062/369] little detail --- src/doc/en/thematic_tutorials/coercion_and_categories.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/src/doc/en/thematic_tutorials/coercion_and_categories.rst b/src/doc/en/thematic_tutorials/coercion_and_categories.rst index 5294b37fb7f..0cedb9f8ff9 100644 --- a/src/doc/en/thematic_tutorials/coercion_and_categories.rst +++ b/src/doc/en/thematic_tutorials/coercion_and_categories.rst @@ -159,7 +159,6 @@ This base class provides a lot more methods than a general parent:: 'random_element', 'unit_ideal', 'zero', - 'zero_ideal', 'zeta', 'zeta_order'] From 9f5239ace31382af9b634cd149f0adf833a04e8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Wed, 7 Feb 2024 11:20:23 +0100 Subject: [PATCH 063/369] fix doctest --- src/doc/en/thematic_tutorials/coercion_and_categories.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/doc/en/thematic_tutorials/coercion_and_categories.rst b/src/doc/en/thematic_tutorials/coercion_and_categories.rst index 0cedb9f8ff9..a79ef618a5f 100644 --- a/src/doc/en/thematic_tutorials/coercion_and_categories.rst +++ b/src/doc/en/thematic_tutorials/coercion_and_categories.rst @@ -155,9 +155,7 @@ This base class provides a lot more methods than a general parent:: 'one', 'order', 'prime_subfield', - 'principal_ideal', 'random_element', - 'unit_ideal', 'zero', 'zeta', 'zeta_order'] From 6617f7963fcaf0572abaf184ee7e41604a2f2f05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Wed, 7 Feb 2024 11:32:37 +0100 Subject: [PATCH 064/369] tentative fixes --- .../categories/finite_dimensional_algebras_with_basis.py | 8 +++++++- src/sage/rings/function_field/order_polymod.py | 7 ++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/sage/categories/finite_dimensional_algebras_with_basis.py b/src/sage/categories/finite_dimensional_algebras_with_basis.py index 0858078f41b..40458687a71 100644 --- a/src/sage/categories/finite_dimensional_algebras_with_basis.py +++ b/src/sage/categories/finite_dimensional_algebras_with_basis.py @@ -396,10 +396,16 @@ def center(self): center.rename("Center of {}".format(self)) return center - def principal_ideal(self, a, side='left'): + def principal_ideal(self, a, side='left', coerce=None): r""" Construct the ``side`` principal ideal generated by ``a``. + INPUT: + + - ``a`` -- an element + - ``side`` -- ``left`` (default) or ``right`` + - ``coerce`` -- ignored, for compatibility with categories + EXAMPLES: In order to highlight the difference between left and diff --git a/src/sage/rings/function_field/order_polymod.py b/src/sage/rings/function_field/order_polymod.py index 7c75a72f51d..c9b8e27d1d9 100644 --- a/src/sage/rings/function_field/order_polymod.py +++ b/src/sage/rings/function_field/order_polymod.py @@ -17,6 +17,7 @@ # **************************************************************************** from sage.arith.functions import lcm +from sage.categories.algebras import Algebras from sage.misc.cachefunc import cached_method from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing @@ -648,9 +649,9 @@ def decomposition(self, ideal): # Let O denote the maximal order self. When reduced modulo p, # matrices_reduced give the multiplication matrices used to form the # algebra O mod pO. - matrices_reduced = list(map(lambda M: M.mod(p), matrices)) - A = FiniteDimensionalAlgebra(k, matrices_reduced, - assume_associative=True) + matrices_reduced = [M.mod(p) for M in matrices] + cat = Algebras(k).FiniteDimensional().WithBasis() + A = FiniteDimensionalAlgebra(k, matrices_reduced, category=cat) # Each prime ideal of the algebra A corresponds to a prime ideal of O, # and since the algebra is an Artinian ring, all of its prime ideals From aede7ce0ee78591267b1868befdefd9055de3ec1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Wed, 7 Feb 2024 11:46:37 +0100 Subject: [PATCH 065/369] one more fix --- src/sage/rings/function_field/order_polymod.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/function_field/order_polymod.py b/src/sage/rings/function_field/order_polymod.py index c9b8e27d1d9..a84f1fd0229 100644 --- a/src/sage/rings/function_field/order_polymod.py +++ b/src/sage/rings/function_field/order_polymod.py @@ -650,7 +650,7 @@ def decomposition(self, ideal): # matrices_reduced give the multiplication matrices used to form the # algebra O mod pO. matrices_reduced = [M.mod(p) for M in matrices] - cat = Algebras(k).FiniteDimensional().WithBasis() + cat = Algebras(k).Commutative().FiniteDimensional().WithBasis() A = FiniteDimensionalAlgebra(k, matrices_reduced, category=cat) # Each prime ideal of the algebra A corresponds to a prime ideal of O, From 10f796a442cd6184378ccaad6847a316fcc5be84 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Wed, 7 Feb 2024 20:43:22 +0100 Subject: [PATCH 066/369] 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 067/369] 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 068/369] 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 069/369] 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 070/369] 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 071/369] 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 072/369] 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 073/369] 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 074/369] 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 075/369] 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 0d72c5892589168f8987d3fbebd6d1ca5f3060f8 Mon Sep 17 00:00:00 2001 From: ymusleh Date: Wed, 21 Feb 2024 00:51:50 -0500 Subject: [PATCH 076/369] Improved doctests --- .../modules/free_module_pseudohomspace.py | 143 ++++++++++++------ .../modules/free_module_pseudomorphism.py | 52 ++++--- 2 files changed, 133 insertions(+), 62 deletions(-) diff --git a/src/sage/modules/free_module_pseudohomspace.py b/src/sage/modules/free_module_pseudohomspace.py index f696be346f6..5299ff2fca7 100644 --- a/src/sage/modules/free_module_pseudohomspace.py +++ b/src/sage/modules/free_module_pseudohomspace.py @@ -7,7 +7,7 @@ """ # **************************************************************************** -# Copyright (C) 2024 Yossef Musleh +# Copyright (C) 2024 Yossef Musleh # # Distributed under the terms of the GNU General Public License (GPL) # @@ -24,7 +24,6 @@ from sage.structure.element import is_Matrix from sage.matrix.constructor import matrix, identity_matrix from sage.matrix.matrix_space import MatrixSpace -from sage.misc.cachefunc import cached_method from sage.categories.morphism import Morphism from sage.misc.lazy_import import lazy_import @@ -37,7 +36,7 @@ class FreeModulePseudoHomspace(sage.categories.homset.HomsetWithBase): For free modules, the elements of a pseudomorphism correspond to matrices which define the mapping on elements of a basis. - EXAMPLES:: + TESTS:: sage: F = GF(125); M = F^2; twist = F.frobenius_endomorphism() sage: PHS = M.PseudoHom(twist) @@ -47,11 +46,30 @@ class FreeModulePseudoHomspace(sage.categories.homset.HomsetWithBase): (z3, 2*z3^2 + 3*z3 + 3) """ - def __init__(self, X, Y, twist=None): - self._domain = X - self._codomain = X - if Y is not None: - self._codomain = Y + def __init__(self, domain, codomain=None, twist=None): + r""" + Constructs the space of pseudomorphisms with a given twist. + + INPUT: + - ``domain`` - the domain of the pseudomorphism; a free module + + - ``codomain`` - the codomain of the pseudomorphism; a free + module (default: None) + + - ``twist`` - a twisting morphism, this is either a morphism or + a derivation (default: None) + + EXAMPLES:: + + sage: F = GF(125); M = F^2; twist = F.frobenius_endomorphism() + sage: PHS = M.PseudoHom(twist); PHS + Set of Pseudomorphisms from Vector space of dimension 2 over Finite Field in z3 of size 5^3 to Vector space of dimension 2 over Finite Field in z3 of size 5^3 + Twisted by the morphism Frobenius endomorphism z3 |--> z3^5 on Finite Field in z3 of size 5^3 + """ + self._domain = domain + self._codomain = domain + if codomain is not None: + self._codomain = codomain super().__init__(self._domain, self._codomain, category=None) self.base_homspace = self._domain.Hom(self._codomain) self.twist = twist @@ -74,6 +92,10 @@ def __call__(self, A, **kwds): r""" Coerce a matrix or free module homomorphism into a pseudomorphism. + INPUTS: + - ``A`` - either a matrix defining the morphism or a free module + morphism + EXAMPLES:: sage: F = GF(125); M = F^2; twist = F.frobenius_endomorphism() @@ -86,34 +108,7 @@ def __call__(self, A, **kwds): Domain: Vector space of dimension 2 over Finite Field in z3 of size 5^3 Codomain: Vector space of dimension 2 over Finite Field in z3 of size 5^3 """ - from . import free_module_pseudomorphism - side = kwds.get("side", "left") - if not is_Matrix(A): - C = self.codomain() - try: - if callable(A): - v = [C(A(g)) for g in self.domain().gens()] - A = matrix([C.coordinates(a) for a in v], ncols=C.rank()) - if side == "right": - A = A.transpose() - else: - v = [C(a) for a in A] - if side == "right": - A = matrix([C.coordinates(a) for a in v], - ncols=C.rank()).transpose() - else: - A = matrix([C.coordinates(a) for a in v], - ncols=C.rank()) - except TypeError: - pass - if not self.codomain().base_ring().has_coerce_map_from( - self.domain().base_ring()) and not A.is_zero(): - raise TypeError("nontrivial morphisms require a coercion map" - "from the base ring of the domain to the base ring of the" - "codomain") - return free_module_pseudomorphism.FreeModulePseudoMorphism( - self.domain(), A, twist=self.twist, - codomain=self.codomain()) + return self._element_constructor_(A, **kwds) def __repr__(self): r""" @@ -137,23 +132,67 @@ def __repr__(self): deriv = deriv.format(self.derivation.__repr__()) return r.format(self.domain(), self.codomain(), morph, deriv) - def zero(self): + def _element_constructor_(self, A, **kwds): r""" - Return the zero pseudomorphism. This corresponds to the zero matrix. + Coerce a matrix or free module homomorphism into a pseudomorphism. - EXAMPLES:: + INPUTS: + - ``A`` - either a matrix defining the morphism or a free module + morphism + + TESTS:: sage: F = GF(125); M = F^2; twist = F.frobenius_endomorphism() sage: PHS = M.PseudoHom(twist) - sage: PHS.zero() + sage: h = PHS._element_constructor_([[1, 2], [1, 1]]); h Free module pseudomorphism defined by the matrix - [0 0] - [0 0] + [1 2] + [1 1] + twisted by the morphism Frobenius endomorphism z3 |--> z3^5 on Finite Field in z3 of size 5^3 + Domain: Vector space of dimension 2 over Finite Field in z3 of size 5^3 + Codomain: Vector space of dimension 2 over Finite Field in z3 of size 5^3 + + :: + + sage: F = GF(125); M = F^2; twist = F.frobenius_endomorphism() + sage: PHS = M.PseudoHom(twist) + sage: morph = M.hom(matrix([[1, 2], [1, 1]])) + sage: phi = PHS._element_constructor_(morph, side="right"); phi + Free module pseudomorphism defined as left-multiplication by the matrix + [1 1] + [2 1] twisted by the morphism Frobenius endomorphism z3 |--> z3^5 on Finite Field in z3 of size 5^3 Domain: Vector space of dimension 2 over Finite Field in z3 of size 5^3 Codomain: Vector space of dimension 2 over Finite Field in z3 of size 5^3 """ - return self(self.base_homspace.zero()) + from . import free_module_pseudomorphism + side = kwds.get("side", "left") + if not is_Matrix(A): + C = self.codomain() + try: + if callable(A): + v = [C(A(g)) for g in self.domain().gens()] + A = matrix([C.coordinates(a) for a in v], ncols=C.rank()) + if side == "right": + A = A.transpose() + else: + v = [C(a) for a in A] + if side == "right": + A = matrix([C.coordinates(a) for a in v], + ncols=C.rank()).transpose() + else: + A = matrix([C.coordinates(a) for a in v], + ncols=C.rank()) + except TypeError: + pass + if not self.codomain().base_ring().has_coerce_map_from( + self.domain().base_ring()) and not A.is_zero(): + raise TypeError("nontrivial morphisms require a coercion map" + "from the base ring of the domain to the base ring of the" + "codomain") + return free_module_pseudomorphism.FreeModulePseudoMorphism( + self.domain(), A, twist=self.twist, + codomain=self.codomain(), side=side) def _matrix_space(self): r""" @@ -217,3 +256,21 @@ def identity(self): Codomain: Vector space of dimension 2 over Finite Field in z3 of size 5^3 """ return self(self.base_homspace.identity()) + + def zero(self): + r""" + Return the zero pseudomorphism. This corresponds to the zero matrix. + + EXAMPLES:: + + sage: F = GF(125); M = F^2; twist = F.frobenius_endomorphism() + sage: PHS = M.PseudoHom(twist) + sage: PHS.zero() + Free module pseudomorphism defined by the matrix + [0 0] + [0 0] + twisted by the morphism Frobenius endomorphism z3 |--> z3^5 on Finite Field in z3 of size 5^3 + Domain: Vector space of dimension 2 over Finite Field in z3 of size 5^3 + Codomain: Vector space of dimension 2 over Finite Field in z3 of size 5^3 + """ + return self(self.base_homspace.zero()) diff --git a/src/sage/modules/free_module_pseudomorphism.py b/src/sage/modules/free_module_pseudomorphism.py index b5d894a7ac2..2c3f0491407 100644 --- a/src/sage/modules/free_module_pseudomorphism.py +++ b/src/sage/modules/free_module_pseudomorphism.py @@ -37,10 +37,10 @@ class FreeModulePseudoMorphism(Morphism): r""" Let `M, M'` be free modules over a ring `R`, `\theta: R \to R` a ring - homomorphism, and `\theta: R \to R` a derivation i.e. an additive - map such that + homomorphism, and `\delta: R \to R` a `\theta`-derivation, which is a map + such that: - `\delta(xy) = x\delta(y) + \delta(x)y`. + `\delta(xy) = \theta(x)\delta(y) + \delta(x)y`. Then a pseudomorphism `f : M to M` is a map such that @@ -99,10 +99,22 @@ def __init__(self, domain, base_morphism, twist=None, codomain=None, side="left" EXAMPLES:: - sage: F = GF(25); V = F^3; twist = F.frobenius_endomorphism(5) - sage: phi = V.pseudohom(matrix(F,3,[1..9]), twist) + sage: F = GF(25); M = F^3; twist = F.frobenius_endomorphism(5) + sage: phi = M.pseudohom(matrix(F,3,[1..9]), twist) sage: type(phi) + + :: + + sage: F = GF(125); M = F^2; twist = F.frobenius_endomorphism() + sage: morph = M.hom(matrix([[1,2],[0,1]])) + sage: phi = M.pseudohom(morph, twist, side="right"); phi + Free module pseudomorphism defined as left-multiplication by the matrix + [1 2] + [0 1] + twisted by the morphism Frobenius endomorphism z3 |--> z3^5 on Finite Field in z3 of size 5^3 + Domain: Vector space of dimension 2 over Finite Field in z3 of size 5^3 + Codomain: Vector space of dimension 2 over Finite Field in z3 of size 5^3 """ from sage.structure.element import is_Matrix Morphism.__init__(self, domain.PseudoHom(twist, codomain)) @@ -115,6 +127,7 @@ def __init__(self, domain, base_morphism, twist=None, codomain=None, side="left" base_morphism) self.derivation = None self.twist_morphism = None + self.side = side if isinstance(twist, Morphism): self.twist_morphism = twist elif isinstance(twist, RingDerivation): @@ -123,7 +136,8 @@ def __init__(self, domain, base_morphism, twist=None, codomain=None, side="left" self.derivation = twist else: self.derivation = None - self.side = side + elif twist is not None: + raise TypeError("twist is not a ring morphism or derivation") def _call_(self, x): r""" @@ -216,19 +230,6 @@ def matrix(self): """ return self._base_matrix - def twisting_morphism(self): - r""" - Return the twisting homomorphism of the pseudomorphism. - - EXAMPLES:: - - sage: Fq = GF(343); M = Fq^3; frob = Fq.frobenius_endomorphism() - sage: ph = M.pseudohom([[1, 2, 3], [0, 1, 1], [2, 1, 1]], frob, side="right") - sage: ph.twisting_morphism() - Frobenius endomorphism z3 |--> z3^7 on Finite Field in z3 of size 7^3 - """ - return self.twist_morphism - def twisting_derivation(self): r""" Return the twisting derivation of the pseudomorphism. @@ -241,3 +242,16 @@ def twisting_derivation(self): d/dx """ return self.derivation + + def twisting_morphism(self): + r""" + Return the twisting homomorphism of the pseudomorphism. + + EXAMPLES:: + + sage: Fq = GF(343); M = Fq^3; frob = Fq.frobenius_endomorphism() + sage: ph = M.pseudohom([[1, 2, 3], [0, 1, 1], [2, 1, 1]], frob, side="right") + sage: ph.twisting_morphism() + Frobenius endomorphism z3 |--> z3^7 on Finite Field in z3 of size 7^3 + """ + return self.twist_morphism From f2751254d9ce717b62ffe1d7b8a32550345fe438 Mon Sep 17 00:00:00 2001 From: ymusleh Date: Wed, 21 Feb 2024 01:01:28 -0500 Subject: [PATCH 077/369] Removed extraneous code from free module pseudomorphism methods --- src/sage/modules/free_module.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/sage/modules/free_module.py b/src/sage/modules/free_module.py index ece22120141..8fde8c3ff7d 100644 --- a/src/sage/modules/free_module.py +++ b/src/sage/modules/free_module.py @@ -3098,9 +3098,7 @@ def pseudohom(self, morphism, twist=None, codomain=None, **kwds): Codomain: Vector space of dimension 2 over Finite Field in z2 of size 5^2 """ from sage.modules.free_module_pseudomorphism import FreeModulePseudoMorphism - from sage.structure.element import is_Matrix - side = kwds.get("side", "left") - return FreeModulePseudoMorphism(self, morphism, twist, codomain, side) + return FreeModulePseudoMorphism(self, morphism, twist, codomain, **kwds) def inner_product_matrix(self): """ From 1134baaa1b7824db6b7623c8e21baab0a935365c Mon Sep 17 00:00:00 2001 From: ymusleh Date: Wed, 21 Feb 2024 17:31:32 -0500 Subject: [PATCH 078/369] refactor pseudomorphism constructor --- src/sage/modules/free_module.py | 5 +- .../modules/free_module_pseudohomspace.py | 26 +------- .../modules/free_module_pseudomorphism.py | 62 ++++++++++--------- 3 files changed, 40 insertions(+), 53 deletions(-) diff --git a/src/sage/modules/free_module.py b/src/sage/modules/free_module.py index 8fde8c3ff7d..b7cd80e61ad 100644 --- a/src/sage/modules/free_module.py +++ b/src/sage/modules/free_module.py @@ -3079,7 +3079,7 @@ def PseudoHom(self, twist=None, codomain=None): """ from sage.modules.free_module_pseudohomspace import FreeModulePseudoHomspace - return FreeModulePseudoHomspace(self, codomain, twist=twist) + return FreeModulePseudoHomspace(self, codomain=codomain, twist=twist) def pseudohom(self, morphism, twist=None, codomain=None, **kwds): r""" @@ -3098,7 +3098,8 @@ def pseudohom(self, morphism, twist=None, codomain=None, **kwds): Codomain: Vector space of dimension 2 over Finite Field in z2 of size 5^2 """ from sage.modules.free_module_pseudomorphism import FreeModulePseudoMorphism - return FreeModulePseudoMorphism(self, morphism, twist, codomain, **kwds) + side = kwds.get("side", "left") + return FreeModulePseudoMorphism(self.PseudoHom(twist=twist, codomain=codomain), morphism, side) def inner_product_matrix(self): """ diff --git a/src/sage/modules/free_module_pseudohomspace.py b/src/sage/modules/free_module_pseudohomspace.py index 5299ff2fca7..65af841f37b 100644 --- a/src/sage/modules/free_module_pseudohomspace.py +++ b/src/sage/modules/free_module_pseudohomspace.py @@ -159,40 +159,20 @@ def _element_constructor_(self, A, **kwds): sage: morph = M.hom(matrix([[1, 2], [1, 1]])) sage: phi = PHS._element_constructor_(morph, side="right"); phi Free module pseudomorphism defined as left-multiplication by the matrix + [1 2] [1 1] - [2 1] twisted by the morphism Frobenius endomorphism z3 |--> z3^5 on Finite Field in z3 of size 5^3 Domain: Vector space of dimension 2 over Finite Field in z3 of size 5^3 Codomain: Vector space of dimension 2 over Finite Field in z3 of size 5^3 """ - from . import free_module_pseudomorphism + from . import free_module_pseudomorphism as pseudo side = kwds.get("side", "left") - if not is_Matrix(A): - C = self.codomain() - try: - if callable(A): - v = [C(A(g)) for g in self.domain().gens()] - A = matrix([C.coordinates(a) for a in v], ncols=C.rank()) - if side == "right": - A = A.transpose() - else: - v = [C(a) for a in A] - if side == "right": - A = matrix([C.coordinates(a) for a in v], - ncols=C.rank()).transpose() - else: - A = matrix([C.coordinates(a) for a in v], - ncols=C.rank()) - except TypeError: - pass if not self.codomain().base_ring().has_coerce_map_from( self.domain().base_ring()) and not A.is_zero(): raise TypeError("nontrivial morphisms require a coercion map" "from the base ring of the domain to the base ring of the" "codomain") - return free_module_pseudomorphism.FreeModulePseudoMorphism( - self.domain(), A, twist=self.twist, - codomain=self.codomain(), side=side) + return pseudo.FreeModulePseudoMorphism(self, A, side=side) def _matrix_space(self): r""" diff --git a/src/sage/modules/free_module_pseudomorphism.py b/src/sage/modules/free_module_pseudomorphism.py index 2c3f0491407..0051aaf9528 100644 --- a/src/sage/modules/free_module_pseudomorphism.py +++ b/src/sage/modules/free_module_pseudomorphism.py @@ -31,6 +31,7 @@ from sage.modules.free_module_morphism import FreeModuleMorphism from sage.modules.free_module_homspace import FreeModuleHomspace, is_FreeModuleHomspace from sage.matrix.constructor import matrix +from sage.matrix.matrix_space import MatrixSpace lazy_import('sage.rings.derivation', 'RingDerivation') @@ -62,6 +63,8 @@ class FreeModulePseudoMorphism(Morphism): sage: f(V((1, 2))) (-4, 1) + :: + sage: P. = ZZ[]; deriv = P.derivation() sage: M = P^2 sage: f = M.pseudohom([[1, 2*x], [x, 1]], deriv, side="right"); f @@ -77,23 +80,32 @@ class FreeModulePseudoMorphism(Morphism): sage: f = M.pseudohom([[1, 2], [1, 1]], deriv) sage: f(e) (x^3 + 2*x^2 + 14*x + 8, x^3 + 7*x^2 + 13*x + 13) + + :: + + sage: Fq = GF(343); M = Fq^3; N = Fq^2; frob = Fq.frobenius_endomorphism(); z = Fq.gen() + sage: phi = M.pseudohom([[2, 3, 1], [1, 4, 6]], frob, N, side="right"); phi + Free module pseudomorphism defined as left-multiplication by the matrix + [2 3 1] + [1 4 6] + twisted by the morphism Frobenius endomorphism z3 |--> z3^7 on Finite Field in z3 of size 7^3 + Domain: Vector space of dimension 3 over Finite Field in z3 of size 7^3 + Codomain: Vector space of dimension 2 over Finite Field in z3 of size 7^3 + sage: elem = (4*z^2 + 4*z + 3, 2, z + 5) + sage: phi(elem) + (2*z3 + 1, 6*z3^2 + 4*z3 + 5) """ - def __init__(self, domain, base_morphism, twist=None, codomain=None, side="left"): + def __init__(self, pseudohomspace, base_morphism, side="left"): """ Constructs a pseudomorphism of free modules. INPUT: - - ``domain`` - the domain of the pseudomorphism; a free module + - ``pseudohomspace`` - the parent space of pseudomorphisms, + containing - ``base_morphism`` - either a morphism or a matrix defining a morphism - - ``twist`` - a twisting morphism, this is either a morphism or - a derivation (default: None) - - - ``codomain`` - the codomain of the pseudomorphism; a free - module (default: None) - - side -- side of the vectors acted on by the matrix (default: ``"left"``) @@ -116,28 +128,22 @@ def __init__(self, domain, base_morphism, twist=None, codomain=None, side="left" Domain: Vector space of dimension 2 over Finite Field in z3 of size 5^3 Codomain: Vector space of dimension 2 over Finite Field in z3 of size 5^3 """ - from sage.structure.element import is_Matrix - Morphism.__init__(self, domain.PseudoHom(twist, codomain)) - if is_Matrix(base_morphism): - self._base_matrix = base_morphism - elif isinstance(base_morphism, Morphism): - self._base_matrix = base_morphism.matrix() + Morphism.__init__(self, pseudohomspace) + dom = pseudohomspace.domain() + codom = pseudohomspace.codomain() + rows = dom.dimension() + cols = codom.dimension() + if side == "right": + rows = codom.dimension() + cols = dom.dimension() + matrix_space = MatrixSpace(dom.coordinate_ring(), rows, cols) + if isinstance(base_morphism, FreeModuleMorphism): + self._base_matrix = matrix_space(base_morphism.matrix()) else: - self._base_matrix = matrix(domain.coordinate_ring(), - base_morphism) - self.derivation = None - self.twist_morphism = None + self._base_matrix = matrix_space(base_morphism) + self.derivation = pseudohomspace.derivation + self.twist_morphism = pseudohomspace.twist_morphism self.side = side - if isinstance(twist, Morphism): - self.twist_morphism = twist - elif isinstance(twist, RingDerivation): - self.twist_morphism = twist.parent().twisting_morphism() - if twist: - self.derivation = twist - else: - self.derivation = None - elif twist is not None: - raise TypeError("twist is not a ring morphism or derivation") def _call_(self, x): r""" From 0688f74e84eb85cc6d6a24d925d157f3a63d7bfd Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Sat, 24 Feb 2024 10:50:25 +0100 Subject: [PATCH 079/369] 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 080/369] 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 081/369] 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 082/369] 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 083/369] 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 084/369] 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 085/369] 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 4032e432fa6cb771f65abcbdc21a9ca86c380221 Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Wed, 6 Mar 2024 17:42:34 +0100 Subject: [PATCH 086/369] Adapted `.ramified_places` slightly, removed docstring modifications --- src/doc/en/reference/references/index.rst | 3 +- .../algebras/quatalg/quaternion_algebra.py | 36 ++++++++++--------- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index b0194d1eef7..09b78514e1c 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -6360,8 +6360,7 @@ REFERENCES: .. [Voi2012] \J. Voight. Identifying the matrix ring: algorithms for quaternion algebras and quadratic forms, to appear. -.. [Voi2021] \J. Voight. Quaternion Algebras. Graduate Texts in - Mathematics 288. Springer Cham, 2021. +.. [Voi2021] \J. Voight. Quaternion algebras, Springer Nature (2021). .. [VS06] \G.D. Villa Salvador. Topics in the Theory of Algebraic Function Fields. Birkh\"auser, 2006. diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index e655004fa6f..97ffc7c21bc 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -1202,18 +1202,21 @@ def ramified_places(self, inf=True): return ram_fin, [] - try: - # Over the number field F, first compute the finite ramified places - ram_fin = [p for p in set(F.primes_above(2)).union(F.primes_above(self._a), - F.primes_above(self._b)) if F.hilbert_symbol(self._a, self._b, p) == -1] + # At this point F needs to be a number field + # Note: Support for global function fields will be added in a future update + if not F is in NumberFields(): + raise NotImplementedError("base field must be rational numbers or a number field") + + # Over the number field F, first compute the finite ramified places + ram_fin = [p for p in set(F.primes_above(2)).union(F.primes_above(self._a), + F.primes_above(self._b)) if F.hilbert_symbol(self._a, self._b, p) == -1] - if not inf: - return ram_fin + if not inf: + return ram_fin - # At this point the infinite ramified places also need to be computed - return ram_fin, [e for e in F.real_embeddings() if F.hilbert_symbol(self._a, self._b, e) == -1] - except (AttributeError, NotImplementedError): - raise ValueError("base field must be rational numbers or a number field") + # At this point the infinite ramified places also need to be computed + return ram_fin, [e for e in F.real_embeddings() if F.hilbert_symbol(self._a, self._b, e) == -1] + @cached_method def ramified_primes(self): @@ -2035,8 +2038,7 @@ def is_maximal(self): r""" Check whether the order of ``self`` is maximal in the ambient quaternion algebra. - Only implemented for quaternion algebras over number fields; for reference, - see Theorem 15.5.5 in [Voi2021]_. + Only works in quaternion algebras over number fields OUTPUT: Boolean @@ -3502,12 +3504,12 @@ def cyclic_right_subideals(self, p, alpha=None): def is_integral(self): r""" - Checks whether a quaternion fractional ideal is integral. An ideal in a quaternion algebra - is integral if and only if it is contained in its left order. If the left order is already - defined this method just checks this definition, otherwise it uses one of the alternative - definitions from Lemma 16.2.8 of [Voi2021]_. + Check if a quaternion fractional ideal is integral. An ideal in a quaternion algebra is + said integral if it is contained in its left order. If the left order is already defined it just + check the definition, otherwise it uses one of the alternative definition of Lemma 16.2.8 of + [Voi2021]_. - OUTPUT: A boolean. + OUTPUT: a boolean. EXAMPLES:: From 89dae3f4289aee0b8bfcb97ce2001e2f8b577a6a Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Wed, 6 Mar 2024 18:02:52 +0100 Subject: [PATCH 087/369] Fixed LINT --- src/sage/algebras/quatalg/quaternion_algebra.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index 97ffc7c21bc..1e7ff5184ca 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -1204,9 +1204,9 @@ def ramified_places(self, inf=True): # At this point F needs to be a number field # Note: Support for global function fields will be added in a future update - if not F is in NumberFields(): - raise NotImplementedError("base field must be rational numbers or a number field") - + if F not in NumberFields(): + raise ValueError("base field must be rational numbers or a number field") + # Over the number field F, first compute the finite ramified places ram_fin = [p for p in set(F.primes_above(2)).union(F.primes_above(self._a), F.primes_above(self._b)) if F.hilbert_symbol(self._a, self._b, p) == -1] @@ -1216,7 +1216,6 @@ def ramified_places(self, inf=True): # At this point the infinite ramified places also need to be computed return ram_fin, [e for e in F.real_embeddings() if F.hilbert_symbol(self._a, self._b, e) == -1] - @cached_method def ramified_primes(self): @@ -1300,10 +1299,7 @@ def discriminant(self): if is_RationalField(F): return ZZ.prod(self.ramified_places(inf=False)) - try: - return F.ideal(F.prod(self.ramified_places(inf=False))) - except NotImplementedError: - raise ValueError("base field must be rational numbers or a number field") + return F.ideal(F.prod(self.ramified_places(inf=False))) def is_isomorphic(self, A) -> bool: """ From deb764e284b0b401012ee871daea7fecffe7a4bb Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Fri, 8 Mar 2024 21:26:42 +0100 Subject: [PATCH 088/369] Small rewrite of `.is_totally_definite()` --- src/sage/algebras/quatalg/quaternion_algebra.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index 1e7ff5184ca..0880281edab 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -1110,12 +1110,12 @@ def is_totally_definite(self): if is_RationalField(F): return self.is_definite() - try: - E = F.real_embeddings() - return [e for e in E if F.hilbert_symbol(self._a, self._b, e) == -1] == E - except (AttributeError, NotImplementedError): + if F not in NumberFields(): raise ValueError("base field must be rational numbers or a number field") + E = F.real_embeddings() + return [e for e in E if F.hilbert_symbol(self._a, self._b, e) == -1] == E + @cached_method def ramified_places(self, inf=True): """ From ad7d628d43e78e566077151e8838a13fb96a0e9a Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Sat, 9 Mar 2024 00:10:17 +0100 Subject: [PATCH 089/369] Cleaned up some code --- .../algebras/quatalg/quaternion_algebra.py | 71 +++++++++++-------- 1 file changed, 40 insertions(+), 31 deletions(-) diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index 0880281edab..92c7efc852d 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -403,8 +403,8 @@ def is_commutative(self) -> bool: def is_division_algebra(self) -> bool: """ - Checks whether the quaternion algebra ``self`` is a division algebra, i.e. whether - every nonzero element in ``self`` is invertible. + Check whether the quaternion algebra ``self`` is a division algebra, + i.e. whether every nonzero element in ``self`` is invertible. EXAMPLES:: @@ -432,8 +432,8 @@ def is_division_algebra(self) -> bool: def is_matrix_ring(self) -> bool: """ - Checks whether the quaternion algebra ``self`` is isomorphic to the 2x2 matrix - ring over the base field. + Check whether the quaternion algebra ``self`` is isomorphic to the + 2x2 matrix ring over the base field. EXAMPLES:: @@ -1057,9 +1057,11 @@ def inner_product_matrix(self): def is_definite(self): """ - Checks whether the quaternion algebra ``self`` is definite, i.e. whether it ramifies at the - unique Archimedean place of its base field QQ. This is the case if and only if both - invariants of ``self`` are negative; see Exercise 2.4(c) in [Voi2021]_. + Check whether the quaternion algebra ``self`` is definite, i.e. whether + it ramifies at the unique Archimedean place of its base field `QQ`. + + A quaternion algebra over `QQ` is definite if and only if both of + its invariants are negative (see Exercise 2.4(c) in [Voi2021]_). EXAMPLES:: @@ -1080,8 +1082,8 @@ def is_definite(self): def is_totally_definite(self): """ - Checks whether the quaternion algebra ``self`` is totally definite, i.e. whether it ramifies - at all real Archimedean places of its base number field. + Check whether the quaternion algebra ``self`` is totally definite, i.e. + whether it ramifies at all real Archimedean places of its base number field. EXAMPLES:: @@ -1119,11 +1121,13 @@ def is_totally_definite(self): @cached_method def ramified_places(self, inf=True): """ - Return the places of the base number field at which the quaternion algebra ``self`` ramifies. + Return the places of the base number field at which the quaternion + algebra``self`` ramifies. - Note: The initial choice of primes (in the case F = QQ) respectively of prime ideals (in the - number field case) to check ramification for is motivated by 12.4.12(a) in [Voi2021]_. The - restriction to real Archimedean embeddings is due to 14.5.8 in [Voi2021]_. + Note: The initial choice of primes (in the case F = QQ) respectively + of prime ideals (in the number field case) to check ramification for + is motivated by 12.4.12(a) in [Voi2021]_. The restriction to real + Archimedean embeddings is due to 14.5.8 in [Voi2021]_. INPUT: @@ -1131,11 +1135,12 @@ def ramified_places(self, inf=True): OUTPUT: - The non-Archimedean (AKA finite) places at which ``self`` ramifies (given as elements of ZZ, - sorted small to large, if ``self`` is defined over the rational field QQ, respectively as - fractional ideals of the number field's ring of integers, otherwise) and, if ``inf`` is set - to ``True``, also the Archimedean (AKA infinite) places at which ``self`` ramifies (given - by real embeddings of the base field). + The non-Archimedean (AKA finite) places at which ``self`` ramifies (given + as elements of ZZ, sorted small to large, if ``self`` is defined over the + rational field QQ, respectively as fractional ideals of the number field's + ring of integers, otherwise) and, if ``inf`` is set to ``True``, also the + Archimedean (AKA infinite) places at which ``self`` ramifies (given by + real embeddings of the base field). EXAMPLES:: @@ -1185,18 +1190,22 @@ def ramified_places(self, inf=True): raise ValueError("inf must be a truth value") F = self.base_ring() + a = self._a + b = self._b - # For efficiency (and to not convert QQ into a number field manually), we handle F = QQ first + # For efficiency (and to not convert QQ into a number field manually), + # we handle the case F = QQ first if is_RationalField(F): - ram_fin = sorted([p for p in set([2]).union(prime_divisors(self._a.numerator()), - prime_divisors(self._a.denominator()), prime_divisors(self._b.numerator()), - prime_divisors(self._b.denominator())) if hilbert_symbol(self._a, self._b, p) == -1]) + ram_fin = sorted([p for p in set([2]).union( + prime_divisors(a.numerator()), prime_divisors(a.denominator()), + prime_divisors(b.numerator()), prime_divisors(b.denominator())) + if hilbert_symbol(a, b, p) == -1]) if not inf: return ram_fin - # The given quaternion algebra ramifies at the unique infinite place of QQ, by definition, - # if and only if it is definite + # The given quaternion algebra ramifies at the unique infinite place + # of QQ, by definition, if and only if it is definite if self.is_definite(): return ram_fin, QQ.places() @@ -1208,14 +1217,14 @@ def ramified_places(self, inf=True): raise ValueError("base field must be rational numbers or a number field") # Over the number field F, first compute the finite ramified places - ram_fin = [p for p in set(F.primes_above(2)).union(F.primes_above(self._a), - F.primes_above(self._b)) if F.hilbert_symbol(self._a, self._b, p) == -1] + ram_fin = [p for p in set(F.primes_above(2)).union(F.primes_above(a), + F.primes_above(b)) if F.hilbert_symbol(a, b, p) == -1] if not inf: return ram_fin # At this point the infinite ramified places also need to be computed - return ram_fin, [e for e in F.real_embeddings() if F.hilbert_symbol(self._a, self._b, e) == -1] + return ram_fin, [e for e in F.real_embeddings() if F.hilbert_symbol(a, b, e) == -1] @cached_method def ramified_primes(self): @@ -1303,10 +1312,10 @@ def discriminant(self): def is_isomorphic(self, A) -> bool: """ - Checks whether ``self`` and ``A`` are isomorphic quaternion algebras. + Check whether ``self`` and ``A`` are isomorphic quaternion algebras. - Currently only implemented over a number field; motivated by Main Theorem 14.6.1 - in [Voi2021]_, noting that QQ has a unique infinite place. + Currently only implemented over a number field; motivated by Main + Theorem 14.6.1 in [Voi2021]_, noting that QQ has a unique infinite place. INPUT: @@ -1334,7 +1343,7 @@ def is_isomorphic(self, A) -> bool: F = self.base_ring() if F != A.base_ring(): - raise ValueError("both quaternion algebras must be defined over the same base ring") + raise ValueError("both quaternion algebras must be defined over the same ring") if is_RationalField(F): return self.ramified_places(inf=False) == A.ramified_places(inf=False) From 6319a5636a36842484280362e0b74e24d49af456 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Wed, 20 Mar 2024 09:37:22 +0100 Subject: [PATCH 090/369] Update rings.py issue instead of trac --- src/sage/categories/rings.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/categories/rings.py b/src/sage/categories/rings.py index 95fef4453c1..6dc83b6b746 100644 --- a/src/sage/categories/rings.py +++ b/src/sage/categories/rings.py @@ -589,7 +589,7 @@ def _ideal_class_(self, n=0): EXAMPLES: - Since :trac:`7797`, non-commutative rings have ideals as well:: + Since :issue:`7797`, non-commutative rings have ideals as well:: sage: A = SteenrodAlgebra(2) # needs sage.combinat sage.modules sage: A._ideal_class_() # needs sage.combinat sage.modules @@ -646,7 +646,7 @@ def zero_ideal(self): TESTS: - Make sure that :trac:`13644` is fixed:: + Make sure that :issue:`13644` is fixed:: sage: # needs sage.rings.padics sage: K = Qp(3) From afe236396113102163e9d374d04fcb1ecb4c33b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Wed, 20 Mar 2024 14:14:49 +0100 Subject: [PATCH 091/369] move ideals to the category of rngs (non-unital rings) --- .../finite_dimensional_algebra.py | 2 +- .../finite_dimensional_algebra_morphism.py | 4 +- src/sage/categories/rings.py | 114 ---------------- src/sage/categories/rngs.py | 124 +++++++++++++++++- 4 files changed, 125 insertions(+), 119 deletions(-) diff --git a/src/sage/algebras/finite_dimensional_algebras/finite_dimensional_algebra.py b/src/sage/algebras/finite_dimensional_algebras/finite_dimensional_algebra.py index a3b8742b4e0..f2d4600a35b 100644 --- a/src/sage/algebras/finite_dimensional_algebras/finite_dimensional_algebra.py +++ b/src/sage/algebras/finite_dimensional_algebras/finite_dimensional_algebra.py @@ -760,7 +760,7 @@ def quotient_map(self, ideal): EXAMPLES:: sage: A = FiniteDimensionalAlgebra(GF(3), [Matrix([[1, 0], [0, 1]]), - ....: Matrix([[0, 1], [0, 0]])]) + ....: Matrix([[0, 1], [0, 0]])], assume_associative=True) sage: q0 = A.quotient_map(A.zero_ideal()); q0 Morphism from Finite-dimensional algebra of degree 2 over Finite Field of size 3 diff --git a/src/sage/algebras/finite_dimensional_algebras/finite_dimensional_algebra_morphism.py b/src/sage/algebras/finite_dimensional_algebras/finite_dimensional_algebra_morphism.py index d2114511ae6..bd5b1691a06 100644 --- a/src/sage/algebras/finite_dimensional_algebras/finite_dimensional_algebra_morphism.py +++ b/src/sage/algebras/finite_dimensional_algebras/finite_dimensional_algebra_morphism.py @@ -181,7 +181,8 @@ def inverse_image(self, I): EXAMPLES:: sage: A = FiniteDimensionalAlgebra(QQ, [Matrix([[1, 0], [0, 1]]), - ....: Matrix([[0, 1], [0, 0]])]) + ....: Matrix([[0, 1], [0, 0]])], + ....: assume_associative=True) sage: I = A.maximal_ideal() # needs sage.libs.pari sage: q = A.quotient_map(I) # needs sage.libs.pari sage: B = q.codomain() # needs sage.libs.pari @@ -191,6 +192,7 @@ def inverse_image(self, I): coker_I = I.basis_matrix().transpose().kernel().basis_matrix().transpose() return self.domain().ideal((self._matrix * coker_I).kernel().basis_matrix(), given_by_matrix=True) + class FiniteDimensionalAlgebraHomset(RingHomset_generic): """ Set of morphisms between two finite-dimensional algebras. diff --git a/src/sage/categories/rings.py b/src/sage/categories/rings.py index 6dc83b6b746..227a3301f61 100644 --- a/src/sage/categories/rings.py +++ b/src/sage/categories/rings.py @@ -543,120 +543,6 @@ def __pow__(self, n): from sage.modules.free_module import FreeModule return FreeModule(self, n) - @cached_method - def ideal_monoid(self): - """ - The monoid of the ideals of this ring. - - .. NOTE:: - - The code is copied from the base class of rings. - This is since there are rings that do not inherit - from that class, such as matrix algebras. See - :issue:`7797`. - - EXAMPLES:: - - sage: # needs sage.modules - sage: MS = MatrixSpace(QQ, 2, 2) - sage: isinstance(MS, Ring) - False - sage: MS in Rings() - True - sage: MS.ideal_monoid() - Monoid of ideals of Full MatrixSpace of 2 by 2 dense matrices - over Rational Field - - Note that the monoid is cached:: - - sage: MS.ideal_monoid() is MS.ideal_monoid() # needs sage.modules - True - """ - try: - from sage.rings.ideal_monoid import IdealMonoid - return IdealMonoid(self) - except TypeError: - from sage.rings.noncommutative_ideals import IdealMonoid_nc - return IdealMonoid_nc(self) - - def _ideal_class_(self, n=0): - r""" - Return a callable object that can be used to create ideals in this - ring. - - The argument `n`, standing for the number of generators - of the ideal, is ignored. - - EXAMPLES: - - Since :issue:`7797`, non-commutative rings have ideals as well:: - - sage: A = SteenrodAlgebra(2) # needs sage.combinat sage.modules - sage: A._ideal_class_() # needs sage.combinat sage.modules - - """ - from sage.rings.noncommutative_ideals import Ideal_nc - return Ideal_nc - - def principal_ideal(self, gen, coerce=True): - """ - Return the principal ideal generated by ``gen``. - - EXAMPLES:: - - sage: R. = ZZ[] - sage: R.principal_ideal(x+2*y) - Ideal (x + 2*y) of Multivariate Polynomial Ring in x, y over Integer Ring - """ - C = self._ideal_class_(1) - if coerce: - gen = self(gen) - return C(self, [gen]) - - @cached_method - def unit_ideal(self): - """ - Return the unit ideal of this ring. - - EXAMPLES:: - - sage: Zp(7).unit_ideal() # needs sage.rings.padics - Principal ideal (1 + O(7^20)) of 7-adic Ring with capped relative precision 20 - """ - return self.principal_ideal(self.one(), coerce=False) - - @cached_method - def zero_ideal(self): - """ - Return the zero ideal of this ring (cached). - - EXAMPLES:: - - sage: ZZ.zero_ideal() - Principal ideal (0) of Integer Ring - sage: QQ.zero_ideal() - Principal ideal (0) of Rational Field - sage: QQ['x'].zero_ideal() - Principal ideal (0) of Univariate Polynomial Ring in x over Rational Field - - The result is cached:: - - sage: ZZ.zero_ideal() is ZZ.zero_ideal() - True - - TESTS: - - Make sure that :issue:`13644` is fixed:: - - sage: # needs sage.rings.padics - sage: K = Qp(3) - sage: R. = K[] - sage: L. = K.extension(a^2-3) - sage: L.ideal(a) - Principal ideal (1 + O(a^40)) of 3-adic Eisenstein Extension Field in a defined by a^2 - 3 - """ - return self.principal_ideal(self.zero(), coerce=False) - def characteristic(self): """ Return the characteristic of this ring. diff --git a/src/sage/categories/rngs.py b/src/sage/categories/rngs.py index 1841b45d1d5..3d890a4af74 100644 --- a/src/sage/categories/rngs.py +++ b/src/sage/categories/rngs.py @@ -1,18 +1,20 @@ r""" Rngs """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2008 Teresa Gomez-Diaz (CNRS) # 2012 Nicolas M. Thiery # # Distributed under the terms of the GNU General Public License (GPL) -# http://www.gnu.org/licenses/ -#****************************************************************************** +# https://www.gnu.org/licenses/ +# ***************************************************************************** +from sage.misc.cachefunc import cached_method from sage.categories.category_with_axiom import CategoryWithAxiom from sage.misc.lazy_import import LazyImport from sage.categories.magmas_and_additive_magmas import MagmasAndAdditiveMagmas + class Rngs(CategoryWithAxiom): """ The category of rngs. @@ -47,3 +49,119 @@ class Rngs(CategoryWithAxiom): _base_category_class_and_axiom = (MagmasAndAdditiveMagmas.Distributive.AdditiveAssociative.AdditiveCommutative.AdditiveUnital.Associative, "AdditiveInverse") Unital = LazyImport('sage.categories.rings', 'Rings', at_startup=True) + + class ParentMethods: + + @cached_method + def ideal_monoid(self): + """ + The monoid of the ideals of this ring. + + .. NOTE:: + + The code is copied from the base class of rings. + This is since there are rings that do not inherit + from that class, such as matrix algebras. See + :issue:`7797`. + + EXAMPLES:: + + sage: # needs sage.modules + sage: MS = MatrixSpace(QQ, 2, 2) + sage: isinstance(MS, Ring) + False + sage: MS in Rings() + True + sage: MS.ideal_monoid() + Monoid of ideals of Full MatrixSpace of 2 by 2 dense matrices + over Rational Field + + Note that the monoid is cached:: + + sage: MS.ideal_monoid() is MS.ideal_monoid() # needs sage.modules + True + """ + try: + from sage.rings.ideal_monoid import IdealMonoid + return IdealMonoid(self) + except TypeError: + from sage.rings.noncommutative_ideals import IdealMonoid_nc + return IdealMonoid_nc(self) + + def _ideal_class_(self, n=0): + r""" + Return a callable object that can be used to create ideals in this + ring. + + The argument `n`, standing for the number of generators + of the ideal, is ignored. + + EXAMPLES: + + Since :issue:`7797`, non-commutative rings have ideals as well:: + + sage: A = SteenrodAlgebra(2) # needs sage.combinat sage.modules + sage: A._ideal_class_() # needs sage.combinat sage.modules + + """ + from sage.rings.noncommutative_ideals import Ideal_nc + return Ideal_nc + + def principal_ideal(self, gen, coerce=True): + """ + Return the principal ideal generated by ``gen``. + + EXAMPLES:: + + sage: R. = ZZ[] + sage: R.principal_ideal(x+2*y) + Ideal (x + 2*y) of Multivariate Polynomial Ring in x, y over Integer Ring + """ + C = self._ideal_class_(1) + if coerce: + gen = self(gen) + return C(self, [gen]) + + @cached_method + def unit_ideal(self): + """ + Return the unit ideal of this ring. + + EXAMPLES:: + + sage: Zp(7).unit_ideal() # needs sage.rings.padics + Principal ideal (1 + O(7^20)) of 7-adic Ring with capped relative precision 20 + """ + return self.principal_ideal(self.one(), coerce=False) + + @cached_method + def zero_ideal(self): + """ + Return the zero ideal of this ring (cached). + + EXAMPLES:: + + sage: ZZ.zero_ideal() + Principal ideal (0) of Integer Ring + sage: QQ.zero_ideal() + Principal ideal (0) of Rational Field + sage: QQ['x'].zero_ideal() + Principal ideal (0) of Univariate Polynomial Ring in x over Rational Field + + The result is cached:: + + sage: ZZ.zero_ideal() is ZZ.zero_ideal() + True + + TESTS: + + Make sure that :issue:`13644` is fixed:: + + sage: # needs sage.rings.padics + sage: K = Qp(3) + sage: R. = K[] + sage: L. = K.extension(a^2-3) + sage: L.ideal(a) + Principal ideal (1 + O(a^40)) of 3-adic Eisenstein Extension Field in a defined by a^2 - 3 + """ + return self.principal_ideal(self.zero(), coerce=False) From 9a518a662f0b176bbaedb736faa99e682a5bca4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Wed, 20 Mar 2024 14:43:16 +0100 Subject: [PATCH 092/369] move back unit_ideal to cat. of rings --- src/sage/categories/rings.py | 12 ++++++++++++ src/sage/categories/rngs.py | 12 ------------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/sage/categories/rings.py b/src/sage/categories/rings.py index 227a3301f61..3fa7757d161 100644 --- a/src/sage/categories/rings.py +++ b/src/sage/categories/rings.py @@ -543,6 +543,18 @@ def __pow__(self, n): from sage.modules.free_module import FreeModule return FreeModule(self, n) + @cached_method + def unit_ideal(self): + """ + Return the unit ideal of this ring. + + EXAMPLES:: + + sage: Zp(7).unit_ideal() # needs sage.rings.padics + Principal ideal (1 + O(7^20)) of 7-adic Ring with capped relative precision 20 + """ + return self.principal_ideal(self.one(), coerce=False) + def characteristic(self): """ Return the characteristic of this ring. diff --git a/src/sage/categories/rngs.py b/src/sage/categories/rngs.py index 3d890a4af74..1adf932cb1c 100644 --- a/src/sage/categories/rngs.py +++ b/src/sage/categories/rngs.py @@ -122,18 +122,6 @@ def principal_ideal(self, gen, coerce=True): gen = self(gen) return C(self, [gen]) - @cached_method - def unit_ideal(self): - """ - Return the unit ideal of this ring. - - EXAMPLES:: - - sage: Zp(7).unit_ideal() # needs sage.rings.padics - Principal ideal (1 + O(7^20)) of 7-adic Ring with capped relative precision 20 - """ - return self.principal_ideal(self.one(), coerce=False) - @cached_method def zero_ideal(self): """ From a2c85642611ab051d574f031c160c6a9ec07335f Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Mon, 25 Mar 2024 18:00:41 +0100 Subject: [PATCH 093/369] Small doctest fix --- src/sage/algebras/quatalg/quaternion_algebra.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index 7689248b5b5..59b8901b93e 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -1179,7 +1179,7 @@ def ramified_places(self, inf=True): To: Real Field with 53 bits of precision Defn: a |--> -0.618033988749895]) - sage: QuaternionAlgebra(QQ[sqrt(2)], 3, 19).ramified_places() # needs sage.symbolic + sage: QuaternionAlgebra(QQ[sqrt(2)], 3, 19).ramified_places() # needs sage.symbolic ([], []) sage: QuaternionAlgebra(RR(2.),1).ramified_places() Traceback (most recent call last): From 914d1b883d2ac5fbd8c866fed154bda4a82e128c Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Mon, 25 Mar 2024 18:18:05 +0100 Subject: [PATCH 094/369] Error type correction --- src/sage/algebras/quatalg/quaternion_algebra.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index 59b8901b93e..ccab3266648 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -1187,7 +1187,7 @@ def ramified_places(self, inf=True): ValueError: base field must be rational numbers or a number field """ if not isinstance(inf, bool): - raise ValueError("inf must be a truth value") + raise TypeError("inf must be a truth value") F = self.base_ring() a = self._a From 9c324bf9a5a351e3279c91180c68c37fe3f430f3 Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Tue, 26 Mar 2024 13:40:02 +0100 Subject: [PATCH 095/369] Remove redundant copy of `.is_definite()` --- .../algebras/quatalg/quaternion_algebra.py | 23 ------------------- 1 file changed, 23 deletions(-) diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index 1b4972c373c..658f8c22869 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -930,29 +930,6 @@ def invariants(self): """ return self._a, self._b - def is_definite(self): - """ - Checks whether the quaternion algebra ``self`` is definite, i.e. whether it ramifies at the - unique Archimedean place of its base field QQ. This is the case if and only if both - invariants of ``self`` are negative. - - EXAMPLES:: - - sage: QuaternionAlgebra(QQ,-5,-2).is_definite() - True - sage: QuaternionAlgebra(1).is_definite() - False - - sage: QuaternionAlgebra(RR(2.),1).is_definite() - Traceback (most recent call last): - ... - ValueError: base field must be rational numbers - """ - if not is_RationalField(self.base_ring()): - raise ValueError("base field must be rational numbers") - a, b = self.invariants() - return a < 0 and b < 0 - def __eq__(self, other): """ Compare self and other. From 15f8fa5ccadffbaba5f5c7c35cf851c3523cf822 Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Tue, 26 Mar 2024 13:51:25 +0100 Subject: [PATCH 096/369] Some doctest refactoring Amend: Refactored `.is_definite()` --- .../algebras/quatalg/quaternion_algebra.py | 65 ++++++++++--------- 1 file changed, 34 insertions(+), 31 deletions(-) diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index 658f8c22869..82401e1224e 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -403,8 +403,8 @@ def is_commutative(self) -> bool: def is_division_algebra(self) -> bool: """ - Check whether the quaternion algebra ``self`` is a division algebra, - i.e. whether every nonzero element in ``self`` is invertible. + Check whether the quaternion algebra is a division algebra, + i.e. whether every nonzero element in it is invertible. EXAMPLES:: @@ -432,7 +432,7 @@ def is_division_algebra(self) -> bool: def is_matrix_ring(self) -> bool: """ - Check whether the quaternion algebra ``self`` is isomorphic to the + Check whether the quaternion algebra is isomorphic to the 2x2 matrix ring over the base field. EXAMPLES:: @@ -1056,11 +1056,11 @@ def inner_product_matrix(self): return diagonal_matrix(self.base_ring(), [2, -2*a, -2*b, 2*a*b]) def is_definite(self): - """ - Check whether the quaternion algebra ``self`` is definite, i.e. whether - it ramifies at the unique Archimedean place of its base field `QQ`. + r""" + Check whether the given quaternion algebra is definite, i.e. whether + it ramifies at the unique Archimedean place of its base field `\QQ`. - A quaternion algebra over `QQ` is definite if and only if both of + A quaternion algebra over `\QQ` is definite if and only if both of its invariants are negative (see Exercise 2.4(c) in [Voi2021]_). EXAMPLES:: @@ -1121,13 +1121,13 @@ def is_totally_definite(self): @cached_method def ramified_places(self, inf=True): """ - Return the places of the base number field at which the quaternion - algebra``self`` ramifies. + Return the places of the base number field at which the given + quaternion algebra ramifies. - Note: The initial choice of primes (in the case F = QQ) respectively - of prime ideals (in the number field case) to check ramification for - is motivated by 12.4.12(a) in [Voi2021]_. The restriction to real - Archimedean embeddings is due to 14.5.8 in [Voi2021]_. + Note: The initial choice of primes (for the base field ``\\QQ``) + respectively of prime ideals (in the number field case) to check + ramification for is motivated by 12.4.12(a) in [Voi2021]_. The + restriction to real embeddings is due to 14.5.8 in [Voi2021]_. INPUT: @@ -1135,12 +1135,12 @@ def ramified_places(self, inf=True): OUTPUT: - The non-Archimedean (AKA finite) places at which ``self`` ramifies (given - as elements of ZZ, sorted small to large, if ``self`` is defined over the - rational field QQ, respectively as fractional ideals of the number field's - ring of integers, otherwise) and, if ``inf`` is set to ``True``, also the - Archimedean (AKA infinite) places at which ``self`` ramifies (given by - real embeddings of the base field). + The non-Archimedean (AKA finite) places at which ``self`` ramifies + (given as elements of ZZ, sorted small to large, if ``self`` is + defined over the rational field QQ, respectively as fractional ideals + of the number field's ring of integers, otherwise) and, if ``inf`` is + set to ``True``, also the Archimedean (AKA infinite) places at which + ``self`` ramifies (given by real embeddings of the base field). EXAMPLES:: @@ -1229,13 +1229,15 @@ def ramified_places(self, inf=True): @cached_method def ramified_primes(self): """ - Return the (finite) primes of the base number field at which the quaternion algebra ``self`` ramifies. + Return the (finite) primes of the base number field at which + the given quaternion algebra ramifies. OUTPUT: - The list of finite primes at which ``self`` ramifies; given as integers, sorted - small to large, if ``self`` is defined over QQ, and as fractional ideals in the - ring of integers of the base number field otherwise. + The list of finite primes at which ``self`` ramifies; given as + integers, sorted small to large, if ``self`` is defined over `\\QQ`, + and as fractional ideals in the ring of integers of the base number + field otherwise. EXAMPLES:: @@ -1265,15 +1267,16 @@ def ramified_primes(self): @cached_method def discriminant(self): - """ - Return the discriminant of the quaternion algebra ``self``, i.e. the product of the - finite places it ramifies at. + r""" + Return the discriminant of the given quaternion algebra, i.e. the + product of the finite places it ramifies at. OUTPUT: - The discriminant of this quaternion algebra (which has to be defined over a number field), - as an element of ZZ if ``self`` is defined over QQ, and as a fractional ideal in the - ring of integers of the base number field otherwise. + The discriminant of this quaternion algebra (which has to be defined + over a number field), as an element of `\ZZ` if the quaternion algebra + is defined over `\QQ`, and as a fractional ideal in the ring of + integers of the base number field otherwise. EXAMPLES:: @@ -1312,10 +1315,10 @@ def discriminant(self): def is_isomorphic(self, A) -> bool: """ - Check whether ``self`` and ``A`` are isomorphic quaternion algebras. + Check whether the given quaternion algebra is isomorphic to ``A``. Currently only implemented over a number field; motivated by Main - Theorem 14.6.1 in [Voi2021]_, noting that QQ has a unique infinite place. + Theorem 14.6.1 in [Voi2021]_, noting that QQ has a unique real place. INPUT: From d09005c52d4933dbd1b4662e90b5e358ab7edfa1 Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Tue, 26 Mar 2024 14:17:53 +0100 Subject: [PATCH 097/369] More small refactoring Amend: Even more refactoring of `.is_definite()` and `.is_totally_definite()` Amend 2: Corrected backtick error --- .../algebras/quatalg/quaternion_algebra.py | 52 ++++++++++--------- 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index 82401e1224e..fd287b0f490 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -403,7 +403,7 @@ def is_commutative(self) -> bool: def is_division_algebra(self) -> bool: """ - Check whether the quaternion algebra is a division algebra, + Check whether this quaternion algebra is a division algebra, i.e. whether every nonzero element in it is invertible. EXAMPLES:: @@ -432,7 +432,7 @@ def is_division_algebra(self) -> bool: def is_matrix_ring(self) -> bool: """ - Check whether the quaternion algebra is isomorphic to the + Check whether this quaternion algebra is isomorphic to the 2x2 matrix ring over the base field. EXAMPLES:: @@ -1057,8 +1057,8 @@ def inner_product_matrix(self): def is_definite(self): r""" - Check whether the given quaternion algebra is definite, i.e. whether - it ramifies at the unique Archimedean place of its base field `\QQ`. + Check whether this quaternion algebra is definite, i.e. whether + it ramifies at the unique real place of its base field `\QQ`. A quaternion algebra over `\QQ` is definite if and only if both of its invariants are negative (see Exercise 2.4(c) in [Voi2021]_). @@ -1082,8 +1082,10 @@ def is_definite(self): def is_totally_definite(self): """ - Check whether the quaternion algebra ``self`` is totally definite, i.e. - whether it ramifies at all real Archimedean places of its base number field. + Check whether this quaternion algebra is totally definite. + + A quaternion algebra defined over a number field is totally definite + if it ramifies at all real places of its base field. EXAMPLES:: @@ -1120,11 +1122,11 @@ def is_totally_definite(self): @cached_method def ramified_places(self, inf=True): - """ - Return the places of the base number field at which the given + r""" + Return the places of the base number field at which this quaternion algebra ramifies. - Note: The initial choice of primes (for the base field ``\\QQ``) + Note: The initial choice of primes (for the base field `\QQ`) respectively of prime ideals (in the number field case) to check ramification for is motivated by 12.4.12(a) in [Voi2021]_. The restriction to real embeddings is due to 14.5.8 in [Voi2021]_. @@ -1135,12 +1137,13 @@ def ramified_places(self, inf=True): OUTPUT: - The non-Archimedean (AKA finite) places at which ``self`` ramifies - (given as elements of ZZ, sorted small to large, if ``self`` is - defined over the rational field QQ, respectively as fractional ideals - of the number field's ring of integers, otherwise) and, if ``inf`` is - set to ``True``, also the Archimedean (AKA infinite) places at which - ``self`` ramifies (given by real embeddings of the base field). + The non-Archimedean (AKA finite) places at which the quaternion + algebra ramifies (given as elements of `\ZZ`, sorted small to + large, if the algebra is defined over the rational field `\QQ`, + respectively as fractional ideals of the number field's ring of + integers, otherwise) and, if ``inf`` is set to ``True``, also the + Archimedean (AKA infinite) places at which the quaternion algebra + ramifies (given by real embeddings of the base field). EXAMPLES:: @@ -1229,15 +1232,15 @@ def ramified_places(self, inf=True): @cached_method def ramified_primes(self): """ - Return the (finite) primes of the base number field at which - the given quaternion algebra ramifies. + Return the (finite) primes of the base number field at + which this quaternion algebra ramifies. OUTPUT: The list of finite primes at which ``self`` ramifies; given as - integers, sorted small to large, if ``self`` is defined over `\\QQ`, - and as fractional ideals in the ring of integers of the base number - field otherwise. + integers, sorted small to large, if ``self`` is defined over + `\\QQ`, and as fractional ideals in the ring of integers of the + base number field otherwise. EXAMPLES:: @@ -1268,7 +1271,7 @@ def ramified_primes(self): @cached_method def discriminant(self): r""" - Return the discriminant of the given quaternion algebra, i.e. the + Return the discriminant of this quaternion algebra, i.e. the product of the finite places it ramifies at. OUTPUT: @@ -1315,10 +1318,11 @@ def discriminant(self): def is_isomorphic(self, A) -> bool: """ - Check whether the given quaternion algebra is isomorphic to ``A``. + Check whether this quaternion algebra is isomorphic to ``A``. - Currently only implemented over a number field; motivated by Main - Theorem 14.6.1 in [Voi2021]_, noting that QQ has a unique real place. + Currently only implemented over a number field; motivated by + Main Theorem 14.6.1 in [Voi2021]_, noting that `\\QQ` has a + unique infinite place. INPUT: From 12f44a34de1799e11248b2eb7ffe6b61c7393899 Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Thu, 28 Mar 2024 05:16:45 +0100 Subject: [PATCH 098/369] Implemented reviewer feedback - Rewrote some docstring descriptions - Broke apart examples into multiple blocks, with added flavor text - Modified docstring examples to emphasize certain features more - Refactored some larger descriptions to use bullet point lists Amend 1: Fixed missing space before NOTE Amend 2: Fixed indent of second line in bullet point list in `.discriminant()` --- .../algebras/quatalg/quaternion_algebra.py | 167 ++++++++++++------ 1 file changed, 116 insertions(+), 51 deletions(-) diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index fd287b0f490..f2debb672cc 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -406,34 +406,46 @@ def is_division_algebra(self) -> bool: Check whether this quaternion algebra is a division algebra, i.e. whether every nonzero element in it is invertible. + Currently only implemented for quaternion algebras + defined over a number field. + EXAMPLES:: sage: QuaternionAlgebra(QQ,-5,-2).is_division_algebra() True - sage: QuaternionAlgebra(1).is_division_algebra() + sage: QuaternionAlgebra(2,9).is_division_algebra() False + By checking ramification, the methods correctly recognizes division + quaternion algebras over a number field even if they have trivial + discriminant:: + sage: K = QuadraticField(3) - sage: L = QuadraticField(-15) - sage: QuaternionAlgebra(K, -1, -1).is_division_algebra() - True - sage: QuaternionAlgebra(L, -1, -1).is_division_algebra() + sage: A = QuaternionAlgebra(K, -1, -1) + sage: A.discriminant() + Fractional ideal (1) + sage: A.is_division_algebra() True + The method is not implemented over arbitrary base rings yet:: + sage: QuaternionAlgebra(RR(2.),1).is_division_algebra() Traceback (most recent call last): ... - NotImplementedError: base field must be rational numbers or a number field + NotImplementedError: base ring must be rational numbers or a number field """ try: return self.ramified_places(inf=True) != ([], []) except ValueError: - raise NotImplementedError("base field must be rational numbers or a number field") + raise NotImplementedError("base ring must be rational numbers or a number field") def is_matrix_ring(self) -> bool: """ Check whether this quaternion algebra is isomorphic to the - 2x2 matrix ring over the base field. + 2x2 matrix ring over the base ring. + + Currently only implemented for quaternion algebras + defined over a number field. EXAMPLES:: @@ -442,22 +454,28 @@ def is_matrix_ring(self) -> bool: sage: QuaternionAlgebra(2,9).is_matrix_ring() True + By checking ramification, the method is able to recognize that + quaternion algebras (defined over a number field) with trivial + discriminant need not be matrix rings:: + sage: K = QuadraticField(3) - sage: L = QuadraticField(-15) - sage: QuaternionAlgebra(K, -1, -1).is_matrix_ring() - False - sage: QuaternionAlgebra(L, -1, -1).is_matrix_ring() + sage: A = QuaternionAlgebra(K, -1, -1) + sage: A.discriminant() + Fractional ideal (1) + sage: A.is_matrix_ring() False + The method is not implemented over arbitrary base rings yet:: + sage: QuaternionAlgebra(RR(2.),1).is_matrix_ring() Traceback (most recent call last): ... - NotImplementedError: base field must be rational numbers or a number field + NotImplementedError: base ring must be rational numbers or a number field """ try: return self.ramified_places(inf=True) == ([], []) except ValueError: - raise NotImplementedError("base field must be rational numbers or a number field") + raise NotImplementedError("base ring must be rational numbers or a number field") def is_exact(self) -> bool: """ @@ -1057,10 +1075,10 @@ def inner_product_matrix(self): def is_definite(self): r""" - Check whether this quaternion algebra is definite, i.e. whether - it ramifies at the unique real place of its base field `\QQ`. + Check whether this quaternion algebra is definite. - A quaternion algebra over `\QQ` is definite if and only if both of + A quaternion algebra over `\QQ` is definite if it ramifies at the + unique real place of `\QQ`, which happens if and only if both of its invariants are negative (see Exercise 2.4(c) in [Voi2021]_). EXAMPLES:: @@ -1070,6 +1088,8 @@ def is_definite(self): sage: QuaternionAlgebra(1).is_definite() False + The method does not make sense over an arbitrary base ring:: + sage: QuaternionAlgebra(RR(2.),1).is_definite() Traceback (most recent call last): ... @@ -1096,15 +1116,15 @@ def is_totally_definite(self): sage: QuaternionAlgebra(K, -1, -1).is_totally_definite() True - sage: L = QuadraticField(-15) - sage: QuaternionAlgebra(L, -1, -1).is_totally_definite() - True + We can also use number field elements as invariants:: sage: x = polygen(ZZ, 'x') sage: F. = NumberField(x^2 - x - 1) sage: QuaternionAlgebra(F, 2*a, F(-1)).is_totally_definite() False + The method does not make sense over an arbitrary base ring:: + sage: QuaternionAlgebra(RR(2.),1).is_totally_definite() Traceback (most recent call last): ... @@ -1117,8 +1137,8 @@ def is_totally_definite(self): if F not in NumberFields(): raise ValueError("base field must be rational numbers or a number field") - E = F.real_embeddings() - return [e for e in E if F.hilbert_symbol(self._a, self._b, e) == -1] == E + return all(F.hilbert_symbol(self._a, self._b, e) == -1 + for e in F.real_embeddings()) @cached_method def ramified_places(self, inf=True): @@ -1126,10 +1146,12 @@ def ramified_places(self, inf=True): Return the places of the base number field at which this quaternion algebra ramifies. - Note: The initial choice of primes (for the base field `\QQ`) - respectively of prime ideals (in the number field case) to check - ramification for is motivated by 12.4.12(a) in [Voi2021]_. The - restriction to real embeddings is due to 14.5.8 in [Voi2021]_. + .. NOTE:: + + The initial choice of primes (for the base field `\QQ`) + respectively of prime ideals (in the number field case) to check + ramification for is motivated by 12.4.12(a) in [Voi2021]_. The + restriction to real embeddings is due to 14.5.8 in [Voi2021]_. INPUT: @@ -1138,18 +1160,26 @@ def ramified_places(self, inf=True): OUTPUT: The non-Archimedean (AKA finite) places at which the quaternion - algebra ramifies (given as elements of `\ZZ`, sorted small to - large, if the algebra is defined over the rational field `\QQ`, - respectively as fractional ideals of the number field's ring of - integers, otherwise) and, if ``inf`` is set to ``True``, also the - Archimedean (AKA infinite) places at which the quaternion algebra - ramifies (given by real embeddings of the base field). + algebra ramifies, given as + + - elements of `\ZZ` (sorted small to large) if the algebra is + defined over `\QQ`, + + - fractional ideals in the ring of integers of the base number field, + otherwise. + + Additionally, if ``inf`` is set to ``True``, then the Archimedean + (AKA infinite) places at which the quaternion algebra ramifies are + also returned, given by real embeddings of the base field. EXAMPLES:: sage: QuaternionAlgebra(210,-22).ramified_places() ([2, 3, 5, 7], []) + For a definite quaternion algebra we get ramification at the + unique infinite place of `\QQ`:: + sage: QuaternionAlgebra(-1, -1).ramified_places() ([2], [Ring morphism: @@ -1157,6 +1187,15 @@ def ramified_places(self, inf=True): To: Real Field with 53 bits of precision Defn: 1 |--> 1.00000000000000]) + Extending the base field can resolve all ramification:: + + sage: F = QuadraticField(-1) + sage: QuaternionAlgebra(F, -1, -1).ramified_places() + ([], []) + + Extending the base field can resolve all ramification at finite places + while still leaving some ramification at infinite places:: + sage: K = QuadraticField(3) sage: QuaternionAlgebra(K, -1, -1).ramified_places() ([], @@ -1169,10 +1208,15 @@ def ramified_places(self, inf=True): To: Real Field with 53 bits of precision Defn: a |--> 1.73205080756888]) + Extending the base field can also get rid of ramification at infinite + places while still leaving some ramification at finite places:: + sage: L = QuadraticField(-15) sage: QuaternionAlgebra(L, -1, -1).ramified_places() ([Fractional ideal (2, 1/2*a + 1/2), Fractional ideal (2, 1/2*a - 1/2)], []) + We can also use number field elements as invariants:: + sage: x = polygen(ZZ, 'x') sage: F. = NumberField(x^2 - x - 1) sage: QuaternionAlgebra(F, 2*a, F(-1)).ramified_places() @@ -1182,8 +1226,8 @@ def ramified_places(self, inf=True): To: Real Field with 53 bits of precision Defn: a |--> -0.618033988749895]) - sage: QuaternionAlgebra(QQ[sqrt(2)], 3, 19).ramified_places() # needs sage.symbolic - ([], []) + The method does not make sense over an arbitrary base ring:: + sage: QuaternionAlgebra(RR(2.),1).ramified_places() Traceback (most recent call last): ... @@ -1231,22 +1275,28 @@ def ramified_places(self, inf=True): @cached_method def ramified_primes(self): - """ + r""" Return the (finite) primes of the base number field at which this quaternion algebra ramifies. OUTPUT: - The list of finite primes at which ``self`` ramifies; given as - integers, sorted small to large, if ``self`` is defined over - `\\QQ`, and as fractional ideals in the ring of integers of the - base number field otherwise. + The list of finite primes at which ``self`` ramifies, given as + + - elements of `\ZZ` (sorted small to large) if the algebra is + defined over `\QQ`, + + - fractional ideals in the ring of integers of the base number field, + otherwise. EXAMPLES:: sage: QuaternionAlgebra(-58, -69).ramified_primes() [3, 23, 29] + Under field extensions, the number of ramified primes can increase + or decrease:: + sage: K = QuadraticField(3) sage: L = QuadraticField(-15) sage: QuaternionAlgebra(-1, -1).ramified_primes() @@ -1256,11 +1306,15 @@ def ramified_primes(self): sage: QuaternionAlgebra(L, -1, -1).ramified_primes() [Fractional ideal (2, 1/2*a + 1/2), Fractional ideal (2, 1/2*a - 1/2)] + We can also use number field elements as invariants:: + sage: x = polygen(ZZ, 'x') sage: F. = NumberField(x^2 - x - 1) sage: QuaternionAlgebra(F, 2*a, F(-1)).ramified_primes() [Fractional ideal (2)] + The method does not make sense over an arbitrary base ring:: + sage: QuaternionAlgebra(RR(2.),1).ramified_primes() Traceback (most recent call last): ... @@ -1271,15 +1325,19 @@ def ramified_primes(self): @cached_method def discriminant(self): r""" - Return the discriminant of this quaternion algebra, i.e. the - product of the finite places it ramifies at. + Return the discriminant of this quaternion algebra. + + The discriminant of a quaternion algebra over a number field is the + product of the finite places at which the algebra ramifies. OUTPUT: - The discriminant of this quaternion algebra (which has to be defined - over a number field), as an element of `\ZZ` if the quaternion algebra - is defined over `\QQ`, and as a fractional ideal in the ring of - integers of the base number field otherwise. + The discriminant of this quaternion algebra, as + + - an element of `\ZZ` if the algebra is defined over `\QQ`, + + - a fractional ideal in the ring of integers of the base number + field, otherwise. EXAMPLES:: @@ -1290,6 +1348,8 @@ def discriminant(self): sage: QuaternionAlgebra(-1, -1).discriminant() 2 + Some examples over number fields:: + sage: K = QuadraticField(3) sage: L = QuadraticField(-15) sage: QuaternionAlgebra(K, -1, -1).discriminant() @@ -1297,13 +1357,14 @@ def discriminant(self): sage: QuaternionAlgebra(L, -1, -1).discriminant() Fractional ideal (2) + We can also use number field elements as invariants:: + sage: x = polygen(ZZ, 'x') sage: F. = NumberField(x^2 - x - 1) sage: QuaternionAlgebra(F, 2*a, F(-1)).discriminant() Fractional ideal (2) - sage: QuaternionAlgebra(QQ[sqrt(2)], 3, 19).discriminant() # needs sage.symbolic - Fractional ideal (1) + The method does not make sense over an arbitrary base ring:: sage: QuaternionAlgebra(RR(2.),1).discriminant() Traceback (most recent call last): @@ -1320,9 +1381,9 @@ def is_isomorphic(self, A) -> bool: """ Check whether this quaternion algebra is isomorphic to ``A``. - Currently only implemented over a number field; motivated by - Main Theorem 14.6.1 in [Voi2021]_, noting that `\\QQ` has a - unique infinite place. + Currently only implemented for quaternion algebras defined over + a number field; based on Main Theorem 14.6.1 in [Voi2021]_, + noting that `\\QQ` has a unique infinite place. INPUT: @@ -1337,6 +1398,10 @@ def is_isomorphic(self, A) -> bool: sage: B.is_isomorphic(A) True + Checking ramification at both finite and infinite places, the method + correctly distinguishes isomorphism classes of quaternion algebras + that the discriminant can not distinguish:: + sage: K = QuadraticField(3) sage: A = QuaternionAlgebra(K, -1, -1) sage: B = QuaternionAlgebra(K, 1, -1) @@ -1349,7 +1414,7 @@ def is_isomorphic(self, A) -> bool: raise TypeError("A must be a quaternion algebra of the form (a,b)_K") F = self.base_ring() - if F != A.base_ring(): + if F is not A.base_ring(): raise ValueError("both quaternion algebras must be defined over the same ring") if is_RationalField(F): From aec353447f0fefd3efc15e9bf33f7350f292b8bd Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Thu, 28 Mar 2024 14:45:15 +0100 Subject: [PATCH 099/369] Small docstring modifications --- .../algebras/quatalg/quaternion_algebra.py | 32 ++++++++----------- 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index f2debb672cc..17d5d5e7475 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -416,7 +416,7 @@ def is_division_algebra(self) -> bool: sage: QuaternionAlgebra(2,9).is_division_algebra() False - By checking ramification, the methods correctly recognizes division + By checking ramification, the method correctly recognizes division quaternion algebras over a number field even if they have trivial discriminant:: @@ -1150,7 +1150,7 @@ def ramified_places(self, inf=True): The initial choice of primes (for the base field `\QQ`) respectively of prime ideals (in the number field case) to check - ramification for is motivated by 12.4.12(a) in [Voi2021]_. The + ramification for is based on 12.4.12(a) in [Voi2021]_. The restriction to real embeddings is due to 14.5.8 in [Voi2021]_. INPUT: @@ -1159,14 +1159,12 @@ def ramified_places(self, inf=True): OUTPUT: - The non-Archimedean (AKA finite) places at which the quaternion + The non-Archimedean (AKA finite) places at which this quaternion algebra ramifies, given as - - elements of `\ZZ` (sorted small to large) if the algebra is - defined over `\QQ`, + - elements of `\ZZ` (sorted small to large) if the base field is `\QQ`, - - fractional ideals in the ring of integers of the base number field, - otherwise. + - integral fractional ideals of the base number field, otherwise. Additionally, if ``inf`` is set to ``True``, then the Archimedean (AKA infinite) places at which the quaternion algebra ramifies are @@ -1193,8 +1191,8 @@ def ramified_places(self, inf=True): sage: QuaternionAlgebra(F, -1, -1).ramified_places() ([], []) - Extending the base field can resolve all ramification at finite places - while still leaving some ramification at infinite places:: + Extending the base field can also resolve all ramification at finite + places while still leaving some ramification at infinite places:: sage: K = QuadraticField(3) sage: QuaternionAlgebra(K, -1, -1).ramified_places() @@ -1215,7 +1213,7 @@ def ramified_places(self, inf=True): sage: QuaternionAlgebra(L, -1, -1).ramified_places() ([Fractional ideal (2, 1/2*a + 1/2), Fractional ideal (2, 1/2*a - 1/2)], []) - We can also use number field elements as invariants:: + We can use number field elements as invariants as well:: sage: x = polygen(ZZ, 'x') sage: F. = NumberField(x^2 - x - 1) @@ -1281,13 +1279,12 @@ def ramified_primes(self): OUTPUT: - The list of finite primes at which ``self`` ramifies, given as + The list of finite primes at which this quaternion algebra ramifies, + given as - - elements of `\ZZ` (sorted small to large) if the algebra is - defined over `\QQ`, + - elements of `\ZZ` (sorted small to large) if the base field is `\QQ`, - - fractional ideals in the ring of integers of the base number field, - otherwise. + - integral fractional ideals of the base number field, otherwise. EXAMPLES:: @@ -1332,12 +1329,11 @@ def discriminant(self): OUTPUT: - The discriminant of this quaternion algebra, as + The discriminant of this quaternion algebra, given as - an element of `\ZZ` if the algebra is defined over `\QQ`, - - a fractional ideal in the ring of integers of the base number - field, otherwise. + - an integral fractional ideal of the base number field, otherwise. EXAMPLES:: From c0b8a6572ade213efa3646d53dcbcc4d65288cd5 Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Fri, 29 Mar 2024 00:59:55 +0100 Subject: [PATCH 100/369] Corrected `.is_totally_definite()`, moved .. NOTE - Added missing check that the base field of a totally definite algebra needs to be totally real - Moved note in `.ramified_places` below input and moved the interal info on initial choice of finite primes to code comments --- .../algebras/quatalg/quaternion_algebra.py | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index 17d5d5e7475..b89110144d7 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -1104,8 +1104,10 @@ def is_totally_definite(self): """ Check whether this quaternion algebra is totally definite. - A quaternion algebra defined over a number field is totally definite - if it ramifies at all real places of its base field. + A quaternion algebra defined over a number field is + totally definite if it ramifies at all Archimedean + places of its base field. In particular, the base number + field has to be totally real (see 14.5.8 in [Voi2021]_). EXAMPLES:: @@ -1137,8 +1139,12 @@ def is_totally_definite(self): if F not in NumberFields(): raise ValueError("base field must be rational numbers or a number field") - return all(F.hilbert_symbol(self._a, self._b, e) == -1 - for e in F.real_embeddings()) + # Since we need the list of real embeddings of the number field (instead + # of just the number of them), we avoid a call of the `is_totally_real()`- + # method by directly comparing the embedding list's length to the degree + E = F.real_embeddings() + return len(E) == F.degree() and all(F.hilbert_symbol(self._a, self._b, e) == -1 + for e in E) @cached_method def ramified_places(self, inf=True): @@ -1146,13 +1152,6 @@ def ramified_places(self, inf=True): Return the places of the base number field at which this quaternion algebra ramifies. - .. NOTE:: - - The initial choice of primes (for the base field `\QQ`) - respectively of prime ideals (in the number field case) to check - ramification for is based on 12.4.12(a) in [Voi2021]_. The - restriction to real embeddings is due to 14.5.8 in [Voi2021]_. - INPUT: - ``inf`` -- (default: ``True``) @@ -1170,6 +1169,11 @@ def ramified_places(self, inf=True): (AKA infinite) places at which the quaternion algebra ramifies are also returned, given by real embeddings of the base field. + .. NOTE:: + + Any Archimedean place at which a quaternion algebra ramifies + has to be real (see 14.5.8 in [Voi2021]_). + EXAMPLES:: sage: QuaternionAlgebra(210,-22).ramified_places() @@ -1238,6 +1242,10 @@ def ramified_places(self, inf=True): a = self._a b = self._b + # The initial choice of primes (for the base field QQ) respectively + # of prime ideals (in the number field case) to check ramification + # for is based on 12.4.12(a) in [Voi2021]_. + # For efficiency (and to not convert QQ into a number field manually), # we handle the case F = QQ first if is_RationalField(F): From da2a8050e4477f09c16b1de86065c2cce0b7af3d Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Fri, 29 Mar 2024 01:43:00 +0100 Subject: [PATCH 101/369] Added missing type descriptor for `inf` argument --- src/sage/algebras/quatalg/quaternion_algebra.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index b89110144d7..d6d245c2b89 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -1154,7 +1154,7 @@ def ramified_places(self, inf=True): INPUT: - - ``inf`` -- (default: ``True``) + - ``inf`` -- bool (default: ``True``) OUTPUT: From 96aea2a02eb780c30eb254a0c5a35b04aae720e8 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Tue, 2 Apr 2024 00:22:00 +0200 Subject: [PATCH 102/369] 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 103/369] 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 104/369] 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 105/369] 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 106/369] 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 107/369] 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 108/369] 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 109/369] 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 110/369] 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 111/369] 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 74c1c4ddea47af6cd010a455b53a8f51b28c9a57 Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Sat, 6 Apr 2024 18:23:15 +0200 Subject: [PATCH 112/369] Implemented reviewer feedback --- src/sage/algebras/quatalg/quaternion_algebra.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index d6d245c2b89..be1a2a622b8 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -1090,7 +1090,7 @@ def is_definite(self): The method does not make sense over an arbitrary base ring:: - sage: QuaternionAlgebra(RR(2.),1).is_definite() + sage: QuaternionAlgebra(RR(2.), 1).is_definite() Traceback (most recent call last): ... ValueError: base field must be rational numbers @@ -1127,7 +1127,7 @@ def is_totally_definite(self): The method does not make sense over an arbitrary base ring:: - sage: QuaternionAlgebra(RR(2.),1).is_totally_definite() + sage: QuaternionAlgebra(RR(2.), 1).is_totally_definite() Traceback (most recent call last): ... ValueError: base field must be rational numbers or a number field @@ -1144,7 +1144,7 @@ def is_totally_definite(self): # method by directly comparing the embedding list's length to the degree E = F.real_embeddings() return len(E) == F.degree() and all(F.hilbert_symbol(self._a, self._b, e) == -1 - for e in E) + for e in E) @cached_method def ramified_places(self, inf=True): @@ -1382,12 +1382,12 @@ def discriminant(self): return F.ideal(F.prod(self.ramified_places(inf=False))) def is_isomorphic(self, A) -> bool: - """ + r""" Check whether this quaternion algebra is isomorphic to ``A``. Currently only implemented for quaternion algebras defined over a number field; based on Main Theorem 14.6.1 in [Voi2021]_, - noting that `\\QQ` has a unique infinite place. + noting that `\QQ` has a unique infinite place. INPUT: From fdb99dcfa8fd516176ad4cd230d291274bf36f92 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Sun, 7 Apr 2024 20:52:50 +0200 Subject: [PATCH 113/369] 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 114/369] 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 115/369] 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 116/369] 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 117/369] 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 118/369] 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 119/369] 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 120/369] 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 121/369] 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 122/369] 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 123/369] 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 124/369] 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 125/369] 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 126/369] 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 127/369] 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 642629675a579712d36a51e2c54ef4418e035c3e Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Sun, 14 Apr 2024 01:03:07 +0200 Subject: [PATCH 128/369] Changed number field embedding target to `AA` - Avoids possible future issues caused by rounding precision - Seems to be more efficient Amend: Docstring fix --- .../algebras/quatalg/quaternion_algebra.py | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index bff96a9d8d9..caf8e1e28e3 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -55,6 +55,7 @@ from sage.rings.rational_field import is_RationalField, QQ from sage.rings.infinity import infinity from sage.rings.number_field.number_field_base import NumberField +from sage.rings.qqbar import AA from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.rings.power_series_ring import PowerSeriesRing from sage.structure.category_object import normalize_names @@ -1235,7 +1236,7 @@ def is_totally_definite(self): # Since we need the list of real embeddings of the number field (instead # of just the number of them), we avoid a call of the `is_totally_real()`- # method by directly comparing the embedding list's length to the degree - E = F.real_embeddings() + E = F.embeddings(AA) return len(E) == F.degree() and all(F.hilbert_symbol(self._a, self._b, e) == -1 for e in E) @@ -1260,7 +1261,11 @@ def ramified_places(self, inf=True): Additionally, if ``inf`` is set to ``True``, then the Archimedean (AKA infinite) places at which the quaternion algebra ramifies are - also returned, given by real embeddings of the base field. + also returned, given as + + - the embeddings of `\QQ` into `\RR` if the base field is `\QQ`, or + + - the embeddings of the base number field into the Algebraic Real Field. .. NOTE:: @@ -1296,12 +1301,12 @@ def ramified_places(self, inf=True): ([], [Ring morphism: From: Number Field in a with defining polynomial x^2 - 3 with a = 1.732050807568878? - To: Real Field with 53 bits of precision - Defn: a |--> -1.73205080756888, + To: Algebraic Real Field + Defn: a |--> -1.732050807568878?, Ring morphism: From: Number Field in a with defining polynomial x^2 - 3 with a = 1.732050807568878? - To: Real Field with 53 bits of precision - Defn: a |--> 1.73205080756888]) + To: Algebraic Real Field + Defn: a |--> 1.732050807568878?]) Extending the base field can also get rid of ramification at infinite places while still leaving some ramification at finite places:: @@ -1318,8 +1323,8 @@ def ramified_places(self, inf=True): ([Fractional ideal (2)], [Ring morphism: From: Number Field in a with defining polynomial x^2 - x - 1 - To: Real Field with 53 bits of precision - Defn: a |--> -0.618033988749895]) + To: Algebraic Real Field + Defn: a |--> -0.618033988749895?]) The method does not make sense over an arbitrary base ring:: @@ -1370,7 +1375,7 @@ def ramified_places(self, inf=True): return ram_fin # At this point the infinite ramified places also need to be computed - return ram_fin, [e for e in F.real_embeddings() if F.hilbert_symbol(a, b, e) == -1] + return ram_fin, [e for e in F.embeddings(AA) if F.hilbert_symbol(a, b, e) == -1] @cached_method def ramified_primes(self): From 11c5cd93ff698493ddf35ffb4f705e3283f3d980 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Thu, 18 Apr 2024 14:15:41 +0200 Subject: [PATCH 129/369] 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 130/369] 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 131/369] 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 132/369] 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 133/369] 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 134/369] 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 135/369] 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 136/369] 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 137/369] 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 138/369] 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 139/369] 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 140/369] 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 141/369] 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 142/369] 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 143/369] 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 144/369] 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 145/369] 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:: From 5a67fa52be228928a13e3058e708eb96597bd1bb Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Wed, 29 May 2024 09:20:42 +0200 Subject: [PATCH 146/369] introduce a functorial construction CoefficientRing --- src/sage/data_structures/stream.py | 107 ++++++++++++++++-- src/sage/modules/free_module_element.pyx | 4 +- src/sage/rings/lazy_series.py | 16 +-- src/sage/rings/lazy_series_ring.py | 14 +-- .../polynomial/infinite_polynomial_ring.py | 2 +- .../polynomial/multi_polynomial_element.py | 6 +- 6 files changed, 116 insertions(+), 33 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index 6108b7568d9..9035d2574a7 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -95,6 +95,7 @@ # **************************************************************************** from sage.rings.integer_ring import ZZ +from sage.rings.rational_field import QQ from sage.rings.infinity import infinity from sage.arith.misc import divisors from sage.misc.misc_c import prod @@ -105,6 +106,8 @@ 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.rings.fraction_field import FractionField_generic +from sage.rings.fraction_field_element import FractionFieldElement from sage.misc.cachefunc import cached_method lazy_import('sage.combinat.sf.sfa', ['_variables_recursive', '_raise_variables']) @@ -1340,6 +1343,84 @@ def variables(self): """ return self._pool +from sage.categories.functor import Functor +from sage.categories.pushout import ConstructionFunctor +from sage.categories.fields import Fields +from sage.categories.integral_domains import IntegralDomains +from sage.categories.quotient_fields import QuotientFields + +class CoefficientRingFunctor(ConstructionFunctor): + rank = 0 + + def __init__(self): + Functor.__init__(self, IntegralDomains(), Fields()) + + def _apply_functor(self, R): + return CoefficientRing(R) + +class CoefficientRingElement(FractionFieldElement): + pass + +class CoefficientRing(UniqueRepresentation, FractionField_generic): + def __init__(self, base_ring): + """ + + EXAMPLES:: + + sage: from sage.data_structures.stream import CoefficientRing + sage: PF = CoefficientRing(ZZ) + sage: S. = PF[] + sage: c = PF.gen(0) + sage: p = c * q + sage: p + FESDUMMY_0*q + sage: p.parent() + Multivariate Polynomial Ring in q, t over CoefficientRing over Integer Ring + + sage: p = c + q + sage: p + q + FESDUMMY_0 + sage: p.parent() + Multivariate Polynomial Ring in q, t over CoefficientRing over Integer Ring + + sage: L. = LazyPowerSeriesRing(ZZ) + sage: p = a + c + sage: p.parent() + Multivariate Lazy Taylor Series Ring in a, b over CoefficientRing over Integer Ring + + sage: S. = ZZ[] + sage: PF = CoefficientRing(S) + sage: L. = LazyPowerSeriesRing(S) + sage: c = PF.gen(0) + sage: p = a + c + sage: p.parent() + Multivariate Lazy Taylor Series Ring in a, b over CoefficientRing over Multivariate Polynomial Ring in q, t over Integer Ring + + sage: PF = CoefficientRing(ZZ) + sage: S. = PF[] + sage: L. = LazyPowerSeriesRing(ZZ) + sage: s = (q + PF.gen(0)*t) + sage: g = (a, b-a) + sage: s(g) + ((-FESDUMMY_0+1)*a+FESDUMMY_0*b) + sage: s(g).parent() + Multivariate Lazy Taylor Series Ring in a, b over CoefficientRing over Integer Ring + """ + B = InfinitePolynomialRing(base_ring, names=["FESDUMMY"]) + FractionField_generic.__init__(self, B, + element_class=CoefficientRingElement, + category=QuotientFields()) + + def _repr_(self): + return "CoefficientRing over %s" % self.base_ring() + + def gen(self, i): + return self._element_class(self, self._R.gen()[i]) + + def construction(self): + return (CoefficientRingFunctor(), + self.base_ring()) + class Stream_uninitialized(Stream): r""" @@ -1523,9 +1604,13 @@ def define_implicitly(self, series, initial_values, equations, self._coefficient_ring = coefficient_ring self._base_ring = base_ring - self._P = InfinitePolynomialRing(self._base_ring, names=["FESDUMMY"]) - self._PF = self._P.fraction_field() - if self._coefficient_ring != self._base_ring: + # self._P = InfinitePolynomialRing(self._base_ring, names=["FESDUMMY"]) + # self._PF = CoefficientRing(self._P, CoefficientRing.Element) + self._PF = CoefficientRing(self._base_ring) + self._P = self._PF.base() + if self._coefficient_ring == self._base_ring: + self._U = self._PF + else: self._U = self._coefficient_ring.change_ring(self._PF) self._pool = VariablePool(self._P) self._uncomputed = True @@ -1733,10 +1818,11 @@ def __getitem__(self, n): # down the multiplication enormously if self._coefficient_ring == self._base_ring: x = (self._pool.new_variable(self._name + "[%s]" % n0) - * self._terms_of_degree(n0, self._PF)[0]) + * self._terms_of_degree(n0, self._P)[0]) else: x = sum(self._pool.new_variable(self._name + "[%s]" % m) * m - for m in self._terms_of_degree(n0, self._PF)) + for m in self._terms_of_degree(n0, self._P)) + x = self._U(x) self._cache.append(x) return x @@ -2537,7 +2623,10 @@ def get_coefficient(self, n): sage: [h.get_coefficient(i) for i in range(10)] [0, 2, 6, 12, 20, 30, 42, 56, 72, 90] """ - return self._left[n] + self._right[n] + l = self._left[n] + r = self._right[n] + m = l + r + return m # self._left[n] + self._right[n] class Stream_sub(Stream_binary): @@ -4366,8 +4455,10 @@ def __getitem__(self, n): sage: [f2[i] for i in range(-1, 4)] [0, 2, 6, 12, 20] """ - return (ZZ.prod(range(n + 1, n + self._shift + 1)) - * self._series[n + self._shift]) + c1 = self._series[n + self._shift] + c2 = ZZ.prod(range(n + 1, n + self._shift + 1)) + p = c1 * c2 + return p def __hash__(self): """ diff --git a/src/sage/modules/free_module_element.pyx b/src/sage/modules/free_module_element.pyx index c93c71f195e..50bbf3091d1 100644 --- a/src/sage/modules/free_module_element.pyx +++ b/src/sage/modules/free_module_element.pyx @@ -4516,7 +4516,7 @@ cdef class FreeModuleElement_generic_dense(FreeModuleElement): (-2/3, 0, 2, 2/3*pi) """ if right._parent is self._parent._base: - v = [(x)._mul_(right) for x in self._entries] + v = [x._mul_(right) for x in self._entries] else: v = [x * right for x in self._entries] return self._new_c(v) @@ -4967,7 +4967,7 @@ cdef class FreeModuleElement_generic_sparse(FreeModuleElement): cdef dict v = {} if right: for i, a in self._entries.iteritems(): - prod = (a)._mul_(right) + prod = a._mul_(right) if prod: v[i] = prod return self._new_c(v) diff --git a/src/sage/rings/lazy_series.py b/src/sage/rings/lazy_series.py index 2a0fc0a0083..57f74f4092c 100644 --- a/src/sage/rings/lazy_series.py +++ b/src/sage/rings/lazy_series.py @@ -3691,7 +3691,7 @@ def log(self): raise ValueError("can only compose with a positive valuation series") # WARNING: d_self need not be a proper element of P, e.g. for # multivariate power series - d_self = Stream_function(lambda n: (n + 1) * coeff_stream[n + 1], + d_self = Stream_function(lambda n: R(n + 1) * coeff_stream[n + 1], P.is_sparse(), 0) coeff_stream_inverse = Stream_cauchy_invert(coeff_stream) # d_self and coeff_stream_inverse always commute @@ -5260,24 +5260,18 @@ 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.parent() == self.base_ring(): + B = c.parent() + if B is ZZ or B is QQ or B == self.base_ring() or B == self.base_ring().fraction_field(): c = P(c) r += c[n] - elif c.parent().base_ring() is self.base_ring(): - r += c(g)[n] else: - if gR is None: - S = c.parent().base_ring() - gR = [h.change_ring(S).map_coefficients(S) for h in g] - s = c(gR)[n] - r += s + d = c(g) + r += d[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 cbb41861301..56360ca571b 100644 --- a/src/sage/rings/lazy_series_ring.py +++ b/src/sage/rings/lazy_series_ring.py @@ -480,7 +480,7 @@ def _element_constructor_(self, x=None, valuation=None, degree=None, constant=No if coefficients is not None and (x is not None and (not isinstance(x, int) or x)): raise ValueError("coefficients must be None if x is provided") - BR = self.base_ring() + BR = self._internal_poly_ring.base_ring() # this is the ring containing the elements of the stream if isinstance(constant, (tuple, list)): constant, degree = constant if isinstance(degree, (tuple, list)): @@ -559,7 +559,6 @@ def _element_constructor_(self, x=None, valuation=None, degree=None, constant=No if isinstance(stream, Stream_zero): return self.zero() elif isinstance(stream, Stream_exact): - BR = self.base_ring() if x.parent()._arity != 1: # Special case for constant series if stream._degree == 1: @@ -793,7 +792,7 @@ def define_implicitly(self, series, equations, max_lookahead=1): 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. = LazyPowerSeriesRing(QQ['x','y','f1'].fraction_field()) sage: L.base_ring().inject_variables() Defining x, y, f1 sage: F = L.undefined() @@ -2775,7 +2774,7 @@ def _element_constructor_(self, x=None, valuation=None, constant=None, degree=No valuation = 0 R = self._laurent_poly_ring - BR = self.base_ring() + BR = self._internal_poly_ring.base_ring() # this is the ring containing the elements of the stream if x is None: assert degree is None coeff_stream = Stream_uninitialized(valuation) @@ -2825,11 +2824,8 @@ def _element_constructor_(self, x=None, valuation=None, constant=None, degree=No if isinstance(x, LazyPowerSeries): stream = x._coeff_stream if isinstance(stream, Stream_exact): - if self._arity == 1: - BR = self.base_ring() - else: - BR = self._laurent_poly_ring coeffs = [BR(val) for val in stream._initial_coefficients] + constant = BR(stream._constant) valuation = stream._approximate_order for i, c in enumerate(coeffs): if c: @@ -2841,7 +2837,7 @@ def _element_constructor_(self, x=None, valuation=None, constant=None, degree=No coeffs = [] return self(coeffs, degree=stream._degree, - constant=self.base_ring()(stream._constant), + constant=constant, valuation=valuation) return self.element_class(self, stream) diff --git a/src/sage/rings/polynomial/infinite_polynomial_ring.py b/src/sage/rings/polynomial/infinite_polynomial_ring.py index 99c6266fedc..8f4155fed78 100644 --- a/src/sage/rings/polynomial/infinite_polynomial_ring.py +++ b/src/sage/rings/polynomial/infinite_polynomial_ring.py @@ -946,7 +946,7 @@ def _element_constructor_(self, x): if not hasattr(x, 'variables'): try: return sage_eval(repr(x), self.gens_dict()) - except (TypeError, ValueError, SyntaxError): + except (TypeError, ValueError, SyntaxError, NameError): raise ValueError(f"cannot convert {x} into an element of {self}") # direct conversion will only be used if the underlying polynomials are libsingular. diff --git a/src/sage/rings/polynomial/multi_polynomial_element.py b/src/sage/rings/polynomial/multi_polynomial_element.py index d2355aa257b..6a33f9291d3 100644 --- a/src/sage/rings/polynomial/multi_polynomial_element.py +++ b/src/sage/rings/polynomial/multi_polynomial_element.py @@ -177,9 +177,11 @@ def __call__(self, *x, **kwds): K = x[0].parent() except AttributeError: K = self.parent().base_ring() - y = K(0) + y = K.zero() for m, c in self.element().dict().items(): - y += c * prod(v ** e for v, e in zip(x, m) if e) + p = prod(v ** e for v, e in zip(x, m) if e) + cp = c * p + y += cp return y def _richcmp_(self, right, op): From 20dd68694897b8c32d4c7efad2fc6f61d1a050e3 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Mon, 3 Jun 2024 10:57:21 +0200 Subject: [PATCH 147/369] do not coerce in InfinitePolynomialRing_sparse._element_constructor_ --- src/sage/rings/polynomial/infinite_polynomial_ring.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/polynomial/infinite_polynomial_ring.py b/src/sage/rings/polynomial/infinite_polynomial_ring.py index 8f4155fed78..4c5f9a4f51d 100644 --- a/src/sage/rings/polynomial/infinite_polynomial_ring.py +++ b/src/sage/rings/polynomial/infinite_polynomial_ring.py @@ -931,7 +931,7 @@ def _element_constructor_(self, x): if isinstance(self._base, MPolynomialRing_polydict): x = sage_eval(repr(), next(self.gens_dict())) else: - x = self._base.coerce(x) + x = self._base(x) # remark: Conversion to self._P (if applicable) # is done in InfinitePolynomial() return InfinitePolynomial(self, x) From bba061126a5b526f62823c8f3184d8370ed90fb9 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Fri, 7 Jun 2024 17:46:11 +0200 Subject: [PATCH 148/369] bad hack to make symmetric functions work --- src/sage/data_structures/stream.py | 9 +++++++++ src/sage/rings/lazy_series_ring.py | 4 ++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index 9035d2574a7..04a402d3828 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -1405,6 +1405,15 @@ def __init__(self, base_ring): ((-FESDUMMY_0+1)*a+FESDUMMY_0*b) sage: s(g).parent() Multivariate Lazy Taylor Series Ring in a, b over CoefficientRing over Integer Ring + + sage: S = SymmetricFunctions(QQ).h().fraction_field() + sage: PF = CoefficientRing(S) + sage: L. = LazyPowerSeriesRing(S) + sage: c = PF.gen(0) + sage: p = a + c + sage: p.parent() + Multivariate Lazy Taylor Series Ring in a, b over CoefficientRing over Fraction Field of Symmetric Functions over Rational Field in the homogeneous basis + """ B = InfinitePolynomialRing(base_ring, names=["FESDUMMY"]) FractionField_generic.__init__(self, B, diff --git a/src/sage/rings/lazy_series_ring.py b/src/sage/rings/lazy_series_ring.py index 56360ca571b..62ad5edbd1e 100644 --- a/src/sage/rings/lazy_series_ring.py +++ b/src/sage/rings/lazy_series_ring.py @@ -2783,7 +2783,7 @@ def _element_constructor_(self, x=None, valuation=None, constant=None, degree=No try: # Try to build stuff using the polynomial ring constructor x = R(x) - except (TypeError, ValueError): + except (TypeError, ValueError, AttributeError): pass if isinstance(constant, (tuple, list)): constant, degree = constant @@ -2792,7 +2792,7 @@ def _element_constructor_(self, x=None, valuation=None, constant=None, degree=No raise ValueError("constant must be zero for multivariate Taylor series") constant = BR(constant) - if x in R: + if parent(x) == R: if not x and not constant: coeff_stream = Stream_zero() else: From 187988a4dd20b82bf294a9bc86cc9818879b2056 Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Tue, 25 Jun 2024 10:50:48 +0200 Subject: [PATCH 149/369] Removed duplicate of `.is_definite()` --- .../algebras/quatalg/quaternion_algebra.py | 25 +------------------ 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index f3394d8b731..3c2e3ee5b8c 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -1078,29 +1078,6 @@ def invariants(self): """ return self._a, self._b - def is_definite(self): - """ - Checks whether the quaternion algebra ``self`` is definite, i.e. whether it ramifies at the - unique Archimedean place of its base field QQ. This is the case if and only if both - invariants of ``self`` are negative. - - EXAMPLES:: - - sage: QuaternionAlgebra(QQ,-5,-2).is_definite() - True - sage: QuaternionAlgebra(1).is_definite() - False - - sage: QuaternionAlgebra(RR(2.),1).is_definite() - Traceback (most recent call last): - ... - ValueError: base field must be rational numbers - """ - if not isinstance(self.base_ring(), RationalField): - raise ValueError("base field must be rational numbers") - a, b = self.invariants() - return a < 0 and b < 0 - def __eq__(self, other): """ Compare self and other. @@ -1248,7 +1225,7 @@ def is_definite(self): ... ValueError: base field must be rational numbers """ - if not is_RationalField(self.base_ring()): + if not isinstance(self.base_ring(), RationalField): raise ValueError("base field must be rational numbers") a, b = self.invariants() return a < 0 and b < 0 From e783d8bdf01b5e3b0385fac7d48cb326c72347e1 Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Tue, 25 Jun 2024 17:13:21 +0200 Subject: [PATCH 150/369] Replace instances of `is_RationalField` with according `isinstance`-calls --- src/sage/algebras/quatalg/quaternion_algebra.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index 3c2e3ee5b8c..6be502c63ec 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -1263,7 +1263,7 @@ def is_totally_definite(self): ValueError: base field must be rational numbers or a number field """ F = self.base_ring() - if is_RationalField(F): + if isinstance(F, RationalField): return self.is_definite() if F not in NumberFields(): @@ -1382,7 +1382,7 @@ def ramified_places(self, inf=True): # For efficiency (and to not convert QQ into a number field manually), # we handle the case F = QQ first - if is_RationalField(F): + if isinstance(F, RationalField): ram_fin = sorted([p for p in set([2]).union( prime_divisors(a.numerator()), prime_divisors(a.denominator()), prime_divisors(b.numerator()), prime_divisors(b.denominator())) @@ -1510,7 +1510,7 @@ def discriminant(self): ValueError: base field must be rational numbers or a number field """ F = self.base_ring() - if is_RationalField(F): + if isinstance(F, RationalField): return ZZ.prod(self.ramified_places(inf=False)) return F.ideal(F.prod(self.ramified_places(inf=False))) @@ -1555,7 +1555,7 @@ def is_isomorphic(self, A) -> bool: if F is not A.base_ring(): raise ValueError("both quaternion algebras must be defined over the same ring") - if is_RationalField(F): + if isinstance(F, RationalField): return self.ramified_places(inf=False) == A.ramified_places(inf=False) try: From 27bec1f05008abfaaa64d266b8e5c580a46c7ded Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Thu, 11 Jul 2024 09:50:04 +0200 Subject: [PATCH 151/369] disactivate coerce keyword --- src/sage/categories/finite_dimensional_algebras_with_basis.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sage/categories/finite_dimensional_algebras_with_basis.py b/src/sage/categories/finite_dimensional_algebras_with_basis.py index 9acac762396..8a64643552c 100644 --- a/src/sage/categories/finite_dimensional_algebras_with_basis.py +++ b/src/sage/categories/finite_dimensional_algebras_with_basis.py @@ -554,6 +554,7 @@ def principal_ideal(self, a, side='left', *args, **opts): - :meth:`peirce_summand` """ + opts.pop("coerce", None) return self.submodule([(a * b if side == 'right' else b * a) for b in self.basis()], *args, **opts) From 26513ef87407d3a578bb7dd52e7c4c494b8cf001 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Tue, 23 Jul 2024 21:22:59 +0200 Subject: [PATCH 152/369] undo wrong and unnecessary changes from before Travis found the real error --- src/sage/data_structures/stream.py | 13 +++---------- src/sage/modules/free_module_element.pyx | 4 ++-- .../rings/polynomial/multi_polynomial_element.py | 6 ++---- 3 files changed, 7 insertions(+), 16 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index 903ee532f0c..bf39a5c6d51 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -1613,8 +1613,6 @@ def define_implicitly(self, series, initial_values, equations, self._coefficient_ring = coefficient_ring self._base_ring = base_ring - # self._P = InfinitePolynomialRing(self._base_ring, names=["FESDUMMY"]) - # self._PF = CoefficientRing(self._P, CoefficientRing.Element) self._PF = CoefficientRing(self._base_ring) self._P = self._PF.base() if self._coefficient_ring == self._base_ring: @@ -2632,10 +2630,7 @@ def get_coefficient(self, n): sage: [h.get_coefficient(i) for i in range(10)] [0, 2, 6, 12, 20, 30, 42, 56, 72, 90] """ - l = self._left[n] - r = self._right[n] - m = l + r - return m # self._left[n] + self._right[n] + return self._left[n] + self._right[n] class Stream_sub(Stream_binary): @@ -4464,10 +4459,8 @@ def __getitem__(self, n): sage: [f2[i] for i in range(-1, 4)] [0, 2, 6, 12, 20] """ - c1 = self._series[n + self._shift] - c2 = ZZ.prod(range(n + 1, n + self._shift + 1)) - p = c1 * c2 - return p + return (ZZ.prod(range(n + 1, n + self._shift + 1)) + * self._series[n + self._shift]) def __hash__(self): """ diff --git a/src/sage/modules/free_module_element.pyx b/src/sage/modules/free_module_element.pyx index e14fb59fc1c..d71d22ac1f7 100644 --- a/src/sage/modules/free_module_element.pyx +++ b/src/sage/modules/free_module_element.pyx @@ -4562,7 +4562,7 @@ cdef class FreeModuleElement_generic_dense(FreeModuleElement): (-2/3, 0, 2, 2/3*pi) """ if right._parent is self._parent._base: - v = [x._mul_(right) for x in self._entries] + v = [(x)._mul_(right) for x in self._entries] else: v = [x * right for x in self._entries] return self._new_c(v) @@ -5012,7 +5012,7 @@ cdef class FreeModuleElement_generic_sparse(FreeModuleElement): cdef dict v = {} if right: for i, a in self._entries.iteritems(): - prod = a._mul_(right) + prod = (a)._mul_(right) if prod: v[i] = prod return self._new_c(v) diff --git a/src/sage/rings/polynomial/multi_polynomial_element.py b/src/sage/rings/polynomial/multi_polynomial_element.py index 08e04c07daa..91fcd38631b 100644 --- a/src/sage/rings/polynomial/multi_polynomial_element.py +++ b/src/sage/rings/polynomial/multi_polynomial_element.py @@ -177,11 +177,9 @@ def __call__(self, *x, **kwds): K = x[0].parent() except AttributeError: K = self.parent().base_ring() - y = K.zero() + y = K(0) for m, c in self.element().dict().items(): - p = prod(v ** e for v, e in zip(x, m) if e) - cp = c * p - y += cp + y += c * prod(v ** e for v, e in zip(x, m) if e) return y def _richcmp_(self, right, op): From 49f28fd2ac2a13218d29f7172177693eae604c58 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Wed, 24 Jul 2024 11:35:04 +0200 Subject: [PATCH 153/369] provide docstrings and doctests --- src/sage/data_structures/stream.py | 95 +++++++++++++++++++++++++----- 1 file changed, 81 insertions(+), 14 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index bf39a5c6d51..7059cc35523 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -95,14 +95,18 @@ # **************************************************************************** from sage.rings.integer_ring import ZZ -from sage.rings.rational_field import QQ from sage.rings.infinity import infinity from sage.arith.misc import divisors from sage.misc.misc_c import prod from sage.misc.lazy_attribute import lazy_attribute 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.fields import Fields +from sage.categories.functor import Functor +from sage.categories.integral_domains import IntegralDomains from sage.categories.hopf_algebras_with_basis import HopfAlgebrasWithBasis +from sage.categories.pushout import ConstructionFunctor +from sage.categories.quotient_fields import QuotientFields 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 @@ -1343,25 +1347,49 @@ def variables(self): """ return self._pool -from sage.categories.functor import Functor -from sage.categories.pushout import ConstructionFunctor -from sage.categories.fields import Fields -from sage.categories.integral_domains import IntegralDomains -from sage.categories.quotient_fields import QuotientFields class CoefficientRingFunctor(ConstructionFunctor): + r""" + A construction functor for the :class:`CoefficientRing`. + + The functor maps the integral domain of coefficients to the field + of unknown coefficients. + """ rank = 0 def __init__(self): + r""" + Initialize the functor. + + EXAMPLES:: + + sage: from sage.data_structures.stream import CoefficientRingFunctor + sage: CoefficientRingFunctor() + CoefficientRingFunctor + """ Functor.__init__(self, IntegralDomains(), Fields()) def _apply_functor(self, R): + r""" + Apply the functor to an integral domain. + + EXAMPLES:: + + sage: from sage.data_structures.stream import CoefficientRingFunctor + sage: CoefficientRingFunctor()(ZZ) # indirect doctest + CoefficientRing over Integer Ring + """ return CoefficientRing(R) + class CoefficientRingElement(FractionFieldElement): pass + class CoefficientRing(UniqueRepresentation, FractionField_generic): + r""" + The class of unknown coefficients in a stream. + """ def __init__(self, base_ring): """ @@ -1412,8 +1440,10 @@ def __init__(self, base_ring): sage: c = PF.gen(0) sage: p = a + c sage: p.parent() - Multivariate Lazy Taylor Series Ring in a, b over CoefficientRing over Fraction Field of Symmetric Functions over Rational Field in the homogeneous basis - + Multivariate Lazy Taylor Series Ring in a, b + over CoefficientRing + over Fraction Field of Symmetric Functions + over Rational Field in the homogeneous basis """ B = InfinitePolynomialRing(base_ring, names=["FESDUMMY"]) FractionField_generic.__init__(self, B, @@ -1421,14 +1451,51 @@ def __init__(self, base_ring): category=QuotientFields()) def _repr_(self): + r""" + Return a string representation of ``self``. + + EXAMPLES:: + + sage: from sage.data_structures.stream import CoefficientRing + sage: CoefficientRing(ZZ["q"]) + CoefficientRing over Univariate Polynomial Ring in q over Integer Ring + """ return "CoefficientRing over %s" % self.base_ring() def gen(self, i): + r""" + Return the ``n``-th generator of ``self``. + + The name of the generator is not to be relied on. + + EXAMPLES:: + + sage: from sage.data_structures.stream import CoefficientRing + sage: PF = CoefficientRing(ZZ["q"]) + sage: PF.gen(0) + FESDUMMY_0 + """ return self._element_class(self, self._R.gen()[i]) def construction(self): - return (CoefficientRingFunctor(), - self.base_ring()) + r""" + Return a pair ``(F, R)``, where ``F`` is a + :class:`CoefficientRingFunctor` and `R` is an integral + domain, such that ``F(R)`` returns ``self``. + + EXAMPLES:: + + sage: from sage.data_structures.stream import CoefficientRing + sage: PF = CoefficientRing(ZZ["q"]) + sage: F, R = PF.construction() + sage: F, R + (CoefficientRingFunctor, + Univariate Polynomial Ring in q over Integer Ring) + + sage: F(R) + CoefficientRing over Univariate Polynomial Ring in q over Integer Ring + """ + return (CoefficientRingFunctor(), self.base_ring()) class Stream_uninitialized(Stream): @@ -2082,7 +2149,7 @@ def _compute(self): raise ValueError("could not determine any coefficients:\n" + eqs_str) raise ValueError("there are no linear equations:\n" - + eqs_str) + + eqs_str) def _eq_str(self, idx, eq): """ @@ -2959,9 +3026,9 @@ def get_coefficient(self, n): sage: f = Stream_function(lambda n: n, True, 1) sage: g = Stream_function(lambda n: n^2, True, 1) sage: h = Stream_cauchy_compose(f, g, True) - sage: h[5] # indirect doctest + sage: h[5] # indirect doctest 527 - sage: [h[i] for i in range(10)] # indirect doctest + sage: [h[i] for i in range(10)] # indirect doctest [0, 1, 6, 28, 124, 527, 2172, 8755, 34704, 135772] """ fv = self._left._approximate_order @@ -3382,7 +3449,7 @@ def _approximate_order(self): sage: from sage.data_structures.stream import Stream_function, Stream_rmul sage: f = Stream_function(lambda n: Zmod(6)(n), True, 2) - sage: h = Stream_rmul(f, 3, True) # indirect doctest + sage: h = Stream_rmul(f, 3, True) # indirect doctest sage: h._approximate_order 2 sage: [h[i] for i in range(5)] From 32a8f35753f0678ce2b3397ea73c3875a24a9122 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Thu, 25 Jul 2024 14:52:46 +0200 Subject: [PATCH 154/369] (hacky) fix division for symmetric functions --- src/sage/combinat/sf/sfa.py | 43 ++++++++++++++++++++++++++++++ src/sage/data_structures/stream.py | 6 +---- 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/src/sage/combinat/sf/sfa.py b/src/sage/combinat/sf/sfa.py index 9292fd709e7..e592a2b11ab 100644 --- a/src/sage/combinat/sf/sfa.py +++ b/src/sage/combinat/sf/sfa.py @@ -418,6 +418,15 @@ def is_integral_domain(self, proof=True): return self.base_ring().is_integral_domain() def fraction_field(self): + r""" + Return the fraction field of ``self``. + + EXAMPLES:: + + sage: s = SymmetricFunctions(QQ).s() + sage: s.fraction_field() + Fraction Field of Symmetric Functions over Rational Field in the Schur basis + """ 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: @@ -3099,6 +3108,40 @@ class SymmetricFunctionAlgebra_generic_Element(CombinatorialFreeModule.Element): m[1, 1, 1] + m[2, 1] + m[3] sage: m.set_print_style('lex') """ + def __truediv__(self, x): + r""" + Return the quotient of ``self`` by ``other``. + + EXAMPLES:: + + sage: s = SymmetricFunctions(QQ).s() + sage: s[1]/(1+s[1]) + s[1]/(s[] + s[1]) + + sage: s[1]/2 + 1/2*s[1] + + TESTS:: + + sage: (s[1]/2).parent() + Symmetric Functions over Rational Field in the Schur basis + """ + from sage.categories.modules import _Fields + B = self.base_ring() + try: + bx = B(x) + except TypeError: + f = self.parent().fraction_field() + return f(self, x) + F = self.parent() + D = self._monomial_coefficients + + if B not in _Fields: + return type(self)(F, {k: c._divide_if_possible(x) + for k, c in D.items()}) + + return ~bx * self + def factor(self): """ Return the factorization of this symmetric function. diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index 7059cc35523..f001abe8fe5 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -1382,10 +1382,6 @@ def _apply_functor(self, R): return CoefficientRing(R) -class CoefficientRingElement(FractionFieldElement): - pass - - class CoefficientRing(UniqueRepresentation, FractionField_generic): r""" The class of unknown coefficients in a stream. @@ -1447,7 +1443,7 @@ def __init__(self, base_ring): """ B = InfinitePolynomialRing(base_ring, names=["FESDUMMY"]) FractionField_generic.__init__(self, B, - element_class=CoefficientRingElement, + element_class=FractionFieldElement, category=QuotientFields()) def _repr_(self): From 3eb493515c9fcf6d35bc76bd1fc54b209aa369f2 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Thu, 12 Sep 2024 10:58:49 +0200 Subject: [PATCH 155/369] rewrite some methods, add documentation --- src/sage/modules/free_module.py | 122 ++++- .../modules/free_module_pseudohomspace.py | 371 +++++++------ .../modules/free_module_pseudomorphism.py | 490 ++++++++++++------ 3 files changed, 648 insertions(+), 335 deletions(-) diff --git a/src/sage/modules/free_module.py b/src/sage/modules/free_module.py index df629169b2a..a78ca3cd3e7 100644 --- a/src/sage/modules/free_module.py +++ b/src/sage/modules/free_module.py @@ -3112,40 +3112,122 @@ def hom(self, im_gens, codomain=None, **kwds): codomain = R**n return super().hom(im_gens, codomain, **kwds) - def PseudoHom(self, twist=None, codomain=None): + def PseudoHom(self, twist, codomain=None): r""" - Create the Pseudo Hom space corresponding to given twist data. + Return the Pseudo Hom space corresponding to given data. + + INPUT: + + - ``twist`` -- the twisting morphism or the twisting derivation + + - ``codomain`` (default: ``None``) -- the codomain of the pseudo + morphisms; if ``None``, the codomain is the same than the domain EXAMPLES:: - sage: F = GF(25); M = F^2; twist = F.frobenius_endomorphism() - sage: PHS = M.PseudoHom(twist); PHS - Set of Pseudomorphisms from Vector space of dimension 2 over Finite Field in z2 of size 5^2 to Vector space of dimension 2 over Finite Field in z2 of size 5^2 - Twisted by the morphism Frobenius endomorphism z2 |--> z2^5 on Finite Field in z2 of size 5^2 + sage: F = GF(25) + sage: Frob = F.frobenius_endomorphism() + sage: M = F^2 + sage: M.PseudoHom(Frob) + Set of Pseudoendomorphisms (twisted by z2 |--> z2^5) of Vector space of dimension 2 over Finite Field in z2 of size 5^2 + .. SEEALSO:: + + :meth:`pseudohom` """ from sage.modules.free_module_pseudohomspace import FreeModulePseudoHomspace - return FreeModulePseudoHomspace(self, codomain=codomain, twist=twist) + if codomain is None: + codomain = self + return FreeModulePseudoHomspace(self, codomain, twist) - def pseudohom(self, morphism, twist=None, codomain=None, **kwds): + def pseudohom(self, f, twist, codomain=None, side="left"): r""" - Create a pseudomorphism defined by a given morphism and twist. - Let A be a ring and M a free module over A. Let \theta: A \to A + Return the pseudohomomorphism corresponding to the given data. + + We recall that, given two `R`-modules `M` and `M'` together with + a ring homomorphism `\theta: R \to R` and a `\theta`-derivation, + `\delta: R \to R` (that is, a map such that: + `\delta(xy) = \theta(x)\delta(y) + \delta(x)y`), a + pseudomorphism `f : M \to M'` is a additive map such that + + .. MATH: + + f(\lambda x) = \theta(\lambda) f(x) + \delta(\lambda) x + + When `\delta` is nonzero, this requires that `M` coerces into `M'`. + + INPUT: + + - ``f`` -- a matrix (or any other data) defining the + pseudomorphism + + - ``twist`` -- the twisting morphism or the twisting derivation + + - ``codomain`` (default: ``None``) -- the codomain of the pseudo + morphisms; if ``None``, the codomain is the same than the domain + + - ``side`` (default: ``left``) -- side of the vectors acted on by + the matrix EXAMPLES:: - sage: F = GF(25); M = F^2; twist = F.frobenius_endomorphism() - sage: ph = M.pseudohom([[1, 2], [0, 1]], twist, side="right"); ph - Free module pseudomorphism defined as left-multiplication by the matrix - [1 2] - [0 1] - twisted by the morphism Frobenius endomorphism z2 |--> z2^5 on Finite Field in z2 of size 5^2 - Domain: Vector space of dimension 2 over Finite Field in z2 of size 5^2 - Codomain: Vector space of dimension 2 over Finite Field in z2 of size 5^2 + sage: K. = GF(5^5) + sage: Frob = K.frobenius_endomorphism() + sage: V = K^2; W = K^3 + sage: f = V.pseudohom([[1, z, z^2], [1, z^2, z^4]], Frob, codomain=W) + sage: f + Free module pseudomorphism (twisted by z |--> z^5) defined by the matrix + [ 1 z z^2] + [ 1 z^2 z^4] + Domain: Vector space of dimension 2 over Finite Field in z of size 5^5 + Codomain: Vector space of dimension 3 over Finite Field in z of size 5^5 + + We check that `f` is indeed semi-linear:: + + sage: v = V([z+1, z+2]) + sage: f(z*v) + (2*z^2 + z + 4, z^4 + 2*z^3 + 3*z^2 + z, 4*z^4 + 2*z^2 + 3*z + 2) + sage: z^5 * f(v) + (2*z^2 + z + 4, z^4 + 2*z^3 + 3*z^2 + z, 4*z^4 + 2*z^2 + 3*z + 2) + + An example with a derivation:: + + sage: R. = ZZ[] + sage: d = R.derivation() + sage: M = R^2 + sage: Nabla = M.pseudohom([[1, t], [t^2, t^3]], d, side='right') + sage: Nabla + Free module pseudomorphism (twisted by d/dt) defined as left-multiplication by the matrix + [ 1 t] + [t^2 t^3] + Domain: Ambient free module of rank 2 over the integral domain Univariate Polynomial Ring in t over Integer Ring + Codomain: Ambient free module of rank 2 over the integral domain Univariate Polynomial Ring in t over Integer Ring + + sage: v = M([1,1]) + sage: Nabla(v) + (t + 1, t^3 + t^2) + sage: Nabla(t*v) + (t^2 + t + 1, t^4 + t^3 + 1) + sage: Nabla(t*v) == t * Nabla(v) + v + True + + If the twisting derivation is not zero, the domain must + coerce into the codomain + + sage: N = R^3 + sage: M.pseudohom([[1, t, t^2], [1, t^2, t^4]], d, codomain=N) + Traceback (most recent call last): + ... + ValueError: the domain does not coerce into the codomain + + .. SEEALSO:: + + :meth:`PseudoHom` """ from sage.modules.free_module_pseudomorphism import FreeModulePseudoMorphism - side = kwds.get("side", "left") - return FreeModulePseudoMorphism(self.PseudoHom(twist=twist, codomain=codomain), morphism, side) + parent = self.PseudoHom(twist, codomain) + return FreeModulePseudoMorphism(parent, f, side) + def inner_product_matrix(self): """ diff --git a/src/sage/modules/free_module_pseudohomspace.py b/src/sage/modules/free_module_pseudohomspace.py index 65af841f37b..93377f58183 100644 --- a/src/sage/modules/free_module_pseudohomspace.py +++ b/src/sage/modules/free_module_pseudohomspace.py @@ -3,11 +3,12 @@ AUTHORS: - - Yossef Musleh (2024-02): initial version + - Xavier Caruso, Yossef Musleh (2024-09): initial version """ # **************************************************************************** -# Copyright (C) 2024 Yossef Musleh +# Copyright (C) 2024 Xavier Caruso +# Yossef Musleh # # Distributed under the terms of the GNU General Public License (GPL) # @@ -20,16 +21,19 @@ # # https://www.gnu.org/licenses/ # **************************************************************************** -import sage.categories.homset -from sage.structure.element import is_Matrix -from sage.matrix.constructor import matrix, identity_matrix + +from sage.structure.unique_representation import UniqueRepresentation + +from sage.categories.homset import HomsetWithBase from sage.matrix.matrix_space import MatrixSpace -from sage.categories.morphism import Morphism -from sage.misc.lazy_import import lazy_import +from sage.structure.sequence import Sequence +from sage.rings.polynomial.ore_polynomial_ring import OrePolynomialRing +from sage.modules.free_module_pseudomorphism import FreeModulePseudoMorphism + -lazy_import('sage.rings.derivation', 'RingDerivation') +class FreeModulePseudoHomspace(UniqueRepresentation, HomsetWithBase): + Element = FreeModulePseudoMorphism -class FreeModulePseudoHomspace(sage.categories.homset.HomsetWithBase): r""" This class implements the space of Pseudomorphisms with a fixed twist. @@ -38,219 +42,256 @@ class FreeModulePseudoHomspace(sage.categories.homset.HomsetWithBase): TESTS:: - sage: F = GF(125); M = F^2; twist = F.frobenius_endomorphism() - sage: PHS = M.PseudoHom(twist) + sage: F = GF(125) + sage: M = F^2 + sage: Frob = F.frobenius_endomorphism() + sage: PHS = M.PseudoHom(Frob) sage: h = PHS([[1, 2], [1, 1]]) sage: e = M((4*F.gen()^2 + F.gen() + 2, 4*F.gen()^2 + 4*F.gen() + 4)) sage: h(e) (z3, 2*z3^2 + 3*z3 + 3) """ - def __init__(self, domain, codomain=None, twist=None): + @staticmethod + def __classcall_private__(cls, domain, codomain, twist): r""" Constructs the space of pseudomorphisms with a given twist. INPUT: - - ``domain`` - the domain of the pseudomorphism; a free module - - ``codomain`` - the codomain of the pseudomorphism; a free - module (default: None) + - ``domain`` -- a free module, the domain of this pseudomorphism - - ``twist`` - a twisting morphism, this is either a morphism or - a derivation (default: None) + - ``codomain`` -- a free module, the codomain of this pseudomorphism - EXAMPLES:: + - ``twist`` -- a twisting morphism or a twisting derivation + + TESTS:: + + sage: F = GF(125) + sage: Frob = F.frobenius_endomorphism() + sage: M = F^2 + sage: H = M.PseudoHom(Frob) + sage: type(H) + + + sage: # Testsuite(H).run() + + """ + ring = domain.base_ring() + if codomain.base_ring() is not ring: + raise ValueError("the domain and the codomain must be defined over the same ring") + ore = OrePolynomialRing(ring, twist, names='x') + if isinstance(ore, OrePolynomialRing) and ore._derivation is not None: + if not codomain.has_coerce_map_from(domain): + raise ValueError("the domain does not coerce into the codomain") + return cls.__classcall__(cls, domain, codomain, ore) + + def __init__(self, domain, codomain, ore): + r""" + Initialize this pseudohom space. + + INPUT: + + - ``domain`` -- a free module, the domain of this pseudomorphism + + - ``codomain`` -- a free module, the codomain of this pseudomorphism + + - ``ore`` -- the underlying Ore polynomial ring + + TESTS:: + + sage: F = GF(125) + sage: Frob = F.frobenius_endomorphism() + sage: M = F^2 + sage: M.PseudoHom(Frob) + Set of Pseudoendomorphisms (twisted by z3 |--> z3^5) of Vector space of dimension 2 over Finite Field in z3 of size 5^3 - sage: F = GF(125); M = F^2; twist = F.frobenius_endomorphism() - sage: PHS = M.PseudoHom(twist); PHS - Set of Pseudomorphisms from Vector space of dimension 2 over Finite Field in z3 of size 5^3 to Vector space of dimension 2 over Finite Field in z3 of size 5^3 - Twisted by the morphism Frobenius endomorphism z3 |--> z3^5 on Finite Field in z3 of size 5^3 """ self._domain = domain - self._codomain = domain - if codomain is not None: - self._codomain = codomain - super().__init__(self._domain, self._codomain, category=None) - self.base_homspace = self._domain.Hom(self._codomain) - self.twist = twist - self.twist_morphism = None - self.derivation = None - if twist is None: - return - if (twist.domain() is not self.domain().coordinate_ring() - or twist.codomain() is not self.codomain().coordinate_ring()): - raise TypeError("twisting morphism domain/codomain do not match\ - coordinate rings of the modules") - elif isinstance(twist, Morphism): - self.twist_morphism = twist - elif isinstance(twist, RingDerivation): - self.derivation = twist + self._codomain = codomain + super().__init__(domain, codomain, category=None) + self._ore = ore + if isinstance(ore, OrePolynomialRing): + self._morphism = ore._morphism + self._derivation = ore._derivation else: - raise TypeError("twist is not a ring morphism or derivation") + self._morphism = self._derivation = None + ring = ore.base_ring() + self._matrix_space = MatrixSpace(ring, domain.dimension(), codomain.dimension()) - def __call__(self, A, **kwds): + def _element_constructor_(self, f, side="left"): r""" - Coerce a matrix or free module homomorphism into a pseudomorphism. + Return the element of this parent constructed from the + given data. - INPUTS: - - ``A`` - either a matrix defining the morphism or a free module - morphism + TESTS:: - EXAMPLES:: + sage: F. = GF(5^3) + sage: Frob = F.frobenius_endomorphism() + sage: V = F^2 + sage: H = V.PseudoHom(Frob) + + sage: H([[1, z], [z, z^2]]) + Free module pseudomorphism (twisted by z |--> z^5) defined by the matrix + [ 1 z] + [ z z^2] + Domain: Vector space of dimension 2 over Finite Field in z of size 5^3 + Codomain: Vector space of dimension 2 over Finite Field in z of size 5^3 - sage: F = GF(125); M = F^2; twist = F.frobenius_endomorphism() - sage: PHS = M.PseudoHom(twist) - sage: h = PHS([[1, 2], [1, 1]]); h - Free module pseudomorphism defined by the matrix - [1 2] - [1 1] - twisted by the morphism Frobenius endomorphism z3 |--> z3^5 on Finite Field in z3 of size 5^3 - Domain: Vector space of dimension 2 over Finite Field in z3 of size 5^3 - Codomain: Vector space of dimension 2 over Finite Field in z3 of size 5^3 """ - return self._element_constructor_(A, **kwds) + return self.element_class(self, f, side) - def __repr__(self): + def __reduce__(self): r""" - Returns the string representation of the pseudomorphism space. - - EXAMPLES:: + TESTS:: - sage: Fq = GF(343); M = Fq^2; frob = Fq.frobenius_endomorphism() - sage: PHS = M.PseudoHom(frob); PHS - Set of Pseudomorphisms from Vector space of dimension 2 over Finite Field in z3 of size 7^3 to Vector space of dimension 2 over Finite Field in z3 of size 7^3 - Twisted by the morphism Frobenius endomorphism z3 |--> z3^7 on Finite Field in z3 of size 7^3 + sage: F = GF(125) + sage: Frob = F.frobenius_endomorphism() + sage: M = F^2 + sage: H = M.PseudoHom(Frob) + sage: loads(dumps(M)) is M + True """ - r = "Set of Pseudomorphisms from {} to {} {} {}" - morph = "" - if self.twist_morphism is not None: - morph = "\nTwisted by the morphism {}" - morph = morph.format(self.twist_morphism.__repr__()) - deriv = "" - if self.derivation is not None: - deriv = "\nTwisted by the derivation {}" - deriv = deriv.format(self.derivation.__repr__()) - return r.format(self.domain(), self.codomain(), morph, deriv) - - def _element_constructor_(self, A, **kwds): + if self._derivation is None: + twist = self._morphism + else: + twist = self._derivation + return FreeModulePseudoHomspace, (self.domain(), self.codomain(), twist) + + def _repr_twist(self): r""" - Coerce a matrix or free module homomorphism into a pseudomorphism. + Return a string representation of the twisting morphisms. - INPUTS: - - ``A`` - either a matrix defining the morphism or a free module - morphism + This is a helper method. TESTS:: - sage: F = GF(125); M = F^2; twist = F.frobenius_endomorphism() - sage: PHS = M.PseudoHom(twist) - sage: h = PHS._element_constructor_([[1, 2], [1, 1]]); h - Free module pseudomorphism defined by the matrix - [1 2] - [1 1] - twisted by the morphism Frobenius endomorphism z3 |--> z3^5 on Finite Field in z3 of size 5^3 - Domain: Vector space of dimension 2 over Finite Field in z3 of size 5^3 - Codomain: Vector space of dimension 2 over Finite Field in z3 of size 5^3 + sage: F. = GF(5^3) + sage: Frob = F.frobenius_endomorphism() + sage: M = F^2 - :: + sage: M.PseudoHom(Frob)._repr_twist() + 'twisted by z |--> z^5' + + sage: M.PseudoHom(Frob^3)._repr_twist() + 'untwisted' - sage: F = GF(125); M = F^2; twist = F.frobenius_endomorphism() - sage: PHS = M.PseudoHom(twist) - sage: morph = M.hom(matrix([[1, 2], [1, 1]])) - sage: phi = PHS._element_constructor_(morph, side="right"); phi - Free module pseudomorphism defined as left-multiplication by the matrix - [1 2] - [1 1] - twisted by the morphism Frobenius endomorphism z3 |--> z3^5 on Finite Field in z3 of size 5^3 - Domain: Vector space of dimension 2 over Finite Field in z3 of size 5^3 - Codomain: Vector space of dimension 2 over Finite Field in z3 of size 5^3 """ - from . import free_module_pseudomorphism as pseudo - side = kwds.get("side", "left") - if not self.codomain().base_ring().has_coerce_map_from( - self.domain().base_ring()) and not A.is_zero(): - raise TypeError("nontrivial morphisms require a coercion map" - "from the base ring of the domain to the base ring of the" - "codomain") - return pseudo.FreeModulePseudoMorphism(self, A, side=side) - - def _matrix_space(self): + s = "" + if self._morphism is not None: + s += self._morphism._repr_short() + if self._derivation is not None: + if s != "": + s += " and " + s += self._derivation._repr_() + if s == "": + return "untwisted" + else: + return "twisted by " + s + + def _repr_(self): r""" - Return the full matrix space of the underlying morphisms. + Returns a string representation of this pseudomorphism space. EXAMPLES:: - sage: Fq = GF(343); M = Fq^2; frob = Fq.frobenius_endomorphism() - sage: PHS = M.PseudoHom(frob) - sage: PHS._matrix_space() - Full MatrixSpace of 2 by 2 dense matrices over Finite Field in z3 of size 7^3 + sage: Fq = GF(7^3) + sage: Frob = Fq.frobenius_endomorphism() + sage: V = Fq^2 + sage: V.PseudoHom(Frob) # indirect doctest + Set of Pseudoendomorphisms (twisted by z3 |--> z3^7) of Vector space of dimension 2 over Finite Field in z3 of size 7^3 + + :: + + sage: V.PseudoHom(Frob, codomain=Fq^3) # indirect doctest + Set of Pseudomorphism (twisted by z3 |--> z3^7) from Vector space of dimension 2 over Finite Field in z3 of size 7^3 to Vector space of dimension 3 over Finite Field in z3 of size 7^3 + + :: + + sage: A. = QQ[] + sage: d = A.derivation() + sage: M = A^3 + sage: M.PseudoHom(d) + Set of Pseudoendomorphisms (twisted by d/dt) of Ambient free module of rank 3 over the principal ideal domain Univariate Polynomial Ring in t over Rational Field + """ - return self.base_homspace._matrix_space() + twist = self._repr_twist() + if self.domain() is self.codomain(): + return "Set of Pseudoendomorphisms (%s) of %s" % (twist, self.domain()) + else: + return "Set of Pseudomorphism (%s) from %s to %s" % (twist, self.domain(), self.codomain()) - def basis(self, side="left"): + def ore_ring(self, var='x'): r""" - Return a basis for the underlying matrix space. + Return the underlying Ore polynomial ring. + + INPUT: + + - ``var`` (default: ``x``) -- a string, the name of + tha variable EXAMPLES:: - sage: Fq = GF(343); M = Fq^2; frob = Fq.frobenius_endomorphism() - sage: PHS = M.PseudoHom(frob) - sage: PHS.basis() - (Vector space morphism represented by the matrix: - [1 0] - [0 0] - Domain: Vector space of dimension 2 over Finite Field in z3 of size 7^3 - Codomain: Vector space of dimension 2 over Finite Field in z3 of size 7^3, - Vector space morphism represented by the matrix: - [0 1] - [0 0] - Domain: Vector space of dimension 2 over Finite Field in z3 of size 7^3 - Codomain: Vector space of dimension 2 over Finite Field in z3 of size 7^3, - Vector space morphism represented by the matrix: - [0 0] - [1 0] - Domain: Vector space of dimension 2 over Finite Field in z3 of size 7^3 - Codomain: Vector space of dimension 2 over Finite Field in z3 of size 7^3, - Vector space morphism represented by the matrix: - [0 0] - [0 1] - Domain: Vector space of dimension 2 over Finite Field in z3 of size 7^3 - Codomain: Vector space of dimension 2 over Finite Field in z3 of size 7^3) + sage: Fq. = GF(7^3) + sage: Frob = Fq.frobenius_endomorphism() + sage: V = Fq^2 + sage: H = V.PseudoHom(Frob) + + sage: H.ore_ring() + Ore Polynomial Ring in x over Finite Field in z of size 7^3 twisted by z |--> z^7 + + sage: H.ore_ring('y') + Ore Polynomial Ring in y over Finite Field in z of size 7^3 twisted by z |--> z^7 + """ - return self.base_homspace.basis(side) + return self._ore.change_var(var) - def identity(self): + def matrix_space(self): r""" - Return the pseudomorphism corresponding to the identity transformation + Return the matrix space used for representing the + pseudomorphism in this space. EXAMPLES:: - sage: F = GF(125); M = F^2; twist = F.frobenius_endomorphism() - sage: PHS = M.PseudoHom(twist) - sage: PHS.identity() - Free module pseudomorphism defined by the matrix - [1 0] - [0 1] - twisted by the morphism Frobenius endomorphism z3 |--> z3^5 on Finite Field in z3 of size 5^3 - Domain: Vector space of dimension 2 over Finite Field in z3 of size 5^3 - Codomain: Vector space of dimension 2 over Finite Field in z3 of size 5^3 + sage: Fq. = GF(7^3) + sage: Frob = Fq.frobenius_endomorphism() + sage: V = Fq^2 + sage: W = Fq^3 + sage: H = V.PseudoHom(Frob, codomain=W) + sage: H.matrix_space() + Full MatrixSpace of 2 by 3 dense matrices over Finite Field in z of size 7^3 + """ - return self(self.base_homspace.identity()) + return self._matrix_space - def zero(self): + def basis(self, side="left"): r""" - Return the zero pseudomorphism. This corresponds to the zero matrix. + Return a basis for the underlying matrix space. EXAMPLES:: - sage: F = GF(125); M = F^2; twist = F.frobenius_endomorphism() - sage: PHS = M.PseudoHom(twist) - sage: PHS.zero() - Free module pseudomorphism defined by the matrix + sage: Fq = GF(7^3) + sage: Frob = Fq.frobenius_endomorphism() + sage: V = Fq^2 + sage: PHS = V.PseudoHom(Frob) + sage: PHS.basis() + [Free module pseudomorphism (twisted by z3 |--> z3^7) defined by the matrix + [1 0] + [0 0] + Domain: Vector space of dimension 2 over Finite Field in z3 of size 7^3 + Codomain: Vector space of dimension 2 over Finite Field in z3 of size 7^3, Free module pseudomorphism (twisted by z3 |--> z3^7) defined by the matrix + [0 1] + [0 0] + Domain: Vector space of dimension 2 over Finite Field in z3 of size 7^3 + Codomain: Vector space of dimension 2 over Finite Field in z3 of size 7^3, Free module pseudomorphism (twisted by z3 |--> z3^7) defined by the matrix [0 0] + [1 0] + Domain: Vector space of dimension 2 over Finite Field in z3 of size 7^3 + Codomain: Vector space of dimension 2 over Finite Field in z3 of size 7^3, Free module pseudomorphism (twisted by z3 |--> z3^7) defined by the matrix [0 0] - twisted by the morphism Frobenius endomorphism z3 |--> z3^5 on Finite Field in z3 of size 5^3 - Domain: Vector space of dimension 2 over Finite Field in z3 of size 5^3 - Codomain: Vector space of dimension 2 over Finite Field in z3 of size 5^3 + [0 1] + Domain: Vector space of dimension 2 over Finite Field in z3 of size 7^3 + Codomain: Vector space of dimension 2 over Finite Field in z3 of size 7^3] + """ - return self(self.base_homspace.zero()) + return Sequence(self(mat) for mat in self._matrix_space.basis()) diff --git a/src/sage/modules/free_module_pseudomorphism.py b/src/sage/modules/free_module_pseudomorphism.py index 0051aaf9528..e32a6f1bb8b 100644 --- a/src/sage/modules/free_module_pseudomorphism.py +++ b/src/sage/modules/free_module_pseudomorphism.py @@ -3,11 +3,12 @@ AUTHORS: - - Yossef Musleh (2024-02): initial version + - Xavier Caruso, Yossef Musleh (2024-09): initial version """ -#################################################################################### -# Copyright (C) 2024 Yossef Musleh +# **************************************************************************** +# Copyright (C) 2024 Xavier Caruso +# Yossef Musleh # # Distributed under the terms of the GNU General Public License (GPL) # @@ -21,243 +22,432 @@ # http://www.gnu.org/licenses/ #################################################################################### -import sage.modules.free_module as free_module from sage.categories.morphism import Morphism -from sage.modules import free_module_homspace, matrix_morphism from sage.structure.richcmp import rich_to_bool, richcmp -from sage.structure.sequence import Sequence -from sage.structure.all import parent -from sage.misc.lazy_import import lazy_import from sage.modules.free_module_morphism import FreeModuleMorphism -from sage.modules.free_module_homspace import FreeModuleHomspace, is_FreeModuleHomspace -from sage.matrix.constructor import matrix -from sage.matrix.matrix_space import MatrixSpace -lazy_import('sage.rings.derivation', 'RingDerivation') class FreeModulePseudoMorphism(Morphism): r""" - Let `M, M'` be free modules over a ring `R`, `\theta: R \to R` a ring - homomorphism, and `\delta: R \to R` a `\theta`-derivation, which is a map - such that: + Let `M, M'` be modules over a ring `R`, `\theta: R \to R` a + ring homomorphism, and `\delta: R \to R` a `\theta`-derivation, + which is a map such that: - `\delta(xy) = \theta(x)\delta(y) + \delta(x)y`. + .. MATH: - Then a pseudomorphism `f : M to M` is a map such that + \delta(xy) = \theta(x)\delta(y) + \delta(x)y. - `f(x + y) = f(x) + f(y)` - `f(\lambda x) = `\theta(\lambda)f(x) + \delta(\lambda)x` + A pseudomorphism `f : M \to M` is an additive map such that - The pair `(\theta, \delta)` may be referred to as the *twist* of - the morphism. + .. MATH: - TESTS:: + f(\lambda x) = \theta(\lambda)f(x) + \delta(\lambda) x - sage: V = ZZ^2 - sage: f = V.pseudohom([V.1, -2*V.0]); f - Free module pseudomorphism defined by the matrix - [ 0 1] - [-2 0] - Domain: Ambient free module of rank 2 over the principal ideal domain Integer Ring - Codomain: Ambient free module of rank 2 over the principal ideal domain Integer Ring - sage: f(V((1, 2))) - (-4, 1) + The map `\theta` (resp. `\delta`) is referred to as the + twisting endomorphism (resp. the twisting derivation) of `f`. - :: + TESTS:: - sage: P. = ZZ[]; deriv = P.derivation() + sage: P. = ZZ[] + sage: d = P.derivation() sage: M = P^2 - sage: f = M.pseudohom([[1, 2*x], [x, 1]], deriv, side="right"); f - Free module pseudomorphism defined as left-multiplication by the matrix + sage: f = M.pseudohom([[1, 2*x], [x, 1]], d); f + Free module pseudomorphism (twisted by d/dx) defined by the matrix [ 1 2*x] [ x 1] - twisted by the derivation d/dx Domain: Ambient free module of rank 2 over the integral domain Univariate Polynomial Ring in x over Integer Ring Codomain: Ambient free module of rank 2 over the integral domain Univariate Polynomial Ring in x over Integer Ring sage: e = M((2*x^2 + 3*x + 1, x^3 + 7*x + 4)) sage: f(e) - (2*x^4 + 16*x^2 + 15*x + 4, 3*x^3 + 6*x^2 + 8*x + 11) - sage: f = M.pseudohom([[1, 2], [1, 1]], deriv) + (x^4 + 9*x^2 + 11*x + 4, 5*x^3 + 9*x^2 + 9*x + 11) + sage: f = M.pseudohom([[1, 2], [1, 1]], d) sage: f(e) (x^3 + 2*x^2 + 14*x + 8, x^3 + 7*x^2 + 13*x + 13) :: - sage: Fq = GF(343); M = Fq^3; N = Fq^2; frob = Fq.frobenius_endomorphism(); z = Fq.gen() - sage: phi = M.pseudohom([[2, 3, 1], [1, 4, 6]], frob, N, side="right"); phi - Free module pseudomorphism defined as left-multiplication by the matrix + sage: Fq. = GF(7^3) + sage: Frob = Fq.frobenius_endomorphism() + sage: M = Fq^3 + sage: N = Fq^2 + sage: phi = M.pseudohom([[2, 3, 1], [1, 4, 6]], Frob, codomain=N, side="right") + sage: phi + Free module pseudomorphism (twisted by z |--> z^7) defined as left-multiplication by the matrix [2 3 1] [1 4 6] - twisted by the morphism Frobenius endomorphism z3 |--> z3^7 on Finite Field in z3 of size 7^3 - Domain: Vector space of dimension 3 over Finite Field in z3 of size 7^3 - Codomain: Vector space of dimension 2 over Finite Field in z3 of size 7^3 - sage: elem = (4*z^2 + 4*z + 3, 2, z + 5) - sage: phi(elem) - (2*z3 + 1, 6*z3^2 + 4*z3 + 5) + Domain: Vector space of dimension 3 over Finite Field in z of size 7^3 + Codomain: Vector space of dimension 2 over Finite Field in z of size 7^3 + sage: v = (4*z^2 + 4*z + 3, 2, z + 5) + sage: phi(v) + (2*z + 1, 6*z^2 + 4*z + 5) + """ - def __init__(self, pseudohomspace, base_morphism, side="left"): + def __init__(self, parent, f, side): """ Constructs a pseudomorphism of free modules. INPUT: - - ``pseudohomspace`` - the parent space of pseudomorphisms, - containing - - ``base_morphism`` - either a morphism or a matrix defining a - morphism + - ``parent`` -- the parent space of pseudomorphisms - - side -- side of the vectors acted on by the matrix - (default: ``"left"``) + - ``f`` -- a pseudomorphism or a matrix defining this + pseudomorphism - EXAMPLES:: + - ``side`` -- side of the vectors acted on by the matrix + + TESTS:: - sage: F = GF(25); M = F^3; twist = F.frobenius_endomorphism(5) - sage: phi = M.pseudohom(matrix(F,3,[1..9]), twist) - sage: type(phi) - + sage: F. = GF(5^3) + sage: Frob = F.frobenius_endomorphism() + sage: M = F^2 + sage: H = M.PseudoHom(Frob) + sage: H + Set of Pseudoendomorphisms (twisted by z |--> z^5) of Vector space of dimension 2 over Finite Field in z of size 5^3 + + The attribute ``f`` can be a matrix:: + + sage: mat = matrix(F, 2, [1, z, z^2, z^3]) + sage: f = H(mat) + sage: f + Free module pseudomorphism (twisted by z |--> z^5) defined by the matrix + [ 1 z] + [ z^2 2*z + 2] + Domain: Vector space of dimension 2 over Finite Field in z of size 5^3 + Codomain: Vector space of dimension 2 over Finite Field in z of size 5^3 + + sage: type(f) + + + or a pseudomorphism with the same parent:: + + sage: H(f) + Free module pseudomorphism (twisted by z |--> z^5) defined by the matrix + [ 1 z] + [ z^2 2*z + 2] + Domain: Vector space of dimension 2 over Finite Field in z of size 5^3 + Codomain: Vector space of dimension 2 over Finite Field in z of size 5^3 + + When the twisting morphism and the twisting derivation are both trivial, + pseudomorphisms are just linear applications and coercion between those + works:: + + sage: id = End(F).identity() + sage: g = M.hom(mat) + sage: M.PseudoHom(id)(g) + Free module pseudomorphism (untwisted) defined by the matrix + [ 1 z] + [ z^2 2*z + 2] + Domain: Vector space of dimension 2 over Finite Field in z of size 5^3 + Codomain: Vector space of dimension 2 over Finite Field in z of size 5^3 + + An example with ``side=right``: + + sage: M.pseudohom(mat, Frob, side="right") + Free module pseudomorphism (twisted by z |--> z^5) defined as left-multiplication by the matrix + [ 1 z] + [ z^2 2*z + 2] + Domain: Vector space of dimension 2 over Finite Field in z of size 5^3 + Codomain: Vector space of dimension 2 over Finite Field in z of size 5^3 :: - sage: F = GF(125); M = F^2; twist = F.frobenius_endomorphism() - sage: morph = M.hom(matrix([[1,2],[0,1]])) - sage: phi = M.pseudohom(morph, twist, side="right"); phi - Free module pseudomorphism defined as left-multiplication by the matrix - [1 2] - [0 1] - twisted by the morphism Frobenius endomorphism z3 |--> z3^5 on Finite Field in z3 of size 5^3 - Domain: Vector space of dimension 2 over Finite Field in z3 of size 5^3 - Codomain: Vector space of dimension 2 over Finite Field in z3 of size 5^3 + sage: M.pseudohom(mat, Frob, side="middle") + Traceback (most recent call last): + ... + ValueError: the side must be either 'left' or 'right' + """ - Morphism.__init__(self, pseudohomspace) - dom = pseudohomspace.domain() - codom = pseudohomspace.codomain() - rows = dom.dimension() - cols = codom.dimension() - if side == "right": - rows = codom.dimension() - cols = dom.dimension() - matrix_space = MatrixSpace(dom.coordinate_ring(), rows, cols) - if isinstance(base_morphism, FreeModuleMorphism): - self._base_matrix = matrix_space(base_morphism.matrix()) + Morphism.__init__(self, parent) + dom = parent.domain() + codom = parent.codomain() + if side != "left" and side != "right": + raise ValueError("the side must be either 'left' or 'right'") + matrix_space = parent.matrix_space() + if ((isinstance(f, FreeModulePseudoMorphism) and f.parent() is parent) + or (isinstance(f, FreeModuleMorphism) + and f.domain() is dom and f.codomain() is codom + and parent._morphism is None and parent._derivation is None)): + if f.side() == 'right': + self._matrix = f.matrix().transpose() + else: + self._matrix = f.matrix() else: - self._base_matrix = matrix_space(base_morphism) - self.derivation = pseudohomspace.derivation - self.twist_morphism = pseudohomspace.twist_morphism - self.side = side + if side == "right": + self._matrix = matrix_space.transposed(f).transpose() + else: + self._matrix = matrix_space(f) + self._morphism = parent._morphism + self._derivation = parent._derivation + self._side = side def _call_(self, x): r""" - Return the result of applying a pseudomorphism to an element of the - free module. + Return the result of applying this pseudomorphism to ``x``. TESTS:: - sage: Fq = GF(343); M = Fq^3; frob = Fq.frobenius_endomorphism() - sage: ph = M.pseudohom([[1, Fq.gen(), 3], [0, 1, 1], [2, 1, 1]], frob, side="right") - sage: e = M((3*Fq.gen()^2 + 5*Fq.gen() + 2, 6*Fq.gen()^2 + 2*Fq.gen() + 2, Fq.gen() + 4)) - sage: ph(e) - (z3^2 + 6*z3 + 2, z3^2 + 2*z3 + 1, 2*z3^2 + 4*z3) + sage: Fq. = GF(7^3) + sage: M = Fq^3 + sage: Frob = Fq.frobenius_endomorphism() + sage: f = M.pseudohom([[1, z, 3], [0, 1, 1], [2, 1, 1]], Frob) + sage: e = M((3*z^2 + 5*z + 2, 6*z^2 + 2*z + 2, z + 4)) + sage: f(e) + (3*z^2 + 4*z + 4, 6*z^2 + 5*z + 6, 6*z^2 + 5*z + 3) + + :: + + sage: g = M.pseudohom([[1, z, 3], [0, 1, 1], [2, 1, 1]], Frob, side="right") + sage: g(e) + (z^2 + 6*z + 2, z^2 + 2*z + 1, 2*z^2 + 4*z) + """ - if self.domain().is_ambient(): + D = self.domain() + C = self.codomain() + if D.is_ambient(): x = x.element() else: - x = self.domain().coordinate_vector(x) - C = self.codomain() - if self.twist_morphism is None: + x = D.coordinate_vector(x) + if self._morphism is None: x_twist = x else: - x_twist = self.domain()(list(map(self.twist_morphism, x))) - if self.side == "left": - v = x_twist * self._base_matrix - else: - v = self._base_matrix * x_twist - if self.derivation is not None: - v += self.domain()(list(map(self.derivation, x))) + x_twist = D(list(map(self._morphism, x))) + v = x_twist * self._matrix + if self._derivation is not None: + v += D(list(map(self._derivation, x))) if not C.is_ambient(): v = C.linear_combination_of_basis(v) return C._element_constructor_(v) - def __repr__(self): + def _repr_(self): r""" Return the string representation of a pseudomorphism. TESTS:: - sage: Fq = GF(343); M = Fq^3; frob = Fq.frobenius_endomorphism() - sage: ph = M.pseudohom([[1,1,1],[2,2,2],[3,3,3]], frob); ph - Free module pseudomorphism defined by the matrix + sage: Fq. = GF(7^3) + sage: M = Fq^3 + sage: Frob = Fq.frobenius_endomorphism() + + sage: f = M.pseudohom([[1,1,1], [2,2,2], [3,3,3]], Frob) + sage: f # indirect doctest + Free module pseudomorphism (twisted by z |--> z^7) defined by the matrix [1 1 1] [2 2 2] [3 3 3] - twisted by the morphism Frobenius endomorphism z3 |--> z3^7 on Finite Field in z3 of size 7^3 - Domain: Vector space of dimension 3 over Finite Field in z3 of size 7^3 - Codomain: Vector space of dimension 3 over Finite Field in z3 of size 7^3 + Domain: Vector space of dimension 3 over Finite Field in z of size 7^3 + Codomain: Vector space of dimension 3 over Finite Field in z of size 7^3 + + sage: g = M.pseudohom([[1,1,1], [2,2,2], [3,3,3]], Frob, side="right") + sage: g # indirect doctest + Free module pseudomorphism (twisted by z |--> z^7) defined as left-multiplication by the matrix + [1 1 1] + [2 2 2] + [3 3 3] + Domain: Vector space of dimension 3 over Finite Field in z of size 7^3 + Codomain: Vector space of dimension 3 over Finite Field in z of size 7^3 """ - r = "Free module pseudomorphism defined {}by the "\ - "matrix\n{!r}{}{}\nDomain: {}\nCodomain: {}" - act = "" - if self.side == "right": - act = "as left-multiplication " - morph = "" - if self.twist_morphism is not None: - morph = "\ntwisted by the morphism {}" - morph = morph.format(self.twist_morphism.__repr__()) - deriv = "" - if self.derivation is not None: - deriv = "\ntwisted by the derivation {}" - deriv = deriv.format(self.derivation.__repr__()) - return r.format(act, self.matrix(), morph, deriv, - self.domain(), self.codomain()) + twist = self.parent()._repr_twist() + s = "Free module pseudomorphism (%s) defined " % twist + if self._side == "right": + s += "as left-multiplication " + s += "by the matrix\n%s\n" % self.matrix() + s += "Domain: %s\n" % self.domain() + s += "Codomain: %s" % self.codomain() + return s def matrix(self): r""" - Return the underlying matrix of a pseudomorphism. + Return the underlying matrix of this pseudomorphism. - If a pseudomorphism `f` on free module `M` has matrix m acting on - the left on elements `v \in M`, with twisting morphism `\theta`. - Then we have + It is defined as the matrix `M` whose lines (resp. columns if + ``side`` is ``right``) are the coordinates of the images of + the distinguished basis of the domain. - `f(v) = m*\theta(v)` + EXAMPLES:: - where `\theta` acts of the coefficients of `v` in terms of the basis - for `m`. + sage: Fq. = GF(7^3) + sage: Frob = Fq.frobenius_endomorphism() + sage: M = Fq^3 + sage: f = M.pseudohom([[1, z, 3], [0, 1, z^2], [z+1, 1, 1]], Frob) + sage: f.matrix() + [ 1 z 3] + [ 0 1 z^2] + [z + 1 1 1] + + sage: e1, e2, e3 = M.basis() + sage: f(e1) + (1, z, 3) + sage: f(e2) + (0, 1, z^2) + sage: f(e3) + (z + 1, 1, 1) - EXAMPLES:: + TESTS:: - sage: Fq = GF(343); M = Fq^3; frob = Fq.frobenius_endomorphism() - sage: ph = M.pseudohom([[1, 2, 3], [0, 1, 1], [2, 1, 1]], frob, side="right") - sage: e = M((3*Fq.gen()^2 + 5*Fq.gen() + 2, 6*Fq.gen()^2 + 2*Fq.gen() + 2, Fq.gen() + 4)) - sage: ph.matrix() - [1 2 3] - [0 1 1] - [2 1 1] - sage: ph(e) == ph.matrix()*vector([frob(c) for c in e]) + sage: v = M.random_element() + sage: f(v) == vector([Frob(c) for c in v]) * f.matrix() True + """ - return self._base_matrix + if self._side == "left": + return self._matrix + else: + return self._matrix.transpose() def twisting_derivation(self): r""" - Return the twisting derivation of the pseudomorphism. + Return the twisting derivation of the pseudomorphism + (or ``None`` if the twisting derivation is zero). EXAMPLES:: - sage: P. = ZZ[]; deriv = P.derivation(); M = P^2 - sage: f = M.pseudohom([[1, 2*x], [x, 1]], deriv, side="right") + sage: P. = ZZ[] + sage: d = P.derivation() + sage: M = P^2 + sage: f = M.pseudohom([[1, 2*x], [x, 1]], d) sage: f.twisting_derivation() d/dx + + :: + + sage: Fq. = GF(7^3) + sage: Frob = Fq.frobenius_endomorphism() + sage: V = Fq^2 + sage: f = V.pseudohom([[1, z], [0, z^2]], Frob) + sage: f.twisting_derivation() + """ - return self.derivation + return self._derivation def twisting_morphism(self): r""" - Return the twisting homomorphism of the pseudomorphism. + Return the twisting morphism of the pseudomorphism + (or ``None`` if the twisting morphism is the identity). + + EXAMPLES:: + + sage: Fq. = GF(7^3) + sage: Frob = Fq.frobenius_endomorphism() + sage: V = Fq^2 + sage: f = V.pseudohom([[1, z], [0, z^2]], Frob) + sage: f.twisting_morphism() + Frobenius endomorphism z |--> z^7 on Finite Field in z of size 7^3 + + :: + + sage: P. = ZZ[] + sage: d = P.derivation() + sage: M = P^2 + sage: f = M.pseudohom([[1, 2*x], [x, 1]], d) + sage: f.twisting_morphism() + + """ + return self._morphism + + def side(self): + """ + Return the side of vectors acted on, relative to the matrix. EXAMPLES:: - sage: Fq = GF(343); M = Fq^3; frob = Fq.frobenius_endomorphism() - sage: ph = M.pseudohom([[1, 2, 3], [0, 1, 1], [2, 1, 1]], frob, side="right") - sage: ph.twisting_morphism() - Frobenius endomorphism z3 |--> z3^7 on Finite Field in z3 of size 7^3 + sage: Fq. = GF(7^3) + sage: Frob = Fq.frobenius_endomorphism() + sage: V = Fq^2 + + sage: m = matrix(2, [1, z, z^2, z^3]) + sage: h1 = V.pseudohom(m, Frob) + sage: h1.side() + 'left' + sage: h1([1, 0]) + (1, z) + + sage: h2 = V.pseudohom(m, Frob, side="right") + sage: h2.side() + 'right' + sage: h2([1, 0]) + (1, z^2) + """ + + return self._side + + def side_switch(self): + """ + Return the same morphism, acting on vectors on the opposite side. + + EXAMPLES:: + + sage: Fq. = GF(7^3) + sage: Frob = Fq.frobenius_endomorphism() + sage: V = Fq^2 + + sage: m = matrix(2, [1, z, z^2, z^3]) + sage: h1 = V.pseudohom(m, Frob) + sage: h1 + Free module pseudomorphism (twisted by z |--> z^7) defined by the matrix + [ 1 z] + [ z^2 z^2 + 3] + Domain: Vector space of dimension 2 over Finite Field in z of size 7^3 + Codomain: Vector space of dimension 2 over Finite Field in z of size 7^3 + + sage: h2 = h1.side_switch() + sage: h2 + Free module pseudomorphism (twisted by z |--> z^7) defined as left-multiplication by the matrix + [ 1 z^2] + [ z z^2 + 3] + Domain: Vector space of dimension 2 over Finite Field in z of size 7^3 + Codomain: Vector space of dimension 2 over Finite Field in z of size 7^3 + + We check that ``h1`` and ``h2`` are the same:: + + sage: v = V.random_element() + sage: h1(v) == h2(v) + True + """ - return self.twist_morphism + if self._side == "left": + side = "right" + mat = self._matrix.transpose() + else: + side = "left" + mat = self._matrix + return self.parent()(mat, side) + + + def __eq__(self, other): + r""" + Compare this morphism with ``other``. + + TESTS:: + + sage: Fq. = GF(7^3) + sage: Frob = Fq.frobenius_endomorphism() + sage: V = Fq^2 + sage: m = random_matrix(Fq, 2) + + sage: f = V.pseudohom(m, Frob) + sage: g = V.pseudohom(m.transpose(), Frob, side="right") + sage: f == g + True + + sage: g = V.pseudohom(m.transpose(), Frob) + sage: f == g + False + + sage: g = V.pseudohom(m, Frob^2) + sage: f == g + False + + sage: g = V.pseudohom(m, Frob^3) + sage: h = V.hom(m) + sage: g == h + True + + """ + if isinstance(other, FreeModuleMorphism): + try: + other = self.parent()(other) + except ValueError: + return False + if isinstance(other, FreeModulePseudoMorphism): + return self.parent() is other.parent() and self._matrix == other._matrix + From 133b35ca9dda0dacef383be708424055add91df3 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Thu, 12 Sep 2024 11:02:32 +0200 Subject: [PATCH 156/369] remove useless import --- src/sage/modules/free_module.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/sage/modules/free_module.py b/src/sage/modules/free_module.py index a78ca3cd3e7..bb8c8ca4484 100644 --- a/src/sage/modules/free_module.py +++ b/src/sage/modules/free_module.py @@ -213,8 +213,6 @@ richcmp_not_equal, ) from sage.structure.sequence import Sequence -from sage.categories.morphism import Morphism -from sage.matrix.constructor import matrix ############################################################################### From 1cbbc0b27f8af0e6600328a0bf025d8de398479d Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Thu, 19 Sep 2024 11:01:02 +0200 Subject: [PATCH 157/369] include doctests in documentation --- src/doc/en/reference/modules/index.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/doc/en/reference/modules/index.rst b/src/doc/en/reference/modules/index.rst index 93a337db04c..a67fffc92d3 100644 --- a/src/doc/en/reference/modules/index.rst +++ b/src/doc/en/reference/modules/index.rst @@ -92,6 +92,15 @@ Morphisms sage/modules/matrix_morphism +Pseudomorphisms +--------------- + +.. toctree:: + :maxdepth: 1 + + sage/modules/free_module_pseudohomspace + sage/modules/free_module_pseudomorphism + Vectors ------- From 4b88e009867e9a0cbe2ca8d8bb2aa8a78165f29f Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Thu, 19 Sep 2024 11:14:17 +0200 Subject: [PATCH 158/369] colon missing --- src/sage/modules/free_module_pseudomorphism.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/modules/free_module_pseudomorphism.py b/src/sage/modules/free_module_pseudomorphism.py index e32a6f1bb8b..54b40ba0024 100644 --- a/src/sage/modules/free_module_pseudomorphism.py +++ b/src/sage/modules/free_module_pseudomorphism.py @@ -140,7 +140,7 @@ def __init__(self, parent, f, side): Domain: Vector space of dimension 2 over Finite Field in z of size 5^3 Codomain: Vector space of dimension 2 over Finite Field in z of size 5^3 - An example with ``side=right``: + An example with ``side=right``:: sage: M.pseudohom(mat, Frob, side="right") Free module pseudomorphism (twisted by z |--> z^5) defined as left-multiplication by the matrix From db5ae0ae676fa9f2ed8180b7dad85e7d6ee6a1f2 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Mon, 23 Sep 2024 18:03:55 +0200 Subject: [PATCH 159/369] move repr_twist to OrePolynomialRing --- .../modules/free_module_pseudohomspace.py | 42 ++++------------- .../modules/free_module_pseudomorphism.py | 10 ++-- .../rings/polynomial/ore_polynomial_ring.py | 47 +++++++++++++++---- 3 files changed, 52 insertions(+), 47 deletions(-) diff --git a/src/sage/modules/free_module_pseudohomspace.py b/src/sage/modules/free_module_pseudohomspace.py index 93377f58183..14762e86078 100644 --- a/src/sage/modules/free_module_pseudohomspace.py +++ b/src/sage/modules/free_module_pseudohomspace.py @@ -63,7 +63,7 @@ def __classcall_private__(cls, domain, codomain, twist): - ``codomain`` -- a free module, the codomain of this pseudomorphism - - ``twist`` -- a twisting morphism or a twisting derivation + - ``twist`` -- a twisting morphism/derivation or a Ore polynomial ring TESTS:: @@ -80,7 +80,12 @@ def __classcall_private__(cls, domain, codomain, twist): ring = domain.base_ring() if codomain.base_ring() is not ring: raise ValueError("the domain and the codomain must be defined over the same ring") - ore = OrePolynomialRing(ring, twist, names='x') + if isinstance(twist, OrePolynomialRing): + ore = twist + if ore.base_ring() is not ring: + raise ValueError("base rings do not match") + else: + ore = OrePolynomialRing(ring, twist, names='x') if isinstance(ore, OrePolynomialRing) and ore._derivation is not None: if not codomain.has_coerce_map_from(domain): raise ValueError("the domain does not coerce into the codomain") @@ -158,37 +163,6 @@ def __reduce__(self): twist = self._derivation return FreeModulePseudoHomspace, (self.domain(), self.codomain(), twist) - def _repr_twist(self): - r""" - Return a string representation of the twisting morphisms. - - This is a helper method. - - TESTS:: - - sage: F. = GF(5^3) - sage: Frob = F.frobenius_endomorphism() - sage: M = F^2 - - sage: M.PseudoHom(Frob)._repr_twist() - 'twisted by z |--> z^5' - - sage: M.PseudoHom(Frob^3)._repr_twist() - 'untwisted' - - """ - s = "" - if self._morphism is not None: - s += self._morphism._repr_short() - if self._derivation is not None: - if s != "": - s += " and " - s += self._derivation._repr_() - if s == "": - return "untwisted" - else: - return "twisted by " + s - def _repr_(self): r""" Returns a string representation of this pseudomorphism space. @@ -215,7 +189,7 @@ def _repr_(self): Set of Pseudoendomorphisms (twisted by d/dt) of Ambient free module of rank 3 over the principal ideal domain Univariate Polynomial Ring in t over Rational Field """ - twist = self._repr_twist() + twist = self._ore._repr_twist() if self.domain() is self.codomain(): return "Set of Pseudoendomorphisms (%s) of %s" % (twist, self.domain()) else: diff --git a/src/sage/modules/free_module_pseudomorphism.py b/src/sage/modules/free_module_pseudomorphism.py index 54b40ba0024..f549fb95e85 100644 --- a/src/sage/modules/free_module_pseudomorphism.py +++ b/src/sage/modules/free_module_pseudomorphism.py @@ -214,9 +214,11 @@ def _call_(self, x): v = x_twist * self._matrix if self._derivation is not None: v += D(list(map(self._derivation, x))) - if not C.is_ambient(): + if C.is_ambient(): + v = v.list() + else: v = C.linear_combination_of_basis(v) - return C._element_constructor_(v) + return C(v) def _repr_(self): r""" @@ -246,7 +248,7 @@ def _repr_(self): Domain: Vector space of dimension 3 over Finite Field in z of size 7^3 Codomain: Vector space of dimension 3 over Finite Field in z of size 7^3 """ - twist = self.parent()._repr_twist() + twist = self.parent()._ore._repr_twist() s = "Free module pseudomorphism (%s) defined " % twist if self._side == "right": s += "as left-multiplication " @@ -290,7 +292,7 @@ def matrix(self): """ if self._side == "left": - return self._matrix + return self._matrix.__copy__() else: return self._matrix.transpose() diff --git a/src/sage/rings/polynomial/ore_polynomial_ring.py b/src/sage/rings/polynomial/ore_polynomial_ring.py index f28b90ad198..1c36401db3c 100644 --- a/src/sage/rings/polynomial/ore_polynomial_ring.py +++ b/src/sage/rings/polynomial/ore_polynomial_ring.py @@ -317,7 +317,7 @@ def __classcall_private__(cls, base_ring, twist=None, names=None, sparse=False, Univariate Polynomial Ring in x over Finite Field in a of size 5^2 sage: S. = OrePolynomialRing(k, Frob, polcast=False) sage: S - Ore Polynomial Ring in x over Finite Field in a of size 5^2 twisted by Identity + Ore Polynomial Ring in x over Finite Field in a of size 5^2 untwisted """ if base_ring not in CommutativeRings(): raise TypeError('base_ring must be a commutative ring') @@ -325,7 +325,7 @@ def __classcall_private__(cls, base_ring, twist=None, names=None, sparse=False, if (twist.domain() is not base_ring or twist.codomain() is not base_ring): raise TypeError("the twisting morphism must be an endomorphism of base_ring (=%s)" % base_ring) - if twist.is_identity() and polcast: + if twist.is_identity(): morphism = None else: morphism = twist @@ -596,6 +596,38 @@ def _coerce_map_from_(self, P): if P.variable_name() == self.variable_name(): return base_ring.has_coerce_map_from(P.base_ring()) + def _repr_twist(self): + r""" + Return a string representation of the twisting morphisms. + + This is a helper method. + + TESTS:: + + sage: F. = GF(5^3) + sage: Frob = F.frobenius_endomorphism() + + sage: S. = OrePolynomialRing(F, Frob) + sage: S._repr_twist() + 'twisted by z |--> z^5' + + sage: T. = OrePolynomialRing(F, Frob^3, polcast=False) + sage: T._repr_twist() + 'untwisted' + + """ + s = "" + if self._morphism is not None: + s += self._morphism._repr_short() + if self._derivation is not None: + if s != "": + s += " and " + s += self._derivation._repr_() + if s == "": + return "untwisted" + else: + return "twisted by " + s + def _repr_(self) -> str: r""" Return a string representation of ``self``. @@ -613,13 +645,7 @@ def _repr_(self) -> str: sage: T Ore Polynomial Ring in d over Univariate Polynomial Ring in t over Rational Field twisted by d/dt """ - s = "Ore Polynomial Ring in %s over %s twisted by " % (self.variable_name(), self.base_ring()) - if self._derivation is None: - s += self._morphism._repr_short() - else: - if self._morphism is not None: - s += "%s and " % self._morphism._repr_short() - s += self._derivation._repr_() + s = "Ore Polynomial Ring in %s over %s %s" % (self.variable_name(), self.base_ring(), self._repr_twist()) if self.is_sparse(): s = "Sparse " + s return s @@ -1201,6 +1227,9 @@ def fraction_field(self): self._fraction_field.register_coercion(self) return self._fraction_field + def quotient(self, P, names=None): + return self(P).quotient_module(names=names) + def _pushout_(self, other): r""" Return the pushout of this Ore polynomial ring and ``other``. From 0dd14fef3740954c8e82a3fe8e05040e5f87a9f1 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Mon, 23 Sep 2024 18:56:58 +0200 Subject: [PATCH 160/369] fix lint and doctest --- src/sage/modules/free_module_pseudohomspace.py | 2 +- src/sage/modules/free_module_pseudomorphism.py | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/sage/modules/free_module_pseudohomspace.py b/src/sage/modules/free_module_pseudohomspace.py index 14762e86078..b78f7fb3c18 100644 --- a/src/sage/modules/free_module_pseudohomspace.py +++ b/src/sage/modules/free_module_pseudohomspace.py @@ -85,7 +85,7 @@ def __classcall_private__(cls, domain, codomain, twist): if ore.base_ring() is not ring: raise ValueError("base rings do not match") else: - ore = OrePolynomialRing(ring, twist, names='x') + ore = OrePolynomialRing(ring, twist, names='x', polcast=False) if isinstance(ore, OrePolynomialRing) and ore._derivation is not None: if not codomain.has_coerce_map_from(domain): raise ValueError("the domain does not coerce into the codomain") diff --git a/src/sage/modules/free_module_pseudomorphism.py b/src/sage/modules/free_module_pseudomorphism.py index f549fb95e85..8e05955bd26 100644 --- a/src/sage/modules/free_module_pseudomorphism.py +++ b/src/sage/modules/free_module_pseudomorphism.py @@ -414,7 +414,6 @@ def side_switch(self): mat = self._matrix return self.parent()(mat, side) - def __eq__(self, other): r""" Compare this morphism with ``other``. @@ -452,4 +451,3 @@ def __eq__(self, other): return False if isinstance(other, FreeModulePseudoMorphism): return self.parent() is other.parent() and self._matrix == other._matrix - From fe44f43a35605fe2a4683225e95760178e8a2168 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Thu, 26 Sep 2024 07:06:56 +0200 Subject: [PATCH 161/369] apply derivation after coercion --- src/sage/modules/free_module_pseudomorphism.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sage/modules/free_module_pseudomorphism.py b/src/sage/modules/free_module_pseudomorphism.py index 8e05955bd26..619c162f9a7 100644 --- a/src/sage/modules/free_module_pseudomorphism.py +++ b/src/sage/modules/free_module_pseudomorphism.py @@ -212,13 +212,13 @@ def _call_(self, x): else: x_twist = D(list(map(self._morphism, x))) v = x_twist * self._matrix - if self._derivation is not None: - v += D(list(map(self._derivation, x))) if C.is_ambient(): - v = v.list() + v = C(v.list()) else: v = C.linear_combination_of_basis(v) - return C(v) + if self._derivation is not None: + v += D(list(map(self._derivation, x))) + return v def _repr_(self): r""" From 438c8c4967b966c9c11f399fd54ac68964d8d94c Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Thu, 26 Sep 2024 07:09:13 +0200 Subject: [PATCH 162/369] fix lint --- src/sage/modules/free_module.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/sage/modules/free_module.py b/src/sage/modules/free_module.py index bb8c8ca4484..70c6a25e05f 100644 --- a/src/sage/modules/free_module.py +++ b/src/sage/modules/free_module.py @@ -3226,7 +3226,6 @@ def pseudohom(self, f, twist, codomain=None, side="left"): parent = self.PseudoHom(twist, codomain) return FreeModulePseudoMorphism(parent, f, side) - def inner_product_matrix(self): """ Return the default identity inner product matrix associated to this From abe978d96c3115f0d8a3d9e3d0af5677bdcfef70 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Thu, 26 Sep 2024 15:55:38 +0200 Subject: [PATCH 163/369] PseudoHom -> pseudoHom --- src/sage/modules/free_module.py | 12 ++++---- .../modules/free_module_pseudohomspace.py | 28 +++++++++---------- .../modules/free_module_pseudomorphism.py | 6 ++-- 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/sage/modules/free_module.py b/src/sage/modules/free_module.py index 70c6a25e05f..b08b428b639 100644 --- a/src/sage/modules/free_module.py +++ b/src/sage/modules/free_module.py @@ -3110,7 +3110,7 @@ def hom(self, im_gens, codomain=None, **kwds): codomain = R**n return super().hom(im_gens, codomain, **kwds) - def PseudoHom(self, twist, codomain=None): + def pseudoHom(self, twist, codomain=None): r""" Return the Pseudo Hom space corresponding to given data. @@ -3126,17 +3126,17 @@ def PseudoHom(self, twist, codomain=None): sage: F = GF(25) sage: Frob = F.frobenius_endomorphism() sage: M = F^2 - sage: M.PseudoHom(Frob) + sage: M.pseudoHom(Frob) Set of Pseudoendomorphisms (twisted by z2 |--> z2^5) of Vector space of dimension 2 over Finite Field in z2 of size 5^2 .. SEEALSO:: :meth:`pseudohom` """ - from sage.modules.free_module_pseudohomspace import FreeModulePseudoHomspace + from sage.modules.free_module_pseudohomspace import FreeModulepseudoHomspace if codomain is None: codomain = self - return FreeModulePseudoHomspace(self, codomain, twist) + return FreeModulepseudoHomspace(self, codomain, twist) def pseudohom(self, f, twist, codomain=None, side="left"): r""" @@ -3220,10 +3220,10 @@ def pseudohom(self, f, twist, codomain=None, side="left"): .. SEEALSO:: - :meth:`PseudoHom` + :meth:`pseudoHom` """ from sage.modules.free_module_pseudomorphism import FreeModulePseudoMorphism - parent = self.PseudoHom(twist, codomain) + parent = self.pseudoHom(twist, codomain) return FreeModulePseudoMorphism(parent, f, side) def inner_product_matrix(self): diff --git a/src/sage/modules/free_module_pseudohomspace.py b/src/sage/modules/free_module_pseudohomspace.py index b78f7fb3c18..e4a30341c32 100644 --- a/src/sage/modules/free_module_pseudohomspace.py +++ b/src/sage/modules/free_module_pseudohomspace.py @@ -31,7 +31,7 @@ from sage.modules.free_module_pseudomorphism import FreeModulePseudoMorphism -class FreeModulePseudoHomspace(UniqueRepresentation, HomsetWithBase): +class FreeModulepseudoHomspace(UniqueRepresentation, HomsetWithBase): Element = FreeModulePseudoMorphism r""" @@ -45,7 +45,7 @@ class FreeModulePseudoHomspace(UniqueRepresentation, HomsetWithBase): sage: F = GF(125) sage: M = F^2 sage: Frob = F.frobenius_endomorphism() - sage: PHS = M.PseudoHom(Frob) + sage: PHS = M.pseudoHom(Frob) sage: h = PHS([[1, 2], [1, 1]]) sage: e = M((4*F.gen()^2 + F.gen() + 2, 4*F.gen()^2 + 4*F.gen() + 4)) sage: h(e) @@ -70,9 +70,9 @@ def __classcall_private__(cls, domain, codomain, twist): sage: F = GF(125) sage: Frob = F.frobenius_endomorphism() sage: M = F^2 - sage: H = M.PseudoHom(Frob) + sage: H = M.pseudoHom(Frob) sage: type(H) - + sage: # Testsuite(H).run() @@ -108,7 +108,7 @@ def __init__(self, domain, codomain, ore): sage: F = GF(125) sage: Frob = F.frobenius_endomorphism() sage: M = F^2 - sage: M.PseudoHom(Frob) + sage: M.pseudoHom(Frob) Set of Pseudoendomorphisms (twisted by z3 |--> z3^5) of Vector space of dimension 2 over Finite Field in z3 of size 5^3 """ @@ -134,7 +134,7 @@ def _element_constructor_(self, f, side="left"): sage: F. = GF(5^3) sage: Frob = F.frobenius_endomorphism() sage: V = F^2 - sage: H = V.PseudoHom(Frob) + sage: H = V.pseudoHom(Frob) sage: H([[1, z], [z, z^2]]) Free module pseudomorphism (twisted by z |--> z^5) defined by the matrix @@ -153,7 +153,7 @@ def __reduce__(self): sage: F = GF(125) sage: Frob = F.frobenius_endomorphism() sage: M = F^2 - sage: H = M.PseudoHom(Frob) + sage: H = M.pseudoHom(Frob) sage: loads(dumps(M)) is M True """ @@ -161,7 +161,7 @@ def __reduce__(self): twist = self._morphism else: twist = self._derivation - return FreeModulePseudoHomspace, (self.domain(), self.codomain(), twist) + return FreeModulepseudoHomspace, (self.domain(), self.codomain(), twist) def _repr_(self): r""" @@ -172,12 +172,12 @@ def _repr_(self): sage: Fq = GF(7^3) sage: Frob = Fq.frobenius_endomorphism() sage: V = Fq^2 - sage: V.PseudoHom(Frob) # indirect doctest + sage: V.pseudoHom(Frob) # indirect doctest Set of Pseudoendomorphisms (twisted by z3 |--> z3^7) of Vector space of dimension 2 over Finite Field in z3 of size 7^3 :: - sage: V.PseudoHom(Frob, codomain=Fq^3) # indirect doctest + sage: V.pseudoHom(Frob, codomain=Fq^3) # indirect doctest Set of Pseudomorphism (twisted by z3 |--> z3^7) from Vector space of dimension 2 over Finite Field in z3 of size 7^3 to Vector space of dimension 3 over Finite Field in z3 of size 7^3 :: @@ -185,7 +185,7 @@ def _repr_(self): sage: A. = QQ[] sage: d = A.derivation() sage: M = A^3 - sage: M.PseudoHom(d) + sage: M.pseudoHom(d) Set of Pseudoendomorphisms (twisted by d/dt) of Ambient free module of rank 3 over the principal ideal domain Univariate Polynomial Ring in t over Rational Field """ @@ -209,7 +209,7 @@ def ore_ring(self, var='x'): sage: Fq. = GF(7^3) sage: Frob = Fq.frobenius_endomorphism() sage: V = Fq^2 - sage: H = V.PseudoHom(Frob) + sage: H = V.pseudoHom(Frob) sage: H.ore_ring() Ore Polynomial Ring in x over Finite Field in z of size 7^3 twisted by z |--> z^7 @@ -231,7 +231,7 @@ def matrix_space(self): sage: Frob = Fq.frobenius_endomorphism() sage: V = Fq^2 sage: W = Fq^3 - sage: H = V.PseudoHom(Frob, codomain=W) + sage: H = V.pseudoHom(Frob, codomain=W) sage: H.matrix_space() Full MatrixSpace of 2 by 3 dense matrices over Finite Field in z of size 7^3 @@ -247,7 +247,7 @@ def basis(self, side="left"): sage: Fq = GF(7^3) sage: Frob = Fq.frobenius_endomorphism() sage: V = Fq^2 - sage: PHS = V.PseudoHom(Frob) + sage: PHS = V.pseudoHom(Frob) sage: PHS.basis() [Free module pseudomorphism (twisted by z3 |--> z3^7) defined by the matrix [1 0] diff --git a/src/sage/modules/free_module_pseudomorphism.py b/src/sage/modules/free_module_pseudomorphism.py index 619c162f9a7..60165dc455a 100644 --- a/src/sage/modules/free_module_pseudomorphism.py +++ b/src/sage/modules/free_module_pseudomorphism.py @@ -100,7 +100,7 @@ def __init__(self, parent, f, side): sage: F. = GF(5^3) sage: Frob = F.frobenius_endomorphism() sage: M = F^2 - sage: H = M.PseudoHom(Frob) + sage: H = M.pseudoHom(Frob) sage: H Set of Pseudoendomorphisms (twisted by z |--> z^5) of Vector space of dimension 2 over Finite Field in z of size 5^3 @@ -116,7 +116,7 @@ def __init__(self, parent, f, side): Codomain: Vector space of dimension 2 over Finite Field in z of size 5^3 sage: type(f) - + or a pseudomorphism with the same parent:: @@ -133,7 +133,7 @@ def __init__(self, parent, f, side): sage: id = End(F).identity() sage: g = M.hom(mat) - sage: M.PseudoHom(id)(g) + sage: M.pseudoHom(id)(g) Free module pseudomorphism (untwisted) defined by the matrix [ 1 z] [ z^2 2*z + 2] From 27a7e4d98b9f9a6cb76a5891372732188cc45926 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Fri, 27 Sep 2024 09:02:29 +0200 Subject: [PATCH 164/369] minor fixes --- src/sage/modules/free_module.py | 4 ++-- src/sage/modules/free_module_pseudohomspace.py | 8 ++++---- src/sage/modules/free_module_pseudomorphism.py | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/sage/modules/free_module.py b/src/sage/modules/free_module.py index b08b428b639..a2c0132291c 100644 --- a/src/sage/modules/free_module.py +++ b/src/sage/modules/free_module.py @@ -3133,10 +3133,10 @@ def pseudoHom(self, twist, codomain=None): :meth:`pseudohom` """ - from sage.modules.free_module_pseudohomspace import FreeModulepseudoHomspace + from sage.modules.free_module_pseudohomspace import FreeModulePseudoHomspace if codomain is None: codomain = self - return FreeModulepseudoHomspace(self, codomain, twist) + return FreeModulePseudoHomspace(self, codomain, twist) def pseudohom(self, f, twist, codomain=None, side="left"): r""" diff --git a/src/sage/modules/free_module_pseudohomspace.py b/src/sage/modules/free_module_pseudohomspace.py index e4a30341c32..11589d64f42 100644 --- a/src/sage/modules/free_module_pseudohomspace.py +++ b/src/sage/modules/free_module_pseudohomspace.py @@ -31,7 +31,7 @@ from sage.modules.free_module_pseudomorphism import FreeModulePseudoMorphism -class FreeModulepseudoHomspace(UniqueRepresentation, HomsetWithBase): +class FreeModulePseudoHomspace(UniqueRepresentation, HomsetWithBase): Element = FreeModulePseudoMorphism r""" @@ -72,7 +72,7 @@ def __classcall_private__(cls, domain, codomain, twist): sage: M = F^2 sage: H = M.pseudoHom(Frob) sage: type(H) - + sage: # Testsuite(H).run() @@ -86,7 +86,7 @@ def __classcall_private__(cls, domain, codomain, twist): raise ValueError("base rings do not match") else: ore = OrePolynomialRing(ring, twist, names='x', polcast=False) - if isinstance(ore, OrePolynomialRing) and ore._derivation is not None: + if ore._derivation is not None: if not codomain.has_coerce_map_from(domain): raise ValueError("the domain does not coerce into the codomain") return cls.__classcall__(cls, domain, codomain, ore) @@ -161,7 +161,7 @@ def __reduce__(self): twist = self._morphism else: twist = self._derivation - return FreeModulepseudoHomspace, (self.domain(), self.codomain(), twist) + return FreeModulePseudoHomspace, (self.domain(), self.codomain(), twist) def _repr_(self): r""" diff --git a/src/sage/modules/free_module_pseudomorphism.py b/src/sage/modules/free_module_pseudomorphism.py index 60165dc455a..30ccb047151 100644 --- a/src/sage/modules/free_module_pseudomorphism.py +++ b/src/sage/modules/free_module_pseudomorphism.py @@ -116,7 +116,7 @@ def __init__(self, parent, f, side): Codomain: Vector space of dimension 2 over Finite Field in z of size 5^3 sage: type(f) - + or a pseudomorphism with the same parent:: From e671a993f5d80ed62d6660c1a915c5e04d2d165c Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Sat, 28 Sep 2024 21:32:15 +0200 Subject: [PATCH 165/369] make iterating over monomial_coefficients more robust --- src/sage/data_structures/stream.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index 73bf5a9c026..d9aa93d4b17 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -2018,7 +2018,8 @@ def _collect_equations(self, offset): # TODO: it is a coincidence that `coefficients` # currently exists in all examples; # the monomials are only needed for the error messages - elt_coeffs = list(zip(elt.monomials(), elt.coefficients())) + elt_coeffs = [(self._coefficient_ring.monomial(idx), coeff) + for idx, coeff in elt.monomial_coefficients().items()] all_coeffs.append(elt_coeffs) for idx, coeff in elt_coeffs: From c5828caff8e7218747cec9519322d59956be51f9 Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Thu, 17 Oct 2024 23:59:42 +0200 Subject: [PATCH 166/369] Turn some doctests into long tests --- .../elliptic_curves/ell_rational_field.py | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/sage/schemes/elliptic_curves/ell_rational_field.py b/src/sage/schemes/elliptic_curves/ell_rational_field.py index 0bc71b09d42..8a935ad0c09 100755 --- a/src/sage/schemes/elliptic_curves/ell_rational_field.py +++ b/src/sage/schemes/elliptic_curves/ell_rational_field.py @@ -441,7 +441,7 @@ def mwrank(self, options=''): EXAMPLES:: sage: E = EllipticCurve('37a1') - sage: E.mwrank() #random + sage: E.mwrank() # random ... sage: print(E.mwrank()) Curve [0,0,1,-1,0] : Basic pair: I=48, J=-432 @@ -2316,7 +2316,7 @@ def gens(self, proof=None, **kwds): over Rational Field sage: E1.gens() # random (if database not used) [(-400 : 8000 : 1), (0 : -8000 : 1)] - sage: E1.gens(algorithm='pari') #random + sage: E1.gens(algorithm='pari') # random [(-400 : 8000 : 1), (0 : -8000 : 1)] TESTS:: @@ -2334,6 +2334,13 @@ def gens(self, proof=None, **kwds): sage: P = E.lift_x(10/9) sage: set(E.gens()) <= set([P,-P]) True + + Check that :issue:`38813` has been fixed: + + sage: set_random_seed(91390048253425197917505296851335255685) + sage: E = EllipticCurve([-127^2,0]) + sage: E.gens(use_database=False, algorithm='pari', pari_effort=4) # long time + [(611429153205013185025/9492121848205441 : 15118836457596902442737698070880/924793900700594415341761 : 1)] """ if proof is None: from sage.structure.proof.proof import get_flag @@ -2386,15 +2393,16 @@ def _compute_gens(self, proof, True sage: E = EllipticCurve([-127^2,0]) - sage: E.gens(use_database=False, algorithm='pari',pari_effort=4) # random + sage: E.gens(use_database=False, algorithm='pari', pari_effort=4) # long time, random [(611429153205013185025/9492121848205441 : 15118836457596902442737698070880/924793900700594415341761 : 1)] TESTS:: + sage: E = EllipticCurve([-127^2,0]) sage: P = E.lift_x(611429153205013185025/9492121848205441) - sage: ge = set(E.gens(use_database=False, algorithm='pari',pari_effort=4)) - sage: ge <= set([P+T for T in E.torsion_points()] - ....: + [-P+T for T in E.torsion_points()]) + sage: (set(E.gens(use_database=False, algorithm='pari', pari_effort=4)) # long time + ....: <= set([P+T for T in E.torsion_points()] + ....: + [-P+T for T in E.torsion_points()])) True """ # If the optional extended database is installed and an From 32103e45c6a354bc7f963e33363f843d7581d175 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Thu, 14 Nov 2024 10:39:30 +0100 Subject: [PATCH 167/369] format docstring --- src/sage/matrix/matrix_cdv.pyx | 16 +++++++++++----- src/sage/modules/free_module.py | 6 +++--- .../modules/free_module_pseudohomspace.py | 19 +++++-------------- .../modules/free_module_pseudomorphism.py | 12 +----------- .../rings/polynomial/ore_polynomial_ring.py | 1 - 5 files changed, 20 insertions(+), 34 deletions(-) diff --git a/src/sage/matrix/matrix_cdv.pyx b/src/sage/matrix/matrix_cdv.pyx index 238d875844f..749180bd85b 100644 --- a/src/sage/matrix/matrix_cdv.pyx +++ b/src/sage/matrix/matrix_cdv.pyx @@ -1,3 +1,4 @@ +# sage_setup: distribution = sagemath-modules r""" Special methods for matrices over discrete valuation rings/fields. """ @@ -36,7 +37,7 @@ cpdef hessenbergize_cdvf(Matrix_generic_dense H): TESTS:: sage: # needs sage.rings.padics - sage: K = Qp(5, print_mode='digits', prec=5) + sage: K = Qp(5, print_mode="digits", prec=5) sage: H = matrix(K, 3, 3, range(9)) sage: H [ 0 ...00001 ...00002] @@ -69,12 +70,14 @@ cpdef hessenbergize_cdvf(Matrix_generic_dense H): n = H.nrows() for j in range(n-1): k = j + 1 - maxi = H.get_unsafe(k, j).precision_relative() + #maxi = H.get_unsafe(k, j).precision_relative() + maxi = - H.get_unsafe(k, j).valuation() i = j + 2 while maxi is not Infinity and i < n: entry = H.get_unsafe(i, j) if entry: - m = entry.precision_relative() + #m = entry.precision_relative() + m = -entry.valuation() if m > maxi: maxi = m k = i @@ -84,9 +87,12 @@ cpdef hessenbergize_cdvf(Matrix_generic_dense H): H.swap_rows_c(j+1, k) H.swap_columns_c(j+1, k) pivot = H.get_unsafe(j+1, j) + # print(pivot) if pivot: - inv = ~pivot + inv = (~pivot).lift_to_precision() for i in range(j+2, n): scalar = inv * H.get_unsafe(i, j) - H.add_multiple_of_row_c(i, j+1, -scalar, j) + scalar = scalar.lift_to_precision() + # print(" ", scalar) + H.add_multiple_of_row_c(i, j+1, -scalar, 0) H.add_multiple_of_column_c(j+1, i, scalar, 0) diff --git a/src/sage/modules/free_module.py b/src/sage/modules/free_module.py index a2c0132291c..c42649bb613 100644 --- a/src/sage/modules/free_module.py +++ b/src/sage/modules/free_module.py @@ -3118,7 +3118,7 @@ def pseudoHom(self, twist, codomain=None): - ``twist`` -- the twisting morphism or the twisting derivation - - ``codomain`` (default: ``None``) -- the codomain of the pseudo + - ``codomain`` -- (default: ``None``) the codomain of the pseudo morphisms; if ``None``, the codomain is the same than the domain EXAMPLES:: @@ -3161,10 +3161,10 @@ def pseudohom(self, f, twist, codomain=None, side="left"): - ``twist`` -- the twisting morphism or the twisting derivation - - ``codomain`` (default: ``None``) -- the codomain of the pseudo + - ``codomain`` -- (default: ``None``) the codomain of the pseudo morphisms; if ``None``, the codomain is the same than the domain - - ``side`` (default: ``left``) -- side of the vectors acted on by + - ``side`` -- (default: ``left``) side of the vectors acted on by the matrix EXAMPLES:: diff --git a/src/sage/modules/free_module_pseudohomspace.py b/src/sage/modules/free_module_pseudohomspace.py index 11589d64f42..fc6705f8f0b 100644 --- a/src/sage/modules/free_module_pseudohomspace.py +++ b/src/sage/modules/free_module_pseudohomspace.py @@ -1,10 +1,9 @@ """ -Space of Pseudomorphisms of free modules +Space of pseudomorphisms of free modules AUTHORS: - - Xavier Caruso, Yossef Musleh (2024-09): initial version - +- Xavier Caruso, Yossef Musleh (2024-09): initial version """ # **************************************************************************** # Copyright (C) 2024 Xavier Caruso @@ -32,8 +31,6 @@ class FreeModulePseudoHomspace(UniqueRepresentation, HomsetWithBase): - Element = FreeModulePseudoMorphism - r""" This class implements the space of Pseudomorphisms with a fixed twist. @@ -50,8 +47,9 @@ class FreeModulePseudoHomspace(UniqueRepresentation, HomsetWithBase): sage: e = M((4*F.gen()^2 + F.gen() + 2, 4*F.gen()^2 + 4*F.gen() + 4)) sage: h(e) (z3, 2*z3^2 + 3*z3 + 3) - """ + Element = FreeModulePseudoMorphism + @staticmethod def __classcall_private__(cls, domain, codomain, twist): r""" @@ -75,7 +73,6 @@ def __classcall_private__(cls, domain, codomain, twist): sage: # Testsuite(H).run() - """ ring = domain.base_ring() if codomain.base_ring() is not ring: @@ -110,7 +107,6 @@ def __init__(self, domain, codomain, ore): sage: M = F^2 sage: M.pseudoHom(Frob) Set of Pseudoendomorphisms (twisted by z3 |--> z3^5) of Vector space of dimension 2 over Finite Field in z3 of size 5^3 - """ self._domain = domain self._codomain = codomain @@ -142,7 +138,6 @@ def _element_constructor_(self, f, side="left"): [ z z^2] Domain: Vector space of dimension 2 over Finite Field in z of size 5^3 Codomain: Vector space of dimension 2 over Finite Field in z of size 5^3 - """ return self.element_class(self, f, side) @@ -187,7 +182,6 @@ def _repr_(self): sage: M = A^3 sage: M.pseudoHom(d) Set of Pseudoendomorphisms (twisted by d/dt) of Ambient free module of rank 3 over the principal ideal domain Univariate Polynomial Ring in t over Rational Field - """ twist = self._ore._repr_twist() if self.domain() is self.codomain(): @@ -201,7 +195,7 @@ def ore_ring(self, var='x'): INPUT: - - ``var`` (default: ``x``) -- a string, the name of + - ``var`` -- string (default: ``x``) the name of tha variable EXAMPLES:: @@ -216,7 +210,6 @@ def ore_ring(self, var='x'): sage: H.ore_ring('y') Ore Polynomial Ring in y over Finite Field in z of size 7^3 twisted by z |--> z^7 - """ return self._ore.change_var(var) @@ -234,7 +227,6 @@ def matrix_space(self): sage: H = V.pseudoHom(Frob, codomain=W) sage: H.matrix_space() Full MatrixSpace of 2 by 3 dense matrices over Finite Field in z of size 7^3 - """ return self._matrix_space @@ -266,6 +258,5 @@ def basis(self, side="left"): [0 1] Domain: Vector space of dimension 2 over Finite Field in z3 of size 7^3 Codomain: Vector space of dimension 2 over Finite Field in z3 of size 7^3] - """ return Sequence(self(mat) for mat in self._matrix_space.basis()) diff --git a/src/sage/modules/free_module_pseudomorphism.py b/src/sage/modules/free_module_pseudomorphism.py index 30ccb047151..a830f422eb2 100644 --- a/src/sage/modules/free_module_pseudomorphism.py +++ b/src/sage/modules/free_module_pseudomorphism.py @@ -3,8 +3,7 @@ AUTHORS: - - Xavier Caruso, Yossef Musleh (2024-09): initial version - +- Xavier Caruso, Yossef Musleh (2024-09): initial version """ # **************************************************************************** # Copyright (C) 2024 Xavier Caruso @@ -80,7 +79,6 @@ class FreeModulePseudoMorphism(Morphism): sage: v = (4*z^2 + 4*z + 3, 2, z + 5) sage: phi(v) (2*z + 1, 6*z^2 + 4*z + 5) - """ def __init__(self, parent, f, side): """ @@ -155,7 +153,6 @@ def __init__(self, parent, f, side): Traceback (most recent call last): ... ValueError: the side must be either 'left' or 'right' - """ Morphism.__init__(self, parent) dom = parent.domain() @@ -199,7 +196,6 @@ def _call_(self, x): sage: g = M.pseudohom([[1, z, 3], [0, 1, 1], [2, 1, 1]], Frob, side="right") sage: g(e) (z^2 + 6*z + 2, z^2 + 2*z + 1, 2*z^2 + 4*z) - """ D = self.domain() C = self.codomain() @@ -289,7 +285,6 @@ def matrix(self): sage: v = M.random_element() sage: f(v) == vector([Frob(c) for c in v]) * f.matrix() True - """ if self._side == "left": return self._matrix.__copy__() @@ -317,7 +312,6 @@ def twisting_derivation(self): sage: V = Fq^2 sage: f = V.pseudohom([[1, z], [0, z^2]], Frob) sage: f.twisting_derivation() - """ return self._derivation @@ -342,7 +336,6 @@ def twisting_morphism(self): sage: M = P^2 sage: f = M.pseudohom([[1, 2*x], [x, 1]], d) sage: f.twisting_morphism() - """ return self._morphism @@ -369,7 +362,6 @@ def side(self): sage: h2([1, 0]) (1, z^2) """ - return self._side def side_switch(self): @@ -404,7 +396,6 @@ def side_switch(self): sage: v = V.random_element() sage: h1(v) == h2(v) True - """ if self._side == "left": side = "right" @@ -442,7 +433,6 @@ def __eq__(self, other): sage: h = V.hom(m) sage: g == h True - """ if isinstance(other, FreeModuleMorphism): try: diff --git a/src/sage/rings/polynomial/ore_polynomial_ring.py b/src/sage/rings/polynomial/ore_polynomial_ring.py index 1c36401db3c..8e4a331b911 100644 --- a/src/sage/rings/polynomial/ore_polynomial_ring.py +++ b/src/sage/rings/polynomial/ore_polynomial_ring.py @@ -614,7 +614,6 @@ def _repr_twist(self): sage: T. = OrePolynomialRing(F, Frob^3, polcast=False) sage: T._repr_twist() 'untwisted' - """ s = "" if self._morphism is not None: From aa0b7135b5e25e1d547e31e274f424d755c0bf3a Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Thu, 14 Nov 2024 15:29:49 +0100 Subject: [PATCH 168/369] fix doctest --- src/sage/matrix/matrix_cdv.pyx | 16 +++++----------- src/sage/modules/free_module.py | 8 ++++---- 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/src/sage/matrix/matrix_cdv.pyx b/src/sage/matrix/matrix_cdv.pyx index 749180bd85b..238d875844f 100644 --- a/src/sage/matrix/matrix_cdv.pyx +++ b/src/sage/matrix/matrix_cdv.pyx @@ -1,4 +1,3 @@ -# sage_setup: distribution = sagemath-modules r""" Special methods for matrices over discrete valuation rings/fields. """ @@ -37,7 +36,7 @@ cpdef hessenbergize_cdvf(Matrix_generic_dense H): TESTS:: sage: # needs sage.rings.padics - sage: K = Qp(5, print_mode="digits", prec=5) + sage: K = Qp(5, print_mode='digits', prec=5) sage: H = matrix(K, 3, 3, range(9)) sage: H [ 0 ...00001 ...00002] @@ -70,14 +69,12 @@ cpdef hessenbergize_cdvf(Matrix_generic_dense H): n = H.nrows() for j in range(n-1): k = j + 1 - #maxi = H.get_unsafe(k, j).precision_relative() - maxi = - H.get_unsafe(k, j).valuation() + maxi = H.get_unsafe(k, j).precision_relative() i = j + 2 while maxi is not Infinity and i < n: entry = H.get_unsafe(i, j) if entry: - #m = entry.precision_relative() - m = -entry.valuation() + m = entry.precision_relative() if m > maxi: maxi = m k = i @@ -87,12 +84,9 @@ cpdef hessenbergize_cdvf(Matrix_generic_dense H): H.swap_rows_c(j+1, k) H.swap_columns_c(j+1, k) pivot = H.get_unsafe(j+1, j) - # print(pivot) if pivot: - inv = (~pivot).lift_to_precision() + inv = ~pivot for i in range(j+2, n): scalar = inv * H.get_unsafe(i, j) - scalar = scalar.lift_to_precision() - # print(" ", scalar) - H.add_multiple_of_row_c(i, j+1, -scalar, 0) + H.add_multiple_of_row_c(i, j+1, -scalar, j) H.add_multiple_of_column_c(j+1, i, scalar, 0) diff --git a/src/sage/modules/free_module.py b/src/sage/modules/free_module.py index c42649bb613..214a02eaa73 100644 --- a/src/sage/modules/free_module.py +++ b/src/sage/modules/free_module.py @@ -3144,11 +3144,11 @@ def pseudohom(self, f, twist, codomain=None, side="left"): We recall that, given two `R`-modules `M` and `M'` together with a ring homomorphism `\theta: R \to R` and a `\theta`-derivation, - `\delta: R \to R` (that is, a map such that: + `\delta: R \to R` (that is, a map such that `\delta(xy) = \theta(x)\delta(y) + \delta(x)y`), a - pseudomorphism `f : M \to M'` is a additive map such that + pseudomorphism `f : M \to M'` is an additive map such that - .. MATH: + .. MATH:: f(\lambda x) = \theta(\lambda) f(x) + \delta(\lambda) x @@ -3210,7 +3210,7 @@ def pseudohom(self, f, twist, codomain=None, side="left"): True If the twisting derivation is not zero, the domain must - coerce into the codomain + coerce into the codomain:: sage: N = R^3 sage: M.pseudohom([[1, t, t^2], [1, t^2, t^4]], d, codomain=N) From 2305dd1953ccfc00a4b3a2976f651c7199656503 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Fri, 15 Nov 2024 08:00:43 +0100 Subject: [PATCH 169/369] formatting --- .../modules/free_module_pseudohomspace.py | 22 +++++++++++++------ .../modules/free_module_pseudomorphism.py | 10 +++++---- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/sage/modules/free_module_pseudohomspace.py b/src/sage/modules/free_module_pseudohomspace.py index fc6705f8f0b..2d1df9c99d8 100644 --- a/src/sage/modules/free_module_pseudohomspace.py +++ b/src/sage/modules/free_module_pseudohomspace.py @@ -106,7 +106,8 @@ def __init__(self, domain, codomain, ore): sage: Frob = F.frobenius_endomorphism() sage: M = F^2 sage: M.pseudoHom(Frob) - Set of Pseudoendomorphisms (twisted by z3 |--> z3^5) of Vector space of dimension 2 over Finite Field in z3 of size 5^3 + Set of Pseudoendomorphisms (twisted by z3 |--> z3^5) of + Vector space of dimension 2 over Finite Field in z3 of size 5^3 """ self._domain = domain self._codomain = codomain @@ -168,12 +169,15 @@ def _repr_(self): sage: Frob = Fq.frobenius_endomorphism() sage: V = Fq^2 sage: V.pseudoHom(Frob) # indirect doctest - Set of Pseudoendomorphisms (twisted by z3 |--> z3^7) of Vector space of dimension 2 over Finite Field in z3 of size 7^3 + Set of Pseudoendomorphisms (twisted by z3 |--> z3^7) of + Vector space of dimension 2 over Finite Field in z3 of size 7^3 :: sage: V.pseudoHom(Frob, codomain=Fq^3) # indirect doctest - Set of Pseudomorphism (twisted by z3 |--> z3^7) from Vector space of dimension 2 over Finite Field in z3 of size 7^3 to Vector space of dimension 3 over Finite Field in z3 of size 7^3 + Set of Pseudomorphism (twisted by z3 |--> z3^7) + from Vector space of dimension 2 over Finite Field in z3 of size 7^3 + to Vector space of dimension 3 over Finite Field in z3 of size 7^3 :: @@ -181,7 +185,8 @@ def _repr_(self): sage: d = A.derivation() sage: M = A^3 sage: M.pseudoHom(d) - Set of Pseudoendomorphisms (twisted by d/dt) of Ambient free module of rank 3 over the principal ideal domain Univariate Polynomial Ring in t over Rational Field + Set of Pseudoendomorphisms (twisted by d/dt) of Ambient free module of rank 3 over + the principal ideal domain Univariate Polynomial Ring in t over Rational Field """ twist = self._ore._repr_twist() if self.domain() is self.codomain(): @@ -245,15 +250,18 @@ def basis(self, side="left"): [1 0] [0 0] Domain: Vector space of dimension 2 over Finite Field in z3 of size 7^3 - Codomain: Vector space of dimension 2 over Finite Field in z3 of size 7^3, Free module pseudomorphism (twisted by z3 |--> z3^7) defined by the matrix + Codomain: Vector space of dimension 2 over Finite Field in z3 of size 7^3, + Free module pseudomorphism (twisted by z3 |--> z3^7) defined by the matrix [0 1] [0 0] Domain: Vector space of dimension 2 over Finite Field in z3 of size 7^3 - Codomain: Vector space of dimension 2 over Finite Field in z3 of size 7^3, Free module pseudomorphism (twisted by z3 |--> z3^7) defined by the matrix + Codomain: Vector space of dimension 2 over Finite Field in z3 of size 7^3, + Free module pseudomorphism (twisted by z3 |--> z3^7) defined by the matrix [0 0] [1 0] Domain: Vector space of dimension 2 over Finite Field in z3 of size 7^3 - Codomain: Vector space of dimension 2 over Finite Field in z3 of size 7^3, Free module pseudomorphism (twisted by z3 |--> z3^7) defined by the matrix + Codomain: Vector space of dimension 2 over Finite Field in z3 of size 7^3, + Free module pseudomorphism (twisted by z3 |--> z3^7) defined by the matrix [0 0] [0 1] Domain: Vector space of dimension 2 over Finite Field in z3 of size 7^3 diff --git a/src/sage/modules/free_module_pseudomorphism.py b/src/sage/modules/free_module_pseudomorphism.py index a830f422eb2..72b9f6de3b3 100644 --- a/src/sage/modules/free_module_pseudomorphism.py +++ b/src/sage/modules/free_module_pseudomorphism.py @@ -19,7 +19,7 @@ # The full text of the GPL is available at: # # http://www.gnu.org/licenses/ -#################################################################################### +# **************************************************************************** from sage.categories.morphism import Morphism from sage.structure.richcmp import rich_to_bool, richcmp @@ -32,13 +32,13 @@ class FreeModulePseudoMorphism(Morphism): ring homomorphism, and `\delta: R \to R` a `\theta`-derivation, which is a map such that: - .. MATH: + .. MATH:: \delta(xy) = \theta(x)\delta(y) + \delta(x)y. A pseudomorphism `f : M \to M` is an additive map such that - .. MATH: + .. MATH:: f(\lambda x) = \theta(\lambda)f(x) + \delta(\lambda) x @@ -258,7 +258,7 @@ def matrix(self): Return the underlying matrix of this pseudomorphism. It is defined as the matrix `M` whose lines (resp. columns if - ``side`` is ``right``) are the coordinates of the images of + ``side`` is ``"right"``) are the coordinates of the images of the distinguished basis of the domain. EXAMPLES:: @@ -272,6 +272,8 @@ def matrix(self): [ 0 1 z^2] [z + 1 1 1] + :: + sage: e1, e2, e3 = M.basis() sage: f(e1) (1, z, 3) From 4d3be5572023df5caef87e3f1ea529bf8b83072d Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Sun, 17 Nov 2024 09:53:03 +0100 Subject: [PATCH 170/369] TestSuite --- src/sage/modules/free_module.py | 7 +- .../modules/free_module_pseudohomspace.py | 70 ++++++++++++++++++- .../modules/free_module_pseudomorphism.py | 18 ++++- .../rings/polynomial/ore_polynomial_ring.py | 4 +- 4 files changed, 91 insertions(+), 8 deletions(-) diff --git a/src/sage/modules/free_module.py b/src/sage/modules/free_module.py index 214a02eaa73..ba9e951854e 100644 --- a/src/sage/modules/free_module.py +++ b/src/sage/modules/free_module.py @@ -3112,7 +3112,7 @@ def hom(self, im_gens, codomain=None, **kwds): def pseudoHom(self, twist, codomain=None): r""" - Return the Pseudo Hom space corresponding to given data. + Return the pseudo-Hom space corresponding to given data. INPUT: @@ -3222,9 +3222,8 @@ def pseudohom(self, f, twist, codomain=None, side="left"): :meth:`pseudoHom` """ - from sage.modules.free_module_pseudomorphism import FreeModulePseudoMorphism - parent = self.pseudoHom(twist, codomain) - return FreeModulePseudoMorphism(parent, f, side) + H = self.pseudoHom(twist, codomain) + return H(f, side) def inner_product_matrix(self): """ diff --git a/src/sage/modules/free_module_pseudohomspace.py b/src/sage/modules/free_module_pseudohomspace.py index 2d1df9c99d8..2df8be4b3a2 100644 --- a/src/sage/modules/free_module_pseudohomspace.py +++ b/src/sage/modules/free_module_pseudohomspace.py @@ -72,7 +72,7 @@ def __classcall_private__(cls, domain, codomain, twist): sage: type(H) - sage: # Testsuite(H).run() + sage: TestSuite(H).run() """ ring = domain.base_ring() if codomain.base_ring() is not ring: @@ -268,3 +268,71 @@ def basis(self, side="left"): Codomain: Vector space of dimension 2 over Finite Field in z3 of size 7^3] """ return Sequence(self(mat) for mat in self._matrix_space.basis()) + + def _test_additive_associativity(self, tester): + r""" + Test associativity for (not necessarily all) elements in this parent. + + This test is not relevant for pseudo-morphisms because they are not + stable by addition. + + TESTS:: + + sage: Fq = GF(7^3) + sage: Frob = Fq.frobenius_endomorphism() + sage: V = Fq^2 + sage: PHS = V.pseudoHom(Frob) + sage: TestSuite(PHS).run() # indirect doctest + """ + pass + + def _test_distributivity(self, tester): + r""" + Test distributivity for (not necessarily all) elements in this parent. + + This test is not relevant for pseudo-morphisms because they are not + stable by addition. + + TESTS:: + + sage: Fq = GF(7^3) + sage: Frob = Fq.frobenius_endomorphism() + sage: V = Fq^2 + sage: PHS = V.pseudoHom(Frob) + sage: TestSuite(PHS).run() # indirect doctest + """ + pass + + def _test_one(self, tester): + r""" + Test properties the identity element. + + This test is not relevant for pseudo-morphisms because the identity + is not a pseudo-morphism in general. + + TESTS:: + + sage: Fq = GF(7^3) + sage: Frob = Fq.frobenius_endomorphism() + sage: V = Fq^2 + sage: PHS = V.pseudoHom(Frob) + sage: TestSuite(PHS).run() # indirect doctest + """ + pass + + def _test_zero(self, tester): + r""" + Test properties of the zero element. + + This test is not relevant for pseudo-morphisms because the zero + map is not a pseudo-morphism in general. + + TESTS:: + + sage: Fq = GF(7^3) + sage: Frob = Fq.frobenius_endomorphism() + sage: V = Fq^2 + sage: PHS = V.pseudoHom(Frob) + sage: TestSuite(PHS).run() # indirect doctest + """ + pass diff --git a/src/sage/modules/free_module_pseudomorphism.py b/src/sage/modules/free_module_pseudomorphism.py index 72b9f6de3b3..965152a893e 100644 --- a/src/sage/modules/free_module_pseudomorphism.py +++ b/src/sage/modules/free_module_pseudomorphism.py @@ -131,7 +131,8 @@ def __init__(self, parent, f, side): sage: id = End(F).identity() sage: g = M.hom(mat) - sage: M.pseudoHom(id)(g) + sage: g2 = M.pseudoHom(id)(g) + sage: g2 Free module pseudomorphism (untwisted) defined by the matrix [ 1 z] [ z^2 2*z + 2] @@ -140,7 +141,8 @@ def __init__(self, parent, f, side): An example with ``side=right``:: - sage: M.pseudohom(mat, Frob, side="right") + sage: h = M.pseudohom(mat, Frob, side="right") + sage: h Free module pseudomorphism (twisted by z |--> z^5) defined as left-multiplication by the matrix [ 1 z] [ z^2 2*z + 2] @@ -153,6 +155,12 @@ def __init__(self, parent, f, side): Traceback (most recent call last): ... ValueError: the side must be either 'left' or 'right' + + :: + + sage: TestSuite(f).run() + sage: TestSuite(g2).run() + sage: TestSuite(h).run() """ Morphism.__init__(self, parent) dom = parent.domain() @@ -407,6 +415,9 @@ def side_switch(self): mat = self._matrix return self.parent()(mat, side) + def __nonzero__(self): + return not (self._derivation is None and self._matrix) + def __eq__(self, other): r""" Compare this morphism with ``other``. @@ -443,3 +454,6 @@ def __eq__(self, other): return False if isinstance(other, FreeModulePseudoMorphism): return self.parent() is other.parent() and self._matrix == other._matrix + + def _test_nonzero_equal(self, tester): + pass diff --git a/src/sage/rings/polynomial/ore_polynomial_ring.py b/src/sage/rings/polynomial/ore_polynomial_ring.py index 8e4a331b911..073a3fc9292 100644 --- a/src/sage/rings/polynomial/ore_polynomial_ring.py +++ b/src/sage/rings/polynomial/ore_polynomial_ring.py @@ -321,7 +321,9 @@ def __classcall_private__(cls, base_ring, twist=None, names=None, sparse=False, """ if base_ring not in CommutativeRings(): raise TypeError('base_ring must be a commutative ring') - if isinstance(twist, Morphism): + if twist is None: + morphism = derivation = None + elif isinstance(twist, Morphism): if (twist.domain() is not base_ring or twist.codomain() is not base_ring): raise TypeError("the twisting morphism must be an endomorphism of base_ring (=%s)" % base_ring) From 65ab9e49aafd357c494dc0e07996beccdc77bff3 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Sun, 17 Nov 2024 09:57:12 +0100 Subject: [PATCH 171/369] remove method `quotient` --- src/sage/rings/polynomial/ore_polynomial_ring.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/sage/rings/polynomial/ore_polynomial_ring.py b/src/sage/rings/polynomial/ore_polynomial_ring.py index 073a3fc9292..adf6edc13d4 100644 --- a/src/sage/rings/polynomial/ore_polynomial_ring.py +++ b/src/sage/rings/polynomial/ore_polynomial_ring.py @@ -1228,9 +1228,6 @@ def fraction_field(self): self._fraction_field.register_coercion(self) return self._fraction_field - def quotient(self, P, names=None): - return self(P).quotient_module(names=names) - def _pushout_(self, other): r""" Return the pushout of this Ore polynomial ring and ``other``. From 7b561584639198874b8c48f663046a753ebf07c3 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Mon, 18 Nov 2024 00:23:31 +0700 Subject: [PATCH 172/369] Allow CRT_list() to be called with one element --- src/sage/arith/misc.py | 45 ++++++++++++++++++++++++++++++++---------- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/src/sage/arith/misc.py b/src/sage/arith/misc.py index 745d5fcbbe7..05a6792f7de 100644 --- a/src/sage/arith/misc.py +++ b/src/sage/arith/misc.py @@ -1841,7 +1841,6 @@ def gcd(a, b=None, **kwargs): except TypeError: return m(py_scalar_to_element(b), **kwargs) - from sage.structure.sequence import Sequence seq = Sequence(py_scalar_to_element(el) for el in a) if seq.universe() is ZZ: return GCD_list(seq) @@ -3498,11 +3497,15 @@ def crt(a, b, m=None, n=None): CRT = crt -def CRT_list(values, moduli): +def CRT_list(values, moduli=None): r""" Given a list ``values`` of elements and a list of corresponding ``moduli``, find a single element that reduces to each element of ``values`` modulo the corresponding moduli. + This function can also be called with one argument, each element + of the list is a :class:`IntegerMod_abstract` object. In this case, it returns + another :class:`IntegerMod_abstract` object. + .. SEEALSO:: - :func:`crt` @@ -3529,6 +3532,13 @@ def CRT_list(values, moduli): ... ValueError: no solution to crt problem since gcd(180,150) does not divide 92-1 + Call with one argument:: + + sage: x = CRT_list([mod(2,3),mod(3,5),mod(2,7)]); x + 23 + sage: x.parent() + Ring of integers modulo 105 + The arguments must be lists:: sage: CRT_list([1,2,3],"not a list") @@ -3575,14 +3585,26 @@ def CRT_list(values, moduli): sage: ms [5, 7, 9] """ - if not isinstance(values, list) or not isinstance(moduli, list): + if not isinstance(values, list) or not isinstance(moduli, (list, type(None))): raise ValueError("arguments to CRT_list should be lists") - if len(values) != len(moduli): - raise ValueError("arguments to CRT_list should be lists of the same length") - if not values: - return ZZ.zero() - if len(values) == 1: - return moduli[0].parent()(values[0]) + return_mod = moduli is None + if return_mod: + from sage.rings.finite_rings.integer_mod import IntegerMod_abstract, Mod + if not values: + return Mod(0, 1) + if not all(isinstance(v, IntegerMod_abstract) for v in values): + raise TypeError("arguments to CRT_list should be lists of IntegerMod") + if len(values) == 1: + return values[0] + moduli = [v.modulus() for v in values] + values = [v.lift() for v in values] + else: + if len(values) != len(moduli): + raise ValueError("arguments to CRT_list should be lists of the same length") + if not values: + return ZZ.zero() + if len(values) == 1: + return moduli[0].parent()(values[0]) # The result is computed using a binary tree. In typical cases, # this scales much better than folding the list from one side. @@ -3593,7 +3615,10 @@ def CRT_list(values, moduli): vs[i] = CRT(vs[i], v, ms[i], m) ms[i] = lcm(ms[i], m) values, moduli = vs, ms - return values[0] % moduli[0] + if return_mod: + return Mod(values[0], moduli[0]) + else: + return values[0] % moduli[0] def CRT_basis(moduli): From 1962918af8a45f63581f1876593f34fa58d319ac Mon Sep 17 00:00:00 2001 From: mklss <59539887+mklss@users.noreply.github.com> Date: Sat, 23 Nov 2024 19:38:56 +0100 Subject: [PATCH 173/369] Add reseed_rng option to p_iter_fork --- src/sage/parallel/decorate.py | 11 +++++++++++ src/sage/parallel/use_fork.py | 29 ++++++++++++++++++++++------- 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/src/sage/parallel/decorate.py b/src/sage/parallel/decorate.py index 90ee3a5cc42..86e455fd955 100644 --- a/src/sage/parallel/decorate.py +++ b/src/sage/parallel/decorate.py @@ -308,6 +308,7 @@ def parallel(p_iter='fork', ncpus=None, **kwds): - ``ncpus`` -- integer; maximal number of subprocesses to use at the same time - ``timeout`` -- number of seconds until each subprocess is killed (only supported by ``'fork'``; zero means not at all) + - ``reseed_rng``: reseed the rng in each subprocess .. warning:: @@ -398,6 +399,16 @@ def parallel(p_iter='fork', ncpus=None, **kwds): sage: Foo.square_classmethod(3) 9 + + By default, all subprocesses use the same random seed and therefore the same deterministic randomness. + For functions that should be randomized, we can reseed the random seed in each subprocess:: + + sage: @parallel(reseed_rng = True) + ....: def unif(n): return ZZ.random_element(x = 0, y = n) + sage: set_random_seed(42) + sage: sorted(unif([1000]*3)) + [(((1000,), {}), 444), (((1000,), {}), 597), (((1000,), {}), 640)] + .. warning:: Currently, parallel methods do not work with the diff --git a/src/sage/parallel/use_fork.py b/src/sage/parallel/use_fork.py index 36269127b80..c7f71b49f58 100644 --- a/src/sage/parallel/use_fork.py +++ b/src/sage/parallel/use_fork.py @@ -19,6 +19,8 @@ from sage.interfaces.process import ContainChildren from sage.misc.timing import walltime +from sage.misc.randstate import set_random_seed +from sage.misc.prandom import getrandbits class WorkerData: """ @@ -68,6 +70,7 @@ class p_iter_fork: about what the iterator does (e.g., killing subprocesses) - ``reset_interfaces`` -- boolean (default: ``True``); whether to reset all pexpect interfaces + - ``reseed_rng`` -- booolean (default: ``False``); whether or not to reseed the rng in the subprocesses EXAMPLES:: @@ -80,7 +83,7 @@ class p_iter_fork: sage: X.verbose False """ - def __init__(self, ncpus, timeout=0, verbose=False, reset_interfaces=True): + def __init__(self, ncpus, timeout=0, verbose=False, reset_interfaces=True, reseed_rng=False): """ Create a ``fork()``-based parallel iterator. @@ -103,6 +106,8 @@ def __init__(self, ncpus, timeout=0, verbose=False, reset_interfaces=True): self.timeout = float(timeout) # require a float self.verbose = verbose self.reset_interfaces = reset_interfaces + self.reseed_rng = reseed_rng + self.worker_seed = None def __call__(self, f, inputs): """ @@ -148,8 +153,6 @@ def __call__(self, f, inputs): sage: list(Polygen([QQ,QQ])) [(((Rational Field,), {}), x), (((Rational Field,), {}), x)] """ - n = self.ncpus - v = list(inputs) import os import sys import signal @@ -158,12 +161,19 @@ def __call__(self, f, inputs): dir = tmp_dir() timeout = self.timeout + n = self.ncpus + inputs = list(inputs) + if self.reseed_rng: + seeds = [getrandbits(512) for _ in range(0, len(inputs))] + vs = list(zip(inputs, seeds)) + else: + vs = list(zip(inputs, [None]*len(inputs))) workers = {} try: - while v or workers: + while vs or workers: # Spawn up to n subprocesses - while v and len(workers) < n: - v0 = v.pop(0) # Input value for the next subprocess + while vs and len(workers) < n: + (v0, seed0) = vs.pop(0) # Input value and seed for the next subprocess with ContainChildren(): pid = os.fork() # The way fork works is that pid returns the @@ -171,8 +181,8 @@ def __call__(self, f, inputs): # process and returns 0 for the subprocess. if not pid: # This is the subprocess. + self.worker_seed = seed0 if self.reseed_rng else None self._subprocess(f, dir, *v0) - workers[pid] = WorkerData(v0) if len(workers) > 0: @@ -304,6 +314,11 @@ def _subprocess(self, f, dir, args, kwds={}): else: invalidate_all() + # Reseed rng, if requested. + if self.reseed_rng == True: + set_random_seed(self.worker_seed) + + # Now evaluate the function f. value = f(*args, **kwds) From 702cebf6e491c7e8bd6092329299623ab06b0c01 Mon Sep 17 00:00:00 2001 From: mklss <59539887+mklss@users.noreply.github.com> Date: Sun, 24 Nov 2024 17:20:42 +0100 Subject: [PATCH 174/369] Appease linter. --- src/sage/parallel/decorate.py | 1 - src/sage/parallel/use_fork.py | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/sage/parallel/decorate.py b/src/sage/parallel/decorate.py index 86e455fd955..fc38410b7bb 100644 --- a/src/sage/parallel/decorate.py +++ b/src/sage/parallel/decorate.py @@ -399,7 +399,6 @@ def parallel(p_iter='fork', ncpus=None, **kwds): sage: Foo.square_classmethod(3) 9 - By default, all subprocesses use the same random seed and therefore the same deterministic randomness. For functions that should be randomized, we can reseed the random seed in each subprocess:: diff --git a/src/sage/parallel/use_fork.py b/src/sage/parallel/use_fork.py index c7f71b49f58..89aa1c42146 100644 --- a/src/sage/parallel/use_fork.py +++ b/src/sage/parallel/use_fork.py @@ -315,10 +315,9 @@ def _subprocess(self, f, dir, args, kwds={}): invalidate_all() # Reseed rng, if requested. - if self.reseed_rng == True: + if self.reseed_rng: set_random_seed(self.worker_seed) - # Now evaluate the function f. value = f(*args, **kwds) From beadc1699b2b363ddb78d1287b6d992b2d0a7c92 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Fri, 29 Nov 2024 19:31:09 +0700 Subject: [PATCH 175/369] More coverage --- src/sage/arith/misc.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/sage/arith/misc.py b/src/sage/arith/misc.py index 408d5c4cfa8..21c30c926d0 100644 --- a/src/sage/arith/misc.py +++ b/src/sage/arith/misc.py @@ -3574,6 +3574,21 @@ def CRT_list(values, moduli=None): sage: CRT_list([mpz(2),mpz(3),mpz(2)], [mpz(3),mpz(5),mpz(7)]) 23 + Tests for call with one argument:: + + sage: x = CRT_list([mod(2,3)]); x + 2 + sage: x.parent() + Ring of integers modulo 3 + sage: x = CRT_list([]); x + 0 + sage: x.parent() + Ring of integers modulo 1 + sage: x = CRT_list([2]); x + Traceback (most recent call last): + ... + TypeError: if one argument is given, it should be a list of IntegerMod + Make sure we are not mutating the input lists:: sage: xs = [1,2,3] @@ -3593,7 +3608,7 @@ def CRT_list(values, moduli=None): if not values: return Mod(0, 1) if not all(isinstance(v, IntegerMod_abstract) for v in values): - raise TypeError("arguments to CRT_list should be lists of IntegerMod") + raise TypeError("if one argument is given, it should be a list of IntegerMod") if len(values) == 1: return values[0] moduli = [v.modulus() for v in values] From 3d79e3cc8ce6756ef3c6f55a873734a644e9d7c9 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Sat, 30 Nov 2024 13:53:46 +0700 Subject: [PATCH 176/369] Fix some errors in documentation of cachefunc --- src/sage/misc/cachefunc.pyx | 76 +++++++++++++++++++++++++++---------- 1 file changed, 55 insertions(+), 21 deletions(-) diff --git a/src/sage/misc/cachefunc.pyx b/src/sage/misc/cachefunc.pyx index 52c10174dca..5efa2f932e8 100644 --- a/src/sage/misc/cachefunc.pyx +++ b/src/sage/misc/cachefunc.pyx @@ -147,7 +147,7 @@ that has generally a slower attribute access, but fully supports cached methods. We remark, however, that cached methods are *much* faster if attribute access works. So, we expect that :class:`~sage.structure.element.ElementWithCachedMethod` will -hardly by used. +hardly be used. :: sage: # needs sage.misc.cython @@ -1882,7 +1882,7 @@ cdef class CachedMethodCaller(CachedFunction): sage: a.f(5) is a.f(y=1,x=5) True - The method can be called as a bound function using the same cache:: + The method can be called as a unbound function using the same cache:: sage: a.f(5) is Foo.f(a, 5) True @@ -1940,7 +1940,7 @@ cdef class CachedMethodCaller(CachedFunction): True """ if self._instance is None: - # cached method bound to a class + # cached method bound to a class i.e. unbound method, such as ``Foo.f`` instance = args[0] args = args[1:] return self._cachedmethod.__get__(instance)(*args, **kwds) @@ -1987,7 +1987,7 @@ cdef class CachedMethodCaller(CachedFunction): 5 """ if self._instance is None: - # cached method bound to a class + # cached method bound to a class i.e. unbound method, such as ``CachedMethodTest.f`` instance = args[0] args = args[1:] return self._cachedmethod.__get__(instance).cached(*args, **kwds) @@ -2548,16 +2548,15 @@ cdef class CachedMethod(): sage: a.f(3) is res True - Note, however, that the :class:`CachedMethod` is replaced by a - :class:`CachedMethodCaller` or :class:`CachedMethodCallerNoArgs` - as soon as it is bound to an instance or class:: + Note, however, that accessing the attribute directly will call :meth:`__get__`, + and returns a :class:`CachedMethodCaller` or :class:`CachedMethodCallerNoArgs`. sage: P. = QQ[] sage: I = P*[a,b] sage: type(I.__class__.gens) - - So, you would hardly ever see an instance of this class alive. + sage: type(I.__class__.__dict__["gens"]) + The parameter ``key`` can be used to pass a function which creates a custom cache key for inputs. In the following example, this parameter is @@ -2640,13 +2639,17 @@ cdef class CachedMethod(): sage: a.f0() 4 - The computations in method ``f`` are tried to store in a - dictionary assigned to the instance ``a``:: + For methods with parameters, computations in method ``f`` are + tried to store in a dictionary assigned to the instance ``a``:: sage: hasattr(a, '_cache__f') True sage: a._cache__f {((2,), ()): 4} + sage: a._cache_f0 + Traceback (most recent call last): + ... + AttributeError: 'Foo' object has no attribute '_cache_f0'... As a shortcut, useful to speed up internal computations, the same dictionary is also available as an attribute @@ -2694,6 +2697,8 @@ cdef class CachedMethod(): def __call__(self, inst, *args, **kwds): """ Call the cached method as a function on an instance. + This code path is not used directly except in a few rare cases, + see examples for details. INPUT: @@ -2745,20 +2750,48 @@ cdef class CachedMethod(): ....: def f(self, n=2): ....: return self._x^n sage: a = Foo(2) + + Initially ``_cache__f`` is not an attribute of ``a``:: + + sage: hasattr(a, "_cache__f") + False + + When the attribute is accessed (thus ``__get__`` is called), + the cache is created and assigned to the attribute:: + + sage: a.f + Cached version of + sage: a._cache__f + {} sage: a.f() 4 + sage: a.f.cache + {((2,), ()): 4} + sage: a._cache__f + {((2,), ()): 4} - Note that we cannot provide a direct test, since ``a.f`` is - an instance of :class:`CachedMethodCaller`. But during its - initialisation, this method was called in order to provide the - cached method caller with its cache, and, if possible, assign - it to an attribute of ``a``. So, the following is an indirect - doctest:: + Testing the method directly:: - sage: a.f.cache # indirect doctest - {((2,), ()): 4} + sage: a = Foo(2) + sage: hasattr(a, "_cache__f") + False + sage: Foo.__dict__["f"]._get_instance_cache(a) + {} sage: a._cache__f + {} + sage: a.f() + 4 + sage: Foo.__dict__["f"]._get_instance_cache(a) {((2,), ()): 4} + + Using ``__dict__`` is needed to access this function because + ``Foo.f`` would call ``__get__`` and thus create a + :class:`CachedMethodCaller`:: + + sage: type(Foo.f) + + sage: type(Foo.__dict__["f"]) + """ default = {} if self._cachedfunc.do_pickle else NonpicklingDict() try: @@ -2868,8 +2901,9 @@ cdef class CachedSpecialMethod(CachedMethod): For new style classes ``C``, it is not possible to override a special method, such as ``__hash__``, in the ``__dict__`` of an instance ``c`` of - ``C``, because Python will for efficiency reasons always use what is - provided by the class, not by the instance. + ``C``, because Python will always use what is provided by the class, not + by the instance to avoid metaclass confusion. See + ``_. By consequence, if ``__hash__`` would be wrapped by using :class:`CachedMethod`, then ``hash(c)`` will access ``C.__hash__`` and bind From 70f47a467889329d16fa4cda2878f6f4fedac29c Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Sat, 30 Nov 2024 14:45:33 +0700 Subject: [PATCH 177/369] Remove a duplicated paragraph --- src/sage/misc/cachefunc.pyx | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/sage/misc/cachefunc.pyx b/src/sage/misc/cachefunc.pyx index 5efa2f932e8..7e65cef7341 100644 --- a/src/sage/misc/cachefunc.pyx +++ b/src/sage/misc/cachefunc.pyx @@ -97,15 +97,6 @@ approach is still needed for cpdef methods:: sage: O.direct_method(5) is O.direct_method(5) True -In some cases, one would only want to keep the result in cache as long -as there is any other reference to the result. By :issue:`12215`, this is -enabled for :class:`~sage.structure.unique_representation.UniqueRepresentation`, -which is used to create unique parents: If an algebraic structure, such -as a finite field, is only temporarily used, then it will not stay in -cache forever. That behaviour is implemented using ``weak_cached_function``, -that behaves the same as ``cached_function``, except that it uses a -:class:`~sage.misc.weak_dict.CachedWeakValueDictionary` for storing the results. - By :issue:`11115`, even if a parent does not allow attribute assignment, it can inherit a cached method from the parent class of a category (previously, the cache would have been broken):: From c16f7b0b69aa6713efdb0e84bbd1be6539cce434 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Sat, 30 Nov 2024 14:51:45 +0700 Subject: [PATCH 178/369] Add warning on shallow copy of object with cached_method --- src/sage/misc/cachefunc.pyx | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/sage/misc/cachefunc.pyx b/src/sage/misc/cachefunc.pyx index 7e65cef7341..67ce1ae15e9 100644 --- a/src/sage/misc/cachefunc.pyx +++ b/src/sage/misc/cachefunc.pyx @@ -406,6 +406,30 @@ the parent as its first argument:: sage: d = a.add_bigoh(1) # needs sage.rings.padics sage: b._cache_key() == d._cache_key() # this would be True if the parents were not included False + +Note that shallow copy of mutable objects may behave unexpectedly:: + + sage: class Foo: + ....: @cached_method + ....: def f(self): + ....: return self.x + sage: from copy import copy, deepcopy + sage: a = Foo() + sage: a.x = 1 + sage: a.f() + 1 + sage: b = copy(a) + sage: b.x = 2 + sage: b.f() # incorrect! + 1 + sage: b.f is a.f # this is the problem + True + sage: b = deepcopy(a) + sage: b.x = 2 + sage: b.f() # correct + 2 + sage: b.f is a.f + False """ # **************************************************************************** From 354bc31c848ef72f8af36a0511d784b19dcec217 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Sun, 1 Dec 2024 15:50:55 +0700 Subject: [PATCH 179/369] Apply suggestions --- src/sage/misc/cachefunc.pyx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/sage/misc/cachefunc.pyx b/src/sage/misc/cachefunc.pyx index 67ce1ae15e9..6790b2ab4e8 100644 --- a/src/sage/misc/cachefunc.pyx +++ b/src/sage/misc/cachefunc.pyx @@ -1897,7 +1897,7 @@ cdef class CachedMethodCaller(CachedFunction): sage: a.f(5) is a.f(y=1,x=5) True - The method can be called as a unbound function using the same cache:: + The method can be called as an unbound function using the same cache:: sage: a.f(5) is Foo.f(a, 5) True @@ -1955,7 +1955,7 @@ cdef class CachedMethodCaller(CachedFunction): True """ if self._instance is None: - # cached method bound to a class i.e. unbound method, such as ``Foo.f`` + # unbound cached method such as ``Foo.f`` instance = args[0] args = args[1:] return self._cachedmethod.__get__(instance)(*args, **kwds) @@ -2002,7 +2002,7 @@ cdef class CachedMethodCaller(CachedFunction): 5 """ if self._instance is None: - # cached method bound to a class i.e. unbound method, such as ``CachedMethodTest.f`` + # unbound cached method such as ``CachedMethodTest.f`` instance = args[0] args = args[1:] return self._cachedmethod.__get__(instance).cached(*args, **kwds) @@ -2654,8 +2654,8 @@ cdef class CachedMethod(): sage: a.f0() 4 - For methods with parameters, computations in method ``f`` are - tried to store in a dictionary assigned to the instance ``a``:: + For methods with parameters, the results of method ``f`` is attempted + to be stored in a dictionary attribute of the instance ``a``:: sage: hasattr(a, '_cache__f') True From 2ca0716feca04a842a4e96e26855b5befb93c3d9 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Sun, 8 Dec 2024 10:49:13 +0700 Subject: [PATCH 180/369] Fix incorrect code block rendering --- src/sage/misc/cachefunc.pyx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/sage/misc/cachefunc.pyx b/src/sage/misc/cachefunc.pyx index 6790b2ab4e8..6f1b2b1d430 100644 --- a/src/sage/misc/cachefunc.pyx +++ b/src/sage/misc/cachefunc.pyx @@ -2566,6 +2566,8 @@ cdef class CachedMethod(): Note, however, that accessing the attribute directly will call :meth:`__get__`, and returns a :class:`CachedMethodCaller` or :class:`CachedMethodCallerNoArgs`. + :: + sage: P. = QQ[] sage: I = P*[a,b] sage: type(I.__class__.gens) From 320daec3714e3ba4f853c2149d8e1e894c538204 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Wed, 11 Dec 2024 14:00:21 +0700 Subject: [PATCH 181/369] Make hack used in debug_options slightly cleaner --- src/sage/structure/debug_options.pyx | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/sage/structure/debug_options.pyx b/src/sage/structure/debug_options.pyx index 78e12faa0ce..a906278715a 100644 --- a/src/sage/structure/debug_options.pyx +++ b/src/sage/structure/debug_options.pyx @@ -41,12 +41,14 @@ cdef class DebugOptions_class: cdef DebugOptions_class debug = DebugOptions_class() -# Since "debug" is declared with a type, it can only be cimported at +# Since "debug" is declared with cdef, it can only be cimported at # the Cython level, not imported in plain Python. So we add it to the -# globals manually. We need to hack into Cython internals for this -# since Sage is compiled with the old_style_globals option. -from cpython.object cimport PyObject -cdef extern from *: - PyObject* __pyx_d - -(__pyx_d)["debug"] = debug +# globals manually. However this will make the variable out of sync +# if some user modifies the object, which is inevitable. +# See https://github.com/cython/cython/issues/3959#issuecomment-753455240 +# and https://github.com/cython/cython/issues/656 +# Note that ``_this_module`` could not be ``globals()`` +# because Sage is compiled with the old_style_globals option. +import sage.structure.debug_options as _this_module +_this_module.debug = debug +del _this_module From c0e9b90bf8bc7a99f9f7411df25e92021a337667 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Mon, 9 Dec 2024 06:02:06 +0700 Subject: [PATCH 182/369] Remove erroneous member declaration in farey_symbol --- src/sage/modular/arithgroup/farey_symbol.pyx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/sage/modular/arithgroup/farey_symbol.pyx b/src/sage/modular/arithgroup/farey_symbol.pyx index af81a0f61fc..499a685036a 100644 --- a/src/sage/modular/arithgroup/farey_symbol.pyx +++ b/src/sage/modular/arithgroup/farey_symbol.pyx @@ -47,7 +47,6 @@ from sage.structure.richcmp cimport richcmp_not_equal cdef extern from "sl2z.hpp": cppclass cpp_SL2Z "SL2Z": - mpz_class a, b, c, d cpp_SL2Z(int, int, int, int) cpp_SL2Z(mpz_class, mpz_class, mpz_class, mpz_class) mpz_class a() From 81cde250c638953d4f3a8b18af8a065e4435d0b7 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Thu, 12 Dec 2024 15:40:27 +0100 Subject: [PATCH 183/369] typos --- src/sage/modules/free_module_pseudohomspace.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/sage/modules/free_module_pseudohomspace.py b/src/sage/modules/free_module_pseudohomspace.py index 2df8be4b3a2..d6e94c7c295 100644 --- a/src/sage/modules/free_module_pseudohomspace.py +++ b/src/sage/modules/free_module_pseudohomspace.py @@ -201,7 +201,7 @@ def ore_ring(self, var='x'): INPUT: - ``var`` -- string (default: ``x``) the name of - tha variable + the variable EXAMPLES:: @@ -273,7 +273,7 @@ def _test_additive_associativity(self, tester): r""" Test associativity for (not necessarily all) elements in this parent. - This test is not relevant for pseudo-morphisms because they are not + This test is not relevant for pseudomorphisms because they are not stable by addition. TESTS:: @@ -290,7 +290,7 @@ def _test_distributivity(self, tester): r""" Test distributivity for (not necessarily all) elements in this parent. - This test is not relevant for pseudo-morphisms because they are not + This test is not relevant for pseudomorphisms because they are not stable by addition. TESTS:: @@ -307,8 +307,8 @@ def _test_one(self, tester): r""" Test properties the identity element. - This test is not relevant for pseudo-morphisms because the identity - is not a pseudo-morphism in general. + This test is not relevant for pseudomorphisms because the identity + is not a pseudomorphism in general. TESTS:: @@ -324,8 +324,8 @@ def _test_zero(self, tester): r""" Test properties of the zero element. - This test is not relevant for pseudo-morphisms because the zero - map is not a pseudo-morphism in general. + This test is not relevant for pseudomorphisms because the zero + map is not a pseudomorphism in general. TESTS:: From c06a1433e2c8b166e74c44a101581f9c249cabda Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Thu, 12 Dec 2024 15:41:21 +0100 Subject: [PATCH 184/369] uncapitalize pseudomorphisms --- src/sage/modules/free_module_pseudohomspace.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/modules/free_module_pseudohomspace.py b/src/sage/modules/free_module_pseudohomspace.py index d6e94c7c295..292d16f086d 100644 --- a/src/sage/modules/free_module_pseudohomspace.py +++ b/src/sage/modules/free_module_pseudohomspace.py @@ -32,7 +32,7 @@ class FreeModulePseudoHomspace(UniqueRepresentation, HomsetWithBase): r""" - This class implements the space of Pseudomorphisms with a fixed twist. + This class implements the space of pseudomorphisms with a fixed twist. For free modules, the elements of a pseudomorphism correspond to matrices which define the mapping on elements of a basis. From d935b8d62ce58071786904c6d9e4ce477ab91e6f Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Sat, 14 Dec 2024 21:55:09 +0700 Subject: [PATCH 185/369] Add note about makeflags and ninja parallelism --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index af91374fe19..f7844bf6ac7 100644 --- a/README.md +++ b/README.md @@ -341,6 +341,20 @@ in the Installation Guide. powerful machines, you might even consider `-j16`, as building with more jobs than CPU cores can speed things up further. + Alternatively, the `MAKEFLAGS` environment variable can be used. + In this case, only provide the flag itself, for example + `export MAKEFLAGS="-j4"`. + + Note that the compilation may nonetheless uses a different number of + threads, because sometimes `ninja` is used. + Unfortunately, [there is no way to control number of jobs `ninja` uses + from environment variables](https://github.com/ninja-build/ninja/issues/1482). + See also https://github.com/sagemath/sage/issues/38950. + + If the [Meson build system](https://doc-release--sagemath.netlify.app/html/en/installation/meson) + is used, the number of jobs running in parallel passed to `meson compile` will be respected, + because everything are managed by `ninja`. + To reduce the terminal output during the build, type `export V=0`. (`V` stands for "verbosity".) From c10623f0ee19d047b46d7f39d2b302f5662d4ae7 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Wed, 18 Dec 2024 10:49:18 +0700 Subject: [PATCH 186/369] Apply suggestions from code review Co-authored-by: Travis Scrimshaw --- src/sage/arith/misc.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/sage/arith/misc.py b/src/sage/arith/misc.py index ca889d2bc74..9a09a3263a2 100644 --- a/src/sage/arith/misc.py +++ b/src/sage/arith/misc.py @@ -3498,7 +3498,8 @@ def crt(a, b, m=None, n=None): def CRT_list(values, moduli=None): - r""" Given a list ``values`` of elements and a list of corresponding + r""" + Given a list ``values`` of elements and a list of corresponding ``moduli``, find a single element that reduces to each element of ``values`` modulo the corresponding moduli. @@ -3600,7 +3601,7 @@ def CRT_list(values, moduli=None): sage: ms [5, 7, 9] """ - if not isinstance(values, list) or not isinstance(moduli, (list, type(None))): + if not isinstance(values, list) or (moduli is not None and not isinstance(moduli, list)): raise ValueError("arguments to CRT_list should be lists") return_mod = moduli is None if return_mod: From c0820e6373b703362a83feb8e6cb65a269844ac8 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Wed, 18 Dec 2024 10:53:35 +0700 Subject: [PATCH 187/369] Apply more suggested changes --- src/sage/arith/misc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/arith/misc.py b/src/sage/arith/misc.py index 9a09a3263a2..e7bb72fc8e3 100644 --- a/src/sage/arith/misc.py +++ b/src/sage/arith/misc.py @@ -3504,8 +3504,8 @@ def CRT_list(values, moduli=None): ``values`` modulo the corresponding moduli. This function can also be called with one argument, each element - of the list is a :class:`IntegerMod_abstract` object. In this case, it returns - another :class:`IntegerMod_abstract` object. + of the list is a :mod:`modular integer `. + In this case, it returns another modular integer. .. SEEALSO:: From 0344c371eaaa6114fe97c3dfd1859067eaa7cb13 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Sun, 22 Dec 2024 20:31:19 +0700 Subject: [PATCH 188/369] Retrigger CI From c1814966ce9319cfeb37eb64e3fa310cb97a96d5 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Fri, 27 Dec 2024 20:56:48 +0700 Subject: [PATCH 189/369] Refactor is_exact for period lattice --- src/sage/schemes/elliptic_curves/ell_field.py | 4 ++ .../schemes/elliptic_curves/period_lattice.py | 71 ++++++++++++------- 2 files changed, 49 insertions(+), 26 deletions(-) diff --git a/src/sage/schemes/elliptic_curves/ell_field.py b/src/sage/schemes/elliptic_curves/ell_field.py index 3ff2826f3e7..1f5e72c0343 100644 --- a/src/sage/schemes/elliptic_curves/ell_field.py +++ b/src/sage/schemes/elliptic_curves/ell_field.py @@ -1548,10 +1548,14 @@ def period_lattice(self): Period lattice associated to Elliptic Curve defined by y^2 = x^3 + x + 6 over Rational Field sage: EllipticCurve(RR, [1, 6]).period_lattice() Period lattice associated to Elliptic Curve defined by y^2 = x^3 + 1.00000000000000*x + 6.00000000000000 over Real Field with 53 bits of precision + sage: EllipticCurve(RDF, [1, 6]).period_lattice() + Period lattice associated to Elliptic Curve defined by y^2 = x^3 + 1.0*x + 6.0 over Real Double Field sage: EllipticCurve(RealField(100), [1, 6]).period_lattice() Period lattice associated to Elliptic Curve defined by y^2 = x^3 + 1.0000000000000000000000000000*x + 6.0000000000000000000000000000 over Real Field with 100 bits of precision sage: EllipticCurve(CC, [1, 6]).period_lattice() Period lattice associated to Elliptic Curve defined by y^2 = x^3 + 1.00000000000000*x + 6.00000000000000 over Complex Field with 53 bits of precision + sage: EllipticCurve(CDF, [1, 6]).period_lattice() + Period lattice associated to Elliptic Curve defined by y^2 = x^3 + 1.0*x + 6.0 over Complex Double Field sage: EllipticCurve(ComplexField(100), [1, 6]).period_lattice() Period lattice associated to Elliptic Curve defined by y^2 = x^3 + 1.0000000000000000000000000000*x + 6.0000000000000000000000000000 over Complex Field with 100 bits of precision sage: EllipticCurve(AA, [1, 6]).period_lattice() diff --git a/src/sage/schemes/elliptic_curves/period_lattice.py b/src/sage/schemes/elliptic_curves/period_lattice.py index 54cd135dea9..050243cb45f 100644 --- a/src/sage/schemes/elliptic_curves/period_lattice.py +++ b/src/sage/schemes/elliptic_curves/period_lattice.py @@ -108,6 +108,7 @@ import sage.rings.abc +from sage.categories.morphism import IdentityMorphism from sage.misc.cachefunc import cached_method from sage.misc.lazy_import import lazy_import from sage.modules.free_module import FreeModule_generic_pid @@ -116,7 +117,7 @@ from sage.rings.integer_ring import ZZ from sage.rings.qqbar import AA, QQbar from sage.rings.rational_field import QQ -from sage.rings.real_mpfr import RealField, RealField_class, RealNumber +from sage.rings.real_mpfr import RealField, RealNumber from sage.schemes.elliptic_curves.constructor import EllipticCurve from sage.structure.richcmp import richcmp_method, richcmp, richcmp_not_equal @@ -231,14 +232,14 @@ def __init__(self, E, embedding=None): # the given embedding: K = E.base_field() - self.is_approximate = isinstance(K, (RealField_class, ComplexField_class)) + self._is_exact = K.is_exact() if embedding is None: if K in (AA, QQbar): embedding = K.hom(QQbar) real = K == AA - elif self.is_approximate: - embedding = K.hom(K) - real = isinstance(K, RealField_class) + elif not self._is_exact: + embedding = IdentityMorphism(K) + real = isinstance(K, (sage.rings.abc.RealField, sage.rings.abc.RealDoubleField)) else: embs = K.embeddings(AA) real = len(embs) > 0 @@ -271,24 +272,24 @@ def __init__(self, E, embedding=None): # The ei are used both for period computation and elliptic # logarithms. - if self.is_approximate: - self.f2 = self.E.two_division_polynomial() - else: + if self._is_exact: self.Ebar = self.E.change_ring(self.embedding) self.f2 = self.Ebar.two_division_polynomial() + else: + self.f2 = self.E.two_division_polynomial() if self.real_flag == 1: # positive discriminant - self._ei = self.f2.roots(K if self.is_approximate else AA,multiplicities=False) + self._ei = self.f2.roots(AA if self._is_exact else K, multiplicities=False) self._ei.sort() # e1 < e2 < e3 e1, e2, e3 = self._ei elif self.real_flag == -1: # negative discriminant - self._ei = self.f2.roots(ComplexField(K.precision()) if self.is_approximate else QQbar, multiplicities=False) + self._ei = self.f2.roots(QQbar if self._is_exact else ComplexField(K.precision()), multiplicities=False) self._ei = sorted(self._ei, key=lambda z: z.imag()) e1, e3, e2 = self._ei # so e3 is real - if not self.is_approximate: + if self._is_exact: e3 = AA(e3) self._ei = [e1, e2, e3] else: - self._ei = self.f2.roots(ComplexField(K.precision()) if self.is_approximate else QQbar, multiplicities=False) + self._ei = self.f2.roots(QQbar if self._is_exact else ComplexField(K.precision()), multiplicities=False) e1, e2, e3 = self._ei # The quantities sqrt(e_i-e_j) are cached (as elements of @@ -350,7 +351,7 @@ def __repr__(self): Defn: a |--> 1.259921049894873? """ K = self.E.base_field() - if K in (QQ, AA, QQbar) or isinstance(K, (RealField_class, ComplexField_class)): + if K in (QQ, AA, QQbar) or isinstance(self.embedding, IdentityMorphism): return "Period lattice associated to %s" % (self.E) return "Period lattice associated to %s with respect to the embedding %s" % (self.E, self.embedding) @@ -656,7 +657,7 @@ def _compute_default_prec(self): r""" Internal function to compute the default precision to be used if nothing is passed in. """ - return self.E.base_field().precision() if self.is_approximate else RealField().precision() + return RealField().precision() if self._is_exact else self.E.base_field().precision() @cached_method def _compute_periods_real(self, prec=None, algorithm='sage'): @@ -704,7 +705,7 @@ def _compute_periods_real(self, prec=None, algorithm='sage'): if algorithm == 'pari': ainvs = self.E.a_invariants() - if self.E.base_field() is not QQ and not self.is_approximate: + if self.E.base_field() is not QQ and self._is_exact: ainvs = [C(self.embedding(ai)).real() for ai in ainvs] # The precision for omega() is determined by ellinit() @@ -716,7 +717,7 @@ def _compute_periods_real(self, prec=None, algorithm='sage'): raise ValueError("invalid value of 'algorithm' parameter") pi = R.pi() - # Up to now everything has been exact in AA or QQbar (unless self.is_approximate), + # Up to now everything has been exact in AA or QQbar (if self._is_exact), # but now we must go transcendental. Only now is the desired precision used! if self.real_flag == 1: # positive discriminant a, b, c = (R(x) for x in self._abc) @@ -788,7 +789,7 @@ def _compute_periods_complex(self, prec=None, normalise=True): prec = self._compute_default_prec() C = ComplexField(prec) - # Up to now everything has been exact in AA or QQbar (unless self.is_approximate), + # Up to now everything has been exact in AA or QQbar (if self._is_exact), # but now we must go transcendental. Only now is the desired precision used! pi = C.pi() a, b, c = (C(x) for x in self._abc) @@ -1757,10 +1758,19 @@ def elliptic_logarithm(self, P, prec=None, reduce=True): sage: L.real_flag -1 sage: P = E(3, 6) - sage: L.elliptic_logarithm(P) + sage: L.elliptic_logarithm(P) # abs tol 1e-26 2.4593388737550379526023682666 - sage: L.elliptic_exponential(_) + sage: L.elliptic_exponential(_) # abs tol 1e-26 (3.0000000000000000000000000000 : 5.9999999999999999999999999999 : 1.0000000000000000000000000000) + sage: E = EllipticCurve(RDF, [1, 6]) + sage: L = E.period_lattice() + sage: L.real_flag + -1 + sage: P = E(3, 6) + sage: L.elliptic_logarithm(P) # abs tol 1e-13 + 2.45933887375504 + sage: L.elliptic_exponential(_) # abs tol 1e-13 + (3.00000000000000 : 6.00000000000001 : 1.00000000000000) Real approximate field, positive discriminant:: @@ -1769,9 +1779,9 @@ def elliptic_logarithm(self, P, prec=None, reduce=True): sage: L.real_flag 1 sage: P = E.lift_x(4) - sage: L.elliptic_logarithm(P) + sage: L.elliptic_logarithm(P) # abs tol 1e-26 0.51188849089267627141925354967 - sage: L.elliptic_exponential(_) + sage: L.elliptic_exponential(_) # abs tol 1e-26 (4.0000000000000000000000000000 : -7.1414284285428499979993998114 : 1.0000000000000000000000000000) Complex approximate field:: @@ -1781,10 +1791,19 @@ def elliptic_logarithm(self, P, prec=None, reduce=True): sage: L.real_flag 0 sage: P = E.lift_x(4) - sage: L.elliptic_logarithm(P) + sage: L.elliptic_logarithm(P) # abs tol 1e-26 -1.1447032790074574712147458157 - 0.72429843602171875396186134806*I - sage: L.elliptic_exponential(_) + sage: L.elliptic_exponential(_) # abs tol 1e-26 (4.0000000000000000000000000000 + 1.2025589033682610849950210280e-30*I : -8.2570982991257407680322611854 - 0.42387771989714340809597881586*I : 1.0000000000000000000000000000) + sage: E = EllipticCurve(CDF, [I, 3*I+4]) + sage: L = E.period_lattice() + sage: L.real_flag + 0 + sage: P = E.lift_x(4) + sage: L.elliptic_logarithm(P) # abs tol 1e-13 + -1.14470327900746 - 0.724298436021719*I + sage: L.elliptic_exponential(_) # abs tol 1e-13 + (4.00000000000000 - 0*I : -8.25709829912574 - 0.423877719897148*I : 1.00000000000000) """ if P.curve() is not self.E: raise ValueError("Point is on the wrong curve") @@ -2002,10 +2021,10 @@ def elliptic_exponential(self, z, to_curve=True): if to_curve: K = x.parent() - if self.is_approximate: - v = self.embedding - else: + if self._is_exact: v = refine_embedding(self.embedding, Infinity) + else: + v = self.embedding a1, a2, a3, a4, a6 = (K(v(a)) for a in self.E.ainvs()) b2 = K(v(self.E.b2())) x = x - b2 / 12 From 174d4a44589b154e69911e27993d819a85f2710b Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Sat, 28 Dec 2024 12:13:20 +0100 Subject: [PATCH 190/369] add keyword prec to log and exp --- .../charzero_drinfeld_module.py | 66 ++++++++++++++----- 1 file changed, 48 insertions(+), 18 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py index 6142ec2fa2d..c69cb6c4dbb 100644 --- a/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py @@ -25,11 +25,13 @@ from .drinfeld_module import DrinfeldModule from sage.rings.integer_ring import ZZ +from sage.rings.infinity import Infinity from sage.misc.cachefunc import cached_method from sage.misc.lazy_import import lazy_import lazy_import('sage.rings.lazy_series_ring', 'LazyPowerSeriesRing') +lazy_import('sage.rings.power_series_ring', 'PowerSeriesRing') class DrinfeldModule_charzero(DrinfeldModule): @@ -149,7 +151,7 @@ def _compute_coefficient_exp(self, k): c += self._compute_coefficient_exp(i)*self._compute_coefficient_log(j)**(q**i) return -c - def exponential(self, name='z'): + def exponential(self, prec=Infinity, name='z'): r""" Return the exponential of this Drinfeld module. @@ -158,28 +160,37 @@ def exponential(self, name='z'): INPUT: + - ``prec`` -- an integer or ``Infinity`` (default: ``Infinity``); + the precision at which the series is returned; if ``Infinity``, + a lazy power series in returned + - ``name`` -- string (default: ``'z'``); the name of the generator of the lazy power series ring - OUTPUT: a lazy power series over the base field - EXAMPLES:: sage: A = GF(2)['T'] sage: K. = Frac(A) sage: phi = DrinfeldModule(A, [T, 1]) sage: q = A.base_ring().cardinality() - sage: exp = phi.exponential(); exp - z + ((1/(T^2+T))*z^2) + ((1/(T^8+T^6+T^5+T^3))*z^4) + O(z^8) - The exponential is returned as a lazy power series, meaning that - any of its coefficients can be computed on demands:: + When ``prec`` is ``Infinity`` (which is the default), + the exponential is returned as a lazy power series, meaning + that any of its coefficients can be computed on demands:: + sage: exp = phi.exponential(); exp + z + ((1/(T^2+T))*z^2) + ((1/(T^8+T^6+T^5+T^3))*z^4) + O(z^8) sage: exp[2^4] 1/(T^64 + T^56 + T^52 + ... + T^27 + T^23 + T^15) sage: exp[2^5] 1/(T^160 + T^144 + T^136 + ... + T^55 + T^47 + T^31) + On the contrary, when ``prec`` is a finite number, all the + required coefficients are computed at once:: + + sage: phi.exponential(prec=10) + z + (1/(T^2 + T))*z^2 + (1/(T^8 + T^6 + T^5 + T^3))*z^4 + (1/(T^24 + T^20 + T^18 + T^17 + T^14 + T^13 + T^11 + T^7))*z^8 + O(z^10) + Example in higher rank:: sage: A = GF(5)['T'] @@ -216,7 +227,6 @@ def exponential(self, name='z'): See section 4.6 of [Gos1998]_ for the definition of the exponential. """ - L = LazyPowerSeriesRing(self._base, name) zero = self._base.zero() q = self._Fq.cardinality() @@ -228,7 +238,13 @@ def coeff_exp(k): return self._compute_coefficient_exp(v) else: return zero - return L(coeff_exp, valuation=1) + + if prec is Infinity: + L = LazyPowerSeriesRing(self._base, name) + return L(coeff_exp, valuation=1) + else: + L = PowerSeriesRing(self._base, name, default_prec=prec) + return L([0] + [coeff_exp(i) for i in range(1,prec)], prec=prec) @cached_method def _compute_coefficient_log(self, k): @@ -264,7 +280,7 @@ def _compute_coefficient_log(self, k): c += self._compute_coefficient_log(i)*self._gen[j]**(q**i) return c/(T - T**(q**k)) - def logarithm(self, name='z'): + def logarithm(self, prec=Infinity, name='z'): r""" Return the logarithm of the given Drinfeld module. @@ -275,27 +291,36 @@ def logarithm(self, name='z'): INPUT: + - ``prec`` -- an integer or ``Infinity`` (default: ``Infinity``); + the precision at which the series is returned; if ``Infinity``, + a lazy power series in returned + - ``name`` -- string (default: ``'z'``); the name of the generator of the lazy power series ring - OUTPUT: a lazy power series over the base field - EXAMPLES:: sage: A = GF(2)['T'] sage: K. = Frac(A) sage: phi = DrinfeldModule(A, [T, 1]) - sage: log = phi.logarithm(); log - z + ((1/(T^2+T))*z^2) + ((1/(T^6+T^5+T^3+T^2))*z^4) + O(z^8) - The logarithm is returned as a lazy power series, meaning that - any of its coefficients can be computed on demands:: + When ``prec`` is ``Infinity`` (which is the default), + the logarithm is returned as a lazy power series, meaning + that any of its coefficients can be computed on demands:: + sage: log = phi.logarithm(); log + z + ((1/(T^2+T))*z^2) + ((1/(T^6+T^5+T^3+T^2))*z^4) + O(z^8) sage: log[2^4] 1/(T^30 + T^29 + T^27 + ... + T^7 + T^5 + T^4) sage: log[2^5] 1/(T^62 + T^61 + T^59 + ... + T^8 + T^6 + T^5) + On the contrary, when ``prec`` is a finite number, all the + required coefficients are computed at once:: + + sage: phi.logarithm(prec=10) + z + (1/(T^2 + T))*z^2 + (1/(T^6 + T^5 + T^3 + T^2))*z^4 + (1/(T^14 + T^13 + T^11 + T^10 + T^7 + T^6 + T^4 + T^3))*z^8 + O(z^10) + Example in higher rank:: sage: A = GF(5)['T'] @@ -317,7 +342,6 @@ def logarithm(self, name='z'): sage: log[2**3] == -1/((T**q - T)*(T**(q**2) - T)*(T**(q**3) - T)) # expected value True """ - L = LazyPowerSeriesRing(self._base, name) q = self._Fq.cardinality() def coeff_log(k): @@ -328,7 +352,13 @@ def coeff_log(k): return self._compute_coefficient_log(v) else: return self._base.zero() - return L(coeff_log, valuation=1) + + if prec is Infinity: + L = LazyPowerSeriesRing(self._base, name) + return L(coeff_log, valuation=1) + else: + L = PowerSeriesRing(self._base, name, default_prec=prec) + return L([0] + [coeff_log(i) for i in range(1, prec)], prec=prec) @cached_method def _compute_goss_polynomial(self, n, q, poly_ring, X): From 8389d723f8cf7468cf535b403d73e3c566f2b06d Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Sat, 28 Dec 2024 14:34:16 +0100 Subject: [PATCH 191/369] class polynomial and Taelman exponential units --- .../charzero_drinfeld_module.py | 183 ++++++++++++++++++ .../drinfeld_modules/drinfeld_module.py | 11 +- src/sage/rings/ring_extension_element.pyx | 40 ++++ 3 files changed, 233 insertions(+), 1 deletion(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py index 6142ec2fa2d..a8cf1e9a2c5 100644 --- a/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py @@ -24,8 +24,12 @@ from .drinfeld_module import DrinfeldModule +from sage.functions.other import ceil from sage.rings.integer_ring import ZZ +from sage.matrix.constructor import matrix +from sage.modules.free_module_element import vector + from sage.misc.cachefunc import cached_method from sage.misc.lazy_import import lazy_import @@ -414,3 +418,182 @@ def goss_polynomial(self, n, var='X'): X = poly_ring.gen() q = self._Fq.cardinality() return self._compute_goss_polynomial(n, q, poly_ring, X) + + +class DrinfeldModule_rational(DrinfeldModule_charzero): + """ + A class for Drinfeld modules defined over the fraction + field of the underlying function field + """ + def _phiT_matrix(self, polynomial_part): + r""" + Return the matrix of `\phi_T` modulo `\pi^s` where `s` is + chosen such that `\pi^s` is in the domain of convergence + of the logarithm. + + It is an helper function; do not call it directly. + """ + A = self.function_ring() + Fq = A.base_ring() + q = Fq.cardinality() + r = self.rank() + + gs = [] + for g in self.coefficients(sparse=False): + g = g.backend(force=True) + if g.denominator().is_one(): + gs.append(A(g.numerator().list())) + else: + raise ValueError("the Drinfeld module must have polynomial coefficients") + s = max(ceil(gs[i].degree() / (q**i - 1)) for i in range(1, r+1)) - 1 + if s < 0: + s = 0 + + M = matrix(Fq, s) + if polynomial_part: + P = vector(A, s) + qk = 1 + for k in range(r+1): + for i in range(s): + e = (i+1)*qk + if polynomial_part: + P[i] += gs[k] >> e + for j in range(s): + e -= 1 + if e < 0: + break + M[i, j] += gs[k][e] + qk *= q + + if polynomial_part: + return M, P + else: + return M + + def class_polynomial(self): + r""" + Return the class polynomial, that is the Fitting ideal + of the class module, of this Drinfeld module. + + EXAMPLES: + + We check that the class module of the Carlitz module + is trivial:: + + sage: q = 5 + sage: Fq = GF(q) + sage: A = Fq['T'] + sage: K. = Frac(A) + sage: C = DrinfeldModule(A, [T, 1]); C + Drinfeld module defined by T |--> t + T + sage: C.class_polynomial() + 1 + + When the coefficients of the Drinfeld module have small + enough degrees, the class module is always trivial:: + + sage: r = 4 + sage: phi = DrinfeldModule(A, [T] + [A.random_element(degree=q**i) for i in range(1, r+1)]) + sage: phi.class_polynomial() + 1 + + Here is an example with a nontrivial class module:: + + sage: phi = DrinfeldModule(A, [T, -T^(2*q-1) + 2*T^(q-1)]) + sage: phi.class_polynomial() + T + 3 + """ + A = self.function_ring() + Fq = A.base_ring() + M = self._phiT_matrix(False) + s = M.nrows() + if s == 0: + # self is small + return A.one() + + v = vector(Fq, s) + v[s-1] = 1 + vs = [v] + for i in range(s-1): + v = v*M + vs.append(v) + V = matrix(vs) + V.echelonize() + + dim = V.rank() + pivots = V.pivots() + j = ip = 0 + for i in range(dim, s): + while ip < dim and j == pivots[ip]: + j += 1 + ip += 1 + V[i,j] = 1 + + N = (V * M * ~V).submatrix(dim, dim) + return A(N.charpoly()) + + def taelman_exponential_unit(self): + r""" + Return the exponential of the fundamental Taelman unit. + + EXAMPLES: + + The Taelman exponential unit of The Carlitz module is `1`:: + + sage: q = 7 + sage: Fq = GF(q) + sage: A = Fq['T'] + sage: K. = Frac(A) + sage: C = DrinfeldModule(A, [T, 1]); C + Drinfeld module defined by T |--> t + T + sage: C.taelman_exponential_unit() + 1 + + The same occurs more generally when the coefficients of the + Drinfeld module have small enough degrees:: + + sage: r = 4 + sage: phi = DrinfeldModule(A, [T] + [A.random_element(degree=q**i) for i in range(1, r+1)]) + sage: phi.taelman_exponential_unit() + 1 + + Usually, as soon as we leave the world of small Drinfeld modules, + Taelman's exponential units are highly non trivial:: + + sage: phi = DrinfeldModule(A, [T, T^(2*q+1), T^3]) + sage: phi.taelman_exponential_unit() + T^52 + T^22 + T^8 + T^2 + 1 + """ + A = self.function_ring() + Fq = A.base_ring() + q = Fq.cardinality() + M, P = self._phiT_matrix(True) + s = M.nrows() + if s == 0: + # self is small + return A(1) + + gs = self.coefficients(sparse=False) + v = vector(Fq, s) + v[s-1] = 1 + p = A.zero() + vs = [v] + ps = [p] + for i in range(s): + pq = p + p = v * P + for j in range(len(gs) - 1): + p += gs[j] * pq + pq = pq ** q + p += gs[-1] * pq + v = v * M + vs.append(v) + ps.append(p) + vs.reverse() + ps.reverse() + V = matrix(vs) + + unit = V.left_kernel().basis()[0] + expunit = sum(unit[i]*ps[i] for i in range(s+1)) + expunit /= expunit.numerator().leading_coefficient() + return expunit diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index 0b8d4cd32ff..f65b6753831 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -37,6 +37,7 @@ from sage.misc.misc_c import prod from sage.rings.integer import Integer from sage.rings.integer_ring import ZZ +from sage.rings.fraction_field import FractionField_generic from sage.rings.polynomial.ore_polynomial_element import OrePolynomial from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic from sage.structure.parent import Parent @@ -621,9 +622,17 @@ def __classcall_private__(cls, function_ring, gen, name='t'): raise ValueError('generator must have positive degree') # Instantiate the appropriate class: - if base_field.is_finite(): + backend = base_field.backend(force=True) + if backend.is_finite(): from sage.rings.function_field.drinfeld_modules.finite_drinfeld_module import DrinfeldModule_finite return DrinfeldModule_finite(gen, category) + if isinstance(backend, FractionField_generic): + ring = backend.ring() + if (isinstance(ring, PolynomialRing_generic) + and ring.base_ring() is function_ring_base + and base_morphism(T) == ring.gen()): + from .charzero_drinfeld_module import DrinfeldModule_rational + return DrinfeldModule_rational(gen, category) if not category._characteristic: from .charzero_drinfeld_module import DrinfeldModule_charzero return DrinfeldModule_charzero(gen, category) diff --git a/src/sage/rings/ring_extension_element.pyx b/src/sage/rings/ring_extension_element.pyx index 600b1d1a62e..d74ed04ac26 100644 --- a/src/sage/rings/ring_extension_element.pyx +++ b/src/sage/rings/ring_extension_element.pyx @@ -129,6 +129,46 @@ cdef class RingExtensionElement(CommutativeAlgebraElement): wrapper.__doc__ = method.__doc__ return wrapper + def __getitem__(self, i): + r""" + Return the `i`-th item of this element. + + This methods calls the appropriate method of the backend if + ``import_methods`` is set to ``True`` + + EXAMPLES:: + + sage: R. = QQ[] + sage: E = R.over() + sage: P = E(x^2 + 2*x + 3) + sage: P[0] + 3 + """ + if (self._parent)._import_methods: + output = self._backend[to_backend(i)] + return from_backend(output, self._parent) + return TypeError("this element is not subscriptable") + + def __call__(self, *args, **kwargs): + r""" + Call this element. + + This methods calls the appropriate method of the backend if + ``import_methods`` is set to ``True`` + + EXAMPLES:: + + sage: R. = QQ[] + sage: E = R.over() + sage: P = E(x^2 + 2*x + 3) + sage: P(1) + 6 + """ + if (self._parent)._import_methods: + output = self._backend(*to_backend(args), **to_backend(kwargs)) + return from_backend(output, self._parent) + return TypeError("this element is not callable") + def __dir__(self): """ Return the list of all the attributes of this element; From f0aed91e051899c6b0da734734c6ed339284dc3b Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Sun, 29 Dec 2024 07:47:13 +0100 Subject: [PATCH 192/369] more doctests --- src/doc/en/reference/references/index.rst | 3 + .../charzero_drinfeld_module.py | 71 ++++++++++++++++--- 2 files changed, 66 insertions(+), 8 deletions(-) diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index ca81cda3f75..42468429656 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -6473,6 +6473,9 @@ REFERENCES: **T** +.. [Tae2012] Lenny Taelman, *Special L-values of Drinfeld modules*, + Ann. Math. 175 (1), 2012, 369–391 + .. [Tak1999] Kisao Takeuchi, Totally real algebraic number fields of degree 9 with small discriminant, Saitama Math. J. 17 (1999), 63--85 (2000). diff --git a/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py index a8cf1e9a2c5..b9c79246a12 100644 --- a/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py @@ -2,14 +2,16 @@ r""" Drinfeld modules over rings of characteristic zero -This module provides the class +This module provides the classes :class:`sage.rings.function_fields.drinfeld_module.charzero_drinfeld_module.DrinfeldModule_charzero`, +:class:`sage.rings.function_fields.drinfeld_module.charzero_drinfeld_module.DrinfeldModule_rational`, which inherits :class:`sage.rings.function_fields.drinfeld_module.drinfeld_module.DrinfeldModule`. AUTHORS: - David Ayotte (2023-09) +- Xavier Caruso (2024-12) - computation of class polynomials and Taelman's units """ # ***************************************************************************** @@ -427,11 +429,45 @@ class DrinfeldModule_rational(DrinfeldModule_charzero): """ def _phiT_matrix(self, polynomial_part): r""" - Return the matrix of `\phi_T` modulo `\pi^s` where `s` is - chosen such that `\pi^s` is in the domain of convergence - of the logarithm. + Return the matrix giving the action of `\phi_T` modulo `u^s` + where `u = 1/T` is the uniformizer at infinity `s` is chosen + such that `u^s` is in the domain of convergence of the logarithm. It is an helper function; do not call it directly. + + INPUT: + + - ``polynomial_part`` -- boolean; if ``False``, omit the + part with negative powers of `u`; if ``True``, return this + part as a polynomial vector in `T` + + TESTS:: + + sage: q = 5 + sage: Fq = GF(q) + sage: A = Fq['T'] + sage: K. = Frac(A) + sage: phi = DrinfeldModule(A, [T, T^20]) + sage: phi._phiT_matrix(False) + [0 0 0 0] + [1 0 0 0] + [0 1 0 0] + [0 0 1 0] + sage: phi._phiT_matrix(True) + ( + [0 0 0 0] + [1 0 0 0] + [0 1 0 0] + [0 0 1 0], (T^15 + 1, T^10, T^5, 1) + ) + + :: + + sage: psi = DrinfeldModule(A, [T, 1/T]) + sage: psi._phiT_matrix(False) + Traceback (most recent call last): + ... + ValueError: the Drinfeld module must have polynomial coefficients """ A = self.function_ring() Fq = A.base_ring() @@ -502,13 +538,23 @@ def class_polynomial(self): sage: phi = DrinfeldModule(A, [T, -T^(2*q-1) + 2*T^(q-1)]) sage: phi.class_polynomial() T + 3 + + TESTS: + + The Drinfeld module must have polynomial coefficients:: + + sage: phi = DrinfeldModule(A, [T, 1/T]) + sage: phi.class_polynomial() + Traceback (most recent call last): + ... + ValueError: the Drinfeld module must have polynomial coefficients """ A = self.function_ring() Fq = A.base_ring() M = self._phiT_matrix(False) s = M.nrows() if s == 0: - # self is small + # small case return A.one() v = vector(Fq, s) @@ -534,7 +580,16 @@ def class_polynomial(self): def taelman_exponential_unit(self): r""" - Return the exponential of the fundamental Taelman unit. + Return the exponential of a fundamental Taelman's unit + of this Drinfeld module. + + A Taelman's unit is by definition an element `x \in + \FF_q((1/T))` whose exponential falls in `\FF_q[T]`. + + Taelman's units form a `\FF_q[T]`-line in `\FF_q((1/T))`; + a fundamental unit is by definition a generator of this line. + + We refer to [Tae2012]_ for more details about this construction. EXAMPLES: @@ -570,8 +625,8 @@ def taelman_exponential_unit(self): M, P = self._phiT_matrix(True) s = M.nrows() if s == 0: - # self is small - return A(1) + # small case + return self.base().one() gs = self.coefficients(sparse=False) v = vector(Fq, s) From f2f9c100036b309eeab5283f3686c0e020e2eb18 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Sun, 29 Dec 2024 07:51:21 +0100 Subject: [PATCH 193/369] small fix --- .../charzero_drinfeld_module.py | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py index b9c79246a12..b7dc1c6d910 100644 --- a/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py @@ -26,7 +26,6 @@ from .drinfeld_module import DrinfeldModule -from sage.functions.other import ceil from sage.rings.integer_ring import ZZ from sage.matrix.constructor import matrix @@ -449,18 +448,19 @@ def _phiT_matrix(self, polynomial_part): sage: K. = Frac(A) sage: phi = DrinfeldModule(A, [T, T^20]) sage: phi._phiT_matrix(False) - [0 0 0 0] - [1 0 0 0] - [0 1 0 0] - [0 0 1 0] + [0 0 0 0 0] + [1 0 0 0 0] + [0 1 0 0 0] + [0 0 1 0 0] + [0 0 0 1 1] sage: phi._phiT_matrix(True) ( - [0 0 0 0] - [1 0 0 0] - [0 1 0 0] - [0 0 1 0], (T^15 + 1, T^10, T^5, 1) + [0 0 0 0 0] + [1 0 0 0 0] + [0 1 0 0 0] + [0 0 1 0 0] + [0 0 0 1 1], (T^15 + 1, T^10, T^5, 1, 0) ) - :: sage: psi = DrinfeldModule(A, [T, 1/T]) @@ -481,9 +481,7 @@ def _phiT_matrix(self, polynomial_part): gs.append(A(g.numerator().list())) else: raise ValueError("the Drinfeld module must have polynomial coefficients") - s = max(ceil(gs[i].degree() / (q**i - 1)) for i in range(1, r+1)) - 1 - if s < 0: - s = 0 + s = max(gs[i].degree() // (q**i - 1) for i in range(1, r+1)) M = matrix(Fq, s) if polynomial_part: From 98f852aeb938d9cc8d9619aeb2c93750f0baec5a Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Sun, 29 Dec 2024 07:56:40 +0100 Subject: [PATCH 194/369] fix lint --- .../drinfeld_modules/charzero_drinfeld_module.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py index b7dc1c6d910..aba96365c02 100644 --- a/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py @@ -440,7 +440,7 @@ def _phiT_matrix(self, polynomial_part): part with negative powers of `u`; if ``True``, return this part as a polynomial vector in `T` - TESTS:: + EXAMPLES:: sage: q = 5 sage: Fq = GF(q) @@ -461,6 +461,7 @@ def _phiT_matrix(self, polynomial_part): [0 0 1 0 0] [0 0 0 1 1], (T^15 + 1, T^10, T^5, 1, 0) ) + :: sage: psi = DrinfeldModule(A, [T, 1/T]) From 988ae0a6556a41db1a03021396cb39d06ee54444 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Sun, 29 Dec 2024 08:12:00 +0100 Subject: [PATCH 195/369] avoid division by zero --- .../charzero_drinfeld_module.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py index aba96365c02..2349d8c8de8 100644 --- a/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py @@ -3,9 +3,9 @@ Drinfeld modules over rings of characteristic zero This module provides the classes -:class:`sage.rings.function_fields.drinfeld_module.charzero_drinfeld_module.DrinfeldModule_charzero`, +:class:`sage.rings.function_fields.drinfeld_module.charzero_drinfeld_module.DrinfeldModule_charzero` and :class:`sage.rings.function_fields.drinfeld_module.charzero_drinfeld_module.DrinfeldModule_rational`, -which inherits +which both inherit :class:`sage.rings.function_fields.drinfeld_module.drinfeld_module.DrinfeldModule`. AUTHORS: @@ -527,8 +527,9 @@ def class_polynomial(self): When the coefficients of the Drinfeld module have small enough degrees, the class module is always trivial:: - sage: r = 4 - sage: phi = DrinfeldModule(A, [T] + [A.random_element(degree=q**i) for i in range(1, r+1)]) + sage: gs = [T] + [A.random_element(degree = q^i) + ....: for i in range(1, 5)] + sage: phi = DrinfeldModule(A, gs) sage: phi.class_polynomial() 1 @@ -606,8 +607,9 @@ def taelman_exponential_unit(self): The same occurs more generally when the coefficients of the Drinfeld module have small enough degrees:: - sage: r = 4 - sage: phi = DrinfeldModule(A, [T] + [A.random_element(degree=q**i) for i in range(1, r+1)]) + sage: gs = [T] + [A.random_element(degree = q^i - 1) + ....: for i in range(1, 5)] + sage: phi = DrinfeldModule(A, gs) sage: phi.taelman_exponential_unit() 1 @@ -649,5 +651,6 @@ def taelman_exponential_unit(self): unit = V.left_kernel().basis()[0] expunit = sum(unit[i]*ps[i] for i in range(s+1)) - expunit /= expunit.numerator().leading_coefficient() + if expunit: + expunit /= expunit.numerator().leading_coefficient() return expunit From eb19696b269e21bacd7ba3dc00660330dd8b98e1 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Sun, 29 Dec 2024 12:03:00 +0100 Subject: [PATCH 196/369] fix lint --- src/sage/rings/ring_extension_element.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/ring_extension_element.pyx b/src/sage/rings/ring_extension_element.pyx index d74ed04ac26..39916ab26f8 100644 --- a/src/sage/rings/ring_extension_element.pyx +++ b/src/sage/rings/ring_extension_element.pyx @@ -167,7 +167,7 @@ cdef class RingExtensionElement(CommutativeAlgebraElement): if (self._parent)._import_methods: output = self._backend(*to_backend(args), **to_backend(kwargs)) return from_backend(output, self._parent) - return TypeError("this element is not callable") + return TypeError("this element is not callable") def __dir__(self): """ From 421bbe5ee14d1dcb64e4eba74d8b2e88fc0a19df Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Sun, 29 Dec 2024 12:11:01 +0100 Subject: [PATCH 197/369] fix documentation (?) --- .../drinfeld_modules/charzero_drinfeld_module.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py index 2349d8c8de8..ba71f2b5086 100644 --- a/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py @@ -584,9 +584,9 @@ def taelman_exponential_unit(self): of this Drinfeld module. A Taelman's unit is by definition an element `x \in - \FF_q((1/T))` whose exponential falls in `\FF_q[T]`. + \mathbb F_q((1/T))` whose exponential falls in `\mathbb F_q[T]`. - Taelman's units form a `\FF_q[T]`-line in `\FF_q((1/T))`; + Taelman's units form a `\mathbb F_q[T]`-line in `\mathbb F_q((1/T))`; a fundamental unit is by definition a generator of this line. We refer to [Tae2012]_ for more details about this construction. From 21f46a64e06927b49a9ac3ea6e951384b362b8e5 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Fri, 3 Jan 2025 08:52:04 +0100 Subject: [PATCH 198/369] remove computation of Taelman's unit (will be for another PR) --- .../charzero_drinfeld_module.py | 240 ++++++++---------- 1 file changed, 101 insertions(+), 139 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py index ba71f2b5086..f34b2ebbe8d 100644 --- a/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py @@ -424,86 +424,109 @@ def goss_polynomial(self, n, var='X'): class DrinfeldModule_rational(DrinfeldModule_charzero): """ A class for Drinfeld modules defined over the fraction - field of the underlying function field + field of the underlying function field. + + TESTS:: + + sage: q = 9 + sage: Fq = GF(q) + sage: A = Fq['T'] + sage: K. = Frac(A) + sage: C = DrinfeldModule(A, [T, 1]); C + Drinfeld module defined by T |--> t + T + sage: type(C) + """ - def _phiT_matrix(self, polynomial_part): + def coefficient_in_function_ring(self, n): r""" - Return the matrix giving the action of `\phi_T` modulo `u^s` - where `u = 1/T` is the uniformizer at infinity `s` is chosen - such that `u^s` is in the domain of convergence of the logarithm. - - It is an helper function; do not call it directly. + Return the `n`-th coefficient of this Drinfeld module as + an element of the underlying function ring. INPUT: - - ``polynomial_part`` -- boolean; if ``False``, omit the - part with negative powers of `u`; if ``True``, return this - part as a polynomial vector in `T` + - ``n`` -- an integer EXAMPLES:: sage: q = 5 sage: Fq = GF(q) sage: A = Fq['T'] - sage: K. = Frac(A) - sage: phi = DrinfeldModule(A, [T, T^20]) - sage: phi._phiT_matrix(False) - [0 0 0 0 0] - [1 0 0 0 0] - [0 1 0 0 0] - [0 0 1 0 0] - [0 0 0 1 1] - sage: phi._phiT_matrix(True) - ( - [0 0 0 0 0] - [1 0 0 0 0] - [0 1 0 0 0] - [0 0 1 0 0] - [0 0 0 1 1], (T^15 + 1, T^10, T^5, 1, 0) - ) - - :: - - sage: psi = DrinfeldModule(A, [T, 1/T]) - sage: psi._phiT_matrix(False) + sage: R = Fq['U'] + sage: K. = Frac(R) + sage: phi = DrinfeldModule(A, [U, 0, U^2, U^3]) + sage: phi.coefficient_in_function_ring(2) + T^2 + + Compare with the method meth:`coefficient`:: + + sage: phi.coefficient(2) + U^2 + + If the required coefficient is not a polynomials, + an error is raised:: + + sage: psi = DrinfeldModule(A, [U, 1/U]) + sage: psi.coefficient_in_function_ring(0) + T + sage: psi.coefficient_in_function_ring(1) Traceback (most recent call last): ... - ValueError: the Drinfeld module must have polynomial coefficients + ValueError: coefficient is not polynomial """ A = self.function_ring() - Fq = A.base_ring() - q = Fq.cardinality() - r = self.rank() + g = self.coefficient(n) + g = g.backend(force=True) + if g.denominator().is_one(): + return A(g.numerator().list()) + else: + raise ValueError("coefficient is not polynomial") + + def coefficients_in_function_ring(self, sparse=True): + r""" + Return the coefficients of this Drinfeld module as elements + of the underlying function ring. + + INPUT: + + - ``sparse`` -- a boolean (default: ``True``); if ``True``, + only return the nonzero coefficients; otherwise, return + all of them. + + EXAMPLES:: + + sage: q = 5 + sage: Fq = GF(q) + sage: A = Fq['T'] + sage: R = Fq['U'] + sage: K. = Frac(R) + sage: phi = DrinfeldModule(A, [U, 0, U^2, U^3]) + sage: phi.coefficients_in_function_ring() + [T, T^2, T^3] + sage: phi.coefficients_in_function_ring(sparse=False) + [T, 0, T^2, T^3] + Compare with the method meth:`coefficients`:: + + sage: phi.coefficients() + [U, U^2, U^3] + + If the coefficients are not polynomials, an error is raised:: + + sage: psi = DrinfeldModule(A, [U, 1/U]) + sage: psi.coefficients_in_function_ring() + Traceback (most recent call last): + ... + ValueError: coefficients are not polynomials + """ + A = self.function_ring() gs = [] - for g in self.coefficients(sparse=False): + for g in self.coefficients(sparse): g = g.backend(force=True) if g.denominator().is_one(): gs.append(A(g.numerator().list())) else: - raise ValueError("the Drinfeld module must have polynomial coefficients") - s = max(gs[i].degree() // (q**i - 1) for i in range(1, r+1)) - - M = matrix(Fq, s) - if polynomial_part: - P = vector(A, s) - qk = 1 - for k in range(r+1): - for i in range(s): - e = (i+1)*qk - if polynomial_part: - P[i] += gs[k] >> e - for j in range(s): - e -= 1 - if e < 0: - break - M[i, j] += gs[k][e] - qk *= q - - if polynomial_part: - return M, P - else: - return M + raise ValueError("coefficients are not polynomials") + return gs def class_polynomial(self): r""" @@ -547,16 +570,32 @@ def class_polynomial(self): sage: phi.class_polynomial() Traceback (most recent call last): ... - ValueError: the Drinfeld module must have polynomial coefficients + ValueError: coefficients are not polynomials """ A = self.function_ring() Fq = A.base_ring() - M = self._phiT_matrix(False) - s = M.nrows() + q = Fq.cardinality() + r = self.rank() + + gs = self.coefficients_in_function_ring(sparse=False) + + s = max(gs[i].degree() // (q**i - 1) for i in range(1, r+1)) if s == 0: # small case return A.one() + M = matrix(Fq, s) + qk = 1 + for k in range(r+1): + for i in range(s): + e = (i+1)*qk + for j in range(s): + e -= 1 + if e < 0: + break + M[i, j] += gs[k][e] + qk *= q + v = vector(Fq, s) v[s-1] = 1 vs = [v] @@ -577,80 +616,3 @@ def class_polynomial(self): N = (V * M * ~V).submatrix(dim, dim) return A(N.charpoly()) - - def taelman_exponential_unit(self): - r""" - Return the exponential of a fundamental Taelman's unit - of this Drinfeld module. - - A Taelman's unit is by definition an element `x \in - \mathbb F_q((1/T))` whose exponential falls in `\mathbb F_q[T]`. - - Taelman's units form a `\mathbb F_q[T]`-line in `\mathbb F_q((1/T))`; - a fundamental unit is by definition a generator of this line. - - We refer to [Tae2012]_ for more details about this construction. - - EXAMPLES: - - The Taelman exponential unit of The Carlitz module is `1`:: - - sage: q = 7 - sage: Fq = GF(q) - sage: A = Fq['T'] - sage: K. = Frac(A) - sage: C = DrinfeldModule(A, [T, 1]); C - Drinfeld module defined by T |--> t + T - sage: C.taelman_exponential_unit() - 1 - - The same occurs more generally when the coefficients of the - Drinfeld module have small enough degrees:: - - sage: gs = [T] + [A.random_element(degree = q^i - 1) - ....: for i in range(1, 5)] - sage: phi = DrinfeldModule(A, gs) - sage: phi.taelman_exponential_unit() - 1 - - Usually, as soon as we leave the world of small Drinfeld modules, - Taelman's exponential units are highly non trivial:: - - sage: phi = DrinfeldModule(A, [T, T^(2*q+1), T^3]) - sage: phi.taelman_exponential_unit() - T^52 + T^22 + T^8 + T^2 + 1 - """ - A = self.function_ring() - Fq = A.base_ring() - q = Fq.cardinality() - M, P = self._phiT_matrix(True) - s = M.nrows() - if s == 0: - # small case - return self.base().one() - - gs = self.coefficients(sparse=False) - v = vector(Fq, s) - v[s-1] = 1 - p = A.zero() - vs = [v] - ps = [p] - for i in range(s): - pq = p - p = v * P - for j in range(len(gs) - 1): - p += gs[j] * pq - pq = pq ** q - p += gs[-1] * pq - v = v * M - vs.append(v) - ps.append(p) - vs.reverse() - ps.reverse() - V = matrix(vs) - - unit = V.left_kernel().basis()[0] - expunit = sum(unit[i]*ps[i] for i in range(s+1)) - if expunit: - expunit /= expunit.numerator().leading_coefficient() - return expunit From f182fdab05901ed7ba7c83249c79ba9821f8025a Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Fri, 3 Jan 2025 09:47:05 +0100 Subject: [PATCH 199/369] simplify doctest Co-authored-by: Martin Rubey --- .../function_field/drinfeld_modules/charzero_drinfeld_module.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py index c69cb6c4dbb..77ec8905aa4 100644 --- a/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py @@ -315,7 +315,7 @@ def logarithm(self, prec=Infinity, name='z'): sage: log[2^5] 1/(T^62 + T^61 + T^59 + ... + T^8 + T^6 + T^5) - On the contrary, when ``prec`` is a finite number, all the + If ``prec`` is a finite number, all the required coefficients are computed at once:: sage: phi.logarithm(prec=10) From cb99e9e26d93722f743a4cf1bcd264a628009719 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Fri, 3 Jan 2025 09:47:32 +0100 Subject: [PATCH 200/369] remove useless else in coeff_exp Co-authored-by: Martin Rubey --- .../drinfeld_modules/charzero_drinfeld_module.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py index 77ec8905aa4..d51dae1ba57 100644 --- a/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py @@ -356,9 +356,8 @@ def coeff_log(k): if prec is Infinity: L = LazyPowerSeriesRing(self._base, name) return L(coeff_log, valuation=1) - else: - L = PowerSeriesRing(self._base, name, default_prec=prec) - return L([0] + [coeff_log(i) for i in range(1, prec)], prec=prec) + L = PowerSeriesRing(self._base, name, default_prec=prec) + return L([0] + [coeff_log(i) for i in range(1, prec)], prec=prec) @cached_method def _compute_goss_polynomial(self, n, q, poly_ring, X): From 492fcd4a4b8c3d082175dfe24df84158c7eb7aec Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Fri, 3 Jan 2025 09:47:46 +0100 Subject: [PATCH 201/369] remove useless else in coeff_log Co-authored-by: Martin Rubey --- .../drinfeld_modules/charzero_drinfeld_module.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py index d51dae1ba57..27a4373bb9a 100644 --- a/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py @@ -242,9 +242,8 @@ def coeff_exp(k): if prec is Infinity: L = LazyPowerSeriesRing(self._base, name) return L(coeff_exp, valuation=1) - else: - L = PowerSeriesRing(self._base, name, default_prec=prec) - return L([0] + [coeff_exp(i) for i in range(1,prec)], prec=prec) + L = PowerSeriesRing(self._base, name, default_prec=prec) + return L([0] + [coeff_exp(i) for i in range(1,prec)], prec=prec) @cached_method def _compute_coefficient_log(self, k): From 64a087cb6261f70387b4bfc85cb6d27e973bc541 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Fri, 3 Jan 2025 14:21:25 +0100 Subject: [PATCH 202/369] introduce new apozeta polynomial for posets --- src/sage/combinat/posets/posets.py | 50 ++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/src/sage/combinat/posets/posets.py b/src/sage/combinat/posets/posets.py index 2a4b2d24f48..a56ffa5c728 100644 --- a/src/sage/combinat/posets/posets.py +++ b/src/sage/combinat/posets/posets.py @@ -184,6 +184,7 @@ :meth:`~FinitePoset.flag_h_polynomial` | Return the flag h-polynomial of the poset. :meth:`~FinitePoset.order_polynomial` | Return the order polynomial of the poset. :meth:`~FinitePoset.zeta_polynomial` | Return the zeta polynomial of the poset. + :meth:`~FinitePoset.apozeta_polynomial` | Return the apozeta polynomial of the poset. :meth:`~FinitePoset.M_triangle` | Return the M-triangle of the poset. :meth:`~FinitePoset.kazhdan_lusztig_polynomial` | Return the Kazhdan-Lusztig polynomial of the poset. :meth:`~FinitePoset.coxeter_polynomial` | Return the characteristic polynomial of the Coxeter transformation. @@ -7240,6 +7241,8 @@ def zeta_polynomial(self): In particular, `Z(2)` is the number of vertices and `Z(3)` is the number of intervals. + .. SEEALSO:: :meth:`apozeta_polynomial` + EXAMPLES:: sage: posets.ChainPoset(2).zeta_polynomial() @@ -7280,6 +7283,53 @@ def zeta_polynomial(self): f = g[n] + f / n return f + def apozeta_polynomial(self): + r""" + Return the apozeta polynomial of the poset ``self``. + + The poset is assumed to be graded. + + The apozeta polynomial of a poset is the unique polynomial + `Z^{a}(q)` such that for every integer `m > 1`, `Z^{a}(m)` is + the number of weakly increasing sequences `x_1 \leq x_2 \leq + \dots \leq x_{m-1}` of elements of the poset whose largest + element belongs to the top level of the poset. + + When the poset `P` has a unique maximal element, this is + equal to `Z(q-1)` where `Z` is the zeta polynomial of `P`. + + The name comes from the greek radical ``apo``. + + .. SEEALSO:: :meth:`zeta_polynomial` + + EXAMPLES:: + + sage: P = posets.NoncrossingPartitions(SymmetricGroup(4)) + sage: P.apozeta_polynomial() + 8/3*q^3 - 10*q^2 + 37/3*q - 5 + + sage: P = Poset({"a": "bc", "b": "d", "c": "de"}) + sage: P.apozeta_polynomial() + 3/2*q^2 - 5/2*q + 1 + sage: P.zeta_polynomial() + 3/2*q^2 - 1/2*q + + TESTS: + + Checking the simplest case:: + + sage: Poset({1: []}).apozeta_polynomial() + 1 + sage: parent(_) + Univariate Polynomial Ring in q over Rational Field + """ + R = PolynomialRing(QQ, 'q') + q = R.gen() + + top_level = self.level_sets()[-1] + return sum(binomial(q - 2, len(c) - 1) + for c in self.chains() if c and c[-1] in top_level) + def M_triangle(self): r""" Return the M-triangle of the poset. From 9a3f8186a9aaf0dc8e531211ecf25ea5a2a7e498 Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Sat, 4 Jan 2025 00:21:59 +0800 Subject: [PATCH 203/369] Require Python 3.11 or newer; remove outdated workarounds --- .github/workflows/dist.yml | 2 +- .gitignore | 1 - README.md | 6 +- pyproject.toml | 4 +- pyrightconfig.json | 2 +- ruff.toml | 4 +- src/MANIFEST.in | 1 - src/doc/en/developer/coding_in_python.rst | 37 +-- src/doc/en/installation/conda.rst | 18 +- src/doc/en/installation/launching.rst | 5 +- src/sage/all__sagemath_repl.py | 6 - src/sage/cpython/atexit.pyx | 25 -- src/sage/cpython/cython_metaclass.h | 9 - src/sage/cpython/debug.pyx | 5 - src/sage/cpython/debugimpl.c | 348 -------------------- src/sage/cpython/pycore_long.h | 7 +- src/sage/doctest/forker.py | 2 +- src/sage/libs/gmp/pylong.pyx | 8 - src/sage/misc/fpickle.pyx | 11 +- src/sage/misc/inherit_comparison_impl.c | 6 - src/sage_docbuild/ext/sage_autodoc.py | 22 -- src/sage_setup/command/sage_build_cython.py | 3 +- src/setup.cfg.m4 | 2 +- 23 files changed, 38 insertions(+), 496 deletions(-) delete mode 100644 src/sage/cpython/debugimpl.c diff --git a/.github/workflows/dist.yml b/.github/workflows/dist.yml index 538b44d1431..4f84a999f44 100644 --- a/.github/workflows/dist.yml +++ b/.github/workflows/dist.yml @@ -188,7 +188,7 @@ jobs: # CIBW_ARCHS: ${{ matrix.arch }} # https://cibuildwheel.readthedocs.io/en/stable/options/#requires-python - CIBW_PROJECT_REQUIRES_PYTHON: ">=3.9, <3.13" + CIBW_PROJECT_REQUIRES_PYTHON: ">=3.11, <3.13" # Environment during wheel build CIBW_ENVIRONMENT: "PATH=$(pwd)/prefix/bin:$PATH CPATH=$(pwd)/prefix/include:$CPATH LIBRARY_PATH=$(pwd)/prefix/lib:$LIBRARY_PATH LD_LIBRARY_PATH=$(pwd)/prefix/lib:$LD_LIBRARY_PATH PKG_CONFIG_PATH=$(pwd)/prefix/share/pkgconfig:$PKG_CONFIG_PATH ACLOCAL_PATH=/usr/share/aclocal PIP_CONSTRAINT=$(pwd)/constraints.txt PIP_FIND_LINKS=file://$(pwd)/wheelhouse SAGE_NUM_THREADS=6" # Use 'build', not 'pip wheel' diff --git a/.gitignore b/.gitignore index 323d81b557b..8111db2b0bc 100644 --- a/.gitignore +++ b/.gitignore @@ -161,7 +161,6 @@ __pycache__/ /src/sage/modular/arithgroup/farey_symbol.h # List of C and C++ files that are actual source files, # NOT generated by Cython. The same list appears in src/MANIFEST.in -!/src/sage/cpython/debugimpl.c !/src/sage/graphs/base/boost_interface.cpp !/src/sage/graphs/cliquer/cl.c !/src/sage/graphs/graph_decompositions/sage_tdlib.cpp diff --git a/README.md b/README.md index af91374fe19..236709f7412 100644 --- a/README.md +++ b/README.md @@ -222,7 +222,7 @@ in the Installation Guide. more details. - Python 3.4 or later, or Python 2.7, a full installation including - `urllib`; but ideally version 3.9.x, 3.10.x, 3.11.x, 3.12.x, which + `urllib`; but ideally version 3.11.x or later, which will avoid having to build Sage's own copy of Python 3. See [build/pkgs/python3/SPKG.rst](build/pkgs/python3/SPKG.rst) for more details. @@ -551,11 +551,11 @@ SAGE_ROOT Root directory (create by git clone) │ │ ├── installed/ │ │ │ Records of installed non-Python packages │ │ ├── scripts/ Scripts for uninstalling installed packages -│ │ └── venv-python3.9 (SAGE_VENV) +│ │ └── venv-python (SAGE_VENV) │ │ │ Installation hierarchy (virtual environment) │ │ │ for Python packages │ │ ├── bin/ Executables and installed scripts -│ │ ├── lib/python3.9/site-packages/ +│ │ ├── lib/python/site-packages/ │ │ │ Python modules/packages are installed here │ │ └── var/lib/sage/ │ │ └── wheels/ diff --git a/pyproject.toml b/pyproject.toml index da06db03649..5b4a4be8cee 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -70,15 +70,13 @@ classifiers = [ "Operating System :: POSIX", "Operating System :: MacOS :: MacOS X", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: Implementation :: CPython", "Topic :: Scientific/Engineering :: Mathematics", ] urls = {Homepage = "https://www.sagemath.org"} -requires-python = ">=3.9, <3.13" +requires-python = ">=3.11, <3.13" [project.optional-dependencies] R = [ diff --git a/pyrightconfig.json b/pyrightconfig.json index 643c56360c9..826c3aabe86 100644 --- a/pyrightconfig.json +++ b/pyrightconfig.json @@ -7,7 +7,7 @@ "root": "src" } ], - "pythonVersion": "3.9", + "pythonVersion": "3.11", "exclude": ["venv"], "venvPath": "./venv/", "venv": "./", diff --git a/ruff.toml b/ruff.toml index b3070914153..bb2e932e430 100644 --- a/ruff.toml +++ b/ruff.toml @@ -1,7 +1,7 @@ # https://docs.astral.sh/ruff/configuration/#config-file-discovery -# Assume Python 3.9 -target-version = "py39" +# Python 3.11 is the minimum supported version +target-version = "py311" lint.select = [ "E", # pycodestyle errors - https://docs.astral.sh/ruff/rules/#error-e diff --git a/src/MANIFEST.in b/src/MANIFEST.in index f6231401294..8428dfff314 100644 --- a/src/MANIFEST.in +++ b/src/MANIFEST.in @@ -23,7 +23,6 @@ global-exclude *.cpp # List of C and C++ files that are actual source files, # NOT generated by Cython. The same list appears in SAGE_ROOT/.gitignore # -include sage/cpython/debugimpl.c include sage/graphs/base/boost_interface.cpp include sage/graphs/cliquer/cl.c include sage/libs/eclib/wrap.cpp diff --git a/src/doc/en/developer/coding_in_python.rst b/src/doc/en/developer/coding_in_python.rst index 1ea6eebb317..f86ab9a09c3 100644 --- a/src/doc/en/developer/coding_in_python.rst +++ b/src/doc/en/developer/coding_in_python.rst @@ -12,17 +12,14 @@ Sage. Python language standard ======================== -Sage library code needs to be compatible with all versions of Python -that Sage supports. The information regarding the supported versions -can be found in the files ``build/pkgs/python3/spkg-configure.m4`` and -``src/setup.cfg.m4``. - -Python 3.9 is the oldest supported version. Hence, -all language and library features that are available in Python 3.9 can -be used; but features introduced in Python 3.10 cannot be used. If a -feature is deprecated in a newer supported version, it must be ensured -that deprecation warnings issued by Python do not lead to failures in -doctests. +Sage follows the time window-based support policy +`SPEC 0 — Minimum Supported Dependencies `_ +for Python versions. +The current minimum supported Python version can be found in the +``pyproject.toml`` file. Accordingly, only language and library features +available in this version can be used. If a feature is deprecated in a newer +supported version, it must be ensured that deprecation warnings issued by +Python do not lead to failures in doctests. Some key language and library features have been backported to older Python versions using one of two mechanisms: @@ -34,21 +31,9 @@ using one of two mechanisms: of annotations). All Sage library code that uses type annotations should include this ``__future__`` import and follow PEP 563. -- Backport packages - - - `importlib_metadata <../reference/spkg/importlib_metadata>`_ - (to be used in place of ``importlib.metadata``), - - `importlib_resources <../reference/spkg/importlib_resources>`_ - (to be used in place of ``importlib.resources``), - - `typing_extensions <../reference/spkg/typing_extensions>`_ - (to be used in place of ``typing``). - - The Sage library declares these packages as dependencies and ensures that - versions that provide features of Python 3.11 are available. - -Meta :issue:`29756` keeps track of newer Python features and serves -as a starting point for discussions on how to make use of them in the -Sage library. +- The `typing_extensions <../reference/spkg/typing_extensions>`_ package + is used to backport features from newer versions of the ``typing`` module. + The Sage library declares this package as a dependency. Design diff --git a/src/doc/en/installation/conda.rst b/src/doc/en/installation/conda.rst index ae560bb5a38..65f03583af6 100644 --- a/src/doc/en/installation/conda.rst +++ b/src/doc/en/installation/conda.rst @@ -50,7 +50,7 @@ Create a new conda environment containing SageMath, either with ``mamba`` or ``c $ conda create -n sage sage python=X -where ``X`` is version of Python, e.g. ``3.9``. +where ``X`` is version of Python, e.g. ``3.12``. To use Sage from there, @@ -86,22 +86,22 @@ Here we assume that you are using a git checkout. .. code-block:: shell - $ mamba env create --file environment-3.11-linux.yml --name sage-dev + $ mamba env create --file environment-3.12-linux.yml --name sage-dev $ conda activate sage-dev .. tab:: conda .. code-block:: shell - $ conda env create --file environment-3.11-linux.yml --name sage-dev + $ conda env create --file environment-3.12-linux.yml --name sage-dev $ conda activate sage-dev - Alternatively, you can use ``environment-3.11-linux.yml`` or - ``environment-optional-3.11-linux.yml``, which will only install standard + Alternatively, you can use ``environment-3.12-linux.yml`` or + ``environment-optional-3.12-linux.yml``, which will only install standard (and optional) packages without any additional developer tools. - A different Python version can be selected by replacing ``3.11`` by ``3.9`` - or ``3.10`` in these commands. + A different Python version can be selected by replacing ``3.12`` with the + desired version. - Bootstrap the source tree and install the build prerequisites and the Sage library:: @@ -137,7 +137,7 @@ After editing any Cython files, rebuild the Sage library using:: In order to update the conda environment later, you can run:: - $ mamba env update --file environment-3.11-linux.yml --name sage-dev + $ mamba env update --file environment-3.12-linux.yml --name sage-dev To build the documentation, use:: @@ -156,5 +156,5 @@ To build the documentation, use:: You can update the conda lock files by running ``.github/workflows/conda-lock-update.py`` or by running - ``conda-lock --platform linux-64 --filename environment-3.11-linux.yml --lockfile environment-3.11-linux.lock`` + ``conda-lock --platform linux-64 --filename environment-3.12-linux.yml --lockfile environment-3.12-linux.lock`` manually. diff --git a/src/doc/en/installation/launching.rst b/src/doc/en/installation/launching.rst index b612a89a9c1..bb99fbcfb00 100644 --- a/src/doc/en/installation/launching.rst +++ b/src/doc/en/installation/launching.rst @@ -67,9 +67,8 @@ Sage uses the following environment variables when it runs: - See https://docs.python.org/3/using/cmdline.html#environment-variables - for more variables used by Python (not an exhaustive list). With - Python 3.11 or later, a brief summary can also be obtained by - running `python3 --help-env`. + for more variables used by Python (not an exhaustive list). + A brief summary can also be obtained by running `python3 --help-env`. Using a Jupyter Notebook remotely --------------------------------- diff --git a/src/sage/all__sagemath_repl.py b/src/sage/all__sagemath_repl.py index c830950c26b..13efd115e1c 100644 --- a/src/sage/all__sagemath_repl.py +++ b/src/sage/all__sagemath_repl.py @@ -100,12 +100,6 @@ message=r"Pickle, copy, and deepcopy support will be " r"removed from itertools in Python 3.14.") -# triggered in Python 3.9 on Redhat-based distributions -# https://github.com/sagemath/sage/issues/37863 -# https://github.com/networkx/networkx/issues/7101 -warnings.filterwarnings('ignore', category=RuntimeWarning, - message="networkx backend defined more than once: nx-loopback") - from sage.all__sagemath_objects import * from sage.all__sagemath_environment import * diff --git a/src/sage/cpython/atexit.pyx b/src/sage/cpython/atexit.pyx index 8f833ab1437..c74c1d0308a 100644 --- a/src/sage/cpython/atexit.pyx +++ b/src/sage/cpython/atexit.pyx @@ -148,8 +148,6 @@ from cpython.ref cimport PyObject # Implement "_atexit_callbacks()" for each supported python version cdef extern from *: """ - #if PY_VERSION_HEX >= 0x030a0000 - /********** Python 3.10 **********/ #define Py_BUILD_CORE #undef _PyGC_FINALIZED #include "internal/pycore_interp.h" @@ -163,29 +161,6 @@ cdef extern from *: struct atexit_state state = interp->atexit; return state.callbacks; } - #else - /********** Python < 3.10 **********/ - /* Internal structures defined in the CPython source in - * Modules/atexitmodule.c and subject to (but unlikely to) change. Watch - * https://bugs.python.org/issue32082 for a request to (eventually) - * re-expose more of the atexit module's internals to Python - * typedef struct - */ - typedef struct { - PyObject *func; - PyObject *args; - PyObject *kwargs; - } atexit_callback; - typedef struct { - atexit_callback **atexit_callbacks; - int ncallbacks; - int callback_len; - } atexitmodule_state; - static atexit_callback ** _atexit_callbacks(PyObject *self) { - atexitmodule_state *state = PyModule_GetState(self); - return state->atexit_callbacks; - } - #endif """ ctypedef struct atexit_callback: PyObject* func diff --git a/src/sage/cpython/cython_metaclass.h b/src/sage/cpython/cython_metaclass.h index ecf7f973c3e..f7b6f345fa3 100644 --- a/src/sage/cpython/cython_metaclass.h +++ b/src/sage/cpython/cython_metaclass.h @@ -8,13 +8,6 @@ * http://www.gnu.org/licenses/ *****************************************************************************/ -/* Compatibility for python 3.8, can be removed later */ -#if PY_VERSION_HEX < 0x030900A4 && !defined(Py_SET_TYPE) -static inline void _Py_SET_TYPE(PyObject *ob, PyTypeObject *type) -{ ob->ob_type = type; } -#define Py_SET_TYPE(ob, type) _Py_SET_TYPE((PyObject*)(ob), type) -#endif - /* Tuple (None, None, None), initialized as needed */ static PyObject* NoneNoneNone; @@ -52,7 +45,6 @@ static CYTHON_INLINE int Sage_PyType_Ready(PyTypeObject* t) if (r < 0) return r; -#if PY_VERSION_HEX >= 0x03050000 // Cython 3 sets Py_TPFLAGS_HEAPTYPE before calling PyType_Ready, // and resets just after the call. We need to reset it earlier, // since otherwise the call to metaclass.__init__ below may have @@ -60,7 +52,6 @@ static CYTHON_INLINE int Sage_PyType_Ready(PyTypeObject* t) // See also: // https://github.com/cython/cython/issues/3603 t->tp_flags &= ~Py_TPFLAGS_HEAPTYPE; -#endif /* Set or get metaclass (the type of t) */ PyTypeObject* metaclass; diff --git a/src/sage/cpython/debug.pyx b/src/sage/cpython/debug.pyx index f4e0a44046f..a11dfc085f3 100644 --- a/src/sage/cpython/debug.pyx +++ b/src/sage/cpython/debug.pyx @@ -19,9 +19,6 @@ cdef extern from "Python.h": # Helper to get a pointer to an object's __dict__ slot, if any PyObject** _PyObject_GetDictPtr(obj) -cdef extern from "debugimpl.c": - void _type_debug(PyTypeObject*) - from sage.cpython.getattr cimport AttributeErrorMessage @@ -303,5 +300,3 @@ def type_debug(cls): """ if not isinstance(cls, type): raise TypeError(f"{cls!r} is not a type") - - _type_debug(cls) diff --git a/src/sage/cpython/debugimpl.c b/src/sage/cpython/debugimpl.c deleted file mode 100644 index 176e93900c2..00000000000 --- a/src/sage/cpython/debugimpl.c +++ /dev/null @@ -1,348 +0,0 @@ -#include - -#if PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 9 - -static void _type_debug(PyTypeObject* tp) -{ - printf("Not implemented for CPython >= 3.9\n"); -} - -#else - -#define HAVE_WEAKREFS(tp) (1) -#define HAVE_CLASS(tp) (1) -#define HAVE_ITER(tp) (1) -#define HAVE_RICHCOMPARE(tp) (1) -#define HAVE_INPLACEOPS(tp) (1) -#define HAVE_SEQUENCE_IN(tp) (1) -#define HAVE_GETCHARBUFFER(tp) (1) -#define HAVE_NEW_DIVISION(tp) (1) -#define HAVE_INDEX(tp) (1) -#define HAVE_NEWBUFFER(tp) (1) -#define HAVE_FINALIZE(tp) (tp->tp_flags & Py_TPFLAGS_HAVE_FINALIZE) - -static void print_object(void* pyobj) -{ - if (pyobj == NULL) - { - printf("NULL\n"); - return; - } - - PyObject* obj = (PyObject*)pyobj; - - if (PyTuple_Check(obj)) - { - printf("%s:\n", Py_TYPE(obj)->tp_name); - - Py_ssize_t i, n = PyTuple_GET_SIZE(obj); - for (i = 0; i < n; i++) - { - printf(" "); - PyObject_Print(PyTuple_GET_ITEM(obj, i), stdout, 0); - printf("\n"); - } - } - else if (PyDict_Check(obj)) - { - printf("%s:\n", Py_TYPE(obj)->tp_name); - - Py_ssize_t pos = 0; - PyObject* key; - PyObject* value; - while (PyDict_Next(obj, &pos, &key, &value)) - { - printf(" "); - PyObject_Print(key, stdout, 0); - printf(": "); - PyObject_Print(value, stdout, 0); - printf("\n"); - } - } - else - { - PyObject_Print(obj, stdout, 0); - printf("\n"); - } -} - - -#define pointer_check_constant(ptr, val) \ - if (ptr == (void*)(val)) \ - { \ - special = 0; \ - printf(#val "\n"); \ - } - -#define subattr_pointer_value(sub, attr) \ - special = 1; \ - pointer_check_constant(tp->attr, NULL) \ - else pointer_check_constant(tp->attr, PyType_GenericAlloc) \ - else pointer_check_constant(tp->attr, PyType_GenericNew) \ - else pointer_check_constant(tp->attr, PyObject_Del) \ - else pointer_check_constant(tp->attr, PyObject_GC_Del) \ - else pointer_check_constant(tp->attr, PyObject_GenericGetAttr) \ - else pointer_check_constant(tp->attr, PyObject_GenericSetAttr) \ - else pointer_check_constant(tp->attr, _Py_HashPointer) \ - else pointer_check_constant(tp->attr, PyObject_HashNotImplemented) \ - else pointer_check_constant(tp->attr, subtype_traverse) \ - else pointer_check_constant(tp->attr, subtype_clear) \ - else pointer_check_constant(tp->attr, subtype_dealloc) \ - else \ - { \ - PyObject* mro = tp->tp_mro; \ - PyTypeObject* subtp; \ - Py_ssize_t i, n = PyTuple_GET_SIZE(mro); \ - for (i = n-1; i >= 0; i--) \ - { \ - subtp = (PyTypeObject*)PyTuple_GET_ITEM(mro, i); \ - if (subtp != tp && PyType_Check(subtp)) \ - { \ - if (subtp->sub && tp->attr == subtp->attr) \ - { \ - special = 0; \ - printf("== %s\n", subtp->tp_name); \ - break; \ - } \ - } \ - } \ - } \ - if (special) printf("%p\n", tp->attr); - -#define attr_pointer_value(attr) subattr_pointer_value(tp_name, attr); - -#define attr_pointer(attr) \ - printf(" " #attr ": "); \ - attr_pointer_value(attr); -#define attr_pointer_meth(attr, method) \ - printf(" " #attr " (" method "): "); \ - attr_pointer_value(attr); -#define subattr_pointer(sub, attr) \ - printf(" " #attr ": "); \ - subattr_pointer_value(sub, sub->attr); -#define subattr_pointer_meth(sub, attr, method) \ - printf(" " #attr " (" method "): "); \ - subattr_pointer_value(sub, sub->attr); - -#define attr_object(attr) \ - printf(" " #attr ": "); \ - print_object(tp->attr); -#define attr_object_meth(attr, method) \ - printf(" " #attr " (" method "): "); \ - print_object(tp->attr); - -#define attr_flag(flag) \ - if (tp->tp_flags & Py_TPFLAGS_ ## flag) \ - printf(" " #flag "\n"); - - -static void _type_debug(PyTypeObject* tp) -{ - int special; - - PyObject_Print((PyObject*)tp, stdout, 0); - printf(" (%p)\n", tp); - printf(" ob_refcnt: %ld\n", (long)Py_REFCNT(tp)); - printf(" ob_type: "); print_object(Py_TYPE(tp)); - printf(" tp_name: %s\n", tp->tp_name); - printf(" tp_basicsize: %ld\n", (long)tp->tp_basicsize); - printf(" tp_itemsize: %ld\n", (long)tp->tp_itemsize); - printf(" tp_dictoffset: %ld\n", (long)tp->tp_dictoffset); - if HAVE_WEAKREFS(tp) - { - printf(" tp_weaklistoffset: %ld\n", (long)tp->tp_weaklistoffset); - } - - if HAVE_CLASS(tp) - { - attr_object_meth(tp_base, "__base__"); - attr_object_meth(tp_bases, "__bases__"); - attr_object_meth(tp_mro, "__mro__"); - attr_object_meth(tp_dict, "__dict__"); - } - - attr_pointer(tp_alloc); - attr_pointer_meth(tp_new, "__new__"); - attr_pointer_meth(tp_init, "__init__"); - attr_pointer_meth(tp_dealloc, "__dealloc__"); - if (tp->tp_flags & Py_TPFLAGS_HEAPTYPE) - { - attr_pointer_meth(tp_del, "__del__"); - } - #if PY_MAJOR_VERSION >= 3 - if HAVE_FINALIZE(tp) - { - attr_pointer_meth(tp_finalize, "__del__"); - } - #endif - attr_pointer(tp_free); - - attr_pointer_meth(tp_repr, "__repr__"); - attr_pointer(tp_print); - attr_pointer_meth(tp_hash, "__hash__"); - attr_pointer_meth(tp_call, "__call__"); - attr_pointer_meth(tp_str, "__str__"); - #if PY_MAJOR_VERSION <= 2 - attr_pointer_meth(tp_compare, "cmp"); - #endif - attr_pointer_meth(tp_richcompare, "__richcmp__"); - attr_pointer_meth(tp_getattr, "__getattribute__"); - attr_pointer_meth(tp_setattr, "__setattribute__"); - attr_pointer_meth(tp_getattro, "__getattribute__"); - attr_pointer_meth(tp_setattro, "__setattribute__"); - if HAVE_ITER(tp) - { - attr_pointer_meth(tp_iter, "__iter__"); - attr_pointer_meth(tp_iternext, "__next__"); - } - if HAVE_CLASS(tp) - { - attr_pointer_meth(tp_descr_get, "__get__"); - attr_pointer_meth(tp_descr_set, "__set__"); - - attr_object(tp_cache); - attr_object(tp_weaklist); - } - if HAVE_RICHCOMPARE(tp) - { - attr_pointer(tp_traverse); - attr_pointer(tp_clear); - } - if HAVE_CLASS(tp) - { - attr_pointer(tp_is_gc); - } - - attr_pointer(tp_as_number); - if (special) - { - subattr_pointer_meth(tp_as_number, nb_add, "__add__"); - subattr_pointer_meth(tp_as_number, nb_subtract, "__sub__"); - subattr_pointer_meth(tp_as_number, nb_multiply, "__mul__"); - #if PY_MAJOR_VERSION <= 2 - subattr_pointer_meth(tp_as_number, nb_divide, "__div__"); - #endif - if HAVE_NEW_DIVISION(tp) - { - subattr_pointer_meth(tp_as_number, nb_floor_divide, "__floordiv__"); - subattr_pointer_meth(tp_as_number, nb_true_divide, "__truediv__"); - } - subattr_pointer_meth(tp_as_number, nb_remainder, "__mod__"); - subattr_pointer_meth(tp_as_number, nb_divmod, "__divmod__"); - subattr_pointer_meth(tp_as_number, nb_power, "__pow__"); - subattr_pointer_meth(tp_as_number, nb_negative, "__neg__"); - subattr_pointer_meth(tp_as_number, nb_positive, "__pos__"); - subattr_pointer_meth(tp_as_number, nb_absolute, "__abs__"); - subattr_pointer_meth(tp_as_number, nb_bool, "__bool__"); - subattr_pointer_meth(tp_as_number, nb_invert, "__invert__"); - subattr_pointer_meth(tp_as_number, nb_lshift, "__lshift__"); - subattr_pointer_meth(tp_as_number, nb_rshift, "__rshift__"); - subattr_pointer_meth(tp_as_number, nb_and, "__and__"); - subattr_pointer_meth(tp_as_number, nb_or, "__or__"); - subattr_pointer_meth(tp_as_number, nb_xor, "__xor__"); - subattr_pointer_meth(tp_as_number, nb_int, "__int__"); - #if PY_MAJOR_VERSION <= 2 - subattr_pointer_meth(tp_as_number, nb_long, "__long__"); - #endif - if HAVE_INDEX(tp) - { - subattr_pointer_meth(tp_as_number, nb_index, "__index__"); - } - subattr_pointer_meth(tp_as_number, nb_float, "__float__"); - #if PY_MAJOR_VERSION <= 2 - subattr_pointer_meth(tp_as_number, nb_oct, "__oct__"); - subattr_pointer_meth(tp_as_number, nb_hex, "__hex__"); - subattr_pointer(tp_as_number, nb_coerce); - #endif - - if HAVE_INPLACEOPS(tp) - { - subattr_pointer_meth(tp_as_number, nb_inplace_add, "__iadd__"); - subattr_pointer_meth(tp_as_number, nb_inplace_subtract, "__isub__"); - subattr_pointer_meth(tp_as_number, nb_inplace_multiply, "__imul__"); - #if PY_MAJOR_VERSION <= 2 - subattr_pointer_meth(tp_as_number, nb_inplace_divide, "__idiv__"); - #endif - if HAVE_NEW_DIVISION(tp) - { - subattr_pointer_meth(tp_as_number, nb_inplace_floor_divide, "__ifloordiv__"); - subattr_pointer_meth(tp_as_number, nb_inplace_true_divide, "__itruediv__"); - } - subattr_pointer_meth(tp_as_number, nb_inplace_remainder, "__imod__"); - subattr_pointer_meth(tp_as_number, nb_inplace_power, "__ipow__"); - subattr_pointer_meth(tp_as_number, nb_inplace_lshift, "__ilshift__"); - subattr_pointer_meth(tp_as_number, nb_inplace_rshift, "__irshift__"); - subattr_pointer_meth(tp_as_number, nb_inplace_and, "__iand__"); - subattr_pointer_meth(tp_as_number, nb_inplace_or, "__ior__"); - subattr_pointer_meth(tp_as_number, nb_inplace_xor, "__ixor__"); - } - } - - attr_pointer(tp_as_sequence); - if (special) - { - subattr_pointer_meth(tp_as_sequence, sq_length, "__len__"); - subattr_pointer_meth(tp_as_sequence, sq_concat, "__add__"); - if HAVE_INPLACEOPS(tp) - { - subattr_pointer_meth(tp_as_sequence, sq_inplace_concat, "__iadd__"); - } - subattr_pointer_meth(tp_as_sequence, sq_repeat, "__mul__"); - if HAVE_INPLACEOPS(tp) - { - subattr_pointer_meth(tp_as_sequence, sq_inplace_repeat, "__imul__"); - } - subattr_pointer_meth(tp_as_sequence, sq_item, "__getitem__"); - subattr_pointer_meth(tp_as_sequence, sq_ass_item, "__setitem__"); - if HAVE_SEQUENCE_IN(tp) - { - subattr_pointer_meth(tp_as_sequence, sq_contains, "__contains__"); - } - } - - attr_pointer(tp_as_mapping); - if (special) - { - subattr_pointer_meth(tp_as_mapping, mp_length, "__len__"); - subattr_pointer_meth(tp_as_mapping, mp_subscript, "__getitem__"); - subattr_pointer_meth(tp_as_mapping, mp_ass_subscript, "__setitem__"); - } - - attr_pointer(tp_as_buffer); - if (special) - { - #if PY_MAJOR_VERSION <= 2 - subattr_pointer(tp_as_buffer, bf_getreadbuffer); - subattr_pointer(tp_as_buffer, bf_getwritebuffer); - subattr_pointer(tp_as_buffer, bf_getsegcount); - if HAVE_GETCHARBUFFER(tp) - { - subattr_pointer(tp_as_buffer, bf_getcharbuffer); - } - #endif - if HAVE_NEWBUFFER(tp) - { - subattr_pointer_meth(tp_as_buffer, bf_getbuffer, "__getbuffer__"); - subattr_pointer_meth(tp_as_buffer, bf_releasebuffer, "__releasebuffer__"); - } - } - - printf(" tp_flags:\n"); - attr_flag(HEAPTYPE); - attr_flag(BASETYPE); - attr_flag(READY); - attr_flag(READYING); - attr_flag(HAVE_GC); - #if PY_MAJOR_VERSION <= 2 - attr_flag(CHECKTYPES); - #endif - attr_flag(HAVE_VERSION_TAG); - attr_flag(VALID_VERSION_TAG); - attr_flag(IS_ABSTRACT); - if (tp->tp_flags & Py_TPFLAGS_HAVE_VERSION_TAG) - { - printf(" tp_version_tag: %lu\n", (unsigned long)tp->tp_version_tag); - } -} - -#endif diff --git a/src/sage/cpython/pycore_long.h b/src/sage/cpython/pycore_long.h index 99561f1ba96..bbe9d3964bd 100644 --- a/src/sage/cpython/pycore_long.h +++ b/src/sage/cpython/pycore_long.h @@ -2,12 +2,14 @@ #include #if PY_VERSION_HEX >= 0x030C00A5 +// For Python 3.12 compatibility #define ob_digit(o) (((PyLongObject*)o)->long_value.ob_digit) #else #define ob_digit(o) (((PyLongObject*)o)->ob_digit) #endif #if PY_VERSION_HEX >= 0x030C00A7 +// For Python 3.12 compatibility // taken from cpython:Include/internal/pycore_long.h @ 3.12 /* Long value tag bits: @@ -87,12 +89,7 @@ _PyLong_DigitCount(const PyLongObject *op) static inline void _PyLong_SetSignAndDigitCount(PyLongObject *op, int sign, Py_ssize_t size) { -#if (PY_MAJOR_VERSION == 3) && (PY_MINOR_VERSION < 9) -// The function Py_SET_SIZE is defined starting with python 3.9. - Py_SIZE(op) = size; -#else Py_SET_SIZE(op, sign < 0 ? -size : size); -#endif } #endif diff --git a/src/sage/doctest/forker.py b/src/sage/doctest/forker.py index bf6d49906de..f3d1369be5a 100644 --- a/src/sage/doctest/forker.py +++ b/src/sage/doctest/forker.py @@ -1082,7 +1082,7 @@ def compile_and_execute(self, example, compiler, globs): False sage: doctests, extras = FDS.create_doctests(globs) sage: ex0 = doctests[0].examples[0] - sage: flags = 32768 if sys.version_info.minor < 8 else 524288 + sage: flags = 524288 sage: def compiler(ex): ....: return compile(ex.source, '', ....: 'single', flags, 1) diff --git a/src/sage/libs/gmp/pylong.pyx b/src/sage/libs/gmp/pylong.pyx index 41587396120..a6049232443 100644 --- a/src/sage/libs/gmp/pylong.pyx +++ b/src/sage/libs/gmp/pylong.pyx @@ -32,14 +32,6 @@ from sage.cpython.pycore_long cimport (ob_digit, _PyLong_IsNegative, from sage.libs.gmp.mpz cimport * cdef extern from *: - """ - /* Compatibility for python 3.8, can be removed later */ - #if PY_VERSION_HEX < 0x030900A4 && !defined(Py_SET_SIZE) - static inline void _Py_SET_SIZE(PyVarObject *ob, Py_ssize_t size) - { ob->ob_size = size; } - #define Py_SET_SIZE(ob, size) _Py_SET_SIZE((PyVarObject*)(ob), size) - #endif - """ void Py_SET_SIZE(object, Py_ssize_t) int hash_bits """ #ifdef _PyHASH_BITS diff --git a/src/sage/misc/fpickle.pyx b/src/sage/misc/fpickle.pyx index 7a45ecc4f75..2c6a336c328 100644 --- a/src/sage/misc/fpickle.pyx +++ b/src/sage/misc/fpickle.pyx @@ -44,18 +44,13 @@ def reduce_code(co): raise ValueError("Cannot pickle code objects from closures") co_args = (co.co_argcount,) - if sys.version_info.minor >= 8: - co_args += (co.co_posonlyargcount,) + co_args += (co.co_posonlyargcount,) co_args += (co.co_kwonlyargcount, co.co_nlocals, co.co_stacksize, co.co_flags, co.co_code, co.co_consts, co.co_names, co.co_varnames, co.co_filename, co.co_name) - if sys.version_info.minor >= 11: - co_args += (co.co_qualname, co.co_firstlineno, - co.co_linetable, co.co_exceptiontable) - else: - co_args += (co.co_firstlineno, co.co_lnotab) - + co_args += (co.co_qualname, co.co_firstlineno, + co.co_linetable, co.co_exceptiontable) return (code_ctor, co_args) diff --git a/src/sage/misc/inherit_comparison_impl.c b/src/sage/misc/inherit_comparison_impl.c index f12dc2a6976..2854beab87c 100644 --- a/src/sage/misc/inherit_comparison_impl.c +++ b/src/sage/misc/inherit_comparison_impl.c @@ -8,14 +8,8 @@ static CYTHON_INLINE void inherit_comparison(PyTypeObject* dst, const PyTypeObject* src) { /* Do nothing if "dst" already has comparison defined */ -#if (PY_MAJOR_VERSION < 3) - if (dst->tp_compare) return; -#endif if (dst->tp_richcompare) return; /* Copy comparison method(s) */ -#if (PY_MAJOR_VERSION < 3) - dst->tp_compare = src->tp_compare; -#endif dst->tp_richcompare = src->tp_richcompare; } diff --git a/src/sage_docbuild/ext/sage_autodoc.py b/src/sage_docbuild/ext/sage_autodoc.py index 94e7d076fa9..8b80350585c 100644 --- a/src/sage_docbuild/ext/sage_autodoc.py +++ b/src/sage_docbuild/ext/sage_autodoc.py @@ -1969,28 +1969,6 @@ def get_doc(self) -> list[list[str]] | None: if isinstance(self.object, TypeVar): if self.object.__doc__ == TypeVar.__doc__: return [] - # ------------------------------------------------------------------ - # This section is kept for compatibility with python 3.9 - # see https://github.com/sagemath/sage/pull/38549#issuecomment-2327790930 - if sys.version_info[:2] < (3, 10): - if inspect.isNewType(self.object) or isinstance(self.object, TypeVar): - parts = self.modname.strip('.').split('.') - orig_objpath = self.objpath - for i in range(len(parts)): - new_modname = '.'.join(parts[:len(parts) - i]) - new_objpath = parts[len(parts) - i:] + orig_objpath - try: - analyzer = ModuleAnalyzer.for_module(new_modname) - analyzer.analyze() - key = ('', new_objpath[-1]) - comment = list(analyzer.attr_docs.get(key, [])) - if comment: - self.objpath = new_objpath - self.modname = new_modname - return [comment] - except PycodeError: - pass - # ------------------------------------------------------------------ if self.doc_as_attr: # Don't show the docstring of the class when it is an alias. if self.get_variable_comment(): diff --git a/src/sage_setup/command/sage_build_cython.py b/src/sage_setup/command/sage_build_cython.py index 880ce7383e2..2e04bd80f87 100644 --- a/src/sage_setup/command/sage_build_cython.py +++ b/src/sage_setup/command/sage_build_cython.py @@ -104,8 +104,7 @@ def finalize_options(self): ('force', 'force')] # Python 3.5 now has a parallel option as well - if sys.version_info[:2] >= (3, 5): - inherit_opts.append(('parallel', 'parallel')) + inherit_opts.append(('parallel', 'parallel')) self.set_undefined_options('build_ext', *inherit_opts) diff --git a/src/setup.cfg.m4 b/src/setup.cfg.m4 index 969793209c8..5fd27e416f8 100644 --- a/src/setup.cfg.m4 +++ b/src/setup.cfg.m4 @@ -9,7 +9,7 @@ license_files = LICENSE.txt include(`setup_cfg_metadata.m4')dnl' [options] -python_requires = >=3.9, <3.13 +python_requires = >=3.11, <3.13 install_requires = SPKG_INSTALL_REQUIRES_six dnl From build/pkgs/sagelib/dependencies From 41b05093ba7800335131c21f7bd5c95978674380 Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Sat, 4 Jan 2025 00:46:03 +0800 Subject: [PATCH 204/369] Use Python 3.12 in CI --- .github/workflows/build.yml | 6 +++--- .github/workflows/doc-build-pdf.yml | 6 +++--- .github/workflows/doc-build.yml | 6 +++--- .github/workflows/pyright.yml | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 535c2948c39..45b4bf83339 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,7 +15,7 @@ on: platform: description: 'Platform' required: true - default: 'ubuntu-jammy-standard' + default: 'ubuntu-noble-standard' docker_tag: description: 'Docker tag' required: true @@ -68,8 +68,8 @@ concurrency: env: # Adapted from docker.yml - TOX_ENV: "docker-${{ github.event.inputs.platform || 'ubuntu-jammy-standard' }}-incremental" - BUILD_IMAGE: "localhost:5000/${{ github.repository }}/sage-${{ github.event.inputs.platform || 'ubuntu-jammy-standard' }}-with-targets:ci" + TOX_ENV: "docker-${{ github.event.inputs.platform || 'ubuntu-noble-standard' }}-incremental" + BUILD_IMAGE: "localhost:5000/${{ github.repository }}/sage-${{ github.event.inputs.platform || 'ubuntu-noble-standard' }}-with-targets:ci" FROM_DOCKER_REPOSITORY: "ghcr.io/sagemath/sage/" FROM_DOCKER_TARGET: "with-targets" FROM_DOCKER_TAG: ${{ github.event.inputs.docker_tag || 'dev'}} diff --git a/.github/workflows/doc-build-pdf.yml b/.github/workflows/doc-build-pdf.yml index dce25a132c9..f8d30cd32b6 100644 --- a/.github/workflows/doc-build-pdf.yml +++ b/.github/workflows/doc-build-pdf.yml @@ -9,7 +9,7 @@ on: platform: description: 'Platform' required: true - default: 'ubuntu-jammy-standard' + default: 'ubuntu-noble-standard' docker_tag: description: 'Docker tag' required: true @@ -22,8 +22,8 @@ concurrency: env: # Same as in build.yml - TOX_ENV: "docker-${{ github.event.inputs.platform || 'ubuntu-jammy-standard' }}-incremental" - BUILD_IMAGE: "localhost:5000/${{ github.repository }}/sage-${{ github.event.inputs.platform || 'ubuntu-jammy-standard' }}-with-targets:ci" + TOX_ENV: "docker-${{ github.event.inputs.platform || 'ubuntu-noble-standard' }}-incremental" + BUILD_IMAGE: "localhost:5000/${{ github.repository }}/sage-${{ github.event.inputs.platform || 'ubuntu-noble-standard' }}-with-targets:ci" FROM_DOCKER_REPOSITORY: "ghcr.io/sagemath/sage/" FROM_DOCKER_TARGET: "with-targets" FROM_DOCKER_TAG: ${{ github.event.inputs.docker_tag || 'dev'}} diff --git a/.github/workflows/doc-build.yml b/.github/workflows/doc-build.yml index f46317ecda7..3d80d4c30a8 100644 --- a/.github/workflows/doc-build.yml +++ b/.github/workflows/doc-build.yml @@ -20,7 +20,7 @@ on: platform: description: 'Platform' required: true - default: 'ubuntu-jammy-standard' + default: 'ubuntu-noble-standard' docker_tag: description: 'Docker tag' required: true @@ -33,8 +33,8 @@ concurrency: env: # Same as in build.yml - TOX_ENV: "docker-${{ github.event.inputs.platform || 'ubuntu-jammy-standard' }}-incremental" - BUILD_IMAGE: "localhost:5000/${{ github.repository }}/sage-${{ github.event.inputs.platform || 'ubuntu-jammy-standard' }}-with-targets:ci" + TOX_ENV: "docker-${{ github.event.inputs.platform || 'ubuntu-noble-standard' }}-incremental" + BUILD_IMAGE: "localhost:5000/${{ github.repository }}/sage-${{ github.event.inputs.platform || 'ubuntu-noble-standard' }}-with-targets:ci" FROM_DOCKER_REPOSITORY: "ghcr.io/sagemath/sage/" FROM_DOCKER_TARGET: "with-targets" FROM_DOCKER_TAG: ${{ github.event.inputs.docker_tag || 'dev'}} diff --git a/.github/workflows/pyright.yml b/.github/workflows/pyright.yml index eb84117cb3a..21ffe785d23 100644 --- a/.github/workflows/pyright.yml +++ b/.github/workflows/pyright.yml @@ -18,7 +18,7 @@ concurrency: jobs: pyright: runs-on: ubuntu-latest - container: ghcr.io/sagemath/sage/sage-ubuntu-jammy-standard-with-targets:dev + container: ghcr.io/sagemath/sage/sage-ubuntu-noble-standard-with-targets:dev steps: - name: Checkout id: checkout From 28e546beaffdb1a697a26797bed95f237b80239c Mon Sep 17 00:00:00 2001 From: dcoudert Date: Sat, 4 Jan 2025 12:39:21 +0100 Subject: [PATCH 205/369] add parameter immutable to DeBruijn, Kautz and ImaseItoh --- src/sage/graphs/digraph_generators.py | 113 ++++++++++++++++---------- 1 file changed, 69 insertions(+), 44 deletions(-) diff --git a/src/sage/graphs/digraph_generators.py b/src/sage/graphs/digraph_generators.py index b374392163e..8d57586f65c 100644 --- a/src/sage/graphs/digraph_generators.py +++ b/src/sage/graphs/digraph_generators.py @@ -950,7 +950,7 @@ def Circulant(self, n, integers): return G - def DeBruijn(self, k, n, vertices='strings'): + def DeBruijn(self, k, n, vertices='strings', immutable=True): r""" Return the De Bruijn digraph with parameters `k,n`. @@ -977,6 +977,9 @@ def DeBruijn(self, k, n, vertices='strings'): are words over an alphabet (default) or integers (``vertices='string'``) + - ``immutable`` -- boolean (default: ``False``); whether to return + an immutable or mutable digraph. + EXAMPLES: de Bruijn digraph of degree 2 and diameter 2:: @@ -1030,40 +1033,46 @@ def DeBruijn(self, k, n, vertices='strings'): """ from sage.rings.integer import Integer + name = f"De Bruijn digraph (k={k}, n={n})" if vertices == 'strings': from sage.combinat.words.words import Words W = Words(list(range(k)) if isinstance(k, Integer) else k, n) A = Words(list(range(k)) if isinstance(k, Integer) else k, 1) - g = DiGraph(loops=True) if not n: - g.allow_multiple_edges(True) - v = W[0] - vs = v.string_rep() - for a in A: - g.add_edge(vs, vs, a.string_rep()) + multiedges = True + def edges(): + v = W[0] + vs = v.string_rep() + return ((vs, vs, a.string_rep()) for a in A) + else: - for w in W: - ww = w[1:] - ws = w.string_rep() - for a in A: - g.add_edge(ws, (ww * a).string_rep(), a.string_rep()) + multiedges = False + def edges(): + for w in W: + ww = w[1:] + ws = w.string_rep() + yield from ((ws, (ww * a).string_rep(), a.string_rep()) + for a in A) + + return DiGraph(edges(), format='list_of_edges', name=name, + loops=True, multiedges=multiedges, + immutable=immutable) elif vertices == 'integers': d = k if isinstance(k, Integer) else len(list(k)) if not d: - g = DiGraph(loops=True, multiedges=True) - else: - g = digraphs.GeneralizedDeBruijn(d ** n, d) + return DiGraph(loops=True, multiedges=True, name=name, + immutable=immutable) + + return digraphs.GeneralizedDeBruijn(d ** n, d, immutable=immutable, + name=name) else: raise ValueError('unknown type for vertices') - g.name("De Bruijn digraph (k={}, n={})".format(k, n)) - return g - - def GeneralizedDeBruijn(self, n, d): + def GeneralizedDeBruijn(self, n, d, immutable=False, name=None): r""" Return the generalized de Bruijn digraph of order `n` and degree `d`. @@ -1082,6 +1091,12 @@ def GeneralizedDeBruijn(self, n, d): - ``d`` -- integer; degree of the digraph (must be at least one) + - ``immutable`` -- boolean (default: ``False``); whether to return + an immutable or mutable digraph. + + - ``name`` -- string (default: ``None``); when set, the specified name + is used instead of the default one. + .. SEEALSO:: * :meth:`sage.graphs.generic_graph.GenericGraph.is_circulant` -- @@ -1115,15 +1130,15 @@ def GeneralizedDeBruijn(self, n, d): raise ValueError("order must be greater than or equal to one") if d < 1: raise ValueError("degree must be greater than or equal to one") + if name is None: + name = f"Generalized de Bruijn digraph (n={n}, d={d})" - GB = DiGraph(n, loops=True, multiedges=True, - name="Generalized de Bruijn digraph (n={}, d={})".format(n, d)) - for u in range(n): - for a in range(u * d, u * d + d): - GB.add_edge(u, a % n) - return GB + edges = ((u, a % n) for u in range(n) for a in range(u * d, u * d + d)) + return DiGraph([range(n), edges], format='vertices_and_edges', + loops=True, multiedges=True, immutable=immutable, + name=name) - def ImaseItoh(self, n, d): + def ImaseItoh(self, n, d, immutable=False, name=None): r""" Return the Imase-Itoh digraph of order `n` and degree `d`. @@ -1145,6 +1160,12 @@ def ImaseItoh(self, n, d): - ``d`` -- integer; degree of the digraph (must be greater than or equal to one) + - ``immutable`` -- boolean (default: ``False``); whether to return + an immutable or mutable digraph. + + - ``name`` -- string (default: ``None``); when set, the specified name + is used instead of the default one. + EXAMPLES:: sage: II = digraphs.ImaseItoh(8, 2) @@ -1181,15 +1202,15 @@ def ImaseItoh(self, n, d): raise ValueError("order must be greater than or equal to two") if d < 1: raise ValueError("degree must be greater than or equal to one") + if name is None: + name = f"Imase and Itoh digraph (n={n}, d={d})" - II = DiGraph(n, loops=True, multiedges=True, - name="Imase and Itoh digraph (n={}, d={})".format(n, d)) - for u in range(n): - for a in range(-u * d - d, -u * d): - II.add_edge(u, a % n) - return II + edges = ((u, a % n) for u in range(n) for a in range(-u * d - d, -u * d)) + return DiGraph([range(n), edges], format='vertices_and_edges', + loops=True, multiedges=True, immutable=immutable, + name=name) - def Kautz(self, k, D, vertices='strings'): + def Kautz(self, k, D, vertices='strings', immutable=False): r""" Return the Kautz digraph of degree `d` and diameter `D`. @@ -1224,6 +1245,9 @@ def Kautz(self, k, D, vertices='strings'): are words over an alphabet (default) or integers (``vertices='strings'``) + - ``immutable`` -- boolean (default: ``False``); whether to return + an immutable or mutable digraph. + EXAMPLES:: sage: # needs sage.combinat @@ -1298,6 +1322,8 @@ def Kautz(self, k, D, vertices='strings'): from sage.rings.integer import Integer + name = f"Kautz digraph (k={k}, D={D})" + if vertices == 'strings': from sage.combinat.words.words import Words @@ -1315,26 +1341,25 @@ def Kautz(self, k, D, vertices='strings'): V = VV # We now build the set of arcs - G = DiGraph() - for u in V: - us = u.string_rep() - for a in my_alphabet: - if not u.has_suffix(a): - G.add_edge(us, (u[1:] * a).string_rep(), - a.string_rep()) + def edges(): + for u in V: + us = u.string_rep() + yield from ((us, (u[1:] * a).string_rep(), a.string_rep()) + for a in my_alphabet if not u.has_suffix(a)) + + return DiGraph(edges(), format='list_of_edges', + name=name, immutable=immutable) elif vertices == 'integers': d = k if isinstance(k, Integer) else (len(list(k)) - 1) if d < 1: raise ValueError("degree must be greater than or equal to one") - G = digraphs.ImaseItoh((d + 1) * (d ** (D - 1)), d) + return digraphs.ImaseItoh((d + 1) * (d ** (D - 1)), d, + name=name, immutable=immutable) else: raise ValueError('unknown type for vertices') - G.name("Kautz digraph (k={}, D={})".format(k, D)) - return G - def RandomDirectedAcyclicGraph(self, n, p, weight_max=None): r""" Return a random (weighted) directed acyclic graph of order `n`. From 7b296b74973d26c4c35a8826871b6594f5ed68a2 Mon Sep 17 00:00:00 2001 From: dcoudert Date: Sat, 4 Jan 2025 12:57:34 +0100 Subject: [PATCH 206/369] #39266: fix lint issue --- src/sage/graphs/digraph_generators.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/sage/graphs/digraph_generators.py b/src/sage/graphs/digraph_generators.py index 8d57586f65c..fde5107bcb4 100644 --- a/src/sage/graphs/digraph_generators.py +++ b/src/sage/graphs/digraph_generators.py @@ -1042,6 +1042,7 @@ def DeBruijn(self, k, n, vertices='strings', immutable=True): if not n: multiedges = True + def edges(): v = W[0] vs = v.string_rep() @@ -1049,6 +1050,7 @@ def edges(): else: multiedges = False + def edges(): for w in W: ww = w[1:] From 1c63178e1c012acff8006ec281e2b2e4b5da076d Mon Sep 17 00:00:00 2001 From: dcoudert Date: Sat, 4 Jan 2025 14:44:13 +0100 Subject: [PATCH 207/369] #39266: fix default parameter in DeBruijn --- src/sage/graphs/digraph_generators.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/graphs/digraph_generators.py b/src/sage/graphs/digraph_generators.py index fde5107bcb4..b7200af58f8 100644 --- a/src/sage/graphs/digraph_generators.py +++ b/src/sage/graphs/digraph_generators.py @@ -950,7 +950,7 @@ def Circulant(self, n, integers): return G - def DeBruijn(self, k, n, vertices='strings', immutable=True): + def DeBruijn(self, k, n, vertices='strings', immutable=False): r""" Return the De Bruijn digraph with parameters `k,n`. From 886bad49b3bfa707011e67ec3787ad52a4b43d65 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Fri, 17 Jan 2025 18:30:40 +0100 Subject: [PATCH 208/369] rename also set-like species --- src/sage/rings/species.py | 116 ++++++++++++++++++++++++++------------ 1 file changed, 80 insertions(+), 36 deletions(-) diff --git a/src/sage/rings/species.py b/src/sage/rings/species.py index 494e7020331..14d2facb8f3 100644 --- a/src/sage/rings/species.py +++ b/src/sage/rings/species.py @@ -33,6 +33,7 @@ from itertools import accumulate, chain, product +from sage.arith.misc import divisors from sage.categories.graded_algebras_with_basis import GradedAlgebrasWithBasis from sage.categories.modules import Modules from sage.categories.monoids import Monoids @@ -48,9 +49,10 @@ from sage.groups.perm_gps.permgroup import PermutationGroup, PermutationGroup_generic from sage.groups.perm_gps.permgroup_named import SymmetricGroup from sage.libs.gap.libgap import libgap -from sage.misc.cachefunc import cached_method +from sage.misc.cachefunc import cached_method, cached_function from sage.misc.fast_methods import WithEqualityById from sage.misc.inherit_comparison import InheritComparisonClasscallMetaclass +from sage.misc.misc_c import prod from sage.modules.free_module_element import vector from sage.monoids.indexed_free_monoid import (IndexedFreeAbelianMonoid, IndexedFreeAbelianMonoidElement) @@ -297,13 +299,13 @@ def __lt__(self, other): sage: len(P.cover_relations()) 7 sage: sorted(P.cover_relations(), key=str) - [[C_4, {((1,2)(3,4),)}], + [[C_4, E_2(X^2)], + [E_2(E_2), C_4], + [E_2(E_2), Pb_4], + [E_4, E_2(E_2)], [E_4, Eo_4], - [E_4, P_4], [Eo_4, Pb_4], - [P_4, C_4], - [P_4, Pb_4], - [Pb_4, {((1,2)(3,4),)}]] + [Pb_4, E_2(X^2)]] TESTS:: @@ -531,7 +533,7 @@ def _element_constructor_(self, G, pi=None, check=True): def _rename(self, n): r""" - Names for common species. + Give common species of degree `n` their traditional names. EXAMPLES:: @@ -589,6 +591,9 @@ def _rename(self, n): [(i, i+1) for i in range(1, n, 2)]] self(PermutationGroup(gens), pi, check=False).rename(f"Pb_{n}" + sort) + if self._arity == 1: + _ = _atomic_set_like_species(n) + def __contains__(self, x): r""" Return whether ``x`` is in ``self``. @@ -940,11 +945,11 @@ def _element_constructor_(self, G, pi=None, check=True): sage: a = lambda g, x: SetPartition([[g(e) for e in b] for b in x]) sage: X = SetPartitions(4, [2, 2]) sage: M((X, a, 'left'), {0: X.base_set()}) - P_4 + E_2(E_2) sage: X = SetPartitions(8, [4, 2, 2]) sage: M((X, a, 'left'), {0: X.base_set()}, check=False) - E_4*P_4 + E_4*E_2(E_2) TESTS:: @@ -1176,23 +1181,23 @@ def _richcmp_(self, other, op): sage: len(P.cover_relations()) 17 sage: sorted(P.cover_relations(), key=str) - [[C_4, {((1,2)(3,4),)}], + [[C_4, E_2(X^2)], + [E_2(E_2), C_4], + [E_2(E_2), E_2^2], + [E_2(E_2), Pb_4], + [E_2(X^2), X^4], + [E_2^2, E_2(X^2)], [E_2^2, X^2*E_2], - [E_2^2, {((1,2)(3,4),)}], + [E_4, E_2(E_2)], [E_4, Eo_4], - [E_4, P_4], [E_4, X*E_3], [Eo_4, Pb_4], [Eo_4, X*C_3], - [P_4, C_4], - [P_4, E_2^2], - [P_4, Pb_4], - [Pb_4, {((1,2)(3,4),)}], + [Pb_4, E_2(X^2)], [X*C_3, X^4], [X*E_3, X*C_3], [X*E_3, X^2*E_2], - [X^2*E_2, X^4], - [{((1,2)(3,4),)}, X^4]] + [X^2*E_2, X^4]] TESTS:: @@ -1439,7 +1444,7 @@ def __call__(self, *args): sage: X(E2) E_2 sage: E2(E2) - P_4 + E_2(E_2) sage: M = MolecularSpecies(["X","Y"]) sage: X = M(SymmetricGroup(1), {0: [1]}) @@ -1637,17 +1642,17 @@ def tilde(self): sage: P = PolynomialSpecies(QQ, "X") sage: sortkey = lambda x: (len(x[1]), sum(x[1].coefficients()), str(x[0])) sage: n=4; table(sorted([(m, P.monomial(m).tilde()) for m in M.subset(n)], key=sortkey)) - X^4 X^4 - X^2*E_2 2*X^2*E_2 - {((1,2)(3,4),)} 2*{((1,2)(3,4),)} - X*C_3 3*X*C_3 - C_4 4*C_4 - E_2^2 4*E_2^2 - Pb_4 4*Pb_4 - X*E_3 X*E_3 + X^2*E_2 + X*C_3 - Eo_4 Eo_4 + 2*X*C_3 + Pb_4 - P_4 2*P_4 + E_2^2 + Pb_4 + C_4 - E_4 E_4 + E_2^2 + X*C_3 + P_4 + C_4 + X^4 X^4 + E_2(X^2) 2*E_2(X^2) + X^2*E_2 2*X^2*E_2 + X*C_3 3*X*C_3 + C_4 4*C_4 + E_2^2 4*E_2^2 + Pb_4 4*Pb_4 + X*E_3 X*E_3 + X^2*E_2 + X*C_3 + Eo_4 Eo_4 + 2*X*C_3 + Pb_4 + E_2(E_2) 2*E_2(E_2) + E_2^2 + Pb_4 + C_4 + E_4 E_4 + E_2^2 + X*C_3 + E_2(E_2) + C_4 sage: P. = PolynomialSpecies(QQ) sage: E2 = PolynomialSpecies(QQ, "X")(SymmetricGroup(2)) @@ -1864,7 +1869,7 @@ def _compose_with_weighted_singletons(self, names, multiplicities, degrees): sage: C4 = P(CyclicPermutationGroup(4)) sage: C4._compose_with_weighted_singletons(["X"], [-1], [[4]]) - -C_4 + {((1,2)(3,4),)} + -C_4 + E_2(X^2) Exercise (2.5.17) in [BLL1998]_:: @@ -1894,7 +1899,7 @@ def _compose_with_weighted_singletons(self, names, multiplicities, degrees): TESTS:: sage: (C4+E2^2)._compose_with_weighted_singletons(["X"], [-1], [[4]]) - -C_4 + E_2^2 + {((1,2)(3,4),)} - 2*X^2*E_2 + X^4 + -C_4 + E_2^2 + E_2(X^2) - 2*X^2*E_2 + X^4 sage: C4._compose_with_weighted_singletons(["X"], [-1, 0], [[4]]) Traceback (most recent call last): @@ -1967,10 +1972,10 @@ def __call__(self, *args): sage: E2(-X) -E_2 + X^2 sage: E2(X^2) - {((1,2)(3,4),)} + E_2(X^2) sage: E2(X + X^2) - E_2 + X^3 + {((1,2)(3,4),)} + E_2 + X^3 + E_2(X^2) sage: P2 = PolynomialSpecies(QQ, ["X", "Y"]) sage: X = P2(SymmetricGroup(1), {0: [1]}) @@ -2194,7 +2199,7 @@ def _element_constructor_(self, G, pi=None, check=True): sage: X = SetPartitions(4, 2) sage: a = lambda g, x: SetPartition([[g(e) for e in b] for b in x]) sage: P((X, a, 'left'), {0: [1,2,3,4]}) - X*E_3 + P_4 + X*E_3 + E_2(E_2) The species of permutation groups:: @@ -2206,7 +2211,7 @@ def _element_constructor_(self, G, pi=None, check=True): ....: H = S.subgroup(G.conjugate(pi).gens()) ....: return next(K for K in X if K == H) sage: P((X, act), {0: range(1, n+1)}, check=False) - 4*E_4 + 4*P_4 + E_2^2 + 2*X*E_3 + 4*E_4 + 4*E_2(E_2) + E_2^2 + 2*X*E_3 Create a multisort species given an action:: @@ -2505,3 +2510,42 @@ def factor(s, c, d): for s in range(self._arity)) Element = PolynomialSpeciesElement + + +@cached_function +def _atomic_set_like_species(n): + r""" + Return a list of the atomic set like species of degree `n`, + and provide their traditional names. + + INPUT: + + - ``n`` -- positive integer, the degree + + EXAMPLES:: + + sage: from sage.rings.species import _atomic_set_like_species + sage: _atomic_set_like_species(6) + [E_2(E_3), E_2(X*E_2), E_2(X^3), E_3(E_2), E_3(X^2), E_6] + + sage: oeis([len(_atomic_set_like_species(n)) for n in range(1,10)]) # optional - internet + 0: A007650: Number of set-like atomic species of degree n. + """ + M = MolecularSpecies("X") + if n == 1: + return [M(SymmetricGroup(1))] + result = [] + for d in divisors(n): + if d == 1: + continue + E_d = M(SymmetricGroup(d)) + if d == n: + result.append(E_d) + continue + for pi in Partitions(n // d): + for l_F in cartesian_product([_atomic_set_like_species(p) for p in pi]): + G = prod(l_F) + F = E_d(G) + F.support()[0].rename(f"E_{d}({G})") + result.append(F) + return result From 81ded324ba86b26829c7a8bf2c31e71be63f70b5 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Sat, 18 Jan 2025 18:03:18 +0100 Subject: [PATCH 209/369] multisort set-like species --- src/sage/rings/species.py | 46 +++++++++++++++++++++++++++------------ 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/src/sage/rings/species.py b/src/sage/rings/species.py index 14d2facb8f3..e747ec1eee3 100644 --- a/src/sage/rings/species.py +++ b/src/sage/rings/species.py @@ -43,6 +43,7 @@ from sage.combinat.cyclic_sieving_phenomenon import orbit_decomposition from sage.combinat.free_module import CombinatorialFreeModule from sage.combinat.integer_vector import IntegerVectors +from sage.combinat.integer_vector_weighted import WeightedIntegerVectors from sage.combinat.partition import Partitions, _Partitions from sage.combinat.sf.sf import SymmetricFunctions from sage.groups.perm_gps.constructor import PermutationGroupElement @@ -592,7 +593,7 @@ def _rename(self, n): self(PermutationGroup(gens), pi, check=False).rename(f"Pb_{n}" + sort) if self._arity == 1: - _ = _atomic_set_like_species(n) + _ = _atomic_set_like_species(n, self._names) def __contains__(self, x): r""" @@ -2513,7 +2514,7 @@ def factor(s, c, d): @cached_function -def _atomic_set_like_species(n): +def _atomic_set_like_species(n, names): r""" Return a list of the atomic set like species of degree `n`, and provide their traditional names. @@ -2521,31 +2522,48 @@ def _atomic_set_like_species(n): INPUT: - ``n`` -- positive integer, the degree + - ``names`` -- an iterable of strings for the sorts of the + species EXAMPLES:: sage: from sage.rings.species import _atomic_set_like_species - sage: _atomic_set_like_species(6) + sage: _atomic_set_like_species(6, "X") [E_2(E_3), E_2(X*E_2), E_2(X^3), E_3(E_2), E_3(X^2), E_6] - sage: oeis([len(_atomic_set_like_species(n)) for n in range(1,10)]) # optional - internet + sage: l = [len(_atomic_set_like_species(n, "X")) for n in range(12)] + sage: l + [0, 1, 1, 1, 3, 1, 6, 1, 10, 4, 12, 1] + sage: oeis(l) # optional - internet 0: A007650: Number of set-like atomic species of degree n. + + sage: _atomic_set_like_species(4, "U, V") + [E_2(E_2(V)), E_2(E_2(U)), E_2(V^2), E_2(U*V), E_2(U^2), E_4(U), E_4(V)] """ - M = MolecularSpecies("X") + if not n: + return [] + M1 = MolecularSpecies("X") + M = MolecularSpecies(names) if n == 1: - return [M(SymmetricGroup(1))] + return [M(SymmetricGroup(1), {s: [1]}) for s in range(M._arity)] result = [] for d in divisors(n): if d == 1: continue - E_d = M(SymmetricGroup(d)) if d == n: - result.append(E_d) + result.extend(M(SymmetricGroup(n), {s: range(1, n+1)}) + for s in range(M._arity)) continue - for pi in Partitions(n // d): - for l_F in cartesian_product([_atomic_set_like_species(p) for p in pi]): - G = prod(l_F) - F = E_d(G) - F.support()[0].rename(f"E_{d}({G})") - result.append(F) + E_d = M1(SymmetricGroup(d)) + l = [] + w = [] + for degree in range(1, n // d + 1): + a_degree = _atomic_set_like_species(degree, names) + l.extend(a_degree) + w.extend([degree]*len(a_degree)) + for a in WeightedIntegerVectors(n // d, w): + G = prod(F ** e for F, e in zip(l, a)) + F = E_d(G) + F.support()[0].rename(f"E_{d}({G})") + result.append(F) return result From f4bc04ca179f0e290801cd732578fa76e55a6ccd Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Sat, 18 Jan 2025 18:44:44 +0100 Subject: [PATCH 210/369] activate renaming also in the multisort case --- src/sage/rings/species.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/sage/rings/species.py b/src/sage/rings/species.py index e747ec1eee3..810256b42b5 100644 --- a/src/sage/rings/species.py +++ b/src/sage/rings/species.py @@ -592,8 +592,7 @@ def _rename(self, n): [(i, i+1) for i in range(1, n, 2)]] self(PermutationGroup(gens), pi, check=False).rename(f"Pb_{n}" + sort) - if self._arity == 1: - _ = _atomic_set_like_species(n, self._names) + _atomic_set_like_species(n, self._names) def __contains__(self, x): r""" From 5f0537dd54e1ec4dd256e79a859efd45a58aeb1d Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Mon, 20 Jan 2025 09:19:03 +0100 Subject: [PATCH 211/369] fix doctests --- src/sage/rings/species.py | 34 +++++++++++++++------------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/src/sage/rings/species.py b/src/sage/rings/species.py index 810256b42b5..10b4d7a4860 100644 --- a/src/sage/rings/species.py +++ b/src/sage/rings/species.py @@ -453,7 +453,7 @@ def _element_constructor_(self, G, pi=None, check=True): sage: G = PermutationGroup([[(1,3),(5,7)]], domain=[1,3,5,7]) sage: A(G, ([1,3], [5,7])) - {((1,2)(3,4),): ({1, 2}, {3, 4})} + E_2(X*Y) Test that errors are raised on some possible misuses:: @@ -549,9 +549,9 @@ def _rename(self, n): sage: A(CyclicPermutationGroup(4), {1: range(1, 5)}) C_4(Y) sage: A(DihedralGroup(4), {0: range(1, 5)}) - P_4(X) + E_2(E_2(X)) sage: A(DihedralGroup(4), {1: range(1, 5)}) - P_4(Y) + E_2(E_2(Y)) sage: A(AlternatingGroup(4), {0: range(1, 5)}) Eo_4(X) sage: A(AlternatingGroup(4), {1: range(1, 5)}) @@ -722,11 +722,7 @@ def _an_element_(self): sage: A = AtomicSpecies("X, Y") sage: a = A.an_element(); a - {((1,2)(3,4),): ({1, 2}, {3, 4})} - - sage: a.rename("E_2(XY)") - sage: a - E_2(XY) + E_2(X*Y) """ G = PermutationGroup([[(2 * s + 1, 2 * s + 2) for s in range(self._arity)]]) m = {s: [2 * s + 1, 2 * s + 2] for s in range(self._arity)} @@ -850,7 +846,7 @@ class MolecularSpecies(IndexedFreeAbelianMonoid): sage: M = MolecularSpecies("X,Y") sage: G = PermutationGroup([[(1,2),(3,4)], [(5,6)]]) sage: M(G, {0: [5,6], 1: [1,2,3,4]}) - E_2(X)*{((1,2)(3,4),): ({}, {1, 2, 3, 4})} + E_2(X)*E_2(Y^2) """ @staticmethod def __classcall__(cls, names): @@ -961,7 +957,7 @@ def _element_constructor_(self, G, pi=None, check=True): sage: G = PermutationGroup([[(2,3),(4,5)]], domain=[2,3,4,5]) sage: M(G, {0: [2, 3], 1: [4, 5]}) - E_2(XY) + E_2(X*Y) sage: X = SetPartitions(4, 2) sage: a = lambda g, x: SetPartition([[g(e) for e in b] for b in x]) @@ -972,7 +968,7 @@ def _element_constructor_(self, G, pi=None, check=True): sage: G = PermutationGroup([[(1,3),(5,7)]], domain=[1,3,5,7]) sage: M(G, ([1,3], [5,7])) - E_2(XY) + E_2(X*Y) sage: G = PermutationGroup([[(1,2), (3,4,5,6)]]) sage: a = M(G, {0:[1,2], 1:[3,4,5,6]}) @@ -1254,7 +1250,7 @@ def permutation_group(self): sage: M = MolecularSpecies("X,Y") sage: G = PermutationGroup([[(1,2),(3,4)], [(5,6)]]) sage: A = M(G, {0: [5,6], 1: [1,2,3,4]}); A - E_2(X)*{((1,2)(3,4),): ({}, {1, 2, 3, 4})} + E_2(X)*E_2(Y^2) sage: A.permutation_group() (Permutation Group with generators [(3,4)(5,6), (1,2)], (frozenset({1, 2}), frozenset({3, 4, 5, 6}))) @@ -1279,7 +1275,7 @@ def permutation_group(self): sage: G = PermutationGroup([[(1,2),(3,4)], [(5,6)]]) sage: A = M(G, {0: [5,6], 1: [1,2,3,4]}) sage: A * B - E_2(X)*C_3(X)*{((1,2)(3,4),): ({}, {1, 2, 3, 4})} + E_2(X)*C_3(X)*E_2(Y^2) sage: (A*B).permutation_group() (Permutation Group with generators [(6,7)(8,9), (3,4,5), (1,2)], (frozenset({1, 2, 3, 4, 5}), frozenset({6, 7, 8, 9}))) @@ -1657,7 +1653,7 @@ def tilde(self): sage: P. = PolynomialSpecies(QQ) sage: E2 = PolynomialSpecies(QQ, "X")(SymmetricGroup(2)) sage: E2(X*Y).tilde() - 2*E_2(XY) + 2*E_2(X*Y) """ P = self.parent() M = P._indices @@ -1781,7 +1777,7 @@ def _compose_with_singletons(self, names, args): sage: P = PolynomialSpecies(ZZ, "X") sage: C4 = P(CyclicPermutationGroup(4)) sage: C4._compose_with_singletons("X, Y", [[2, 2]]) - E_2(XY) + X^2*Y^2 + E_2(X*Y) + X^2*Y^2 sage: P = PolynomialSpecies(ZZ, ["X", "Y"]) sage: F = P(PermutationGroup([[(1,2,3), (4,5,6)]]), {0: [1,2,3], 1: [4,5,6]}) @@ -1874,7 +1870,7 @@ def _compose_with_weighted_singletons(self, names, multiplicities, degrees): Exercise (2.5.17) in [BLL1998]_:: sage: C4._compose_with_weighted_singletons(["X", "Y"], [1, 1], [[2, 2]]) - E_2(XY) + X^2*Y^2 + E_2(X*Y) + X^2*Y^2 sage: C4._compose_with_weighted_singletons(["X", "Y"], [1, 1], [[3, 1]]) X^3*Y sage: C4._compose_with_weighted_singletons(["X", "Y"], [1, 1], [[4, 0]]) @@ -1883,7 +1879,7 @@ def _compose_with_weighted_singletons(self, names, multiplicities, degrees): Equation (4.60) in [ALL2002]_:: sage: C4._compose_with_weighted_singletons(["X", "Y"], [1, -1], [[2, 2]]) - -E_2(XY) + 2*X^2*Y^2 + -E_2(X*Y) + 2*X^2*Y^2 A bivariate example:: @@ -1984,7 +1980,7 @@ def __call__(self, *args): E_2(X) + X*Y + E_2(Y) sage: E2(X*Y)(E2(X), E2(Y)) - {((7,8), (5,6), (3,4), (1,2), (1,3)(2,4)(5,7)(6,8)): ({1, 2, 3, 4}, {5, 6, 7, 8})} + E_2(E_2(X)*E_2(Y)) sage: R. = QQ[] sage: P = PolynomialSpecies(R, ["X"]) @@ -2193,7 +2189,7 @@ def _element_constructor_(self, G, pi=None, check=True): sage: X = SetPartitions(4, 2) sage: a = lambda g, x: SetPartition([[g(e) for e in b] for b in x]) sage: P((X, a, 'left'), {0: [1,2], 1: [3,4]}) - E_2(X)*E_2(Y) + X^2*E_2(Y) + E_2(XY) + Y^2*E_2(X) + E_2(X)*E_2(Y) + X^2*E_2(Y) + E_2(X*Y) + Y^2*E_2(X) sage: P = PolynomialSpecies(ZZ, ["X"]) sage: X = SetPartitions(4, 2) From 20ac0fb2c48731afab4870304c4bd31643a672c6 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Wed, 22 Jan 2025 13:41:06 +0700 Subject: [PATCH 212/369] Allow coercion from Frac(QQ[x]) to LaurentSeriesRing(QQ) --- src/sage/rings/laurent_series_ring.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/sage/rings/laurent_series_ring.py b/src/sage/rings/laurent_series_ring.py index 1feba6e675b..6cb56980190 100644 --- a/src/sage/rings/laurent_series_ring.py +++ b/src/sage/rings/laurent_series_ring.py @@ -649,6 +649,8 @@ def _coerce_map_from_(self, P): True sage: S.has_coerce_map_from(PolynomialRing(ZZ, 't')) True + sage: S.has_coerce_map_from(Frac(PolynomialRing(ZZ, 't'))) + False sage: S.has_coerce_map_from(LaurentPolynomialRing(ZZ, 't')) True sage: S.has_coerce_map_from(PowerSeriesRing(ZZ, 't')) @@ -671,6 +673,12 @@ def _coerce_map_from_(self, P): sage: S.has_coerce_map_from(LaurentSeriesRing(QQ, 't')) False + sage: T. = LaurentSeriesRing(QQ) + sage: T.has_coerce_map_from(QQ['t']) + True + sage: T.has_coerce_map_from(Frac(QQ['t'])) + True + sage: R. = LaurentSeriesRing(QQ['x']) sage: R.has_coerce_map_from(QQ[['t']]) True @@ -687,7 +695,10 @@ def _coerce_map_from_(self, P): sage: R.has_coerce_map_from(LazyLaurentSeriesRing(ZZ['x'], 't')) True """ + from sage.rings.fraction_field import FractionField_generic A = self.base_ring() + if isinstance(P, FractionField_generic) and A.is_field(): + return self.has_coerce_map_from(P.base()) if (isinstance(P, (LaurentSeriesRing, LazyLaurentSeriesRing, LaurentPolynomialRing_generic, PowerSeriesRing_generic, LazyPowerSeriesRing, From d11d805e4d36256507a7ebe78f11acd8c86e3646 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Wed, 22 Jan 2025 13:47:30 +0700 Subject: [PATCH 213/369] Add documentation to LaurentSeries point to accessors --- src/sage/rings/laurent_series_ring_element.pyx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/sage/rings/laurent_series_ring_element.pyx b/src/sage/rings/laurent_series_ring_element.pyx index 405752c6191..85d716b8c7a 100644 --- a/src/sage/rings/laurent_series_ring_element.pyx +++ b/src/sage/rings/laurent_series_ring_element.pyx @@ -97,9 +97,11 @@ cdef class LaurentSeries(AlgebraElement): - ``parent`` -- a Laurent series ring - ``f`` -- a power series (or something can be coerced - to one); note that ``f`` does *not* have to be a unit + to one); note that ``f`` does *not* have to be a unit. + This can be accessed through :meth:`valuation_zero_part`. - - ``n`` -- (default: 0) integer + - ``n`` -- (default: 0) integer. This can be accessed + through :meth:`valuation`. """ def __init__(self, parent, f, n=0): r""" From 023415a4405361441b9fd7f9a36dfec03e243ad2 Mon Sep 17 00:00:00 2001 From: Antonio Rojas Date: Sun, 26 Jan 2025 15:49:37 +0100 Subject: [PATCH 214/369] Allow system python 3.13 Fixes archlinux CI --- build/pkgs/python3/spkg-configure.m4 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build/pkgs/python3/spkg-configure.m4 b/build/pkgs/python3/spkg-configure.m4 index c6938ea7080..a9433207975 100644 --- a/build/pkgs/python3/spkg-configure.m4 +++ b/build/pkgs/python3/spkg-configure.m4 @@ -1,8 +1,8 @@ SAGE_SPKG_CONFIGURE([python3], [ m4_pushdef([MIN_VERSION], [3.9.0]) m4_pushdef([MIN_NONDEPRECATED_VERSION], [3.9.0]) - m4_pushdef([LT_STABLE_VERSION], [3.13.0]) - m4_pushdef([LT_VERSION], [3.13.0]) + m4_pushdef([LT_STABLE_VERSION], [3.14.0]) + m4_pushdef([LT_VERSION], [3.14.0]) AC_ARG_WITH([python], [AS_HELP_STRING([--with-python=PYTHON3], [Python 3 executable to use for the Sage venv; default: python3])]) From aca1905dd8b735deec586ad980b7b21a2f284b47 Mon Sep 17 00:00:00 2001 From: Antonio Rojas Date: Sun, 26 Jan 2025 18:38:21 +0100 Subject: [PATCH 215/369] Allow Python 3.13 for sage-setup --- pkgs/sage-setup/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/sage-setup/pyproject.toml b/pkgs/sage-setup/pyproject.toml index a8cac2c6a64..820da4fbbeb 100644 --- a/pkgs/sage-setup/pyproject.toml +++ b/pkgs/sage-setup/pyproject.toml @@ -23,7 +23,7 @@ classifiers = [ "Topic :: Scientific/Engineering :: Mathematics", ] urls = {Homepage = "https://www.sagemath.org"} -requires-python = ">=3.9, <3.13" +requires-python = ">=3.9, <3.14" dependencies = [] dynamic = ["version"] From d332d958307ffc5f4df6bba10a9ab45106e08f4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Mon, 27 Jan 2025 21:48:19 +0100 Subject: [PATCH 216/369] details in braid groups --- src/sage/groups/braid.py | 103 ++++++++++++++++++++++----------------- 1 file changed, 58 insertions(+), 45 deletions(-) diff --git a/src/sage/groups/braid.py b/src/sage/groups/braid.py index ce0375b121f..d67b4c8b8e5 100644 --- a/src/sage/groups/braid.py +++ b/src/sage/groups/braid.py @@ -4,7 +4,8 @@ Braid groups are implemented as a particular case of finitely presented groups, but with a lot of specific methods for braids. -A braid group can be created by giving the number of strands, and the name of the generators:: +A braid group can be created by giving the number of strands, and the name +of the generators:: sage: BraidGroup(3) Braid group on 3 strands @@ -15,8 +16,8 @@ sage: BraidGroup(3,'a,b').gens() (a, b) -The elements can be created by operating with the generators, or by passing a list -with the indices of the letters to the group:: +The elements can be created by operating with the generators, or by passing +a list with the indices of the letters to the group:: sage: B. = BraidGroup(4) sage: s0*s1*s0 @@ -81,7 +82,8 @@ GroupMorphismWithGensImages, ) from sage.groups.free_group import FreeGroup, is_FreeGroup -from sage.groups.perm_gps.permgroup_named import SymmetricGroup, SymmetricGroupElement +from sage.groups.perm_gps.permgroup_named import (SymmetricGroup, + SymmetricGroupElement) from sage.libs.gap.libgap import libgap from sage.matrix.constructor import identity_matrix, matrix from sage.misc.cachefunc import cached_method @@ -96,10 +98,11 @@ from sage.structure.richcmp import rich_to_bool, richcmp lazy_import('sage.libs.braiding', - ['leftnormalform', 'rightnormalform', 'centralizer', 'supersummitset', 'greatestcommondivisor', + ['leftnormalform', 'rightnormalform', 'centralizer', + 'supersummitset', 'greatestcommondivisor', 'leastcommonmultiple', 'conjugatingbraid', 'ultrasummitset', 'thurston_type', 'rigidity', 'sliding_circuits', 'send_to_sss', - 'send_to_uss', 'send_to_sc', 'trajectory', 'cyclic_slidings' ], + 'send_to_uss', 'send_to_sc', 'trajectory', 'cyclic_slidings'], feature=sage__libs__braiding()) lazy_import('sage.knots.knot', 'Knot') @@ -466,7 +469,8 @@ def permutation(self, W=None): """ return self.coxeter_group_element(W) - def plot(self, color='rainbow', orientation='bottom-top', gap=0.05, aspect_ratio=1, axes=False, **kwds): + def plot(self, color='rainbow', orientation='bottom-top', gap=0.05, + aspect_ratio=1, axes=False, **kwds): """ Plot the braid. @@ -598,7 +602,7 @@ def plot(self, color='rainbow', orientation='bottom-top', gap=0.05, aspect_ratio def plot3d(self, color='rainbow'): """ - Plots the braid in 3d. + Plot the braid in 3d. The following option is available: @@ -796,6 +800,7 @@ def links_gould_matrix(self, symbolics=False): r""" Return the representation matrix of ``self`` according to the R-matrix representation being attached to the quantum superalgebra `\mathfrak{sl}_q(2|1)`. + See [MW2012]_, section 3 and references given there. INPUT: @@ -827,15 +832,16 @@ def links_gould_matrix(self, symbolics=False): M = rep[0][0].parent().one() for i in self.Tietze(): if i > 0: - M = M*rep[i-1][0] + M = M * rep[i-1][0] if i < 0: - M = M*rep[-i-1][1] + M = M * rep[-i-1][1] return M @cached_method def links_gould_polynomial(self, varnames=None, use_symbolics=False): r""" Return the Links-Gould polynomial of the closure of ``self``. + See [MW2012]_, section 3 and references given there. INPUT: @@ -872,12 +878,13 @@ def links_gould_polynomial(self, varnames=None, use_symbolics=False): mu = rep[ln - 1] # quantum trace factor M = mu * self.links_gould_matrix(symbolics=use_symbolics) d1, d2 = M.dimensions() - e = d1//4 + e = d1 // 4 B = M.base_ring() R = LaurentPolynomialRing(ZZ, varnames) # partial quantum trace according to I. Marin section 2.5 - part_trace = matrix(B, 4, 4, lambda i, j: sum(M[e * i + k, e * j + k] for k in range(e))) + part_trace = matrix(B, 4, 4, lambda i, j: sum(M[e * i + k, e * j + k] + for k in range(e))) ptemp = part_trace[0, 0] # part_trace == psymb*M.parent().one() if use_symbolics: v1, v2 = R.variable_names() @@ -888,13 +895,13 @@ def links_gould_polynomial(self, varnames=None, use_symbolics=False): else: ltemp = ptemp.lift().constant_coefficient() # Since the result of the calculation is known to be a Laurent polynomial - # in t0 and t1 all exponents of ltemp must be divisable by 2 + # in t0 and t1 all exponents of ltemp must be divisible by 2 L = ltemp.parent() lred = L({(k[0]/2, k[1]/2): v for k, v in ltemp.monomial_coefficients().items()}) t0, t1 = R.gens() return lred(t0, t1) - def tropical_coordinates(self): + def tropical_coordinates(self) -> list: r""" Return the tropical coordinates of ``self`` in the braid group `B_n`. @@ -938,7 +945,7 @@ def tropical_coordinates(self): from sage.rings.semirings.tropical_semiring import TropicalSemiring T = TropicalSemiring(ZZ) - return [T(_) for _ in coord] + return [T(c) for c in coord] def markov_trace(self, variab=None, normalized=True): r""" @@ -1606,7 +1613,7 @@ def right_normal_form(self): B = self.parent() return tuple([B(b) for b in rnf[:-1]] + [B.delta()**rnf[-1][0]]) - def centralizer(self): + def centralizer(self) -> list: """ Return a list of generators of the centralizer of the braid. @@ -1621,7 +1628,7 @@ def centralizer(self): B = self.parent() return [B._element_from_libbraiding(b) for b in c] - def super_summit_set(self): + def super_summit_set(self) -> list: """ Return a list with the super summit set of the braid. @@ -1739,10 +1746,9 @@ def conjugating_braid(self, other): cb = conjugatingbraid(self, other) if not cb: return None - else: - B = self.parent() - cb[0][0] %= 2 - return B._element_from_libbraiding(cb) + B = self.parent() + cb[0][0] %= 2 + return B._element_from_libbraiding(cb) def is_conjugated(self, other) -> bool: """ @@ -1859,7 +1865,7 @@ def pure_conjugating_braid(self, other): n2 = len(b2.Tietze()) return b2 if n2 <= n0 else b0 - def ultra_summit_set(self): + def ultra_summit_set(self) -> list: """ Return a list with the orbits of the ultra summit set of ``self``. @@ -1888,7 +1894,7 @@ def ultra_summit_set(self): B = self.parent() return [[B._element_from_libbraiding(i) for i in s] for s in uss] - def thurston_type(self): + def thurston_type(self) -> str: """ Return the thurston_type of ``self``. @@ -1973,7 +1979,7 @@ def rigidity(self): """ return Integer(rigidity(self)) - def sliding_circuits(self): + def sliding_circuits(self) -> list: """ Return the sliding circuits of the braid. @@ -2270,7 +2276,7 @@ def ultra_summit_set_element(self): B = self.parent() return tuple([B._element_from_libbraiding(b) for b in to_uss]) - def sliding_circuits_element(self): + def sliding_circuits_element(self) -> tuple: r""" Return an element of the braid's sliding circuits, and the conjugating braid. @@ -2286,7 +2292,7 @@ def sliding_circuits_element(self): B = self.parent() return tuple([B._element_from_libbraiding(b) for b in to_sc]) - def trajectory(self): + def trajectory(self) -> list: r""" Return the braid's trajectory. @@ -2304,7 +2310,7 @@ def trajectory(self): B = self.parent() return [B._element_from_libbraiding(b) for b in traj] - def cyclic_slidings(self): + def cyclic_slidings(self) -> list: r""" Return the braid's cyclic slidings. @@ -2484,6 +2490,7 @@ def reduced_word(self): def tuple_to_word(q_tuple): return M.prod(self._gens[i]**exp for i, exp in enumerate(q_tuple)) + ret = {tuple_to_word(q_tuple): q_factor for q_tuple, q_factor in self.tuples.items() if q_factor} return self._algebra._from_dict(ret, remove_zeros=False) @@ -2546,7 +2553,7 @@ def eps_monom(q_tuple): return sum(q_factor * eps_monom(q_tuple) for q_tuple, q_factor in self.tuples.items()) - def __repr__(self): + def __repr__(self) -> str: r""" String representation of ``self``. @@ -2582,7 +2589,7 @@ class BraidGroup_class(FiniteTypeArtinGroup): """ Element = Braid - def __init__(self, names): + def __init__(self, names) -> None: """ Python constructor. @@ -2657,7 +2664,7 @@ def __init__(self, names): # For caching hermitian form of unitary Burau representation self._hermitian_form = None - def __reduce__(self): + def __reduce__(self) -> tuple: """ TESTS:: @@ -2670,7 +2677,7 @@ def __reduce__(self): """ return (BraidGroup_class, (self.variable_names(), )) - def _repr_(self): + def _repr_(self) -> str: """ Return a string representation. @@ -2761,7 +2768,7 @@ def an_element(self): """ return self.gen(0) - def some_elements(self): + def some_elements(self) -> list: """ Return a list of some elements of the braid group. @@ -2778,7 +2785,7 @@ def some_elements(self): elements_list.append(elements_list[-1]**self.strands()) return elements_list - def _standard_lift_Tietze(self, p): + def _standard_lift_Tietze(self, p) -> tuple: """ Helper for :meth:`_standard_lift_Tietze`. @@ -2806,8 +2813,7 @@ def _standard_lift_Tietze(self, p): (1, 2, 3, 4, 1, 2) """ G = SymmetricGroup(self.strands()) - pl = G(p) - return tuple(pl.reduced_word()) + return tuple(G(p).reduced_word()) @cached_method def _links_gould_representation(self, symbolics=False): @@ -2853,8 +2859,9 @@ def _links_gould_representation(self, symbolics=False): else: from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing LR = LaurentPolynomialRing(ZZ, 's0r, s1r') + s0r, s1r = LR.gens() PR = PolynomialRing(LR, 'Yr') - s0r, s1r, Yr = PR.gens_dict_recursive().values() + Yr = PR.gens() pqr = Yr**2 + (s0r**2 - 1) * (s1r**2 - 1) BR = PR.quotient_ring(pqr) s0 = BR(s0r) @@ -3419,9 +3426,10 @@ def mirror_involution(self): r""" Return the mirror involution of ``self``. - This automorphism maps a braid to another one by replacing each generator - in its word by the inverse. In general this is different from the inverse - of the braid since the order of the generators in the word is not reversed. + This automorphism maps a braid to another one by replacing + each generator in its word by the inverse. In general this is + different from the inverse of the braid since the order of the + generators in the word is not reversed. EXAMPLES:: @@ -3485,7 +3493,7 @@ def presentation_two_generators(self, isomorphisms=False): h2 = G.hom(codomain=self, im_gens=[self(a) for a in L2], check=False) return (G, h1, h2) - def epimorphisms(self, H): + def epimorphisms(self, H) -> list: r""" Return the epimorphisms from ``self`` to ``H``, up to automorphism of `H` passing through the :meth:`two generator presentation @@ -3518,12 +3526,15 @@ def epimorphisms(self, H): Gg = libgap(G) Hg = libgap(H) gquotients = Gg.GQuotients(Hg) - hom1g = libgap.GroupHomomorphismByImagesNC(G0g, Gg, [libgap(hom1(u)) for u in self.gens()]) + hom1g = libgap.GroupHomomorphismByImagesNC(G0g, Gg, + [libgap(hom1(u)) + for u in self.gens()]) g0quotients = [hom1g * h for h in gquotients] res = [] # the following closure is needed to attach a specific value of quo to # each function in the different morphisms - fmap = lambda tup: (lambda a: H(prod(tup[abs(i)-1]**sign(i) for i in a.Tietze()))) + fmap = lambda tup: (lambda a: H(prod(tup[abs(i)-1]**sign(i) + for i in a.Tietze()))) for quo in g0quotients: tup = tuple(H(quo.ImageElm(i.gap()).sage()) for i in self.gens()) fhom = GroupMorphismWithGensImages(HomSpace, fmap(tup)) @@ -3651,7 +3662,8 @@ class group of the punctured disk. sage: A = B.mapping_class_action(F) sage: A - Right action by Braid group on 4 strands on Free Group on generators {x0, x1, x2, x3} + Right action by Braid group on 4 strands on Free Group + on generators {x0, x1, x2, x3} sage: A(x0, s1) x0 sage: A(x1, s1) @@ -3659,14 +3671,15 @@ class group of the punctured disk. sage: A(x1^-1, s1) x1*x2^-1*x1^-1 """ - def __init__(self, G, M): + def __init__(self, G, M) -> None: """ TESTS:: sage: B = BraidGroup(3) sage: G = FreeGroup('a, b, c') sage: B.mapping_class_action(G) # indirect doctest - Right action by Braid group on 3 strands on Free Group on generators {a, b, c} + Right action by Braid group on 3 strands on Free Group + on generators {a, b, c} """ import operator Action.__init__(self, G, M, False, operator.mul) From 56c0322fd852be7787dbb2832291fa3d45d06d6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Mon, 27 Jan 2025 21:50:32 +0100 Subject: [PATCH 217/369] fix typo --- src/sage/groups/braid.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/groups/braid.py b/src/sage/groups/braid.py index d67b4c8b8e5..3348d15b525 100644 --- a/src/sage/groups/braid.py +++ b/src/sage/groups/braid.py @@ -2861,7 +2861,7 @@ def _links_gould_representation(self, symbolics=False): LR = LaurentPolynomialRing(ZZ, 's0r, s1r') s0r, s1r = LR.gens() PR = PolynomialRing(LR, 'Yr') - Yr = PR.gens() + Yr = PR.gen() pqr = Yr**2 + (s0r**2 - 1) * (s1r**2 - 1) BR = PR.quotient_ring(pqr) s0 = BR(s0r) From cbf590668f25b3738c7d78bb06c70ea16f87521a Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Sat, 1 Feb 2025 18:01:49 +0700 Subject: [PATCH 218/369] Use import_module instead of find_spec --- src/sage/misc/dev_tools.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/sage/misc/dev_tools.py b/src/sage/misc/dev_tools.py index b11b2078129..2a49310d66a 100644 --- a/src/sage/misc/dev_tools.py +++ b/src/sage/misc/dev_tools.py @@ -171,7 +171,7 @@ def load_submodules(module=None, exclude_pattern=None): load sage.geometry.polyhedron.ppl_lattice_polygon... succeeded """ from .package_dir import walk_packages - import importlib.util + import importlib if module is None: import sage @@ -195,12 +195,8 @@ def load_submodules(module=None, exclude_pattern=None): try: sys.stdout.write("load %s..." % module_name) sys.stdout.flush() - # see - # https://docs.python.org/3/library/importlib.html#importing-a-source-file-directly - spec = importer.find_spec(module_name) - module = importlib.util.module_from_spec(spec) - sys.modules[module_name] = module - spec.loader.exec_module(module) + module = importlib.import_module(module_name) + assert sys.modules[module_name] is module sys.stdout.write(" succeeded\n") except (ValueError, AttributeError, TypeError, ImportError): # we might get error because of cython code that has been From c58d841a1702a5b4c42aa49655764297575430e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Tue, 4 Feb 2025 15:39:14 +0100 Subject: [PATCH 219/369] conversion of padics rings from magma --- src/sage/ext_data/magma/sage/basic.m | 30 +++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/sage/ext_data/magma/sage/basic.m b/src/sage/ext_data/magma/sage/basic.m index 678174dfd49..02a2680b7d9 100644 --- a/src/sage/ext_data/magma/sage/basic.m +++ b/src/sage/ext_data/magma/sage/basic.m @@ -125,7 +125,7 @@ intrinsic Sage(X::RngIntRes) -> MonStgElt, BoolElt return Sprintf("Zmod(%o)", Characteristic(X)), false; end intrinsic; -/* Approximate fields */ +/* Approximate real and complex fields */ intrinsic Sage(X::FldRe) -> MonStgElt, BoolElt {} @@ -147,6 +147,34 @@ intrinsic Sage(X::FldComElt) -> MonStgElt, BoolElt return Sprintf("%o([%o, %o])", Sage(Parent(X)), Sage(Real(X)), Sage(Imaginary(X))), true; end intrinsic; +/* p-adic rings and fields */ + +intrinsic Sage(X::RngPad) -> MonStgElt, BoolElt +{p-adic rings, either free precision model or exact model} + prec := Precision(X); + if prec eq Infinity then; + return Sprintf("Zp(%o, %o, 'relaxed')", Sage(Prime(X)), Sage(Precision(X))), false; + else: + return Sprintf("Zp(%o, %o, 'capped-rel')", Sage(Prime(X)), Sage(Precision(X))), false; + end if; +end intrinsic; + +intrinsic Sage(X::FldPAd) -> MonStgElt, BoolElt +{p-adic fields, either free precision model or exact model} + prec := Precision(X); + if prec eq Infinity then; + return Sprintf("Qp(%o, %o, 'relaxed')", Sage(Prime(X)), Sage(Precision(X))), false; + else: + return Sprintf("Qp(%o, %o, 'capped-rel')", Sage(Prime(X)), Sage(Precision(X))), false; + end if; +end intrinsic; + +intrinsic Sage(X::RngPadRes) -> MonStgElt, BoolElt +{fixed precision model} + return Sprintf("Zp(%o, %o, 'fixed-mod')", Sage(Prime(X)), Sage(Precision(X))), false; +end intrinsic; + + /* Polynomials */ intrinsic SageNamesHelper(X::.) -> MonStgElt From bb05936f1a89f98971e2fb7ef3aa60c8b88accf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Tue, 4 Feb 2025 16:15:11 +0100 Subject: [PATCH 220/369] fix --- src/sage/ext_data/magma/sage/basic.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/ext_data/magma/sage/basic.m b/src/sage/ext_data/magma/sage/basic.m index 02a2680b7d9..823660077cb 100644 --- a/src/sage/ext_data/magma/sage/basic.m +++ b/src/sage/ext_data/magma/sage/basic.m @@ -154,7 +154,7 @@ intrinsic Sage(X::RngPad) -> MonStgElt, BoolElt prec := Precision(X); if prec eq Infinity then; return Sprintf("Zp(%o, %o, 'relaxed')", Sage(Prime(X)), Sage(Precision(X))), false; - else: + else return Sprintf("Zp(%o, %o, 'capped-rel')", Sage(Prime(X)), Sage(Precision(X))), false; end if; end intrinsic; @@ -164,7 +164,7 @@ intrinsic Sage(X::FldPAd) -> MonStgElt, BoolElt prec := Precision(X); if prec eq Infinity then; return Sprintf("Qp(%o, %o, 'relaxed')", Sage(Prime(X)), Sage(Precision(X))), false; - else: + else return Sprintf("Qp(%o, %o, 'capped-rel')", Sage(Prime(X)), Sage(Precision(X))), false; end if; end intrinsic; From 311d8a6524e8444378704dc38f39d84e6f7d184f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Tue, 4 Feb 2025 16:18:46 +0100 Subject: [PATCH 221/369] fix --- src/sage/ext_data/magma/sage/basic.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/ext_data/magma/sage/basic.m b/src/sage/ext_data/magma/sage/basic.m index 823660077cb..57aba52270d 100644 --- a/src/sage/ext_data/magma/sage/basic.m +++ b/src/sage/ext_data/magma/sage/basic.m @@ -152,7 +152,7 @@ intrinsic Sage(X::FldComElt) -> MonStgElt, BoolElt intrinsic Sage(X::RngPad) -> MonStgElt, BoolElt {p-adic rings, either free precision model or exact model} prec := Precision(X); - if prec eq Infinity then; + if Type(prec) eq Infty then return Sprintf("Zp(%o, %o, 'relaxed')", Sage(Prime(X)), Sage(Precision(X))), false; else return Sprintf("Zp(%o, %o, 'capped-rel')", Sage(Prime(X)), Sage(Precision(X))), false; @@ -162,7 +162,7 @@ intrinsic Sage(X::RngPad) -> MonStgElt, BoolElt intrinsic Sage(X::FldPAd) -> MonStgElt, BoolElt {p-adic fields, either free precision model or exact model} prec := Precision(X); - if prec eq Infinity then; + if Type(prec) eq Infty then return Sprintf("Qp(%o, %o, 'relaxed')", Sage(Prime(X)), Sage(Precision(X))), false; else return Sprintf("Qp(%o, %o, 'capped-rel')", Sage(Prime(X)), Sage(Precision(X))), false; From fd93751270cfe95ec5110d6269e4bc7fbb72a499 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Tue, 4 Feb 2025 16:22:22 +0100 Subject: [PATCH 222/369] fix --- src/sage/ext_data/magma/sage/basic.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/ext_data/magma/sage/basic.m b/src/sage/ext_data/magma/sage/basic.m index 57aba52270d..bcbe795bb45 100644 --- a/src/sage/ext_data/magma/sage/basic.m +++ b/src/sage/ext_data/magma/sage/basic.m @@ -159,7 +159,7 @@ intrinsic Sage(X::RngPad) -> MonStgElt, BoolElt end if; end intrinsic; -intrinsic Sage(X::FldPAd) -> MonStgElt, BoolElt +intrinsic Sage(X::FldPad) -> MonStgElt, BoolElt {p-adic fields, either free precision model or exact model} prec := Precision(X); if Type(prec) eq Infty then From eab6095787316841bf155366e41934f33e2df9a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Tue, 4 Feb 2025 16:31:24 +0100 Subject: [PATCH 223/369] conversion of padics rings to magma --- src/sage/rings/padics/padic_base_leaves.py | 39 ++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/sage/rings/padics/padic_base_leaves.py b/src/sage/rings/padics/padic_base_leaves.py index 4eb9e584de1..1aea397227a 100644 --- a/src/sage/rings/padics/padic_base_leaves.py +++ b/src/sage/rings/padics/padic_base_leaves.py @@ -304,6 +304,19 @@ def _convert_map_from_(self, R): from sage.rings.padics.padic_generic import ResidueLiftingMap return ResidueLiftingMap._create_(R, self) + def _magma_init_(self, magma): + """ + Conversion to magma. + + EXAMPLES:: + + sage: # optional - magma + sage: F = Qp(5,7,"capped-rel") + sage: magma(F) + ? + """ + return f"pAdicRing({self.prime()},{self.precision_cap()})" + class pAdicRingCappedAbsolute(pAdicRingBaseGeneric, pAdicCappedAbsoluteRingGeneric): r""" @@ -607,6 +620,19 @@ def _convert_map_from_(self, R): from sage.rings.padics.padic_generic import ResidueLiftingMap return ResidueLiftingMap._create_(R, self) + def _magma_init_(self, magma): + """ + Conversion to magma. + + EXAMPLES:: + + sage: # optional - magma + sage: F = Zp(5,7,"fixed-mod") + sage: magma(F) + ? + """ + return f"pAdicQuotientRing({self.prime()},{self.precision_cap()})" + class pAdicFieldCappedRelative(pAdicFieldBaseGeneric, pAdicCappedRelativeFieldGeneric): r""" @@ -719,6 +745,19 @@ def _convert_map_from_(self, R): from sage.rings.padics.padic_generic import ResidueLiftingMap return ResidueLiftingMap._create_(R, self) + def _magma_init_(self, magma): + """ + Conversion to magma. + + EXAMPLES:: + + sage: # optional - magma + sage: F = Qp(5,7,"capped-rel") + sage: magma(F) + ? + """ + return f"pAdicField({self.prime()},{self.precision_cap()})" + def random_element(self, algorithm='default'): r""" Return a random element of ``self``, optionally using the ``algorithm`` From fb6375c25e5def3332dc68a2d78c6c24c7510d55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Tue, 4 Feb 2025 16:33:42 +0100 Subject: [PATCH 224/369] add doctests --- src/sage/rings/padics/padic_base_leaves.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sage/rings/padics/padic_base_leaves.py b/src/sage/rings/padics/padic_base_leaves.py index 1aea397227a..878c54b748f 100644 --- a/src/sage/rings/padics/padic_base_leaves.py +++ b/src/sage/rings/padics/padic_base_leaves.py @@ -313,7 +313,7 @@ def _magma_init_(self, magma): sage: # optional - magma sage: F = Qp(5,7,"capped-rel") sage: magma(F) - ? + 5-adic field mod 5^7 """ return f"pAdicRing({self.prime()},{self.precision_cap()})" @@ -629,7 +629,7 @@ def _magma_init_(self, magma): sage: # optional - magma sage: F = Zp(5,7,"fixed-mod") sage: magma(F) - ? + Quotient of the 5-adic ring modulo the ideal generated by 5^7 """ return f"pAdicQuotientRing({self.prime()},{self.precision_cap()})" @@ -754,7 +754,7 @@ def _magma_init_(self, magma): sage: # optional - magma sage: F = Qp(5,7,"capped-rel") sage: magma(F) - ? + 5-adic field mod 5^7 """ return f"pAdicField({self.prime()},{self.precision_cap()})" From 15188988b11166b4900dba683c87d525a8cbcffe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Wed, 5 Feb 2025 11:46:31 +0100 Subject: [PATCH 225/369] remove deprecated stuff in permutation.py --- src/sage/combinat/permutation.py | 54 ++------------------------------ 1 file changed, 2 insertions(+), 52 deletions(-) diff --git a/src/sage/combinat/permutation.py b/src/sage/combinat/permutation.py index a1043f59646..8df962d5779 100644 --- a/src/sage/combinat/permutation.py +++ b/src/sage/combinat/permutation.py @@ -7758,7 +7758,7 @@ def reflection(self, i): return self.element_class(self, data, check=False) class Element(Permutation): - def has_left_descent(self, i, mult=None): + def has_left_descent(self, i): r""" Check if ``i`` is a left descent of ``self``. @@ -7789,23 +7789,7 @@ def has_left_descent(self, i, mult=None): [1, 2] sage: [i for i in P.index_set() if x.has_left_descent(i)] [1, 2] - - TESTS:: - - sage: P = Permutations(4) - sage: x = P([3, 2, 4, 1]) - sage: x.has_left_descent(2, mult='l2r') - doctest:warning - ... - DeprecationWarning: The mult option is deprecated and ignored. - See https://github.com/sagemath/sage/issues/27467 for details. - True - sage: x.has_left_descent(2, mult='r2l') - True """ - if mult is not None: - from sage.misc.superseded import deprecation - deprecation(27467, "The mult option is deprecated and ignored.") for val in self._list: if val == i: return False @@ -7843,24 +7827,8 @@ def has_right_descent(self, i, mult=None): [1, 3] sage: [i for i in P.index_set() if x.has_right_descent(i)] [1, 3] - - TESTS:: - - sage: P = Permutations(4) - sage: x = P([3, 2, 4, 1]) - sage: x.has_right_descent(3, mult='l2r') - doctest:warning - ... - DeprecationWarning: The mult option is deprecated and ignored. - See https://github.com/sagemath/sage/issues/27467 for details. - True - sage: x.has_right_descent(3, mult='r2l') - True """ - if mult is not None: - from sage.misc.superseded import deprecation - deprecation(27467, "The mult option is deprecated and ignored.") - return self[i-1] > self[i] + return self[i - 1] > self[i] def __mul__(self, other): r""" @@ -9602,24 +9570,6 @@ def __init__(self, n, a): StandardPermutations_n_abstract.__init__(self, n) self._a = a - @property - def a(self): - r""" - ``self.a`` is deprecated; use :meth:`patterns` instead. - - TESTS:: - - sage: P = Permutations(3, avoiding=[[2,1,3],[1,2,3]]) - sage: P.a - doctest:...: DeprecationWarning: The attribute a for the list of patterns to avoid is deprecated, use the method patterns instead. - See https://github.com/sagemath/sage/issues/26810 for details. - ([2, 1, 3], [1, 2, 3]) - """ - from sage.misc.superseded import deprecation - deprecation(26810, "The attribute a for the list of patterns to avoid is " - "deprecated, use the method patterns instead.") - return self.patterns() - def patterns(self): """ Return the patterns avoided by this class of permutations. From 7c5d6dcf1892b04f311c6f6c21121ad6dc584c33 Mon Sep 17 00:00:00 2001 From: rhinopotamus Date: Thu, 10 Aug 2023 15:12:19 -0600 Subject: [PATCH 226/369] Trivial simplifications for arccos This fixes a couple of places where arccos doesn't automatically simplify for ratios arising from the unit circle (but arcsin does). Turns out that asin_eval has some logic handling unit circle cases directly that was not replicated in acos_eval. I copy-pasted the logic from asin_eval and modified appropriately. https://github.com/sagemath/sage/issues/24211 --- src/doc/de/thematische_anleitungen/sage_gymnasium.rst | 8 +------- src/sage/functions/trig.py | 3 +++ src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py | 4 ++-- src/sage/symbolic/ginac/inifcns_trig.cpp | 7 +++++++ .../premierspas_doctest.py | 2 +- 5 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/doc/de/thematische_anleitungen/sage_gymnasium.rst b/src/doc/de/thematische_anleitungen/sage_gymnasium.rst index 9dc38d8bf0b..dfeb3e13c82 100644 --- a/src/doc/de/thematische_anleitungen/sage_gymnasium.rst +++ b/src/doc/de/thematische_anleitungen/sage_gymnasium.rst @@ -774,8 +774,7 @@ falls wir im Bogenmass rechnen möchten. Ansonsten müssen wir wie oben beschrie Ihre Umkehrfunktionen sind auch mit den nicht sehr überraschenden Namen ``asin()``, ``acos()``, ``atan()`` und ``acot()`` versehen. Sie geben uns aber wie oben erklärt nur Winkel im Bogenmass zurück. Möchten wir im Gradmass rechnen, müssen wir wieder -konvertieren. Die exakte Berechnung der Werte funktioniert in die Gegenrichtung nur, falls im ursprünglichen Wert keine -Wurzeln vorkommen:: +konvertieren. Exakte Berechnung der Werte funktioniert, wenn es möglich ist:: sage: atan(1) 1/4*pi @@ -784,12 +783,7 @@ Wurzeln vorkommen:: sage: rad2deg(x) = x*(180/pi) sage: rad2deg(acos(-1/2)) 120 - -Falls wir Wurzelterme verwenden, müssen wir mit der Funktion ``simplify_full()`` vereinfachen:: - sage: acos(sqrt(3)/2) - arccos(1/2*sqrt(3)) - sage: (acos(sqrt(3)/2)).simplify_full() 1/6*pi Sage kann auch weitere Regeln für trigonometrische Funktionen anwenden, um Terme zu vereinfachen. Es kennt zum Beispiel auch die diff --git a/src/sage/functions/trig.py b/src/sage/functions/trig.py index 8f390205cae..242b07f9cd4 100644 --- a/src/sage/functions/trig.py +++ b/src/sage/functions/trig.py @@ -642,6 +642,9 @@ def __init__(self): (0.9045568943023814-1.0612750619050357j) sage: acos(SR(2.1)) # needs sage.symbolic 1.37285914424258*I + + sage: arcsin(sqrt(2)/2) + 1/4*pi """ GinacFunction.__init__(self, 'arccos', latex_name=r"\arccos", conversions=dict(maxima='acos', sympy='acos', diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py b/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py index b9488e77b16..52f889f30d1 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py @@ -1854,9 +1854,9 @@ def angle(self, other): # UHP sage: g = HyperbolicPlane().UHP().get_geodesic(1, 1 + I) sage: h = HyperbolicPlane().UHP().get_geodesic(-sqrt(2), sqrt(2)) sage: g.angle(h) - arccos(1/2*sqrt(2)) + 1/4*pi sage: h.angle(g) - arccos(1/2*sqrt(2)) + 1/4*pi Angle is unoriented, as opposed to oriented. :: diff --git a/src/sage/symbolic/ginac/inifcns_trig.cpp b/src/sage/symbolic/ginac/inifcns_trig.cpp index 71ee9978b9d..37bd7507d05 100644 --- a/src/sage/symbolic/ginac/inifcns_trig.cpp +++ b/src/sage/symbolic/ginac/inifcns_trig.cpp @@ -1229,6 +1229,13 @@ static ex acos_eval(const ex & x) return UnsignedInfinity; throw (std::runtime_error("arccos_eval(): arccos(infinity) encountered")); } + + if (x.is_equal(mul(pow(_ex2, _ex1_2), _ex1_2))) + return mul(Pi, _ex1_4); + + if (x.is_equal(mul(pow(_ex3, _ex1_2), _ex1_2))) + return numeric(1,6)*Pi; + return acos(x).hold(); } diff --git a/src/sage/tests/books/computational-mathematics-with-sagemath/premierspas_doctest.py b/src/sage/tests/books/computational-mathematics-with-sagemath/premierspas_doctest.py index feb5391b4d6..027c31eecfe 100644 --- a/src/sage/tests/books/computational-mathematics-with-sagemath/premierspas_doctest.py +++ b/src/sage/tests/books/computational-mathematics-with-sagemath/premierspas_doctest.py @@ -65,7 +65,7 @@ Sage example in ./premierspas.tex, line 967:: sage: arccos(sin(pi/3)) - arccos(1/2*sqrt(3)) + 1/6*pi sage: sqrt(2) sqrt(2) sage: exp(I*pi/7) From 00dcf7aa81996569a389aee1ee0f60222626b8d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Thu, 6 Feb 2025 14:44:33 +0100 Subject: [PATCH 227/369] remove some deprecations in groups --- src/sage/groups/matrix_gps/homset.py | 53 ------------------------- src/sage/groups/matrix_gps/morphism.py | 54 ------------------------- src/sage/groups/perm_gps/permgroup.py | 55 ++++---------------------- 3 files changed, 8 insertions(+), 154 deletions(-) delete mode 100644 src/sage/groups/matrix_gps/homset.py delete mode 100644 src/sage/groups/matrix_gps/morphism.py diff --git a/src/sage/groups/matrix_gps/homset.py b/src/sage/groups/matrix_gps/homset.py deleted file mode 100644 index 02818275424..00000000000 --- a/src/sage/groups/matrix_gps/homset.py +++ /dev/null @@ -1,53 +0,0 @@ -""" -Matrix Group Homsets - -AUTHORS: - -- William Stein (2006-05-07): initial version - -- Volker Braun (2013-1) port to new Parent, libGAP -""" - -############################################################################## -# Copyright (C) 2006 David Joyner and William Stein -# -# Distributed under the terms of the GNU General Public License (GPL) -# -# The full text of the GPL is available at: -# -# http://www.gnu.org/licenses/ -############################################################################## - -from sage.misc.lazy_import import lazy_import -from sage.misc.superseded import deprecation - - -def is_MatrixGroupHomset(x): - r""" - Test whether ``x`` is a matrix group homset. - - EXAMPLES:: - - sage: from sage.groups.matrix_gps.homset import is_MatrixGroupHomset - sage: is_MatrixGroupHomset(4) - doctest:...: DeprecationWarning: - Importing MatrixGroupHomset from here is deprecated; please use - "from sage.groups.libgap_morphism import GroupHomset_libgap as MatrixGroupHomset" instead. - See https://github.com/sagemath/sage/issues/25444 for details. - False - - sage: F = GF(5) - sage: gens = [matrix(F,2,[1,2, -1, 1]), matrix(F,2, [1,1, 0,1])] - sage: G = MatrixGroup(gens) - sage: from sage.groups.matrix_gps.homset import MatrixGroupHomset - sage: M = MatrixGroupHomset(G, G) - sage: is_MatrixGroupHomset(M) - True - """ - deprecation(25444, "MatrixGroupHomset is deprecated. " - "Use GroupHomset_libgap instead.") - return isinstance(x, MatrixGroupHomset) - - -lazy_import('sage.groups.libgap_morphism', 'GroupHomset_libgap', - 'MatrixGroupHomset', deprecation=25444) diff --git a/src/sage/groups/matrix_gps/morphism.py b/src/sage/groups/matrix_gps/morphism.py deleted file mode 100644 index e73ba7e945a..00000000000 --- a/src/sage/groups/matrix_gps/morphism.py +++ /dev/null @@ -1,54 +0,0 @@ -""" -Homomorphisms Between Matrix Groups - -Deprecated May, 2018; use :class:`sage.groups.libgap_morphism` instead. -""" - -#***************************************************************************** -# Copyright (C) 2006 David Joyner and William Stein -# -# Distributed under the terms of the GNU General Public License (GPL) -# as published by the Free Software Foundation; either version 2 of -# the License, or (at your option) any later version. -# http://www.gnu.org/licenses/ -#***************************************************************************** - -from sage.misc.lazy_import import lazy_import - - -def to_libgap(x): - """ - Helper to convert ``x`` to a LibGAP matrix or matrix group - element. - - Deprecated; use the ``x.gap()`` method or ``libgap(x)`` instead. - - EXAMPLES:: - - sage: from sage.groups.matrix_gps.morphism import to_libgap - sage: to_libgap(GL(2,3).gen(0)) - doctest:...: DeprecationWarning: this function is deprecated. - Use x.gap() or libgap(x) instead. - See https://github.com/sagemath/sage/issues/25444 for details. - [ [ Z(3), 0*Z(3) ], [ 0*Z(3), Z(3)^0 ] ] - sage: to_libgap(matrix(QQ, [[1,2],[3,4]])) - [ [ 1, 2 ], [ 3, 4 ] ] - """ - from sage.misc.superseded import deprecation - deprecation(25444, "this function is deprecated." - " Use x.gap() or libgap(x) instead.") - try: - return x.gap() - except AttributeError: - from sage.libs.gap.libgap import libgap - return libgap(x) - - -lazy_import('sage.groups.libgap_morphism', 'GroupMorphism_libgap', - 'MatrixGroupMorphism_im_gens', deprecation=25444) - -lazy_import('sage.categories.morphism', 'Morphism', - 'MatrixGroupMap', deprecation=25444) - -lazy_import('sage.categories.morphism', 'Morphism', - 'MatrixGroupMorphism', deprecation=25444) diff --git a/src/sage/groups/perm_gps/permgroup.py b/src/sage/groups/perm_gps/permgroup.py index a56b233c4db..f8e6c6730d1 100644 --- a/src/sage/groups/perm_gps/permgroup.py +++ b/src/sage/groups/perm_gps/permgroup.py @@ -380,13 +380,12 @@ def PermutationGroup(gens=None, *args, **kwds): ... TypeError: gens must be a tuple, list, or GapElement - This will raise an error after the deprecation period:: + This now raises an error:: sage: G = PermutationGroup([(1,2,3,4)], [(1,7,3,5)]) - doctest:warning + Traceback (most recent call last): ... - DeprecationWarning: gap_group, domain, canonicalize, category will become keyword only - See https://github.com/sagemath/sage/issues/31510 for details. + ValueError: please use keywords gap_group=, domain=, canonicalize=, category= in the input """ if not isinstance(gens, ExpectElement) and hasattr(gens, '_permgroup_'): return gens._permgroup_() @@ -402,18 +401,7 @@ def PermutationGroup(gens=None, *args, **kwds): raise ValueError("you must specify the domain for an action") return PermutationGroup_action(gens, action, domain, gap_group=gap_group) if args: - from sage.misc.superseded import deprecation - deprecation(31510, "gap_group, domain, canonicalize, category will become keyword only") - if len(args) > 4: - raise ValueError("invalid input") - args = list(args) - gap_group = args.pop(0) - if args: - domain = args.pop(0) - if args: - canonicalize = args.pop(0) - if args: - category = args.pop(0) + raise ValueError("please use keywords gap_group=, domain=, canonicalize=, category= in the input") return PermutationGroup_generic(gens=gens, gap_group=gap_group, domain=domain, canonicalize=canonicalize, category=category) @@ -1068,7 +1056,7 @@ def list(self): """ return list(self) - def __contains__(self, item): + def __contains__(self, item) -> bool: """ Return whether ``item`` is an element of this group. @@ -1105,33 +1093,6 @@ def __contains__(self, item): return False return True - def has_element(self, item): - """ - Return whether ``item`` is an element of this group - - however *ignores* parentage. - - EXAMPLES:: - - sage: G = CyclicPermutationGroup(4) - sage: gens = G.gens() - sage: H = DihedralGroup(4) - sage: g = G([(1,2,3,4)]); g - (1,2,3,4) - sage: G.has_element(g) - doctest:warning - ... - DeprecationWarning: G.has_element(g) is deprecated; use :meth:`__contains__`, i.e., `g in G` instead - See https://github.com/sagemath/sage/issues/33831 for details. - True - sage: h = H([(1,2),(3,4)]); h - (1,2)(3,4) - sage: G.has_element(h) - False - """ - from sage.misc.superseded import deprecation - deprecation(33831, "G.has_element(g) is deprecated; use :meth:`__contains__`, i.e., `g in G` instead") - return item in self - def __iter__(self): r""" Return an iterator going through all elements in ``self``. @@ -1161,9 +1122,9 @@ def __iter__(self): """ if len(self._gens) == 1: return self._iteration_monogen() - else: - # TODO: this is too slow for moderatly small permutation groups - return self.iteration(algorithm='SGS') + + # TODO: this is too slow for moderately small permutation groups + return self.iteration(algorithm='SGS') def _iteration_monogen(self): r""" From 98534b10338f905ab89dce82838212d3e178d492 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Thu, 6 Feb 2025 20:51:50 +0700 Subject: [PATCH 228/369] Move mention of accessor functions to implementation section --- src/sage/rings/laurent_series_ring_element.pyx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/sage/rings/laurent_series_ring_element.pyx b/src/sage/rings/laurent_series_ring_element.pyx index 85d716b8c7a..408b7810c3f 100644 --- a/src/sage/rings/laurent_series_ring_element.pyx +++ b/src/sage/rings/laurent_series_ring_element.pyx @@ -40,6 +40,10 @@ as a power of the variable times the unit part (which need not be a unit - it's a polynomial with nonzero constant term). The zero Laurent series has unit part 0. +For a Laurent series internally represented as `t^n \cdot f` where +`t` is the variable, `f` can be accessed through :meth:`valuation_zero_part` +and `n` can be accessed through :meth:`valuation`. + AUTHORS: - William Stein: original version @@ -97,11 +101,9 @@ cdef class LaurentSeries(AlgebraElement): - ``parent`` -- a Laurent series ring - ``f`` -- a power series (or something can be coerced - to one); note that ``f`` does *not* have to be a unit. - This can be accessed through :meth:`valuation_zero_part`. + to one); note that ``f`` does *not* have to be a unit - - ``n`` -- (default: 0) integer. This can be accessed - through :meth:`valuation`. + - ``n`` -- (default: 0) integer """ def __init__(self, parent, f, n=0): r""" @@ -1303,7 +1305,7 @@ cdef class LaurentSeries(AlgebraElement): sage: g.valuation() 0 - Note that the valuation of an element undistinguishable from + Note that the valuation of an element indistinguishable from zero is infinite:: sage: h = f - f; h From 09db0b2e52e7d05536f503888f5f5354e3c2f584 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Thu, 6 Feb 2025 21:17:51 +0700 Subject: [PATCH 229/369] Fix lint --- src/sage/rings/laurent_series_ring_element.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/laurent_series_ring_element.pyx b/src/sage/rings/laurent_series_ring_element.pyx index 408b7810c3f..758b7012240 100644 --- a/src/sage/rings/laurent_series_ring_element.pyx +++ b/src/sage/rings/laurent_series_ring_element.pyx @@ -1,4 +1,4 @@ -""" +r""" Laurent Series EXAMPLES:: From a18857ca4690aac9efb21fcf96578110d1259d0a Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Fri, 7 Feb 2025 00:31:17 +0700 Subject: [PATCH 230/369] Retrigger CI From 85611d79251e4baae34a04da6a0ba03843152e7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Thu, 6 Feb 2025 18:54:11 +0100 Subject: [PATCH 231/369] fix detail --- src/sage/groups/perm_gps/permgroup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/groups/perm_gps/permgroup.py b/src/sage/groups/perm_gps/permgroup.py index f8e6c6730d1..d48f1f8ec80 100644 --- a/src/sage/groups/perm_gps/permgroup.py +++ b/src/sage/groups/perm_gps/permgroup.py @@ -380,7 +380,7 @@ def PermutationGroup(gens=None, *args, **kwds): ... TypeError: gens must be a tuple, list, or GapElement - This now raises an error:: + This now raises an error (:issue:`31510`):: sage: G = PermutationGroup([(1,2,3,4)], [(1,7,3,5)]) Traceback (most recent call last): From 77e830f72c4dcb32700b185ab44e60339315fc4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Fri, 7 Feb 2025 14:02:29 +0100 Subject: [PATCH 232/369] convert gens method in modular to return tuple --- src/sage/modular/abvar/finite_subgroup.py | 6 +- src/sage/modular/abvar/homspace.py | 2 +- src/sage/modular/dirichlet.py | 4 +- src/sage/modular/drinfeld_modform/ring.py | 20 ++--- src/sage/modular/hecke/algebra.py | 8 +- src/sage/modular/hecke/module.py | 2 +- src/sage/modular/modform/space.py | 2 +- .../modform_hecketriangle/abstract_space.py | 89 +++++++++---------- .../modular/modform_hecketriangle/space.py | 59 ++++++------ .../modular/modform_hecketriangle/subspace.py | 16 ++-- src/sage/modular/overconvergent/genus0.py | 3 +- .../modular/pollack_stevens/fund_domain.py | 18 ++-- src/sage/modular/quasimodform/ring.py | 40 ++++----- 13 files changed, 135 insertions(+), 134 deletions(-) diff --git a/src/sage/modular/abvar/finite_subgroup.py b/src/sage/modular/abvar/finite_subgroup.py index cfaf762eae2..ecdfbeebf77 100644 --- a/src/sage/modular/abvar/finite_subgroup.py +++ b/src/sage/modular/abvar/finite_subgroup.py @@ -579,11 +579,13 @@ def order(self): self.__order = o return o - def gens(self): + def gens(self) -> Sequence: """ Return generators for this finite subgroup. - EXAMPLES: We list generators for several cuspidal subgroups:: + EXAMPLES: + + We list generators for several cuspidal subgroups:: sage: J0(11).cuspidal_subgroup().gens() [[(0, 1/5)]] diff --git a/src/sage/modular/abvar/homspace.py b/src/sage/modular/abvar/homspace.py index 97200d8a6bc..c14830d8815 100644 --- a/src/sage/modular/abvar/homspace.py +++ b/src/sage/modular/abvar/homspace.py @@ -508,7 +508,7 @@ def ngens(self): self.calculate_generators() return len(self._gens) - def gens(self): + def gens(self) -> tuple: """ Return tuple of generators for this endomorphism ring. diff --git a/src/sage/modular/dirichlet.py b/src/sage/modular/dirichlet.py index f93984335bd..db1bd95d246 100644 --- a/src/sage/modular/dirichlet.py +++ b/src/sage/modular/dirichlet.py @@ -3104,7 +3104,7 @@ def gen(self, n=0): return g[n] @cached_method - def gens(self): + def gens(self) -> tuple: """ Return generators of ``self``. @@ -3117,7 +3117,7 @@ def gens(self): g = [] ord = self.zeta_order() M = self._module - zero = M(0) + zero = M.zero() orders = self.integers_mod().unit_group().gens_orders() for i in range(len(self.unit_gens())): z = zero.__copy__() diff --git a/src/sage/modular/drinfeld_modform/ring.py b/src/sage/modular/drinfeld_modform/ring.py index b027edc08ca..0106f3bdc24 100644 --- a/src/sage/modular/drinfeld_modform/ring.py +++ b/src/sage/modular/drinfeld_modform/ring.py @@ -119,7 +119,7 @@ class DrinfeldModularForms(Parent, UniqueRepresentation): Use the :meth:`gens` method to obtain the generators of the ring:: sage: M.gens() - [g1, g2, g3] + (g1, g2, g3) sage: M.inject_variables() # assign the variable g1, g2, g3 Defining g1, g2, g3 sage: T*g1*g2 + g3 @@ -130,23 +130,23 @@ class DrinfeldModularForms(Parent, UniqueRepresentation): sage: M. = DrinfeldModularForms(K) sage: M.gens() - [F, G, H] + (F, G, H) sage: M = DrinfeldModularForms(K, 5, names='f') # must specify the rank sage: M.gens() - [f1, f2, f3, f4, f5] + (f1, f2, f3, f4, f5) sage: M = DrinfeldModularForms(K, names='u, v, w, x') sage: M.gens() - [u, v, w, x] + (u, v, w, x) sage: M = DrinfeldModularForms(K, names=['F', 'G', 'H']) sage: M.gens() - [F, G, H] + (F, G, H) Set the keyword parameter ``has_type`` to ``True`` in order to create the ring of Drinfeld modular forms of arbitrary type:: sage: M = DrinfeldModularForms(K, 4, has_type=True) sage: M.gens() - [g1, g2, g3, h4] + (g1, g2, g3, h4) sage: h4 = M.3 sage: h4.type() 1 @@ -618,18 +618,18 @@ def gen(self, n): """ return self(self._poly_ring.gen(n)) - def gens(self): + def gens(self) -> tuple: r""" - Return a list of generators of this ring. + Return a tuple of generators of this ring. EXAMPLES:: sage: A = GF(3)['T']; K = Frac(A); T = K.gen() sage: M = DrinfeldModularForms(K, 5) sage: M.gens() - [g1, g2, g3, g4, g5] + (g1, g2, g3, g4, g5) """ - return [self(g) for g in self._poly_ring.gens()] + return tuple(self(g) for g in self._poly_ring.gens()) def ngens(self): r""" diff --git a/src/sage/modular/hecke/algebra.py b/src/sage/modular/hecke/algebra.py index 7c209d146b6..f4f25ee3932 100644 --- a/src/sage/modular/hecke/algebra.py +++ b/src/sage/modular/hecke/algebra.py @@ -518,10 +518,12 @@ def discriminant(self): trace_matrix[i, j] = trace_matrix[j, i] = basis[i].matrix().trace_of_product(basis[j].matrix()) return trace_matrix.det() - def gens(self): + def gens(self) -> Iterator: r""" - Return a generator over all Hecke operator `T_n` for - `n = 1, 2, 3, \ldots`. This is infinite. + Return a generator over all Hecke operator `T_n` + for `n = 1, 2, 3, \ldots`. + + This is infinite. EXAMPLES:: diff --git a/src/sage/modular/hecke/module.py b/src/sage/modular/hecke/module.py index bd5e3be5733..2d1218c1f45 100644 --- a/src/sage/modular/hecke/module.py +++ b/src/sage/modular/hecke/module.py @@ -1341,7 +1341,7 @@ def factor_number(self): except AttributeError: return -1 - def gens(self): + def gens(self) -> tuple: """ Return a tuple of basis elements of ``self``. diff --git a/src/sage/modular/modform/space.py b/src/sage/modular/modform/space.py index 9a80d4c97aa..7ee965d2589 100644 --- a/src/sage/modular/modform/space.py +++ b/src/sage/modular/modform/space.py @@ -1358,7 +1358,7 @@ def gen(self, n): except IndexError: raise ValueError("Generator %s not defined" % n) - def gens(self): + def gens(self) -> list: """ Return a complete set of generators for ``self``. diff --git a/src/sage/modular/modform_hecketriangle/abstract_space.py b/src/sage/modular/modform_hecketriangle/abstract_space.py index 8c9a5db1aac..6a970f28cc1 100644 --- a/src/sage/modular/modform_hecketriangle/abstract_space.py +++ b/src/sage/modular/modform_hecketriangle/abstract_space.py @@ -1352,7 +1352,7 @@ def _canonical_min_exp(self, min_exp, order_1): return (min_exp, order_1) - def quasi_part_gens(self, r=None, min_exp=0, max_exp=infinity, order_1=ZZ.zero()): + def quasi_part_gens(self, r=None, min_exp=0, max_exp=infinity, order_1=ZZ.zero()) -> tuple: r""" Return a basis in ``self`` of the subspace of (quasi) weakly holomorphic forms which satisfy the specified properties on @@ -1391,17 +1391,20 @@ def quasi_part_gens(self, r=None, min_exp=0, max_exp=infinity, order_1=ZZ.zero() sage: QF = QuasiWeakModularForms(n=8, k=10/3, ep=-1) sage: QF.default_prec(1) sage: QF.quasi_part_gens(min_exp=-1) - [q^-1 + O(q), 1 + O(q), q^-1 - 9/(128*d) + O(q), 1 + O(q), q^-1 - 19/(64*d) + O(q), q^-1 + 1/(64*d) + O(q)] + (q^-1 + O(q), 1 + O(q), q^-1 - 9/(128*d) + O(q), + 1 + O(q), q^-1 - 19/(64*d) + O(q), q^-1 + 1/(64*d) + O(q)) sage: QF.quasi_part_gens(min_exp=-1, max_exp=-1) - [q^-1 + O(q), q^-1 - 9/(128*d) + O(q), q^-1 - 19/(64*d) + O(q), q^-1 + 1/(64*d) + O(q)] + (q^-1 + O(q), q^-1 - 9/(128*d) + O(q), + q^-1 - 19/(64*d) + O(q), q^-1 + 1/(64*d) + O(q)) sage: QF.quasi_part_gens(min_exp=-2, r=1) - [q^-2 - 9/(128*d)*q^-1 - 261/(131072*d^2) + O(q), q^-1 - 9/(128*d) + O(q), 1 + O(q)] + (q^-2 - 9/(128*d)*q^-1 - 261/(131072*d^2) + O(q), + q^-1 - 9/(128*d) + O(q), 1 + O(q)) sage: from sage.modular.modform_hecketriangle.space import ModularForms sage: MF = ModularForms(k=36) sage: MF.quasi_part_gens(min_exp=2) - [q^2 + 194184*q^4 + O(q^5), q^3 - 72*q^4 + O(q^5)] + (q^2 + 194184*q^4 + O(q^5), q^3 - 72*q^4 + O(q^5)) sage: from sage.modular.modform_hecketriangle.space import QuasiModularForms sage: MF = QuasiModularForms(n=5, k=6, ep=-1) @@ -1409,17 +1412,17 @@ def quasi_part_gens(self, r=None, min_exp=0, max_exp=infinity, order_1=ZZ.zero() sage: MF.dimension() 3 sage: MF.quasi_part_gens(r=0) - [1 - 37/(200*d)*q + O(q^2)] + (1 - 37/(200*d)*q + O(q^2),) sage: MF.quasi_part_gens(r=0)[0] == MF.E6() True sage: MF.quasi_part_gens(r=1) - [1 + 33/(200*d)*q + O(q^2)] + (1 + 33/(200*d)*q + O(q^2),) sage: MF.quasi_part_gens(r=1)[0] == MF.E2()*MF.E4() True sage: MF.quasi_part_gens(r=2) - [] + () sage: MF.quasi_part_gens(r=3) - [1 - 27/(200*d)*q + O(q^2)] + (1 - 27/(200*d)*q + O(q^2),) sage: MF.quasi_part_gens(r=3)[0] == MF.E2()^3 True @@ -1429,18 +1432,18 @@ def quasi_part_gens(self, r=None, min_exp=0, max_exp=infinity, order_1=ZZ.zero() sage: MF.dimension() 8 sage: MF.quasi_part_gens(r=0) - [q - 34743/(640000*d^2)*q^3 + O(q^4), q^2 - 69/(200*d)*q^3 + O(q^4)] + (q - 34743/(640000*d^2)*q^3 + O(q^4), q^2 - 69/(200*d)*q^3 + O(q^4)) sage: MF.quasi_part_gens(r=1) - [q - 9/(200*d)*q^2 + 37633/(640000*d^2)*q^3 + O(q^4), - q^2 + 1/(200*d)*q^3 + O(q^4)] + (q - 9/(200*d)*q^2 + 37633/(640000*d^2)*q^3 + O(q^4), + q^2 + 1/(200*d)*q^3 + O(q^4)) sage: MF.quasi_part_gens(r=2) - [q - 1/(4*d)*q^2 - 24903/(640000*d^2)*q^3 + O(q^4)] + (q - 1/(4*d)*q^2 - 24903/(640000*d^2)*q^3 + O(q^4),) sage: MF.quasi_part_gens(r=3) - [q + 1/(10*d)*q^2 - 7263/(640000*d^2)*q^3 + O(q^4)] + (q + 1/(10*d)*q^2 - 7263/(640000*d^2)*q^3 + O(q^4),) sage: MF.quasi_part_gens(r=4) - [q - 11/(20*d)*q^2 + 53577/(640000*d^2)*q^3 + O(q^4)] + (q - 11/(20*d)*q^2 + 53577/(640000*d^2)*q^3 + O(q^4),) sage: MF.quasi_part_gens(r=5) - [q - 1/(5*d)*q^2 + 4017/(640000*d^2)*q^3 + O(q^4)] + (q - 1/(5*d)*q^2 + 4017/(640000*d^2)*q^3 + O(q^4),) sage: MF.quasi_part_gens(r=1)[0] == MF.E2() * CuspForms(n=5, k=16, ep=1).gen(0) True @@ -1453,9 +1456,9 @@ def quasi_part_gens(self, r=None, min_exp=0, max_exp=infinity, order_1=ZZ.zero() sage: MF.quasi_part_gens(r=1, min_exp=-2) == MF.quasi_part_gens(r=1, min_exp=1) True sage: MF.quasi_part_gens(r=1) - [q - 8*q^2 - 8*q^3 + 5952*q^4 + O(q^5), + (q - 8*q^2 - 8*q^3 + 5952*q^4 + O(q^5), q^2 - 8*q^3 + 208*q^4 + O(q^5), - q^3 - 16*q^4 + O(q^5)] + q^3 - 16*q^4 + O(q^5)) sage: MF = QuasiWeakModularForms(n=infinity, k=4, ep=1) sage: MF.quasi_part_gens(r=2, min_exp=2, order_1=-2)[0] == MF.E2()^2 * MF.E4()^(-2) * MF.f_inf()^2 @@ -1463,23 +1466,22 @@ def quasi_part_gens(self, r=None, min_exp=0, max_exp=infinity, order_1=ZZ.zero() sage: [v.order_at(-1) for v in MF.quasi_part_gens(r=0, min_exp=2, order_1=-2)] [-2, -2] """ - - if (not self.is_weakly_holomorphic()): + if not self.is_weakly_holomorphic(): from warnings import warn warn("This function only determines generators of (quasi) weakly modular forms!") - (min_exp, order_1) = self._canonical_min_exp(min_exp, order_1) + min_exp, order_1 = self._canonical_min_exp(min_exp, order_1) # For modular forms spaces the quasi parts are all zero except for r=0 - if (self.is_modular()): + if self.is_modular(): r = ZZ(r) - if (r != 0): - return [] + if r: + return () # The lower bounds on the powers of f_inf and E4 determine # how large powers of E2 we can fit in... n = self.hecke_n() - if (n == infinity): + if n == infinity: max_numerator_weight = self._weight - 4*min_exp - 4*order_1 + 4 else: max_numerator_weight = self._weight - 4*n/(n-2)*min_exp + 4 @@ -1487,30 +1489,28 @@ def quasi_part_gens(self, r=None, min_exp=0, max_exp=infinity, order_1=ZZ.zero() # If r is not specified we gather all generators for all possible r's if r is None: gens = [] - for rnew in range(QQ(max_numerator_weight/ZZ(2)).floor() + 1): - gens += self.quasi_part_gens(r=rnew, min_exp=min_exp, max_exp=max_exp, order_1=order_1) - return gens + for rnew in range(QQ(max_numerator_weight / ZZ(2)).floor() + 1): + gens.extend(self.quasi_part_gens(r=rnew, min_exp=min_exp, max_exp=max_exp, order_1=order_1)) + return tuple(gens) r = ZZ(r) if r < 0 or 2*r > max_numerator_weight: - return [] + return () E2 = self.E2() - ambient_weak_space = self.graded_ring().reduce_type("weak", degree=(self._weight-QQ(2*r), self._ep*(-1)**r)) + ambient_weak_space = self.graded_ring().reduce_type("weak", + degree=(self._weight-QQ(2*r), self._ep*(-1)**r)) order_inf = ambient_weak_space._l1 - order_1 - if (max_exp == infinity): + if max_exp == infinity: max_exp = order_inf - elif (max_exp < min_exp): - return [] + elif max_exp < min_exp: + return () else: max_exp = min(ZZ(max_exp), order_inf) - gens = [] - for m in range(min_exp, max_exp + 1): - gens += [ self(ambient_weak_space.F_basis(m, order_1=order_1)*E2**r) ] - - return gens + return tuple(self(ambient_weak_space.F_basis(m, order_1=order_1) * E2**r) + for m in range(min_exp, max_exp + 1)) def quasi_part_dimension(self, r=None, min_exp=0, max_exp=infinity, order_1=ZZ.zero()): r""" @@ -2096,7 +2096,7 @@ def q_basis(self, m=None, min_exp=0, order_1=ZZ.zero()): q^-1 + O(q^5) sage: MF = ModularForms(k=36) - sage: MF.q_basis() == MF.gens() + sage: MF.q_basis() == list(MF.gens()) True sage: QF = QuasiModularForms(k=6) @@ -2490,11 +2490,11 @@ def ambient_coordinate_vector(self, v): return self.module()(self.ambient_space().coordinate_vector(v)) - def gens(self): + def gens(self) -> tuple: r""" This method should be overloaded by subclasses. - Return a basis of ``self``. + Return a basis of ``self`` as a tuple. Note that the coordinate vector of elements of ``self`` are with respect to this basis. @@ -2503,11 +2503,10 @@ def gens(self): sage: from sage.modular.modform_hecketriangle.space import ModularForms sage: ModularForms(k=12).gens() # defined in space.py - [1 + 196560*q^2 + 16773120*q^3 + 398034000*q^4 + O(q^5), - q - 24*q^2 + 252*q^3 - 1472*q^4 + O(q^5)] + (1 + 196560*q^2 + 16773120*q^3 + 398034000*q^4 + O(q^5), + q - 24*q^2 + 252*q^3 - 1472*q^4 + O(q^5)) """ - - raise NotImplementedError("No generators are implemented yet for {}!".format(self)) + raise NotImplementedError(f"No generators are implemented yet for {self}!") def gen(self, k=0): r""" diff --git a/src/sage/modular/modform_hecketriangle/space.py b/src/sage/modular/modform_hecketriangle/space.py index 3a55142a63d..2e2093d4d04 100644 --- a/src/sage/modular/modform_hecketriangle/space.py +++ b/src/sage/modular/modform_hecketriangle/space.py @@ -219,9 +219,9 @@ def __init__(self, group, base_ring, k, ep, n): self._module = FreeModule(self.coeff_ring(), self.dimension()) @cached_method - def gens(self): + def gens(self) -> tuple: r""" - Return a basis of ``self`` as a list of basis elements. + Return a basis of ``self`` as a tuple of basis elements. EXAMPLES:: @@ -229,16 +229,15 @@ def gens(self): sage: MF = QuasiModularForms(n=5, k=6, ep=-1) sage: MF.default_prec(2) sage: MF.gens() - [1 - 37/(200*d)*q + O(q^2), + (1 - 37/(200*d)*q + O(q^2), 1 + 33/(200*d)*q + O(q^2), - 1 - 27/(200*d)*q + O(q^2)] + 1 - 27/(200*d)*q + O(q^2)) sage: MF = QuasiModularForms(n=infinity, k=2, ep=-1) sage: MF.default_prec(2) sage: MF.gens() - [1 - 24*q + O(q^2), 1 - 8*q + O(q^2)] + (1 - 24*q + O(q^2), 1 - 8*q + O(q^2)) """ - return self.quasi_part_gens() @cached_method @@ -255,7 +254,6 @@ def dimension(self): sage: len(MF.gens()) == MF.dimension() True """ - return self.quasi_part_dimension() @cached_method @@ -384,9 +382,9 @@ def __init__(self, group, base_ring, k, ep, n): self._module = FreeModule(self.coeff_ring(), self.dimension()) @cached_method - def gens(self): + def gens(self) -> tuple: r""" - Return a basis of ``self`` as a list of basis elements. + Return a basis of ``self`` as a tuple of basis elements. EXAMPLES:: @@ -396,19 +394,18 @@ def gens(self): sage: MF.dimension() 7 sage: MF.gens() - [q - 17535/(262144*d^2)*q^3 + O(q^4), + (q - 17535/(262144*d^2)*q^3 + O(q^4), q^2 - 47/(128*d)*q^3 + O(q^4), q - 9/(128*d)*q^2 + 15633/(262144*d^2)*q^3 + O(q^4), q^2 - 7/(128*d)*q^3 + O(q^4), q - 23/(64*d)*q^2 - 3103/(262144*d^2)*q^3 + O(q^4), q - 3/(64*d)*q^2 - 4863/(262144*d^2)*q^3 + O(q^4), - q - 27/(64*d)*q^2 + 17217/(262144*d^2)*q^3 + O(q^4)] + q - 27/(64*d)*q^2 + 17217/(262144*d^2)*q^3 + O(q^4)) sage: MF = QuasiCuspForms(n=infinity, k=10, ep=-1) sage: MF.gens() - [q - 16*q^2 - 156*q^3 - 256*q^4 + O(q^5), q - 60*q^3 - 256*q^4 + O(q^5)] + (q - 16*q^2 - 156*q^3 - 256*q^4 + O(q^5), q - 60*q^3 - 256*q^4 + O(q^5)) """ - return self.quasi_part_gens() @cached_method @@ -660,9 +657,9 @@ def __init__(self, group, base_ring, k, ep, n): self._module = FreeModule(self.coeff_ring(), self.dimension()) @cached_method - def gens(self): + def gens(self) -> tuple: r""" - Return a basis of ``self`` as a list of basis elements. + Return a basis of ``self`` as a tuple of basis elements. EXAMPLES:: @@ -671,15 +668,16 @@ def gens(self): sage: MF.dimension() 4 sage: MF.gens() - [1 + 360360*q^4 + O(q^5), + (1 + 360360*q^4 + O(q^5), q + 21742*q^4 + O(q^5), q^2 + 702*q^4 + O(q^5), - q^3 - 6*q^4 + O(q^5)] + q^3 - 6*q^4 + O(q^5)) sage: ModularForms(n=infinity, k=4).gens() - [1 + 240*q^2 + 2160*q^4 + O(q^5), q - 8*q^2 + 28*q^3 - 64*q^4 + O(q^5)] + (1 + 240*q^2 + 2160*q^4 + O(q^5), + q - 8*q^2 + 28*q^3 - 64*q^4 + O(q^5)) """ - return [self.F_basis(m) for m in range(self.dimension())] + return tuple(self.F_basis(m) for m in range(self.dimension())) @cached_method def dimension(self): @@ -800,9 +798,9 @@ def __init__(self, group, base_ring, k, ep, n): self._module = FreeModule(self.coeff_ring(), self.dimension()) @cached_method - def gens(self): + def gens(self) -> tuple: r""" - Return a basis of ``self`` as a list of basis elements. + Return a basis of ``self`` as a tuple of basis elements. EXAMPLES:: @@ -813,16 +811,16 @@ def gens(self): sage: MF.dimension() 3 sage: MF.gens() - [q + 296888795/(10319560704*d^3)*q^4 + O(q^5), + (q + 296888795/(10319560704*d^3)*q^4 + O(q^5), q^2 + 6629/(221184*d^2)*q^4 + O(q^5), - q^3 - 25/(96*d)*q^4 + O(q^5)] + q^3 - 25/(96*d)*q^4 + O(q^5)) sage: MF = CuspForms(n=infinity, k=8, ep=1) sage: MF.gen(0) == MF.E4()*MF.f_inf() True """ - return [self.F_basis(m, order_1=ZZ.one()) - for m in range(1, self.dimension() + 1)] + return tuple(self.F_basis(m, order_1=ZZ.one()) + for m in range(1, self.dimension() + 1)) @cached_method def dimension(self): @@ -978,18 +976,19 @@ def _change_degree(self, k, ep): k=k, ep=ep) @cached_method - def gens(self): + def gens(self) -> tuple: r""" - Return a basis of ``self`` as a list of basis elements. - Since this is the zero module an empty list is returned. + Return a basis of ``self`` as a tuple of basis elements. + + Since this is the zero module an empty tuple is returned. EXAMPLES:: sage: from sage.modular.modform_hecketriangle.space import ZeroForm sage: ZeroForm(6, CC, 3, -1).gens() - [] + () """ - return [] + return () @cached_method def dimension(self): diff --git a/src/sage/modular/modform_hecketriangle/subspace.py b/src/sage/modular/modform_hecketriangle/subspace.py index b22840338dd..b67c3f47bc0 100644 --- a/src/sage/modular/modform_hecketriangle/subspace.py +++ b/src/sage/modular/modform_hecketriangle/subspace.py @@ -73,7 +73,8 @@ def ModularFormsSubSpace(*args, **kwargs): sage: subspace.ambient_space() ModularForms(n=3, k=12, ep=1) over Integer Ring sage: subspace.gens() - [1 + 720*q + 179280*q^2 + 16954560*q^3 + 396974160*q^4 + O(q^5), 1 - 1007*q + 220728*q^2 + 16519356*q^3 + 399516304*q^4 + O(q^5)] + (1 + 720*q + 179280*q^2 + 16954560*q^3 + 396974160*q^4 + O(q^5), + 1 - 1007*q + 220728*q^2 + 16519356*q^3 + 399516304*q^4 + O(q^5)) sage: ModularFormsSubSpace(MF.E4()^3-MF.E6()^2, reduce=True).ambient_space() CuspForms(n=3, k=12, ep=1) over Integer Ring sage: ModularFormsSubSpace(MF.E4()^3-MF.E6()^2, MF.J_inv()*MF.E4()^3, reduce=True) @@ -186,7 +187,8 @@ def __init__(self, ambient_space, basis, check): sage: subspace.basis()[0].parent() == MF True sage: subspace.gens() - [q + 78*q^2 + 2781*q^3 + 59812*q^4 + O(q^5), 1 + 360360*q^4 + O(q^5)] + (q + 78*q^2 + 2781*q^3 + 59812*q^4 + O(q^5), + 1 + 360360*q^4 + O(q^5)) sage: subspace.gens()[0].parent() == subspace True sage: subspace.is_ambient() @@ -200,7 +202,7 @@ def __init__(self, ambient_space, basis, check): sage: subspace Subspace of dimension 3 of QuasiCuspForms(n=+Infinity, k=12, ep=1) over Integer Ring sage: subspace.gens() - [q + 24*q^2 + O(q^3), q - 24*q^2 + O(q^3), q - 8*q^2 + O(q^3)] + (q + 24*q^2 + O(q^3), q - 24*q^2 + O(q^3), q - 8*q^2 + O(q^3)) """ FormsSpace_abstract.__init__(self, group=ambient_space.group(), base_ring=ambient_space.base_ring(), k=ambient_space.weight(), ep=ambient_space.ep(), n=ambient_space.hecke_n()) Module.__init__(self, base=ambient_space.base_ring()) @@ -208,7 +210,7 @@ def __init__(self, ambient_space, basis, check): self._ambient_space = ambient_space self._basis = list(basis) # self(v) instead would somehow mess up the coercion model - self._gens = [self._element_constructor_(v) for v in basis] + self._gens = tuple(self._element_constructor_(v) for v in basis) self._module = ambient_space._module.submodule([ambient_space.coordinate_vector(v) for v in basis]) # TODO: get the analytic type from the basis # self._analytic_type=self.AT(["quasi", "mero"]) @@ -306,7 +308,7 @@ def basis(self): return self._basis @cached_method - def gens(self): + def gens(self) -> tuple: r""" Return the basis of ``self``. @@ -316,11 +318,11 @@ def gens(self): sage: MF = ModularForms(n=6, k=20, ep=1) sage: subspace = MF.subspace([(MF.Delta()*MF.E4()^2).as_ring_element(), MF.gen(0)]) sage: subspace.gens() - [q + 78*q^2 + 2781*q^3 + 59812*q^4 + O(q^5), 1 + 360360*q^4 + O(q^5)] + (q + 78*q^2 + 2781*q^3 + 59812*q^4 + O(q^5), + 1 + 360360*q^4 + O(q^5)) sage: subspace.gens()[0].parent() == subspace True """ - return self._gens @cached_method diff --git a/src/sage/modular/overconvergent/genus0.py b/src/sage/modular/overconvergent/genus0.py index ef2d143b1af..f2a3df040cc 100644 --- a/src/sage/modular/overconvergent/genus0.py +++ b/src/sage/modular/overconvergent/genus0.py @@ -194,6 +194,7 @@ # Distributed under the terms of the GNU General Public License (GPL) # https://www.gnu.org/licenses/ # **************************************************************************** +from typing import Iterator import weakref @@ -693,7 +694,7 @@ def radius(self): """ return self._radius - def gens(self): + def gens(self) -> Iterator: r""" Return a generator object that iterates over the (infinite) set of basis vectors of ``self``. diff --git a/src/sage/modular/pollack_stevens/fund_domain.py b/src/sage/modular/pollack_stevens/fund_domain.py index 99a8a772743..806b08709c3 100644 --- a/src/sage/modular/pollack_stevens/fund_domain.py +++ b/src/sage/modular/pollack_stevens/fund_domain.py @@ -129,20 +129,16 @@ def __init__(self, N, reps, indices, rels, equiv_ind): self._reps = reps self._indices = sorted(indices) - self._gens = [M2Z(reps[i]) for i in self._indices] + self._gens = tuple(M2Z(reps[i]) for i in self._indices) self._ngens = len(indices) if len(rels) != len(reps): raise ValueError("length of reps and length of rels must be equal") self._rels = rels - self._rel_dict = {} - for j, L in enumerate(rels): - self._rel_dict[reps[j]] = L + self._rel_dict = {reps[j]: L for j, L in enumerate(rels)} self._equiv_ind = equiv_ind - self._equiv_rep = {} - for ky in equiv_ind: - self._equiv_rep[ky] = reps[equiv_ind[ky]] + self._equiv_rep = {ky: reps[vy] for ky, vy in equiv_ind.items()} def _repr_(self): r""" @@ -203,19 +199,19 @@ def __iter__(self): """ return iter(self._reps) - def gens(self): + def gens(self) -> tuple: r""" - Return the list of coset representatives chosen as generators. + Return the tuple of coset representatives chosen as generators. EXAMPLES:: sage: from sage.modular.pollack_stevens.fund_domain import ManinRelations sage: A = ManinRelations(11) sage: A.gens() - [ + ( [1 0] [ 0 -1] [-1 -1] [0 1], [ 1 3], [ 3 2] - ] + ) """ return self._gens diff --git a/src/sage/modular/quasimodform/ring.py b/src/sage/modular/quasimodform/ring.py index 29df3bfcde0..a1aa46b9be6 100644 --- a/src/sage/modular/quasimodform/ring.py +++ b/src/sage/modular/quasimodform/ring.py @@ -45,9 +45,9 @@ sage: QM.category() Category of commutative graded algebras over Rational Field sage: QM.gens() - [1 - 24*q - 72*q^2 - 96*q^3 - 168*q^4 - 144*q^5 + O(q^6), + (1 - 24*q - 72*q^2 - 96*q^3 - 168*q^4 - 144*q^5 + O(q^6), 1 + 240*q + 2160*q^2 + 6720*q^3 + 17520*q^4 + 30240*q^5 + O(q^6), - 1 - 504*q - 16632*q^2 - 122976*q^3 - 532728*q^4 - 1575504*q^5 + O(q^6)] + 1 - 504*q - 16632*q^2 - 122976*q^3 - 532728*q^4 - 1575504*q^5 + O(q^6)) sage: E2 = QM.0; E4 = QM.1; E6 = QM.2 sage: E2 * E4 + E6 2 - 288*q - 20304*q^2 - 185472*q^3 - 855216*q^4 - 2697408*q^5 + O(q^6) @@ -92,10 +92,10 @@ :meth:`sage.modular.modform.ring.ModularFormsRing.gens`:: sage: QM.gens() - [1 - 24*q - 72*q^2 - 96*q^3 - 168*q^4 - 144*q^5 + O(q^6), + (1 - 24*q - 72*q^2 - 96*q^3 - 168*q^4 - 144*q^5 + O(q^6), 1 + 6*q + 18*q^2 + 24*q^3 + 42*q^4 + 6*q^5 + O(q^6), 1 + 240*q^5 + O(q^6), - q + 10*q^3 + 28*q^4 + 35*q^5 + O(q^6)] + q + 10*q^3 + 28*q^4 + 35*q^5 + O(q^6)) sage: QM.modular_forms_subring().gens() [1 + 6*q + 18*q^2 + 24*q^3 + 42*q^4 + 6*q^5 + O(q^6), 1 + 240*q^5 + O(q^6), @@ -200,9 +200,9 @@ class QuasiModularForms(Parent, UniqueRepresentation): sage: QM = QuasiModularForms(1); QM Ring of Quasimodular Forms for Modular Group SL(2,Z) over Rational Field sage: QM.gens() - [1 - 24*q - 72*q^2 - 96*q^3 - 168*q^4 - 144*q^5 + O(q^6), + (1 - 24*q - 72*q^2 - 96*q^3 - 168*q^4 - 144*q^5 + O(q^6), 1 + 240*q + 2160*q^2 + 6720*q^3 + 17520*q^4 + 30240*q^5 + O(q^6), - 1 - 504*q - 16632*q^2 - 122976*q^3 - 532728*q^4 - 1575504*q^5 + O(q^6)] + 1 - 504*q - 16632*q^2 - 122976*q^3 - 532728*q^4 - 1575504*q^5 + O(q^6)) It is possible to access the weight 2 Eisenstein series:: @@ -454,9 +454,9 @@ def weight_2_eisenstein_series(self): """ return self(self.__polynomial_subring.gen()) - def gens(self): + def gens(self) -> tuple: r""" - Return a list of generators of the quasimodular forms ring. + Return a tuple of generators of the quasimodular forms ring. Note that the generators of the modular forms subring are the one given by the method :meth:`sage.modular.modform.ring.ModularFormsRing.gen_forms` @@ -465,30 +465,30 @@ def gens(self): sage: QM = QuasiModularForms(1) sage: QM.gens() - [1 - 24*q - 72*q^2 - 96*q^3 - 168*q^4 - 144*q^5 + O(q^6), + (1 - 24*q - 72*q^2 - 96*q^3 - 168*q^4 - 144*q^5 + O(q^6), 1 + 240*q + 2160*q^2 + 6720*q^3 + 17520*q^4 + 30240*q^5 + O(q^6), - 1 - 504*q - 16632*q^2 - 122976*q^3 - 532728*q^4 - 1575504*q^5 + O(q^6)] + 1 - 504*q - 16632*q^2 - 122976*q^3 - 532728*q^4 - 1575504*q^5 + O(q^6)) sage: QM.modular_forms_subring().gen_forms() [1 + 240*q + 2160*q^2 + 6720*q^3 + 17520*q^4 + 30240*q^5 + O(q^6), 1 - 504*q - 16632*q^2 - 122976*q^3 - 532728*q^4 - 1575504*q^5 + O(q^6)] sage: QM = QuasiModularForms(5) sage: QM.gens() - [1 - 24*q - 72*q^2 - 96*q^3 - 168*q^4 - 144*q^5 + O(q^6), + (1 - 24*q - 72*q^2 - 96*q^3 - 168*q^4 - 144*q^5 + O(q^6), 1 + 6*q + 18*q^2 + 24*q^3 + 42*q^4 + 6*q^5 + O(q^6), 1 + 240*q^5 + O(q^6), - q + 10*q^3 + 28*q^4 + 35*q^5 + O(q^6)] + q + 10*q^3 + 28*q^4 + 35*q^5 + O(q^6)) An alias of this method is ``generators``:: sage: QuasiModularForms(1).generators() - [1 - 24*q - 72*q^2 - 96*q^3 - 168*q^4 - 144*q^5 + O(q^6), + (1 - 24*q - 72*q^2 - 96*q^3 - 168*q^4 - 144*q^5 + O(q^6), 1 + 240*q + 2160*q^2 + 6720*q^3 + 17520*q^4 + 30240*q^5 + O(q^6), - 1 - 504*q - 16632*q^2 - 122976*q^3 - 532728*q^4 - 1575504*q^5 + O(q^6)] + 1 - 504*q - 16632*q^2 - 122976*q^3 - 532728*q^4 - 1575504*q^5 + O(q^6)) """ gen_list = [self.weight_2_eisenstein_series()] - for f in self.__modular_forms_subring.gen_forms(): - gen_list.append(self(f)) - return gen_list + gen_list.extend(self(f) + for f in self.__modular_forms_subring.gen_forms()) + return tuple(gen_list) generators = gens # alias @@ -529,7 +529,7 @@ def gen(self, n): sage: QM.4 Traceback (most recent call last): ... - IndexError: list index out of range + IndexError: tuple index out of range """ return self.gens()[n] @@ -568,9 +568,9 @@ def some_elements(self): EXAMPLES:: sage: QuasiModularForms(1).some_elements() - [1 - 24*q - 72*q^2 - 96*q^3 - 168*q^4 - 144*q^5 + O(q^6), + (1 - 24*q - 72*q^2 - 96*q^3 - 168*q^4 - 144*q^5 + O(q^6), 1 + 240*q + 2160*q^2 + 6720*q^3 + 17520*q^4 + 30240*q^5 + O(q^6), - 1 - 504*q - 16632*q^2 - 122976*q^3 - 532728*q^4 - 1575504*q^5 + O(q^6)] + 1 - 504*q - 16632*q^2 - 122976*q^3 - 532728*q^4 - 1575504*q^5 + O(q^6)) """ return self.gens() From f910c5627df27122671c12023371420e21c6f8fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Fri, 7 Feb 2025 17:14:55 +0100 Subject: [PATCH 233/369] fixing doctests --- src/sage/modular/drinfeld_modform/tutorial.py | 2 +- src/sage/modular/modform_hecketriangle/readme.py | 14 +++++++------- src/sage/modular/pollack_stevens/manin_map.py | 8 ++++---- src/sage/modular/quasimodform/element.py | 4 ++-- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/sage/modular/drinfeld_modform/tutorial.py b/src/sage/modular/drinfeld_modform/tutorial.py index e4f64f10daf..3753e3e9ab7 100644 --- a/src/sage/modular/drinfeld_modform/tutorial.py +++ b/src/sage/modular/drinfeld_modform/tutorial.py @@ -213,7 +213,7 @@ sage: M = DrinfeldModularForms(K, 4, has_type=True) sage: M.gens() - [g1, g2, g3, h4] + (g1, g2, g3, h4) sage: h4 = M.3 sage: h4.weight() 40 diff --git a/src/sage/modular/modform_hecketriangle/readme.py b/src/sage/modular/modform_hecketriangle/readme.py index 60747e9578a..409a437473b 100644 --- a/src/sage/modular/modform_hecketriangle/readme.py +++ b/src/sage/modular/modform_hecketriangle/readme.py @@ -1015,12 +1015,12 @@ sage: QF = QuasiWeakModularForms(n=8, k=10/3, ep=-1) sage: QF.default_prec(1) sage: QF.quasi_part_gens(min_exp=-1) - [q^-1 + O(q), + (q^-1 + O(q), 1 + O(q), q^-1 - 9/(128*d) + O(q), 1 + O(q), q^-1 - 19/(64*d) + O(q), - q^-1 + 1/(64*d) + O(q)] + q^-1 + 1/(64*d) + O(q)) sage: QF.default_prec(QF.required_laurent_prec(min_exp=-1)) sage: QF.q_basis(min_exp=-1) # long time [q^-1 + O(q^5), @@ -1042,9 +1042,9 @@ 3 sage: MF.default_prec(2) sage: MF.gens() - [1 - 37/(200*d)*q + O(q^2), + (1 - 37/(200*d)*q + O(q^2), 1 + 33/(200*d)*q + O(q^2), - 1 - 27/(200*d)*q + O(q^2)] + 1 - 27/(200*d)*q + O(q^2)) - **Coordinate vectors for (quasi) holomorphic modular forms and (quasi) cusp forms:** @@ -1135,7 +1135,7 @@ sage: MF.dimension() 2 sage: MF.gens() - [1 + 240*q^2 + 2160*q^4 + O(q^5), q - 8*q^2 + 28*q^3 - 64*q^4 + O(q^5)] + (1 + 240*q^2 + 2160*q^4 + O(q^5), q - 8*q^2 + 28*q^3 - 64*q^4 + O(q^5)) sage: E4(i) 1.941017189... sage: E4.order_at(-1) @@ -1143,8 +1143,8 @@ sage: MF = (E2/E4).reduced_parent() sage: MF.quasi_part_gens(order_1=-1) - [1 - 40*q + 552*q^2 - 4896*q^3 + 33320*q^4 + O(q^5), - 1 - 24*q + 264*q^2 - 2016*q^3 + 12264*q^4 + O(q^5)] + (1 - 40*q + 552*q^2 - 4896*q^3 + 33320*q^4 + O(q^5), + 1 - 24*q + 264*q^2 - 2016*q^3 + 12264*q^4 + O(q^5)) sage: prec = MF.required_laurent_prec(order_1=-1) sage: qexp = (E2/E4).q_expansion(prec=prec) sage: qexp diff --git a/src/sage/modular/pollack_stevens/manin_map.py b/src/sage/modular/pollack_stevens/manin_map.py index 04120ba5d7d..9d22bdadbc5 100644 --- a/src/sage/modular/pollack_stevens/manin_map.py +++ b/src/sage/modular/pollack_stevens/manin_map.py @@ -315,13 +315,13 @@ def __getitem__(self, B): sage: from sage.modular.pollack_stevens.fund_domain import ManinRelations sage: S = Symk(0,QQ) sage: MR = ManinRelations(37); MR.gens() - [ + ( [1 0] [ 0 -1] [-1 -1] [-1 -2] [-2 -3] [-3 -1] [-1 -4] [-4 -3] [0 1], [ 1 4], [ 4 3], [ 3 5], [ 5 7], [ 7 2], [ 2 7], [ 7 5], [-2 -3] [ 3 4] - ] + ) sage: data = {M2Z([-2,-3,5,7]): S(0), M2Z([1,0,0,1]): S(0), M2Z([-1,-2,3,5]): S(0), M2Z([-1,-4,2,7]): S(1), M2Z([0,-1,1,4]): S(1), M2Z([-3,-1,7,2]): S(-1), M2Z([-2,-3,3,4]): S(0), M2Z([-4,-3,7,5]): S(0), M2Z([-1,-1,4,3]): S(0)} sage: D = OverconvergentDistributions(2, 37, 40) @@ -354,13 +354,13 @@ def compute_full_data(self): sage: from sage.modular.pollack_stevens.fund_domain import ManinRelations sage: S = Symk(0,QQ) sage: MR = ManinRelations(37); MR.gens() - [ + ( [1 0] [ 0 -1] [-1 -1] [-1 -2] [-2 -3] [-3 -1] [-1 -4] [-4 -3] [0 1], [ 1 4], [ 4 3], [ 3 5], [ 5 7], [ 7 2], [ 2 7], [ 7 5], [-2 -3] [ 3 4] - ] + ) sage: data = {M2Z([-2,-3,5,7]): S(0), M2Z([1,0,0,1]): S(0), M2Z([-1,-2,3,5]): S(0), M2Z([-1,-4,2,7]): S(1), M2Z([0,-1,1,4]): S(1), M2Z([-3,-1,7,2]): S(-1), M2Z([-2,-3,3,4]): S(0), M2Z([-4,-3,7,5]): S(0), M2Z([-1,-1,4,3]): S(0)} sage: f = ManinMap(S,MR,data) diff --git a/src/sage/modular/quasimodform/element.py b/src/sage/modular/quasimodform/element.py index c8bbdd1a9a3..142d7ea885b 100644 --- a/src/sage/modular/quasimodform/element.py +++ b/src/sage/modular/quasimodform/element.py @@ -52,9 +52,9 @@ class QuasiModularFormsElement(ModuleElement): sage: QM = QuasiModularForms(1) sage: QM.gens() - [1 - 24*q - 72*q^2 - 96*q^3 - 168*q^4 - 144*q^5 + O(q^6), + (1 - 24*q - 72*q^2 - 96*q^3 - 168*q^4 - 144*q^5 + O(q^6), 1 + 240*q + 2160*q^2 + 6720*q^3 + 17520*q^4 + 30240*q^5 + O(q^6), - 1 - 504*q - 16632*q^2 - 122976*q^3 - 532728*q^4 - 1575504*q^5 + O(q^6)] + 1 - 504*q - 16632*q^2 - 122976*q^3 - 532728*q^4 - 1575504*q^5 + O(q^6)) sage: QM.0 + QM.1 2 + 216*q + 2088*q^2 + 6624*q^3 + 17352*q^4 + 30096*q^5 + O(q^6) sage: QM.0 * QM.1 From cc9d9b15455446e0bfdafb84302a254ebb8f104e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Fri, 7 Feb 2025 13:26:19 +0100 Subject: [PATCH 234/369] add tuple typing to gens methods in algebras --- src/sage/algebras/askey_wilson.py | 2 +- src/sage/algebras/clifford_algebra.py | 4 ++-- src/sage/algebras/cluster_algebra.py | 2 +- src/sage/algebras/down_up_algebra.py | 2 +- src/sage/algebras/free_zinbiel_algebra.py | 2 +- .../algebras/hecke_algebras/ariki_koike_algebra.py | 2 +- .../algebras/hecke_algebras/cubic_hecke_algebra.py | 2 +- src/sage/algebras/jordan_algebra.py | 6 +++--- .../letterplace/free_algebra_letterplace.pyx | 2 +- .../algebras/lie_algebras/classical_lie_algebra.py | 2 +- src/sage/algebras/lie_algebras/free_lie_algebra.py | 2 +- src/sage/algebras/lie_algebras/heisenberg.py | 8 ++++---- src/sage/algebras/lie_algebras/lie_algebra.py | 2 +- src/sage/algebras/lie_algebras/subalgebra.py | 2 +- src/sage/algebras/lie_algebras/verma_module.py | 2 +- src/sage/algebras/octonion_algebra.pyx | 2 +- src/sage/algebras/q_commuting_polynomials.py | 2 +- src/sage/algebras/q_system.py | 2 +- src/sage/algebras/quantum_clifford.py | 2 +- .../algebras/quantum_groups/quantum_group_gap.py | 2 +- .../algebras/quantum_matrix_coordinate_algebra.py | 4 ++-- src/sage/algebras/quatalg/quaternion_algebra.py | 2 +- src/sage/algebras/steenrod/steenrod_algebra.py | 12 ++++++------ src/sage/algebras/yangian.py | 4 ++-- src/sage/algebras/yokonuma_hecke_algebra.py | 2 +- 25 files changed, 38 insertions(+), 38 deletions(-) diff --git a/src/sage/algebras/askey_wilson.py b/src/sage/algebras/askey_wilson.py index f8fc56f5f46..a2411f079cd 100644 --- a/src/sage/algebras/askey_wilson.py +++ b/src/sage/algebras/askey_wilson.py @@ -348,7 +348,7 @@ def build_monomial(g): return Family(A, build_monomial) @cached_method - def gens(self): + def gens(self) -> tuple: r""" Return the generators of ``self``. diff --git a/src/sage/algebras/clifford_algebra.py b/src/sage/algebras/clifford_algebra.py index 091f5e0d559..aa19526e51c 100644 --- a/src/sage/algebras/clifford_algebra.py +++ b/src/sage/algebras/clifford_algebra.py @@ -787,7 +787,7 @@ def gen(self, i): """ return self._from_dict({FrozenBitset((i,)): self.base_ring().one()}, remove_zeros=False) - def algebra_generators(self): + def algebra_generators(self) -> Family: """ Return the algebra generators of ``self``. @@ -801,7 +801,7 @@ def algebra_generators(self): d = {x: self.gen(i) for i, x in enumerate(self.variable_names())} return Family(self.variable_names(), lambda x: d[x]) - def gens(self): + def gens(self) -> tuple: r""" Return the generators of ``self`` (as an algebra). diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index ca34a34acfc..5cb746983f7 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -2094,7 +2094,7 @@ def retract(self, x): return self(x) @cached_method - def gens(self): + def gens(self) -> tuple: r""" Return the list of initial cluster variables and coefficients of ``self``. diff --git a/src/sage/algebras/down_up_algebra.py b/src/sage/algebras/down_up_algebra.py index 48e24c4fa2d..d7a8c3b14cd 100644 --- a/src/sage/algebras/down_up_algebra.py +++ b/src/sage/algebras/down_up_algebra.py @@ -319,7 +319,7 @@ def algebra_generators(self): return Family({'d': d, 'u': u}) @cached_method - def gens(self): + def gens(self) -> tuple: r""" Return the generators of ``self``. diff --git a/src/sage/algebras/free_zinbiel_algebra.py b/src/sage/algebras/free_zinbiel_algebra.py index 875fde1fba9..1be0a4600be 100644 --- a/src/sage/algebras/free_zinbiel_algebra.py +++ b/src/sage/algebras/free_zinbiel_algebra.py @@ -343,7 +343,7 @@ def change_ring(self, R): return FreeZinbielAlgebra(R, n=len(A), names=A, side=self._side) @cached_method - def gens(self): + def gens(self) -> tuple: """ Return the generators of ``self``. diff --git a/src/sage/algebras/hecke_algebras/ariki_koike_algebra.py b/src/sage/algebras/hecke_algebras/ariki_koike_algebra.py index 2e6cba1e965..336e8c28a40 100644 --- a/src/sage/algebras/hecke_algebras/ariki_koike_algebra.py +++ b/src/sage/algebras/hecke_algebras/ariki_koike_algebra.py @@ -532,7 +532,7 @@ def cyclotomic_parameters(self): u = cyclotomic_parameters @cached_method - def gens(self): + def gens(self) -> tuple: r""" Return the generators of ``self``. diff --git a/src/sage/algebras/hecke_algebras/cubic_hecke_algebra.py b/src/sage/algebras/hecke_algebras/cubic_hecke_algebra.py index ac5d9b945f7..ecb9551561f 100644 --- a/src/sage/algebras/hecke_algebras/cubic_hecke_algebra.py +++ b/src/sage/algebras/hecke_algebras/cubic_hecke_algebra.py @@ -1415,7 +1415,7 @@ def algebra_generators(self): from sage.sets.family import Family return Family(self._cubic_braid_group.gens(), self.monomial) - def gens(self): + def gens(self) -> tuple: r""" Return the generators of ``self``. diff --git a/src/sage/algebras/jordan_algebra.py b/src/sage/algebras/jordan_algebra.py index 0b6fc0111bf..f5881fbba88 100644 --- a/src/sage/algebras/jordan_algebra.py +++ b/src/sage/algebras/jordan_algebra.py @@ -328,7 +328,7 @@ def basis(self): algebra_generators = basis # TODO: Keep this until we can better handle R.<...> shorthand - def gens(self): + def gens(self) -> tuple: """ Return the generators of ``self``. @@ -763,7 +763,7 @@ def basis(self): algebra_generators = basis - def gens(self): + def gens(self) -> tuple: """ Return the generators of ``self``. @@ -1316,7 +1316,7 @@ def basis(self): algebra_generators = basis - def gens(self): + def gens(self) -> tuple: """ Return the generators of ``self``. diff --git a/src/sage/algebras/letterplace/free_algebra_letterplace.pyx b/src/sage/algebras/letterplace/free_algebra_letterplace.pyx index 10146c36aeb..188600b3006 100644 --- a/src/sage/algebras/letterplace/free_algebra_letterplace.pyx +++ b/src/sage/algebras/letterplace/free_algebra_letterplace.pyx @@ -348,7 +348,7 @@ cdef class FreeAlgebra_letterplace(Parent): p *= self._current_ring.gen(j) return FreeAlgebraElement_letterplace(self, p) - def gens(self): + def gens(self) -> tuple: """ Return the tuple of generators. diff --git a/src/sage/algebras/lie_algebras/classical_lie_algebra.py b/src/sage/algebras/lie_algebras/classical_lie_algebra.py index e4e2eb75e68..b149a57576c 100644 --- a/src/sage/algebras/lie_algebras/classical_lie_algebra.py +++ b/src/sage/algebras/lie_algebras/classical_lie_algebra.py @@ -2170,7 +2170,7 @@ def _part_generators(self, positive=False): return Family(I, d.__getitem__) @cached_method - def gens(self): + def gens(self) -> tuple: r""" Return the generators of ``self`` in the order of `e_i`, `f_i`, and `h_i`. diff --git a/src/sage/algebras/lie_algebras/free_lie_algebra.py b/src/sage/algebras/lie_algebras/free_lie_algebra.py index e841be77a59..08462de239b 100644 --- a/src/sage/algebras/lie_algebras/free_lie_algebra.py +++ b/src/sage/algebras/lie_algebras/free_lie_algebra.py @@ -419,7 +419,7 @@ def lie_algebra_generators(self): """ return self.Lyndon().lie_algebra_generators() - def gens(self): + def gens(self) -> tuple: """ Return the generators of ``self`` in the Lyndon basis. diff --git a/src/sage/algebras/lie_algebras/heisenberg.py b/src/sage/algebras/lie_algebras/heisenberg.py index de4a6b27562..377ac303465 100644 --- a/src/sage/algebras/lie_algebras/heisenberg.py +++ b/src/sage/algebras/lie_algebras/heisenberg.py @@ -6,15 +6,15 @@ - Travis Scrimshaw (2013-08-13): Initial version """ -#***************************************************************************** +# *************************************************************************** # Copyright (C) 2013-2017 Travis Scrimshaw # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. -# http://www.gnu.org/licenses/ -#***************************************************************************** +# https://www.gnu.org/licenses/ +# *************************************************************************** from sage.misc.cachefunc import cached_method from sage.structure.indexed_generators import IndexedGenerators @@ -234,7 +234,7 @@ def n(self): return self._n @cached_method - def gens(self): + def gens(self) -> tuple: """ Return the Lie algebra generators of ``self``. diff --git a/src/sage/algebras/lie_algebras/lie_algebra.py b/src/sage/algebras/lie_algebras/lie_algebra.py index 12a992bbffd..11ebbeec026 100644 --- a/src/sage/algebras/lie_algebras/lie_algebra.py +++ b/src/sage/algebras/lie_algebras/lie_algebra.py @@ -823,7 +823,7 @@ def lie_algebra_generators(self): return Family(self._indices, self.monomial, name="monomial map") @cached_method - def gens(self): + def gens(self) -> tuple: """ Return a tuple whose entries are the generators for this object, in some order. diff --git a/src/sage/algebras/lie_algebras/subalgebra.py b/src/sage/algebras/lie_algebras/subalgebra.py index 1dd5444f766..84fb9addb5a 100644 --- a/src/sage/algebras/lie_algebras/subalgebra.py +++ b/src/sage/algebras/lie_algebras/subalgebra.py @@ -646,7 +646,7 @@ def retract(self, X): return self.element_class(self, X) - def gens(self): + def gens(self) -> tuple: r""" Return the generating set of ``self``. diff --git a/src/sage/algebras/lie_algebras/verma_module.py b/src/sage/algebras/lie_algebras/verma_module.py index 71ea8e68cb9..09962f11b6d 100644 --- a/src/sage/algebras/lie_algebras/verma_module.py +++ b/src/sage/algebras/lie_algebras/verma_module.py @@ -356,7 +356,7 @@ def highest_weight_vector(self): return self._from_dict({self._indices.one(): one}, remove_zeros=False, coerce=False) - def gens(self): + def gens(self) -> tuple: r""" Return the generators of ``self`` as a `U(\mathfrak{g})`-module. diff --git a/src/sage/algebras/octonion_algebra.pyx b/src/sage/algebras/octonion_algebra.pyx index c38250b0be7..46e2028db53 100644 --- a/src/sage/algebras/octonion_algebra.pyx +++ b/src/sage/algebras/octonion_algebra.pyx @@ -952,7 +952,7 @@ class OctonionAlgebra(UniqueRepresentation, Parent): return 0 @cached_method - def gens(self): + def gens(self) -> tuple: r""" Return the generators of ``self``. diff --git a/src/sage/algebras/q_commuting_polynomials.py b/src/sage/algebras/q_commuting_polynomials.py index f19eaa042e4..6e2647a1be2 100644 --- a/src/sage/algebras/q_commuting_polynomials.py +++ b/src/sage/algebras/q_commuting_polynomials.py @@ -147,7 +147,7 @@ def gen(self, i): return self.monomial(self._indices.gen(i)) @cached_method - def gens(self): + def gens(self) -> tuple: r""" Return the generators of ``self``. diff --git a/src/sage/algebras/q_system.py b/src/sage/algebras/q_system.py index a8342434847..dc4dd4d17f5 100644 --- a/src/sage/algebras/q_system.py +++ b/src/sage/algebras/q_system.py @@ -382,7 +382,7 @@ def algebra_generators(self): d = {a: self.Q(a, 1) for a in I} return Family(I, d.__getitem__) - def gens(self): + def gens(self) -> tuple: """ Return the generators of ``self``. diff --git a/src/sage/algebras/quantum_clifford.py b/src/sage/algebras/quantum_clifford.py index 99c0c1e226f..60a280820c7 100644 --- a/src/sage/algebras/quantum_clifford.py +++ b/src/sage/algebras/quantum_clifford.py @@ -301,7 +301,7 @@ def algebra_generators(self): return Family(sorted(d), lambda i: d[i]) @cached_method - def gens(self): + def gens(self) -> tuple: r""" Return the generators of ``self``. diff --git a/src/sage/algebras/quantum_groups/quantum_group_gap.py b/src/sage/algebras/quantum_groups/quantum_group_gap.py index 931ee211ff1..4e95d692dd6 100644 --- a/src/sage/algebras/quantum_groups/quantum_group_gap.py +++ b/src/sage/algebras/quantum_groups/quantum_group_gap.py @@ -500,7 +500,7 @@ def zero(self): return self.element_class(self, self._libgap.ZeroImmutable()) @cached_method - def gens(self): + def gens(self) -> tuple: """ Return the generators of ``self``. diff --git a/src/sage/algebras/quantum_matrix_coordinate_algebra.py b/src/sage/algebras/quantum_matrix_coordinate_algebra.py index 9ddde4376c8..c0bff39501e 100644 --- a/src/sage/algebras/quantum_matrix_coordinate_algebra.py +++ b/src/sage/algebras/quantum_matrix_coordinate_algebra.py @@ -188,7 +188,7 @@ def one_basis(self): return self._indices.one() @cached_method - def gens(self): + def gens(self) -> tuple: r""" Return the generators of ``self`` as a tuple. @@ -586,7 +586,7 @@ def m(self): return self._m @cached_method - def algebra_generators(self): + def algebra_generators(self) -> Family: """ Return the algebra generators of ``self``. diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index 2409db3840b..6c535334361 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -1694,7 +1694,7 @@ def one(self): """ return self.quaternion_algebra().one() - def gens(self): + def gens(self) -> tuple: """ Return generators for ``self``. diff --git a/src/sage/algebras/steenrod/steenrod_algebra.py b/src/sage/algebras/steenrod/steenrod_algebra.py index e9b86a59098..953bfc00bbe 100644 --- a/src/sage/algebras/steenrod/steenrod_algebra.py +++ b/src/sage/algebras/steenrod/steenrod_algebra.py @@ -452,12 +452,13 @@ # Distributed under the terms of the GNU General Public License (GPL) # **************************************************************************** -from sage.combinat.free_module import CombinatorialFreeModule -from sage.misc.lazy_attribute import lazy_attribute -from sage.misc.cachefunc import cached_method +from sage.categories.homset import Hom from sage.categories.modules_with_basis import ModulesWithBasis from sage.categories.tensor import tensor -from sage.categories.homset import Hom +from sage.combinat.free_module import CombinatorialFreeModule +from sage.misc.cachefunc import cached_method +from sage.misc.lazy_attribute import lazy_attribute +from sage.sets.family import Family ###################################################### # the main class @@ -2619,7 +2620,7 @@ def ngens(self): return sum(self._profile) return sum(self._profile[0]) + len([a for a in self._profile[1] if a == 2]) - def gens(self): + def gens(self) -> Family: r""" Family of generators for this algebra. @@ -2676,7 +2677,6 @@ def gens(self): sage: SteenrodAlgebra(p=5, profile=[[2,1], [2,2,2]]).algebra_generators() Family (Q_0, P(1), P(5)) """ - from sage.sets.family import Family from sage.sets.non_negative_integers import NonNegativeIntegers from sage.rings.infinity import Infinity n = self.ngens() diff --git a/src/sage/algebras/yangian.py b/src/sage/algebras/yangian.py index 748586a30b9..73e5698a8ec 100644 --- a/src/sage/algebras/yangian.py +++ b/src/sage/algebras/yangian.py @@ -832,13 +832,13 @@ def gen(self, r, i=None, j=None): 0 """ if i is None and j is None: - r,i,j = r + r, i, j = r if r > self._level: return self.zero() return Yangian.gen(self, r, i, j) @cached_method - def gens(self): + def gens(self) -> tuple: """ Return the generators of ``self``. diff --git a/src/sage/algebras/yokonuma_hecke_algebra.py b/src/sage/algebras/yokonuma_hecke_algebra.py index f8e8f724adc..00bdde88012 100644 --- a/src/sage/algebras/yokonuma_hecke_algebra.py +++ b/src/sage/algebras/yokonuma_hecke_algebra.py @@ -252,7 +252,7 @@ def algebra_generators(self): return Family(sorted(d), lambda i: d[i]) @cached_method - def gens(self): + def gens(self) -> tuple: """ Return the generators of ``self``. From 450d6f60c00a4e4bd6fe0cd9a3171771a080c7f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Fri, 7 Feb 2025 20:37:52 +0100 Subject: [PATCH 235/369] typing annotation for gens method in rings and groups (pyx files) --- src/sage/groups/libgap_wrapper.pyx | 2 +- src/sage/rings/complex_arb.pyx | 2 +- src/sage/rings/integer_ring.pyx | 2 +- src/sage/rings/polynomial/pbori/pbori.pyx | 4 ++-- src/sage/rings/polynomial/symmetric_reduction.pyx | 2 +- src/sage/rings/real_arb.pyx | 2 +- src/sage/rings/real_mpfi.pyx | 8 ++++---- src/sage/rings/real_mpfr.pyx | 8 ++++---- src/sage/rings/ring_extension.pyx | 4 ++-- src/sage/rings/semirings/tropical_semiring.pyx | 2 +- 10 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/sage/groups/libgap_wrapper.pyx b/src/sage/groups/libgap_wrapper.pyx index fe09dc080c1..771280fdbf9 100644 --- a/src/sage/groups/libgap_wrapper.pyx +++ b/src/sage/groups/libgap_wrapper.pyx @@ -362,7 +362,7 @@ class ParentLibGAP(SageObject): for gap_subgroup in self._libgap.MaximalNormalSubgroups()] @cached_method - def gens(self): + def gens(self) -> tuple: """ Return the generators of the group. diff --git a/src/sage/rings/complex_arb.pyx b/src/sage/rings/complex_arb.pyx index fb9d821a413..89a91f10eab 100644 --- a/src/sage/rings/complex_arb.pyx +++ b/src/sage/rings/complex_arb.pyx @@ -492,7 +492,7 @@ class ComplexBallField(UniqueRepresentation, sage.rings.abc.ComplexBallField): else: raise ValueError("only one generator") - def gens(self): + def gens(self) -> tuple: r""" Return the tuple of generators of this complex ball field, i.e. ``(i,)``. diff --git a/src/sage/rings/integer_ring.pyx b/src/sage/rings/integer_ring.pyx index d6555ac0eab..f565ef22430 100644 --- a/src/sage/rings/integer_ring.pyx +++ b/src/sage/rings/integer_ring.pyx @@ -1036,7 +1036,7 @@ cdef class IntegerRing_class(CommutativeRing): from sage.rings.finite_rings.residue_field import ResidueField return ResidueField(p, names = None, check = check) - def gens(self): + def gens(self) -> tuple: """ Return the tuple ``(1,)`` containing a single element, the additive generator of the integers, which is 1. diff --git a/src/sage/rings/polynomial/pbori/pbori.pyx b/src/sage/rings/polynomial/pbori/pbori.pyx index 404771a2c85..0f4fcd92b8d 100644 --- a/src/sage/rings/polynomial/pbori/pbori.pyx +++ b/src/sage/rings/polynomial/pbori/pbori.pyx @@ -528,7 +528,7 @@ cdef class BooleanPolynomialRing(BooleanPolynomialRing_base): raise ValueError("generator not defined") return new_BP_from_PBVar(self, self._pbring.variable(self.pbind[idx])) - def gens(self): + def gens(self) -> tuple: """ Return the tuple of variables in this ring. @@ -1973,7 +1973,7 @@ class BooleanMonomialMonoid(UniqueRepresentation, Monoid_class): return new_BM_from_PBVar(self, (self._ring), newvar) - def gens(self): + def gens(self) -> tuple: """ Return the tuple of generators of this monoid. diff --git a/src/sage/rings/polynomial/symmetric_reduction.pyx b/src/sage/rings/polynomial/symmetric_reduction.pyx index 0794ca8a941..0559aa77ca8 100644 --- a/src/sage/rings/polynomial/symmetric_reduction.pyx +++ b/src/sage/rings/polynomial/symmetric_reduction.pyx @@ -254,7 +254,7 @@ cdef class SymmetricReductionStrategy: return richcmp((left._parent, left._lm, left._tail), (right._parent, right._lm, right._tail), op) - def gens(self): + def gens(self) -> list: """ Return the list of Infinite Polynomials modulo which ``self`` reduces. diff --git a/src/sage/rings/real_arb.pyx b/src/sage/rings/real_arb.pyx index 3c063f193a8..280e08ea1d7 100644 --- a/src/sage/rings/real_arb.pyx +++ b/src/sage/rings/real_arb.pyx @@ -577,7 +577,7 @@ class RealBallField(UniqueRepresentation, sage.rings.abc.RealBallField): return super()._repr_option(key) - def gens(self): + def gens(self) -> tuple: r""" EXAMPLES:: diff --git a/src/sage/rings/real_mpfi.pyx b/src/sage/rings/real_mpfi.pyx index ce9958ce7e7..63906f4e02b 100644 --- a/src/sage/rings/real_mpfi.pyx +++ b/src/sage/rings/real_mpfi.pyx @@ -950,16 +950,16 @@ cdef class RealIntervalField_class(sage.rings.abc.RealIntervalField): """ return 1 - def gens(self): + def gens(self) -> tuple: """ - Return a list of generators. + Return a tuple of generators. EXAMPLES:: sage: RIF.gens() - [1] + (1,) """ - return [self.gen()] + return (self.gen(),) def _is_valid_homomorphism_(self, codomain, im_gens, base_map=None): """ diff --git a/src/sage/rings/real_mpfr.pyx b/src/sage/rings/real_mpfr.pyx index 31d0bb96aed..1a6acd9a9f7 100644 --- a/src/sage/rings/real_mpfr.pyx +++ b/src/sage/rings/real_mpfr.pyx @@ -907,16 +907,16 @@ cdef class RealField_class(sage.rings.abc.RealField): """ return 1 - def gens(self): + def gens(self) -> tuple: """ - Return a list of generators. + Return a tuple of generators. EXAMPLES:: sage: RR.gens() - [1.00000000000000] + (1.00000000000000,) """ - return [self.gen()] + return (self.gen(),) def _is_valid_homomorphism_(self, codomain, im_gens, base_map=None): """ diff --git a/src/sage/rings/ring_extension.pyx b/src/sage/rings/ring_extension.pyx index 9d8a626094a..96449d10537 100644 --- a/src/sage/rings/ring_extension.pyx +++ b/src/sage/rings/ring_extension.pyx @@ -1314,7 +1314,7 @@ cdef class RingExtension_generic(Parent): elt = self._backend.an_element() return self.element_class(self, elt) - def gens(self, base=None): + def gens(self, base=None) -> tuple: r""" Return the generators of this extension over ``base``. @@ -2661,7 +2661,7 @@ cdef class RingExtensionWithGen(RingExtensionWithBasis): S = PolynomialRing(self._base, name=var) return S(coeffs) - def gens(self, base=None): + def gens(self, base=None) -> tuple: r""" Return the generators of this extension over ``base``. diff --git a/src/sage/rings/semirings/tropical_semiring.pyx b/src/sage/rings/semirings/tropical_semiring.pyx index 205966541ab..993086004c6 100644 --- a/src/sage/rings/semirings/tropical_semiring.pyx +++ b/src/sage/rings/semirings/tropical_semiring.pyx @@ -640,7 +640,7 @@ class TropicalSemiring(Parent, UniqueRepresentation): multiplicative_identity = one - def gens(self): + def gens(self) -> tuple: """ Return the generators of ``self``. From b95f344a698d26faae50de392c773fe53d70f777 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Fri, 7 Feb 2025 20:53:56 +0100 Subject: [PATCH 236/369] typing and details in tableaux files --- src/sage/combinat/skew_tableau.py | 18 +++++++++--------- src/sage/combinat/tableau.py | 26 +++++++++++++------------- src/sage/combinat/tableau_tuple.py | 12 ++++++------ 3 files changed, 28 insertions(+), 28 deletions(-) diff --git a/src/sage/combinat/skew_tableau.py b/src/sage/combinat/skew_tableau.py index 44189013d5c..469a84e9ee9 100644 --- a/src/sage/combinat/skew_tableau.py +++ b/src/sage/combinat/skew_tableau.py @@ -620,7 +620,7 @@ def weight(self): evaluation = weight - def is_standard(self): + def is_standard(self) -> bool: """ Return ``True`` if ``self`` is a standard skew tableau and ``False`` otherwise. @@ -640,7 +640,7 @@ def is_standard(self): w = [i for row in self for i in row if i is not None] return sorted(w) == list(range(1, len(w) + 1)) and self.is_semistandard() - def is_semistandard(self): + def is_semistandard(self) -> bool: """ Return ``True`` if ``self`` is a semistandard skew tableau and ``False`` otherwise. @@ -1324,7 +1324,7 @@ def shuffle(self, t2): corner = self.cells_containing(i)[0] # slide t2_new backwards, record i in the vacated square - (t2_new, (x, y)) = t2_new.slide(corner, True) + t2_new, (x, y) = t2_new.slide(corner, True) t1_new[x][y] = i t1_new = SkewTableau(t1_new) @@ -1582,10 +1582,12 @@ def to_expr(self): rows.reverse() return [self.inner_shape(), rows] - def is_ribbon(self): + def is_ribbon(self) -> bool: r""" Return ``True`` if and only if the shape of ``self`` is a - ribbon, that is, if it has exactly one cell in each of `q` + ribbon. + + This means that it has exactly one cell in each of `q` consecutive diagonals for some nonnegative integer `q`. EXAMPLES:: @@ -1824,7 +1826,7 @@ def cells_containing(self, i): cell_list.append((r, c)) return cell_list - def is_k_tableau(self, k): + def is_k_tableau(self, k) -> bool: r""" Check whether ``self`` is a valid skew weak `k`-tableau. @@ -1857,11 +1859,9 @@ def _label_skew(list_of_cells, sk): sage: skew_tableau._label_skew(l, empty) [[1, 4], [3, 2]] """ - i = 1 skew = [list(row) for row in sk] - for row, column in list_of_cells: + for i, (row, column) in enumerate(list_of_cells, start=1): skew[row][column] = i - i += 1 return skew diff --git a/src/sage/combinat/tableau.py b/src/sage/combinat/tableau.py index eef2536beba..8b33b1cbd66 100644 --- a/src/sage/combinat/tableau.py +++ b/src/sage/combinat/tableau.py @@ -1694,7 +1694,7 @@ def weight(self): evaluation = weight - def is_row_strict(self): + def is_row_strict(self) -> bool: """ Return ``True`` if ``self`` is a row strict tableau and ``False`` otherwise. @@ -1715,7 +1715,7 @@ def is_row_strict(self): """ return all(row[i] < row[i+1] for row in self for i in range(len(row)-1)) - def is_row_increasing(self, weak=False): + def is_row_increasing(self, weak=False) -> bool: r""" Return ``True`` if the entries in each row are in increasing order, and ``False`` otherwise. @@ -1741,7 +1741,7 @@ def test(a, b): return a < b return all(test(a, b) for row in self for (a, b) in zip(row, row[1:])) - def is_column_increasing(self, weak=False): + def is_column_increasing(self, weak=False) -> bool: r""" Return ``True`` if the entries in each column are in increasing order, and ``False`` otherwise. @@ -1770,7 +1770,7 @@ def tworow(a, b): return all(test(a[i], b_i) for i, b_i in enumerate(b)) return all(tworow(self[r], self[r + 1]) for r in range(len(self) - 1)) - def is_column_strict(self): + def is_column_strict(self) -> bool: """ Return ``True`` if ``self`` is a column strict tableau and ``False`` otherwise. @@ -1801,7 +1801,7 @@ def tworow(a, b): return all(a[i] < b_i for i, b_i in enumerate(b)) return all(tworow(self[r], self[r+1]) for r in range(len(self)-1)) - def is_semistandard(self): + def is_semistandard(self) -> bool: r""" Return ``True`` if ``self`` is a semistandard tableau, and ``False`` otherwise. @@ -1824,7 +1824,7 @@ def is_semistandard(self): """ return self.is_row_increasing(weak=True) and self.is_column_increasing() - def is_standard(self): + def is_standard(self) -> bool: """ Return ``True`` if ``self`` is a standard tableau and ``False`` otherwise. @@ -1843,7 +1843,7 @@ def is_standard(self): entries = sorted(self.entries()) return entries == list(range(1, self.size() + 1)) and self.is_row_strict() and self.is_column_strict() - def is_increasing(self): + def is_increasing(self) -> bool: """ Return ``True`` if ``self`` is an increasing tableau and ``False`` otherwise. @@ -1865,7 +1865,7 @@ def is_increasing(self): """ return self.is_row_strict() and self.is_column_strict() - def is_rectangular(self): + def is_rectangular(self) -> bool: """ Return ``True`` if the tableau ``self`` is rectangular and ``False`` otherwise. @@ -2067,7 +2067,7 @@ def k_weight(self, k): return res - def is_k_tableau(self, k): + def is_k_tableau(self, k) -> bool: r""" Check whether ``self`` is a valid weak `k`-tableau. @@ -2518,7 +2518,7 @@ def reverse_bump(self, loc): if not (self.is_semistandard()): raise ValueError("reverse bumping is only defined for semistandard tableaux") try: - (r, c) = loc + r, c = loc if (r, c) not in self.corners(): raise ValueError("invalid corner") except TypeError: @@ -3177,7 +3177,7 @@ def add_entry(self, cell, m): IndexError: (2, 2) is not an addable cell of the tableau """ tab = self.to_list() - (r, c) = cell + r, c = cell try: tab[r][c] = m # will work if we are replacing an entry except IndexError: @@ -3616,7 +3616,7 @@ def symmetric_group_action_on_entries(self, w): except Exception: return Tableau([[w[entry-1] for entry in row] for row in self]) - def is_key_tableau(self): + def is_key_tableau(self) -> bool: r""" Return ``True`` if ``self`` is a key tableau or ``False`` otherwise. @@ -4788,7 +4788,7 @@ def dominates(self, t): return all(self.restrict(m).shape().dominates(t.restrict(m).shape()) for m in range(1, 1 + self.size())) - def is_standard(self): + def is_standard(self) -> bool: """ Return ``True`` since ``self`` is a standard tableau. diff --git a/src/sage/combinat/tableau_tuple.py b/src/sage/combinat/tableau_tuple.py index 6c75106154b..863b47dc6a6 100644 --- a/src/sage/combinat/tableau_tuple.py +++ b/src/sage/combinat/tableau_tuple.py @@ -823,7 +823,7 @@ def entry(self, l, r, c): """ return self[l][r][c] - def is_row_strict(self): + def is_row_strict(self) -> bool: """ Return ``True`` if the tableau ``self`` is row strict and ``False`` otherwise. @@ -874,7 +874,7 @@ def first_row_descent(self): return (k, cell[0], cell[1]) return None - def is_column_strict(self): + def is_column_strict(self) -> bool: """ Return ``True`` if the tableau ``self`` is column strict and ``False`` otherwise. @@ -925,7 +925,7 @@ def first_column_descent(self): return (k, cell[0], cell[1]) return None - def is_standard(self): + def is_standard(self) -> bool: r""" Return ``True`` if the tableau ``self`` is a standard tableau and ``False`` otherwise. @@ -1173,7 +1173,7 @@ def add_entry(self, cell, m): ... IndexError: (2, 1, 2) is not an addable cell of the tableau """ - (k, r, c) = cell + k, r, c = cell tab = self.to_list() try: @@ -5017,7 +5017,7 @@ def random_element(self): while m < mu.size(): m += 1 i = randint(0, len(addables) - 1) # index for a random addable cell - (k, r, c) = addables[i] # the actual cell + k, r, c = addables[i] # the actual cell # remove the cell we just added from the list addable nodes addables.pop(i) # add m into the tableau @@ -5336,7 +5336,7 @@ def _add_entry_fast(T, cell, m): 6 8 12 14 2 11 10 """ - (k, r, c) = cell + k, r, c = cell tab = T.to_list() try: From 400bd424232aa508c34388a17249b311073a4f7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Fri, 7 Feb 2025 21:06:17 +0100 Subject: [PATCH 237/369] two more details in tableaux --- src/sage/combinat/ribbon_tableau.py | 4 ++-- src/sage/combinat/tableau.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sage/combinat/ribbon_tableau.py b/src/sage/combinat/ribbon_tableau.py index 6a1643a46c7..66e6348e58b 100644 --- a/src/sage/combinat/ribbon_tableau.py +++ b/src/sage/combinat/ribbon_tableau.py @@ -854,8 +854,8 @@ def weight(self): sage: a.weight() [5, 3, 1] """ - weights = [x.weight() for x in self] - m = max([len(x) for x in weights]) + weights = (x.weight() for x in self) + m = max(len(x) for x in weights) weight = [0] * m for w in weights: for i in range(len(w)): diff --git a/src/sage/combinat/tableau.py b/src/sage/combinat/tableau.py index 8b33b1cbd66..c9d1164d0a5 100644 --- a/src/sage/combinat/tableau.py +++ b/src/sage/combinat/tableau.py @@ -1298,7 +1298,7 @@ def to_sign_matrix(self, max_entry=None): raise ValueError("the entries must be nonnegative integers") from sage.matrix.matrix_space import MatrixSpace if max_entry is None: - max_entry = max([max(c) for c in self]) + max_entry = max(max(c) for c in self) MS = MatrixSpace(ZZ, len(self[0]), max_entry) Tconj = self.conjugate() conj_len = len(Tconj) From 033c93b2b263cc61f00528658038023c6c762eb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Fri, 7 Feb 2025 21:59:21 +0100 Subject: [PATCH 238/369] fix mistake --- src/sage/combinat/ribbon_tableau.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/sage/combinat/ribbon_tableau.py b/src/sage/combinat/ribbon_tableau.py index 66e6348e58b..a849c6ac88f 100644 --- a/src/sage/combinat/ribbon_tableau.py +++ b/src/sage/combinat/ribbon_tableau.py @@ -26,7 +26,8 @@ from sage.rings.integer import Integer from sage.combinat.combinat import CombinatorialElement from sage.combinat.skew_partition import SkewPartition, SkewPartitions -from sage.combinat.skew_tableau import SkewTableau, SkewTableaux, SemistandardSkewTableaux +from sage.combinat.skew_tableau import (SkewTableau, SkewTableaux, + SemistandardSkewTableaux) from sage.combinat.tableau import Tableaux from sage.combinat.partition import Partition, _Partitions from sage.combinat.permutation import to_standard @@ -94,7 +95,8 @@ def __classcall_private__(cls, rt=None, expr=None): try: rt = [tuple(row) for row in rt] except TypeError: - raise TypeError("each element of the ribbon tableau must be an iterable") + raise TypeError("each element of the ribbon tableau " + "must be an iterable") if not all(row for row in rt): raise TypeError("a ribbon tableau cannot have empty rows") # calls the inherited __init__ method (of SkewTableau ) @@ -195,9 +197,10 @@ class RibbonTableaux(UniqueRepresentation, Parent): REFERENCES: - .. [vanLeeuwen91] Marc. A. A. van Leeuwen, *Edge sequences, ribbon tableaux, - and an action of affine permutations*. Europe J. Combinatorics. **20** - (1999). http://wwwmathlabo.univ-poitiers.fr/~maavl/pdf/edgeseqs.pdf + .. [vanLeeuwen91] Marc. A. A. van Leeuwen, *Edge sequences, + ribbon tableaux, and an action of affine permutations*. + Europe J. Combinatorics. **20** (1999). + http://wwwmathlabo.univ-poitiers.fr/~maavl/pdf/edgeseqs.pdf """ @staticmethod def __classcall_private__(cls, shape=None, weight=None, length=None): @@ -318,10 +321,11 @@ def __iter__(self): sage: RibbonTableaux([[2,2],[]],[1,1],2).list() [[[0, 0], [1, 2]], [[1, 0], [2, 0]]] """ - for x in graph_implementation_rec(self._shape, self._weight, self._length, list_rec): + for x in graph_implementation_rec(self._shape, self._weight, + self._length, list_rec): yield self.from_expr(x) - def _repr_(self): + def _repr_(self) -> str: """ Return a string representation of ``self``. @@ -332,7 +336,7 @@ def _repr_(self): """ return "Ribbon tableaux of shape %s and weight %s with %s-ribbons" % (repr(self._shape), list(self._weight), self._length) - def __contains__(self, x): + def __contains__(self, x) -> bool: """ Note that this just checks to see if ``x`` appears in ``self``. @@ -854,7 +858,7 @@ def weight(self): sage: a.weight() [5, 3, 1] """ - weights = (x.weight() for x in self) + weights = [x.weight() for x in self] m = max(len(x) for x in weights) weight = [0] * m for w in weights: From 3e386bb9bf4821f7da929758ba89d32c93f04424 Mon Sep 17 00:00:00 2001 From: mklss <59539887+mklss@users.noreply.github.com> Date: Sat, 8 Feb 2025 22:05:03 +0100 Subject: [PATCH 239/369] Address comments. --- src/sage/parallel/decorate.py | 8 ++++---- src/sage/parallel/use_fork.py | 9 +++++---- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/sage/parallel/decorate.py b/src/sage/parallel/decorate.py index fc38410b7bb..a344ddcfa5a 100644 --- a/src/sage/parallel/decorate.py +++ b/src/sage/parallel/decorate.py @@ -308,7 +308,7 @@ def parallel(p_iter='fork', ncpus=None, **kwds): - ``ncpus`` -- integer; maximal number of subprocesses to use at the same time - ``timeout`` -- number of seconds until each subprocess is killed (only supported by ``'fork'``; zero means not at all) - - ``reseed_rng``: reseed the rng in each subprocess + - ``reseed_rng``: reseed the rng (random number generator) in each subprocess .. warning:: @@ -402,10 +402,10 @@ def parallel(p_iter='fork', ncpus=None, **kwds): By default, all subprocesses use the same random seed and therefore the same deterministic randomness. For functions that should be randomized, we can reseed the random seed in each subprocess:: - sage: @parallel(reseed_rng = True) - ....: def unif(n): return ZZ.random_element(x = 0, y = n) + sage: @parallel(reseed_rng=True) + ....: def unif(n): return ZZ.random_element(x=0, y=n) sage: set_random_seed(42) - sage: sorted(unif([1000]*3)) + sage: sorted(unif([1000]*3)) # random [(((1000,), {}), 444), (((1000,), {}), 597), (((1000,), {}), 640)] .. warning:: diff --git a/src/sage/parallel/use_fork.py b/src/sage/parallel/use_fork.py index 89aa1c42146..f83ebbc44e6 100644 --- a/src/sage/parallel/use_fork.py +++ b/src/sage/parallel/use_fork.py @@ -70,7 +70,7 @@ class p_iter_fork: about what the iterator does (e.g., killing subprocesses) - ``reset_interfaces`` -- boolean (default: ``True``); whether to reset all pexpect interfaces - - ``reseed_rng`` -- booolean (default: ``False``); whether or not to reseed the rng in the subprocesses + - ``reseed_rng`` -- boolean (default: ``False``); whether or not to reseed the rng in the subprocesses EXAMPLES:: @@ -164,7 +164,7 @@ def __call__(self, f, inputs): n = self.ncpus inputs = list(inputs) if self.reseed_rng: - seeds = [getrandbits(512) for _ in range(0, len(inputs))] + seeds = [getrandbits(512) for _ in range(len(inputs))] vs = list(zip(inputs, seeds)) else: vs = list(zip(inputs, [None]*len(inputs))) @@ -173,7 +173,7 @@ def __call__(self, f, inputs): while vs or workers: # Spawn up to n subprocesses while vs and len(workers) < n: - (v0, seed0) = vs.pop(0) # Input value and seed for the next subprocess + v0, seed0 = vs.pop(0) # Input value and seed for the next subprocess with ContainChildren(): pid = os.fork() # The way fork works is that pid returns the @@ -181,7 +181,8 @@ def __call__(self, f, inputs): # process and returns 0 for the subprocess. if not pid: # This is the subprocess. - self.worker_seed = seed0 if self.reseed_rng else None + if self.reseed_rng: + self.worker_seed = seed0 self._subprocess(f, dir, *v0) workers[pid] = WorkerData(v0) From 23c9a11c9adc9e6d4cec001b3c37c3043cc3fb24 Mon Sep 17 00:00:00 2001 From: mklss <59539887+mklss@users.noreply.github.com> Date: Sun, 9 Feb 2025 00:15:38 +0100 Subject: [PATCH 240/369] Split line. --- src/sage/parallel/use_fork.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sage/parallel/use_fork.py b/src/sage/parallel/use_fork.py index f83ebbc44e6..b3bca5ad5b6 100644 --- a/src/sage/parallel/use_fork.py +++ b/src/sage/parallel/use_fork.py @@ -70,7 +70,8 @@ class p_iter_fork: about what the iterator does (e.g., killing subprocesses) - ``reset_interfaces`` -- boolean (default: ``True``); whether to reset all pexpect interfaces - - ``reseed_rng`` -- boolean (default: ``False``); whether or not to reseed the rng in the subprocesses + - ``reseed_rng`` -- boolean (default: ``False``); whether or not to reseed + the rng in the subprocesses EXAMPLES:: From ada208e705d660f62ad218d2504a625808ddd162 Mon Sep 17 00:00:00 2001 From: Noel Roemmele Date: Sat, 8 Feb 2025 17:24:27 -0700 Subject: [PATCH 241/369] Fixed crash when exp(0) of p-adic numbers is called --- src/sage/rings/padics/padic_generic_element.pyx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/sage/rings/padics/padic_generic_element.pyx b/src/sage/rings/padics/padic_generic_element.pyx index 7a103be85b3..3f77660f4a7 100644 --- a/src/sage/rings/padics/padic_generic_element.pyx +++ b/src/sage/rings/padics/padic_generic_element.pyx @@ -2959,6 +2959,14 @@ cdef class pAdicGenericElement(LocalGenericElement): sage: x.exp(algorithm='generic') # indirect doctest # needs sage.libs.ntl 1 + w*7 + (4*w + 2)*7^2 + (w + 6)*7^3 + 5*7^4 + O(7^5) + TESTS:: + + Verify that :trac:`38037` is fixed:: + + sage: R. = Zq(9) + sage: exp(R.zero()) + 1 + O(3^20) + AUTHORS: - Genya Zaytman (2007-02-15) @@ -2973,6 +2981,8 @@ cdef class pAdicGenericElement(LocalGenericElement): R=self.parent() p=self.parent().prime() e=self.parent().absolute_e() + if self._is_exact_zero(): + return R.one() x_unit=self.unit_part() p_unit=R(p).unit_part().lift_to_precision() x_val=self.valuation() From 349bc2d69c5ea6549f3245657504a12242e82f92 Mon Sep 17 00:00:00 2001 From: Noel Roemmele Date: Sat, 8 Feb 2025 21:20:40 -0700 Subject: [PATCH 242/369] Fixed issue in list_plot where it assumed data had been enumerated when it might not have been --- src/sage/plot/plot.py | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/src/sage/plot/plot.py b/src/sage/plot/plot.py index c5ab3b74aed..cfe7779b009 100644 --- a/src/sage/plot/plot.py +++ b/src/sage/plot/plot.py @@ -3107,6 +3107,22 @@ def list_plot(data, plotjoined=False, **kwargs): 100.0 sage: d['ymin'] 100.0 + + Verify that :trac:`38037` is fixed:: + + sage: list_plot([(0,-1),(1,-2),(2,-3),(3,-4),(4,None)]) + Traceback (most recent call last): + ... + TypeError: unable to coerce to a ComplexNumber: + + + #Non enumerated list example + sage: list_plot([3+I, 4, I, 1+5*i, None, 1+i]) + Graphics object consisting of 1 graphics primitive + + #Enumerated list example + sage: list_plot([4, 3+I, I, 1+5*i, None, 1+i]) + Graphics object consisting of 1 graphics primitive """ from sage.plot.all import point try: @@ -3124,10 +3140,12 @@ def list_plot(data, plotjoined=False, **kwargs): else: list_data = list(data.items()) return list_plot(list_data, plotjoined=plotjoined, **kwargs) + listEnumerated = False try: from sage.rings.real_double import RDF RDF(data[0]) data = list(enumerate(data)) + listEnumerated = True except TypeError: # we can get this TypeError if the element is a list # or tuple or numpy array, or an element of CC, CDF # We also want to avoid doing CC(data[0]) here since it will go @@ -3138,6 +3156,7 @@ def list_plot(data, plotjoined=False, **kwargs): # element of the Symbolic Ring. if isinstance(data[0], Expression): data = list(enumerate(data)) + listEnumerated = True try: if plotjoined: @@ -3150,9 +3169,11 @@ def list_plot(data, plotjoined=False, **kwargs): # point3d() throws an IndexError on the (0,1) before it ever # gets to (1, I). from sage.rings.cc import CC - # if we get here, we already did "list(enumerate(data))", - # so look at z[1] in inner list - data = [(z.real(), z.imag()) for z in [CC(z[1]) for z in data]] + # It is not guaranteed that we enumerated the data so we have two cases + if listEnumerated: + data = [(z.real(), z.imag()) for z in [CC(z[1]) for z in data]] + else: + data = [(z.real(), z.imag()) for z in [CC(z) for z in data]] if plotjoined: return line(data, **kwargs) else: From 8e0678d78397d5eefe3134ed2b86c6465de7ecd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Sun, 9 Feb 2025 18:17:54 +0100 Subject: [PATCH 243/369] fix one typo --- src/sage/graphs/cographs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/graphs/cographs.py b/src/sage/graphs/cographs.py index a78e8a24376..30569f63130 100644 --- a/src/sage/graphs/cographs.py +++ b/src/sage/graphs/cographs.py @@ -395,7 +395,7 @@ def change_label(tree, status, counter): - ``tree`` -- the tree to relabel - - ``status`` -- boolean; used to to detect series (``True``) and parallel + - ``status`` -- boolean; used to detect series (``True``) and parallel (``False``) internal nodes - ``counter`` -- list; the first integer of the list is used to assign a From 8e5ca6a3158b67996f12246a2019c710319c8386 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Mon, 10 Feb 2025 09:45:05 +0700 Subject: [PATCH 244/369] Add back is_approximate as deprecated property --- .../schemes/elliptic_curves/period_lattice.py | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/sage/schemes/elliptic_curves/period_lattice.py b/src/sage/schemes/elliptic_curves/period_lattice.py index 050243cb45f..d341ab74dba 100644 --- a/src/sage/schemes/elliptic_curves/period_lattice.py +++ b/src/sage/schemes/elliptic_curves/period_lattice.py @@ -1167,6 +1167,38 @@ def curve(self): """ return self.E + @property + def is_approximate(self): + """ + ``self.is_approximate`` is deprecated, use ``not self.curve().is_exact()`` instead. + + TESTS:: + + sage: E = EllipticCurve(ComplexField(100), [I, 3*I+4]) + sage: L = E.period_lattice() + sage: L.is_approximate + doctest:...: DeprecationWarning: The attribute is_approximate for period lattice is deprecated, + use self.curve().is_exact() instead. + See https://github.com/sagemath/sage/issues/39212 for details. + True + sage: L.curve() is E + True + sage: E.is_exact() + False + sage: E = EllipticCurve(QQ, [0, 2]) + sage: L = E.period_lattice() + sage: L.is_approximate + False + sage: L.curve() is E + True + sage: E.is_exact() + True + """ + from sage.misc.superseded import deprecation + deprecation(39212, "The attribute is_approximate for period lattice is " + "deprecated, use self.curve().is_exact() instead.") + return not self._is_exact + def ei(self): r""" Return the x-coordinates of the 2-division points of the elliptic curve associated From 0e54be2b1608f9158c462df48c41c8b0d5651e27 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Mon, 10 Feb 2025 11:07:40 +0700 Subject: [PATCH 245/369] Apply suggestions --- .../rings/laurent_series_ring_element.pyx | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/sage/rings/laurent_series_ring_element.pyx b/src/sage/rings/laurent_series_ring_element.pyx index 758b7012240..cd33190e935 100644 --- a/src/sage/rings/laurent_series_ring_element.pyx +++ b/src/sage/rings/laurent_series_ring_element.pyx @@ -1,6 +1,12 @@ r""" Laurent Series +Laurent series in Sage are represented internally as a power of the variable +times the power series part. If a Laurent series `f` is represented as +`f = t^n \cdot u` where `t` is the variable and `u` has nonzero constant term, +`u` can be accessed through :meth:`valuation_zero_part` and `n` can be accessed +through :meth:`valuation`. + EXAMPLES:: sage: R. = LaurentSeriesRing(GF(7), 't'); R @@ -35,15 +41,6 @@ Saving and loading. sage: loads(K.dumps()) == K # needs sage.rings.real_mpfr True -IMPLEMENTATION: Laurent series in Sage are represented internally -as a power of the variable times the unit part (which need not be a -unit - it's a polynomial with nonzero constant term). The zero -Laurent series has unit part 0. - -For a Laurent series internally represented as `t^n \cdot f` where -`t` is the variable, `f` can be accessed through :meth:`valuation_zero_part` -and `n` can be accessed through :meth:`valuation`. - AUTHORS: - William Stein: original version @@ -93,8 +90,8 @@ cdef class LaurentSeries(AlgebraElement): r""" A Laurent Series. - We consider a Laurent series of the form `t^n \cdot f` where `f` is a - power series. + We consider a Laurent series of the form `f = t^n \cdot u` where `u` is a + power series with nonzero constant term. INPUT: From 3f4805290586b5af86485fa5402528bf72463a4b Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Mon, 10 Feb 2025 11:20:55 +0700 Subject: [PATCH 246/369] Add test for the current situation --- src/sage/rings/fraction_field.py | 58 ++++++++++++++++++++++++++++++-- 1 file changed, 56 insertions(+), 2 deletions(-) diff --git a/src/sage/rings/fraction_field.py b/src/sage/rings/fraction_field.py index 4e3e923c263..7b51ad23261 100644 --- a/src/sage/rings/fraction_field.py +++ b/src/sage/rings/fraction_field.py @@ -653,6 +653,61 @@ def _element_constructor_(self, x, y=None, coerce=True): sage: x = FF(elt) sage: F(x) -1/2/(a^2 + a) + + Conversion from power series to rational function field truncates, but is deprecated:: + + sage: F. = Frac(QQ['x']) + sage: R. = QQ[[]] + sage: f = 1/(x+1) + sage: f.parent() + Power Series Ring in x over Rational Field + sage: F(f) + -x^19 + x^18 - x^17 + x^16 - x^15 + x^14 - x^13 + x^12 - x^11 + x^10 - x^9 + x^8 - x^7 + x^6 - x^5 + x^4 - x^3 + x^2 - x + 1 + + Conversion from Laurent series to rational function field gives an approximation:: + + sage: F. = Frac(QQ['x']) + sage: R. = QQ[[]] + sage: f = Frac(R)(1/(x+1)) + sage: f.parent() + Laurent Series Ring in x over Rational Field + sage: F(f) + Traceback (most recent call last): + ... + TypeError: cannot convert 1 - x + x^2 - x^3 + x^4 - x^5 + x^6 - x^7 + x^8 - x^9 + x^10 - x^11 + x^12 - x^13 + x^14 - x^15 + x^16 - x^17 + x^18 - x^19 + O(x^20)/1 to an element of Fraction Field of Univariate Polynomial Ring in x over Rational Field + sage: f = f.truncate(20); f # infinite precision + 1 - x + x^2 - x^3 + x^4 - x^5 + x^6 - x^7 + x^8 - x^9 + x^10 - x^11 + x^12 - x^13 + x^14 - x^15 + x^16 - x^17 + x^18 - x^19 + sage: f.parent() + Laurent Series Ring in x over Rational Field + sage: F(f) + Traceback (most recent call last): + ... + TypeError: cannot convert 1 - x + x^2 - x^3 + x^4 - x^5 + x^6 - x^7 + x^8 - x^9 + x^10 - x^11 + x^12 - x^13 + x^14 - x^15 + x^16 - x^17 + x^18 - x^19/1 to an element of Fraction Field of Univariate Polynomial Ring in x over Rational Field + sage: f = 1/(x*(x+1)) + sage: f.parent() + Laurent Series Ring in x over Rational Field + sage: F(f) + Traceback (most recent call last): + ... + TypeError: cannot convert x^-1 - 1 + x - x^2 + x^3 - x^4 + x^5 - x^6 + x^7 - x^8 + x^9 - x^10 + x^11 - x^12 + x^13 - x^14 + x^15 - x^16 + x^17 - x^18 + O(x^19)/1 to an element of Fraction Field of Univariate Polynomial Ring in x over Rational Field + + :: + + sage: K. = FunctionField(QQ) + sage: R. = QQ[[]] + sage: f = 1/(x+1) + sage: K(f) + -x^19 + x^18 - x^17 + x^16 - x^15 + x^14 - x^13 + x^12 - x^11 + x^10 - x^9 + x^8 - x^7 + x^6 - x^5 + x^4 - x^3 + x^2 - x + 1 + sage: f = Frac(R)(1/(x+1)) + sage: K(f) + Traceback (most recent call last): + ... + TypeError: cannot convert 1 - x + x^2 - x^3 + x^4 - x^5 + x^6 - x^7 + x^8 - x^9 + x^10 - x^11 + x^12 - x^13 + x^14 - x^15 + x^16 - x^17 + x^18 - x^19 + O(x^20)/1 to an element of Fraction Field of Univariate Polynomial Ring in x over Rational Field + sage: f = 1/(x*(x+1)) + sage: K(f) + Traceback (most recent call last): + ... + TypeError: cannot convert x^-1 - 1 + x - x^2 + x^3 - x^4 + x^5 - x^6 + x^7 - x^8 + x^9 - x^10 + x^11 - x^12 + x^13 - x^14 + x^15 - x^16 + x^17 - x^18 + O(x^19)/1 to an element of Fraction Field of Univariate Polynomial Ring in x over Rational Field """ if isinstance(x, (list, tuple)) and len(x) == 1: x = x[0] @@ -664,8 +719,7 @@ def _element_constructor_(self, x, y=None, coerce=True): return self._element_class(self, x, ring_one, coerce=coerce) except (TypeError, ValueError): pass - y = self._element_class(self, ring_one, ring_one, - coerce=False, reduce=False) + y = self.one() else: if parent(x) is self: y = self(y) From 428abde2ac8ce92a7bb44edcd432ca3e041d17f4 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Mon, 10 Feb 2025 11:58:32 +0700 Subject: [PATCH 247/369] Implement conversion from laurent series to rational function field --- src/sage/rings/fraction_field.py | 65 ++++++++++++++++++++++++-------- 1 file changed, 50 insertions(+), 15 deletions(-) diff --git a/src/sage/rings/fraction_field.py b/src/sage/rings/fraction_field.py index 7b51ad23261..ba374d69994 100644 --- a/src/sage/rings/fraction_field.py +++ b/src/sage/rings/fraction_field.py @@ -557,6 +557,30 @@ def is_exact(self): """ return self.ring().is_exact() + def _convert_from_finite_precision_laurent_series(self, x): + """ + Construct an element of this fraction field approximating a Laurent series. + + INPUT: + + - ``x`` -- a Laurent series, must have finite precision + + OUTPUT: Element of ``self`` + + This internal method should not be used directly, use :meth:`__call__` instead, + which will delegates to :meth:`_element_constructor_`. There are some tests there. + + .. NOTE:: + + Uses the algorithm described in ``_. + This may be changed to use Berlekamp--Massey algorithm or something else + to compute Padé approximant in the future. + """ + integral_part, fractional_part = self(x.truncate(1)), x.truncate_neg(1) + if fractional_part.is_zero(): + return integral_part + return integral_part + ~self._convert_from_finite_precision_laurent_series(~fractional_part) + def _element_constructor_(self, x, y=None, coerce=True): """ Construct an element of this fraction field. @@ -662,6 +686,9 @@ def _element_constructor_(self, x, y=None, coerce=True): sage: f.parent() Power Series Ring in x over Rational Field sage: F(f) + doctest:warning... + DeprecationWarning: Conversion from power series to rational function field is deprecated, use .truncate() instead + See https://github.com/sagemath/sage/issues/39485 for details. -x^19 + x^18 - x^17 + x^16 - x^15 + x^14 - x^13 + x^12 - x^11 + x^10 - x^9 + x^8 - x^7 + x^6 - x^5 + x^4 - x^3 + x^2 - x + 1 Conversion from Laurent series to rational function field gives an approximation:: @@ -672,24 +699,18 @@ def _element_constructor_(self, x, y=None, coerce=True): sage: f.parent() Laurent Series Ring in x over Rational Field sage: F(f) - Traceback (most recent call last): - ... - TypeError: cannot convert 1 - x + x^2 - x^3 + x^4 - x^5 + x^6 - x^7 + x^8 - x^9 + x^10 - x^11 + x^12 - x^13 + x^14 - x^15 + x^16 - x^17 + x^18 - x^19 + O(x^20)/1 to an element of Fraction Field of Univariate Polynomial Ring in x over Rational Field + 1/(x + 1) sage: f = f.truncate(20); f # infinite precision 1 - x + x^2 - x^3 + x^4 - x^5 + x^6 - x^7 + x^8 - x^9 + x^10 - x^11 + x^12 - x^13 + x^14 - x^15 + x^16 - x^17 + x^18 - x^19 sage: f.parent() Laurent Series Ring in x over Rational Field sage: F(f) - Traceback (most recent call last): - ... - TypeError: cannot convert 1 - x + x^2 - x^3 + x^4 - x^5 + x^6 - x^7 + x^8 - x^9 + x^10 - x^11 + x^12 - x^13 + x^14 - x^15 + x^16 - x^17 + x^18 - x^19/1 to an element of Fraction Field of Univariate Polynomial Ring in x over Rational Field + -x^19 + x^18 - x^17 + x^16 - x^15 + x^14 - x^13 + x^12 - x^11 + x^10 - x^9 + x^8 - x^7 + x^6 - x^5 + x^4 - x^3 + x^2 - x + 1 sage: f = 1/(x*(x+1)) sage: f.parent() Laurent Series Ring in x over Rational Field sage: F(f) - Traceback (most recent call last): - ... - TypeError: cannot convert x^-1 - 1 + x - x^2 + x^3 - x^4 + x^5 - x^6 + x^7 - x^8 + x^9 - x^10 + x^11 - x^12 + x^13 - x^14 + x^15 - x^16 + x^17 - x^18 + O(x^19)/1 to an element of Fraction Field of Univariate Polynomial Ring in x over Rational Field + 1/(x^2 + x) :: @@ -697,23 +718,37 @@ def _element_constructor_(self, x, y=None, coerce=True): sage: R. = QQ[[]] sage: f = 1/(x+1) sage: K(f) + doctest:warning... + DeprecationWarning: Conversion from power series to rational function field is deprecated, use .truncate() instead + See https://github.com/sagemath/sage/issues/39485 for details. -x^19 + x^18 - x^17 + x^16 - x^15 + x^14 - x^13 + x^12 - x^11 + x^10 - x^9 + x^8 - x^7 + x^6 - x^5 + x^4 - x^3 + x^2 - x + 1 sage: f = Frac(R)(1/(x+1)) sage: K(f) - Traceback (most recent call last): - ... - TypeError: cannot convert 1 - x + x^2 - x^3 + x^4 - x^5 + x^6 - x^7 + x^8 - x^9 + x^10 - x^11 + x^12 - x^13 + x^14 - x^15 + x^16 - x^17 + x^18 - x^19 + O(x^20)/1 to an element of Fraction Field of Univariate Polynomial Ring in x over Rational Field + 1/(x + 1) sage: f = 1/(x*(x+1)) sage: K(f) - Traceback (most recent call last): - ... - TypeError: cannot convert x^-1 - 1 + x - x^2 + x^3 - x^4 + x^5 - x^6 + x^7 - x^8 + x^9 - x^10 + x^11 - x^12 + x^13 - x^14 + x^15 - x^16 + x^17 - x^18 + O(x^19)/1 to an element of Fraction Field of Univariate Polynomial Ring in x over Rational Field + 1/(x^2 + x) """ if isinstance(x, (list, tuple)) and len(x) == 1: x = x[0] if y is None: if parent(x) is self: return x + from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic + if isinstance(self.ring(), PolynomialRing_generic): + from sage.rings.power_series_ring_element import PowerSeries + from sage.rings.laurent_series_ring_element import LaurentSeries + if isinstance(x, PowerSeries): + from sage.misc.superseded import deprecation + deprecation( + 39485, + "Conversion from power series to rational function field is deprecated, use .truncate() instead", + ) + if isinstance(x, LaurentSeries): + from sage.rings.infinity import infinity + if x.prec() == infinity: + return self(x.laurent_polynomial()) + return self._convert_from_finite_precision_laurent_series(x) ring_one = self.ring().one() try: return self._element_class(self, x, ring_one, coerce=coerce) From d8dfdcb2aa129bd3c061a203cfc69b6679181ea8 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Mon, 10 Feb 2025 17:25:04 +0700 Subject: [PATCH 248/369] Finish changing Rational's round method default rounding to even --- .../number_field_element_quadratic.pyx | 10 ++++------ src/sage/rings/rational.pyx | 18 ++++-------------- 2 files changed, 8 insertions(+), 20 deletions(-) diff --git a/src/sage/rings/number_field/number_field_element_quadratic.pyx b/src/sage/rings/number_field/number_field_element_quadratic.pyx index cc2e63ec8e7..ac28ba8f504 100644 --- a/src/sage/rings/number_field/number_field_element_quadratic.pyx +++ b/src/sage/rings/number_field/number_field_element_quadratic.pyx @@ -2382,8 +2382,6 @@ cdef class NumberFieldElement_quadratic(NumberFieldElement_absolute): TESTS:: - sage: import warnings - sage: warnings.filterwarnings("ignore", category=DeprecationWarning) sage: K2. = QuadraticField(2) sage: K3. = QuadraticField(3) sage: K5. = QuadraticField(5) @@ -2398,15 +2396,15 @@ cdef class NumberFieldElement_quadratic(NumberFieldElement_absolute): ....: assert round(a+b*sqrt(5.)) == round(a+b*sqrt5), (a, b) """ n = self.floor() - test = 2 * (self - n).abs() + test = 2 * (self - n) if test < 1: return n elif test > 1: return n + 1 - elif self > 0: - return n + 1 - else: + elif n % 2 == 0: return n + else: + return n + 1 cdef class NumberFieldElement_quadratic_sqrt(NumberFieldElement_quadratic): diff --git a/src/sage/rings/rational.pyx b/src/sage/rings/rational.pyx index 68ee004a251..ccfd4f9ce52 100644 --- a/src/sage/rings/rational.pyx +++ b/src/sage/rings/rational.pyx @@ -3375,11 +3375,9 @@ cdef class Rational(sage.structure.element.FieldElement): mpz_tdiv_q(n.value, mpq_numref(self.value), mpq_denref(self.value)) return n - def round(Rational self, mode=None): + def round(Rational self, mode="even"): """ - Return the nearest integer to ``self``, rounding away by default. - Deprecation: in the future the default will be changed to rounding to - even, for consistency with the builtin Python :func:`round`. + Return the nearest integer to ``self``, rounding to even by default. INPUT: @@ -3399,15 +3397,13 @@ cdef class Rational(sage.structure.element.FieldElement): EXAMPLES:: sage: (9/2).round() - doctest:...: DeprecationWarning: the default rounding for rationals, currently `away`, will be changed to `even`. - See https://github.com/sagemath/sage/issues/35473 for details. - 5 + 4 sage: n = 4/3; n.round() 1 sage: n = -17/4; n.round() -4 sage: n = -5/2; n.round() - -3 + -2 sage: n.round("away") -3 sage: n.round("up") @@ -3419,12 +3415,6 @@ cdef class Rational(sage.structure.element.FieldElement): sage: n.round("odd") -3 """ - if mode is None: - if self.denominator() == 2: - from sage.misc.superseded import deprecation - deprecation(35473, - "the default rounding for rationals, currently `away`, will be changed to `even`.") - mode = "away" if not (mode in ['toward', 'away', 'up', 'down', 'even', 'odd']): raise ValueError("rounding mode must be one of 'toward', 'away', 'up', 'down', 'even', or 'odd'") if self.denominator() == 1: From ca13a623a20790efd62e2a584050ac0e7e29b741 Mon Sep 17 00:00:00 2001 From: Mercedes Haiech Date: Mon, 10 Feb 2025 13:53:30 +0100 Subject: [PATCH 249/369] improve LaTex formula --- src/sage/rings/polynomial/multi_polynomial_sequence.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/sage/rings/polynomial/multi_polynomial_sequence.py b/src/sage/rings/polynomial/multi_polynomial_sequence.py index ca6f864f807..11224bf0acc 100644 --- a/src/sage/rings/polynomial/multi_polynomial_sequence.py +++ b/src/sage/rings/polynomial/multi_polynomial_sequence.py @@ -1271,7 +1271,9 @@ def is_groebner(self, singular=singular): forms a Groebner basis if and only if for every element `S` in `Syz(LM(I))`: - `S * G = \sum_{i=0}^{m} h_ig_i ---->_G 0.` + .. math:: + + S \star G = \sum_{i=0}^{m} h_i g_i \longrightarrow_G 0. EXAMPLES:: From 602b67e1f1dabb253445c9ca7b21267949a0c0aa Mon Sep 17 00:00:00 2001 From: Hugo Passe Date: Mon, 10 Feb 2025 15:01:50 +0100 Subject: [PATCH 250/369] Removed python int to long cast raising error for large integers --- src/sage/matrix/matrix_modn_dense_template.pxi | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/sage/matrix/matrix_modn_dense_template.pxi b/src/sage/matrix/matrix_modn_dense_template.pxi index 03e60d56394..38836b1113f 100644 --- a/src/sage/matrix/matrix_modn_dense_template.pxi +++ b/src/sage/matrix/matrix_modn_dense_template.pxi @@ -525,10 +525,7 @@ cdef class Matrix_modn_dense_template(Matrix_dense): se = t x = se.entry v = self._matrix[se.i] - if type(x) is int: - tmp = (x) % p - v[se.j] = tmp + (tmp<0)*p - elif type(x) is IntegerMod_int and (x)._parent is R: + if type(x) is IntegerMod_int and (x)._parent is R: v[se.j] = (x).ivalue elif type(x) is Integer: if coerce: From 287430a897555a205f6256165c54f794b7e8e52e Mon Sep 17 00:00:00 2001 From: MercedesHaiech <73114674+MercedesHaiech@users.noreply.github.com> Date: Mon, 10 Feb 2025 15:10:30 +0100 Subject: [PATCH 251/369] Update src/sage/rings/polynomial/multi_polynomial_sequence.py Co-authored-by: Travis Scrimshaw --- src/sage/rings/polynomial/multi_polynomial_sequence.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/polynomial/multi_polynomial_sequence.py b/src/sage/rings/polynomial/multi_polynomial_sequence.py index 11224bf0acc..24acc8e1656 100644 --- a/src/sage/rings/polynomial/multi_polynomial_sequence.py +++ b/src/sage/rings/polynomial/multi_polynomial_sequence.py @@ -1271,7 +1271,7 @@ def is_groebner(self, singular=singular): forms a Groebner basis if and only if for every element `S` in `Syz(LM(I))`: - .. math:: + .. MATH:: S \star G = \sum_{i=0}^{m} h_i g_i \longrightarrow_G 0. From d40c2a5cdc658ed97175e8e45a92cee159dbf66b Mon Sep 17 00:00:00 2001 From: Hugo Passe Date: Mon, 10 Feb 2025 15:53:57 +0100 Subject: [PATCH 252/369] Added test for patch of issue 36104 --- src/sage/matrix/matrix_modn_dense_template.pxi | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/sage/matrix/matrix_modn_dense_template.pxi b/src/sage/matrix/matrix_modn_dense_template.pxi index 38836b1113f..dc3a2b651a6 100644 --- a/src/sage/matrix/matrix_modn_dense_template.pxi +++ b/src/sage/matrix/matrix_modn_dense_template.pxi @@ -514,6 +514,9 @@ cdef class Matrix_modn_dense_template(Matrix_dense): sage: Matrix(Integers(4618990), 2, 2, [-1, int(-2), GF(7)(-3), 1/7]) # needs sage.rings.finite_rings [4618989 4618988] [ 4 2639423] + + sage: Matrix(IntegerModRing(200), [[int(2**128+1), int(2**256+1), int(2**1024+1)]]) # needs sage.rings.finite_rings + [ 57 137 17] """ ma = MatrixArgs_init(parent, entries) cdef long i, j From f7ae0c6788f4e7a12492d1c503a96f49a43a0992 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Mon, 10 Feb 2025 19:03:19 +0100 Subject: [PATCH 253/369] Apply suggestions from code review return tuples since the function value is cached. Co-authored-by: Travis Scrimshaw --- src/sage/rings/species.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/sage/rings/species.py b/src/sage/rings/species.py index 10b4d7a4860..5ae953a32ef 100644 --- a/src/sage/rings/species.py +++ b/src/sage/rings/species.py @@ -2516,9 +2516,8 @@ def _atomic_set_like_species(n, names): INPUT: - - ``n`` -- positive integer, the degree - - ``names`` -- an iterable of strings for the sorts of the - species + - ``n`` -- positive integer, the degree + - ``names`` -- an iterable of strings for the sorts of the species EXAMPLES:: @@ -2533,14 +2532,14 @@ def _atomic_set_like_species(n, names): 0: A007650: Number of set-like atomic species of degree n. sage: _atomic_set_like_species(4, "U, V") - [E_2(E_2(V)), E_2(E_2(U)), E_2(V^2), E_2(U*V), E_2(U^2), E_4(U), E_4(V)] + (E_2(E_2(V)), E_2(E_2(U)), E_2(V^2), E_2(U*V), E_2(U^2), E_4(U), E_4(V)) """ if not n: - return [] + return () M1 = MolecularSpecies("X") M = MolecularSpecies(names) if n == 1: - return [M(SymmetricGroup(1), {s: [1]}) for s in range(M._arity)] + return tuple([M(SymmetricGroup(1), {s: [1]}) for s in range(M._arity)]) result = [] for d in divisors(n): if d == 1: @@ -2561,4 +2560,4 @@ def _atomic_set_like_species(n, names): F = E_d(G) F.support()[0].rename(f"E_{d}({G})") result.append(F) - return result + return tuple(result) From 6e20ba86f4ecc3490db801e3851379af23a33c0a Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Mon, 10 Feb 2025 19:26:26 +0100 Subject: [PATCH 254/369] adapt a doctest --- src/sage/rings/species.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/species.py b/src/sage/rings/species.py index 5ae953a32ef..c2ae9df66fc 100644 --- a/src/sage/rings/species.py +++ b/src/sage/rings/species.py @@ -2523,7 +2523,7 @@ def _atomic_set_like_species(n, names): sage: from sage.rings.species import _atomic_set_like_species sage: _atomic_set_like_species(6, "X") - [E_2(E_3), E_2(X*E_2), E_2(X^3), E_3(E_2), E_3(X^2), E_6] + (E_2(E_3), E_2(X*E_2), E_2(X^3), E_3(E_2), E_3(X^2), E_6) sage: l = [len(_atomic_set_like_species(n, "X")) for n in range(12)] sage: l From 0a1dd77b31ae8d3ef316404e09a0a015ac7a52ba Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Mon, 10 Feb 2025 19:38:15 +0100 Subject: [PATCH 255/369] replace functor with dedicated action (authored by tscrim) --- src/sage/data_structures/stream.py | 101 +++++++++++++++++------------ src/sage/rings/lazy_series_ring.py | 2 +- 2 files changed, 59 insertions(+), 44 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index f69c243c83d..d777d22d12a 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -101,11 +101,8 @@ from sage.misc.lazy_attribute import lazy_attribute 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.fields import Fields -from sage.categories.functor import Functor -from sage.categories.integral_domains import IntegralDomains +from sage.categories.action import Action from sage.categories.hopf_algebras_with_basis import HopfAlgebrasWithBasis -from sage.categories.pushout import ConstructionFunctor from sage.categories.quotient_fields import QuotientFields from sage.structure.unique_representation import UniqueRepresentation from sage.rings.polynomial.infinite_polynomial_ring import InfinitePolynomialRing @@ -1348,38 +1345,55 @@ def variables(self): return self._pool -class CoefficientRingFunctor(ConstructionFunctor): - r""" - A construction functor for the :class:`CoefficientRing`. - - The functor maps the integral domain of coefficients to the field - of unknown coefficients. +class DominatingAction(Action): """ - rank = 0 - - def __init__(self): - r""" - Initialize the functor. + The action defined by ``G`` acting on ``S`` by any operation such that + the result is either in ``G`` if ``S`` is in the base ring of ``G`` or + ``G`` is the coefficient ring of ``S`` otherwise. - EXAMPLES:: - - sage: from sage.data_structures.stream import CoefficientRingFunctor - sage: CoefficientRingFunctor() - CoefficientRingFunctor + This is meant specifically for use by :class:`CoefficientRing` as part + of the function solver. This is not a mathematically defined action of + ``G`` on ``S`` since the result might not be in ``S``. + """ + def _act_(self, g, x): """ - Functor.__init__(self, IntegralDomains(), Fields()) - - def _apply_functor(self, R): - r""" - Apply the functor to an integral domain. + Return the action of ``g`` on ``x``. EXAMPLES:: - sage: from sage.data_structures.stream import CoefficientRingFunctor - sage: CoefficientRingFunctor()(ZZ) # indirect doctest + sage: from sage.data_structures.stream import CoefficientRing + sage: PF = CoefficientRing(ZZ) + sage: g = PF.gen(0) + sage: x = g - 2; x + FESDUMMY_0 - 2 + sage: x.parent() CoefficientRing over Integer Ring - """ - return CoefficientRing(R) + sage: x = 2 - g; x + -FESDUMMY_0 + 2 + sage: x.parent() + CoefficientRing over Integer Ring + sage: R = QQ['a'] + sage: a = R.gen() + sage: S = ZZ['t']['b'] + sage: b = S.gen() + sage: x = a * g + b; x + FESDUMMY_0*a + b + sage: x.parent() + Univariate Polynomial Ring in a over + Fraction Field of Infinite polynomial ring in FESDUMMY over + Univariate Polynomial Ring in b over Univariate Polynomial Ring in t over Integer Ring + """ + G = g.parent() + if x in G.base_ring(): + if self.is_left(): + return self.operation()(g, G(x)) + return self.operation()(G(x), g) + if x.base_ring() is not G: + x = x.change_ring(G) + g = x.parent()(g) + if self.is_left(): + return self.operation()(g, x) + return self.operation()(x, g) class CoefficientRing(UniqueRepresentation, FractionField_generic): @@ -1473,25 +1487,26 @@ def gen(self, i): """ return self._element_class(self, self._R.gen()[i]) - def construction(self): - r""" - Return a pair ``(F, R)``, where ``F`` is a - :class:`CoefficientRingFunctor` and `R` is an integral - domain, such that ``F(R)`` returns ``self``. + def _get_action_(self, S, op, self_on_left): + """ + Return the left/right action of ``S`` on ``self`` given by ``op``. EXAMPLES:: sage: from sage.data_structures.stream import CoefficientRing + sage: R = ZZ['q'] + sage: S = QQ['t']['a','q'] sage: PF = CoefficientRing(ZZ["q"]) - sage: F, R = PF.construction() - sage: F, R - (CoefficientRingFunctor, - Univariate Polynomial Ring in q over Integer Ring) - - sage: F(R) - CoefficientRing over Univariate Polynomial Ring in q over Integer Ring - """ - return (CoefficientRingFunctor(), self.base_ring()) + sage: PF._get_action_(PF, operator.mul, True) is None + True + sage: type(PF._get_action_(R, operator.add, False)) + + sage: type(PF._get_action_(S, operator.mul, True)) + + """ + if S is not self: + return DominatingAction(self, S, op=op, is_left=self_on_left) + return super()._get_action_(S, op, self_on_left) class Stream_uninitialized(Stream): diff --git a/src/sage/rings/lazy_series_ring.py b/src/sage/rings/lazy_series_ring.py index 1b3a87bf828..5f8635d9746 100644 --- a/src/sage/rings/lazy_series_ring.py +++ b/src/sage/rings/lazy_series_ring.py @@ -1012,8 +1012,8 @@ def define_implicitly(self, series, equations, max_lookahead=1): equation 0: coefficient [x*y*t]: A[x*y] - A[t] == 0 equation 1: - coefficient [x*y*t]: B[x*y] - B[t] == 0 coefficient [x*t^2]: B[x*t] + B[t] == 0 + coefficient [x*y*t]: B[x*y] - B[t] == 0 Check the error message in the case of symmetric functions:: From bf658617995c4f39c83da21748f16677355ca105 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Mon, 10 Feb 2025 22:05:42 +0100 Subject: [PATCH 256/369] fix two oeis related doctests --- src/doc/en/developer/coding_basics.rst | 4 +--- src/sage/combinat/quickref.py | 3 +-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/doc/en/developer/coding_basics.rst b/src/doc/en/developer/coding_basics.rst index 02936d3e423..0ac10e44f12 100644 --- a/src/doc/en/developer/coding_basics.rst +++ b/src/doc/en/developer/coding_basics.rst @@ -1302,9 +1302,7 @@ framework. Here is a comprehensive list: For lines that require an internet connection:: sage: oeis(60843) # optional - internet - A060843: Busy Beaver problem: a(n) = maximal number of steps that an - n-state Turing machine can make on an initially blank tape before - eventually halting. + A060843: ... - **known bugs:** For lines that describe known bugs, you can use ``# optional - bug``, although ``# known bug`` is preferred. diff --git a/src/sage/combinat/quickref.py b/src/sage/combinat/quickref.py index ee7d9ca8ec1..dacdc266ef1 100644 --- a/src/sage/combinat/quickref.py +++ b/src/sage/combinat/quickref.py @@ -4,8 +4,7 @@ Integer Sequences:: sage: s = oeis([1,3,19,211]); s # optional - internet - 0: A000275: Coefficients of a Bessel function (reciprocal of J_0(z)); - also pairs of permutations with rise/rise forbidden. + 0: A000275: ... sage: s[0].programs() # optional - internet [('maple', ...), ('mathematica', ...), From 2a4c8cd1a125b8aa5f296b54b1012e35b997b061 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Mon, 10 Feb 2025 22:11:42 +0100 Subject: [PATCH 257/369] fix one more --- src/sage/combinat/species/library.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/combinat/species/library.py b/src/sage/combinat/species/library.py index c50a9fe75fa..4ab233c3bb3 100644 --- a/src/sage/combinat/species/library.py +++ b/src/sage/combinat/species/library.py @@ -56,7 +56,7 @@ def SimpleGraphSpecies(): sage: seq = S.isotype_generating_series().counts(6)[1:] # needs sage.modules sage: oeis(seq)[0] # optional - internet # needs sage.modules - A000088: Number of graphs on n unlabeled nodes. + A000088: ... :: From f7438fcfc713efb7f7aaf4262a9c092bb2e9bf46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Mon, 10 Feb 2025 22:15:14 +0100 Subject: [PATCH 258/369] one more fix --- src/sage/rings/lazy_series.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/rings/lazy_series.py b/src/sage/rings/lazy_series.py index b801c6f1b57..b1d370a3bd4 100644 --- a/src/sage/rings/lazy_series.py +++ b/src/sage/rings/lazy_series.py @@ -1482,8 +1482,8 @@ def define(self, s): sage: f 1 + t + t^2 + 2*t^3 + 6*t^4 + 23*t^5 + 104*t^6 + O(t^7) sage: oeis(f[1:20]) # optional - internet - 0: A030266: Shifts left under COMPOSE transform with itself. - 1: A110447: Permutations containing 3241 patterns only as part of 35241 patterns. + 0: A030266: ... + 1: A110447: ... The following can only work for power series, where we have a minimal valuation of `0`:: From 89b74b56322acdec0874390854b014cee26d511c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Labb=C3=A9?= Date: Mon, 10 Feb 2025 22:41:33 +0100 Subject: [PATCH 259/369] fixing doctests failures in misc/latex*.py --- src/sage/misc/latex.py | 1 - src/sage/misc/latex_standalone.py | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/sage/misc/latex.py b/src/sage/misc/latex.py index 9d8fcd4a341..8fc6f04a313 100644 --- a/src/sage/misc/latex.py +++ b/src/sage/misc/latex.py @@ -1025,7 +1025,6 @@ def eval(self, x, globals, strip=False, filename=None, debug=None, '' sage: latex.eval(r"\ThisIsAnInvalidCommand", {}) # optional -- latex ImageMagick An error occurred... - No pages of output... """ MACROS = latex_extra_preamble() diff --git a/src/sage/misc/latex_standalone.py b/src/sage/misc/latex_standalone.py index 483e8f52035..0fab052609d 100644 --- a/src/sage/misc/latex_standalone.py +++ b/src/sage/misc/latex_standalone.py @@ -676,7 +676,7 @@ def pdf(self, filename=None, view=True, program=None): Traceback (most recent call last): ... CalledProcessError: Command '['...latex', '-interaction=nonstopmode', - 'tikz_...tex']' returned nonzero exit status 1. + 'tikz_...tex']' returned non-zero exit status 1. """ from sage.features.latex import lualatex, pdflatex @@ -797,7 +797,7 @@ def dvi(self, filename=None, view=True, program='latex'): Traceback (most recent call last): ... CalledProcessError: Command '['latex', '-interaction=nonstopmode', - 'tikz_...tex']' returned nonzero exit status 1. + 'tikz_...tex']' returned non-zero exit status 1. We test the behavior when a wrong value is provided:: From 5aeb5c43707d2559ae22b640b6cdaddcdcadd167 Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Tue, 11 Feb 2025 00:52:23 +0100 Subject: [PATCH 260/369] Extend matrix_ring/division_algebra examples --- src/sage/algebras/quatalg/quaternion_algebra.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index fd011fc358b..aef82c87feb 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -424,13 +424,16 @@ def is_division_algebra(self) -> bool: True sage: QuaternionAlgebra(2,9).is_division_algebra() False + sage: K. = QuadraticField(3) + sage: QuaternionAlgebra(K, 1+z, 3-z).is_division_algebra() + False By checking ramification, the method correctly recognizes division quaternion algebras over a number field even if they have trivial discriminant:: - sage: K = QuadraticField(3) - sage: A = QuaternionAlgebra(K, -1, -1) + sage: L = QuadraticField(5) + sage: A = QuaternionAlgebra(L, -1, -1) sage: A.discriminant() Fractional ideal (1) sage: A.is_division_algebra() @@ -462,13 +465,16 @@ def is_matrix_ring(self) -> bool: False sage: QuaternionAlgebra(2,9).is_matrix_ring() True + sage: K. = QuadraticField(3) + sage: QuaternionAlgebra(K, 1+z, 3-z).is_matrix_ring() + True By checking ramification, the method is able to recognize that quaternion algebras (defined over a number field) with trivial discriminant need not be matrix rings:: - sage: K = QuadraticField(3) - sage: A = QuaternionAlgebra(K, -1, -1) + sage: L = QuadraticField(5) + sage: A = QuaternionAlgebra(L, -1, -1) sage: A.discriminant() Fractional ideal (1) sage: A.is_matrix_ring() From 2c7615f22cb57cdc93d0a503dc3a559676beadd3 Mon Sep 17 00:00:00 2001 From: Noel Roemmele Date: Mon, 10 Feb 2025 20:45:33 -0700 Subject: [PATCH 261/369] Fixed semicolon in test block. --- src/sage/rings/padics/padic_generic_element.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/padics/padic_generic_element.pyx b/src/sage/rings/padics/padic_generic_element.pyx index 3f77660f4a7..da93110fe1b 100644 --- a/src/sage/rings/padics/padic_generic_element.pyx +++ b/src/sage/rings/padics/padic_generic_element.pyx @@ -2959,7 +2959,7 @@ cdef class pAdicGenericElement(LocalGenericElement): sage: x.exp(algorithm='generic') # indirect doctest # needs sage.libs.ntl 1 + w*7 + (4*w + 2)*7^2 + (w + 6)*7^3 + 5*7^4 + O(7^5) - TESTS:: + TESTS: Verify that :trac:`38037` is fixed:: From 33316d1ce481bdb5768fd63d7fbc1b51104e4580 Mon Sep 17 00:00:00 2001 From: Noel Roemmele Date: Mon, 10 Feb 2025 21:03:57 -0700 Subject: [PATCH 262/369] Changed listEnumerated to list_enumerated and made comments into seperate tests. --- src/sage/plot/plot.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/sage/plot/plot.py b/src/sage/plot/plot.py index cfe7779b009..4c6ad2000b7 100644 --- a/src/sage/plot/plot.py +++ b/src/sage/plot/plot.py @@ -3108,7 +3108,7 @@ def list_plot(data, plotjoined=False, **kwargs): sage: d['ymin'] 100.0 - Verify that :trac:`38037` is fixed:: + Verify that :issue:`38037` is fixed:: sage: list_plot([(0,-1),(1,-2),(2,-3),(3,-4),(4,None)]) Traceback (most recent call last): @@ -3116,11 +3116,13 @@ def list_plot(data, plotjoined=False, **kwargs): TypeError: unable to coerce to a ComplexNumber: - #Non enumerated list example + Test the codepath where ``list_enumerated`` is ``False``:: + sage: list_plot([3+I, 4, I, 1+5*i, None, 1+i]) Graphics object consisting of 1 graphics primitive - #Enumerated list example + Test the codepath where ``list_enumerated`` is ``True``:: + sage: list_plot([4, 3+I, I, 1+5*i, None, 1+i]) Graphics object consisting of 1 graphics primitive """ @@ -3140,12 +3142,12 @@ def list_plot(data, plotjoined=False, **kwargs): else: list_data = list(data.items()) return list_plot(list_data, plotjoined=plotjoined, **kwargs) - listEnumerated = False + list_enumerated = False try: from sage.rings.real_double import RDF RDF(data[0]) data = list(enumerate(data)) - listEnumerated = True + list_enumerated = True except TypeError: # we can get this TypeError if the element is a list # or tuple or numpy array, or an element of CC, CDF # We also want to avoid doing CC(data[0]) here since it will go @@ -3156,7 +3158,7 @@ def list_plot(data, plotjoined=False, **kwargs): # element of the Symbolic Ring. if isinstance(data[0], Expression): data = list(enumerate(data)) - listEnumerated = True + list_enumerated = True try: if plotjoined: @@ -3170,7 +3172,7 @@ def list_plot(data, plotjoined=False, **kwargs): # gets to (1, I). from sage.rings.cc import CC # It is not guaranteed that we enumerated the data so we have two cases - if listEnumerated: + if list_enumerated: data = [(z.real(), z.imag()) for z in [CC(z[1]) for z in data]] else: data = [(z.real(), z.imag()) for z in [CC(z) for z in data]] From 145daf914bce7fdc66dcc60dbc3590ad5100aef9 Mon Sep 17 00:00:00 2001 From: Noel Roemmele Date: Tue, 11 Feb 2025 00:25:11 -0700 Subject: [PATCH 263/369] Changed :trac: into :issue:. --- src/sage/rings/padics/padic_generic_element.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/padics/padic_generic_element.pyx b/src/sage/rings/padics/padic_generic_element.pyx index da93110fe1b..cf233392cd3 100644 --- a/src/sage/rings/padics/padic_generic_element.pyx +++ b/src/sage/rings/padics/padic_generic_element.pyx @@ -2961,7 +2961,7 @@ cdef class pAdicGenericElement(LocalGenericElement): TESTS: - Verify that :trac:`38037` is fixed:: + Verify that :issue:`38037` is fixed:: sage: R. = Zq(9) sage: exp(R.zero()) From 2eb46acd72ea48a0261abce9c98c54fc2aba8516 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Tue, 11 Feb 2025 09:54:24 +0100 Subject: [PATCH 264/369] fix documentation (removed files were still listed) --- src/doc/en/reference/groups/index.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/doc/en/reference/groups/index.rst b/src/doc/en/reference/groups/index.rst index 97a7c126698..930406b336e 100644 --- a/src/doc/en/reference/groups/index.rst +++ b/src/doc/en/reference/groups/index.rst @@ -78,8 +78,6 @@ Matrix and Affine Groups sage/groups/matrix_gps/group_element_gap sage/groups/matrix_gps/finitely_generated sage/groups/matrix_gps/finitely_generated_gap - sage/groups/matrix_gps/morphism - sage/groups/matrix_gps/homset sage/groups/matrix_gps/binary_dihedral sage/groups/matrix_gps/coxeter_group sage/groups/matrix_gps/linear From c08051e98537b810d7ae29af9e85f5a609a837cb Mon Sep 17 00:00:00 2001 From: dcoudert Date: Tue, 11 Feb 2025 10:58:43 +0100 Subject: [PATCH 265/369] #39266: remove some dots in INPUT blocks --- src/sage/graphs/digraph_generators.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/sage/graphs/digraph_generators.py b/src/sage/graphs/digraph_generators.py index 70d8b05ba7d..90809e6c3fe 100644 --- a/src/sage/graphs/digraph_generators.py +++ b/src/sage/graphs/digraph_generators.py @@ -1022,7 +1022,7 @@ def DeBruijn(self, k, n, vertices='strings', immutable=False): (``vertices='string'``) - ``immutable`` -- boolean (default: ``False``); whether to return - an immutable or mutable digraph. + an immutable or mutable digraph EXAMPLES: @@ -1138,10 +1138,10 @@ def GeneralizedDeBruijn(self, n, d, immutable=False, name=None): - ``d`` -- integer; degree of the digraph (must be at least one) - ``immutable`` -- boolean (default: ``False``); whether to return - an immutable or mutable digraph. + an immutable or mutable digraph - ``name`` -- string (default: ``None``); when set, the specified name - is used instead of the default one. + is used instead of the default one .. SEEALSO:: @@ -1207,10 +1207,10 @@ def ImaseItoh(self, n, d, immutable=False, name=None): equal to one) - ``immutable`` -- boolean (default: ``False``); whether to return - an immutable or mutable digraph. + an immutable or mutable digraph - ``name`` -- string (default: ``None``); when set, the specified name - is used instead of the default one. + is used instead of the default one EXAMPLES:: @@ -1292,7 +1292,7 @@ def Kautz(self, k, D, vertices='strings', immutable=False): (``vertices='strings'``) - ``immutable`` -- boolean (default: ``False``); whether to return - an immutable or mutable digraph. + an immutable or mutable digraph EXAMPLES:: From 6531546a3203fd8faf3dbcd2725903adf6fa12f8 Mon Sep 17 00:00:00 2001 From: Jean-Philippe/Thinkbook Date: Tue, 11 Feb 2025 12:28:28 -0500 Subject: [PATCH 266/369] First version of deformation cone for pc and polyhedron --- src/doc/en/reference/references/index.rst | 4 + src/sage/geometry/polyhedron/base5.py | 50 ++++++++ .../triangulation/point_configuration.py | 115 +++++++++++++++++- 3 files changed, 167 insertions(+), 2 deletions(-) diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index 4fef159bfd0..e4af2c346e0 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -104,6 +104,10 @@ REFERENCES: graphs and isoperimetric inequalities*, The Annals of Probability 32 (2004), no. 3A, 1727-1745. +.. [ACEP2020] Federico Ardila, Federico Castillo, Christopher Eur, Alexander Postnikov, + *Coxeter submodular functions and deformations of Coxeter permutahedra*, + Advances in Mathematics, Volume 365, 13 May 2020. + .. [ALL2002] P. Auger, G. Labelle and P. Leroux, *Combinatorial addition formulas and applications*, Advances in Applied Mathematics 28 (2002) 302-342. diff --git a/src/sage/geometry/polyhedron/base5.py b/src/sage/geometry/polyhedron/base5.py index c94efd9428c..d2f174a5975 100644 --- a/src/sage/geometry/polyhedron/base5.py +++ b/src/sage/geometry/polyhedron/base5.py @@ -659,6 +659,56 @@ def lawrence_polytope(self): parent = self.parent().change_ring(self.base_ring(), ambient_dim=self.ambient_dim() + n) return parent.element_class(parent, [lambda_V, [], []], None) + def deformation_cone(self): + r""" + Return the deformation cone of ``self``. + + Let `P` be a `d`-polytope in `\RR^r` with `n` facets. The deformation + cone is a polyhedron in `\RR^n` who points are the right-hand side `b` + in `Ax\leq b` where `A` is the matrix of facet normals of ``self``, so + that the resulting polytope has a normal fan which is a coarsening of + the normal fan of ``self``. + + EXAMPLES:: + + sage: tc = Polyhedron([(1, -1), (1/3, 1), (1, 1/3), (-1, 1), (-1, -1)]) + sage: dc = tc.deformation_cone() + sage: dc.an_element() + (2, 1, 1, 0, 0) + sage: [_.A() for _ in tc.Hrepresentation()] + [(1, 0), (0, 1), (0, -1), (-3, -3), (-1, 0)] + sage: P = Polyhedron(rays=[(1, 0, 2), (0, 1, 1), (0, -1, 1), (-3, -3, 0), (-1, 0, 0)]) + sage: P.rays() + (A ray in the direction (-1, -1, 0), + A ray in the direction (-1, 0, 0), + A ray in the direction (0, -1, 1), + A ray in the direction (0, 1, 1), + A ray in the direction (1, 0, 2)) + + .. SEEALSO:: + + :meth:`~sage.schemes.toric.variety.Kaehler_cone` + + REFERENCES: + + For more information, see Section 5.4 of [DLRS2010]_ and Section + 2.2 of [ACEP2020]. + """ + from .constructor import Polyhedron + A = matrix([_.A() for _ in self.Hrepresentation()]) + A = A.transpose() + A_ker = A.right_kernel_matrix(basis='computed') + gale = tuple(A_ker.columns()) + collection = [_.ambient_H_indices() for _ in self.faces(0)] + n = len(gale) + K = None + for cone_indices in collection: + dual_cone = Polyhedron(rays=[gale[i] for i in range(n) if i not in + cone_indices]) + K = K.intersection(dual_cone) if K is not None else dual_cone + preimages = [A_ker.solve_right(r.vector()) for r in K.rays()] + return Polyhedron(lines=A.rows(), rays=preimages) + ########################################################### # Binary operations. ########################################################### diff --git a/src/sage/geometry/triangulation/point_configuration.py b/src/sage/geometry/triangulation/point_configuration.py index 88aa25f6c46..5b242bffd67 100644 --- a/src/sage/geometry/triangulation/point_configuration.py +++ b/src/sage/geometry/triangulation/point_configuration.py @@ -2039,7 +2039,7 @@ def facets_of_simplex(simplex): pushing_triangulation = placing_triangulation @cached_method - def Gale_transform(self, points=None): + def Gale_transform(self, points=None, homogenize=True): r""" Return the Gale transform of ``self``. @@ -2049,6 +2049,9 @@ def Gale_transform(self, points=None): (default). A subset of points for which to compute the Gale transform. By default, all points are used. + - ``homogenize`` -- boolean (default: ``True``); whether to add a row + of 1's before taking the transform. + OUTPUT: a matrix over :meth:`base_ring` EXAMPLES:: @@ -2064,6 +2067,50 @@ def Gale_transform(self, points=None): sage: points = (pc.point(0), pc.point(1), pc.point(3), pc.point(4)) sage: pc.Gale_transform(points) [ 1 -1 1 -1] + + It is possible to take the inverse of the Gale transform, by specifying + whether to homogenize or not:: + + sage: pc2 = PointConfiguration([[0,0],[3,0],[0,3],[3,3],[1,1]]) + sage: pc2.Gale_transform(homogenize=False) + [-1 0 0 0 0] + [ 0 1 1 -1 0] + [ 0 1 1 0 -3] + sage: pc2.Gale_transform(homogenize=True) + [ 1 1 1 0 -3] + [ 0 2 2 -1 -3] + + It might not affect the dimension of the result:: + + sage: PC = PointConfiguration([[4,0,0],[0,4,0],[0,0,4],[2,1,1],[1,2,1],[1,1,2]]) + sage: GT = PC.Gale_transform(homogenize=False);GT + [ 2 1 1 -4 0 0] + [ 1 2 1 0 -4 0] + [-2 -2 -1 3 3 -1] + sage: GT = PC.Gale_transform(homogenize=True);GT + [ 1 0 0 -3 1 1] + [ 0 1 0 1 -3 1] + [ 0 0 1 1 1 -3] + + The following point configuration is totally cyclic (the cone spanned + by the vectors is equal to the vector space spanned by the points), + hence its Gale dual is acyclic (there is a linear functional that is + positive in all the points of the configuration) when not homogenized:: + + sage: pc3 = PointConfiguration([[-1, -1, -1], [-1, 0, 0], [0, -1, 0], [0, 0, -1], [1, 0, 0], [0, 0, 1], [0, 1, 0]]) + sage: g_hom = pc3.Gale_transform(homogenize=True);g_hom + [ 1 0 0 -2 1 -1 1] + [ 0 1 0 -1 1 -1 0] + [ 0 0 1 -1 0 -1 1] + sage: g_inhom = pc3.Gale_transform(homogenize=False);g_inhom + [-1 1 1 1 0 0 0] + [ 0 1 0 0 1 0 0] + [ 1 -1 -1 0 0 1 0] + [ 0 0 1 0 0 0 1] + sage: Polyhedron(rays=g_hom.columns()) + A 3-dimensional polyhedron in ZZ^3 defined as the convex hull of 1 vertex and 3 lines + sage: Polyhedron(rays=g_inhom.columns()) + A 4-dimensional polyhedron in ZZ^4 defined as the convex hull of 1 vertex and 4 rays """ self._assert_is_affine() if points is None: @@ -2073,9 +2120,73 @@ def Gale_transform(self, points=None): points = [ self.point(ZZ(i)) for i in points ] except TypeError: pass - m = matrix([ (1,) + p.affine() for p in points]) + if homogenize: + m = matrix([ (1,) + p.affine() for p in points]) + else: + m = matrix([p.affine() for p in points]) return m.left_kernel().matrix() + def deformation_cone(self, collection): + r""" + Return the deformation cone for the ``collection`` of subconfigurations + of ``self``. + + INPUT: + + - ``collection`` -- a collection of subconfigurations of ``self``. + Subconfigurations are given as indices + + OUTPUT: a polyhedron. It contains the liftings of the point configuration + making the collection a regular (or coherent, or projective) + subdivision. + + EXAMPLES:: + + sage: PC = PointConfiguration([(-1, -1), (-1, 0), (0, -1), (1, 0), (0, 1)]) + sage: coll = [(1, 4), (0, 2), (0, 1), (2, 3), (3, 4)] + sage: dc = PC.deformation_cone(coll);dc + A 5-dimensional polyhedron in QQ^5 defined as the convex hull of 1 vertex, 3 rays, 2 lines + sage: dc.rays() + (A ray in the direction (1, 0, 1, 0, 0), + A ray in the direction (1, 1, 0, 0, 0), + A ray in the direction (1, 1, 1, 0, 0)) + sage: dc.lines() + (A line in the direction (1, 0, 1, 0, -1), + A line in the direction (1, 1, 0, -1, 0)) + sage: dc.an_element() + (3, 2, 2, 0, 0) + + We add to the interior element the first line and we verify that the + given rays are defining rays of the lower hull:: + + sage: P = Polyhedron(rays=[(-1, -1, 4), (-1, 0, 3), (0, -1, 2), (1, 0, -1), (0, 1, 0)]) + sage: P.rays() + (A ray in the direction (-1, -1, 4), + A ray in the direction (-1, 0, 3), + A ray in the direction (0, -1, 2), + A ray in the direction (0, 1, 0), + A ray in the direction (1, 0, -1)) + + .. SEEALSO:: + + :meth:`~sage.schemes.toric.variety.Kaehler_cone` + + REFERENCES: + + For more information, see Section 5.4 of [DLRS2010]_ and Section + 2.2 of [ACEP2020]. + """ + from sage.geometry.polyhedron.constructor import Polyhedron + gale = self.Gale_transform(homogenize=False) + dual_rays = gale.columns() + n = self.n_points() + K = None + for cone_indices in collection: + dual_cone = Polyhedron(rays=[dual_rays[i] for i in range(n) if i not in cone_indices]) + K = K.intersection(dual_cone) if K is not None else dual_cone + preimages = [gale.solve_right(r.vector()) for r in K.rays()] + return Polyhedron(lines=matrix(self.points()).transpose().rows(),rays=preimages) + def plot(self, **kwds): r""" Produce a graphical representation of the point configuration. From 12831d8d29c321ecc2202d86ac762bc623522a82 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Tue, 11 Feb 2025 18:33:59 +0100 Subject: [PATCH 267/369] provide gens for FiniteField_prime_modn --- .../finite_rings/finite_field_prime_modn.py | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/sage/rings/finite_rings/finite_field_prime_modn.py b/src/sage/rings/finite_rings/finite_field_prime_modn.py index d94b0a4335a..21e431745ab 100644 --- a/src/sage/rings/finite_rings/finite_field_prime_modn.py +++ b/src/sage/rings/finite_rings/finite_field_prime_modn.py @@ -300,6 +300,30 @@ def gen(self, n=0): self.__gen = self.one() return self.__gen + def gens(self) -> tuple: + r""" + Return a tuple containing the generator of ``self``. + + .. WARNING:: + + The generator is not guaranteed to be a generator for the + multiplicative group. To obtain the latter, use + :meth:`~sage.rings.finite_rings.finite_field_base.FiniteFields.multiplicative_generator()` + or use the ``modulus="primitive"`` option when constructing + the field. + + EXAMPLES:: + + sage: k = GF(1009, modulus='primitive') + sage: k.gens() + (11,) + + sage: k = GF(1009) + sage: k.gens() + (1,) + """ + return (self.gen(),) + def __iter__(self): """ Return an iterator over ``self``. From 7ed18733bc643fa9237135d724d037d3995791cf Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Tue, 11 Feb 2025 18:49:08 +0100 Subject: [PATCH 268/369] add link to documentation of Gabidulin codes --- conftest.py | 347 -------------------------- src/doc/en/reference/coding/index.rst | 1 + 2 files changed, 1 insertion(+), 347 deletions(-) delete mode 100644 conftest.py diff --git a/conftest.py b/conftest.py deleted file mode 100644 index 5307d7f6233..00000000000 --- a/conftest.py +++ /dev/null @@ -1,347 +0,0 @@ -# pyright: strict -"""Configuration and fixtures for pytest. - -This file configures pytest and provides some global fixtures. -See https://docs.pytest.org/en/latest/index.html for more details. -""" - -from __future__ import annotations - -import doctest -import inspect -import sys -import warnings -from pathlib import Path -from typing import Any, Iterable, Optional - -import pytest -from _pytest.doctest import ( - DoctestItem, - DoctestModule, - _get_continue_on_failure, - _get_runner, - _is_mocked, - _patch_unwrap_mock_aware, - get_optionflags, -) -from _pytest.pathlib import ImportMode, import_path - -from sage.doctest.forker import ( - init_sage, - showwarning_with_traceback, -) -from sage.doctest.parsing import SageDocTestParser, SageOutputChecker - - -class SageDoctestModule(DoctestModule): - """ - This is essentially a copy of `DoctestModule` from - https://github.com/pytest-dev/pytest/blob/main/src/_pytest/doctest.py. - The only change is that we use `SageDocTestParser` to extract the doctests - and `SageOutputChecker` to verify the output. - """ - - def collect(self) -> Iterable[DoctestItem]: - import doctest - - class MockAwareDocTestFinder(doctest.DocTestFinder): - """A hackish doctest finder that overrides stdlib internals to fix a stdlib bug. - https://github.com/pytest-dev/pytest/issues/3456 - https://bugs.python.org/issue25532 - """ - - def __init__(self) -> None: - super().__init__(parser=SageDocTestParser(set(["sage"]))) - - def _find_lineno(self, obj, source_lines): - """Doctest code does not take into account `@property`, this - is a hackish way to fix it. https://bugs.python.org/issue17446 - Wrapped Doctests will need to be unwrapped so the correct - line number is returned. This will be reported upstream. #8796 - """ - if isinstance(obj, property): - obj = getattr(obj, "fget", obj) - - if hasattr(obj, "__wrapped__"): - # Get the main obj in case of it being wrapped - obj = inspect.unwrap(obj) - - # Type ignored because this is a private function. - return super()._find_lineno( # type:ignore[misc] - obj, - source_lines, - ) - - def _find( - self, tests, obj, name, module, source_lines, globs, seen - ) -> None: - if _is_mocked(obj): - return - with _patch_unwrap_mock_aware(): - # Type ignored because this is a private function. - super()._find( # type:ignore[misc] - tests, obj, name, module, source_lines, globs, seen - ) - - if self.path.name == "conftest.py": - module = self.config.pluginmanager._importconftest( - self.path, - self.config.getoption("importmode"), - rootpath=self.config.rootpath, - consider_namespace_packages=True, - ) - else: - try: - module = import_path( - self.path, - mode=ImportMode.importlib, - root=self.config.rootpath, - consider_namespace_packages=True, - ) - except ImportError as exception: - if self.config.getvalue("doctest_ignore_import_errors"): - pytest.skip("unable to import module %r" % self.path) - else: - if isinstance(exception, ModuleNotFoundError): - # Ignore some missing features/modules for now - # TODO: Remove this once all optional things are using Features - if exception.name in ( - "valgrind", - "rpy2", - "sage.libs.coxeter3.coxeter", - ): - pytest.skip( - f"unable to import module { self.path } due to missing feature { exception.name }" - ) - raise - # Uses internal doctest module parsing mechanism. - finder = MockAwareDocTestFinder() - optionflags = get_optionflags(self.config) - from sage.features import FeatureNotPresentError - - runner = _get_runner( - verbose=False, - optionflags=optionflags, - checker=SageOutputChecker(), - continue_on_failure=_get_continue_on_failure(self.config), - ) - try: - for test in finder.find(module, module.__name__): - if test.examples: # skip empty doctests - yield DoctestItem.from_parent( - self, name=test.name, runner=runner, dtest=test - ) - except FeatureNotPresentError as exception: - pytest.skip( - f"unable to import module { self.path } due to missing feature { exception.feature.name }" - ) - except ModuleNotFoundError as exception: - # TODO: Remove this once all optional things are using Features - pytest.skip( - f"unable to import module { self.path } due to missing module { exception.name }" - ) - - -class IgnoreCollector(pytest.Collector): - """ - Ignore a file. - """ - - def __init__(self, parent: pytest.Collector) -> None: - super().__init__("ignore", parent) - - def collect(self) -> Iterable[pytest.Item | pytest.Collector]: - return [] - - -def pytest_collect_file( - file_path: Path, parent: pytest.Collector -) -> pytest.Collector | None: - """ - This hook is called when collecting test files, and can be used to - modify the file or test selection logic by returning a list of - ``pytest.Item`` objects which the ``pytest`` command will directly - add to the list of test items. - - See `pytest documentation `_. - """ - if ( - file_path.parent.name == "combinat" - or file_path.parent.parent.name == "combinat" - ): - # Crashes CI for some reason - return IgnoreCollector.from_parent(parent) - if file_path.suffix == ".pyx": - # We don't allow pytests to be defined in Cython files. - # Normally, Cython files are filtered out already by pytest and we only - # hit this here if someone explicitly runs `pytest some_file.pyx`. - return IgnoreCollector.from_parent(parent) - elif file_path.suffix == ".py": - if parent.config.option.doctest: - if file_path.name == "__main__.py" or file_path.name == "setup.py": - # We don't allow tests to be defined in __main__.py/setup.py files (because their import will fail). - return IgnoreCollector.from_parent(parent) - if ( - ( - file_path.name == "postprocess.py" - and file_path.parent.name == "nbconvert" - ) - or ( - file_path.name == "giacpy-mkkeywords.py" - and file_path.parent.name == "autogen" - ) - or ( - file_path.name == "flint_autogen.py" - and file_path.parent.name == "autogen" - ) - ): - # This is an executable file. - return IgnoreCollector.from_parent(parent) - - if file_path.name == "conftest_inputtest.py": - # This is an input file for testing the doctest machinery (and contains broken doctests). - return IgnoreCollector.from_parent(parent) - - if ( - ( - file_path.name == "finite_dimensional_lie_algebras_with_basis.py" - and file_path.parent.name == "categories" - ) - or ( - file_path.name == "__init__.py" - and file_path.parent.name == "crypto" - ) - or (file_path.name == "__init__.py" and file_path.parent.name == "mq") - ): - # TODO: Fix these (import fails with "RuntimeError: dictionary changed size during iteration") - return IgnoreCollector.from_parent(parent) - - if ( - file_path.name in ("forker.py", "reporting.py") - ) and file_path.parent.name == "doctest": - # Fails with many errors due to different testing framework - return IgnoreCollector.from_parent(parent) - - if ( - ( - file_path.name == "arithgroup_generic.py" - and file_path.parent.name == "arithgroup" - ) - or ( - file_path.name == "pari.py" - and file_path.parent.name == "lfunctions" - ) - or ( - file_path.name == "permgroup_named.py" - and file_path.parent.name == "perm_gps" - ) - or ( - file_path.name == "finitely_generated.py" - and file_path.parent.name == "matrix_gps" - ) - or ( - file_path.name == "libgap_mixin.py" - and file_path.parent.name == "groups" - ) - or ( - file_path.name == "finitely_presented.py" - and file_path.parent.name == "groups" - ) - or ( - file_path.name == "classical_geometries.py" - and file_path.parent.name == "generators" - ) - ): - # Fails with "Fatal Python error" - return IgnoreCollector.from_parent(parent) - - return SageDoctestModule.from_parent(parent, path=file_path) - - -def pytest_addoption(parser): - # Add a command line option to run doctests - # (we don't use the built-in --doctest-modules option because then doctests are collected twice) - group = parser.getgroup("collect") - group.addoption( - "--doctest", - action="store_true", - default=False, - help="Run doctests in all .py modules", - dest="doctest", - ) - - -# Monkey patch exception printing to replace the full qualified name of the exception by its short name -# TODO: Remove this hack once migration to pytest is complete -import traceback - -old_format_exception_only = traceback.format_exception_only - - -def format_exception_only(etype: type, value: BaseException) -> list[str]: - formatted_exception = old_format_exception_only(etype, value) - exception_name = etype.__name__ - if etype.__module__: - exception_full_name = etype.__module__ + "." + etype.__qualname__ - else: - exception_full_name = etype.__qualname__ - - for i, line in enumerate(formatted_exception): - if line.startswith(exception_full_name): - formatted_exception[i] = line.replace( - exception_full_name, exception_name, 1 - ) - return formatted_exception - - -# Initialize Sage-specific doctest stuff -init_sage() - -# Monkey patch doctest to use our custom printer etc -old_run = doctest.DocTestRunner.run - - -def doctest_run( - self: doctest.DocTestRunner, - test: doctest.DocTest, - compileflags: Optional[int] = None, - out: Any = None, - clear_globs: bool = True, -) -> doctest.TestResults: - from sage.repl.rich_output import get_display_manager - from sage.repl.user_globals import set_globals - - traceback.format_exception_only = format_exception_only - - # Display warnings in doctests - warnings.showwarning = showwarning_with_traceback - setattr(sys, "__displayhook__", get_display_manager().displayhook) - - # Ensure that injecting globals works as expected in doctests - set_globals(test.globs) - return old_run(self, test, compileflags, out, clear_globs) - - -doctest.DocTestRunner.run = doctest_run - - -@pytest.fixture(autouse=True, scope="session") -def add_imports(doctest_namespace: dict[str, Any]): - """ - Add global imports for doctests. - - See `pytest documentation `. - """ - # Inject sage.all into each doctest - import sage.repl.ipython_kernel.all_jupyter - - dict_all = sage.repl.ipython_kernel.all_jupyter.__dict__ - - # Remove '__package__' item from the globals since it is not - # always in the globals in an actual Sage session. - dict_all.pop("__package__", None) - - sage_namespace = dict(dict_all) - sage_namespace["__name__"] = "__main__" - - doctest_namespace.update(**sage_namespace) diff --git a/src/doc/en/reference/coding/index.rst b/src/doc/en/reference/coding/index.rst index 637d1a2e65f..c2c30ef8338 100644 --- a/src/doc/en/reference/coding/index.rst +++ b/src/doc/en/reference/coding/index.rst @@ -70,6 +70,7 @@ computations for structural invariants are available. sage/coding/goppa_code sage/coding/kasami_codes sage/coding/ag_code + sage/coding/gabidulin_code .. toctree:: :hidden: From fcf880214559e9f640f85b0bfdb412cb6bf75dc1 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Tue, 11 Feb 2025 18:52:24 +0100 Subject: [PATCH 269/369] add again conftest.py (which was gone) --- conftest.py | 347 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 347 insertions(+) create mode 100644 conftest.py diff --git a/conftest.py b/conftest.py new file mode 100644 index 00000000000..5307d7f6233 --- /dev/null +++ b/conftest.py @@ -0,0 +1,347 @@ +# pyright: strict +"""Configuration and fixtures for pytest. + +This file configures pytest and provides some global fixtures. +See https://docs.pytest.org/en/latest/index.html for more details. +""" + +from __future__ import annotations + +import doctest +import inspect +import sys +import warnings +from pathlib import Path +from typing import Any, Iterable, Optional + +import pytest +from _pytest.doctest import ( + DoctestItem, + DoctestModule, + _get_continue_on_failure, + _get_runner, + _is_mocked, + _patch_unwrap_mock_aware, + get_optionflags, +) +from _pytest.pathlib import ImportMode, import_path + +from sage.doctest.forker import ( + init_sage, + showwarning_with_traceback, +) +from sage.doctest.parsing import SageDocTestParser, SageOutputChecker + + +class SageDoctestModule(DoctestModule): + """ + This is essentially a copy of `DoctestModule` from + https://github.com/pytest-dev/pytest/blob/main/src/_pytest/doctest.py. + The only change is that we use `SageDocTestParser` to extract the doctests + and `SageOutputChecker` to verify the output. + """ + + def collect(self) -> Iterable[DoctestItem]: + import doctest + + class MockAwareDocTestFinder(doctest.DocTestFinder): + """A hackish doctest finder that overrides stdlib internals to fix a stdlib bug. + https://github.com/pytest-dev/pytest/issues/3456 + https://bugs.python.org/issue25532 + """ + + def __init__(self) -> None: + super().__init__(parser=SageDocTestParser(set(["sage"]))) + + def _find_lineno(self, obj, source_lines): + """Doctest code does not take into account `@property`, this + is a hackish way to fix it. https://bugs.python.org/issue17446 + Wrapped Doctests will need to be unwrapped so the correct + line number is returned. This will be reported upstream. #8796 + """ + if isinstance(obj, property): + obj = getattr(obj, "fget", obj) + + if hasattr(obj, "__wrapped__"): + # Get the main obj in case of it being wrapped + obj = inspect.unwrap(obj) + + # Type ignored because this is a private function. + return super()._find_lineno( # type:ignore[misc] + obj, + source_lines, + ) + + def _find( + self, tests, obj, name, module, source_lines, globs, seen + ) -> None: + if _is_mocked(obj): + return + with _patch_unwrap_mock_aware(): + # Type ignored because this is a private function. + super()._find( # type:ignore[misc] + tests, obj, name, module, source_lines, globs, seen + ) + + if self.path.name == "conftest.py": + module = self.config.pluginmanager._importconftest( + self.path, + self.config.getoption("importmode"), + rootpath=self.config.rootpath, + consider_namespace_packages=True, + ) + else: + try: + module = import_path( + self.path, + mode=ImportMode.importlib, + root=self.config.rootpath, + consider_namespace_packages=True, + ) + except ImportError as exception: + if self.config.getvalue("doctest_ignore_import_errors"): + pytest.skip("unable to import module %r" % self.path) + else: + if isinstance(exception, ModuleNotFoundError): + # Ignore some missing features/modules for now + # TODO: Remove this once all optional things are using Features + if exception.name in ( + "valgrind", + "rpy2", + "sage.libs.coxeter3.coxeter", + ): + pytest.skip( + f"unable to import module { self.path } due to missing feature { exception.name }" + ) + raise + # Uses internal doctest module parsing mechanism. + finder = MockAwareDocTestFinder() + optionflags = get_optionflags(self.config) + from sage.features import FeatureNotPresentError + + runner = _get_runner( + verbose=False, + optionflags=optionflags, + checker=SageOutputChecker(), + continue_on_failure=_get_continue_on_failure(self.config), + ) + try: + for test in finder.find(module, module.__name__): + if test.examples: # skip empty doctests + yield DoctestItem.from_parent( + self, name=test.name, runner=runner, dtest=test + ) + except FeatureNotPresentError as exception: + pytest.skip( + f"unable to import module { self.path } due to missing feature { exception.feature.name }" + ) + except ModuleNotFoundError as exception: + # TODO: Remove this once all optional things are using Features + pytest.skip( + f"unable to import module { self.path } due to missing module { exception.name }" + ) + + +class IgnoreCollector(pytest.Collector): + """ + Ignore a file. + """ + + def __init__(self, parent: pytest.Collector) -> None: + super().__init__("ignore", parent) + + def collect(self) -> Iterable[pytest.Item | pytest.Collector]: + return [] + + +def pytest_collect_file( + file_path: Path, parent: pytest.Collector +) -> pytest.Collector | None: + """ + This hook is called when collecting test files, and can be used to + modify the file or test selection logic by returning a list of + ``pytest.Item`` objects which the ``pytest`` command will directly + add to the list of test items. + + See `pytest documentation `_. + """ + if ( + file_path.parent.name == "combinat" + or file_path.parent.parent.name == "combinat" + ): + # Crashes CI for some reason + return IgnoreCollector.from_parent(parent) + if file_path.suffix == ".pyx": + # We don't allow pytests to be defined in Cython files. + # Normally, Cython files are filtered out already by pytest and we only + # hit this here if someone explicitly runs `pytest some_file.pyx`. + return IgnoreCollector.from_parent(parent) + elif file_path.suffix == ".py": + if parent.config.option.doctest: + if file_path.name == "__main__.py" or file_path.name == "setup.py": + # We don't allow tests to be defined in __main__.py/setup.py files (because their import will fail). + return IgnoreCollector.from_parent(parent) + if ( + ( + file_path.name == "postprocess.py" + and file_path.parent.name == "nbconvert" + ) + or ( + file_path.name == "giacpy-mkkeywords.py" + and file_path.parent.name == "autogen" + ) + or ( + file_path.name == "flint_autogen.py" + and file_path.parent.name == "autogen" + ) + ): + # This is an executable file. + return IgnoreCollector.from_parent(parent) + + if file_path.name == "conftest_inputtest.py": + # This is an input file for testing the doctest machinery (and contains broken doctests). + return IgnoreCollector.from_parent(parent) + + if ( + ( + file_path.name == "finite_dimensional_lie_algebras_with_basis.py" + and file_path.parent.name == "categories" + ) + or ( + file_path.name == "__init__.py" + and file_path.parent.name == "crypto" + ) + or (file_path.name == "__init__.py" and file_path.parent.name == "mq") + ): + # TODO: Fix these (import fails with "RuntimeError: dictionary changed size during iteration") + return IgnoreCollector.from_parent(parent) + + if ( + file_path.name in ("forker.py", "reporting.py") + ) and file_path.parent.name == "doctest": + # Fails with many errors due to different testing framework + return IgnoreCollector.from_parent(parent) + + if ( + ( + file_path.name == "arithgroup_generic.py" + and file_path.parent.name == "arithgroup" + ) + or ( + file_path.name == "pari.py" + and file_path.parent.name == "lfunctions" + ) + or ( + file_path.name == "permgroup_named.py" + and file_path.parent.name == "perm_gps" + ) + or ( + file_path.name == "finitely_generated.py" + and file_path.parent.name == "matrix_gps" + ) + or ( + file_path.name == "libgap_mixin.py" + and file_path.parent.name == "groups" + ) + or ( + file_path.name == "finitely_presented.py" + and file_path.parent.name == "groups" + ) + or ( + file_path.name == "classical_geometries.py" + and file_path.parent.name == "generators" + ) + ): + # Fails with "Fatal Python error" + return IgnoreCollector.from_parent(parent) + + return SageDoctestModule.from_parent(parent, path=file_path) + + +def pytest_addoption(parser): + # Add a command line option to run doctests + # (we don't use the built-in --doctest-modules option because then doctests are collected twice) + group = parser.getgroup("collect") + group.addoption( + "--doctest", + action="store_true", + default=False, + help="Run doctests in all .py modules", + dest="doctest", + ) + + +# Monkey patch exception printing to replace the full qualified name of the exception by its short name +# TODO: Remove this hack once migration to pytest is complete +import traceback + +old_format_exception_only = traceback.format_exception_only + + +def format_exception_only(etype: type, value: BaseException) -> list[str]: + formatted_exception = old_format_exception_only(etype, value) + exception_name = etype.__name__ + if etype.__module__: + exception_full_name = etype.__module__ + "." + etype.__qualname__ + else: + exception_full_name = etype.__qualname__ + + for i, line in enumerate(formatted_exception): + if line.startswith(exception_full_name): + formatted_exception[i] = line.replace( + exception_full_name, exception_name, 1 + ) + return formatted_exception + + +# Initialize Sage-specific doctest stuff +init_sage() + +# Monkey patch doctest to use our custom printer etc +old_run = doctest.DocTestRunner.run + + +def doctest_run( + self: doctest.DocTestRunner, + test: doctest.DocTest, + compileflags: Optional[int] = None, + out: Any = None, + clear_globs: bool = True, +) -> doctest.TestResults: + from sage.repl.rich_output import get_display_manager + from sage.repl.user_globals import set_globals + + traceback.format_exception_only = format_exception_only + + # Display warnings in doctests + warnings.showwarning = showwarning_with_traceback + setattr(sys, "__displayhook__", get_display_manager().displayhook) + + # Ensure that injecting globals works as expected in doctests + set_globals(test.globs) + return old_run(self, test, compileflags, out, clear_globs) + + +doctest.DocTestRunner.run = doctest_run + + +@pytest.fixture(autouse=True, scope="session") +def add_imports(doctest_namespace: dict[str, Any]): + """ + Add global imports for doctests. + + See `pytest documentation `. + """ + # Inject sage.all into each doctest + import sage.repl.ipython_kernel.all_jupyter + + dict_all = sage.repl.ipython_kernel.all_jupyter.__dict__ + + # Remove '__package__' item from the globals since it is not + # always in the globals in an actual Sage session. + dict_all.pop("__package__", None) + + sage_namespace = dict(dict_all) + sage_namespace["__name__"] = "__main__" + + doctest_namespace.update(**sage_namespace) From 1d9d49050ccc353a9a3610914da6031ae01d218b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Tue, 11 Feb 2025 19:07:24 +0100 Subject: [PATCH 270/369] moving random_element to category of rings --- src/sage/categories/rings.py | 25 +++++++++++++++++++++++++ src/sage/rings/ring.pyx | 31 ------------------------------- 2 files changed, 25 insertions(+), 31 deletions(-) diff --git a/src/sage/categories/rings.py b/src/sage/categories/rings.py index e0769336c6f..46fc677c840 100644 --- a/src/sage/categories/rings.py +++ b/src/sage/categories/rings.py @@ -16,6 +16,7 @@ from sage.misc.cachefunc import cached_method from sage.misc.lazy_import import LazyImport +from sage.misc.prandom import randint from sage.categories.category_with_axiom import CategoryWithAxiom from sage.categories.rngs import Rngs from sage.structure.element import Element @@ -1646,6 +1647,30 @@ def _random_nonzero_element(self, *args, **kwds): if not x.is_zero(): return x + def random_element(self, bound=2): + """ + Return a random integer coerced into this ring. + + The integer is chosen uniformly + from the interval ``[-bound,bound]``. + + INPUT: + + - ``bound`` -- integer (default: 2) + + ALGORITHM: + + This uses Python's ``randint``. + + EXAMPLES:: + + sage: ZZ.random_element(8) # random + 1 + sage: QQ.random_element(8) # random + 2 + """ + return randint(-bound, bound) * self.one() + class ElementMethods: def is_unit(self) -> bool: r""" diff --git a/src/sage/rings/ring.pyx b/src/sage/rings/ring.pyx index 6f41d0cbb7a..e54546587e4 100644 --- a/src/sage/rings/ring.pyx +++ b/src/sage/rings/ring.pyx @@ -114,7 +114,6 @@ from sage.misc.superseded import deprecation from sage.structure.coerce cimport coercion_model from sage.structure.parent cimport Parent from sage.structure.category_object cimport check_default_category -from sage.misc.prandom import randint from sage.categories.rings import Rings from sage.categories.algebras import Algebras from sage.categories.commutative_algebras import CommutativeAlgebras @@ -637,36 +636,6 @@ cdef class Ring(ParentWithGens): """ return self.zeta().multiplicative_order() - def random_element(self, bound=2): - """ - Return a random integer coerced into this ring, where the - integer is chosen uniformly from the interval ``[-bound,bound]``. - - INPUT: - - - ``bound`` -- integer (default: 2) - - ALGORITHM: - - Uses Python's randint. - - TESTS: - - The following example returns a :exc:`NotImplementedError` since the - generic ring class ``__call__`` function returns a - :exc:`NotImplementedError`. Note that - ``sage.rings.ring.Ring.random_element`` performs a call in the generic - ring class by a random integer:: - - sage: R = sage.rings.ring.Ring(ZZ); R - - sage: R.random_element() - Traceback (most recent call last): - ... - NotImplementedError: cannot construct elements of - """ - return self(randint(-bound,bound)) - @cached_method def epsilon(self): """ From 97f4d34c1413cfd17280569e15cc147b456c900d Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Tue, 11 Feb 2025 19:24:04 +0100 Subject: [PATCH 271/369] add docstring for gens --- src/sage/rings/quotient_ring.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/sage/rings/quotient_ring.py b/src/sage/rings/quotient_ring.py index ddac8d5b248..159674aec69 100644 --- a/src/sage/rings/quotient_ring.py +++ b/src/sage/rings/quotient_ring.py @@ -1248,6 +1248,16 @@ def gen(self, i=0): return self(self.__R.gen(i)) def gens(self) -> tuple: + r""" + Return a tuple containing generators of ``self``. + + EXAMPLES:: + + sage: R. = PolynomialRing(QQ) + sage: S = R.quotient_ring(x^2 + y^2) + sage: S.gens() + (xbar, ybar) + """ return tuple(self(self.__R.gen(i)) for i in range(self.cover_ring().ngens())) From bc23f92c49ed799c45c58057686b5a4eab3e4309 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Tue, 11 Feb 2025 21:23:06 +0100 Subject: [PATCH 272/369] remove from meson build --- src/sage/groups/matrix_gps/meson.build | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/sage/groups/matrix_gps/meson.build b/src/sage/groups/matrix_gps/meson.build index 77c70adf7fa..e9cd43cf946 100644 --- a/src/sage/groups/matrix_gps/meson.build +++ b/src/sage/groups/matrix_gps/meson.build @@ -9,13 +9,11 @@ py.install_sources( 'group_element.pxd', 'group_element_gap.pxd', 'heisenberg.py', - 'homset.py', 'isometries.py', 'linear.py', 'linear_gap.py', 'matrix_group.py', 'matrix_group_gap.py', - 'morphism.py', 'named_group.py', 'named_group_gap.py', 'orthogonal.py', From 1fa959b45b95f11465b73ffbc33b934628724587 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Tue, 11 Feb 2025 22:04:30 +0100 Subject: [PATCH 273/369] Update src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Antoine Leudière --- .../drinfeld_modules/charzero_drinfeld_module.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py index 27a4373bb9a..e9de0b8b1f6 100644 --- a/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py @@ -162,7 +162,8 @@ def exponential(self, prec=Infinity, name='z'): - ``prec`` -- an integer or ``Infinity`` (default: ``Infinity``); the precision at which the series is returned; if ``Infinity``, - a lazy power series in returned + a lazy power series in returned, else, a classical power series + is returned. - ``name`` -- string (default: ``'z'``); the name of the generator of the lazy power series ring From b27a7baf12914171682aa7f646af3b0758fb5f11 Mon Sep 17 00:00:00 2001 From: Jean-Philippe/Thinkbook Date: Tue, 11 Feb 2025 16:29:41 -0500 Subject: [PATCH 274/369] Added the missing methods --- src/sage/geometry/fan.py | 52 +++++++++++++++++++ src/sage/geometry/polyhedron/base5.py | 20 ++++++- .../triangulation/point_configuration.py | 51 +++++++++++++++++- 3 files changed, 120 insertions(+), 3 deletions(-) diff --git a/src/sage/geometry/fan.py b/src/sage/geometry/fan.py index 86cd4c83c65..7e6b482aa1e 100644 --- a/src/sage/geometry/fan.py +++ b/src/sage/geometry/fan.py @@ -2439,6 +2439,58 @@ def Gale_transform(self): m = m.augment(matrix(ZZ, m.nrows(), 1, [1] * m.nrows())) return matrix(ZZ, m.integer_kernel().matrix()) + def is_regular(self): + r""" + Check if ``self`` is regular. + + A rational polyhedral fan is *regular* if it is the normal fan of a + polytope. + + OUTPUT: ``True`` if ``self`` is complete and ``False`` otherwise + + EXAMPLES: + + This is the mother of all examples, which is not regular (see Section + 7.1.1 in [DLRS2010]_):: + + sage: epsilon = 0 + sage: rays = [(4-epsilon,epsilon,0),(0,4-epsilon,epsilon),(epsilon,0,4-epsilon),(2,1,1),(1,2,1),(1,1,2),(-1,-1,-1)] + sage: S1 = [Cone([rays[i] for i in indices]) for indices in [(0,1,4),(0,3,4),(1,2,5),(1,4,5),(0,2,3),(2,3,5),(3,4,5),(6,0,1),(6,1,2),(6,2,0)]] + sage: mother = Fan(S1) + sage: mother.is_regular() + False + + Doing a slight perturbation makes the same subdivision regular:: + + sage: epsilon = 1/2 + sage: rays = [(4-epsilon,epsilon,0),(0,4-epsilon,epsilon),(epsilon,0,4-epsilon),(2,1,1),(1,2,1),(1,1,2),(-1,-1,-1)] + sage: S1 = [Cone([rays[i] for i in indices]) for indices in [(0,1,4),(0,3,4),(1,2,5),(1,4,5),(0,2,3),(2,3,5),(3,4,5),(6,0,1),(6,1,2),(6,2,0)]] + sage: mother = Fan(S1) + sage: mother.is_regular() + True + + .. SEEALSO:: + + :meth:`is_projective`. + """ + if not self.is_complete(): + raise ValueError('the fan is not complete') + from sage.geometry.triangulation.point_configuration import PointConfiguration + from sage.geometry.polyhedron.constructor import Polyhedron + pc = PointConfiguration(self.rays()) + v_pc = [vector(pc.point(i)) for i in range(pc.n_points())] + v_r = [vector(list(r)) for r in self.rays()] + cone_indices = [_.ambient_ray_indices() for _ in self.generating_cones()] + translator = [v_pc.index(v_r[i]) for i in range(pc.n_points())] + translated_cone_indices = [[translator[i] for i in ci] for ci in cone_indices] + dc_pc = pc.deformation_cone(translated_cone_indices) + lift = dc_pc.an_element() + ieqs = [[lift[i]] + list(v_pc[i]) for i in range(self.nrays())] + poly = Polyhedron(ieqs=ieqs) + return self.is_equivalent(poly.normal_fan()) + + is_projective = is_regular + def generating_cone(self, n): r""" Return the ``n``-th generating cone of ``self``. diff --git a/src/sage/geometry/polyhedron/base5.py b/src/sage/geometry/polyhedron/base5.py index d2f174a5975..a7a7ab4cfb4 100644 --- a/src/sage/geometry/polyhedron/base5.py +++ b/src/sage/geometry/polyhedron/base5.py @@ -669,7 +669,10 @@ def deformation_cone(self): that the resulting polytope has a normal fan which is a coarsening of the normal fan of ``self``. - EXAMPLES:: + EXAMPLES: + + Let's examine the deformation cone of the square with one truncated + vertex:: sage: tc = Polyhedron([(1, -1), (1/3, 1), (1, 1/3), (-1, 1), (-1, -1)]) sage: dc = tc.deformation_cone() @@ -685,6 +688,19 @@ def deformation_cone(self): A ray in the direction (0, 1, 1), A ray in the direction (1, 0, 2)) + Now, let's compute the deformation cone of the pyramid over a square + and verify that it is not full dimensional:: + + sage: py = Polyhedron([(0, -1, -1), (0, -1, 1), (0, 1, -1), (0, 1, 1), (1, 0, 0)]) + sage: dc_py = py.deformation_cone(); dc_py + A 4-dimensional polyhedron in QQ^5 defined as the convex hull of 1 vertex, 1 ray, 3 lines + sage: [_.b() for _ in py.Hrepresentation()] + [0, 1, 1, 1, 1] + sage: r = dc_py.rays()[0] + sage: l1,l2,l3 = dc_py.lines() + sage: r.vector()-l1.vector()/2-l2.vector()-l3.vector()/2 + (0, 1, 1, 1, 1) + .. SEEALSO:: :meth:`~sage.schemes.toric.variety.Kaehler_cone` @@ -699,7 +715,7 @@ def deformation_cone(self): A = A.transpose() A_ker = A.right_kernel_matrix(basis='computed') gale = tuple(A_ker.columns()) - collection = [_.ambient_H_indices() for _ in self.faces(0)] + collection = [f.ambient_H_indices() for f in self.faces(0)] n = len(gale) K = None for cone_indices in collection: diff --git a/src/sage/geometry/triangulation/point_configuration.py b/src/sage/geometry/triangulation/point_configuration.py index 5b242bffd67..e6ac31dc97b 100644 --- a/src/sage/geometry/triangulation/point_configuration.py +++ b/src/sage/geometry/triangulation/point_configuration.py @@ -2121,7 +2121,7 @@ def Gale_transform(self, points=None, homogenize=True): except TypeError: pass if homogenize: - m = matrix([ (1,) + p.affine() for p in points]) + m = matrix([(1,) + p.affine() for p in points]) else: m = matrix([p.affine() for p in points]) return m.left_kernel().matrix() @@ -2167,6 +2167,55 @@ def deformation_cone(self, collection): A ray in the direction (0, 1, 0), A ray in the direction (1, 0, -1)) + Let's verify the mother of all examples explained in Section 7.1.1 of + [DLRS2010]_:: + + sage: epsilon = 0 + sage: mother = PointConfiguration([(4-epsilon,epsilon,0),(0,4-epsilon,epsilon),(epsilon,0,4-epsilon),(2,1,1),(1,2,1),(1,1,2)]) + sage: mother.points() + (P(4, 0, 0), P(0, 4, 0), P(0, 0, 4), P(2, 1, 1), P(1, 2, 1), P(1, 1, 2)) + sage: S1 = [(0,1,4),(0,3,4),(1,2,5),(1,4,5),(0,2,3),(2,3,5)] + sage: S2 = [(0,1,3),(1,3,4),(1,2,4),(2,4,5),(0,2,5),(0,3,5)] + + Both subdivisions `S1` and `S2` are not regular:: + + sage: mother_dc1 = mother.deformation_cone(S1) + sage: mother_dc1 + A 4-dimensional polyhedron in QQ^6 defined as the convex hull of 1 vertex, 1 ray, 3 lines + sage: mother_dc2 = mother.deformation_cone(S2) + sage: mother_dc2 + A 4-dimensional polyhedron in QQ^6 defined as the convex hull of 1 vertex, 1 ray, 3 lines + + Notice that they have a ray which provides a degenerate lifting which + only provides a coarsening of the subdivision from the lower hull (it + has 5 facets, and should have 8):: + + sage: result = Polyhedron([vector(list(mother.points()[_])+[mother_dc1.rays()[0][_]]) for _ in range(len(mother.points()))]) + sage: result.f_vector() + (1, 6, 9, 5, 1) + + But if we use epsilon to perturb the configuration, suddenly + `S1` becomes regular:: + + sage: epsilon = 1/2 + sage: mother = PointConfiguration([(4-epsilon,epsilon,0), + (0,4-epsilon,epsilon), + (epsilon,0,4-epsilon), + (2,1,1), + (1,2,1), + (1,1,2)]) + sage: mother.points() + (P(7/2, 1/2, 0), + P(0, 7/2, 1/2), + P(1/2, 0, 7/2), + P(2, 1, 1), + P(1, 2, 1), + P(1, 1, 2)) + sage: mother_dc1 = mother.deformation_cone(S1);mother_dc1 + A 6-dimensional polyhedron in QQ^6 defined as the convex hull of 1 vertex, 3 rays, 3 lines + sage: mother_dc2 = mother.deformation_cone(S2);mother_dc2 + A 3-dimensional polyhedron in QQ^6 defined as the convex hull of 1 vertex and 3 lines + .. SEEALSO:: :meth:`~sage.schemes.toric.variety.Kaehler_cone` From aeb512f8f52529ccc31d0b4af326dd331488efe8 Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Tue, 11 Feb 2025 22:47:57 +0100 Subject: [PATCH 275/369] Undo line gathering --- src/sage/schemes/elliptic_curves/ell_rational_field.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sage/schemes/elliptic_curves/ell_rational_field.py b/src/sage/schemes/elliptic_curves/ell_rational_field.py index 8a935ad0c09..a865996556e 100755 --- a/src/sage/schemes/elliptic_curves/ell_rational_field.py +++ b/src/sage/schemes/elliptic_curves/ell_rational_field.py @@ -2400,9 +2400,9 @@ def _compute_gens(self, proof, sage: E = EllipticCurve([-127^2,0]) sage: P = E.lift_x(611429153205013185025/9492121848205441) - sage: (set(E.gens(use_database=False, algorithm='pari', pari_effort=4)) # long time - ....: <= set([P+T for T in E.torsion_points()] - ....: + [-P+T for T in E.torsion_points()])) + sage: ge = set(E.gens(use_database=False, algorithm='pari',pari_effort=4)) # long time + sage: ge <= set([P+T for T in E.torsion_points()] # long time + ....: + [-P+T for T in E.torsion_points()]) True """ # If the optional extended database is installed and an From ad8ac8660e16d60c7c41332fd67ce8ddba69cfac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Labb=C3=A9?= Date: Tue, 11 Feb 2025 23:21:24 +0100 Subject: [PATCH 276/369] Python optimization of the code --- src/sage/geometry/fan.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/sage/geometry/fan.py b/src/sage/geometry/fan.py index 7e6b482aa1e..39fd694972b 100644 --- a/src/sage/geometry/fan.py +++ b/src/sage/geometry/fan.py @@ -2444,7 +2444,7 @@ def is_regular(self): Check if ``self`` is regular. A rational polyhedral fan is *regular* if it is the normal fan of a - polytope. + polytope. OUTPUT: ``True`` if ``self`` is complete and ``False`` otherwise @@ -2478,14 +2478,15 @@ def is_regular(self): from sage.geometry.triangulation.point_configuration import PointConfiguration from sage.geometry.polyhedron.constructor import Polyhedron pc = PointConfiguration(self.rays()) - v_pc = [vector(pc.point(i)) for i in range(pc.n_points())] - v_r = [vector(list(r)) for r in self.rays()] - cone_indices = [_.ambient_ray_indices() for _ in self.generating_cones()] - translator = [v_pc.index(v_r[i]) for i in range(pc.n_points())] + v_pc = [tuple(p) for p in pc] + pc_to_indices = {tuple(p):i for (i,p) in enumerate(pc)} + indices_to_vr = [tuple(r) for r in self.rays()] + cone_indices = [cone.ambient_ray_indices() for cone in self.generating_cones()] + translator = [pc_to_indices[t] for t in indices_to_vr] translated_cone_indices = [[translator[i] for i in ci] for ci in cone_indices] dc_pc = pc.deformation_cone(translated_cone_indices) lift = dc_pc.an_element() - ieqs = [[lift[i]] + list(v_pc[i]) for i in range(self.nrays())] + ieqs = [(lift_i,) + v for (lift_i, v) in zip(lift, v_pc)] poly = Polyhedron(ieqs=ieqs) return self.is_equivalent(poly.normal_fan()) From 949c745c5f658b65f7684895e670e681e2cabc8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Labb=C3=A9?= Date: Tue, 11 Feb 2025 23:32:24 +0100 Subject: [PATCH 277/369] creating a def mother(epsilon) in the example section --- src/sage/geometry/fan.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/sage/geometry/fan.py b/src/sage/geometry/fan.py index 39fd694972b..09a3d18bc77 100644 --- a/src/sage/geometry/fan.py +++ b/src/sage/geometry/fan.py @@ -2450,23 +2450,25 @@ def is_regular(self): EXAMPLES: - This is the mother of all examples, which is not regular (see Section - 7.1.1 in [DLRS2010]_):: + This is the mother of all examples (see Section 7.1.1 in + [DLRS2010]_):: + + sage: def mother(epsilon=0): + ....: rays = [(4-epsilon,epsilon,0),(0,4-epsilon,epsilon),(epsilon,0,4-epsilon),(2,1,1),(1,2,1),(1,1,2),(-1,-1,-1)] + ....: L = [(0,1,4),(0,3,4),(1,2,5),(1,4,5),(0,2,3),(2,3,5),(3,4,5),(6,0,1),(6,1,2),(6,2,0)] + ....: S1 = [Cone([rays[i] for i in indices]) for indices in L] + ....: return Fan(S1) + + When epsilon=0, it is not regular:: sage: epsilon = 0 - sage: rays = [(4-epsilon,epsilon,0),(0,4-epsilon,epsilon),(epsilon,0,4-epsilon),(2,1,1),(1,2,1),(1,1,2),(-1,-1,-1)] - sage: S1 = [Cone([rays[i] for i in indices]) for indices in [(0,1,4),(0,3,4),(1,2,5),(1,4,5),(0,2,3),(2,3,5),(3,4,5),(6,0,1),(6,1,2),(6,2,0)]] - sage: mother = Fan(S1) - sage: mother.is_regular() + sage: mother(epsilon).is_regular() False Doing a slight perturbation makes the same subdivision regular:: sage: epsilon = 1/2 - sage: rays = [(4-epsilon,epsilon,0),(0,4-epsilon,epsilon),(epsilon,0,4-epsilon),(2,1,1),(1,2,1),(1,1,2),(-1,-1,-1)] - sage: S1 = [Cone([rays[i] for i in indices]) for indices in [(0,1,4),(0,3,4),(1,2,5),(1,4,5),(0,2,3),(2,3,5),(3,4,5),(6,0,1),(6,1,2),(6,2,0)]] - sage: mother = Fan(S1) - sage: mother.is_regular() + sage: mother(epsilon).is_regular() True .. SEEALSO:: From 4b2452404c3259367679129ed70730a3cfd40c1c Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Wed, 12 Feb 2025 09:42:39 +0700 Subject: [PATCH 278/369] Apply sort and filter of walk_packages consistently --- src/sage/misc/package_dir.py | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/sage/misc/package_dir.py b/src/sage/misc/package_dir.py index 6b6ca12803f..d84b896f6e5 100644 --- a/src/sage/misc/package_dir.py +++ b/src/sage/misc/package_dir.py @@ -370,9 +370,9 @@ def iter_modules(path=None, prefix=''): yielded[name] = 1 yield ModuleInfo(i, name, ispkg) - def iter_importer_modules(importer, prefix=''): + def _iter_importer_modules_helper(importer, prefix=''): r""" - Yield :class:`ModuleInfo` for all modules of ``importer``. + Helper function for :func:`iter_importer_modules`. """ from importlib.machinery import FileFinder @@ -391,11 +391,6 @@ def iter_importer_modules(importer, prefix=''): for fn in filenames: modname = inspect.getmodulename(fn) - if modname and (modname in ['__init__', 'all'] - or modname.startswith('all__') - or modname in yielded): - continue - path = os.path.join(importer.path, fn) ispkg = False @@ -414,6 +409,18 @@ def iter_importer_modules(importer, prefix=''): else: yield from importer.iter_modules(prefix) + def iter_importer_modules(importer, prefix=''): + r""" + Yield :class:`ModuleInfo` for all modules of ``importer``. + """ + for name, ispkg in sorted(list(_iter_importer_modules_helper(importer, prefix))): + # we sort again for consistency of output ordering if importer is not + # a FileFinder (needed in doctest of :func:`sage.misc.dev_tools/load_submodules`) + modname = name.rsplit('.', 1)[-1] + if modname in ['__init__', 'all'] or modname.startswith('all__'): + continue + yield name, ispkg + def seen(p, m={}): if p in m: return True From 3dc5ce3e8f52ce688c5f184ba85a3b462c193494 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Tue, 11 Feb 2025 23:59:10 +0700 Subject: [PATCH 279/369] Fix more doctests in meson_editable install --- src/sage/doctest/external.py | 4 +++ src/sage/doctest/sources.py | 10 +++++++ src/sage/env.py | 4 +++ src/sage/features/meson_editable.py | 45 +++++++++++++++++++++++++++++ src/sage/misc/package_dir.py | 36 +++++++++++++++++++++-- 5 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 src/sage/features/meson_editable.py diff --git a/src/sage/doctest/external.py b/src/sage/doctest/external.py index 45b3987de05..2cc374f0763 100644 --- a/src/sage/doctest/external.py +++ b/src/sage/doctest/external.py @@ -366,6 +366,10 @@ def external_features(): r""" Generate the features that are only to be tested if ``--optional=external`` is used. + .. SEEALSO:: + + :func:`sage.features.all.all_features` + EXAMPLES:: sage: from sage.doctest.external import external_features diff --git a/src/sage/doctest/sources.py b/src/sage/doctest/sources.py index 7589f62922b..01d24d67634 100644 --- a/src/sage/doctest/sources.py +++ b/src/sage/doctest/sources.py @@ -87,8 +87,18 @@ def get_basename(path): sage: import os sage: get_basename(sage.doctest.sources.__file__) 'sage.doctest.sources' + + :: + + sage: # optional - !meson_editable sage: get_basename(os.path.join(sage.structure.__path__[0], 'element.pxd')) 'sage.structure.element.pxd' + + TESTS:: + + sage: # optional - meson_editable + sage: get_basename(os.path.join(os.path.dirname(sage.structure.__file__), 'element.pxd')) + 'sage.structure.element.pxd' """ if path is None: return None diff --git a/src/sage/env.py b/src/sage/env.py index 060eb2209a9..826c0343ac8 100644 --- a/src/sage/env.py +++ b/src/sage/env.py @@ -319,6 +319,10 @@ def sage_include_directories(use_sources=False): sage: dirs = sage.env.sage_include_directories(use_sources=True) sage: any(os.path.isfile(os.path.join(d, file)) for d in dirs) True + + :: + + sage: # optional - !meson_editable (no need, see :issue:`39275`) sage: dirs = sage.env.sage_include_directories(use_sources=False) sage: any(os.path.isfile(os.path.join(d, file)) for d in dirs) True diff --git a/src/sage/features/meson_editable.py b/src/sage/features/meson_editable.py new file mode 100644 index 00000000000..a110648b873 --- /dev/null +++ b/src/sage/features/meson_editable.py @@ -0,0 +1,45 @@ +r""" +Feature for testing if Meson editable install is used. +""" + +import sys +from . import Feature, FeatureTestResult + + +class MesonEditable(Feature): + r""" + A :class:`~sage.features.Feature` describing if Meson editable install is used. + + EXAMPLES:: + + sage: from sage.features.meson_editable import MesonEditable + sage: MesonEditable() + Feature('meson_editable') + """ + def __init__(self): + r""" + TESTS:: + + sage: from sage.features.meson_editable import MesonEditable + sage: MesonEditable() is MesonEditable() + True + """ + Feature.__init__(self, 'meson_editable') + + def _is_present(self): + r""" + Test whether Meson editable install is used. + + EXAMPLES:: + + sage: from sage.features.meson_editable import MesonEditable + sage: MesonEditable()._is_present() # random + FeatureTestResult('meson_editable', True) + """ + import sage + result = type(sage.__loader__).__module__ == '_sagemath_editable_loader' + return FeatureTestResult(self, result) + + +def all_features(): + return [MesonEditable()] diff --git a/src/sage/misc/package_dir.py b/src/sage/misc/package_dir.py index 6b6ca12803f..b9742a6d1c2 100644 --- a/src/sage/misc/package_dir.py +++ b/src/sage/misc/package_dir.py @@ -261,6 +261,7 @@ def is_package_or_sage_namespace_package_dir(path, *, distribution_filter=None): :mod:`sage.cpython` is an ordinary package:: + sage: # optional - !meson_editable sage: from sage.misc.package_dir import is_package_or_sage_namespace_package_dir sage: directory = sage.cpython.__path__[0]; directory '.../sage/cpython' @@ -270,24 +271,48 @@ def is_package_or_sage_namespace_package_dir(path, *, distribution_filter=None): :mod:`sage.libs.mpfr` only has an ``__init__.pxd`` file, but we consider it a package directory for consistency with Cython:: + sage: # optional - !meson_editable sage: directory = os.path.join(sage.libs.__path__[0], 'mpfr'); directory '.../sage/libs/mpfr' - sage: is_package_or_sage_namespace_package_dir(directory) # known bug (seen in build.yml) + sage: is_package_or_sage_namespace_package_dir(directory) True :mod:`sage` is designated to become an implicit namespace package:: + sage: # optional - !meson_editable sage: directory = sage.__path__[0]; directory '.../sage' - sage: is_package_or_sage_namespace_package_dir(directory) # known bug (seen in build.yml) + sage: is_package_or_sage_namespace_package_dir(directory) True Not a package:: + sage: # optional - !meson_editable sage: directory = os.path.join(sage.symbolic.__path__[0], 'ginac'); directory # needs sage.symbolic '.../sage/symbolic/ginac' sage: is_package_or_sage_namespace_package_dir(directory) # needs sage.symbolic False + + TESTS:: + + sage: # optional - meson_editable + sage: from sage.misc.package_dir import is_package_or_sage_namespace_package_dir + sage: directory = os.path.dirname(sage.cpython.__file__); directory + '.../sage/cpython' + sage: is_package_or_sage_namespace_package_dir(directory) + True + + sage: # optional - meson_editable + sage: directory = os.path.join(os.path.dirname(sage.libs.__file__), 'mpfr'); directory + '.../sage/libs/mpfr' + sage: is_package_or_sage_namespace_package_dir(directory) + True + + sage: # optional - meson_editable, sage.symbolic + sage: directory = os.path.join(os.path.dirname(sage.symbolic.__file__), 'ginac'); directory + '.../sage/symbolic/ginac' + sage: is_package_or_sage_namespace_package_dir(directory) + False """ if os.path.exists(os.path.join(path, '__init__.py')): # ordinary package return True @@ -345,8 +370,15 @@ def walk_packages(path=None, prefix='', onerror=None): EXAMPLES:: + sage: # optional - !meson_editable sage: sorted(sage.misc.package_dir.walk_packages(sage.misc.__path__)) # a namespace package [..., ModuleInfo(module_finder=FileFinder('.../sage/misc'), name='package_dir', ispkg=False), ...] + + TESTS:: + + sage: # optional - meson_editable + sage: sorted(sage.misc.package_dir.walk_packages(sage.misc.__path__)) + [..., ModuleInfo(module_finder=<...MesonpyPathFinder object...>, name='package_dir', ispkg=False), ...] """ # Adapted from https://github.com/python/cpython/blob/3.11/Lib/pkgutil.py From f77df2a9d93c4c69a15c2233fc96ad280e43400b Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Wed, 12 Feb 2025 10:17:42 +0700 Subject: [PATCH 280/369] Improve sage_getfile by looking at __init__ --- src/sage/misc/sageinspect.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/sage/misc/sageinspect.py b/src/sage/misc/sageinspect.py index 4f7aed2820f..9066671f6f9 100644 --- a/src/sage/misc/sageinspect.py +++ b/src/sage/misc/sageinspect.py @@ -1328,6 +1328,16 @@ def sage_getfile(obj): if isinstance(obj, functools.partial): return sage_getfile(obj.func) return sage_getfile(obj.__class__) # inspect.getabsfile(obj.__class__) + else: + try: + objinit = obj.__init__ + except AttributeError: + pass + else: + pos = _extract_embedded_position(_sage_getdoc_unformatted(objinit)) + if pos is not None: + (_, filename, _) = pos + return filename # No go? fall back to inspect. try: @@ -1336,6 +1346,10 @@ def sage_getfile(obj): return '' for suffix in import_machinery.EXTENSION_SUFFIXES: if sourcefile.endswith(suffix): + # TODO: the following is incorrect in meson editable install + # but as long as either the class or its __init__ method has a + # docstring, _sage_getdoc_unformatted should return correct result + # see https://github.com/mesonbuild/meson-python/issues/723 return sourcefile.removesuffix(suffix)+os.path.extsep+'pyx' return sourcefile From f2dd36fe94e1782647aa77fb05b2ff89c05d19c8 Mon Sep 17 00:00:00 2001 From: Dave Witte Morris Date: Tue, 11 Feb 2025 22:50:11 -0700 Subject: [PATCH 281/369] #34821 strings as matrix entries --- src/sage/matrix/args.pyx | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/src/sage/matrix/args.pyx b/src/sage/matrix/args.pyx index 332eaf82df8..19406c16121 100644 --- a/src/sage/matrix/args.pyx +++ b/src/sage/matrix/args.pyx @@ -1515,12 +1515,19 @@ cdef class MatrixArgs: [() 0 0] [ 0 () 0] [ 0 0 ()] + + Verify that :issue:`34821` is fixed:: + + sage: matrix(ZZ, 2, 2, "3") + [3 0] + [0 3] """ # Check basic Python types. This is very fast, so it doesn't # hurt to do these first. if self.entries is None: return MA_ENTRIES_ZERO - if isinstance(self.entries, (int, float, complex, Integer)): + if isinstance(self.entries, (int, float, complex, Integer, str)): + # Note that a string is not considered to be a sequence. if self.entries: return MA_ENTRIES_SCALAR return MA_ENTRIES_ZERO @@ -1565,9 +1572,6 @@ cdef class MatrixArgs: if isinstance(self.entries, MatrixArgs): # Prevent recursion return MA_ENTRIES_UNKNOWN - if isinstance(self.entries, str): - # Blacklist strings, we don't want them to be considered a sequence - return MA_ENTRIES_UNKNOWN try: self.entries = list(self.entries) except TypeError: @@ -1586,6 +1590,16 @@ cdef class MatrixArgs: is a sequence. If the entries are invalid, return ``MA_ENTRIES_UNKNOWN``. + + TESTS: + + Verify that :issue:`34821` is fixed:: + + sage: matrix(ZZ, 1,2, ["1", "2"]) + [1 2] + sage: matrix(ZZ, 2,1, ["1", "2"]) + [1] + [2] """ if not self.entries: return MA_ENTRIES_SEQ_FLAT @@ -1601,13 +1615,11 @@ cdef class MatrixArgs: return MA_ENTRIES_SEQ_SEQ else: return MA_ENTRIES_SEQ_FLAT - if isinstance(x, (int, float, complex)): + if isinstance(x, (int, float, complex, str)): + # Note that a string is not considered to be a sequence. return MA_ENTRIES_SEQ_FLAT if isinstance(x, Element) and element_is_scalar(x): return MA_ENTRIES_SEQ_FLAT - if isinstance(x, str): - # Blacklist strings, we don't want them to be considered a sequence - return MA_ENTRIES_UNKNOWN try: iter(x) except TypeError: From 0bdb8e299fcbfac76f8dce80ec9d34335a9cfb02 Mon Sep 17 00:00:00 2001 From: Dave Witte Morris Date: Tue, 11 Feb 2025 23:02:26 -0700 Subject: [PATCH 282/369] fix doctest --- src/sage/matrix/args.pyx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/sage/matrix/args.pyx b/src/sage/matrix/args.pyx index 19406c16121..f7091d1efba 100644 --- a/src/sage/matrix/args.pyx +++ b/src/sage/matrix/args.pyx @@ -297,9 +297,12 @@ cdef class MatrixArgs: Test invalid input:: sage: MatrixArgs(ZZ, 2, 2, entries='abcd').finalized() + + sage: matrix(ZZ, 2, 2, entries='abcd') Traceback (most recent call last): ... - TypeError: unable to convert 'abcd' to a matrix + TypeError: unable to convert 'abcd' to an integer sage: MatrixArgs(ZZ, 2, 2, entries=MatrixArgs()).finalized() Traceback (most recent call last): ... From b6cb2f4bbe85113348fc2899d06cbbd029663967 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Wed, 12 Feb 2025 11:11:10 +0700 Subject: [PATCH 283/369] Workaround for test-new failure --- src/sage/misc/package_dir.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/misc/package_dir.py b/src/sage/misc/package_dir.py index b9742a6d1c2..e041167e945 100644 --- a/src/sage/misc/package_dir.py +++ b/src/sage/misc/package_dir.py @@ -274,7 +274,7 @@ def is_package_or_sage_namespace_package_dir(path, *, distribution_filter=None): sage: # optional - !meson_editable sage: directory = os.path.join(sage.libs.__path__[0], 'mpfr'); directory '.../sage/libs/mpfr' - sage: is_package_or_sage_namespace_package_dir(directory) + sage: is_package_or_sage_namespace_package_dir(directory) # known bug (seen in build.yml) True :mod:`sage` is designated to become an implicit namespace package:: From 7002dbdcdd855a3c0e84f982f759d683b2799ed3 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Wed, 12 Feb 2025 14:29:27 +0700 Subject: [PATCH 284/369] Fix links --- src/sage/rings/laurent_series_ring_element.pyx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/rings/laurent_series_ring_element.pyx b/src/sage/rings/laurent_series_ring_element.pyx index cd33190e935..bdc2cdea53e 100644 --- a/src/sage/rings/laurent_series_ring_element.pyx +++ b/src/sage/rings/laurent_series_ring_element.pyx @@ -4,8 +4,8 @@ Laurent Series Laurent series in Sage are represented internally as a power of the variable times the power series part. If a Laurent series `f` is represented as `f = t^n \cdot u` where `t` is the variable and `u` has nonzero constant term, -`u` can be accessed through :meth:`valuation_zero_part` and `n` can be accessed -through :meth:`valuation`. +`u` can be accessed through :meth:`~LaurentSeries.valuation_zero_part` and `n` +can be accessed through :meth:`~LaurentSeries.valuation`. EXAMPLES:: From b20679750d123412e14138723530886719a20c46 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Wed, 12 Feb 2025 10:04:35 +0100 Subject: [PATCH 285/369] comment code --- .../charzero_drinfeld_module.py | 28 +++++++++++++++++-- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py index f34b2ebbe8d..32225468aa8 100644 --- a/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py @@ -572,18 +572,28 @@ def class_polynomial(self): ... ValueError: coefficients are not polynomials """ + # The algorithm is based on the following remark: + # writing phi_T = g_0 + g_1*tau + ... + g_r*tau^r, + # if s > deg(g_i/(q^i - 1)) - 1 for all i, then the + # class module is equal to + # H := E(Kinfty/A) / < T^(-s), T^(-s-1), ... > + # where E(Kinfty/A) is Kinfty/A equipped with the + # A-module structure coming from phi. + A = self.function_ring() Fq = A.base_ring() q = Fq.cardinality() r = self.rank() + # We compute the bound s gs = self.coefficients_in_function_ring(sparse=False) - s = max(gs[i].degree() // (q**i - 1) for i in range(1, r+1)) if s == 0: - # small case return A.one() + # We compute the matrix of phi_T acting on the quotient + # M := (Kinfty/A) / < T^(-s), T^(-s-1), ... > + # (for the standard structure of A-module!) M = matrix(Fq, s) qk = 1 for k in range(r+1): @@ -596,6 +606,12 @@ def class_polynomial(self): M[i, j] += gs[k][e] qk *= q + # We compute the subspace of E(Kinfty/A) (for the twisted + # structure of A-module!) + # V = < T^(-s), T^(-s+1), ... > + # It is also the phi_T-saturation of T^(-s+1) in M, i.e. + # the Fq-vector space generated by the phi_T^i(T^(-s+1)) + # for i varying in NN. v = vector(Fq, s) v[s-1] = 1 vs = [v] @@ -605,6 +621,8 @@ def class_polynomial(self): V = matrix(vs) V.echelonize() + # We compute the action of phi_T on H = M/V + # as an Fq-linear map (encoded in the matrix N) dim = V.rank() pivots = V.pivots() j = ip = 0 @@ -613,6 +631,10 @@ def class_polynomial(self): j += 1 ip += 1 V[i,j] = 1 - N = (V * M * ~V).submatrix(dim, dim) + + # The class module is now H where the action of T + # is given by the matrix N + # The class polynomial is then the characteristic + # polynomial of N return A(N.charpoly()) From ac3ad051562acea4c5a694480576d17f16b40f0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Wed, 12 Feb 2025 10:05:46 +0100 Subject: [PATCH 286/369] fix one of the failures --- src/sage/categories/sets_cat.py | 2 +- src/sage/rings/finite_rings/integer_mod_ring.py | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/sage/categories/sets_cat.py b/src/sage/categories/sets_cat.py index ba13c6bc9a4..951bc7f2292 100644 --- a/src/sage/categories/sets_cat.py +++ b/src/sage/categories/sets_cat.py @@ -2476,7 +2476,7 @@ def random_element(self, *args): True """ return self._cartesian_product_of_elements( - c.random_element(*args) for c in self.cartesian_factors()) + c.random_element(*args) for c in self.cartesian_factors()) @abstract_method def _sets_keys(self): diff --git a/src/sage/rings/finite_rings/integer_mod_ring.py b/src/sage/rings/finite_rings/integer_mod_ring.py index 70ab41447b4..bd04ae7fae6 100644 --- a/src/sage/rings/finite_rings/integer_mod_ring.py +++ b/src/sage/rings/finite_rings/integer_mod_ring.py @@ -1541,14 +1541,15 @@ def random_element(self, bound=None): sage: while not all(found): ....: found[R.random_element()] = True - We test ``bound``-option:: + We test the ``bound`` option:: - sage: R.random_element(2) in [R(16), R(17), R(0), R(1), R(2)] + sage: R.random_element(2) in [R(-2), R(-1), R(0), R(1), R(2)] True """ if bound is not None: - return CommutativeRing.random_element(self, bound) - a = random.randint(0, self.order() - 1) + a = random.randint(-bound, bound) + else: + a = random.randint(0, self.order() - 1) return self(a) @staticmethod From 34ed89c6be7db97b2873bb2b5ac889aa09e4c103 Mon Sep 17 00:00:00 2001 From: Jean-Philippe/Thinkbook Date: Wed, 12 Feb 2025 04:30:25 -0500 Subject: [PATCH 287/369] Linting the changes --- src/sage/geometry/fan.py | 23 ++++--- src/sage/geometry/polyhedron/base5.py | 16 ++--- .../triangulation/point_configuration.py | 62 +++++++++---------- 3 files changed, 49 insertions(+), 52 deletions(-) diff --git a/src/sage/geometry/fan.py b/src/sage/geometry/fan.py index 09a3d18bc77..66451a679b0 100644 --- a/src/sage/geometry/fan.py +++ b/src/sage/geometry/fan.py @@ -2439,14 +2439,15 @@ def Gale_transform(self): m = m.augment(matrix(ZZ, m.nrows(), 1, [1] * m.nrows())) return matrix(ZZ, m.integer_kernel().matrix()) - def is_regular(self): + def is_polytopal(self): r""" - Check if ``self`` is regular. + Check if ``self`` is the normal fan of a polytope. - A rational polyhedral fan is *regular* if it is the normal fan of a - polytope. + A rational polyhedral fan is *polytopal* if it is the normal fan of a + polytope. This is also called *regular*, or provide a *coherent* + subdivision or leads to a *projective* toric variety. - OUTPUT: ``True`` if ``self`` is complete and ``False`` otherwise + OUTPUT: ``True`` if ``self`` is polytopal and ``False`` otherwise EXAMPLES: @@ -2459,16 +2460,16 @@ def is_regular(self): ....: S1 = [Cone([rays[i] for i in indices]) for indices in L] ....: return Fan(S1) - When epsilon=0, it is not regular:: + When epsilon=0, it is not polytopal:: sage: epsilon = 0 - sage: mother(epsilon).is_regular() + sage: mother(epsilon).is_polytopal() False - Doing a slight perturbation makes the same subdivision regular:: + Doing a slight perturbation makes the same subdivision polytopal:: sage: epsilon = 1/2 - sage: mother(epsilon).is_regular() + sage: mother(epsilon).is_polytopal() True .. SEEALSO:: @@ -2476,7 +2477,7 @@ def is_regular(self): :meth:`is_projective`. """ if not self.is_complete(): - raise ValueError('the fan is not complete') + raise ValueError('To be polytopal, the fan should be complete.') from sage.geometry.triangulation.point_configuration import PointConfiguration from sage.geometry.polyhedron.constructor import Polyhedron pc = PointConfiguration(self.rays()) @@ -2492,8 +2493,6 @@ def is_regular(self): poly = Polyhedron(ieqs=ieqs) return self.is_equivalent(poly.normal_fan()) - is_projective = is_regular - def generating_cone(self, n): r""" Return the ``n``-th generating cone of ``self``. diff --git a/src/sage/geometry/polyhedron/base5.py b/src/sage/geometry/polyhedron/base5.py index a7a7ab4cfb4..405f8cb8f24 100644 --- a/src/sage/geometry/polyhedron/base5.py +++ b/src/sage/geometry/polyhedron/base5.py @@ -711,19 +711,19 @@ def deformation_cone(self): 2.2 of [ACEP2020]. """ from .constructor import Polyhedron - A = matrix([_.A() for _ in self.Hrepresentation()]) - A = A.transpose() - A_ker = A.right_kernel_matrix(basis='computed') - gale = tuple(A_ker.columns()) + m = matrix([_.A() for _ in self.Hrepresentation()]) + m = m.transpose() + m_ker = m.right_kernel_matrix(basis='computed') + gale = tuple(m_ker.columns()) collection = [f.ambient_H_indices() for f in self.faces(0)] n = len(gale) - K = None + c = None for cone_indices in collection: dual_cone = Polyhedron(rays=[gale[i] for i in range(n) if i not in cone_indices]) - K = K.intersection(dual_cone) if K is not None else dual_cone - preimages = [A_ker.solve_right(r.vector()) for r in K.rays()] - return Polyhedron(lines=A.rows(), rays=preimages) + c = c.intersection(dual_cone) if c is not None else dual_cone + preimages = [A_ker.solve_right(r.vector()) for r in c.rays()] + return Polyhedron(lines=m.rows(), rays=preimages) ########################################################### # Binary operations. diff --git a/src/sage/geometry/triangulation/point_configuration.py b/src/sage/geometry/triangulation/point_configuration.py index e6ac31dc97b..e76e9bcc3c9 100644 --- a/src/sage/geometry/triangulation/point_configuration.py +++ b/src/sage/geometry/triangulation/point_configuration.py @@ -61,7 +61,7 @@ (2, 3, 4) sage: list(t) [(1, 3, 4), (2, 3, 4)] - sage: t.plot(axes=False) # needs sage.plot + sage: t.plot(axes=False) # needs sage.plot Graphics object consisting of 12 graphics primitives .. PLOT:: @@ -91,7 +91,7 @@ sage: p = [[0,-1,-1], [0,0,1], [0,1,0], [1,-1,-1], [1,0,1], [1,1,0]] sage: points = PointConfiguration(p) sage: triang = points.triangulate() - sage: triang.plot(axes=False) # needs sage.plot + sage: triang.plot(axes=False) # needs sage.plot Graphics3d Object .. PLOT:: @@ -116,7 +116,7 @@ 16 sage: len(nonregular) 2 - sage: nonregular[0].plot(aspect_ratio=1, axes=False) # needs sage.plot + sage: nonregular[0].plot(aspect_ratio=1, axes=False) # needs sage.plot Graphics object consisting of 25 graphics primitives sage: PointConfiguration.set_engine('internal') # to make doctests independent of TOPCOM @@ -1131,10 +1131,10 @@ def restricted_automorphism_group(self): sage: pyramid = PointConfiguration([[1,0,0], [0,1,1], [0,1,-1], ....: [0,-1,-1], [0,-1,1]]) - sage: G = pyramid.restricted_automorphism_group() # needs sage.graphs sage.groups - sage: G == PermutationGroup([[(3,5)], [(2,3),(4,5)], [(2,4)]]) # needs sage.graphs sage.groups + sage: G = pyramid.restricted_automorphism_group() # needs sage.graphs sage.groups + sage: G == PermutationGroup([[(3,5)], [(2,3),(4,5)], [(2,4)]]) # needs sage.graphs sage.groups True - sage: DihedralGroup(4).is_isomorphic(G) # needs sage.graphs sage.groups + sage: DihedralGroup(4).is_isomorphic(G) # needs sage.graphs sage.groups True The square with an off-center point in the middle. Note that @@ -1142,9 +1142,9 @@ def restricted_automorphism_group(self): `D_4` of the convex hull:: sage: square = PointConfiguration([(3/4,3/4), (1,1), (1,-1), (-1,-1), (-1,1)]) - sage: square.restricted_automorphism_group() # needs sage.graphs sage.groups + sage: square.restricted_automorphism_group() # needs sage.graphs sage.groups Permutation Group with generators [(3,5)] - sage: DihedralGroup(1).is_isomorphic(_) # needs sage.graphs sage.groups + sage: DihedralGroup(1).is_isomorphic(_) # needs sage.graphs sage.groups True """ v_list = [ vector(p.projective()) for p in self ] @@ -1532,9 +1532,9 @@ def bistellar_flips(self): sage: pc.bistellar_flips() (((<0,1,3>, <0,2,3>), (<0,1,2>, <1,2,3>)),) sage: Tpos, Tneg = pc.bistellar_flips()[0] - sage: Tpos.plot(axes=False) # needs sage.plot + sage: Tpos.plot(axes=False) # needs sage.plot Graphics object consisting of 11 graphics primitives - sage: Tneg.plot(axes=False) # needs sage.plot + sage: Tneg.plot(axes=False) # needs sage.plot Graphics object consisting of 11 graphics primitives The 3d analog:: @@ -1549,7 +1549,7 @@ def bistellar_flips(self): sage: pc.bistellar_flips() (((<0,1,3>, <0,2,3>), (<0,1,2>, <1,2,3>)),) sage: Tpos, Tneg = pc.bistellar_flips()[0] - sage: Tpos.plot(axes=False) # needs sage.plot + sage: Tpos.plot(axes=False) # needs sage.plot Graphics3d Object """ flips = [] @@ -2080,7 +2080,7 @@ def Gale_transform(self, points=None, homogenize=True): [ 1 1 1 0 -3] [ 0 2 2 -1 -3] - It might not affect the dimension of the result:: + It might not affect the dimension of the result:: sage: PC = PointConfiguration([[4,0,0],[0,4,0],[0,0,4],[2,1,1],[1,2,1],[1,1,2]]) sage: GT = PC.Gale_transform(homogenize=False);GT @@ -2136,9 +2136,9 @@ def deformation_cone(self, collection): - ``collection`` -- a collection of subconfigurations of ``self``. Subconfigurations are given as indices - OUTPUT: a polyhedron. It contains the liftings of the point configuration - making the collection a regular (or coherent, or projective) - subdivision. + OUTPUT: a polyhedron. It contains the liftings of the point configuration + making the collection a regular (or coherent, or projective, or + polytopal) subdivision. EXAMPLES:: @@ -2170,8 +2170,11 @@ def deformation_cone(self, collection): Let's verify the mother of all examples explained in Section 7.1.1 of [DLRS2010]_:: + sage: def mother(epsilon=0): + ....: return PointConfiguration([(4-epsilon,epsilon,0),(0,4-epsilon,epsilon),(epsilon,0,4-epsilon),(2,1,1),(1,2,1),(1,1,2)]) + sage: epsilon = 0 - sage: mother = PointConfiguration([(4-epsilon,epsilon,0),(0,4-epsilon,epsilon),(epsilon,0,4-epsilon),(2,1,1),(1,2,1),(1,1,2)]) + sage: m = mother(0) sage: mother.points() (P(4, 0, 0), P(0, 4, 0), P(0, 0, 4), P(2, 1, 1), P(1, 2, 1), P(1, 1, 2)) sage: S1 = [(0,1,4),(0,3,4),(1,2,5),(1,4,5),(0,2,3),(2,3,5)] @@ -2179,10 +2182,10 @@ def deformation_cone(self, collection): Both subdivisions `S1` and `S2` are not regular:: - sage: mother_dc1 = mother.deformation_cone(S1) + sage: mother_dc1 = m.deformation_cone(S1) sage: mother_dc1 A 4-dimensional polyhedron in QQ^6 defined as the convex hull of 1 vertex, 1 ray, 3 lines - sage: mother_dc2 = mother.deformation_cone(S2) + sage: mother_dc2 = m.deformation_cone(S2) sage: mother_dc2 A 4-dimensional polyhedron in QQ^6 defined as the convex hull of 1 vertex, 1 ray, 3 lines @@ -2190,7 +2193,7 @@ def deformation_cone(self, collection): only provides a coarsening of the subdivision from the lower hull (it has 5 facets, and should have 8):: - sage: result = Polyhedron([vector(list(mother.points()[_])+[mother_dc1.rays()[0][_]]) for _ in range(len(mother.points()))]) + sage: result = Polyhedron([vector(list(m.points()[_])+[mother_dc1.rays()[0][_]]) for _ in range(len(m.points()))]) sage: result.f_vector() (1, 6, 9, 5, 1) @@ -2198,30 +2201,25 @@ def deformation_cone(self, collection): `S1` becomes regular:: sage: epsilon = 1/2 - sage: mother = PointConfiguration([(4-epsilon,epsilon,0), - (0,4-epsilon,epsilon), - (epsilon,0,4-epsilon), - (2,1,1), - (1,2,1), - (1,1,2)]) - sage: mother.points() + sage: mp = mother(epsilon) + sage: mp.points() (P(7/2, 1/2, 0), P(0, 7/2, 1/2), P(1/2, 0, 7/2), P(2, 1, 1), P(1, 2, 1), P(1, 1, 2)) - sage: mother_dc1 = mother.deformation_cone(S1);mother_dc1 + sage: mother_dc1 = mp.deformation_cone(S1);mother_dc1 A 6-dimensional polyhedron in QQ^6 defined as the convex hull of 1 vertex, 3 rays, 3 lines - sage: mother_dc2 = mother.deformation_cone(S2);mother_dc2 + sage: mother_dc2 = mp.deformation_cone(S2);mother_dc2 A 3-dimensional polyhedron in QQ^6 defined as the convex hull of 1 vertex and 3 lines - .. SEEALSO:: - + .. SEEALSO:: + :meth:`~sage.schemes.toric.variety.Kaehler_cone` - + REFERENCES: - + For more information, see Section 5.4 of [DLRS2010]_ and Section 2.2 of [ACEP2020]. """ From 931ed170b8593beabba533391cabd5114557e8d4 Mon Sep 17 00:00:00 2001 From: Jean-Philippe/Thinkbook Date: Wed, 12 Feb 2025 04:46:47 -0500 Subject: [PATCH 288/369] More linting --- src/sage/geometry/fan.py | 2 +- src/sage/geometry/polyhedron/base5.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sage/geometry/fan.py b/src/sage/geometry/fan.py index 66451a679b0..c9f83337aae 100644 --- a/src/sage/geometry/fan.py +++ b/src/sage/geometry/fan.py @@ -2477,7 +2477,7 @@ def is_polytopal(self): :meth:`is_projective`. """ if not self.is_complete(): - raise ValueError('To be polytopal, the fan should be complete.') + raise ValueError('to be polytopal, the fan should be complete') from sage.geometry.triangulation.point_configuration import PointConfiguration from sage.geometry.polyhedron.constructor import Polyhedron pc = PointConfiguration(self.rays()) diff --git a/src/sage/geometry/polyhedron/base5.py b/src/sage/geometry/polyhedron/base5.py index 405f8cb8f24..0367bdebaad 100644 --- a/src/sage/geometry/polyhedron/base5.py +++ b/src/sage/geometry/polyhedron/base5.py @@ -694,7 +694,7 @@ def deformation_cone(self): sage: py = Polyhedron([(0, -1, -1), (0, -1, 1), (0, 1, -1), (0, 1, 1), (1, 0, 0)]) sage: dc_py = py.deformation_cone(); dc_py A 4-dimensional polyhedron in QQ^5 defined as the convex hull of 1 vertex, 1 ray, 3 lines - sage: [_.b() for _ in py.Hrepresentation()] + sage: [ineq.b() for ineq in py.Hrepresentation()] [0, 1, 1, 1, 1] sage: r = dc_py.rays()[0] sage: l1,l2,l3 = dc_py.lines() @@ -711,7 +711,7 @@ def deformation_cone(self): 2.2 of [ACEP2020]. """ from .constructor import Polyhedron - m = matrix([_.A() for _ in self.Hrepresentation()]) + m = matrix([ineq.A() for ineq in self.Hrepresentation()]) m = m.transpose() m_ker = m.right_kernel_matrix(basis='computed') gale = tuple(m_ker.columns()) @@ -722,7 +722,7 @@ def deformation_cone(self): dual_cone = Polyhedron(rays=[gale[i] for i in range(n) if i not in cone_indices]) c = c.intersection(dual_cone) if c is not None else dual_cone - preimages = [A_ker.solve_right(r.vector()) for r in c.rays()] + preimages = [m_ker.solve_right(r.vector()) for r in c.rays()] return Polyhedron(lines=m.rows(), rays=preimages) ########################################################### From bc9692dbc5c4876a5c196fe6621f74168d465c02 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Wed, 12 Feb 2025 10:54:18 +0100 Subject: [PATCH 289/369] is_prime_field is provided by the category Rings now --- src/sage/combinat/sf/sfa.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/sage/combinat/sf/sfa.py b/src/sage/combinat/sf/sfa.py index f746ff03b0a..92a913a71ec 100644 --- a/src/sage/combinat/sf/sfa.py +++ b/src/sage/combinat/sf/sfa.py @@ -1877,9 +1877,6 @@ 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 34d865fe04461a40abc0f033cb9d3e355a1fb7dc Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Wed, 12 Feb 2025 10:55:17 +0100 Subject: [PATCH 290/369] reviewer's suggestions about docstring formatting and one list comprehension --- src/sage/data_structures/stream.py | 5 ++-- src/sage/rings/lazy_series_ring.py | 25 ++++++++----------- .../polynomial/multi_polynomial_sequence.py | 18 +++++++++++-- 3 files changed, 30 insertions(+), 18 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index d777d22d12a..387f1e31fc2 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -1402,6 +1402,7 @@ class CoefficientRing(UniqueRepresentation, FractionField_generic): """ def __init__(self, base_ring): """ + Initialize ``self``. EXAMPLES:: @@ -1919,8 +1920,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:: diff --git a/src/sage/rings/lazy_series_ring.py b/src/sage/rings/lazy_series_ring.py index 5f8635d9746..6919335ca75 100644 --- a/src/sage/rings/lazy_series_ring.py +++ b/src/sage/rings/lazy_series_ring.py @@ -667,14 +667,12 @@ def _terms_of_degree(self, n, 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``. + For example, if ``self`` is a univariate Laurent, power, or + Dirichlet series, this is the list containing the one of the + base ring. If ``self`` is a multivariate power series, this + is the list of monomials of total degree ``n``. If ``self`` + is a lazy symmetric function, this is the list of basis + elements of total degree ``n``. EXAMPLES:: @@ -3292,12 +3290,11 @@ def _terms_of_degree(self, n, R): B = B.change_ring(R) if self._arity == 1: return list(B.homogeneous_component_basis(n)) - l = [] - for c in IntegerVectors(n, self._arity): - for m in cartesian_product_iterator([F.homogeneous_component_basis(p) - for F, p in zip(B.tensor_factors(), c)]): - l.append(tensor(m)) - return l + + return [tensor(m) + 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)])] def _element_constructor_(self, x=None, valuation=None, degree=None, constant=None, check=True): r""" diff --git a/src/sage/rings/polynomial/multi_polynomial_sequence.py b/src/sage/rings/polynomial/multi_polynomial_sequence.py index 0493b2f2e07..291d3802698 100644 --- a/src/sage/rings/polynomial/multi_polynomial_sequence.py +++ b/src/sage/rings/polynomial/multi_polynomial_sequence.py @@ -273,7 +273,7 @@ def PolynomialSequence(arg1, arg2=None, immutable=False, cr=False, cr_str=None): TESTS: - A PolynomialSequence can exist with elements in an infinite field of + A ``PolynomialSequence`` can exist with elements in an infinite field of characteristic 2 (see :issue:`19452`):: sage: from sage.rings.polynomial.multi_polynomial_sequence import PolynomialSequence @@ -283,7 +283,7 @@ def PolynomialSequence(arg1, arg2=None, immutable=False, cr=False, cr_str=None): sage: PolynomialSequence([0], R) [0] - A PolynomialSequence can be created from an iterator (see :issue:`25989`):: + A ``PolynomialSequence`` can be created from an iterator (see :issue:`25989`):: sage: R. = QQ[] sage: PolynomialSequence(iter(R.gens())) @@ -292,6 +292,20 @@ def PolynomialSequence(arg1, arg2=None, immutable=False, cr=False, cr_str=None): [x, y, z] sage: PolynomialSequence(iter([(x,y), (z,)]), R) [x, y, z] + + A ``PolynomialSequence`` can be created from elements of an + ``InfinitePolynomialRing``:: + + sage: R. = InfinitePolynomialRing(QQ) + sage: s = PolynomialSequence([a[i]-a[i+1] for i in range(3)]) + sage: s + [-a_1 + a_0, -a_2 + a_1, -a_3 + a_2] + sage: s.coefficients_monomials() + ( + [ 0 0 -1 1] + [ 0 -1 1 0] + [-1 1 0 0], (a_3, a_2, a_1, a_0) + ) """ from sage.structure.element import Matrix try: From 3edbf8bb6ab1dcc061f58850a7e9cc6aababd423 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Wed, 12 Feb 2025 13:33:27 +0100 Subject: [PATCH 291/369] remove duplicate methods --- src/sage/categories/commutative_rings.py | 43 ++++-------------------- src/sage/categories/rings.py | 17 ++-------- 2 files changed, 9 insertions(+), 51 deletions(-) diff --git a/src/sage/categories/commutative_rings.py b/src/sage/categories/commutative_rings.py index 1730bd6fa82..961ca17d629 100644 --- a/src/sage/categories/commutative_rings.py +++ b/src/sage/categories/commutative_rings.py @@ -140,12 +140,13 @@ def is_commutative(self) -> bool: def _ideal_class_(self, n=0): r""" - Return a callable object that can be used to create ideals in this - commutative ring. + Return a callable object that can be used to create ideals + in this commutative ring. - This class can depend on `n`, the number of generators of the ideal. - The default input of `n=0` indicates an unspecified number of generators, - in which case a class that works for any number of generators is returned. + This class can depend on `n`, the number of generators of + the ideal. The default input of `n=0` indicates an + unspecified number of generators, in which case a class + that works for any number of generators is returned. EXAMPLES:: @@ -340,38 +341,6 @@ def over(self, base=None, gen=None, gens=None, name=None, names=None): gens = (gen,) return RingExtension(self, base, gens, names) - def _ideal_class_(self, n=0): - r""" - Return a callable object that can be used to create ideals in this - ring. - - This class can depend on `n`, the number of generators of the ideal. - The default input of `n=0` indicates an unspecified number of generators, - in which case a class that works for any number of generators is returned. - - EXAMPLES:: - - sage: ZZ._ideal_class_() - - sage: RR._ideal_class_() - - sage: R. = GF(5)[] - sage: R._ideal_class_(1) - - sage: S = R.quo(x^3 - y^2) - sage: S._ideal_class_(1) - - sage: S._ideal_class_(2) - - sage: T. = S[] # needs sage.libs.singular - sage: T._ideal_class_(5) # needs sage.libs.singular - - sage: T._ideal_class_(1) # needs sage.libs.singular - - """ - from sage.rings.ideal import Ideal_generic, Ideal_principal - return Ideal_principal if n == 1 else Ideal_generic - def frobenius_endomorphism(self, n=1): """ Return the Frobenius endomorphism. diff --git a/src/sage/categories/rings.py b/src/sage/categories/rings.py index b1e229d1af2..0de9d22e407 100644 --- a/src/sage/categories/rings.py +++ b/src/sage/categories/rings.py @@ -5,7 +5,8 @@ # **************************************************************************** # Copyright (C) 2005 David Kohel # William Stein -# 2008 Teresa Gomez-Diaz (CNRS) +# 2008 Teresa Gomez-Diaz (CNRS) +# # 2008-2011 Nicolas M. Thiery # # Distributed under the terms of the GNU General Public License (GPL) @@ -761,7 +762,7 @@ def unit_ideal(self): sage: Zp(7).unit_ideal() # needs sage.rings.padics Principal ideal (1 + O(7^20)) of 7-adic Ring with capped relative precision 20 """ - return self.principal_ideal(self.one(), coerce=False) + return self._ideal_class_(1)(self, [self.one()]) def _ideal_class_(self, n=0): r""" @@ -815,18 +816,6 @@ def zero_ideal(self): """ return self._ideal_class_(1)(self, [self.zero()]) - @cached_method - def unit_ideal(self): - """ - Return the unit ideal of this ring. - - EXAMPLES:: - - sage: Zp(7).unit_ideal() # needs sage.rings.padics - Principal ideal (1 + O(7^20)) of 7-adic Ring with capped relative precision 20 - """ - return self._ideal_class_(1)(self, [self.one()]) - def principal_ideal(self, gen, coerce=True): """ Return the principal ideal generated by gen. From c84ed114be1a614a117e683eddb2daffcc90bf38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Wed, 12 Feb 2025 13:39:54 +0100 Subject: [PATCH 292/369] suggested detail --- src/sage/ext_data/magma/sage/basic.m | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sage/ext_data/magma/sage/basic.m b/src/sage/ext_data/magma/sage/basic.m index 8689711341c..918e523e403 100644 --- a/src/sage/ext_data/magma/sage/basic.m +++ b/src/sage/ext_data/magma/sage/basic.m @@ -153,9 +153,9 @@ intrinsic Sage(X::RngPad) -> MonStgElt, BoolElt {p-adic rings, either free precision model or exact model} prec := Precision(X); if Type(prec) eq Infty then - return Sprintf("Zp(%o, %o, 'relaxed')", Sage(Prime(X)), Sage(Precision(X))), false; + return Sprintf("Zp(%o, %o, 'relaxed')", Sage(Prime(X)), Sage(prec), false; else - return Sprintf("Zp(%o, %o, 'capped-rel')", Sage(Prime(X)), Sage(Precision(X))), false; + return Sprintf("Zp(%o, %o, 'capped-rel')", Sage(Prime(X)), Sage(prec)), false; end if; end intrinsic; @@ -163,9 +163,9 @@ intrinsic Sage(X::FldPad) -> MonStgElt, BoolElt {p-adic fields, either free precision model or exact model} prec := Precision(X); if Type(prec) eq Infty then - return Sprintf("Qp(%o, %o, 'relaxed')", Sage(Prime(X)), Sage(Precision(X))), false; + return Sprintf("Qp(%o, %o, 'relaxed')", Sage(Prime(X)), Sage(prec)), false; else - return Sprintf("Qp(%o, %o, 'capped-rel')", Sage(Prime(X)), Sage(Precision(X))), false; + return Sprintf("Qp(%o, %o, 'capped-rel')", Sage(Prime(X)), Sage(prec)), false; end if; end intrinsic; From a21e9f4e86b41e724b5462d5c00ee7e4d865b487 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Wed, 12 Feb 2025 13:41:46 +0100 Subject: [PATCH 293/369] remove import --- src/sage/rings/finite_rings/integer_mod_ring.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/finite_rings/integer_mod_ring.py b/src/sage/rings/finite_rings/integer_mod_ring.py index bd04ae7fae6..590f40ffb5a 100644 --- a/src/sage/rings/finite_rings/integer_mod_ring.py +++ b/src/sage/rings/finite_rings/integer_mod_ring.py @@ -67,7 +67,7 @@ from sage.arith.misc import factor from sage.arith.misc import primitive_root from sage.arith.misc import CRT_basis -from sage.rings.ring import Field, CommutativeRing +from sage.rings.ring import Field from sage.misc.mrange import cartesian_product_iterator import sage.rings.abc from sage.rings.finite_rings import integer_mod From bb64b5b8e8a553060b75e400e0a80cd6348da943 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Wed, 12 Feb 2025 14:09:47 +0100 Subject: [PATCH 294/369] add TODO for possible future performance improvement --- src/sage/data_structures/stream.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index 387f1e31fc2..8f93782b850 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -1951,6 +1951,11 @@ def retract(c): def fix_cache(j, s, ao): if s._cache[ao]: + # TODO: perhaps, if not + # self._coefficient_ring.has_coerce_map_from(s._cache[ao].parent()) + # we can be certain that there is still an + # undetermined coefficient -- if so, we could replace + # the following line for a performance improvement if s._cache[ao] in self._coefficient_ring: s._true_order = True return False From 2215f72c6cb8d2aa59c3f342541de38abec29cd0 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Wed, 12 Feb 2025 14:17:24 +0100 Subject: [PATCH 295/369] special case repr of generators for better performance --- src/sage/rings/polynomial/multi_polynomial_element.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/sage/rings/polynomial/multi_polynomial_element.py b/src/sage/rings/polynomial/multi_polynomial_element.py index 3b18664f505..09a3a025ca5 100644 --- a/src/sage/rings/polynomial/multi_polynomial_element.py +++ b/src/sage/rings/polynomial/multi_polynomial_element.py @@ -467,6 +467,8 @@ def _repr_(self): sage: repr(-I*y - x^2) # indirect doctest '-x^2 + (-I)*y' """ + if self.is_gen(): + return self.parent().variable_names()[self.degrees().nonzero_positions()[0]] try: key = self.parent().term_order().sortkey except AttributeError: From a69cabd2e51dc735269a5109b488d1e48cebfb60 Mon Sep 17 00:00:00 2001 From: Jean-Philippe/Thinkbook Date: Wed, 12 Feb 2025 09:16:30 -0500 Subject: [PATCH 296/369] Fix the tests --- .../triangulation/point_configuration.py | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/sage/geometry/triangulation/point_configuration.py b/src/sage/geometry/triangulation/point_configuration.py index e76e9bcc3c9..312da9db9a0 100644 --- a/src/sage/geometry/triangulation/point_configuration.py +++ b/src/sage/geometry/triangulation/point_configuration.py @@ -2073,20 +2073,20 @@ def Gale_transform(self, points=None, homogenize=True): sage: pc2 = PointConfiguration([[0,0],[3,0],[0,3],[3,3],[1,1]]) sage: pc2.Gale_transform(homogenize=False) - [-1 0 0 0 0] - [ 0 1 1 -1 0] + [ 1 0 0 0 0] [ 0 1 1 0 -3] + [ 0 0 0 1 -3] sage: pc2.Gale_transform(homogenize=True) [ 1 1 1 0 -3] [ 0 2 2 -1 -3] - It might not affect the dimension of the result:: + It might not affect the result (when acyclic):: sage: PC = PointConfiguration([[4,0,0],[0,4,0],[0,0,4],[2,1,1],[1,2,1],[1,1,2]]) sage: GT = PC.Gale_transform(homogenize=False);GT - [ 2 1 1 -4 0 0] - [ 1 2 1 0 -4 0] - [-2 -2 -1 3 3 -1] + [ 1 0 0 -3 1 1] + [ 0 1 0 1 -3 1] + [ 0 0 1 1 1 -3] sage: GT = PC.Gale_transform(homogenize=True);GT [ 1 0 0 -3 1 1] [ 0 1 0 1 -3 1] @@ -2103,10 +2103,10 @@ def Gale_transform(self, points=None, homogenize=True): [ 0 1 0 -1 1 -1 0] [ 0 0 1 -1 0 -1 1] sage: g_inhom = pc3.Gale_transform(homogenize=False);g_inhom - [-1 1 1 1 0 0 0] - [ 0 1 0 0 1 0 0] - [ 1 -1 -1 0 0 1 0] - [ 0 0 1 0 0 0 1] + [1 0 0 0 1 1 1] + [0 1 0 0 1 0 0] + [0 0 1 0 0 0 1] + [0 0 0 1 0 1 0] sage: Polyhedron(rays=g_hom.columns()) A 3-dimensional polyhedron in ZZ^3 defined as the convex hull of 1 vertex and 3 lines sage: Polyhedron(rays=g_inhom.columns()) @@ -2175,7 +2175,7 @@ def deformation_cone(self, collection): sage: epsilon = 0 sage: m = mother(0) - sage: mother.points() + sage: m.points() (P(4, 0, 0), P(0, 4, 0), P(0, 0, 4), P(2, 1, 1), P(1, 2, 1), P(1, 1, 2)) sage: S1 = [(0,1,4),(0,3,4),(1,2,5),(1,4,5),(0,2,3),(2,3,5)] sage: S2 = [(0,1,3),(1,3,4),(1,2,4),(2,4,5),(0,2,5),(0,3,5)] From 3aab2e3fa96ab5ea463f87f9ed6c91e92f570182 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Wed, 12 Feb 2025 17:05:52 +0100 Subject: [PATCH 297/369] adapt repr of TropicalMPolynomial --- src/sage/rings/semirings/tropical_mpolynomial.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/sage/rings/semirings/tropical_mpolynomial.py b/src/sage/rings/semirings/tropical_mpolynomial.py index c1f1a744acb..225435ba488 100644 --- a/src/sage/rings/semirings/tropical_mpolynomial.py +++ b/src/sage/rings/semirings/tropical_mpolynomial.py @@ -662,7 +662,14 @@ def _repr_(self): """ if not self.monomial_coefficients(): return str(self.parent().base().zero()) - s = super()._repr_() + try: + key = self.parent().term_order().sortkey + except AttributeError: + key = None + atomic = self.parent().base_ring()._repr_option('element_is_atomic') + s = self.element().poly_repr(self.parent().variable_names(), + atomic_coefficients=atomic, + sortkey=key) if self.monomials()[-1].is_constant(): if self.monomial_coefficient(self.parent()(0)) < 0: s = s.replace(" - ", " + -") From ef5ca3a67ae798a1f8790012a7072a31f208aa4f Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Wed, 12 Feb 2025 17:08:35 +0100 Subject: [PATCH 298/369] implement im_gens in the class fraction_field_FpT --- src/sage/rings/fraction_field_FpT.pyx | 30 +++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/sage/rings/fraction_field_FpT.pyx b/src/sage/rings/fraction_field_FpT.pyx index 5317c1551fb..bf9d5d6ed37 100644 --- a/src/sage/rings/fraction_field_FpT.pyx +++ b/src/sage/rings/fraction_field_FpT.pyx @@ -572,6 +572,36 @@ cdef class FpTElement(FieldElement): normalize(x._numer, x._denom, self.p) return x + def _im_gens_(self, codomain, im_gens, base_map=None): + r""" + Return the image of this element in ``codomain`` under the + map that sends the images of the generators of the parent + to the tuple of elements of ``im_gens``. + + INPUT: + + - ``codomain`` -- a ring; where the image is computed + + - ``im_gens`` -- a list; the images of the generators + of the parent + + - ``base_map`` -- a morphism (default: ``None``); + the action on the underlying base ring + + EXAMPLES:: + + sage: Fq = GF(5) + sage: A. = GF(5)[] + sage: K. = Frac(A) + sage: f = K.hom([T^2]) + sage: f(1/T) + 1/T^2 + """ + nden = self.denom()._im_gens_(codomain, im_gens, base_map=base_map) + invden = nden.inverse_of_unit() + nnum = self.numer()._im_gens_(codomain, im_gens, base_map=base_map) + return nnum * invden + cpdef FpTElement next(self): """ Iterate through all polynomials, returning the "next" polynomial after this one. From aad8498d37668b397c1b880b7f90f6d7e577bf4d Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Wed, 12 Feb 2025 18:56:39 +0100 Subject: [PATCH 299/369] mark a doctest as random --- 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 6919335ca75..d601b73d358 100644 --- a/src/sage/rings/lazy_series_ring.py +++ b/src/sage/rings/lazy_series_ring.py @@ -801,7 +801,7 @@ def define_implicitly(self, series, equations, max_lookahead=1): 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 + sage: F # random From c8b88af9d0c1495c325c98a071d89edac81a6748 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Wed, 12 Feb 2025 19:04:03 +0100 Subject: [PATCH 300/369] fix the other doctest --- src/sage/categories/rings.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/sage/categories/rings.py b/src/sage/categories/rings.py index 46fc677c840..78919cda38b 100644 --- a/src/sage/categories/rings.py +++ b/src/sage/categories/rings.py @@ -1647,16 +1647,17 @@ def _random_nonzero_element(self, *args, **kwds): if not x.is_zero(): return x - def random_element(self, bound=2): + def random_element(self, *args): """ Return a random integer coerced into this ring. - The integer is chosen uniformly - from the interval ``[-bound,bound]``. - INPUT: - - ``bound`` -- integer (default: 2) + - either no integer, one integer or two integers + + The integer is chosen uniformly from the closed interval + ``[-2,2]``, ``[-a,a]`` or ``[a,b]`` according to the + length of the input. ALGORITHM: @@ -1668,8 +1669,17 @@ def random_element(self, bound=2): 1 sage: QQ.random_element(8) # random 2 + sage: ZZ.random_element(4,12) # random + 7 """ - return randint(-bound, bound) * self.one() + if not args: + a, b = -2, 2 + elif len(args) == 1: + bound = args[0] + a, b = -bound, bound + else: + a, b = args[0], args[1] + return randint(a, b) * self.one() class ElementMethods: def is_unit(self) -> bool: From 7cdd4978a1e6dd0d28c69423912807e3b44d78a6 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Wed, 12 Feb 2025 19:35:34 +0100 Subject: [PATCH 301/369] remove more duplicate code --- src/sage/categories/rings.py | 67 ------------------------------------ src/sage/categories/rngs.py | 8 +++-- 2 files changed, 6 insertions(+), 69 deletions(-) diff --git a/src/sage/categories/rings.py b/src/sage/categories/rings.py index 0de9d22e407..7f55192fad5 100644 --- a/src/sage/categories/rings.py +++ b/src/sage/categories/rings.py @@ -764,73 +764,6 @@ def unit_ideal(self): """ return self._ideal_class_(1)(self, [self.one()]) - def _ideal_class_(self, n=0): - r""" - Return a callable object that can be used to create ideals in this - ring. - - EXAMPLES:: - - sage: MS = MatrixSpace(QQ, 2, 2) # needs sage.modules - sage: MS._ideal_class_() # needs sage.modules - - - Since :issue:`7797`, non-commutative rings have ideals as well:: - - sage: A = SteenrodAlgebra(2) # needs sage.combinat sage.modules - sage: A._ideal_class_() # needs sage.combinat sage.modules - - """ - from sage.rings.noncommutative_ideals import Ideal_nc - return Ideal_nc - - @cached_method - def zero_ideal(self): - """ - Return the zero ideal of this ring (cached). - - EXAMPLES:: - - sage: ZZ.zero_ideal() - Principal ideal (0) of Integer Ring - sage: QQ.zero_ideal() - Principal ideal (0) of Rational Field - sage: QQ['x'].zero_ideal() - Principal ideal (0) of Univariate Polynomial Ring in x over Rational Field - - The result is cached:: - - sage: ZZ.zero_ideal() is ZZ.zero_ideal() - True - - TESTS: - - Make sure that :issue:`13644` is fixed:: - - sage: # needs sage.rings.padics - sage: K = Qp(3) - sage: R. = K[] - sage: L. = K.extension(a^2-3) - sage: L.ideal(a) - Principal ideal (1 + O(a^40)) of 3-adic Eisenstein Extension Field in a defined by a^2 - 3 - """ - return self._ideal_class_(1)(self, [self.zero()]) - - def principal_ideal(self, gen, coerce=True): - """ - Return the principal ideal generated by gen. - - EXAMPLES:: - - sage: R. = ZZ[] - sage: R.principal_ideal(x+2*y) - Ideal (x + 2*y) of Multivariate Polynomial Ring in x, y over Integer Ring - """ - C = self._ideal_class_(1) - if coerce: - gen = self(gen) - return C(self, [gen]) - def characteristic(self): """ Return the characteristic of this ring. diff --git a/src/sage/categories/rngs.py b/src/sage/categories/rngs.py index e463bf56e2d..5c13497fee1 100644 --- a/src/sage/categories/rngs.py +++ b/src/sage/categories/rngs.py @@ -97,7 +97,11 @@ def _ideal_class_(self, n=0): The argument `n`, standing for the number of generators of the ideal, is ignored. - EXAMPLES: + EXAMPLES:: + + sage: MS = MatrixSpace(QQ, 2, 2) # needs sage.modules + sage: MS._ideal_class_() # needs sage.modules + Since :issue:`7797`, non-commutative rings have ideals as well:: @@ -153,4 +157,4 @@ def zero_ideal(self): sage: L.ideal(a) Principal ideal (1 + O(a^40)) of 3-adic Eisenstein Extension Field in a defined by a^2 - 3 """ - return self.principal_ideal(self.zero(), coerce=False) + return self._ideal_class_(1)(self, [self.zero()]) From 152c47f98115349bcf1b1f205110c4f51102178b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Wed, 12 Feb 2025 21:10:14 +0100 Subject: [PATCH 302/369] fix --- src/sage/categories/sets_cat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/categories/sets_cat.py b/src/sage/categories/sets_cat.py index 951bc7f2292..6fadc7f9ccf 100644 --- a/src/sage/categories/sets_cat.py +++ b/src/sage/categories/sets_cat.py @@ -2472,7 +2472,7 @@ def random_element(self, *args): sage: c2 = C.random_element(4,7) sage: c2 # random (6, 5, 6, 4, 5, 6, 6, 4, 5, 5) - sage: all(4 <= i < 7 for i in c2) + sage: all(4 <= i <= 7 for i in c2) True """ return self._cartesian_product_of_elements( From 52655a9a1b0512992c268ed0e6751edec9faf095 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Wed, 12 Feb 2025 22:21:36 +0100 Subject: [PATCH 303/369] address Antoine's comments --- .../drinfeld_modules/charzero_drinfeld_module.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py index 32225468aa8..41323879414 100644 --- a/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py @@ -533,6 +533,9 @@ def class_polynomial(self): Return the class polynomial, that is the Fitting ideal of the class module, of this Drinfeld module. + We refer to [Tae2012]_ for the definition and basic + properties of the class module. + EXAMPLES: We check that the class module of the Carlitz module @@ -558,7 +561,7 @@ def class_polynomial(self): Here is an example with a nontrivial class module:: - sage: phi = DrinfeldModule(A, [T, -T^(2*q-1) + 2*T^(q-1)]) + sage: phi = DrinfeldModule(A, [T, 2*T^14 + 2*T^4]) sage: phi.class_polynomial() T + 3 @@ -629,7 +632,7 @@ def class_polynomial(self): for i in range(dim, s): while ip < dim and j == pivots[ip]: j += 1 - ip += 1 + ip += 1 V[i,j] = 1 N = (V * M * ~V).submatrix(dim, dim) From 67496edeaef29e9f0252a3f9540a2c44c46e5d15 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Wed, 12 Feb 2025 22:24:08 +0100 Subject: [PATCH 304/369] remove "Taelman's unit" from the documentation --- .../function_field/drinfeld_modules/charzero_drinfeld_module.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py index 41323879414..ff7c2b60ab7 100644 --- a/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py @@ -11,7 +11,7 @@ AUTHORS: - David Ayotte (2023-09) -- Xavier Caruso (2024-12) - computation of class polynomials and Taelman's units +- Xavier Caruso (2024-12) - computation of class polynomials """ # ***************************************************************************** From 2876619203b19e5fed0d15359b484827cf2c57c4 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Wed, 12 Feb 2025 22:45:28 +0100 Subject: [PATCH 305/369] singular --- src/sage/rings/fraction_field_FpT.pyx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/sage/rings/fraction_field_FpT.pyx b/src/sage/rings/fraction_field_FpT.pyx index bf9d5d6ed37..cf2b12861da 100644 --- a/src/sage/rings/fraction_field_FpT.pyx +++ b/src/sage/rings/fraction_field_FpT.pyx @@ -575,22 +575,21 @@ cdef class FpTElement(FieldElement): def _im_gens_(self, codomain, im_gens, base_map=None): r""" Return the image of this element in ``codomain`` under the - map that sends the images of the generators of the parent - to the tuple of elements of ``im_gens``. + map that sends the image of the generator of the parent to + the element in ``im_gens``. INPUT: - ``codomain`` -- a ring; where the image is computed - - ``im_gens`` -- a list; the images of the generators - of the parent + - ``im_gens`` -- a list containing the image of the + generator of the parent as unique element - ``base_map`` -- a morphism (default: ``None``); the action on the underlying base ring EXAMPLES:: - sage: Fq = GF(5) sage: A. = GF(5)[] sage: K. = Frac(A) sage: f = K.hom([T^2]) From ce094d1820c31bcc6ecc361c5f0736579239373e Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Wed, 12 Feb 2025 23:14:05 +0100 Subject: [PATCH 306/369] add details in documentation --- src/sage/modules/free_module.py | 2 ++ src/sage/modules/free_module_pseudohomspace.py | 18 ++++++++++++------ src/sage/modules/free_module_pseudomorphism.py | 9 +++++++++ 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/src/sage/modules/free_module.py b/src/sage/modules/free_module.py index 71efa38e777..7d6004ff7d7 100644 --- a/src/sage/modules/free_module.py +++ b/src/sage/modules/free_module.py @@ -3163,6 +3163,8 @@ def pseudohom(self, f, twist, codomain=None, side="left"): pseudomorphism - ``twist`` -- the twisting morphism or the twisting derivation + (if a derivation is given, the corresponding morphism `\theta` + is automatically infered) - ``codomain`` -- (default: ``None``) the codomain of the pseudo morphisms; if ``None``, the codomain is the same than the domain diff --git a/src/sage/modules/free_module_pseudohomspace.py b/src/sage/modules/free_module_pseudohomspace.py index 292d16f086d..b55abedb5fe 100644 --- a/src/sage/modules/free_module_pseudohomspace.py +++ b/src/sage/modules/free_module_pseudohomspace.py @@ -37,6 +37,10 @@ class FreeModulePseudoHomspace(UniqueRepresentation, HomsetWithBase): For free modules, the elements of a pseudomorphism correspond to matrices which define the mapping on elements of a basis. + This class is not supposed to be instantiated directly; the user should + use instead the method :meth:`sage.rings.module.free_module.FreeModule_generic.pseudoHom` + to create a space of pseudomorphisms. + TESTS:: sage: F = GF(125) @@ -57,11 +61,12 @@ def __classcall_private__(cls, domain, codomain, twist): INPUT: - - ``domain`` -- a free module, the domain of this pseudomorphism + - ``domain`` -- a free module, the domain of this pseudomorphism - - ``codomain`` -- a free module, the codomain of this pseudomorphism + - ``codomain`` -- a free module, the codomain of this pseudomorphism - - ``twist`` -- a twisting morphism/derivation or a Ore polynomial ring + - ``twist`` -- a twisting morphism/derivation or the corresponding + Ore polynomial ring TESTS:: @@ -94,11 +99,12 @@ def __init__(self, domain, codomain, ore): INPUT: - - ``domain`` -- a free module, the domain of this pseudomorphism + - ``domain`` -- a free module, the domain of this pseudomorphism - - ``codomain`` -- a free module, the codomain of this pseudomorphism + - ``codomain`` -- a free module, the codomain of this pseudomorphism - - ``ore`` -- the underlying Ore polynomial ring + - ``ore`` -- the underlying Ore polynomial ring (built from the + twisting morphism and derivation) TESTS:: diff --git a/src/sage/modules/free_module_pseudomorphism.py b/src/sage/modules/free_module_pseudomorphism.py index 965152a893e..39eadae6764 100644 --- a/src/sage/modules/free_module_pseudomorphism.py +++ b/src/sage/modules/free_module_pseudomorphism.py @@ -45,6 +45,15 @@ class FreeModulePseudoMorphism(Morphism): The map `\theta` (resp. `\delta`) is referred to as the twisting endomorphism (resp. the twisting derivation) of `f`. + .. NOTE:: + + The implementation currently requires that `M` and `M'` + are free modules. + + This class is not supposed to be instantiated directly; the user should + use instead the method :meth:`sage.rings.module.free_module.FreeModule_generic.pseudohom` + to create a pseudomorphism. + TESTS:: sage: P. = ZZ[] From a3f602ed7d989d796da31e10d86d5744f82762a5 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Thu, 13 Feb 2025 07:55:18 +0100 Subject: [PATCH 307/369] Update src/sage/modules/free_module_pseudohomspace.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Antoine Leudière --- src/sage/modules/free_module_pseudohomspace.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/modules/free_module_pseudohomspace.py b/src/sage/modules/free_module_pseudohomspace.py index b55abedb5fe..b3831d4c5e4 100644 --- a/src/sage/modules/free_module_pseudohomspace.py +++ b/src/sage/modules/free_module_pseudohomspace.py @@ -227,7 +227,7 @@ def ore_ring(self, var='x'): def matrix_space(self): r""" Return the matrix space used for representing the - pseudomorphism in this space. + pseudomorphisms in this space. EXAMPLES:: From 90b7ca8269b53111007dad4d311c9c5985ce11d0 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Thu, 13 Feb 2025 07:58:09 +0100 Subject: [PATCH 308/369] Update src/sage/modules/free_module_pseudomorphism.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Antoine Leudière --- .../modules/free_module_pseudomorphism.py | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/src/sage/modules/free_module_pseudomorphism.py b/src/sage/modules/free_module_pseudomorphism.py index 39eadae6764..8628409b65d 100644 --- a/src/sage/modules/free_module_pseudomorphism.py +++ b/src/sage/modules/free_module_pseudomorphism.py @@ -49,6 +49,48 @@ class FreeModulePseudoMorphism(Morphism): The implementation currently requires that `M` and `M'` are free modules. + .. WARNING:: + + At the moment, it is not possible to specify both a twisting + endomorphism and a twisting derivation. Only one of those can be + used, preferably using the `twist` argument in the method + :meth:`sage.rings.module.free_module.FreeModule_generic.pseudohom`. + + We represent pseudo morphisms by matrices with coefficient in the + base ring `R`. The matrix `\mathcal M_f` representing a pseudo + morphism is such that its lines (resp. columns if ``side`` is + ``"right"``) are the coordinates of the images of the distinguished + basis of the domain (see also method :meth:`matrix`). More + concretely, let `n` (resp. `n'`) be the dimension of `M` (resp. + `M'`), let `(e_1, \dots, e_n)` be a basis of `M`. For any `x = + \sum_{i=1}^n x_i e_i \in M`, we have + + .. MATH:: + + f(x) = \begin{pmatrix} + \theta(x_1) & \cdots & \theta(x_n) + \end{pmatrix} + \mathcal M_f + + + \begin{pmatrix} + \delta(x_1) & \cdots & \theta(x_n) + \end{pmatrix} + . + + If ``side`` is ``"right"``, we have: + + .. MATH:: + + f(x) = \mathcal M_f + \begin{pmatrix} + \theta(x_1) \\ \vdots \\ \theta(x_n) + \end{pmatrix} + + + + \begin{pmatrix} + \delta(x_1) \\ \vdots \\ \theta(x_n) + \end{pmatrix} + . This class is not supposed to be instantiated directly; the user should use instead the method :meth:`sage.rings.module.free_module.FreeModule_generic.pseudohom` From 048009bcffc839d7a26360c4c113aa14f12cfc11 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Thu, 13 Feb 2025 07:58:29 +0100 Subject: [PATCH 309/369] Update src/sage/modules/free_module.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Antoine Leudière --- src/sage/modules/free_module.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/sage/modules/free_module.py b/src/sage/modules/free_module.py index 7d6004ff7d7..a93f42b8213 100644 --- a/src/sage/modules/free_module.py +++ b/src/sage/modules/free_module.py @@ -3156,6 +3156,16 @@ def pseudohom(self, f, twist, codomain=None, side="left"): f(\lambda x) = \theta(\lambda) f(x) + \delta(\lambda) x When `\delta` is nonzero, this requires that `M` coerces into `M'`. + .. WARNING:: + + At the moment, it is not possible to specify both a twisting + endomorphism and a twisting derivation. Only one of those can be + used, preferably using the `twist` argument. + + We represent pseudo morphisms by matrices with coefficient in the base + ring `R`. See class + :class:`sage.modules.free_module_pseudomorphism.FreeModulePseudoMorphism` + for details. INPUT: From 941e26f157d0f06b0e3cffc4c2b20c3f963be52d Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Thu, 13 Feb 2025 07:58:43 +0100 Subject: [PATCH 310/369] Update src/sage/modules/free_module_pseudohomspace.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Antoine Leudière --- src/sage/modules/free_module_pseudohomspace.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/sage/modules/free_module_pseudohomspace.py b/src/sage/modules/free_module_pseudohomspace.py index b3831d4c5e4..1b7ea94dcfb 100644 --- a/src/sage/modules/free_module_pseudohomspace.py +++ b/src/sage/modules/free_module_pseudohomspace.py @@ -245,6 +245,9 @@ def basis(self, side="left"): r""" Return a basis for the underlying matrix space. + The result does not depend on the `side` of the homspace, i.e. + if matrices are acted upon on the left or on the right. + EXAMPLES:: sage: Fq = GF(7^3) From a342fbe2c6e61245abb3e1364b1ac7b8b8669d81 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Thu, 13 Feb 2025 08:09:56 +0100 Subject: [PATCH 311/369] improve documentation --- src/sage/modules/free_module.py | 13 +++----- .../modules/free_module_pseudohomspace.py | 5 ++- .../modules/free_module_pseudomorphism.py | 32 ++++++++----------- 3 files changed, 22 insertions(+), 28 deletions(-) diff --git a/src/sage/modules/free_module.py b/src/sage/modules/free_module.py index a93f42b8213..19b586a3102 100644 --- a/src/sage/modules/free_module.py +++ b/src/sage/modules/free_module.py @@ -3156,16 +3156,13 @@ def pseudohom(self, f, twist, codomain=None, side="left"): f(\lambda x) = \theta(\lambda) f(x) + \delta(\lambda) x When `\delta` is nonzero, this requires that `M` coerces into `M'`. - .. WARNING:: - At the moment, it is not possible to specify both a twisting - endomorphism and a twisting derivation. Only one of those can be - used, preferably using the `twist` argument. + .. NOTE:: - We represent pseudo morphisms by matrices with coefficient in the base - ring `R`. See class - :class:`sage.modules.free_module_pseudomorphism.FreeModulePseudoMorphism` - for details. + Internally, pseudomorphisms are represented by matrices with + coefficient in the base ring `R`. See class + :class:`sage.modules.free_module_pseudomorphism.FreeModulePseudoMorphism` + for details. INPUT: diff --git a/src/sage/modules/free_module_pseudohomspace.py b/src/sage/modules/free_module_pseudohomspace.py index 1b7ea94dcfb..293fda25e40 100644 --- a/src/sage/modules/free_module_pseudohomspace.py +++ b/src/sage/modules/free_module_pseudohomspace.py @@ -202,7 +202,10 @@ def _repr_(self): def ore_ring(self, var='x'): r""" - Return the underlying Ore polynomial ring. + Return the underlying Ore polynomial ring, that is + the Ore polynomial ring over the base field twisted + by the twisting morphism and the twisting derivation + attached to this homspace. INPUT: diff --git a/src/sage/modules/free_module_pseudomorphism.py b/src/sage/modules/free_module_pseudomorphism.py index 8628409b65d..7ed8cf1ef49 100644 --- a/src/sage/modules/free_module_pseudomorphism.py +++ b/src/sage/modules/free_module_pseudomorphism.py @@ -49,21 +49,15 @@ class FreeModulePseudoMorphism(Morphism): The implementation currently requires that `M` and `M'` are free modules. - .. WARNING:: - - At the moment, it is not possible to specify both a twisting - endomorphism and a twisting derivation. Only one of those can be - used, preferably using the `twist` argument in the method - :meth:`sage.rings.module.free_module.FreeModule_generic.pseudohom`. - - We represent pseudo morphisms by matrices with coefficient in the - base ring `R`. The matrix `\mathcal M_f` representing a pseudo - morphism is such that its lines (resp. columns if ``side`` is - ``"right"``) are the coordinates of the images of the distinguished - basis of the domain (see also method :meth:`matrix`). More - concretely, let `n` (resp. `n'`) be the dimension of `M` (resp. - `M'`), let `(e_1, \dots, e_n)` be a basis of `M`. For any `x = - \sum_{i=1}^n x_i e_i \in M`, we have + + We represent pseudomorphisms by matrices with coefficient in the + base ring `R`. The matrix `\mathcal M_f` representing `f` is such + that its lines (resp. columns if ``side`` is ``"right"``) are the + coordinates of the images of the distinguished basis of the domain + (see also method :meth:`matrix`). + More concretely, let `n` (resp. `n'`) be the dimension of `M` + (resp. `M'`), let `(e_1, \dots, e_n)` be a basis of `M`. + For any `x = \sum_{i=1}^n x_i e_i \in M`, we have .. MATH:: @@ -77,7 +71,7 @@ class FreeModulePseudoMorphism(Morphism): \end{pmatrix} . - If ``side`` is ``"right"``, we have: + When ``side`` is ``"right"``, the formula is .. MATH:: @@ -85,15 +79,15 @@ class FreeModulePseudoMorphism(Morphism): \begin{pmatrix} \theta(x_1) \\ \vdots \\ \theta(x_n) \end{pmatrix} - + \begin{pmatrix} \delta(x_1) \\ \vdots \\ \theta(x_n) \end{pmatrix} . - This class is not supposed to be instantiated directly; the user should - use instead the method :meth:`sage.rings.module.free_module.FreeModule_generic.pseudohom` + This class is not supposed to be instantiated directly; the user + should use instead the method + :meth:`sage.rings.module.free_module.FreeModule_generic.pseudohom` to create a pseudomorphism. TESTS:: From 1db04c23b18cd048ed2a018522a56eddb8eb3308 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Thu, 13 Feb 2025 08:11:26 +0100 Subject: [PATCH 312/369] grammar --- src/sage/modules/free_module.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/modules/free_module.py b/src/sage/modules/free_module.py index 19b586a3102..a077baf30da 100644 --- a/src/sage/modules/free_module.py +++ b/src/sage/modules/free_module.py @@ -3122,7 +3122,7 @@ def pseudoHom(self, twist, codomain=None): - ``twist`` -- the twisting morphism or the twisting derivation - ``codomain`` -- (default: ``None``) the codomain of the pseudo - morphisms; if ``None``, the codomain is the same than the domain + morphisms; if ``None``, the codomain is the same as the domain EXAMPLES:: From 5e1477e5dffc99dcb1c6ece0db10e5eccb48e3b2 Mon Sep 17 00:00:00 2001 From: LudovicSchwob Date: Thu, 13 Feb 2025 10:06:51 +0100 Subject: [PATCH 313/369] New algorithm for cuts of a Poset --- src/sage/combinat/posets/posets.py | 48 ++++++++++++++++++++---------- 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/src/sage/combinat/posets/posets.py b/src/sage/combinat/posets/posets.py index 173cbb872da..df33e9af532 100644 --- a/src/sage/combinat/posets/posets.py +++ b/src/sage/combinat/posets/posets.py @@ -8541,35 +8541,53 @@ def cuts(self): A cut is a subset `A` of ``self`` such that the set of lower bounds of the set of upper bounds of `A` is exactly `A`. - The cuts are computed here using the maximal independent sets in the - auxiliary graph defined as `P \times [0,1]` with an edge - from `(x, 0)` to `(y, 1)` if - and only if `x \not\geq_P y`. See the end of section 4 in [JRJ94]_. + The cuts are computed as the smallest family of subsets of P containing its + principal order filters, the whose set P and which is closed by intersection. EXAMPLES:: sage: P = posets.AntichainPoset(3) sage: Pc = P.cuts() sage: Pc # random - [frozenset({0}), + [frozenset({2}), + frozenset({1}), + frozenset({0}), frozenset(), - frozenset({0, 1, 2}), - frozenset({2}), - frozenset({1})] + frozenset({0, 1, 2})] sage: sorted(list(c) for c in Pc) [[], [0], [0, 1, 2], [1], [2]] + TESTS:: + + sage: P = Poset() + sage: P.cuts() + [frozenset()] + .. SEEALSO:: :meth:`completion_by_cuts` """ - from sage.graphs.graph import Graph - from sage.graphs.independent_sets import IndependentSets - auxg = Graph({(u, 0): [(v, 1) for v in self if not self.ge(u, v)] - for u in self}, format='dict_of_lists') - auxg.add_vertices([(v, 1) for v in self]) - return [frozenset([xa for xa, xb in c if xb == 0]) - for c in IndependentSets(auxg, maximal=True)] + C, C2 = [], [] + for x in P: + C.append(set(P.order_filter([x]))) + for i, c in enumerate(C): + for j in range(i + 1, len(C)): + I = c.intersection(C[j]) + if I not in C + C2: + C2.append(I) + while C2: + D = [] + for x in C: + for y in C2: + I = x.intersection(y) + if all(I not in X for X in [C, C2, D]): + D.append(I) + C.extend(C2) + C2 = D + S = set(P) + if S not in C: + C.append(S) + return [frozenset(x) for x in C] def completion_by_cuts(self): """ From 7ada96b58534168a537fbf817331f3b43a08a4d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Thu, 13 Feb 2025 10:36:45 +0100 Subject: [PATCH 314/369] trying to enhance the correspondance --- src/sage/ext_data/magma/sage/basic.m | 2 +- src/sage/rings/padics/padic_base_leaves.py | 26 +++++++++++----------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/sage/ext_data/magma/sage/basic.m b/src/sage/ext_data/magma/sage/basic.m index 918e523e403..24ddb9bb2d9 100644 --- a/src/sage/ext_data/magma/sage/basic.m +++ b/src/sage/ext_data/magma/sage/basic.m @@ -155,7 +155,7 @@ intrinsic Sage(X::RngPad) -> MonStgElt, BoolElt if Type(prec) eq Infty then return Sprintf("Zp(%o, %o, 'relaxed')", Sage(Prime(X)), Sage(prec), false; else - return Sprintf("Zp(%o, %o, 'capped-rel')", Sage(Prime(X)), Sage(prec)), false; + return Sprintf("Zp(%o, %o, 'capped-abs')", Sage(Prime(X)), Sage(prec)), false; end if; end intrinsic; diff --git a/src/sage/rings/padics/padic_base_leaves.py b/src/sage/rings/padics/padic_base_leaves.py index 878c54b748f..e0a7d4fceb3 100644 --- a/src/sage/rings/padics/padic_base_leaves.py +++ b/src/sage/rings/padics/padic_base_leaves.py @@ -304,19 +304,6 @@ def _convert_map_from_(self, R): from sage.rings.padics.padic_generic import ResidueLiftingMap return ResidueLiftingMap._create_(R, self) - def _magma_init_(self, magma): - """ - Conversion to magma. - - EXAMPLES:: - - sage: # optional - magma - sage: F = Qp(5,7,"capped-rel") - sage: magma(F) - 5-adic field mod 5^7 - """ - return f"pAdicRing({self.prime()},{self.precision_cap()})" - class pAdicRingCappedAbsolute(pAdicRingBaseGeneric, pAdicCappedAbsoluteRingGeneric): r""" @@ -417,6 +404,19 @@ def _convert_map_from_(self, R): from sage.rings.padics.padic_generic import ResidueLiftingMap return ResidueLiftingMap._create_(R, self) + def _magma_init_(self, magma): + """ + Conversion to magma. + + EXAMPLES:: + + sage: # optional - magma + sage: F = Qp(5,7,"capped-abs") + sage: magma(F) + 5-adic field mod 5^7 + """ + return f"pAdicRing({self.prime()},{self.precision_cap()})" + class pAdicRingFloatingPoint(pAdicRingBaseGeneric, pAdicFloatingPointRingGeneric): r""" From adbfc15205117a4173a999597f4893d6b2f10ecb Mon Sep 17 00:00:00 2001 From: LudovicSchwob Date: Thu, 13 Feb 2025 10:56:58 +0100 Subject: [PATCH 315/369] Fixing a mistake --- src/sage/combinat/posets/posets.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/combinat/posets/posets.py b/src/sage/combinat/posets/posets.py index df33e9af532..31ab27c0799 100644 --- a/src/sage/combinat/posets/posets.py +++ b/src/sage/combinat/posets/posets.py @@ -8568,8 +8568,8 @@ def cuts(self): :meth:`completion_by_cuts` """ C, C2 = [], [] - for x in P: - C.append(set(P.order_filter([x]))) + for x in self: + C.append(set(self.order_filter([x]))) for i, c in enumerate(C): for j in range(i + 1, len(C)): I = c.intersection(C[j]) From 784dd6306542fee9e3d89775bdbd09054158d57a Mon Sep 17 00:00:00 2001 From: LudovicSchwob Date: Thu, 13 Feb 2025 11:24:01 +0100 Subject: [PATCH 316/369] Fixing an other mistake --- src/sage/combinat/posets/posets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/combinat/posets/posets.py b/src/sage/combinat/posets/posets.py index 31ab27c0799..5fdfeed0057 100644 --- a/src/sage/combinat/posets/posets.py +++ b/src/sage/combinat/posets/posets.py @@ -8584,7 +8584,7 @@ def cuts(self): D.append(I) C.extend(C2) C2 = D - S = set(P) + S = set(self) if S not in C: C.append(S) return [frozenset(x) for x in C] From ba76a9019eda55891935dff72648a8a42af486b1 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Thu, 13 Feb 2025 17:32:56 +0700 Subject: [PATCH 317/369] Fix some typo --- src/sage/rings/padics/factory.py | 4 ++-- src/sage/rings/polynomial/binary_form_reduce.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sage/rings/padics/factory.py b/src/sage/rings/padics/factory.py index 9dce85e130f..c632b86600a 100644 --- a/src/sage/rings/padics/factory.py +++ b/src/sage/rings/padics/factory.py @@ -1643,7 +1643,7 @@ class Zp_class(UniqueFactory): 1 + 2*5^2 + 5^3 The floating point case is similar to the fixed modulus type - in that elements do not trac their own precision. However, relative + in that elements do not track their own precision. However, relative precision is truncated with each operation rather than absolute precision. On the contrary, the lattice type tracks precision using lattices @@ -2196,7 +2196,7 @@ def Zq(q, prec=None, type='capped-rel', modulus=None, names=None, 2*3^2 + (2*a + 2)*3^3 The floating point case is similar to the fixed modulus type - in that elements do not trac their own precision. However, relative + in that elements do not track their own precision. However, relative precision is truncated with each operation rather than absolute precision. MODULUS: diff --git a/src/sage/rings/polynomial/binary_form_reduce.py b/src/sage/rings/polynomial/binary_form_reduce.py index f56dfe459ac..56de0ec7199 100644 --- a/src/sage/rings/polynomial/binary_form_reduce.py +++ b/src/sage/rings/polynomial/binary_form_reduce.py @@ -192,7 +192,7 @@ def covariant_z0(F, z0_cov=False, prec=53, emb=None, error_limit=0.000001): FM = f # for Julia's invariant else: # solve the minimization problem for 'true' covariant - CF = ComplexIntervalField(prec=prec) # keeps trac of our precision error + CF = ComplexIntervalField(prec=prec) # keeps track of our precision error z = CF(z) FM = F(list(mat * vector(R.gens()))).subs({R.gen(1): 1}).univariate_polynomial() from sage.rings.polynomial.complex_roots import complex_roots From 2393565d2d9c4aa14c84f74d87b50b23fb0d7666 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Thu, 13 Feb 2025 17:36:57 +0700 Subject: [PATCH 318/369] Add a doctest for HomsetsCategory._make_named_class_key --- src/sage/categories/homsets.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/sage/categories/homsets.py b/src/sage/categories/homsets.py index 73dc6be120c..a9b8ad9383e 100644 --- a/src/sage/categories/homsets.py +++ b/src/sage/categories/homsets.py @@ -163,6 +163,11 @@ def _make_named_class_key(self, name): - :meth:`CategoryWithParameters` - :meth:`CategoryWithParameters._make_named_class_key` + + TESTS:: + + sage: ModulesWithBasis(ZZ).Homsets()._make_named_class_key('parent_class') + """ return getattr(self.base_category(), name) From a052a1f0aa30285b91927b3d1019656ecb9932cf Mon Sep 17 00:00:00 2001 From: Camille Garnier Date: Thu, 13 Feb 2025 11:51:30 +0100 Subject: [PATCH 319/369] add of the function rank_support_of_vector --- src/sage/coding/linear_rank_metric.py | 104 +++++++++++++++++++++----- 1 file changed, 86 insertions(+), 18 deletions(-) diff --git a/src/sage/coding/linear_rank_metric.py b/src/sage/coding/linear_rank_metric.py index 97a37e96c9b..e45b1b1b0f7 100644 --- a/src/sage/coding/linear_rank_metric.py +++ b/src/sage/coding/linear_rank_metric.py @@ -91,6 +91,7 @@ AUTHORS: - Marketa Slukova (2019-08-16): initial version +- Camille Garnier and Rubén Muñoz--Bertrand (2024-02-13): added rank_support_of_vector, and corrected the documentation TESTS:: @@ -146,9 +147,9 @@ def to_matrix_representation(v, sub_field=None, basis=None): specified, it is the prime subfield `\GF{p}` of `\GF{q^m}` - ``basis`` -- (default: ``None``) a basis of `\GF{q^m}` as a vector space over - ``sub_field``. If not specified, given that `q = p^s`, let - `1,\beta,\ldots,\beta^{sm}` be the power basis that SageMath uses to - represent `\GF{q^m}`. The default basis is then `1,\beta,\ldots,\beta^{m-1}`. + ``sub_field``. If not specified, given that `q = p^s`, let `\beta` be a generator + of the multiplicative group of `\GF{q^m}`. + The default basis is then `1,\beta,\ldots,\beta^{m-1}`. EXAMPLES:: @@ -198,9 +199,9 @@ def from_matrix_representation(w, base_field=None, basis=None): ``w``. - ``basis`` -- (default: ``None``) a basis of `\GF{q^m}` as a vector space over - `\GF{q}`. If not specified, given that `q = p^s`, let - `1,\beta,\ldots,\beta^{sm}` be the power basis that SageMath uses to - represent `\GF{q^m}`. The default basis is then `1,\beta,\ldots,\beta^{m-1}`. + `\GF{q}`. If not specified, given that `q = p^s`, let `\beta` be a generator + of the multiplicative group of `\GF{q^m}`. + The default basis is then `1,\beta,\ldots,\beta^{m-1}`. EXAMPLES:: @@ -230,7 +231,7 @@ def rank_weight(c, sub_field=None, basis=None): Return the rank of ``c`` as a matrix over ``sub_field``. If ``c`` is a vector over some field `\GF{q^m}`, the function converts it - into a matrix over `\GF{q}`. + into a matrix over ``sub_field```. INPUT: @@ -241,8 +242,8 @@ def rank_weight(c, sub_field=None, basis=None): - ``basis`` -- (default: ``None``) a basis of `\GF{q^m}` as a vector space over ``sub_field``. If not specified, given that `q = p^s`, let - `1,\beta,\ldots,\beta^{sm}` be the power basis that SageMath uses to - represent `\GF{q^m}`. The default basis is then `1,\beta,\ldots,\beta^{m-1}`. + `1,\beta,\ldots,\beta^{sm-1}` be the basis that SageMath uses to + represent `\GF{q^m}`. The default basis is then `1,\beta,\ldots,\beta^{m-1}` EXAMPLES:: @@ -278,9 +279,9 @@ def rank_distance(a, b, sub_field=None, basis=None): specified, it is the prime subfield `\GF{p}` of `\GF{q^m}` - ``basis`` -- (default: ``None``) a basis of `\GF{q^m}` as a vector space over - ``sub_field``. If not specified, given that `q = p^s`, let - `1,\beta,\ldots,\beta^{sm}` be the power basis that SageMath uses to - represent `\GF{q^m}`. The default basis is then `1,\beta,\ldots,\beta^{m-1}`. + ``sub_field``. If not specified, given that `q = p^s`, let `\beta` be a generator + of the multiplicative group of `\GF{q^m}`. + The default basis is then `1,\beta,\ldots,\beta^{m-1}`. EXAMPLES:: @@ -379,9 +380,9 @@ def __init__(self, base_field, sub_field, length, default_encoder_name, - ``default_decoder_name`` -- the name of the default decoder of ``self`` - ``basis`` -- (default: ``None``) a basis of `\GF{q^m}` as a vector space over - ``sub_field``. If not specified, given that `q = p^s`, let - `1,\beta,\ldots,\beta^{sm}` be the power basis that SageMath uses to - represent `\GF{q^m}`. The default basis is then `1,\beta,\ldots,\beta^{m-1}`. + ``sub_field``. If not specified, given that `q = p^s`, let `\beta` be a generator + of the multiplicative group of `\GF{q^m}`. + The default basis is then `1,\beta,\ldots,\beta^{m-1}`. EXAMPLES: @@ -587,6 +588,73 @@ def rank_weight_of_vector(self, word): 2 """ return rank_weight(word, self.sub_field()) + + def rank_support_of_vector(self, word, sub_field=None, basis=None): + r""" + Return the rank support of ``word`` over ``sub_field``, i.e. the vector space over + ``sub_field`` generated by its coefficients. + If ``word`` is a vector over some field `\GF{q^m}`, and ``sub_field`` is a subfield of + `\GF{q^m}`, the function converts it into a matrix over ``sub_field``, with + respect to the basis ``basis``. + + + INPUT: + + - ``word`` -- a vector over the ``base_field`` of ``self`` + + - ``sub_field`` -- (default: ``None``) a sub field of the ``base_field`` of ``self``; if not + specified, it is the prime subfield `\GF{p}` of the ``base_field`` of ``self`` + + - ``basis`` -- (default: ``None``) a basis of ``base_field`` of ``self`` as a vector space over + ``sub_field``. + + If not specified, given that `q = p^s`, let `\beta` be a generator of the multiplicative group + of `\GF{q^m}`. The default basis is then `1,\beta,\ldots,\beta^{m-1}`. + + EXAMPLES:: + + sage: G = Matrix(GF(64), [[1,1,0], [0,0,1]]) + sage: C = codes.LinearRankMetricCode(G, GF(4)) + sage: a = GF(64).gen() + sage: c = vector([a^4 + a^3 + 1, a^4 + a^3 + 1, a^4 + a^3 + a^2 + 1]) + sage: c in C + True + sage: C.rank_support_of_vector(c) + Vector space of degree 6 and dimension 2 over Finite Field of size 2 + Basis matrix: + [1 0 0 1 1 0] + [0 0 1 0 0 0] + + An example with a non canonical basis:: + + sage: K. = GF(2^3) + sage: G = Matrix(K, [[1,1,0], [0,0,1]]) + sage: C = codes.LinearRankMetricCode(G) + sage: c = vector([a^2, a^2, 0]) + sage: basis = [a, a+1, a^2] + sage: C.rank_support_of_vector(c, basis=basis) + Vector space of degree 3 and dimension 1 over Finite Field of size 2 + Basis matrix: + [0 0 1] + + TESTS:: + + sage: C.rank_support_of_vector(a) + Traceback (most recent call last): + ... + TypeError: input must be a vector + + sage: C.rank_support_of_vector(c, GF(2^4)) + Traceback (most recent call last): + ... + TypeError: the input subfield Finite Field in z4 of size 2^4 is not a subfield of Finite Field in a of size 2^3 + """ + if not isinstance(word, Vector): + raise TypeError("input must be a vector") + if sub_field != None: + if self.base_field().degree() % sub_field.degree() != 0: + raise TypeError(f"the input subfield {sub_field} is not a subfield of {self.base_field()}") + return to_matrix_representation(word, sub_field, basis).column_module() def matrix_form_of_vector(self, word): r""" @@ -679,9 +747,9 @@ def __init__(self, generator, sub_field=None, basis=None): specified, it is the prime field of ``base_field`` - ``basis`` -- (default: ``None``) a basis of `\GF{q^m}` as a vector space over - ``sub_field``. If not specified, given that `q = p^s`, let - `1,\beta,\ldots,\beta^{sm}` be the power basis that SageMath uses to - represent `\GF{q^m}`. The default basis is then `1,\beta,\ldots,\beta^{m-1}`. + ``sub_field``. If not specified, given that `q = p^s`, let `\beta` be a generator + of the multiplicative group of `\GF{q^m}`. + The default basis is then `1,\beta,\ldots,\beta^{m-1}`. EXAMPLES:: From d270b3f28b1ae03ac7d7371cc2b13a741faff1bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Mu=C3=B1oz--Bertrand?= Date: Thu, 13 Feb 2025 14:25:08 +0100 Subject: [PATCH 320/369] Update documentation and condition --- src/sage/coding/linear_rank_metric.py | 72 +++++++++++++-------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/src/sage/coding/linear_rank_metric.py b/src/sage/coding/linear_rank_metric.py index e45b1b1b0f7..62a1deb4b86 100644 --- a/src/sage/coding/linear_rank_metric.py +++ b/src/sage/coding/linear_rank_metric.py @@ -147,9 +147,9 @@ def to_matrix_representation(v, sub_field=None, basis=None): specified, it is the prime subfield `\GF{p}` of `\GF{q^m}` - ``basis`` -- (default: ``None``) a basis of `\GF{q^m}` as a vector space over - ``sub_field``. If not specified, given that `q = p^s`, let `\beta` be a generator - of the multiplicative group of `\GF{q^m}`. - The default basis is then `1,\beta,\ldots,\beta^{m-1}`. + ``sub_field``. If not specified, the default basis is + `1,\beta,\ldots,\beta^{m-1}` where `\beta` is the generator of the + multiplicative group of `\GF{q^m}` given by Sage. EXAMPLES:: @@ -199,9 +199,9 @@ def from_matrix_representation(w, base_field=None, basis=None): ``w``. - ``basis`` -- (default: ``None``) a basis of `\GF{q^m}` as a vector space over - `\GF{q}`. If not specified, given that `q = p^s`, let `\beta` be a generator - of the multiplicative group of `\GF{q^m}`. - The default basis is then `1,\beta,\ldots,\beta^{m-1}`. + `\GF{q}`. If not specified, the default basis is + `1,\beta,\ldots,\beta^{m-1}` where `\beta` is the generator of the + multiplicative group of `\GF{q^m}` given by Sage. EXAMPLES:: @@ -241,9 +241,9 @@ def rank_weight(c, sub_field=None, basis=None): specified, it is the prime subfield `\GF{p}` of `\GF{q^m}` - ``basis`` -- (default: ``None``) a basis of `\GF{q^m}` as a vector space over - ``sub_field``. If not specified, given that `q = p^s`, let - `1,\beta,\ldots,\beta^{sm-1}` be the basis that SageMath uses to - represent `\GF{q^m}`. The default basis is then `1,\beta,\ldots,\beta^{m-1}` + ``sub_field``. If not specified, the default basis is + `1,\beta,\ldots,\beta^{m-1}` where `\beta` is the generator of the + multiplicative group of `\GF{q^m}` given by Sage. EXAMPLES:: @@ -279,9 +279,9 @@ def rank_distance(a, b, sub_field=None, basis=None): specified, it is the prime subfield `\GF{p}` of `\GF{q^m}` - ``basis`` -- (default: ``None``) a basis of `\GF{q^m}` as a vector space over - ``sub_field``. If not specified, given that `q = p^s`, let `\beta` be a generator - of the multiplicative group of `\GF{q^m}`. - The default basis is then `1,\beta,\ldots,\beta^{m-1}`. + ``sub_field``. If not specified, the default basis is + `1,\beta,\ldots,\beta^{m-1}` where `\beta` is the generator of the + multiplicative group of `\GF{q^m}` given by Sage. EXAMPLES:: @@ -380,9 +380,9 @@ def __init__(self, base_field, sub_field, length, default_encoder_name, - ``default_decoder_name`` -- the name of the default decoder of ``self`` - ``basis`` -- (default: ``None``) a basis of `\GF{q^m}` as a vector space over - ``sub_field``. If not specified, given that `q = p^s`, let `\beta` be a generator - of the multiplicative group of `\GF{q^m}`. - The default basis is then `1,\beta,\ldots,\beta^{m-1}`. + ``sub_field``. If not specified, the default basis is + `1,\beta,\ldots,\beta^{m-1}` where `\beta` is the generator of the + multiplicative group of `\GF{q^m}` given by Sage. EXAMPLES: @@ -588,29 +588,29 @@ def rank_weight_of_vector(self, word): 2 """ return rank_weight(word, self.sub_field()) - + def rank_support_of_vector(self, word, sub_field=None, basis=None): r""" - Return the rank support of ``word`` over ``sub_field``, i.e. the vector space over - ``sub_field`` generated by its coefficients. - If ``word`` is a vector over some field `\GF{q^m}`, and ``sub_field`` is a subfield of - `\GF{q^m}`, the function converts it into a matrix over ``sub_field``, with - respect to the basis ``basis``. + Return the rank support of ``word`` over ``sub_field``, i.e. the vector space over + ``sub_field`` generated by its coefficients. + If ``word`` is a vector over some field `\GF{q^m}`, and ``sub_field`` is a subfield of + `\GF{q^m}`, the function converts it into a matrix over ``sub_field``, with + respect to the basis ``basis``. INPUT: - - ``word`` -- a vector over the ``base_field`` of ``self`` + - ``word`` -- a vector over the ``base_field`` of ``self``. - - ``sub_field`` -- (default: ``None``) a sub field of the ``base_field`` of ``self``; if not - specified, it is the prime subfield `\GF{p}` of the ``base_field`` of ``self`` + - ``sub_field`` -- (default: ``None``) a sub field of the + ``base_field`` of ``self``; if not specified, it is the prime + subfield `\GF{p}` of the ``base_field`` of ``self``. + + - ``basis`` -- (default: ``None``) a basis of ``base_field`` of + ``self`` as a vector space over ``sub_field``. If not specified, + the default basis is `1,\beta,\ldots,\beta^{m-1}`, where `\beta` is + the generator of the multiplicative group of `\GF{q^m}` given by Sage. - - ``basis`` -- (default: ``None``) a basis of ``base_field`` of ``self`` as a vector space over - ``sub_field``. - - If not specified, given that `q = p^s`, let `\beta` be a generator of the multiplicative group - of `\GF{q^m}`. The default basis is then `1,\beta,\ldots,\beta^{m-1}`. - EXAMPLES:: sage: G = Matrix(GF(64), [[1,1,0], [0,0,1]]) @@ -636,9 +636,9 @@ def rank_support_of_vector(self, word, sub_field=None, basis=None): Vector space of degree 3 and dimension 1 over Finite Field of size 2 Basis matrix: [0 0 1] - + TESTS:: - + sage: C.rank_support_of_vector(a) Traceback (most recent call last): ... @@ -651,7 +651,7 @@ def rank_support_of_vector(self, word, sub_field=None, basis=None): """ if not isinstance(word, Vector): raise TypeError("input must be a vector") - if sub_field != None: + if sub_field is not None: if self.base_field().degree() % sub_field.degree() != 0: raise TypeError(f"the input subfield {sub_field} is not a subfield of {self.base_field()}") return to_matrix_representation(word, sub_field, basis).column_module() @@ -747,9 +747,9 @@ def __init__(self, generator, sub_field=None, basis=None): specified, it is the prime field of ``base_field`` - ``basis`` -- (default: ``None``) a basis of `\GF{q^m}` as a vector space over - ``sub_field``. If not specified, given that `q = p^s`, let `\beta` be a generator - of the multiplicative group of `\GF{q^m}`. - The default basis is then `1,\beta,\ldots,\beta^{m-1}`. + ``sub_field``. If not specified, the default basis is + `1,\beta,\ldots,\beta^{m-1}` where `\beta` is the generator of the + multiplicative group of `\GF{q^m}` given by Sage. EXAMPLES:: From 753a3a4e3fdb26d63faf3ccbf1dbc0a8da9563be Mon Sep 17 00:00:00 2001 From: LudovicSchwob Date: Thu, 13 Feb 2025 14:57:50 +0100 Subject: [PATCH 321/369] Adding one more doctest --- src/sage/combinat/posets/posets.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/sage/combinat/posets/posets.py b/src/sage/combinat/posets/posets.py index 5fdfeed0057..182d214ecc5 100644 --- a/src/sage/combinat/posets/posets.py +++ b/src/sage/combinat/posets/posets.py @@ -8542,7 +8542,7 @@ def cuts(self): bounds of the set of upper bounds of `A` is exactly `A`. The cuts are computed as the smallest family of subsets of P containing its - principal order filters, the whose set P and which is closed by intersection. + principal order filters, the whole set P and which is closed by intersection. EXAMPLES:: @@ -8562,6 +8562,22 @@ def cuts(self): sage: P = Poset() sage: P.cuts() [frozenset()] + sage: P = Poset({3: [4, 5, 7], 1: [2, 4, 6], 4: [], 0: [2, 5], 2: [7], 7: [], 5: [6], 6: []}) + sage: P.cuts() + [frozenset({3, 4, 5, 6, 7}), + frozenset({1, 2, 4, 6, 7}), + frozenset({4}), + frozenset({0, 2, 5, 6, 7}), + frozenset({2, 7}), + frozenset({7}), + frozenset({5, 6}), + frozenset({6}), + frozenset({4, 6, 7}), + frozenset({5, 6, 7}), + frozenset({2, 6, 7}), + frozenset(), + frozenset({6, 7}), + frozenset({0, 1, 2, 3, 4, 5, 6, 7})] .. SEEALSO:: From e2e9491f87def8a42f3c42ac74f5a6baef8369e2 Mon Sep 17 00:00:00 2001 From: Camille Garnier Date: Thu, 13 Feb 2025 15:05:42 +0100 Subject: [PATCH 322/369] correction of indentation --- src/sage/coding/linear_rank_metric.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/sage/coding/linear_rank_metric.py b/src/sage/coding/linear_rank_metric.py index 62a1deb4b86..46a71a2c64f 100644 --- a/src/sage/coding/linear_rank_metric.py +++ b/src/sage/coding/linear_rank_metric.py @@ -600,16 +600,17 @@ def rank_support_of_vector(self, word, sub_field=None, basis=None): INPUT: - - ``word`` -- a vector over the ``base_field`` of ``self``. + - ``word`` -- a vector over the ``base_field`` of ``self``. - - ``sub_field`` -- (default: ``None``) a sub field of the - ``base_field`` of ``self``; if not specified, it is the prime - subfield `\GF{p}` of the ``base_field`` of ``self``. + - ``sub_field`` -- (default: ``None``) a sub field of the + ``base_field`` of ``self``; if not specified, it is the prime + subfield `\GF{p}` of the ``base_field`` of ``self``. + + - ``basis`` -- (default: ``None``) a basis of ``base_field`` of + ``self`` as a vector space over ``sub_field``. If not specified, + the default basis is `1,\beta,\ldots,\beta^{m-1}`, where `\beta` is + the generator of the multiplicative group of `\GF{q^m}` given by Sage. - - ``basis`` -- (default: ``None``) a basis of ``base_field`` of - ``self`` as a vector space over ``sub_field``. If not specified, - the default basis is `1,\beta,\ldots,\beta^{m-1}`, where `\beta` is - the generator of the multiplicative group of `\GF{q^m}` given by Sage. EXAMPLES:: From 763521d0971289c3f20a31af4063603d43ec884e Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Thu, 13 Feb 2025 21:19:18 +0700 Subject: [PATCH 323/369] Show test failures of ci-meson as annotations --- .github/workflows/ci-meson.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-meson.yml b/.github/workflows/ci-meson.yml index ab073aae87c..ca7fcd60fc5 100644 --- a/.github/workflows/ci-meson.yml +++ b/.github/workflows/ci-meson.yml @@ -81,7 +81,7 @@ jobs: run: | # We don't install sage_setup, so don't try to test it rm -R ./src/sage_setup/ - ./sage -t --all -p4 + ./sage -t --all -p4 --format github - name: Upload log uses: actions/upload-artifact@v4.5.0 From d94fd8942587da666a730340bae75e00102d4d31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20R=C3=BCth?= Date: Thu, 13 Feb 2025 15:26:42 +0100 Subject: [PATCH 324/369] Do not recommend to set MAKE=make -jX Setting MAKE=make -jX can lead to an exponential growth of build processes (as noted during SD128.) It's better to rely on make's jobserver that is available on macOS and all relevant Linux distributions. We also recommend a sane default for MAKEFLAGS that people can just copy & paste. Unfortunately, a lot of non-developers of SageMath still have to build from source and they appreciate not having to think about these things and just being able to copy a good default value. --- README.md | 32 +++++++++++------------------ src/doc/en/installation/source.rst | 33 ++++++++++++++++++------------ 2 files changed, 32 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index 8a77ffe1b42..243e3c03868 100644 --- a/README.md +++ b/README.md @@ -334,26 +334,18 @@ in the Installation Guide. 11. Optional, but highly recommended: Set some environment variables to customize the build. - For example, the `MAKE` environment variable controls whether to - run several jobs in parallel. On a machine with 4 processors, say, - typing `export MAKE="make -j4"` will configure the build script to - perform a parallel compilation of Sage using 4 jobs. On some - powerful machines, you might even consider `-j16`, as building with - more jobs than CPU cores can speed things up further. - - Alternatively, the `MAKEFLAGS` environment variable can be used. - In this case, only provide the flag itself, for example - `export MAKEFLAGS="-j4"`. - - Note that the compilation may nonetheless uses a different number of - threads, because sometimes `ninja` is used. - Unfortunately, [there is no way to control number of jobs `ninja` uses - from environment variables](https://github.com/ninja-build/ninja/issues/1482). - See also https://github.com/sagemath/sage/issues/38950. - - If the [Meson build system](https://doc-release--sagemath.netlify.app/html/en/installation/meson) - is used, the number of jobs running in parallel passed to `meson compile` will be respected, - because everything are managed by `ninja`. + The `MAKEFLAGS` variable controls whether to run several jobs in parallel. + To saturate all the execution threads of your CPU, we recommend to run + `export MAKEFLAGS="-j$(nproc) -l$(nproc).5"` if you are on Linux, and + `export MAKEFLAGS="-j$(sysctl -n hw.ncpu) -l$(sysctl -n hw.ncpu).5"` if you + are on macOS. + + Note that the compilation may nonetheless use a different number of + processes, e.g., for parts that are built with `ninja` which automatically + decides on the amount of parallelity to use. In practice, you might + therefore see twice as many processes during the build process than your + CPU has execution threads. Unless your system is low on RAM, this should + not affect the time the compilation takes substantially. To reduce the terminal output during the build, type `export V=0`. (`V` stands for "verbosity".) diff --git a/src/doc/en/installation/source.rst b/src/doc/en/installation/source.rst index 12b1483dfa2..12ab938f0f3 100644 --- a/src/doc/en/installation/source.rst +++ b/src/doc/en/installation/source.rst @@ -669,8 +669,8 @@ Environment variables Sage uses several environment variables to control its build process. Most users won't need to set any of these: the build process just works on many platforms. -(Note though that setting :envvar:`MAKE`, as described below, can significantly -speed up the process.) +(Note though that setting :envvar:`MAKEFLAGS`, as described below, can +significantly speed up the process.) Building Sage involves building many packages, each of which has its own compilation instructions. @@ -680,19 +680,26 @@ Standard environment controlling the build process Here are some of the more commonly used variables affecting the build process: -.. envvar:: MAKE +.. envvar:: MAKEFLAGS - One useful setting for this variable when building Sage is - ``MAKE='make -jNUM'`` to tell the ``make`` program to run ``NUM`` jobs in - parallel when building. - Note that some Sage packages may not support this variable. + This variable can be set to tell the ``make`` program to build things in + parallel. Set it to ``-jNUM`` to run ``NUM`` jobs in parallel when building. + Add ``-lNUM`` to tell make not to spawn more processes when the load exceeds + ``NUM``. + + A good value for this variable is ``MAKEFLAGS="-j$(nproc) -l$(nproc).5"`` on + Linux and ``MAKEFLAGS="-j$(sysctl -n hw.ncpu) -l$(sysctl -n hw.ncpu).5"`` on + macOS. This instructs make to use all the execution threads of your CPU while + bounding the load if there are other processes generating load. If your + system does not have a lot of RAM, you might want to choose lower limits, if + you have lots of RAM, it can sometimes be beneficial to set these limits + slightly higher. + + Note that some parts of the SageMath build system do not respect this + variable, e.g., when ninja gets invoked, it figures out the number of + processes to use on its own so the number of processes and the system load + you see might exceed the number configured here. - Some people advise using more jobs than there are CPU cores, at least if the - system is not heavily loaded and has plenty of RAM; for example, a good - setting for ``NUM`` might be between 1 and 1.5 times the number of cores. - In addition, the ``-l`` option sets a load limit: ``MAKE='make -j4 -l5.5``, - for example, tells ``make`` to try to use four jobs, but to not start more - than one job if the system load average is above 5.5. See the manual page for GNU ``make``: `Command-line options `_ and `Parallel building From 2af2e49c2febb79559782003912d0cd7e535811b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20R=C3=BCth?= Date: Thu, 13 Feb 2025 16:20:18 +0100 Subject: [PATCH 325/369] Remove recommendation of MAKE from the FAQ --- src/doc/en/faq/faq-usage.rst | 2 +- src/doc/it/faq/faq-usage.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/doc/en/faq/faq-usage.rst b/src/doc/en/faq/faq-usage.rst index 393a9c369e5..ca7cab3458b 100644 --- a/src/doc/en/faq/faq-usage.rst +++ b/src/doc/en/faq/faq-usage.rst @@ -70,7 +70,7 @@ Sage. The command .. CODE-BLOCK:: shell-session - $ export MAKE='make -j8' + $ export MAKEFLAGS='-j8' will enable 8 threads for parts of the build that support parallelism. Change the number 8 as appropriate to suit the number of diff --git a/src/doc/it/faq/faq-usage.rst b/src/doc/it/faq/faq-usage.rst index 677d1a24bc2..2d2b32352a0 100644 --- a/src/doc/it/faq/faq-usage.rst +++ b/src/doc/it/faq/faq-usage.rst @@ -70,7 +70,7 @@ questi prerequisiti come segue:: Se hai un sistema multiprocessore puoi scegliere una compilazione parallela di Sage. Il comando :: - export MAKE='make -j8' + export MAKEFLAGS='-j8' abiliterà 8 threads per quelle parti della compilazione che supportano il parallelismo. Al posto del numero 8 metti il numero di From 5f68d0baa289cfe4781ce18d892a5683adad0893 Mon Sep 17 00:00:00 2001 From: Camille Garnier Date: Thu, 13 Feb 2025 17:13:33 +0100 Subject: [PATCH 326/369] correction of documentation --- src/sage/coding/linear_rank_metric.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/sage/coding/linear_rank_metric.py b/src/sage/coding/linear_rank_metric.py index 46a71a2c64f..897275a17e3 100644 --- a/src/sage/coding/linear_rank_metric.py +++ b/src/sage/coding/linear_rank_metric.py @@ -603,14 +603,13 @@ def rank_support_of_vector(self, word, sub_field=None, basis=None): - ``word`` -- a vector over the ``base_field`` of ``self``. - ``sub_field`` -- (default: ``None``) a sub field of the - ``base_field`` of ``self``; if not specified, it is the prime - subfield `\GF{p}` of the ``base_field`` of ``self``. + ``base_field`` of ``self``; if not specified, it is the prime + subfield of `\GF{p}` the ``base_field`` of ``self``. - ``basis`` -- (default: ``None``) a basis of ``base_field`` of - ``self`` as a vector space over ``sub_field``. If not specified, - the default basis is `1,\beta,\ldots,\beta^{m-1}`, where `\beta` is - the generator of the multiplicative group of `\GF{q^m}` given by Sage. - + ``self`` as a vector space over ``sub_field``. If not specified, + the default basis is `1,\beta,\ldots,\beta^{m-1}`, where `\beta` is + the generator of the multiplicative group of `\GF{q^m}` given by Sage. EXAMPLES:: From 65dd442515295b8dfa09f6a2e3d302ddc321f9ee Mon Sep 17 00:00:00 2001 From: Fabien Vignes-Tourneret Date: Thu, 13 Feb 2025 18:34:20 +0100 Subject: [PATCH 327/369] line_graph for multigraphs --- src/sage/graphs/line_graph.pyx | 107 +++++++++++++++++++++++++++------ 1 file changed, 88 insertions(+), 19 deletions(-) diff --git a/src/sage/graphs/line_graph.pyx b/src/sage/graphs/line_graph.pyx index e54caf52897..648d16531b0 100644 --- a/src/sage/graphs/line_graph.pyx +++ b/src/sage/graphs/line_graph.pyx @@ -263,21 +263,32 @@ def is_line_graph(g, certificate=False): return True -def line_graph(g, labels=True): +def line_graph(g, labels=True, origlabels=False): """ - Return the line graph of the (di)graph ``g``. + Return the line graph of the (di)graph ``g`` (multiedges and loops allowed). INPUT: - ``labels`` -- boolean (default: ``True``); whether edge labels should be taken in consideration. If ``labels=True``, the vertices of the line graph - will be triples ``(u,v,label)``, and pairs of vertices otherwise. - - The line graph of an undirected graph G is an undirected graph H such that - the vertices of H are the edges of G and two vertices e and f of H are + will be triples ``(u,v,label)``, and pairs of vertices otherwise. In case + of multiple edges, the vertices of the line graph will be triples + ``(u,v,an integer)``. + + - ``origlabels`` -- boolean (default: ``False``); wether edge labels should + be stored or not. If g has multiple edges, if ``origlabels=True``, the + method returns a list the first element of which is the line-graph of g + and the second element is a dictionary {vertex of the line-graph: label of + the corresponding original edge}. If ``origlabels=False``, the method + returns only the line-graph. + + The line graph of an undirected graph G is an undirected simple graph H such + that the vertices of H are the edges of G and two vertices e and f of H are adjacent if e and f share a common vertex in G. In other words, an edge in H represents a path of length 2 in G. + Loops are not adjacent to themselves. + The line graph of a directed graph G is a directed graph H such that the vertices of H are the edges of G and two vertices e and f of H are adjacent if e and f share a common vertex in G and the terminal vertex of e is the @@ -311,7 +322,7 @@ def line_graph(g, labels=True): (1, 2, None), (1, 3, None), (2, 3, None)] - sage: h.am() # needs sage.modules + sage: h.am() # needs sage.modules [0 1 1 1 1 0] [1 0 1 1 0 1] [1 1 0 0 1 1] @@ -321,7 +332,7 @@ def line_graph(g, labels=True): sage: h2 = g.line_graph(labels=False) sage: h2.vertices(sort=True) [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)] - sage: h2.am() == h.am() # needs sage.modules + sage: h2.am() == h.am() # needs sage.modules True sage: g = DiGraph([[1..4], lambda i,j: i < j]) sage: h = g.line_graph() @@ -338,6 +349,40 @@ def line_graph(g, labels=True): ((1, 3, None), (3, 4, None), None), ((2, 3, None), (3, 4, None), None)] + Examples with multiple edges:: + + sage: L = Graph([(0,1),(0,1),(1,2)],multiedges=True).line_graph() + sage: L.edges() + [((0, 1, 0), (0, 1, 1), None), ((0, 1, 1), (1, 2, 2), None), + ((0, 1, 0), (1, 2, 2), None)] + sage: G = Graph([(0,1),(0,1,'a'),(0,1,'b'),(0,2),(1,2,'c')], + ....: multiedges=True) + sage: L = G.line_graph(False,True) + sage: L[0].edges() + [((0, 1, 1), (0, 1, 2), None), ((0, 1, 0), (0, 1, 2), None), + ((0, 1, 2), (0, 2, 3), None), ((0, 1, 2), (1, 2, 4), None), ((0, 1, 0), + (0, 1, 1), None), ((0, 1, 1), (0, 2, 3), None), ((0, 1, 1), + (1, 2, 4), None), ((0, 1, 0), (0, 2, 3), None), ((0, 1, 0), + (1, 2, 4), None), ((0, 2, 3), (1, 2, 4), None)] + sage: L[1] + {(0, 1, 0): None, + (0, 1, 1): 'a', + (0, 1, 2): 'b', + (0, 2, 3): None, + (1, 2, 4): 'c'} + sage: g = DiGraph([(0,1),(0,1),(1,2)],multiedges=True) + sage: g.line_graph().edges() + [((0, 1, 1), (1, 2, 2), None), ((0, 1, 0), (1, 2, 2), None)] + + An example with a loop:: + + sage: g = Graph([(0,0),(0,1),(0,2),(1,2)],multiedges=True,loops=True) + sage: L = g.line_graph() + sage: L.edges() + [((0, 0, None), (0, 1, None), None), ((0, 0, None), (0, 2, None), None), + ((0, 1, None), (0, 2, None), None), ((0, 1, None), (1, 2, None), None), + ((0, 2, None), (1, 2, None), None)] + TESTS: :issue:`13787`:: @@ -351,17 +396,36 @@ def line_graph(g, labels=True): """ cdef dict conflicts = {} cdef list elist = [] + cdef dict origlabels_dic = {} # stores original labels of edges in case of multiple edges - g._scream_if_not_simple() - if g._directed: + multiple = g.has_multiple_edges() + if multiple: + labels = True + + h = g.copy() + + # replace labels of edges of g with integers in range(len(g.edges())) in order to distinguish multiple edges. + if multiple: + for i, e in enumerate(h.edges()): + f = (e[0], e[1], i) + h.delete_edge(e) + h.add_edge(f) + if origlabels: + origlabels_dic[f] = e[2] + + if h._directed: from sage.graphs.digraph import DiGraph G = DiGraph() - G.add_vertices(g.edge_iterator(labels=labels)) - for v in g: + G.add_vertices(h.edge_iterator(labels=labels)) + for v in h: # Connect appropriate incident edges of the vertex v - G.add_edges((e, f) for e in g.incoming_edge_iterator(v, labels=labels) - for f in g.outgoing_edge_iterator(v, labels=labels)) - return G + G.add_edges((e, f) for e in h.incoming_edge_iterator(v, labels=labels) + for f in h.outgoing_edge_iterator(v, labels=labels)) + if origlabels and multiple: + return [G, origlabels_dic] + else: + return G + from sage.graphs.graph import Graph G = Graph() @@ -375,7 +439,7 @@ def line_graph(g, labels=True): # pair in the dictionary of conflicts # 1) List of vertices in the line graph - for e in g.edge_iterator(labels=labels): + for e in h.edge_iterator(labels=labels): if hash(e[0]) < hash(e[1]): elist.append(e) elif hash(e[0]) > hash(e[1]): @@ -389,11 +453,11 @@ def line_graph(g, labels=True): G.add_vertices(elist) # 2) adjacencies in the line graph - for v in g: + for v in h: elist = [] # Add the edge to the list, according to hashes, as previously - for e in g.edge_iterator(v, labels=labels): + for e in h.edge_iterator(v, labels=labels): # iterates over the edges incident to v if hash(e[0]) < hash(e[1]): elist.append(e) elif hash(e[0]) > hash(e[1]): @@ -402,12 +466,17 @@ def line_graph(g, labels=True): elist.append(conflicts[e]) # All pairs of elements in elist are edges of the line graph + # if g has multiple edges, some pairs appear more than once but as G is defined as simple, + # the corresponding edges are not added as multiedges (as it should be). while elist: x = elist.pop() for y in elist: G.add_edge(x, y) - return G + if origlabels and multiple: + return [G, origlabels_dic] + else: + return G def root_graph(g, verbose=False): From cf997285fc2d24036b1e486f444f6fc44dc54f0a Mon Sep 17 00:00:00 2001 From: Fabien Vignes-Tourneret Date: Thu, 13 Feb 2025 18:51:37 +0100 Subject: [PATCH 328/369] blank line removed --- src/sage/graphs/line_graph.pyx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/sage/graphs/line_graph.pyx b/src/sage/graphs/line_graph.pyx index 648d16531b0..57547206f02 100644 --- a/src/sage/graphs/line_graph.pyx +++ b/src/sage/graphs/line_graph.pyx @@ -426,7 +426,6 @@ def line_graph(g, labels=True, origlabels=False): else: return G - from sage.graphs.graph import Graph G = Graph() From 3cb417405c44fa484be4031356ff8f64252a0891 Mon Sep 17 00:00:00 2001 From: JP Labbe Date: Thu, 13 Feb 2025 12:53:34 -0500 Subject: [PATCH 329/369] Update fan.py Corrected typo --- src/sage/geometry/fan.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/geometry/fan.py b/src/sage/geometry/fan.py index c9f83337aae..edd28e9576b 100644 --- a/src/sage/geometry/fan.py +++ b/src/sage/geometry/fan.py @@ -2444,7 +2444,7 @@ def is_polytopal(self): Check if ``self`` is the normal fan of a polytope. A rational polyhedral fan is *polytopal* if it is the normal fan of a - polytope. This is also called *regular*, or provide a *coherent* + polytope. This is also called *regular*, or provides a *coherent* subdivision or leads to a *projective* toric variety. OUTPUT: ``True`` if ``self`` is polytopal and ``False`` otherwise From e40fef78f8d6e04c109a387ea4be9ddf2a332611 Mon Sep 17 00:00:00 2001 From: Jean-Philippe/Thinkbook Date: Thu, 13 Feb 2025 16:03:40 -0500 Subject: [PATCH 330/369] More fixes from review --- src/sage/geometry/fan.py | 9 +++++++++ src/sage/geometry/polyhedron/base5.py | 8 ++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/sage/geometry/fan.py b/src/sage/geometry/fan.py index edd28e9576b..e5946f98fa6 100644 --- a/src/sage/geometry/fan.py +++ b/src/sage/geometry/fan.py @@ -2472,6 +2472,15 @@ def is_polytopal(self): sage: mother(epsilon).is_polytopal() True + TESTS:: + + sage: cone = Cone([(1,1), (2,1)]) + sage: F = Fan([cone]) + sage: F.is_polytopal() + Traceback (most recent call last): + ... + ValueError: to be polytopal, the fan should be complete + .. SEEALSO:: :meth:`is_projective`. diff --git a/src/sage/geometry/polyhedron/base5.py b/src/sage/geometry/polyhedron/base5.py index 0367bdebaad..64862bf28e0 100644 --- a/src/sage/geometry/polyhedron/base5.py +++ b/src/sage/geometry/polyhedron/base5.py @@ -664,7 +664,7 @@ def deformation_cone(self): Return the deformation cone of ``self``. Let `P` be a `d`-polytope in `\RR^r` with `n` facets. The deformation - cone is a polyhedron in `\RR^n` who points are the right-hand side `b` + cone is a polyhedron in `\RR^n` whose points are the right-hand side `b` in `Ax\leq b` where `A` is the matrix of facet normals of ``self``, so that the resulting polytope has a normal fan which is a coarsening of the normal fan of ``self``. @@ -707,15 +707,15 @@ def deformation_cone(self): REFERENCES: - For more information, see Section 5.4 of [DLRS2010]_ and Section - 2.2 of [ACEP2020]. + For more information, see Section 5.4 of [DLRS2010]_ and Section + 2.2 of [ACEP2020]. """ from .constructor import Polyhedron m = matrix([ineq.A() for ineq in self.Hrepresentation()]) m = m.transpose() m_ker = m.right_kernel_matrix(basis='computed') gale = tuple(m_ker.columns()) - collection = [f.ambient_H_indices() for f in self.faces(0)] + collection = (f.ambient_H_indices() for f in self.faces(0)) n = len(gale) c = None for cone_indices in collection: From 2a9328a4ccdf79f1186eefb0af30d04b62f2a659 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Fri, 14 Feb 2025 09:27:20 +0100 Subject: [PATCH 331/369] some details in fan.py --- src/sage/geometry/fan.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sage/geometry/fan.py b/src/sage/geometry/fan.py index e5946f98fa6..fc004dd88ac 100644 --- a/src/sage/geometry/fan.py +++ b/src/sage/geometry/fan.py @@ -2439,7 +2439,7 @@ def Gale_transform(self): m = m.augment(matrix(ZZ, m.nrows(), 1, [1] * m.nrows())) return matrix(ZZ, m.integer_kernel().matrix()) - def is_polytopal(self): + def is_polytopal(self) -> bool: r""" Check if ``self`` is the normal fan of a polytope. @@ -2491,9 +2491,9 @@ def is_polytopal(self): from sage.geometry.polyhedron.constructor import Polyhedron pc = PointConfiguration(self.rays()) v_pc = [tuple(p) for p in pc] - pc_to_indices = {tuple(p):i for (i,p) in enumerate(pc)} - indices_to_vr = [tuple(r) for r in self.rays()] - cone_indices = [cone.ambient_ray_indices() for cone in self.generating_cones()] + pc_to_indices = {tuple(p):i for i, p in enumerate(pc)} + indices_to_vr = (tuple(r) for r in self.rays()) + cone_indices = (cone.ambient_ray_indices() for cone in self.generating_cones()) translator = [pc_to_indices[t] for t in indices_to_vr] translated_cone_indices = [[translator[i] for i in ci] for ci in cone_indices] dc_pc = pc.deformation_cone(translated_cone_indices) From a99b6a365af29a5951ffcd1563f1403bc087e11d Mon Sep 17 00:00:00 2001 From: Camille Garnier Date: Fri, 14 Feb 2025 09:39:44 +0100 Subject: [PATCH 332/369] correction of documentation --- src/sage/coding/linear_rank_metric.py | 30 +++++++++++++-------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/sage/coding/linear_rank_metric.py b/src/sage/coding/linear_rank_metric.py index 897275a17e3..8ab3f611e7c 100644 --- a/src/sage/coding/linear_rank_metric.py +++ b/src/sage/coding/linear_rank_metric.py @@ -91,7 +91,7 @@ AUTHORS: - Marketa Slukova (2019-08-16): initial version -- Camille Garnier and Rubén Muñoz--Bertrand (2024-02-13): added rank_support_of_vector, and corrected the documentation +- Camille Garnier and Rubén Muñoz-\-Bertrand (2024-02-13): added rank_support_of_vector, and corrected the documentation TESTS:: @@ -148,8 +148,8 @@ def to_matrix_representation(v, sub_field=None, basis=None): - ``basis`` -- (default: ``None``) a basis of `\GF{q^m}` as a vector space over ``sub_field``. If not specified, the default basis is - `1,\beta,\ldots,\beta^{m-1}` where `\beta` is the generator of the - multiplicative group of `\GF{q^m}` given by Sage. + `1,\beta,\ldots,\beta^{m-1}` where `\beta` is the generator of `\GF{q^m}` + given by SageMath. EXAMPLES:: @@ -200,9 +200,9 @@ def from_matrix_representation(w, base_field=None, basis=None): - ``basis`` -- (default: ``None``) a basis of `\GF{q^m}` as a vector space over `\GF{q}`. If not specified, the default basis is - `1,\beta,\ldots,\beta^{m-1}` where `\beta` is the generator of the - multiplicative group of `\GF{q^m}` given by Sage. - + `1,\beta,\ldots,\beta^{m-1}` where `\beta` is the generator + of `\GF{q^m}` given by SageMath. + EXAMPLES:: sage: from sage.coding.linear_rank_metric import from_matrix_representation @@ -242,8 +242,8 @@ def rank_weight(c, sub_field=None, basis=None): - ``basis`` -- (default: ``None``) a basis of `\GF{q^m}` as a vector space over ``sub_field``. If not specified, the default basis is - `1,\beta,\ldots,\beta^{m-1}` where `\beta` is the generator of the - multiplicative group of `\GF{q^m}` given by Sage. + `1,\beta,\ldots,\beta^{m-1}` where `\beta` is the generator + of `\GF{q^m}` given by SageMath. EXAMPLES:: @@ -280,8 +280,8 @@ def rank_distance(a, b, sub_field=None, basis=None): - ``basis`` -- (default: ``None``) a basis of `\GF{q^m}` as a vector space over ``sub_field``. If not specified, the default basis is - `1,\beta,\ldots,\beta^{m-1}` where `\beta` is the generator of the - multiplicative group of `\GF{q^m}` given by Sage. + `1,\beta,\ldots,\beta^{m-1}` where `\beta` is the generator + of `\GF{q^m}` given by SageMath. EXAMPLES:: @@ -381,8 +381,8 @@ def __init__(self, base_field, sub_field, length, default_encoder_name, - ``basis`` -- (default: ``None``) a basis of `\GF{q^m}` as a vector space over ``sub_field``. If not specified, the default basis is - `1,\beta,\ldots,\beta^{m-1}` where `\beta` is the generator of the - multiplicative group of `\GF{q^m}` given by Sage. + `1,\beta,\ldots,\beta^{m-1}` where `\beta` is the generator + of `\GF{q^m}` given by SageMath. EXAMPLES: @@ -609,7 +609,7 @@ def rank_support_of_vector(self, word, sub_field=None, basis=None): - ``basis`` -- (default: ``None``) a basis of ``base_field`` of ``self`` as a vector space over ``sub_field``. If not specified, the default basis is `1,\beta,\ldots,\beta^{m-1}`, where `\beta` is - the generator of the multiplicative group of `\GF{q^m}` given by Sage. + the generator of `\GF{q^m}` given by SageMath. EXAMPLES:: @@ -748,8 +748,8 @@ def __init__(self, generator, sub_field=None, basis=None): - ``basis`` -- (default: ``None``) a basis of `\GF{q^m}` as a vector space over ``sub_field``. If not specified, the default basis is - `1,\beta,\ldots,\beta^{m-1}` where `\beta` is the generator of the - multiplicative group of `\GF{q^m}` given by Sage. + `1,\beta,\ldots,\beta^{m-1}` where `\beta` is the generator `\GF{q^m}` + given by SageMath. EXAMPLES:: From 76b71cf9916b8d44b01ec83f9bf4bba60f6766b8 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Fri, 14 Feb 2025 09:42:06 +0100 Subject: [PATCH 333/369] add new files in meson + add a small comment in documentation --- src/sage/modules/free_module.py | 3 ++- src/sage/modules/meson.build | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/sage/modules/free_module.py b/src/sage/modules/free_module.py index a077baf30da..e9e3dfe5a0b 100644 --- a/src/sage/modules/free_module.py +++ b/src/sage/modules/free_module.py @@ -3171,7 +3171,8 @@ def pseudohom(self, f, twist, codomain=None, side="left"): - ``twist`` -- the twisting morphism or the twisting derivation (if a derivation is given, the corresponding morphism `\theta` - is automatically infered) + is automatically infered; + see also :class:`sage.rings.polynomial.ore_polynomial_ring.OrePolynomialRing`) - ``codomain`` -- (default: ``None``) the codomain of the pseudo morphisms; if ``None``, the codomain is the same than the domain diff --git a/src/sage/modules/meson.build b/src/sage/modules/meson.build index a5c78e98633..2edb6b1f10a 100644 --- a/src/sage/modules/meson.build +++ b/src/sage/modules/meson.build @@ -10,6 +10,8 @@ py.install_sources( 'free_module_homspace.py', 'free_module_integer.py', 'free_module_morphism.py', + 'free_module_pseudohomspace.py', + 'free_module_pseudomorphism.py', 'free_quadratic_module.py', 'free_quadratic_module_integer_symmetric.py', 'matrix_morphism.py', From d44d4794142b1c7fb2dbf3d3a240f44021863480 Mon Sep 17 00:00:00 2001 From: Camille Garnier Date: Fri, 14 Feb 2025 09:46:13 +0100 Subject: [PATCH 334/369] correction of documentation --- src/sage/coding/linear_rank_metric.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/coding/linear_rank_metric.py b/src/sage/coding/linear_rank_metric.py index 8ab3f611e7c..1f85664f5c0 100644 --- a/src/sage/coding/linear_rank_metric.py +++ b/src/sage/coding/linear_rank_metric.py @@ -202,7 +202,7 @@ def from_matrix_representation(w, base_field=None, basis=None): `\GF{q}`. If not specified, the default basis is `1,\beta,\ldots,\beta^{m-1}` where `\beta` is the generator of `\GF{q^m}` given by SageMath. - + EXAMPLES:: sage: from sage.coding.linear_rank_metric import from_matrix_representation From f66a0053e089cbd05f33f6c2b85778e3e0b7d780 Mon Sep 17 00:00:00 2001 From: camille-garnier Date: Fri, 14 Feb 2025 11:05:45 +0100 Subject: [PATCH 335/369] Update src/sage/coding/linear_rank_metric.py Co-authored-by: Xavier Caruso --- src/sage/coding/linear_rank_metric.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/sage/coding/linear_rank_metric.py b/src/sage/coding/linear_rank_metric.py index 1f85664f5c0..99865a3ef44 100644 --- a/src/sage/coding/linear_rank_metric.py +++ b/src/sage/coding/linear_rank_metric.py @@ -649,8 +649,7 @@ def rank_support_of_vector(self, word, sub_field=None, basis=None): ... TypeError: the input subfield Finite Field in z4 of size 2^4 is not a subfield of Finite Field in a of size 2^3 """ - if not isinstance(word, Vector): - raise TypeError("input must be a vector") + word = self.ambient_space()(word) if sub_field is not None: if self.base_field().degree() % sub_field.degree() != 0: raise TypeError(f"the input subfield {sub_field} is not a subfield of {self.base_field()}") From fc04aab0d294ec7dda33cca3d784c93e9763e6f6 Mon Sep 17 00:00:00 2001 From: Camille Garnier Date: Fri, 14 Feb 2025 11:26:11 +0100 Subject: [PATCH 336/369] correction of documentation --- src/sage/coding/linear_rank_metric.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/sage/coding/linear_rank_metric.py b/src/sage/coding/linear_rank_metric.py index 99865a3ef44..f7ce79c822c 100644 --- a/src/sage/coding/linear_rank_metric.py +++ b/src/sage/coding/linear_rank_metric.py @@ -638,12 +638,7 @@ def rank_support_of_vector(self, word, sub_field=None, basis=None): [0 0 1] TESTS:: - - sage: C.rank_support_of_vector(a) - Traceback (most recent call last): - ... - TypeError: input must be a vector - + sage: C.rank_support_of_vector(c, GF(2^4)) Traceback (most recent call last): ... From b8c384a1142376169ab19e1addf4f68abfdd302a Mon Sep 17 00:00:00 2001 From: Camille Garnier Date: Fri, 14 Feb 2025 11:35:49 +0100 Subject: [PATCH 337/369] correction of documentation --- src/sage/coding/linear_rank_metric.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/coding/linear_rank_metric.py b/src/sage/coding/linear_rank_metric.py index f7ce79c822c..e25d326cc8b 100644 --- a/src/sage/coding/linear_rank_metric.py +++ b/src/sage/coding/linear_rank_metric.py @@ -638,7 +638,7 @@ def rank_support_of_vector(self, word, sub_field=None, basis=None): [0 0 1] TESTS:: - + sage: C.rank_support_of_vector(c, GF(2^4)) Traceback (most recent call last): ... From 92bc9948fedcd4d615f367b706a786e7cbe98c6a Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Fri, 14 Feb 2025 20:44:20 +0700 Subject: [PATCH 338/369] Reformat meson.build file to satisfy update-meson --- src/sage/data_structures/meson.build | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/sage/data_structures/meson.build b/src/sage/data_structures/meson.build index 8c100328378..3cc243fe62a 100644 --- a/src/sage/data_structures/meson.build +++ b/src/sage/data_structures/meson.build @@ -42,9 +42,7 @@ foreach name, pyx : extension_data ) endforeach -extension_data_cpp = { - 'pairing_heap' : files('pairing_heap.pyx'), -} +extension_data_cpp = {'pairing_heap' : files('pairing_heap.pyx')} foreach name, pyx : extension_data_cpp py.extension_module( From 6aee4983263425ab17a8050f199e3ae42499f7d4 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Fri, 14 Feb 2025 22:15:28 +0700 Subject: [PATCH 339/369] Apply suggestions --- src/sage/misc/sageinspect.py | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/src/sage/misc/sageinspect.py b/src/sage/misc/sageinspect.py index 9066671f6f9..6fc0e29551f 100644 --- a/src/sage/misc/sageinspect.py +++ b/src/sage/misc/sageinspect.py @@ -1329,12 +1329,8 @@ def sage_getfile(obj): return sage_getfile(obj.func) return sage_getfile(obj.__class__) # inspect.getabsfile(obj.__class__) else: - try: - objinit = obj.__init__ - except AttributeError: - pass - else: - pos = _extract_embedded_position(_sage_getdoc_unformatted(objinit)) + if hasattr(obj, '__init__'): + pos = _extract_embedded_position(_sage_getdoc_unformatted(obj.__init__)) if pos is not None: (_, filename, _) = pos return filename @@ -1347,6 +1343,7 @@ def sage_getfile(obj): for suffix in import_machinery.EXTENSION_SUFFIXES: if sourcefile.endswith(suffix): # TODO: the following is incorrect in meson editable install + # because the build is out-of-tree, # but as long as either the class or its __init__ method has a # docstring, _sage_getdoc_unformatted should return correct result # see https://github.com/mesonbuild/meson-python/issues/723 @@ -2370,12 +2367,8 @@ class Element: try: return inspect.getsourcelines(obj) except (OSError, TypeError) as err: - try: - objinit = obj.__init__ - except AttributeError: - pass - else: - d = _sage_getdoc_unformatted(objinit) + if hasattr(obj, '__init__'): + d = _sage_getdoc_unformatted(obj.__init__) pos = _extract_embedded_position(d) if pos is None: if inspect.isclass(obj): From 82a439a9ec694c19022808a67cf47732a4a5379a Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Fri, 14 Feb 2025 20:51:49 +0700 Subject: [PATCH 340/369] Test on CI that update-meson is properly ran --- .github/workflows/ci-meson.yml | 25 +++++++++++++++++++++++++ .gitignore | 5 +++++ 2 files changed, 30 insertions(+) diff --git a/.github/workflows/ci-meson.yml b/.github/workflows/ci-meson.yml index ab073aae87c..13ae8d9dc92 100644 --- a/.github/workflows/ci-meson.yml +++ b/.github/workflows/ci-meson.yml @@ -72,6 +72,23 @@ jobs: # Use --no-deps and pip check below to verify that all necessary dependencies are installed via conda pip install --no-build-isolation --no-deps --config-settings=builddir=builddir . -v + - name: Check update-meson + # this step must be after build, because meson.build creates a number of __init__.py files + # that is needed to make tools/update-meson.py run correctly + shell: bash -l {0} + id: check_update_meson + run: | + python3 tools/update-meson.py + make test-git-no-uncommitted-changes + continue-on-error: true + + - name: Show files changed by update-meson + if: ${{ steps.check_update_meson.outcome == 'failure' }} + shell: bash -l {0} + run: | + git status + git diff + - name: Verify dependencies shell: bash -l {0} run: pip check @@ -83,6 +100,14 @@ jobs: rm -R ./src/sage_setup/ ./sage -t --all -p4 + - name: Report update-meson failure + if: ${{ steps.check_update_meson.outcome == 'failure' }} + shell: bash -l {0} + run: | + # Please see step 'Show files changed by update-meson' above and apply the changes, + # or run tools/update-meson.py locally + false + - name: Upload log uses: actions/upload-artifact@v4.5.0 if: failure() diff --git a/.gitignore b/.gitignore index b8bfc364a26..c0a86090cad 100644 --- a/.gitignore +++ b/.gitignore @@ -467,3 +467,8 @@ src/sage/libs/mpfr/__init__.py src/sage/libs/mpc/__init__.py src/sage/calculus/transforms/__init__.py src/sage/calculus/__init__.py + +# Temporary files generated by Meson CI (needed to make test pass because +# ci-meson.yml runs a `make test-git-no-uncommitted-changes` step) +/.ccache +/setup-miniconda-patched-environment-*.yml From 56f350c839d004646ee0a6ede9d671248b4326fb Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Sat, 15 Feb 2025 12:37:54 +0700 Subject: [PATCH 341/369] Handle backslash in flint reader --- src/sage_setup/autogen/flint/reader.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/sage_setup/autogen/flint/reader.py b/src/sage_setup/autogen/flint/reader.py index 671fe5c491d..3673b330d96 100644 --- a/src/sage_setup/autogen/flint/reader.py +++ b/src/sage_setup/autogen/flint/reader.py @@ -176,11 +176,16 @@ def process_line(self): line = self.lines[self.i] if line.startswith('.. function::'): self.add_declaration() - if line[13] != ' ': + line_rest = line.removeprefix('.. function::') + if not line_rest.startswith(' '): print('Warning: no space {}'.format(line)) - self.signatures.append(line[13:].strip()) self.state = self.FUNCTION_DECLARATION self.i += 1 + signature = line_rest.strip() + while signature.endswith('\\'): + signature = signature.removesuffix('\\').strip() + ' ' + self.lines[self.i].strip() + self.i += 1 + self.signatures.append(signature) elif line.startswith('.. macro::'): self.add_declaration() if line[10] != ' ': From a787dec08f032abad8f476e0261e79ab0cc5992d Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Sat, 15 Feb 2025 13:27:46 +0700 Subject: [PATCH 342/369] Update template files --- .../autogen/flint/templates/flint_sage.pyx.template | 2 +- .../autogen/flint/templates/types.pxd.template | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/sage_setup/autogen/flint/templates/flint_sage.pyx.template b/src/sage_setup/autogen/flint/templates/flint_sage.pyx.template index b77c1a764aa..a553d99d33c 100644 --- a/src/sage_setup/autogen/flint/templates/flint_sage.pyx.template +++ b/src/sage_setup/autogen/flint/templates/flint_sage.pyx.template @@ -12,7 +12,7 @@ Import this module:: sage: import sage.libs.flint.flint_sage -We verify that :trac:`6919` is correctly fixed:: +We verify that :issue:`6919` is correctly fixed:: sage: R. = PolynomialRing(ZZ) sage: A = 2^(2^17+2^15) diff --git a/src/sage_setup/autogen/flint/templates/types.pxd.template b/src/sage_setup/autogen/flint/templates/types.pxd.template index ae6bd308f79..4b9d982c7fa 100644 --- a/src/sage_setup/autogen/flint/templates/types.pxd.template +++ b/src/sage_setup/autogen/flint/templates/types.pxd.template @@ -24,6 +24,9 @@ ctypedef mp_limb_t ulong ctypedef mp_limb_signed_t slong ctypedef mp_limb_t flint_bitcnt_t +# New in flint 3.2.0-rc1 +ctypedef mp_ptr nn_ptr +ctypedef mp_srcptr nn_srcptr cdef extern from "flint_wrap.h": # flint/fmpz.h @@ -269,6 +272,16 @@ cdef extern from "flint_wrap.h": ctypedef struct flint_rand_s: pass ctypedef flint_rand_s flint_rand_t[1] + ctypedef enum flint_err_t: + # flint_autogen.py does not support parsing .. enum:: yet + FLINT_ERROR + FLINT_OVERFLOW + FLINT_IMPINV + FLINT_DOMERR + FLINT_DIVZERO + FLINT_EXPOF + FLINT_INEXACT + FLINT_TEST_FAIL cdef long FLINT_BITS cdef long FLINT_D_BITS From 5ce77b66439ac47f7da0b7fcdf41f67e5549947f Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Sat, 15 Feb 2025 08:29:47 +0100 Subject: [PATCH 343/369] explain that equations in the error messages may be printed in random order --- src/sage/rings/lazy_series_ring.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/sage/rings/lazy_series_ring.py b/src/sage/rings/lazy_series_ring.py index d601b73d358..a3ef6705093 100644 --- a/src/sage/rings/lazy_series_ring.py +++ b/src/sage/rings/lazy_series_ring.py @@ -799,11 +799,21 @@ def define_implicitly(self, series, equations, max_lookahead=1): + Let us now try to only specify the degree 0 and degree 1 + components. We will see that this is still not enough to + remove the ambiguity, so an error is raised. However, we + will see that the dependence on ``series[1]`` disappears. + The equation which has no unique solution is now + ``6*series[3] + (-2*x - 2*y)*series[2] + (x*y*f1) == 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 # random + sage: F + coefficient [3]: ... == 0> + + (Note that the order of summands of the equation in the error + message is not deterministic.) Laurent series examples:: From dc38269c5490a007aaba7d8122ec5dd8fe00a5a4 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Sat, 15 Feb 2025 08:45:05 +0100 Subject: [PATCH 344/369] explain rationale behind convention how monomials are represented --- src/sage/rings/semirings/tropical_mpolynomial.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/sage/rings/semirings/tropical_mpolynomial.py b/src/sage/rings/semirings/tropical_mpolynomial.py index 225435ba488..d5f5a9d2e43 100644 --- a/src/sage/rings/semirings/tropical_mpolynomial.py +++ b/src/sage/rings/semirings/tropical_mpolynomial.py @@ -653,12 +653,17 @@ def _repr_(self): r""" Return a string representation of ``self``. + Note that ``x`` equals ``0*x``, which is different from + ``1*x``. Therefore, we represent monomials always together + with their coefficients, to avoid confusion. + EXAMPLES:: sage: T = TropicalSemiring(QQ) sage: R. = PolynomialRing(T) sage: x + R(-1)*y + R(-3) 0*x + (-1)*y + (-3) + """ if not self.monomial_coefficients(): return str(self.parent().base().zero()) From f89cb8a7b525485eb573ca5ce295eee116ad902d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Sat, 15 Feb 2025 09:03:11 +0100 Subject: [PATCH 345/369] fix doctest in doc --- src/doc/en/thematic_tutorials/coercion_and_categories.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/src/doc/en/thematic_tutorials/coercion_and_categories.rst b/src/doc/en/thematic_tutorials/coercion_and_categories.rst index 2a039fb920b..31a39a94c8a 100644 --- a/src/doc/en/thematic_tutorials/coercion_and_categories.rst +++ b/src/doc/en/thematic_tutorials/coercion_and_categories.rst @@ -132,7 +132,6 @@ This base class provides a lot more methods than a general parent:: 'ngens', 'one', 'order', - 'random_element', 'zero', 'zeta', 'zeta_order'] From 7438eb96d30d0724118e2d0252829bc097d2a6d3 Mon Sep 17 00:00:00 2001 From: John Cremona Date: Wed, 13 Nov 2024 14:46:51 +0000 Subject: [PATCH 346/369] #38960-eclib-upgrade-20241112 --- build/pkgs/eclib/SPKG.rst | 2 +- build/pkgs/eclib/checksums.ini | 4 ++-- build/pkgs/eclib/package-version.txt | 2 +- build/pkgs/eclib/spkg-configure.m4 | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/build/pkgs/eclib/SPKG.rst b/build/pkgs/eclib/SPKG.rst index 5627fdcb57c..ac8b27b3606 100644 --- a/build/pkgs/eclib/SPKG.rst +++ b/build/pkgs/eclib/SPKG.rst @@ -30,5 +30,5 @@ Upstream Contact - Author: John Cremona - Email: john.cremona@gmail.com - Website: - http://homepages.warwick.ac.uk/staff/J.E.Cremona/mwrank/index.html + https://johncremona.github.io/mwrank/index.html - Repository: https://github.com/JohnCremona/eclib diff --git a/build/pkgs/eclib/checksums.ini b/build/pkgs/eclib/checksums.ini index fde4faaee15..b046783341d 100644 --- a/build/pkgs/eclib/checksums.ini +++ b/build/pkgs/eclib/checksums.ini @@ -1,4 +1,4 @@ tarball=eclib-VERSION.tar.bz2 -sha1=3028ac95e1b76699f5f9e871ac706cda363ab842 -sha256=32d116a3e359b0de4f6486c2bb6188bb8b553c8b833f618cc2596484e8b6145a +sha1=749e4fda3660006a9459f129148d05ba482daa29 +sha256=30765c27ca1420141f83517897119d0185fea9b31132392170ddae40b060e46f upstream_url=https://github.com/JohnCremona/eclib/releases/download/vVERSION/eclib-VERSION.tar.bz2 diff --git a/build/pkgs/eclib/package-version.txt b/build/pkgs/eclib/package-version.txt index 190b92f716b..353869c3cba 100644 --- a/build/pkgs/eclib/package-version.txt +++ b/build/pkgs/eclib/package-version.txt @@ -1 +1 @@ -20231212 +20241112 diff --git a/build/pkgs/eclib/spkg-configure.m4 b/build/pkgs/eclib/spkg-configure.m4 index ba5c22fa090..23771dad1bd 100644 --- a/build/pkgs/eclib/spkg-configure.m4 +++ b/build/pkgs/eclib/spkg-configure.m4 @@ -1,7 +1,7 @@ SAGE_SPKG_CONFIGURE([eclib], [ SAGE_SPKG_DEPCHECK([ntl pari flint], [ dnl use existing eclib only if the version reported by pkg-config is recent enough - m4_pushdef([SAGE_ECLIB_VER],["20231212"]) + m4_pushdef([SAGE_ECLIB_VER],["20241112"]) PKG_CHECK_MODULES([ECLIB], [eclib >= SAGE_ECLIB_VER], [ AC_CACHE_CHECK([for mwrank version == SAGE_ECLIB_VER], [ac_cv_path_MWRANK], [ AC_PATH_PROGS_FEATURE_CHECK([MWRANK], [mwrank], [ From eaef4ae0cfb87c7461b51dacc634e72a9db58e41 Mon Sep 17 00:00:00 2001 From: John Cremona Date: Tue, 19 Nov 2024 13:51:49 +0000 Subject: [PATCH 347/369] #38960 fix eclib interface after upgrade --- src/sage/libs/eclib/__init__.pxd | 4 ++-- src/sage/libs/eclib/mat.pyx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sage/libs/eclib/__init__.pxd b/src/sage/libs/eclib/__init__.pxd index d44d4fba865..84d1fc92275 100644 --- a/src/sage/libs/eclib/__init__.pxd +++ b/src/sage/libs/eclib/__init__.pxd @@ -55,7 +55,7 @@ cdef extern from "eclib/matrix.h": cdef cppclass mat: mat() mat(mat m) - scalar* get_entries() + vector[scalar] get_entries() scalar sub(long, long) long nrows() long ncols() @@ -67,7 +67,7 @@ cdef extern from "eclib/smatrix.h": cdef cppclass smat: smat() smat(smat m) - scalar* get_entries() + vector[scalar] get_entries() scalar sub(long, long) long nrows() long ncols() diff --git a/src/sage/libs/eclib/mat.pyx b/src/sage/libs/eclib/mat.pyx index bfdeb6ae5c1..e7abe369b2b 100644 --- a/src/sage/libs/eclib/mat.pyx +++ b/src/sage/libs/eclib/mat.pyx @@ -10,7 +10,7 @@ from sage.rings.integer_ring import ZZ from sage.matrix.matrix_integer_sparse cimport Matrix_integer_sparse from sage.matrix.matrix_integer_dense cimport Matrix_integer_dense from sage.rings.integer cimport Integer - +from libcpp.vector cimport vector cdef class Matrix: """ @@ -213,7 +213,7 @@ cdef class Matrix: """ cdef long n = self.nrows() cdef long i, j, k - cdef scalar* v = self.M.get_entries() # coercion needed to deal with const + cdef vector[scalar] v = self.M.get_entries() # coercion needed to deal with const cdef Matrix_integer_dense Td cdef Matrix_integer_sparse Ts From 34ffbd9460cfdf89bf21f6631e1989ca6247c45b Mon Sep 17 00:00:00 2001 From: John Cremona Date: Wed, 4 Dec 2024 08:35:58 +0000 Subject: [PATCH 348/369] #38960 simplify eclib interface --- src/sage/libs/eclib/__init__.pxd | 2 -- src/sage/libs/eclib/mat.pyx | 18 +++++++----------- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/src/sage/libs/eclib/__init__.pxd b/src/sage/libs/eclib/__init__.pxd index 84d1fc92275..2673af0613f 100644 --- a/src/sage/libs/eclib/__init__.pxd +++ b/src/sage/libs/eclib/__init__.pxd @@ -55,7 +55,6 @@ cdef extern from "eclib/matrix.h": cdef cppclass mat: mat() mat(mat m) - vector[scalar] get_entries() scalar sub(long, long) long nrows() long ncols() @@ -67,7 +66,6 @@ cdef extern from "eclib/smatrix.h": cdef cppclass smat: smat() smat(smat m) - vector[scalar] get_entries() scalar sub(long, long) long nrows() long ncols() diff --git a/src/sage/libs/eclib/mat.pyx b/src/sage/libs/eclib/mat.pyx index e7abe369b2b..5dbb39faa0e 100644 --- a/src/sage/libs/eclib/mat.pyx +++ b/src/sage/libs/eclib/mat.pyx @@ -10,7 +10,6 @@ from sage.rings.integer_ring import ZZ from sage.matrix.matrix_integer_sparse cimport Matrix_integer_sparse from sage.matrix.matrix_integer_dense cimport Matrix_integer_dense from sage.rings.integer cimport Integer -from libcpp.vector cimport vector cdef class Matrix: """ @@ -212,8 +211,7 @@ cdef class Matrix: """ cdef long n = self.nrows() - cdef long i, j, k - cdef vector[scalar] v = self.M.get_entries() # coercion needed to deal with const + cdef long i, j cdef Matrix_integer_dense Td cdef Matrix_integer_sparse Ts @@ -221,21 +219,19 @@ cdef class Matrix: # Ugly code... if sparse: Ts = MatrixSpace(ZZ, n, sparse=sparse).zero_matrix().__copy__() - k = 0 for i from 0 <= i < n: for j from 0 <= j < n: - if v[k]: - Ts.set_unsafe(i, j, Integer(v[k])) - k += 1 + Mij = Integer(self.M.sub(i+1,j+1)); + if Mij: + Ts.set_unsafe(i, j, Mij) return Ts else: Td = MatrixSpace(ZZ, n, sparse=sparse).zero_matrix().__copy__() - k = 0 for i from 0 <= i < n: for j from 0 <= j < n: - if v[k]: - Td.set_unsafe(i, j, Integer(v[k])) - k += 1 + Mij = Integer(self.M.sub(i+1,j+1)); + if Mij: + Td.set_unsafe(i, j, Mij) return Td From 37aa9f8eb373462eecf01909fbca114d188cf716 Mon Sep 17 00:00:00 2001 From: John Cremona Date: Wed, 4 Dec 2024 09:20:07 +0000 Subject: [PATCH 349/369] #38960: fix lint issue --- src/sage/libs/eclib/mat.pyx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/libs/eclib/mat.pyx b/src/sage/libs/eclib/mat.pyx index 5dbb39faa0e..989d9d1a70c 100644 --- a/src/sage/libs/eclib/mat.pyx +++ b/src/sage/libs/eclib/mat.pyx @@ -221,7 +221,7 @@ cdef class Matrix: Ts = MatrixSpace(ZZ, n, sparse=sparse).zero_matrix().__copy__() for i from 0 <= i < n: for j from 0 <= j < n: - Mij = Integer(self.M.sub(i+1,j+1)); + Mij = Integer(self.M.sub(i+1,j+1)) if Mij: Ts.set_unsafe(i, j, Mij) return Ts @@ -229,7 +229,7 @@ cdef class Matrix: Td = MatrixSpace(ZZ, n, sparse=sparse).zero_matrix().__copy__() for i from 0 <= i < n: for j from 0 <= j < n: - Mij = Integer(self.M.sub(i+1,j+1)); + Mij = Integer(self.M.sub(i+1,j+1)) if Mij: Td.set_unsafe(i, j, Mij) return Td From 5d94a65310d1b552cbdb2dfab1a0f7b4bccf7adb Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Sat, 15 Feb 2025 07:04:34 -0500 Subject: [PATCH 350/369] build/pkgs: update eclib to 20250122 --- build/pkgs/eclib/checksums.ini | 6 +++--- build/pkgs/eclib/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/eclib/checksums.ini b/build/pkgs/eclib/checksums.ini index b046783341d..ff0ea83451a 100644 --- a/build/pkgs/eclib/checksums.ini +++ b/build/pkgs/eclib/checksums.ini @@ -1,4 +1,4 @@ tarball=eclib-VERSION.tar.bz2 -sha1=749e4fda3660006a9459f129148d05ba482daa29 -sha256=30765c27ca1420141f83517897119d0185fea9b31132392170ddae40b060e46f -upstream_url=https://github.com/JohnCremona/eclib/releases/download/vVERSION/eclib-VERSION.tar.bz2 +sha1=ec8dd87df46ac5a54b548354681085d1da6e7e13 +sha256=9f8c2b32e24a4f20d7cc2d336ea30c8ea03b5b0953c2d32adda0c496e7616899 +upstream_url=https://github.com/JohnCremona/eclib/releases/download/VERSION/eclib-VERSION.tar.bz2 diff --git a/build/pkgs/eclib/package-version.txt b/build/pkgs/eclib/package-version.txt index 353869c3cba..b0aec664e8d 100644 --- a/build/pkgs/eclib/package-version.txt +++ b/build/pkgs/eclib/package-version.txt @@ -1 +1 @@ -20241112 +20250122 From 2da824dcb2389b5c2c66e06d9d5159bdd1779511 Mon Sep 17 00:00:00 2001 From: mklss <59539887+mklss@users.noreply.github.com> Date: Sat, 15 Feb 2025 18:53:43 +0100 Subject: [PATCH 351/369] Appease linter. --- src/sage/parallel/use_fork.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sage/parallel/use_fork.py b/src/sage/parallel/use_fork.py index b3bca5ad5b6..60cb56eed37 100644 --- a/src/sage/parallel/use_fork.py +++ b/src/sage/parallel/use_fork.py @@ -22,6 +22,7 @@ from sage.misc.randstate import set_random_seed from sage.misc.prandom import getrandbits + class WorkerData: """ Simple class which stores data about a running ``p_iter_fork`` From 06530ce5fe3b236043c99180ae56fd91948ae410 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Sun, 16 Feb 2025 10:33:24 +0100 Subject: [PATCH 352/369] better doctest in src/sage/categories/rings.py Co-authored-by: Travis Scrimshaw --- src/sage/categories/rings.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/sage/categories/rings.py b/src/sage/categories/rings.py index 78919cda38b..49f08930071 100644 --- a/src/sage/categories/rings.py +++ b/src/sage/categories/rings.py @@ -1665,12 +1665,12 @@ def random_element(self, *args): EXAMPLES:: - sage: ZZ.random_element(8) # random - 1 - sage: QQ.random_element(8) # random - 2 - sage: ZZ.random_element(4,12) # random - 7 + sage: -8 <= ZZ.random_element(8) <= 8 + True + sage: -8 <= QQ.random_element(8) <= 8 + True + sage: 4 <= ZZ.random_element(4,12) <= 12 + True """ if not args: a, b = -2, 2 From b7ae258f69610e6373bdbbd7341e16964d8e1c79 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Mon, 17 Feb 2025 15:56:51 +0700 Subject: [PATCH 353/369] Refactor test-git-no-uncommitted-changes to independent script --- .github/workflows/ci-meson.yml | 2 +- Makefile | 8 +------- tools/test-git-no-uncommitted-changes | 10 ++++++++++ 3 files changed, 12 insertions(+), 8 deletions(-) create mode 100755 tools/test-git-no-uncommitted-changes diff --git a/.github/workflows/ci-meson.yml b/.github/workflows/ci-meson.yml index 13ae8d9dc92..edac659b006 100644 --- a/.github/workflows/ci-meson.yml +++ b/.github/workflows/ci-meson.yml @@ -79,7 +79,7 @@ jobs: id: check_update_meson run: | python3 tools/update-meson.py - make test-git-no-uncommitted-changes + ./tools/test-git-no-uncommitted-changes continue-on-error: true - name: Show files changed by update-meson diff --git a/Makefile b/Makefile index 15dbbb411c0..4b5e9350d3e 100644 --- a/Makefile +++ b/Makefile @@ -254,13 +254,7 @@ TEST_TARGET = $@ TEST = ./sage -t --logfile=$(TEST_LOG) $(TEST_FLAGS) --optional=$(TEST_OPTIONAL) $(TEST_FILES) test-git-no-uncommitted-changes: - @UNCOMMITTED=$$(git status --porcelain); \ - if [ -n "$$UNCOMMITTED" ]; then \ - echo "Error: the git repo has uncommitted changes:"; \ - echo "$$UNCOMMITTED"; \ - echo; \ - exit 1; \ - fi + ./tools/test-git-no-uncommitted-changes test: all @echo '### make $(TEST_TARGET): Running $(TEST)' >> $(TEST_LOG) diff --git a/tools/test-git-no-uncommitted-changes b/tools/test-git-no-uncommitted-changes new file mode 100755 index 00000000000..f79997062f5 --- /dev/null +++ b/tools/test-git-no-uncommitted-changes @@ -0,0 +1,10 @@ +#!/usr/bin/env sh +# Test that there is no uncommitted changes in the repository. Return failure if there is. +# Can also be invoked with `make test-git-no-uncommitted-changes` from top level. +UNCOMMITTED="$(git status --porcelain)"; +if [ -n "$UNCOMMITTED" ]; then + echo "Error: the git repo has uncommitted changes:"; + echo "$UNCOMMITTED"; + echo; + exit 1; +fi From 44e75720cd63667e1924d8752eceadbc3862e186 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Mon, 17 Feb 2025 15:57:05 +0700 Subject: [PATCH 354/369] Show newly created files in git diff, if any --- .github/workflows/ci-meson.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci-meson.yml b/.github/workflows/ci-meson.yml index edac659b006..eb8784fb650 100644 --- a/.github/workflows/ci-meson.yml +++ b/.github/workflows/ci-meson.yml @@ -86,6 +86,7 @@ jobs: if: ${{ steps.check_update_meson.outcome == 'failure' }} shell: bash -l {0} run: | + git add --intent-to-add . # also show newly created files in git diff git status git diff From c3705802e4af59ef280bd973b593fcb2fe922f79 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Mon, 17 Feb 2025 15:59:16 +0700 Subject: [PATCH 355/369] Merge two report steps --- .github/workflows/ci-meson.yml | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci-meson.yml b/.github/workflows/ci-meson.yml index eb8784fb650..228767fc32e 100644 --- a/.github/workflows/ci-meson.yml +++ b/.github/workflows/ci-meson.yml @@ -82,14 +82,6 @@ jobs: ./tools/test-git-no-uncommitted-changes continue-on-error: true - - name: Show files changed by update-meson - if: ${{ steps.check_update_meson.outcome == 'failure' }} - shell: bash -l {0} - run: | - git add --intent-to-add . # also show newly created files in git diff - git status - git diff - - name: Verify dependencies shell: bash -l {0} run: pip check @@ -101,12 +93,14 @@ jobs: rm -R ./src/sage_setup/ ./sage -t --all -p4 - - name: Report update-meson failure + - name: Show files changed by update-meson if: ${{ steps.check_update_meson.outcome == 'failure' }} shell: bash -l {0} + # must be after "Test" since we still want to run test when check_update_meson fails run: | - # Please see step 'Show files changed by update-meson' above and apply the changes, - # or run tools/update-meson.py locally + git add --intent-to-add . # also show newly created files in git diff + git status + git diff false - name: Upload log From 7ac3f7db87959a823d32af7e94b7c416f6502905 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Mon, 17 Feb 2025 16:23:41 +0700 Subject: [PATCH 356/369] Fix a nonfunctional long time doctest tag --- src/sage/modular/btquotients/pautomorphicform.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/modular/btquotients/pautomorphicform.py b/src/sage/modular/btquotients/pautomorphicform.py index 3d49d59bdfc..8358822f88e 100644 --- a/src/sage/modular/btquotients/pautomorphicform.py +++ b/src/sage/modular/btquotients/pautomorphicform.py @@ -2497,7 +2497,7 @@ def _make_invariant(self, F): sage: H = X.harmonic_cocycles(2,prec = 5) sage: A = X.padic_automorphic_forms(2,prec = 5) sage: h = H.basis()[0] - sage: A.lift(h) # indirect doctest long time + sage: A.lift(h) # indirect doctest, long time p-adic automorphic form of cohomological weight 0 """ S = self._source.get_stabilizers() From fc0e64084daa7334375da6937cf03b959f59485d Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Mon, 17 Feb 2025 20:00:40 +0700 Subject: [PATCH 357/369] Apply suggestion --- src/sage/modular/btquotients/pautomorphicform.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/modular/btquotients/pautomorphicform.py b/src/sage/modular/btquotients/pautomorphicform.py index 8358822f88e..a059d4a5064 100644 --- a/src/sage/modular/btquotients/pautomorphicform.py +++ b/src/sage/modular/btquotients/pautomorphicform.py @@ -2497,7 +2497,7 @@ def _make_invariant(self, F): sage: H = X.harmonic_cocycles(2,prec = 5) sage: A = X.padic_automorphic_forms(2,prec = 5) sage: h = H.basis()[0] - sage: A.lift(h) # indirect doctest, long time + sage: A.lift(h) # indirect doctest, long time p-adic automorphic form of cohomological weight 0 """ S = self._source.get_stabilizers() From ae47c25043c856c044bb1064681b320af0301f00 Mon Sep 17 00:00:00 2001 From: Fabien Vignes-Tourneret Date: Mon, 17 Feb 2025 15:32:41 +0100 Subject: [PATCH 358/369] Modifications suggested by David Coudert Copy of the input graph g only if g.has_multiple_edges(). New copy is called g as the input. --- src/sage/graphs/line_graph.pyx | 72 ++++++++++++++++------------------ 1 file changed, 34 insertions(+), 38 deletions(-) diff --git a/src/sage/graphs/line_graph.pyx b/src/sage/graphs/line_graph.pyx index 57547206f02..e3788d9c680 100644 --- a/src/sage/graphs/line_graph.pyx +++ b/src/sage/graphs/line_graph.pyx @@ -263,7 +263,7 @@ def is_line_graph(g, certificate=False): return True -def line_graph(g, labels=True, origlabels=False): +def line_graph(g, labels=True, return_labels=False): """ Return the line graph of the (di)graph ``g`` (multiedges and loops allowed). @@ -275,12 +275,12 @@ def line_graph(g, labels=True, origlabels=False): of multiple edges, the vertices of the line graph will be triples ``(u,v,an integer)``. - - ``origlabels`` -- boolean (default: ``False``); wether edge labels should - be stored or not. If g has multiple edges, if ``origlabels=True``, the + - ``return_labels`` -- boolean (default: ``False``); whether edge labels should + be stored or not. If g has multiple edges and if ``return_labels=True``, the method returns a list the first element of which is the line-graph of g - and the second element is a dictionary {vertex of the line-graph: label of - the corresponding original edge}. If ``origlabels=False``, the method - returns only the line-graph. + and the second element is a dictionary {vertex of the line-graph: former + corresponding edge with its original label}. If ``return_labels=False``, + the method returns only the line-graph. The line graph of an undirected graph G is an undirected simple graph H such that the vertices of H are the edges of G and two vertices e and f of H are @@ -322,7 +322,7 @@ def line_graph(g, labels=True, origlabels=False): (1, 2, None), (1, 3, None), (2, 3, None)] - sage: h.am() # needs sage.modules + sage: h.am() # needs sage.modules [0 1 1 1 1 0] [1 0 1 1 0 1] [1 1 0 0 1 1] @@ -332,7 +332,7 @@ def line_graph(g, labels=True, origlabels=False): sage: h2 = g.line_graph(labels=False) sage: h2.vertices(sort=True) [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)] - sage: h2.am() == h.am() # needs sage.modules + sage: h2.am() == h.am() # needs sage.modules True sage: g = DiGraph([[1..4], lambda i,j: i < j]) sage: h = g.line_graph() @@ -350,14 +350,14 @@ def line_graph(g, labels=True, origlabels=False): ((2, 3, None), (3, 4, None), None)] Examples with multiple edges:: - - sage: L = Graph([(0,1),(0,1),(1,2)],multiedges=True).line_graph() + + sage: L=line_graph(Graph([(0,1),(0,1),(1,2)],multiedges=True)) sage: L.edges() [((0, 1, 0), (0, 1, 1), None), ((0, 1, 1), (1, 2, 2), None), ((0, 1, 0), (1, 2, 2), None)] sage: G = Graph([(0,1),(0,1,'a'),(0,1,'b'),(0,2),(1,2,'c')], ....: multiedges=True) - sage: L = G.line_graph(False,True) + sage: L=line_graph(G,False,True) sage: L[0].edges() [((0, 1, 1), (0, 1, 2), None), ((0, 1, 0), (0, 1, 2), None), ((0, 1, 2), (0, 2, 3), None), ((0, 1, 2), (1, 2, 4), None), ((0, 1, 0), @@ -371,18 +371,18 @@ def line_graph(g, labels=True, origlabels=False): (0, 2, 3): None, (1, 2, 4): 'c'} sage: g = DiGraph([(0,1),(0,1),(1,2)],multiedges=True) - sage: g.line_graph().edges() + sage: line_graph(g).edges() [((0, 1, 1), (1, 2, 2), None), ((0, 1, 0), (1, 2, 2), None)] An example with a loop:: - + sage: g = Graph([(0,0),(0,1),(0,2),(1,2)],multiedges=True,loops=True) - sage: L = g.line_graph() + sage: L = line_graph(g) sage: L.edges() [((0, 0, None), (0, 1, None), None), ((0, 0, None), (0, 2, None), None), ((0, 1, None), (0, 2, None), None), ((0, 1, None), (1, 2, None), None), ((0, 2, None), (1, 2, None), None)] - + TESTS: :issue:`13787`:: @@ -399,29 +399,25 @@ def line_graph(g, labels=True, origlabels=False): cdef dict origlabels_dic = {} # stores original labels of edges in case of multiple edges multiple = g.has_multiple_edges() - if multiple: - labels = True - - h = g.copy() - # replace labels of edges of g with integers in range(len(g.edges())) in order to distinguish multiple edges. if multiple: - for i, e in enumerate(h.edges()): - f = (e[0], e[1], i) - h.delete_edge(e) - h.add_edge(f) - if origlabels: - origlabels_dic[f] = e[2] - - if h._directed: + # As the edges of g are the vertices of its line graph, we need to distinguish between the mutliple edges of g. + # To this aim, we assign to each edge of g an integer label (between 0 and g.size() - 1) and set labels to True + # in order to keep these labels during the construction of the line graph. + labels = True + origlabels_dic = {(u, v, id): (u, v, label) + for id, (u, v, label) in enumerate(g.edge_iterator())} + g = parent(g)([g, origlabels_dic.keys()], format='vertices_and_edges', multiedges=True) + + if g._directed: from sage.graphs.digraph import DiGraph G = DiGraph() - G.add_vertices(h.edge_iterator(labels=labels)) - for v in h: + G.add_vertices(g.edge_iterator(labels=labels)) + for v in g: # Connect appropriate incident edges of the vertex v - G.add_edges((e, f) for e in h.incoming_edge_iterator(v, labels=labels) - for f in h.outgoing_edge_iterator(v, labels=labels)) - if origlabels and multiple: + G.add_edges((e, f) for e in g.incoming_edge_iterator(v, labels=labels) + for f in g.outgoing_edge_iterator(v, labels=labels)) + if return_labels and multiple: return [G, origlabels_dic] else: return G @@ -438,7 +434,7 @@ def line_graph(g, labels=True, origlabels=False): # pair in the dictionary of conflicts # 1) List of vertices in the line graph - for e in h.edge_iterator(labels=labels): + for e in g.edge_iterator(labels=labels): if hash(e[0]) < hash(e[1]): elist.append(e) elif hash(e[0]) > hash(e[1]): @@ -450,13 +446,13 @@ def line_graph(g, labels=True, origlabels=False): elist.append(e) G.add_vertices(elist) - + # 2) adjacencies in the line graph - for v in h: + for v in g: elist = [] # Add the edge to the list, according to hashes, as previously - for e in h.edge_iterator(v, labels=labels): # iterates over the edges incident to v + for e in g.edge_iterator(v, labels=labels): # iterates over the edges incident to v if hash(e[0]) < hash(e[1]): elist.append(e) elif hash(e[0]) > hash(e[1]): @@ -472,7 +468,7 @@ def line_graph(g, labels=True, origlabels=False): for y in elist: G.add_edge(x, y) - if origlabels and multiple: + if return_labels and multiple: return [G, origlabels_dic] else: return G From 738731bbc8306fbede604b475352c5694fde1c2f Mon Sep 17 00:00:00 2001 From: Fabien Vignes-Tourneret Date: Mon, 17 Feb 2025 18:29:12 +0100 Subject: [PATCH 359/369] line_graph(g) -> g.line_graph() dans Examples --- src/sage/graphs/line_graph.pyx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/sage/graphs/line_graph.pyx b/src/sage/graphs/line_graph.pyx index e3788d9c680..a2653422a4b 100644 --- a/src/sage/graphs/line_graph.pyx +++ b/src/sage/graphs/line_graph.pyx @@ -351,13 +351,13 @@ def line_graph(g, labels=True, return_labels=False): Examples with multiple edges:: - sage: L=line_graph(Graph([(0,1),(0,1),(1,2)],multiedges=True)) + sage: L = Graph([(0,1),(0,1),(1,2)],multiedges=True).line_graph() sage: L.edges() [((0, 1, 0), (0, 1, 1), None), ((0, 1, 1), (1, 2, 2), None), ((0, 1, 0), (1, 2, 2), None)] sage: G = Graph([(0,1),(0,1,'a'),(0,1,'b'),(0,2),(1,2,'c')], ....: multiedges=True) - sage: L=line_graph(G,False,True) + sage: L = G.line_graph(False,True) sage: L[0].edges() [((0, 1, 1), (0, 1, 2), None), ((0, 1, 0), (0, 1, 2), None), ((0, 1, 2), (0, 2, 3), None), ((0, 1, 2), (1, 2, 4), None), ((0, 1, 0), @@ -371,13 +371,13 @@ def line_graph(g, labels=True, return_labels=False): (0, 2, 3): None, (1, 2, 4): 'c'} sage: g = DiGraph([(0,1),(0,1),(1,2)],multiedges=True) - sage: line_graph(g).edges() + sage: g.line_graph().edges() [((0, 1, 1), (1, 2, 2), None), ((0, 1, 0), (1, 2, 2), None)] An example with a loop:: sage: g = Graph([(0,0),(0,1),(0,2),(1,2)],multiedges=True,loops=True) - sage: L = line_graph(g) + sage: L = g.line_graph() sage: L.edges() [((0, 0, None), (0, 1, None), None), ((0, 0, None), (0, 2, None), None), ((0, 1, None), (0, 2, None), None), ((0, 1, None), (1, 2, None), None), @@ -408,7 +408,7 @@ def line_graph(g, labels=True, return_labels=False): origlabels_dic = {(u, v, id): (u, v, label) for id, (u, v, label) in enumerate(g.edge_iterator())} g = parent(g)([g, origlabels_dic.keys()], format='vertices_and_edges', multiedges=True) - + if g._directed: from sage.graphs.digraph import DiGraph G = DiGraph() @@ -446,13 +446,13 @@ def line_graph(g, labels=True, return_labels=False): elist.append(e) G.add_vertices(elist) - + # 2) adjacencies in the line graph for v in g: elist = [] # Add the edge to the list, according to hashes, as previously - for e in g.edge_iterator(v, labels=labels): # iterates over the edges incident to v + for e in g.edge_iterator(v, labels=labels): # iterates over the edges incident to v if hash(e[0]) < hash(e[1]): elist.append(e) elif hash(e[0]) > hash(e[1]): From 5daa54618a604af5119d6e9a236a048504d428bc Mon Sep 17 00:00:00 2001 From: M Bussonnier Date: Mon, 17 Feb 2025 20:56:52 +0100 Subject: [PATCH 360/369] Remove dead mailing lists. AFAICT, the `@scipy.org` mailing lists are dead since 2018 technically ipython-dev was migrated to `@python.org`, but had no messages since 2023 and -user was never migrated. --- build/pkgs/ipython/SPKG.rst | 4 ---- 1 file changed, 4 deletions(-) diff --git a/build/pkgs/ipython/SPKG.rst b/build/pkgs/ipython/SPKG.rst index fa370a16841..5a1a7cb7913 100644 --- a/build/pkgs/ipython/SPKG.rst +++ b/build/pkgs/ipython/SPKG.rst @@ -33,7 +33,3 @@ Upstream Contact ---------------- http://ipython.org - -ipython-dev@scipy.org - -ipython-user@scipy.org From 6db279f74c8ef9b5f12e8a12fe72dbe012977c88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Tue, 18 Feb 2025 08:33:40 +0100 Subject: [PATCH 361/369] add missing import --- src/sage/combinat/posets/posets.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sage/combinat/posets/posets.py b/src/sage/combinat/posets/posets.py index 0c7e6c4f414..5bea9176d03 100644 --- a/src/sage/combinat/posets/posets.py +++ b/src/sage/combinat/posets/posets.py @@ -7319,6 +7319,7 @@ def apozeta_polynomial(self): sage: parent(_) Univariate Polynomial Ring in q over Rational Field """ + from sage.functions.other import binomial R = PolynomialRing(QQ, 'q') q = R.gen() From 2fc50b022d411feb165fd36656e2bd8423ddc916 Mon Sep 17 00:00:00 2001 From: Fabien Vignes-Tourneret Date: Tue, 18 Feb 2025 11:05:53 +0100 Subject: [PATCH 362/369] Modifications to pass the tests (hopefully) - Removed white spaces in blanklines (in the Examples) - Add import command for parent --- src/sage/graphs/line_graph.pyx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/sage/graphs/line_graph.pyx b/src/sage/graphs/line_graph.pyx index a2653422a4b..b28cd239a48 100644 --- a/src/sage/graphs/line_graph.pyx +++ b/src/sage/graphs/line_graph.pyx @@ -350,7 +350,7 @@ def line_graph(g, labels=True, return_labels=False): ((2, 3, None), (3, 4, None), None)] Examples with multiple edges:: - + sage: L = Graph([(0,1),(0,1),(1,2)],multiedges=True).line_graph() sage: L.edges() [((0, 1, 0), (0, 1, 1), None), ((0, 1, 1), (1, 2, 2), None), @@ -375,14 +375,14 @@ def line_graph(g, labels=True, return_labels=False): [((0, 1, 1), (1, 2, 2), None), ((0, 1, 0), (1, 2, 2), None)] An example with a loop:: - + sage: g = Graph([(0,0),(0,1),(0,2),(1,2)],multiedges=True,loops=True) sage: L = g.line_graph() sage: L.edges() [((0, 0, None), (0, 1, None), None), ((0, 0, None), (0, 2, None), None), ((0, 1, None), (0, 2, None), None), ((0, 1, None), (1, 2, None), None), ((0, 2, None), (1, 2, None), None)] - + TESTS: :issue:`13787`:: @@ -407,6 +407,7 @@ def line_graph(g, labels=True, return_labels=False): labels = True origlabels_dic = {(u, v, id): (u, v, label) for id, (u, v, label) in enumerate(g.edge_iterator())} + from sage.structure.element cimport parent g = parent(g)([g, origlabels_dic.keys()], format='vertices_and_edges', multiedges=True) if g._directed: From 65a494669ec20cdb3bcd4ccbfd1f6f0f018e6ad9 Mon Sep 17 00:00:00 2001 From: Fabien Vignes-Tourneret Date: Wed, 19 Feb 2025 08:08:36 +0100 Subject: [PATCH 363/369] Moved cimport parent at the top of the file --- src/sage/graphs/line_graph.pyx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sage/graphs/line_graph.pyx b/src/sage/graphs/line_graph.pyx index b28cd239a48..e4cb99fb05c 100644 --- a/src/sage/graphs/line_graph.pyx +++ b/src/sage/graphs/line_graph.pyx @@ -1,4 +1,6 @@ # cython: binding=True + +from sage.structure.element cimport parent r""" Line graphs @@ -407,7 +409,6 @@ def line_graph(g, labels=True, return_labels=False): labels = True origlabels_dic = {(u, v, id): (u, v, label) for id, (u, v, label) in enumerate(g.edge_iterator())} - from sage.structure.element cimport parent g = parent(g)([g, origlabels_dic.keys()], format='vertices_and_edges', multiedges=True) if g._directed: From 0421445dafa0604ae80739520370283133b881b1 Mon Sep 17 00:00:00 2001 From: Fabien Vignes-Tourneret Date: Wed, 19 Feb 2025 11:14:04 +0100 Subject: [PATCH 364/369] Modification of examples and new location for import of parent In case return_labels == True, the returned dictionary is different from a previous version. An example output had to be modified accordingly. --- src/sage/graphs/line_graph.pyx | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/sage/graphs/line_graph.pyx b/src/sage/graphs/line_graph.pyx index e4cb99fb05c..ae799621708 100644 --- a/src/sage/graphs/line_graph.pyx +++ b/src/sage/graphs/line_graph.pyx @@ -1,6 +1,4 @@ # cython: binding=True - -from sage.structure.element cimport parent r""" Line graphs @@ -129,6 +127,7 @@ Functions --------- """ +from sage.structure.element cimport parent def is_line_graph(g, certificate=False): r""" @@ -367,11 +366,11 @@ def line_graph(g, labels=True, return_labels=False): (1, 2, 4), None), ((0, 1, 0), (0, 2, 3), None), ((0, 1, 0), (1, 2, 4), None), ((0, 2, 3), (1, 2, 4), None)] sage: L[1] - {(0, 1, 0): None, - (0, 1, 1): 'a', - (0, 1, 2): 'b', - (0, 2, 3): None, - (1, 2, 4): 'c'} + {(0, 1, 0): (0, 1, None), + (0, 1, 1): (0, 1, 'b'), + (0, 1, 2): (0, 1, 'a'), + (0, 2, 3): (0, 2, None), + (1, 2, 4): (1, 2, 'c')} sage: g = DiGraph([(0,1),(0,1),(1,2)],multiedges=True) sage: g.line_graph().edges() [((0, 1, 1), (1, 2, 2), None), ((0, 1, 0), (1, 2, 2), None)] From 34bece3ad817112be90d855641fde8ffbced08eb Mon Sep 17 00:00:00 2001 From: Fabien Vignes-Tourneret Date: Wed, 19 Feb 2025 13:20:44 +0100 Subject: [PATCH 365/369] Added a second blank line below an import statement --- src/sage/graphs/line_graph.pyx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sage/graphs/line_graph.pyx b/src/sage/graphs/line_graph.pyx index ae799621708..bc655b50003 100644 --- a/src/sage/graphs/line_graph.pyx +++ b/src/sage/graphs/line_graph.pyx @@ -129,6 +129,7 @@ Functions from sage.structure.element cimport parent + def is_line_graph(g, certificate=False): r""" Check whether the graph `g` is a line graph. From d796a29cf8503ddaef7c219d42ef41fee6b0260e Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Wed, 19 Feb 2025 23:29:31 +0700 Subject: [PATCH 366/369] Disable running test step when update-meson check fails --- .github/workflows/ci-meson.yml | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/.github/workflows/ci-meson.yml b/.github/workflows/ci-meson.yml index 228767fc32e..3c433e130af 100644 --- a/.github/workflows/ci-meson.yml +++ b/.github/workflows/ci-meson.yml @@ -76,11 +76,14 @@ jobs: # this step must be after build, because meson.build creates a number of __init__.py files # that is needed to make tools/update-meson.py run correctly shell: bash -l {0} - id: check_update_meson run: | python3 tools/update-meson.py - ./tools/test-git-no-uncommitted-changes - continue-on-error: true + if ! ./tools/test-git-no-uncommitted-changes; then + git add --intent-to-add . # also show newly created files in git diff + git status + git diff + false + fi - name: Verify dependencies shell: bash -l {0} @@ -93,16 +96,6 @@ jobs: rm -R ./src/sage_setup/ ./sage -t --all -p4 - - name: Show files changed by update-meson - if: ${{ steps.check_update_meson.outcome == 'failure' }} - shell: bash -l {0} - # must be after "Test" since we still want to run test when check_update_meson fails - run: | - git add --intent-to-add . # also show newly created files in git diff - git status - git diff - false - - name: Upload log uses: actions/upload-artifact@v4.5.0 if: failure() From 6bf8c80c19d9777bf3fdc2cec512d57c7dbfad67 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Wed, 19 Feb 2025 23:58:08 +0700 Subject: [PATCH 367/369] Update an outdated comment --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index c0a86090cad..9190feb4b13 100644 --- a/.gitignore +++ b/.gitignore @@ -469,6 +469,6 @@ src/sage/calculus/transforms/__init__.py src/sage/calculus/__init__.py # Temporary files generated by Meson CI (needed to make test pass because -# ci-meson.yml runs a `make test-git-no-uncommitted-changes` step) +# ci-meson.yml runs a `./tools/test-git-no-uncommitted-changes` step) /.ccache /setup-miniconda-patched-environment-*.yml From 7471239ba7ed7795994a8bbae8c1a19dd4312f31 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Thu, 20 Feb 2025 14:32:33 +0700 Subject: [PATCH 368/369] Apply suggestions --- src/sage/rings/fraction_field.py | 36 +++++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/src/sage/rings/fraction_field.py b/src/sage/rings/fraction_field.py index fabe94c58da..625abbb3f54 100644 --- a/src/sage/rings/fraction_field.py +++ b/src/sage/rings/fraction_field.py @@ -575,8 +575,18 @@ def _convert_from_finite_precision_laurent_series(self, x): Uses the algorithm described in ``_. This may be changed to use Berlekamp--Massey algorithm or something else to compute Padé approximant in the future. + + TESTS:: + + sage: F = QQ['x'].fraction_field() + sage: R = LaurentSeriesRing(QQ, 'x') + sage: f = ~R(x^2 + x + 3); f + 1/3 - 1/9*x - 2/27*x^2 + 5/81*x^3 + ... + O(x^20) + sage: F._convert_from_finite_precision_laurent_series(f) + 1/(x^2 + x + 3) """ - integral_part, fractional_part = self(x.truncate(1)), x.truncate_neg(1) + integral_part = self(x.truncate(1)) + fractional_part = x.truncate_neg(1) if fractional_part.is_zero(): return integral_part return integral_part + ~self._convert_from_finite_precision_laurent_series(~fractional_part) @@ -678,7 +688,7 @@ def _element_constructor_(self, x, y=None, coerce=True): sage: F(x) -1/2/(a^2 + a) - Conversion from power series to rational function field truncates, but is deprecated:: + Conversion from power series to rational function field gives an approximation:: sage: F. = Frac(QQ['x']) sage: R. = QQ[[]] @@ -687,8 +697,15 @@ def _element_constructor_(self, x, y=None, coerce=True): Power Series Ring in x over Rational Field sage: F(f) doctest:warning... - DeprecationWarning: Conversion from power series to rational function field is deprecated, use .truncate() instead + DeprecationWarning: Previously conversion from power series to rational function field truncates + instead of gives an approximation. Use .truncate() to recover the old behavior See https://github.com/sagemath/sage/issues/39485 for details. + 1/(x + 1) + + Previously, the power series was truncated. To recover the old behavior, use + :meth:`~sage.rings.power_series_ring_element.PowerSeries.truncate`:: + + sage: F(f.truncate()) -x^19 + x^18 - x^17 + x^16 - x^15 + x^14 - x^13 + x^12 - x^11 + x^10 - x^9 + x^8 - x^7 + x^6 - x^5 + x^4 - x^3 + x^2 - x + 1 Conversion from Laurent series to rational function field gives an approximation:: @@ -716,12 +733,14 @@ def _element_constructor_(self, x, y=None, coerce=True): sage: K. = FunctionField(QQ) sage: R. = QQ[[]] - sage: f = 1/(x+1) + sage: f = 1/(x+1); f.parent() + Power Series Ring in x over Rational Field sage: K(f) doctest:warning... - DeprecationWarning: Conversion from power series to rational function field is deprecated, use .truncate() instead + DeprecationWarning: Previously conversion from power series to rational function field truncates + instead of gives an approximation. Use .truncate() to recover the old behavior See https://github.com/sagemath/sage/issues/39485 for details. - -x^19 + x^18 - x^17 + x^16 - x^15 + x^14 - x^13 + x^12 - x^11 + x^10 - x^9 + x^8 - x^7 + x^6 - x^5 + x^4 - x^3 + x^2 - x + 1 + 1/(x + 1) sage: f = Frac(R)(1/(x+1)) sage: K(f) 1/(x + 1) @@ -742,8 +761,9 @@ def _element_constructor_(self, x, y=None, coerce=True): from sage.misc.superseded import deprecation deprecation( 39485, - "Conversion from power series to rational function field is deprecated, use .truncate() instead", - ) + "Previously conversion from power series to rational function field truncates " + "instead of gives an approximation. Use .truncate() to recover the old behavior") + x = x.laurent_series() if isinstance(x, LaurentSeries): from sage.rings.infinity import infinity if x.prec() == infinity: From 9cd86e9596a6d996611cd7cc9281ac5a95fda89c Mon Sep 17 00:00:00 2001 From: Release Manager Date: Fri, 21 Feb 2025 21:59:24 +0100 Subject: [PATCH 369/369] Updated SageMath version to 10.6.beta7 --- CITATION.cff | 4 ++-- VERSION.txt | 2 +- build/pkgs/configure/checksums.ini | 4 ++-- build/pkgs/configure/package-version.txt | 2 +- build/pkgs/sage_conf/version_requirements.txt | 2 +- build/pkgs/sage_docbuild/version_requirements.txt | 2 +- build/pkgs/sage_setup/version_requirements.txt | 2 +- build/pkgs/sage_sws2rst/version_requirements.txt | 2 +- build/pkgs/sagelib/version_requirements.txt | 2 +- build/pkgs/sagemath_bliss/version_requirements.txt | 2 +- build/pkgs/sagemath_categories/version_requirements.txt | 2 +- build/pkgs/sagemath_coxeter3/version_requirements.txt | 2 +- build/pkgs/sagemath_environment/version_requirements.txt | 2 +- build/pkgs/sagemath_mcqd/version_requirements.txt | 2 +- build/pkgs/sagemath_meataxe/version_requirements.txt | 2 +- build/pkgs/sagemath_objects/version_requirements.txt | 2 +- build/pkgs/sagemath_repl/version_requirements.txt | 2 +- build/pkgs/sagemath_sirocco/version_requirements.txt | 2 +- build/pkgs/sagemath_tdlib/version_requirements.txt | 2 +- pkgs/sage-conf/VERSION.txt | 2 +- pkgs/sage-conf_conda/VERSION.txt | 2 +- pkgs/sage-conf_pypi/VERSION.txt | 2 +- pkgs/sage-docbuild/VERSION.txt | 2 +- pkgs/sage-setup/VERSION.txt | 2 +- pkgs/sage-sws2rst/VERSION.txt | 2 +- pkgs/sagemath-bliss/VERSION.txt | 2 +- pkgs/sagemath-categories/VERSION.txt | 2 +- pkgs/sagemath-coxeter3/VERSION.txt | 2 +- pkgs/sagemath-environment/VERSION.txt | 2 +- pkgs/sagemath-mcqd/VERSION.txt | 2 +- pkgs/sagemath-meataxe/VERSION.txt | 2 +- pkgs/sagemath-objects/VERSION.txt | 2 +- pkgs/sagemath-repl/VERSION.txt | 2 +- pkgs/sagemath-sirocco/VERSION.txt | 2 +- pkgs/sagemath-tdlib/VERSION.txt | 2 +- src/VERSION.txt | 2 +- src/bin/sage-version.sh | 6 +++--- src/sage/version.py | 6 +++--- 38 files changed, 44 insertions(+), 44 deletions(-) diff --git a/CITATION.cff b/CITATION.cff index 305ef37cbd5..bf07776a356 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -4,8 +4,8 @@ title: SageMath abstract: SageMath is a free open-source mathematics software system. authors: - name: "The SageMath Developers" -version: 10.6.beta6 +version: 10.6.beta7 doi: 10.5281/zenodo.8042260 -date-released: 2025-02-10 +date-released: 2025-02-21 repository-code: "https://github.com/sagemath/sage" url: "https://www.sagemath.org/" diff --git a/VERSION.txt b/VERSION.txt index 0dec762feaa..c20b0627cda 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -SageMath version 10.6.beta6, Release Date: 2025-02-10 +SageMath version 10.6.beta7, Release Date: 2025-02-21 diff --git a/build/pkgs/configure/checksums.ini b/build/pkgs/configure/checksums.ini index ad88cab35b5..67bb44a82c3 100644 --- a/build/pkgs/configure/checksums.ini +++ b/build/pkgs/configure/checksums.ini @@ -1,3 +1,3 @@ tarball=configure-VERSION.tar.gz -sha1=6aea4942af8ac8278125e205c7408aa98f327cd6 -sha256=d088d2ad67c8381bc8eaee6f46ce88f3db07fe557dfe2c50ad2f37e3300c24ff +sha1=d2332c79700273e351ce2b63347e4599e4e2ce43 +sha256=7aa9a2c919f5afc679372a632ad93494563e6ff2218ec76a3422b267cf684030 diff --git a/build/pkgs/configure/package-version.txt b/build/pkgs/configure/package-version.txt index 5589a99c8ee..a3e4b7e2ebc 100644 --- a/build/pkgs/configure/package-version.txt +++ b/build/pkgs/configure/package-version.txt @@ -1 +1 @@ -950a954e4dc44a1b1fe746e052ac1501643ca507 +c2766d1f6250032244ca3880f14c5b424f1bdd83 diff --git a/build/pkgs/sage_conf/version_requirements.txt b/build/pkgs/sage_conf/version_requirements.txt index 77d6c0cb0c5..07a61ab735e 100644 --- a/build/pkgs/sage_conf/version_requirements.txt +++ b/build/pkgs/sage_conf/version_requirements.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sage-conf ~= 10.6b6 +sage-conf ~= 10.6b7 diff --git a/build/pkgs/sage_docbuild/version_requirements.txt b/build/pkgs/sage_docbuild/version_requirements.txt index 50eb65e2172..f0421af21af 100644 --- a/build/pkgs/sage_docbuild/version_requirements.txt +++ b/build/pkgs/sage_docbuild/version_requirements.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sage-docbuild ~= 10.6b6 +sage-docbuild ~= 10.6b7 diff --git a/build/pkgs/sage_setup/version_requirements.txt b/build/pkgs/sage_setup/version_requirements.txt index 06adbdc6df6..48fc6cd26a5 100644 --- a/build/pkgs/sage_setup/version_requirements.txt +++ b/build/pkgs/sage_setup/version_requirements.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sage-setup ~= 10.6b6 +sage-setup ~= 10.6b7 diff --git a/build/pkgs/sage_sws2rst/version_requirements.txt b/build/pkgs/sage_sws2rst/version_requirements.txt index dd05f21376e..22e0bd981fc 100644 --- a/build/pkgs/sage_sws2rst/version_requirements.txt +++ b/build/pkgs/sage_sws2rst/version_requirements.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sage-sws2rst ~= 10.6b6 +sage-sws2rst ~= 10.6b7 diff --git a/build/pkgs/sagelib/version_requirements.txt b/build/pkgs/sagelib/version_requirements.txt index e64a5d0c206..92e731e689a 100644 --- a/build/pkgs/sagelib/version_requirements.txt +++ b/build/pkgs/sagelib/version_requirements.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-standard ~= 10.6b6 +sagemath-standard ~= 10.6b7 diff --git a/build/pkgs/sagemath_bliss/version_requirements.txt b/build/pkgs/sagemath_bliss/version_requirements.txt index d88731f06e9..1dcf8a9322a 100644 --- a/build/pkgs/sagemath_bliss/version_requirements.txt +++ b/build/pkgs/sagemath_bliss/version_requirements.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-bliss ~= 10.6b6 +sagemath-bliss ~= 10.6b7 diff --git a/build/pkgs/sagemath_categories/version_requirements.txt b/build/pkgs/sagemath_categories/version_requirements.txt index 198cb911336..639f01d78ee 100644 --- a/build/pkgs/sagemath_categories/version_requirements.txt +++ b/build/pkgs/sagemath_categories/version_requirements.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-categories ~= 10.6b6 +sagemath-categories ~= 10.6b7 diff --git a/build/pkgs/sagemath_coxeter3/version_requirements.txt b/build/pkgs/sagemath_coxeter3/version_requirements.txt index 7415f70536b..f715f9357e4 100644 --- a/build/pkgs/sagemath_coxeter3/version_requirements.txt +++ b/build/pkgs/sagemath_coxeter3/version_requirements.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-coxeter3 ~= 10.6b6 +sagemath-coxeter3 ~= 10.6b7 diff --git a/build/pkgs/sagemath_environment/version_requirements.txt b/build/pkgs/sagemath_environment/version_requirements.txt index 09106be9ba3..93ecd3e5a14 100644 --- a/build/pkgs/sagemath_environment/version_requirements.txt +++ b/build/pkgs/sagemath_environment/version_requirements.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-environment ~= 10.6b6 +sagemath-environment ~= 10.6b7 diff --git a/build/pkgs/sagemath_mcqd/version_requirements.txt b/build/pkgs/sagemath_mcqd/version_requirements.txt index bff6125dcaf..70273aad838 100644 --- a/build/pkgs/sagemath_mcqd/version_requirements.txt +++ b/build/pkgs/sagemath_mcqd/version_requirements.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-mcqd ~= 10.6b6 +sagemath-mcqd ~= 10.6b7 diff --git a/build/pkgs/sagemath_meataxe/version_requirements.txt b/build/pkgs/sagemath_meataxe/version_requirements.txt index 662a7b7e7f6..8036474a775 100644 --- a/build/pkgs/sagemath_meataxe/version_requirements.txt +++ b/build/pkgs/sagemath_meataxe/version_requirements.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-meataxe ~= 10.6b6 +sagemath-meataxe ~= 10.6b7 diff --git a/build/pkgs/sagemath_objects/version_requirements.txt b/build/pkgs/sagemath_objects/version_requirements.txt index df4dc506909..66992114227 100644 --- a/build/pkgs/sagemath_objects/version_requirements.txt +++ b/build/pkgs/sagemath_objects/version_requirements.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-objects ~= 10.6b6 +sagemath-objects ~= 10.6b7 diff --git a/build/pkgs/sagemath_repl/version_requirements.txt b/build/pkgs/sagemath_repl/version_requirements.txt index 5f906d7000a..22b16b93978 100644 --- a/build/pkgs/sagemath_repl/version_requirements.txt +++ b/build/pkgs/sagemath_repl/version_requirements.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-repl ~= 10.6b6 +sagemath-repl ~= 10.6b7 diff --git a/build/pkgs/sagemath_sirocco/version_requirements.txt b/build/pkgs/sagemath_sirocco/version_requirements.txt index 9077fb7fff0..3dd38fd94ac 100644 --- a/build/pkgs/sagemath_sirocco/version_requirements.txt +++ b/build/pkgs/sagemath_sirocco/version_requirements.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-sirocco ~= 10.6b6 +sagemath-sirocco ~= 10.6b7 diff --git a/build/pkgs/sagemath_tdlib/version_requirements.txt b/build/pkgs/sagemath_tdlib/version_requirements.txt index 37d47b62c5f..15ac9d50e66 100644 --- a/build/pkgs/sagemath_tdlib/version_requirements.txt +++ b/build/pkgs/sagemath_tdlib/version_requirements.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-tdlib ~= 10.6b6 +sagemath-tdlib ~= 10.6b7 diff --git a/pkgs/sage-conf/VERSION.txt b/pkgs/sage-conf/VERSION.txt index 8b3698135db..51a36a91c8a 100644 --- a/pkgs/sage-conf/VERSION.txt +++ b/pkgs/sage-conf/VERSION.txt @@ -1 +1 @@ -10.6.beta6 +10.6.beta7 diff --git a/pkgs/sage-conf_conda/VERSION.txt b/pkgs/sage-conf_conda/VERSION.txt index 8b3698135db..51a36a91c8a 100644 --- a/pkgs/sage-conf_conda/VERSION.txt +++ b/pkgs/sage-conf_conda/VERSION.txt @@ -1 +1 @@ -10.6.beta6 +10.6.beta7 diff --git a/pkgs/sage-conf_pypi/VERSION.txt b/pkgs/sage-conf_pypi/VERSION.txt index 8b3698135db..51a36a91c8a 100644 --- a/pkgs/sage-conf_pypi/VERSION.txt +++ b/pkgs/sage-conf_pypi/VERSION.txt @@ -1 +1 @@ -10.6.beta6 +10.6.beta7 diff --git a/pkgs/sage-docbuild/VERSION.txt b/pkgs/sage-docbuild/VERSION.txt index 8b3698135db..51a36a91c8a 100644 --- a/pkgs/sage-docbuild/VERSION.txt +++ b/pkgs/sage-docbuild/VERSION.txt @@ -1 +1 @@ -10.6.beta6 +10.6.beta7 diff --git a/pkgs/sage-setup/VERSION.txt b/pkgs/sage-setup/VERSION.txt index 8b3698135db..51a36a91c8a 100644 --- a/pkgs/sage-setup/VERSION.txt +++ b/pkgs/sage-setup/VERSION.txt @@ -1 +1 @@ -10.6.beta6 +10.6.beta7 diff --git a/pkgs/sage-sws2rst/VERSION.txt b/pkgs/sage-sws2rst/VERSION.txt index 8b3698135db..51a36a91c8a 100644 --- a/pkgs/sage-sws2rst/VERSION.txt +++ b/pkgs/sage-sws2rst/VERSION.txt @@ -1 +1 @@ -10.6.beta6 +10.6.beta7 diff --git a/pkgs/sagemath-bliss/VERSION.txt b/pkgs/sagemath-bliss/VERSION.txt index 8b3698135db..51a36a91c8a 100644 --- a/pkgs/sagemath-bliss/VERSION.txt +++ b/pkgs/sagemath-bliss/VERSION.txt @@ -1 +1 @@ -10.6.beta6 +10.6.beta7 diff --git a/pkgs/sagemath-categories/VERSION.txt b/pkgs/sagemath-categories/VERSION.txt index 8b3698135db..51a36a91c8a 100644 --- a/pkgs/sagemath-categories/VERSION.txt +++ b/pkgs/sagemath-categories/VERSION.txt @@ -1 +1 @@ -10.6.beta6 +10.6.beta7 diff --git a/pkgs/sagemath-coxeter3/VERSION.txt b/pkgs/sagemath-coxeter3/VERSION.txt index 8b3698135db..51a36a91c8a 100644 --- a/pkgs/sagemath-coxeter3/VERSION.txt +++ b/pkgs/sagemath-coxeter3/VERSION.txt @@ -1 +1 @@ -10.6.beta6 +10.6.beta7 diff --git a/pkgs/sagemath-environment/VERSION.txt b/pkgs/sagemath-environment/VERSION.txt index 8b3698135db..51a36a91c8a 100644 --- a/pkgs/sagemath-environment/VERSION.txt +++ b/pkgs/sagemath-environment/VERSION.txt @@ -1 +1 @@ -10.6.beta6 +10.6.beta7 diff --git a/pkgs/sagemath-mcqd/VERSION.txt b/pkgs/sagemath-mcqd/VERSION.txt index 8b3698135db..51a36a91c8a 100644 --- a/pkgs/sagemath-mcqd/VERSION.txt +++ b/pkgs/sagemath-mcqd/VERSION.txt @@ -1 +1 @@ -10.6.beta6 +10.6.beta7 diff --git a/pkgs/sagemath-meataxe/VERSION.txt b/pkgs/sagemath-meataxe/VERSION.txt index 8b3698135db..51a36a91c8a 100644 --- a/pkgs/sagemath-meataxe/VERSION.txt +++ b/pkgs/sagemath-meataxe/VERSION.txt @@ -1 +1 @@ -10.6.beta6 +10.6.beta7 diff --git a/pkgs/sagemath-objects/VERSION.txt b/pkgs/sagemath-objects/VERSION.txt index 8b3698135db..51a36a91c8a 100644 --- a/pkgs/sagemath-objects/VERSION.txt +++ b/pkgs/sagemath-objects/VERSION.txt @@ -1 +1 @@ -10.6.beta6 +10.6.beta7 diff --git a/pkgs/sagemath-repl/VERSION.txt b/pkgs/sagemath-repl/VERSION.txt index 8b3698135db..51a36a91c8a 100644 --- a/pkgs/sagemath-repl/VERSION.txt +++ b/pkgs/sagemath-repl/VERSION.txt @@ -1 +1 @@ -10.6.beta6 +10.6.beta7 diff --git a/pkgs/sagemath-sirocco/VERSION.txt b/pkgs/sagemath-sirocco/VERSION.txt index 8b3698135db..51a36a91c8a 100644 --- a/pkgs/sagemath-sirocco/VERSION.txt +++ b/pkgs/sagemath-sirocco/VERSION.txt @@ -1 +1 @@ -10.6.beta6 +10.6.beta7 diff --git a/pkgs/sagemath-tdlib/VERSION.txt b/pkgs/sagemath-tdlib/VERSION.txt index 8b3698135db..51a36a91c8a 100644 --- a/pkgs/sagemath-tdlib/VERSION.txt +++ b/pkgs/sagemath-tdlib/VERSION.txt @@ -1 +1 @@ -10.6.beta6 +10.6.beta7 diff --git a/src/VERSION.txt b/src/VERSION.txt index 8b3698135db..51a36a91c8a 100644 --- a/src/VERSION.txt +++ b/src/VERSION.txt @@ -1 +1 @@ -10.6.beta6 +10.6.beta7 diff --git a/src/bin/sage-version.sh b/src/bin/sage-version.sh index d1e2aa14731..2dea11b2d1e 100644 --- a/src/bin/sage-version.sh +++ b/src/bin/sage-version.sh @@ -4,6 +4,6 @@ # which stops "setup.py develop" from rewriting it as a Python file. : # This file is auto-generated by the sage-update-version script, do not edit! -SAGE_VERSION='10.6.beta6' -SAGE_RELEASE_DATE='2025-02-10' -SAGE_VERSION_BANNER='SageMath version 10.6.beta6, Release Date: 2025-02-10' +SAGE_VERSION='10.6.beta7' +SAGE_RELEASE_DATE='2025-02-21' +SAGE_VERSION_BANNER='SageMath version 10.6.beta7, Release Date: 2025-02-21' diff --git a/src/sage/version.py b/src/sage/version.py index 6291f1b503f..37a2f5f5fba 100644 --- a/src/sage/version.py +++ b/src/sage/version.py @@ -1,5 +1,5 @@ # Sage version information for Python scripts # This file is auto-generated by the sage-update-version script, do not edit! -version = '10.6.beta6' -date = '2025-02-10' -banner = 'SageMath version 10.6.beta6, Release Date: 2025-02-10' +version = '10.6.beta7' +date = '2025-02-21' +banner = 'SageMath version 10.6.beta7, Release Date: 2025-02-21'