From 08db50cfdeae1aabe6062798af5cb321aa156f11 Mon Sep 17 00:00:00 2001 From: Peter Bruin Date: Thu, 18 Jan 2024 13:49:03 +0100 Subject: [PATCH 001/518] Move some code from Brandt modules to quaternion algebras --- .../algebras/quatalg/quaternion_algebra.py | 148 +++++++++++++++++- src/sage/modular/quatalg/brandt.py | 103 ++++-------- 2 files changed, 172 insertions(+), 79 deletions(-) diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index 5a615b43978..ef16d1e491c 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -52,6 +52,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.polynomial.polynomial_ring_constructor import PolynomialRing from sage.rings.power_series_ring import PowerSeriesRing from sage.structure.category_object import normalize_names from sage.structure.parent import Parent @@ -898,6 +899,76 @@ def maximal_order(self, take_shortcuts=True): e_new = basis_for_quaternion_lattice(list(R.basis()) + e_new_gens, reverse=True) return self.quaternion_order(e_new) + def order_with_level(self, level): + """ + Return an order in this quaternion algebra with given level. + + INPUT: + + - ``level`` -- positive integer + + Currently this is only implemented when the base field is the + rational numbers and the level is divisible by at most one + power of a prime that ramifies in this quaternion algebra. + + EXAMPLES:: + + sage: A. = QuaternionAlgebra(5) + sage: level = 2 * 5 * 17 + sage: O = A.order_with_level(level); O + Order of Quaternion Algebra (-2, -5) with base ring Rational Field with basis (1/2 + 1/2*j + 7/2*k, 1/2*i + 19/2*k, j + 7*k, 17*k) + + Check that the order has the right index in the maximal order:: + + sage: L = O.free_module() + sage: N = A.maximal_order().free_module() + sage: L.index_in(N) == level / 5 + True + """ + if self.base_ring() is not QQ: + raise NotImplementedError("base field must be rational numbers") + + if len(self.ramified_primes()) > 1: + raise NotImplementedError("currently this algorithm only works when the quaternion algebra is only ramified at one finite prime") + + # The algorithm we use is similar to that in Magma (by David Kohel). + level = abs(level) + N = self.discriminant() + N1 = gcd(level, N) + M1 = level // N1 + + O = self.maximal_order() + # if N1 != 1: + # # we do not know why magma does the following, so we do not do it. + # for p in self.ramified_primes(): + # if not (level % p**2): + # raise NotImplementedError("Currently sage can only compute orders whose level is divisible by at most one power of any prime that ramifies in the quaternion algebra") + + # P = O._left_ideal_basis([N1] + [x * y - y * x + # for x in self.basis() + # for y in self.basis()]) + # O = self.quaternion_order(P) + + fact = factor(M1) + B = O.basis() + + for (p, r) in fact: + a = int(-p) // 2 + for v in GF(p)**4: + x = sum([int(v[i] + a) * B[i] for i in range(4)]) + D = x.reduced_trace()**2 - 4 * x.reduced_norm() + # x = O.random_element((-p/2).floor(), (p/2).ceil()) + if kronecker_symbol(D, p) == 1: + break + X = PolynomialRing(GF(p), 'x').gen() + a = ZZ((X**2 - ZZ(x.reduced_trace()) * X + ZZ(x.reduced_norm())).roots()[0][0]) + I = O._left_ideal_basis([p**r, (x - a)**r]) + O = O._right_order_from_ideal_basis(I) + # right_order returns the RightOrder of I inside O, so we + # do not need to do another intersection + + return O + def invariants(self): """ Return the structural invariants `a`, `b` of this quaternion @@ -1758,6 +1829,75 @@ def discriminant(self): return (MatrixSpace(QQ, 4, 4)(L)).determinant().sqrt() + def _left_ideal_basis(self, gens): + """ + Return a basis for the left ideal of ``self`` with given generators. + + INPUT: + + - ``gens`` -- list of elements of ``self`` + + OUTPUT: + + A list of four elements of ``self``. + + EXAMPLES:: + + sage: A. = QuaternionAlgebra(-17, -3) + sage: A.maximal_order()._left_ideal_basis([i + j, i - j, 2*k, A(3)]) + [1/2 + 1/6*j + 2/3*k, 1/2*i + 1/2*k, 1/3*j + 1/3*k, k] + sage: A.maximal_order()._left_ideal_basis([3*(i + j), 3*(i - j), 6*k, A(3)]) + [3/2 + 1/2*j + 2*k, 3/2*i + 3/2*k, j + k, 3*k] + """ + return basis_for_quaternion_lattice([b * g for b in self.basis() for g in gens], reverse=False) + + def _right_order_from_ideal_basis(self, basis): + """ + Given a basis for a left ideal `I`, return the right order in + ``self`` of elements `x` such that `I x` is contained in `I`. + + INPUT: + + - ``basis`` -- basis for an ideal `I` + + EXAMPLES: + + sage: A. = QuaternionAlgebra(17) + sage: O = A.maximal_order() + sage: basis = O._left_ideal_basis([1]); basis + [1/2 + 1/6*i + 1/3*k, 1/3*i + 2/3*k, 1/2*j + 1/2*k, k] + sage: O._right_order_from_ideal_basis(basis) + Order of Quaternion Algebra (-3, -17) with base ring Rational Field with basis (1/2 + 1/6*i + 1/3*k, 1/3*i + 2/3*k, 1/2*j + 1/2*k, k) + + sage: basis = O._left_ideal_basis([i*j - j]); basis + [17 + 17/3*i + 4/3*k, 34/3*i + 2/3*k, j + k, 2*k] + sage: O._right_order_from_ideal_basis(basis) + Order of Quaternion Algebra (-3, -17) with base ring Rational Field with basis (1/2 + 1/6*i + 1/3*k, 1/3*i + 2/3*k, 1/2*j + 1/2*k, k) + """ + # Compute matrix of multiplication by each element of the basis. + B = self.basis() + Z = self.quaternion_algebra() + M = MatrixSpace(QQ, 4) + + # I = matrix with rows the given basis for I + I = M([list(f) for f in basis]) + + # psi = matrix of right multiplication on each basis element + psi = [M([list(f * x) for x in Z.basis()]) for f in basis] + + # invert them + psi_inv = [x**(-1) for x in psi] + + # apply the four inverses to I + W = [I * x for x in psi_inv] + + # The right order is the intersection of the row span of the W with the row span of B. + X = M([list(b) for b in B]).row_module(ZZ) + for A in W: + X = X.intersection(A.row_module(ZZ)) + C = [Z(list(b)) for b in X.basis()] + return Z.quaternion_order(C) + def left_ideal(self, gens, check=True, *, is_basis=False): r""" Return the left ideal of this order generated by the given generators. @@ -2155,14 +2295,16 @@ def quaternion_algebra(self): def _compute_order(self, side='left'): r""" Used internally to compute either the left or right order - associated to an ideal in a quaternion algebra. If - action='right', compute the left order, and if action='left' - compute the right order. + associated to an ideal in a quaternion algebra. INPUT: - ``side`` -- 'left' or 'right' + OUTPUT: + + The left order if side='left'; the right order if side='right'. + EXAMPLES:: sage: R. = QuaternionAlgebra(-1,-11) diff --git a/src/sage/modular/quatalg/brandt.py b/src/sage/modular/quatalg/brandt.py index 1c74d953f6d..05f201d915c 100644 --- a/src/sage/modular/quatalg/brandt.py +++ b/src/sage/modular/quatalg/brandt.py @@ -202,7 +202,7 @@ # https://www.gnu.org/licenses/ # **************************************************************************** -from sage.arith.misc import gcd, factor, prime_divisors, kronecker, next_prime +from sage.arith.misc import gcd, kronecker, next_prime, prime_divisors from sage.matrix.constructor import matrix from sage.matrix.matrix_space import MatrixSpace from sage.misc.cachefunc import cached_method @@ -216,7 +216,6 @@ from sage.rings.finite_rings.finite_field_constructor import GF from sage.rings.integer import Integer from sage.rings.integer_ring import ZZ -from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.rings.power_series_ring import PowerSeriesRing from sage.rings.rational_field import QQ from sage.rings.ring import CommutativeRing @@ -370,13 +369,18 @@ def maximal_order(A): EXAMPLES:: sage: A = BrandtModule(17).quaternion_algebra() + sage: sage.modular.quatalg.brandt.maximal_order(A) + doctest:...: DeprecationWarning: The function maximal_order() is deprecated, use the maximal_order() method of quaternion algebras + See https://github.com/sagemath/sage/issues/37089 for details. Order of Quaternion Algebra (-3, -17) with base ring Rational Field with basis (1/2 + 1/2*i, 1/2*j - 1/2*k, -1/3*i + 1/3*k, -k) sage: A = QuaternionAlgebra(17,names='i,j,k') sage: A.maximal_order() Order of Quaternion Algebra (-3, -17) with base ring Rational Field with basis (1/2 + 1/2*i, 1/2*j - 1/2*k, -1/3*i + 1/3*k, -k) """ + from sage.misc.superseded import deprecation + deprecation(37089, "The function maximal_order() is deprecated, use the maximal_order() method of quaternion algebras") return A.maximal_order() @@ -397,11 +401,15 @@ def basis_for_left_ideal(R, gens): sage: B = BrandtModule(17); A = B.quaternion_algebra(); i,j,k = A.gens() sage: sage.modular.quatalg.brandt.basis_for_left_ideal(B.maximal_order(), [i+j,i-j,2*k,A(3)]) + doctest:...: DeprecationWarning: The function basis_for_left_ideal() is deprecated, use the _left_ideal_basis() method of quaternion algebras + See https://github.com/sagemath/sage/issues/37089 for details. [1/2 + 1/6*i + 1/3*k, 1/3*i + 2/3*k, 1/2*j + 1/2*k, k] sage: sage.modular.quatalg.brandt.basis_for_left_ideal(B.maximal_order(), [3*(i+j),3*(i-j),6*k,A(3)]) [3/2 + 1/2*i + k, i + 2*k, 3/2*j + 3/2*k, 3*k] """ - return basis_for_quaternion_lattice([b * g for b in R.basis() for g in gens], reverse=False) + from sage.misc.superseded import deprecation + deprecation(37089, "The function basis_for_left_ideal() is deprecated, use the _left_ideal_basis() method of quaternion algebras") + return R._left_ideal_basis(gens) def right_order(R, basis): @@ -422,40 +430,22 @@ def right_order(R, basis): We do a consistency check with the ideal equal to a maximal order:: - sage: B = BrandtModule(17); basis = sage.modular.quatalg.brandt.basis_for_left_ideal(B.maximal_order(), B.maximal_order().basis()) + sage: B = BrandtModule(17); basis = B.maximal_order()._left_ideal_basis([1]) sage: sage.modular.quatalg.brandt.right_order(B.maximal_order(), basis) + doctest:...: DeprecationWarning: The function right_order() is deprecated, use the _right_order_from_ideal_basis() method of quaternion algebras + See https://github.com/sagemath/sage/issues/37089 for details. Order of Quaternion Algebra (-3, -17) with base ring Rational Field with basis (1/2 + 1/6*i + 1/3*k, 1/3*i + 2/3*k, 1/2*j + 1/2*k, k) sage: basis [1/2 + 1/6*i + 1/3*k, 1/3*i + 2/3*k, 1/2*j + 1/2*k, k] sage: B = BrandtModule(17); A = B.quaternion_algebra(); i,j,k = A.gens() - sage: basis = sage.modular.quatalg.brandt.basis_for_left_ideal(B.maximal_order(), [i*j-j]) + sage: basis = B.maximal_order()._left_ideal_basis([i*j - j]) sage: sage.modular.quatalg.brandt.right_order(B.maximal_order(), basis) Order of Quaternion Algebra (-3, -17) with base ring Rational Field with basis (1/2 + 1/6*i + 1/3*k, 1/3*i + 2/3*k, 1/2*j + 1/2*k, k) """ - # Compute matrix of multiplication by each element of the basis. - B = R.basis() - Z = R.quaternion_algebra() - M = MatrixSpace(QQ, 4) - - # I = matrix with rows the given basis for I - I = M([list(f) for f in basis]) - - # psi = matrix of right multiplication on each basis element - psi = [M([list(f * x) for x in Z.basis()]) for f in basis] - - # invert them - psi_inv = [x**(-1) for x in psi] - - # apply the four inverses to I - W = [I * x for x in psi_inv] - - # The right order is the intersection of the row span of the W with the row span of B. - X = M([list(b) for b in B]).row_module(ZZ) - for A in W: - X = X.intersection(A.row_module(ZZ)) - C = [Z(list(b)) for b in X.basis()] - return Z.quaternion_order(C) + from sage.misc.superseded import deprecation + deprecation(37089, "The function right_order() is deprecated, use the _right_order_from_ideal_basis() method of quaternion algebras") + return R._right_order_from_ideal_basis(basis) def quaternion_order_with_given_level(A, level): @@ -476,56 +466,17 @@ def quaternion_order_with_given_level(A, level): sage: A. = QuaternionAlgebra(5) sage: level = 2 * 5 * 17 sage: O = quaternion_order_with_given_level(A, level) - sage: M = maximal_order(A) + doctest:...: DeprecationWarning: The function quaternion_order_with_given_level() is deprecated, use the order_with_level() method of quaternion algebras + See https://github.com/sagemath/sage/issues/37089 for details. + sage: M = A.maximal_order() sage: L = O.free_module() sage: N = M.free_module() sage: L.index_in(N) == level/5 #check that the order has the right index in the maximal order True """ - if A.base_ring() is not QQ: - raise NotImplementedError("base field must be rational numbers") - - if len(A.ramified_primes()) > 1: - raise NotImplementedError("Currently this algorithm only works when the quaternion algebra is only ramified at one finite prime.") - - # (The algorithm we use is similar to that in Magma (by David Kohel).) - # in the following magma code, M denotes the level - level = abs(level) - N = A.discriminant() - N1 = gcd(level, N) - M1 = level // N1 - - O = maximal_order(A) - # if N1 != 1: - # # we do not know why magma does the following, so we do not do it. - # for p in A.ramified_primes(): - # if not (level % p**2): - # raise NotImplementedError("Currently sage can only compute orders whose level is divisible by at most one power of any prime that ramifies in the quaternion algebra") - - # P = basis_for_left_ideal(O, [N1] + [x * y - y * x - # for x in A.basis() - # for y in A.basis()]) - # O = A.quaternion_order(P) - - fact = factor(M1) - B = O.basis() - - for (p, r) in fact: - a = int(-p) // 2 - for v in GF(p)**4: - x = sum([int(v[i] + a) * B[i] for i in range(4)]) - D = x.reduced_trace()**2 - 4 * x.reduced_norm() - # x = O.random_element((-p/2).floor(), (p/2).ceil()) - if kronecker(D, p) == 1: - break - X = PolynomialRing(GF(p), 'x').gen() - a = ZZ((X**2 - ZZ(x.reduced_trace()) * X + ZZ(x.reduced_norm())).roots()[0][0]) - I = basis_for_left_ideal(O, [p**r, (x - a)**r]) - O = right_order(O, I) - # right_order returns the RightOrder of I inside O, so we - # do not need to do another intersection - - return O + from sage.misc.superseded import deprecation + deprecation(37089, "The function quaternion_order_with_given_level() is deprecated, use the order_with_level() method of quaternion algebras") + return A.order_with_level(level) class BrandtSubmodule(HeckeSubmodule): @@ -809,7 +760,6 @@ def quaternion_algebra(self): """ return QuaternionAlgebra(self.N()) - @cached_method def maximal_order(self): """ Return a maximal order in the quaternion algebra associated to this Brandt module. @@ -821,7 +771,7 @@ def maximal_order(self): sage: BrandtModule(17).maximal_order() is BrandtModule(17).maximal_order() True """ - return maximal_order(self.quaternion_algebra()) + return self.quaternion_algebra().maximal_order() @cached_method def order_of_level_N(self): @@ -838,7 +788,7 @@ def order_of_level_N(self): sage: BrandtModule(7,3*17).order_of_level_N() Order of Quaternion Algebra (-1, -7) with base ring Rational Field with basis (1/2 + 1/2*j + 35*k, 1/2*i + 65/2*k, j + 19*k, 51*k) """ - return quaternion_order_with_given_level(self.quaternion_algebra(), self.level()) + return self.quaternion_algebra().order_with_level(self.level()) def cyclic_submodules(self, I, p): """ @@ -1328,6 +1278,7 @@ def right_ideals(self, B=None): sage: prod(not Is[i].is_equivalent(Is[j]) for i in range(n) for j in range(i)) 1 """ + # TODO: move this code to orders, along with cyclic_submodules() p = self._smallest_good_prime() R = self.order_of_level_N() I = R.unit_ideal() From e974bd133efd06c94d7d59184356b11635acf058 Mon Sep 17 00:00:00 2001 From: Peter Bruin Date: Thu, 18 Jan 2024 13:57:56 +0100 Subject: [PATCH 002/518] correct pull request number --- src/sage/modular/quatalg/brandt.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/sage/modular/quatalg/brandt.py b/src/sage/modular/quatalg/brandt.py index 05f201d915c..8a0dcb2dbbc 100644 --- a/src/sage/modular/quatalg/brandt.py +++ b/src/sage/modular/quatalg/brandt.py @@ -372,7 +372,7 @@ def maximal_order(A): sage: sage.modular.quatalg.brandt.maximal_order(A) doctest:...: DeprecationWarning: The function maximal_order() is deprecated, use the maximal_order() method of quaternion algebras - See https://github.com/sagemath/sage/issues/37089 for details. + See https://github.com/sagemath/sage/issues/37090 for details. Order of Quaternion Algebra (-3, -17) with base ring Rational Field with basis (1/2 + 1/2*i, 1/2*j - 1/2*k, -1/3*i + 1/3*k, -k) sage: A = QuaternionAlgebra(17,names='i,j,k') @@ -380,7 +380,7 @@ def maximal_order(A): Order of Quaternion Algebra (-3, -17) with base ring Rational Field with basis (1/2 + 1/2*i, 1/2*j - 1/2*k, -1/3*i + 1/3*k, -k) """ from sage.misc.superseded import deprecation - deprecation(37089, "The function maximal_order() is deprecated, use the maximal_order() method of quaternion algebras") + deprecation(37090, "The function maximal_order() is deprecated, use the maximal_order() method of quaternion algebras") return A.maximal_order() @@ -402,13 +402,13 @@ def basis_for_left_ideal(R, gens): sage: B = BrandtModule(17); A = B.quaternion_algebra(); i,j,k = A.gens() sage: sage.modular.quatalg.brandt.basis_for_left_ideal(B.maximal_order(), [i+j,i-j,2*k,A(3)]) doctest:...: DeprecationWarning: The function basis_for_left_ideal() is deprecated, use the _left_ideal_basis() method of quaternion algebras - See https://github.com/sagemath/sage/issues/37089 for details. + See https://github.com/sagemath/sage/issues/37090 for details. [1/2 + 1/6*i + 1/3*k, 1/3*i + 2/3*k, 1/2*j + 1/2*k, k] sage: sage.modular.quatalg.brandt.basis_for_left_ideal(B.maximal_order(), [3*(i+j),3*(i-j),6*k,A(3)]) [3/2 + 1/2*i + k, i + 2*k, 3/2*j + 3/2*k, 3*k] """ from sage.misc.superseded import deprecation - deprecation(37089, "The function basis_for_left_ideal() is deprecated, use the _left_ideal_basis() method of quaternion algebras") + deprecation(37090, "The function basis_for_left_ideal() is deprecated, use the _left_ideal_basis() method of quaternion algebras") return R._left_ideal_basis(gens) @@ -433,7 +433,7 @@ def right_order(R, basis): sage: B = BrandtModule(17); basis = B.maximal_order()._left_ideal_basis([1]) sage: sage.modular.quatalg.brandt.right_order(B.maximal_order(), basis) doctest:...: DeprecationWarning: The function right_order() is deprecated, use the _right_order_from_ideal_basis() method of quaternion algebras - See https://github.com/sagemath/sage/issues/37089 for details. + See https://github.com/sagemath/sage/issues/37090 for details. Order of Quaternion Algebra (-3, -17) with base ring Rational Field with basis (1/2 + 1/6*i + 1/3*k, 1/3*i + 2/3*k, 1/2*j + 1/2*k, k) sage: basis [1/2 + 1/6*i + 1/3*k, 1/3*i + 2/3*k, 1/2*j + 1/2*k, k] @@ -444,7 +444,7 @@ def right_order(R, basis): Order of Quaternion Algebra (-3, -17) with base ring Rational Field with basis (1/2 + 1/6*i + 1/3*k, 1/3*i + 2/3*k, 1/2*j + 1/2*k, k) """ from sage.misc.superseded import deprecation - deprecation(37089, "The function right_order() is deprecated, use the _right_order_from_ideal_basis() method of quaternion algebras") + deprecation(37090, "The function right_order() is deprecated, use the _right_order_from_ideal_basis() method of quaternion algebras") return R._right_order_from_ideal_basis(basis) @@ -467,7 +467,7 @@ def quaternion_order_with_given_level(A, level): sage: level = 2 * 5 * 17 sage: O = quaternion_order_with_given_level(A, level) doctest:...: DeprecationWarning: The function quaternion_order_with_given_level() is deprecated, use the order_with_level() method of quaternion algebras - See https://github.com/sagemath/sage/issues/37089 for details. + See https://github.com/sagemath/sage/issues/37090 for details. sage: M = A.maximal_order() sage: L = O.free_module() sage: N = M.free_module() @@ -475,7 +475,7 @@ def quaternion_order_with_given_level(A, level): True """ from sage.misc.superseded import deprecation - deprecation(37089, "The function quaternion_order_with_given_level() is deprecated, use the order_with_level() method of quaternion algebras") + deprecation(37090, "The function quaternion_order_with_given_level() is deprecated, use the order_with_level() method of quaternion algebras") return A.order_with_level(level) From 8b381646ff533207daf250ef0b87ba6fbf5e1b5f Mon Sep 17 00:00:00 2001 From: Lorenz Panny Date: Thu, 18 Jan 2024 23:05:30 +0100 Subject: [PATCH 003/518] throw ValueError instead of TypeError when logarithm doesn't exist ...because attempting to run this with unhashable elements throws the same exception, which can cause confusion in higher-level algorithms. --- .../additive_abelian/additive_abelian_wrapper.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/sage/groups/additive_abelian/additive_abelian_wrapper.py b/src/sage/groups/additive_abelian/additive_abelian_wrapper.py index 2a7496cb215..9cb8d958c61 100644 --- a/src/sage/groups/additive_abelian/additive_abelian_wrapper.py +++ b/src/sage/groups/additive_abelian/additive_abelian_wrapper.py @@ -365,7 +365,7 @@ def discrete_log(self, x, gens=None): sage: G.discrete_log(V([6, 4])) Traceback (most recent call last): ... - TypeError: Not in group + ValueError: not in group :: @@ -617,7 +617,7 @@ def _base(j, k, c): if key in tab: return tab[key] + vector(y) - raise TypeError('Not in group') + raise ValueError('not in group') def _rec(j, k, c): @@ -744,7 +744,7 @@ def _expand_basis_pgroup(p, alphas, vals, beta, h, rel): beta_q *= p try: e = _discrete_log_pgroup(p, vals, alphas, -beta_q) - except TypeError: + except ValueError: continue # step 6 _expand_basis_pgroup(p, alphas, vals, beta, h, list(e) + [p**v]) @@ -815,7 +815,7 @@ def basis_from_generators(gens, ords=None): beta, ord_beta = pgens.pop() try: dlog = _discrete_log_pgroup(p, vals, alphas, beta) - except TypeError: + except ValueError: pass else: continue @@ -828,7 +828,7 @@ def basis_from_generators(gens, ords=None): # assert beta_q == beta * p**v try: e = _discrete_log_pgroup(p, vals, alphas, -beta_q) - except TypeError: + except ValueError: continue _expand_basis_pgroup(p, alphas, vals, beta, val_beta, list(e) + [p**v]) # assert all(a.order() == p**v for a,v in zip(alphas, vals)) From 7ffd84827154df5d9dcc3e691dfb0bc7a7975f92 Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Fri, 26 Jan 2024 17:13:28 +0000 Subject: [PATCH 004/518] fix mod 0 polyring --- src/sage/libs/ntl/ntl_ZZ_pEX_linkage.pxi | 13 +++++---- .../rings/polynomial/polynomial_template.pxi | 28 ++++++++++++++++++- 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/src/sage/libs/ntl/ntl_ZZ_pEX_linkage.pxi b/src/sage/libs/ntl/ntl_ZZ_pEX_linkage.pxi index 36c87c75efa..4cf76fce96e 100644 --- a/src/sage/libs/ntl/ntl_ZZ_pEX_linkage.pxi +++ b/src/sage/libs/ntl/ntl_ZZ_pEX_linkage.pxi @@ -367,12 +367,15 @@ cdef inline int celement_pow(ZZ_pEX_c* res, ZZ_pEX_c* x, long e, ZZ_pEX_c *modul sig_off() else: if ZZ_pEX_deg(modulus[0]) == 1: - ZZ_pEX_rem(y, x[0], modulus[0]) - sig_on() - ZZ_pEX_power(res[0], y, e) - sig_off() - return 0 + ZZ_pEX_rem(y, x[0], modulus[0]) + sig_on() + ZZ_pEX_power(res[0], y, e) + sig_off() + return 0 + # This was causing a crash, but it's fixed "upstream" + # sig_on() ZZ_pEX_Modulus_build(mod, modulus[0]) + # sig_off() if ZZ_pEX_deg(x[0]) < ZZ_pEX_deg(modulus[0]): sig_on() ZZ_pEX_PowerMod_pre(res[0], x[0], e, mod) diff --git a/src/sage/rings/polynomial/polynomial_template.pxi b/src/sage/rings/polynomial/polynomial_template.pxi index 7c53f107180..61143f071db 100644 --- a/src/sage/rings/polynomial/polynomial_template.pxi +++ b/src/sage/rings/polynomial/polynomial_template.pxi @@ -569,7 +569,6 @@ cdef class Polynomial_template(Polynomial): """ EXAMPLES:: - sage: P. = GF(2)[] sage: P. = GF(2)[] sage: x^1000 x^1000 @@ -583,6 +582,28 @@ cdef class Polynomial_template(Polynomial): x^9 + x^8 + x^7 + x^5 + x^3 sage: pow(f, 2, h) x^9 + x^8 + x^7 + x^5 + x^3 + + TESTS: + + Ensure modulo `0` and modulo `1` does not crash (:issue:`37169`):: + + sage: R. = GF(2)[] + sage: pow(x + 1, 2, R.zero()) + Traceback (most recent call last): + ... + ZeroDivisionError: modulus must be nonzero + sage: pow(x + 1, 2, R.one()) + 0 + + :: + + sage: R. = GF(2^8)[] + sage: pow(x + 1, 2, R.zero()) + Traceback (most recent call last): + ... + ZeroDivisionError: modulus must be nonzero + sage: pow(x + 1, 2, R.one()) + 0 """ if not isinstance(self, Polynomial_template): raise NotImplementedError("%s^%s not defined."%(ee,self)) @@ -615,6 +636,11 @@ cdef class Polynomial_template(Polynomial): else: if parent is not (modulus)._parent and parent != (modulus)._parent: modulus = parent.coerce(modulus) + # I tried using `celement_is_zero` but it errors because `modulus` is a Python object + if modulus.is_zero(): + raise ZeroDivisionError("modulus must be nonzero") + if modulus.is_one(): + return parent.zero() celement_pow(&r.x, &(self).x, e, &(modulus).x, (self)._cparent) #assert(r._parent(pari(self)**ee) == r) From 320ebbe066bcd4c5463c4771e9bad50f96f23d93 Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Fri, 26 Jan 2024 19:10:04 +0000 Subject: [PATCH 005/518] fix ring SIGABRT --- src/sage/rings/polynomial/polynomial_zmod_flint.pyx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/sage/rings/polynomial/polynomial_zmod_flint.pyx b/src/sage/rings/polynomial/polynomial_zmod_flint.pyx index 3a66198d568..815b9758176 100644 --- a/src/sage/rings/polynomial/polynomial_zmod_flint.pyx +++ b/src/sage/rings/polynomial/polynomial_zmod_flint.pyx @@ -554,7 +554,11 @@ cdef class Polynomial_zmod_flint(Polynomial_template): while nmod_poly_length(&t1.x) != 0 and n_deg < nmod_poly_degree(&t1.x): q = self._new() r1 = self._new() + + sig_on() nmod_poly_divrem(&q.x, &r1.x, &s1.x, &t1.x) + sig_off() + r0 = s0 - q*t0 s0 = t0 s1 = t1 From 5ead46930bf50dc9c9d7c3dc02bea354a254fc84 Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Fri, 26 Jan 2024 19:14:34 +0000 Subject: [PATCH 006/518] Revert "throw ValueError instead of TypeError when logarithm doesn't exist" This reverts commit 8b381646ff533207daf250ef0b87ba6fbf5e1b5f, which is from another PR. --- .../additive_abelian/additive_abelian_wrapper.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/sage/groups/additive_abelian/additive_abelian_wrapper.py b/src/sage/groups/additive_abelian/additive_abelian_wrapper.py index 9cb8d958c61..2a7496cb215 100644 --- a/src/sage/groups/additive_abelian/additive_abelian_wrapper.py +++ b/src/sage/groups/additive_abelian/additive_abelian_wrapper.py @@ -365,7 +365,7 @@ def discrete_log(self, x, gens=None): sage: G.discrete_log(V([6, 4])) Traceback (most recent call last): ... - ValueError: not in group + TypeError: Not in group :: @@ -617,7 +617,7 @@ def _base(j, k, c): if key in tab: return tab[key] + vector(y) - raise ValueError('not in group') + raise TypeError('Not in group') def _rec(j, k, c): @@ -744,7 +744,7 @@ def _expand_basis_pgroup(p, alphas, vals, beta, h, rel): beta_q *= p try: e = _discrete_log_pgroup(p, vals, alphas, -beta_q) - except ValueError: + except TypeError: continue # step 6 _expand_basis_pgroup(p, alphas, vals, beta, h, list(e) + [p**v]) @@ -815,7 +815,7 @@ def basis_from_generators(gens, ords=None): beta, ord_beta = pgens.pop() try: dlog = _discrete_log_pgroup(p, vals, alphas, beta) - except ValueError: + except TypeError: pass else: continue @@ -828,7 +828,7 @@ def basis_from_generators(gens, ords=None): # assert beta_q == beta * p**v try: e = _discrete_log_pgroup(p, vals, alphas, -beta_q) - except ValueError: + except TypeError: continue _expand_basis_pgroup(p, alphas, vals, beta, val_beta, list(e) + [p**v]) # assert all(a.order() == p**v for a,v in zip(alphas, vals)) From 7a72ca50ad57bc920d2675d1d1d2e74ecc5bf356 Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Sat, 10 Feb 2024 15:38:21 +0000 Subject: [PATCH 007/518] =?UTF-8?q?apply=20review=20changes=20=F0=9F=93=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/sage/libs/ntl/ntl_ZZ_pEX_linkage.pxi | 1 - src/sage/rings/polynomial/polynomial_template.pxi | 5 ++--- src/sage/rings/polynomial/polynomial_zmod_flint.pyx | 10 ++++++++++ 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/sage/libs/ntl/ntl_ZZ_pEX_linkage.pxi b/src/sage/libs/ntl/ntl_ZZ_pEX_linkage.pxi index 4cf76fce96e..bce8452d84a 100644 --- a/src/sage/libs/ntl/ntl_ZZ_pEX_linkage.pxi +++ b/src/sage/libs/ntl/ntl_ZZ_pEX_linkage.pxi @@ -372,7 +372,6 @@ cdef inline int celement_pow(ZZ_pEX_c* res, ZZ_pEX_c* x, long e, ZZ_pEX_c *modul ZZ_pEX_power(res[0], y, e) sig_off() return 0 - # This was causing a crash, but it's fixed "upstream" # sig_on() ZZ_pEX_Modulus_build(mod, modulus[0]) # sig_off() diff --git a/src/sage/rings/polynomial/polynomial_template.pxi b/src/sage/rings/polynomial/polynomial_template.pxi index 61143f071db..ae58bcfb925 100644 --- a/src/sage/rings/polynomial/polynomial_template.pxi +++ b/src/sage/rings/polynomial/polynomial_template.pxi @@ -636,10 +636,9 @@ cdef class Polynomial_template(Polynomial): else: if parent is not (modulus)._parent and parent != (modulus)._parent: modulus = parent.coerce(modulus) - # I tried using `celement_is_zero` but it errors because `modulus` is a Python object - if modulus.is_zero(): + if celement_is_zero(&(modulus).x, (self)._cparent): raise ZeroDivisionError("modulus must be nonzero") - if modulus.is_one(): + if celement_is_one(&(modulus).x, (self)._cparent): return parent.zero() celement_pow(&r.x, &(self).x, e, &(modulus).x, (self)._cparent) diff --git a/src/sage/rings/polynomial/polynomial_zmod_flint.pyx b/src/sage/rings/polynomial/polynomial_zmod_flint.pyx index 815b9758176..aff24b6dfe8 100644 --- a/src/sage/rings/polynomial/polynomial_zmod_flint.pyx +++ b/src/sage/rings/polynomial/polynomial_zmod_flint.pyx @@ -532,6 +532,16 @@ cdef class Polynomial_zmod_flint(Polynomial_template): (3*x^4 + 2*x^3 + x^2 + 2*x, x^4 + 3*x^3 + x^2 + x) sage: (p*d % x^9) == n True + + Check that :issue:`37169` is fixed - it does not throw an error:: + + sage: R. = Zmod(4)[] + sage: _. = R.quotient_ring(x^2 - 1) + sage: c = 2 * z + 1 + sage: c * Zmod(2).zero() + Traceback (most recent call last): + ... + RuntimeError: Aborted """ if n_deg < 0 or d_deg < 0: raise ValueError("The degree bounds n_deg and d_deg should be positive.") From 542bfdd47e5fa0675bcea9b80bdbbea11d63c696 Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Tue, 13 Feb 2024 11:35:53 +0000 Subject: [PATCH 008/518] fix crashes + normalise code --- src/sage/libs/flint/nmod_poly_linkage.pxi | 13 ++++++---- .../rings/polynomial/polynomial_template.pxi | 24 +++++++++++++++++-- .../polynomial/polynomial_zmod_flint.pyx | 7 ++++-- 3 files changed, 35 insertions(+), 9 deletions(-) diff --git a/src/sage/libs/flint/nmod_poly_linkage.pxi b/src/sage/libs/flint/nmod_poly_linkage.pxi index 303cb6472fe..eb181588704 100644 --- a/src/sage/libs/flint/nmod_poly_linkage.pxi +++ b/src/sage/libs/flint/nmod_poly_linkage.pxi @@ -578,15 +578,18 @@ cdef inline int celement_gcd(nmod_poly_t res, nmod_poly_t a, nmod_poly_t b, unsi sage: (G//d)*d == G True """ + cdef unsigned long leadcoeff, modulus, leadcoeff_res if celement_is_zero(b, n): nmod_poly_set(res, a) return 0 - nmod_poly_gcd(res, a, b) - cdef unsigned long leadcoeff = nmod_poly_get_coeff_ui(res, nmod_poly_degree(res)) - cdef unsigned long modulus = nmod_poly_modulus(res) - if n_gcd(modulus,leadcoeff) == 1: - nmod_poly_make_monic(res, res) + # A check that the leading coefficients are invertible is *not* sufficient + try: + sig_on() + nmod_poly_gcd(res, a, b) + sig_off() + except RuntimeError: + raise ValueError("non-invertible elements encountered during GCD") cdef inline int celement_xgcd(nmod_poly_t res, nmod_poly_t s, nmod_poly_t t, nmod_poly_t a, nmod_poly_t b, unsigned long n) except -2: """ diff --git a/src/sage/rings/polynomial/polynomial_template.pxi b/src/sage/rings/polynomial/polynomial_template.pxi index ae58bcfb925..d3bc6cf6660 100644 --- a/src/sage/rings/polynomial/polynomial_template.pxi +++ b/src/sage/rings/polynomial/polynomial_template.pxi @@ -352,10 +352,30 @@ cdef class Polynomial_template(Polynomial): x + 1 sage: f.gcd(x^2) x + + TESTS: + + Ensure non-invertible elements does not crash Sage:: + + sage: R. = Zmod(4)[] + sage: f = R(2 * x) + sage: f.gcd(f) + 2*x + + :: + + sage: f = x^2 + 3 * x + 1 + sage: g = x^2 + x + 1 + sage: f.gcd(g) + Traceback (most recent call last): + ... + ValueError: non-invertible elements encountered during GCD """ - if(celement_is_zero(&self.x, (self)._cparent)): + if celement_is_zero(&self.x, (self)._cparent): return other - if(celement_is_zero(&other.x, (self)._cparent)): + if celement_is_zero(&other.x, (self)._cparent): + return self + if celement_equal(&self.x, &other.x, (self)._cparent): return self cdef type T = type(self) diff --git a/src/sage/rings/polynomial/polynomial_zmod_flint.pyx b/src/sage/rings/polynomial/polynomial_zmod_flint.pyx index aff24b6dfe8..17f1e1878af 100644 --- a/src/sage/rings/polynomial/polynomial_zmod_flint.pyx +++ b/src/sage/rings/polynomial/polynomial_zmod_flint.pyx @@ -757,9 +757,12 @@ cdef class Polynomial_zmod_flint(Polynomial_template): ... ValueError: leading coefficient must be invertible """ - if self.base_ring().characteristic().gcd( - self.leading_coefficient().lift()) != 1: + cdef unsigned long leadcoeff, modulus + leadcoeff = nmod_poly_get_coeff_ui(&self.x, nmod_poly_degree(&self.x)) + modulus = nmod_poly_modulus(&self.x) + if leadcoeff > 1 and n_gcd(modulus, leadcoeff) != 1: raise ValueError("leading coefficient must be invertible") + cdef Polynomial_zmod_flint res = self._new() nmod_poly_make_monic(&res.x, &self.x) return res From 256b3a747d4187069f4b4cfdbeb3e3addffb0040 Mon Sep 17 00:00:00 2001 From: grhkm21 <83517584+grhkm21@users.noreply.github.com> Date: Tue, 13 Feb 2024 11:48:42 +0000 Subject: [PATCH 009/518] review ye Co-authored-by: Giacomo Pope <44242839+GiacomoPope@users.noreply.github.com> --- src/sage/libs/ntl/ntl_ZZ_pEX_linkage.pxi | 2 -- src/sage/rings/polynomial/polynomial_template.pxi | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/sage/libs/ntl/ntl_ZZ_pEX_linkage.pxi b/src/sage/libs/ntl/ntl_ZZ_pEX_linkage.pxi index bce8452d84a..31805daaaf3 100644 --- a/src/sage/libs/ntl/ntl_ZZ_pEX_linkage.pxi +++ b/src/sage/libs/ntl/ntl_ZZ_pEX_linkage.pxi @@ -372,9 +372,7 @@ cdef inline int celement_pow(ZZ_pEX_c* res, ZZ_pEX_c* x, long e, ZZ_pEX_c *modul ZZ_pEX_power(res[0], y, e) sig_off() return 0 - # sig_on() ZZ_pEX_Modulus_build(mod, modulus[0]) - # sig_off() if ZZ_pEX_deg(x[0]) < ZZ_pEX_deg(modulus[0]): sig_on() ZZ_pEX_PowerMod_pre(res[0], x[0], e, mod) diff --git a/src/sage/rings/polynomial/polynomial_template.pxi b/src/sage/rings/polynomial/polynomial_template.pxi index d3bc6cf6660..c7b97c75a9b 100644 --- a/src/sage/rings/polynomial/polynomial_template.pxi +++ b/src/sage/rings/polynomial/polynomial_template.pxi @@ -355,7 +355,7 @@ cdef class Polynomial_template(Polynomial): TESTS: - Ensure non-invertible elements does not crash Sage:: + Ensure non-invertible elements do not crash Sage:: sage: R. = Zmod(4)[] sage: f = R(2 * x) From 059c73346efcb9f6819e1af160c412e879d308f3 Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Tue, 13 Feb 2024 12:41:11 +0000 Subject: [PATCH 010/518] link issue #37317 --- src/sage/rings/polynomial/polynomial_template.pxi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/polynomial/polynomial_template.pxi b/src/sage/rings/polynomial/polynomial_template.pxi index c7b97c75a9b..863f642754d 100644 --- a/src/sage/rings/polynomial/polynomial_template.pxi +++ b/src/sage/rings/polynomial/polynomial_template.pxi @@ -355,7 +355,7 @@ cdef class Polynomial_template(Polynomial): TESTS: - Ensure non-invertible elements do not crash Sage:: + Ensure non-invertible elements does not crash Sage (:issue:`37317`):: sage: R. = Zmod(4)[] sage: f = R(2 * x) From 2e4020a3c691d39b117816b5be2c7905bcd858a0 Mon Sep 17 00:00:00 2001 From: Giorgos Mousa Date: Wed, 14 Feb 2024 13:46:24 +0200 Subject: [PATCH 011/518] Add class FlatsMatroid --- src/sage/matroids/constructor.py | 17 +- src/sage/matroids/flats_matroid.pxd | 20 ++ src/sage/matroids/flats_matroid.pyx | 524 ++++++++++++++++++++++++++++ src/sage/matroids/unpickling.pyx | 46 +++ 4 files changed, 605 insertions(+), 2 deletions(-) create mode 100644 src/sage/matroids/flats_matroid.pxd create mode 100644 src/sage/matroids/flats_matroid.pyx diff --git a/src/sage/matroids/constructor.py b/src/sage/matroids/constructor.py index 137f4c28230..ef14148947b 100644 --- a/src/sage/matroids/constructor.py +++ b/src/sage/matroids/constructor.py @@ -113,6 +113,7 @@ import sage.matroids.basis_exchange_matroid from .rank_matroid import RankMatroid from .circuits_matroid import CircuitsMatroid +from .flats_matroid import FlatsMatroid from .circuit_closures_matroid import CircuitClosuresMatroid from .basis_matroid import BasisMatroid from .linear_matroid import LinearMatroid, RegularMatroid, BinaryMatroid, TernaryMatroid, QuaternaryMatroid @@ -180,6 +181,7 @@ def Matroid(groundset=None, data=None, **kwds): - ``circuits`` -- The list of circuits of the matroid. - ``nonspanning_circuits`` -- The list of nonspanning_circuits of the matroid. + - ``flats`` -- The dictionary of flats indexed by their rank. - ``graph`` -- A graph, whose edges form the elements of the matroid. - ``matrix`` -- A matrix representation of the matroid. - ``reduced_matrix`` -- A reduced representation of the matroid: if @@ -698,8 +700,9 @@ def Matroid(groundset=None, data=None, **kwds): key = None if data is None: for k in ['bases', 'independent_sets', 'circuits', - 'nonspanning_circuits', 'graph', 'matrix', 'reduced_matrix', - 'rank_function', 'revlex', 'circuit_closures', 'matroid']: + 'nonspanning_circuits', 'flats', 'graph', 'matrix', + 'reduced_matrix', 'rank_function', 'revlex', + 'circuit_closures', 'matroid']: if k in kwds: data = kwds.pop(k) key = k @@ -791,6 +794,16 @@ def Matroid(groundset=None, data=None, **kwds): nsc_defined=True ) + # Flats + elif key == 'flats': + # Determine groundset + if groundset is None: + groundset = set() + for i in data: + for F in data[i]: + groundset.update(F) + M = FlatsMatroid(groundset=groundset, flats=data) + # Graphs: elif key == 'graph': from sage.graphs.graph import Graph diff --git a/src/sage/matroids/flats_matroid.pxd b/src/sage/matroids/flats_matroid.pxd new file mode 100644 index 00000000000..f3ceb787f30 --- /dev/null +++ b/src/sage/matroids/flats_matroid.pxd @@ -0,0 +1,20 @@ +from sage.matroids.matroid cimport Matroid + +cdef class FlatsMatroid(Matroid): + cdef frozenset _groundset # _E + cdef int _matroid_rank # _R + cdef dict _F # flats + cpdef groundset(self) noexcept + cpdef _rank(self, X) noexcept + cpdef full_rank(self) noexcept + cpdef _is_independent(self, F) noexcept + + # enumeration + cpdef flats(self, k) noexcept + cpdef whitney_numbers2(self) noexcept + + # isomorphism + cpdef _is_isomorphic(self, other, certificate=*) noexcept + + # verification + cpdef is_valid(self) noexcept diff --git a/src/sage/matroids/flats_matroid.pyx b/src/sage/matroids/flats_matroid.pyx new file mode 100644 index 00000000000..546526ee300 --- /dev/null +++ b/src/sage/matroids/flats_matroid.pyx @@ -0,0 +1,524 @@ +r""" +Flats matroids + +Matroids are characterized by a set of flats, which are sets invariant under +closure. The ``FlatsMatroid`` class implements matroids using this information +as data. + +A ``FlatsMatroid`` can be created from another matroid or from a dictionary of +flats. For a full description of allowed inputs, see +:class:`below `. It is +recommended to use the :func:`Matroid() ` +function for a more flexible way of constructing a ``FlatsMatroid`` and other +classes of matroids. For direct access to the ``FlatsMatroid`` constructor, +run:: + + sage: from sage.matroids.flats_matroid import FlatsMatroid + +AUTHORS: + +- Giorgos Mousa (2024-01-01): initial version +""" + +# **************************************************************************** +# Copyright (C) 2024 Giorgos Mousa +# +# 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. +# https://www.gnu.org/licenses/ +# **************************************************************************** + +from sage.structure.richcmp cimport rich_to_bool, richcmp +from sage.matroids.matroid cimport Matroid +from sage.matroids.set_system cimport SetSystem +from sage.matroids.utilities import setprint_s +from cpython.object cimport Py_EQ, Py_NE + + +cdef class FlatsMatroid(Matroid): + r""" + INPUT: + + - ``M`` -- a matroid (default: ``None``) + - ``groundset`` -- a list (default: ``None``); the groundset of the matroid + - ``flats`` -- a dictionary (default: ``None``); the lists of `k`-flats of + the matroid, indexed by their rank `k` + + .. NOTE:: + + For a more flexible means of input, use the ``Matroid()`` function. + """ + + # necessary (__init__, groundset, _rank) + + def __init__(self, M=None, groundset=None, flats=None): + """ + Initialization of the matroid. See class docstring for full + documentation. + + TESTS:: + + sage: from sage.matroids.flats_matroid import FlatsMatroid + sage: M = FlatsMatroid(matroids.catalog.Fano()) + sage: TestSuite(M).run() + """ + self._F = {} + if M is not None: + self._groundset = frozenset(M.groundset()) + for i in range(len(M.groundset()) + 1): + for F in M.flats(i): + try: + self._F[i].add(frozenset(F)) + except KeyError: + self._F[i] = set() + self._F[i].add(frozenset(F)) + else: + self._groundset = frozenset(groundset) + for i in flats: + for F in flats[i]: + try: + self._F[i].add(frozenset(F)) + except KeyError: + self._F[i] = set() + self._F[i].add(frozenset(F)) + self._matroid_rank = self.rank(self._groundset) + + cpdef groundset(self) noexcept: + """ + Return the groundset of the matroid. + + The groundset is the set of elements that comprise the matroid. + + OUTPUT: a set + + EXAMPLES:: + + sage: from sage.matroids.flats_matroid import FlatsMatroid + sage: M = FlatsMatroid(matroids.Theta(2)) + sage: sorted(M.groundset()) + ['x0', 'x1', 'y0', 'y1'] + """ + return self._groundset + + cpdef _rank(self, X) noexcept: + """ + Return the rank of a set ``X``. + + This method does no checking on ``X``, and ``X`` may be assumed to have + the same interface as ``frozenset``. + + INPUT: + + - ``X`` -- an object with Python's ``frozenset`` interface + + OUTPUT: an integer; the rank of ``X`` in the matroid + + EXAMPLES:: + + sage: from sage.matroids.flats_matroid import FlatsMatroid + sage: M = FlatsMatroid(matroids.Theta(3)) + sage: M._rank(['x1', 'y0', 'y2']) + 2 + """ + cdef frozenset XX = frozenset(X) + cdef int min = len(self._groundset) + for i in self._F: + if i < min: + for f in self._F[i]: + if f >= XX: + min = i + break + return min + + # optional + + cpdef full_rank(self) noexcept: + r""" + Return the rank of the matroid. + + The *rank* of the matroid is the size of the largest independent + subset of the groundset. + + OUTPUT: an integer; the rank of the matroid + + EXAMPLES:: + + sage: from sage.matroids.flats_matroid import FlatsMatroid + sage: M = FlatsMatroid(matroids.Theta(7)) + sage: M.full_rank() + 7 + """ + return self._matroid_rank + + cpdef _is_isomorphic(self, other, certificate=False) noexcept: + """ + Test if ``self`` is isomorphic to ``other``. + + INPUT: + + - ``other`` -- a matroid + - ``certificate`` -- boolean (default: ``False``) + + + OUTPUT: boolean, and, if ``certificate=True``, a dictionary giving the + isomorphism or ``None`` + + EXAMPLES:: + + sage: from sage.matroids.flats_matroid import FlatsMatroid + sage: M = matroids.catalog.NonDesargues() + sage: N = FlatsMatroid(M) + sage: N._is_isomorphic(M) + True + sage: N._is_isomorphic(matroids.catalog.R9()) + False + + .. NOTE:: + + Internal version that does no input checking. + """ + if certificate: + return self._is_isomorphic(other), self._isomorphism(other) + N = FlatsMatroid(other) + flats_self = frozenset([F for i in self._F for F in self._F[i]]) + flats_other = frozenset([F for i in N._F for F in N._F[i]]) + SS = SetSystem(list(self._groundset), flats_self) + OS = SetSystem(list(N._groundset), flats_other) + return SS._isomorphism(OS) is not None + + # representation + + def _repr_(self): + """ + Return a string representation of the matroid. + + EXAMPLES:: + + sage: from sage.matroids.flats_matroid import FlatsMatroid + sage: M = FlatsMatroid(matroids.Uniform(6, 6)); M + Matroid of rank 6 on 6 elements with 64 flats + """ + flats_num = sum(1 for i in self._F for F in self._F[i]) + return Matroid._repr_(self) + " with " + str(flats_num) + " flats" + + # comparison + + def __hash__(self): + r""" + Return an invariant of the matroid. + + This function is called when matroids are added to a set. It is very + desirable to override it so it can distinguish matroids on the same + groundset, which is a very typical use case! + + .. WARNING:: + + This method is linked to __richcmp__ (in Cython) and __cmp__ or + __eq__/__ne__ (in Python). If you override one, you should + (and in Cython: MUST) override the other! + + EXAMPLES:: + + sage: from sage.matroids.flats_matroid import FlatsMatroid + sage: M = FlatsMatroid(matroids.catalog.Vamos()) + sage: N = FlatsMatroid(matroids.catalog.Vamos()) + sage: hash(M) == hash(N) + True + sage: O = FlatsMatroid(matroids.catalog.NonVamos()) + sage: hash(M) == hash(O) + False + """ + flats = frozenset([F for i in self._F for F in self._F[i]]) + return hash(tuple([self._groundset, flats])) + + def __richcmp__(left, right, int op): + r""" + Compare two matroids. + + We take a very restricted view on equality: the objects need to be of + the exact same type (so no subclassing) and the internal data need to + be the same. For FlatsMatroids, this means that the groundsets and the + dictionaries of flats of the two matroids are equal. + + EXAMPLES:: + + sage: from sage.matroids.flats_matroid import FlatsMatroid + sage: M = FlatsMatroid(matroids.catalog.Pappus()) + sage: N = FlatsMatroid(matroids.catalog.NonPappus()) + sage: M == N + False + sage: N = Matroid(M.bases()) + sage: M == N + False + """ + cdef FlatsMatroid lt, rt + if op not in [Py_EQ, Py_NE]: + return NotImplemented + if type(left) is not type(right): + return NotImplemented + lt = left + rt = right + if lt.groundset() != rt.groundset(): + return rich_to_bool(op, 1) + if lt.full_rank() != rt.full_rank(): + return rich_to_bool(op, 1) + return richcmp(lt._F, rt._F, op) + + # copying, loading, saving + + def __copy__(self): + """ + Create a shallow copy. + + EXAMPLES:: + + sage: from sage.matroids.flats_matroid import FlatsMatroid + sage: M = FlatsMatroid(matroids.catalog.Vamos()) + sage: N = copy(M) # indirect doctest + sage: M == N + True + sage: M.groundset() is N.groundset() + True + """ + N = FlatsMatroid(groundset=[], flats={}) + N._groundset = self._groundset + N._F = self._F + N._matroid_rank = self._matroid_rank + N.rename(self.get_custom_name()) + return N + + def __deepcopy__(self, memo=None): + """ + Create a deep copy. + + .. NOTE:: + + Since matroids are immutable, a shallow copy normally suffices. + + EXAMPLES:: + + sage: from sage.matroids.flats_matroid import FlatsMatroid + sage: M = FlatsMatroid(matroids.catalog.Vamos()) + sage: N = deepcopy(M) # indirect doctest + sage: M == N + True + sage: M.groundset() is N.groundset() + False + """ + if memo is None: + memo = {} + from copy import deepcopy + # Since matroids are immutable, N cannot reference itself in correct code, so no need to worry about the recursion. + N = FlatsMatroid(groundset=deepcopy(self._groundset, memo), flats=deepcopy(self._F, memo)) + N.rename(deepcopy(self.get_custom_name(), memo)) + return N + + def __reduce__(self): + """ + Save the matroid for later reloading. + + OUTPUT: + + A tuple ``(unpickle, (version, data))``, where ``unpickle`` is the + name of a function that, when called with ``(version, data)``, + produces a matroid isomorphic to ``self``. ``version`` is an integer + (currently 0) and ``data`` is a tuple ``(E, F, name)`` where ``E`` is + the groundset, ``F`` is the dictionary of flats, and ``name`` is a + custom name. + + EXAMPLES:: + + sage: from sage.matroids.flats_matroid import FlatsMatroid + sage: M = FlatsMatroid(matroids.catalog.Vamos()) + sage: M == loads(dumps(M)) # indirect doctest + True + sage: M.reset_name() + sage: loads(dumps(M)) + Matroid of rank 4 on 8 elements with 79 flats + """ + import sage.matroids.unpickling + data = (self._groundset, self._F, self.get_custom_name()) + version = 0 + return sage.matroids.unpickling.unpickle_flats_matroid, (version, data) + + # enumeration + + cpdef flats(self, k) noexcept: + r""" + Return the flats of the matroid of specified rank. + + INPUT: + + - ``k`` -- an integer + + OUTPUT: a SetSystem + + EXAMPLES:: + + sage: from sage.matroids.flats_matroid import FlatsMatroid + sage: M = FlatsMatroid(matroids.Uniform(3, 4)) + sage: sorted(M.flats(2), key=str) + [frozenset({0, 1}), + frozenset({0, 2}), + frozenset({0, 3}), + frozenset({1, 2}), + frozenset({1, 3}), + frozenset({2, 3})] + """ + if k in self._F: + return SetSystem(list(self._groundset), self._F[k]) + return SetSystem(list(self._groundset)) + + def flats_iterator(self, k): + r""" + Return an iterator over the flats of the matroid of specified rank. + + INPUT: + + - ``k`` -- an integer + + EXAMPLES:: + + sage: from sage.matroids.flats_matroid import FlatsMatroid + sage: M = FlatsMatroid(matroids.Uniform(3, 4)) + sage: sorted([list(F) for F in M.flats_iterator(2)]) + [[0, 1], [0, 2], [0, 3], [1, 2], [1, 3], [2, 3]] + """ + if k in self._F: + for F in self._F[k]: + yield F + + cpdef whitney_numbers2(self) noexcept: + r""" + Return the Whitney numbers of the second kind of the matroid. + + The Whitney numbers of the second kind are here encoded as a vector + `(W_0, ..., W_r)`, where `W_i` is the number of flats of rank `i`, and + `r` is the rank of the matroid. + + OUTPUT: a list of integers + + EXAMPLES:: + + sage: from sage.matroids.flats_matroid import FlatsMatroid + sage: M = FlatsMatroid(matroids.catalog.XY13()) + sage: M.whitney_numbers2() + [1, 13, 78, 250, 394, 191, 1] + """ + cdef list W = [] + cdef int i + for i in sorted(self._F): + W.append(len(self._F[i])) + return W + + # verification + + cpdef is_valid(self) noexcept: + r""" + Test if ``self`` obeys the matroid axioms. + + For a matroid defined by its flats, we check the flat axioms. + + OUTPUT: boolean + + EXAMPLES:: + + sage: M = Matroid(flats={0: [[]], 1: [[0], [1]], 2: [[0, 1]]}) + sage: M.is_valid() + True + sage: M = Matroid(flats={0: [''], 1: ['a', 'b'], 2: ['ab']}) + sage: M.is_valid() + True + sage: M = Matroid(flats={0: [[]], 1: [[0], [1]]}) # missing groundset + sage: M.is_valid() + False + sage: M = Matroid(flats={0: [''], + ....: 1: ['0','1','2','3','4','5','6','7','8','9','a','b','c'], + ....: 2: ['45','46','47','4c','56','57','5c','67','6c','7c', + ....: '048','149','24a','34b','059','15a','25b','358', + ....: '06a','16b','268','369','07b','178','279','37a', + ....: '0123c','89abc'], + ....: 3: ['0123456789abc']}) + sage: M.is_valid() + True + sage: from sage.matroids.flats_matroid import FlatsMatroid + sage: M = FlatsMatroid(matroids.catalog.NonVamos()) + sage: M.is_valid() + True + + TESTS:: + + sage: Matroid(flats={0: [], 1: [[0], [1]], 2: [[0, 1]]}).is_valid() # missing an intersection + False + sage: Matroid(flats={0: [[]], 2: [[0], [1]], 3: [[0, 1]]}).is_valid() # invalid ranks + False + sage: Matroid(flats={0: [[]], 1: [[0], [1]], 2: [[0], [0, 1]]}).is_valid() # duplicates + False + sage: Matroid(flats={0: [[]], 1: [[0], [1], [0, 1]]}).is_valid() + False + sage: Matroid(flats={0: [[]], 1: [[0, 1], [2]], 2: [[0], [1], [0, 1, 2]]}).is_valid() + False + sage: M = Matroid(flats={0: [''], # missing an extention of flat ['5'] by '6' + ....: 1: ['0','1','2','3','4','5','6','7','8','9','a','b','c'], + ....: 2: ['45','46','47','4c','57','5c','67','6c','7c', + ....: '048','149','24a','34b','059','15a','25b','358', + ....: '06a','16b','268','369','07b','178','279','37a', + ....: '0123c','89abc'], + ....: 3: ['0123456789abc']}) + sage: M.is_valid() + False + """ + cdef int i, j, k + cdef frozenset F1, F2, F3, I12 + cdef list ranks, cover, flats_lst + cdef bint flag + + # check flats dictionary for invalid ranks and repeated flats + ranks = sorted(self._F) + if ranks != list(range(len(ranks))): + return False + flats_lst = [F for i in self._F for F in self._F[i]] + if len(flats_lst) != len(set(flats_lst)): + return False + + # the groundset must be a flat + flag = False + for i in self._F: + for F1 in self._F[i]: + if F1 == self._groundset: + flag = True + break + if not flag: + return False + + # a single element extension of a flat must be a subset of exactly one flat + for i in ranks[:-1]: + for F1 in self._F[i]: + cover = [] + for F2 in self._F[i+1]: + if F2 >= F1: + cover.extend(F1 ^ F2) + if len(cover) != len(F1 ^ self._groundset) or set(cover) != F1 ^ self._groundset: + return False + + # the intersection of two flats must be a flat + for i in ranks: + for j in ranks[i:]: + for F1 in self._F[i]: + for F2 in self._F[j]: + flag = False + I12 = F1 & F2 + for k in self._F: + if k <= i: + for F3 in self._F[k]: + if F3 == I12: + flag = True + break + if flag: + break + if not flag: + return False + + return True diff --git a/src/sage/matroids/unpickling.pyx b/src/sage/matroids/unpickling.pyx index 2cd946d7503..da9ef0a5e5f 100644 --- a/src/sage/matroids/unpickling.pyx +++ b/src/sage/matroids/unpickling.pyx @@ -14,6 +14,7 @@ Python terminology) functions for Sage's matroids. AUTHORS: - Rudi Pendavingh, Stefan van Zwam (2013-07-01): initial version +- Giorgos Mousa (2024-01-01): add CircuitsMatroid and FlatsMatroid """ # **************************************************************************** # Copyright (C) 2013 Rudi Pendavingh @@ -32,6 +33,7 @@ from sage.rings.rational cimport Rational from sage.matroids.basis_matroid cimport BasisMatroid from sage.matroids.circuits_matroid cimport CircuitsMatroid from sage.matroids.circuit_closures_matroid cimport CircuitClosuresMatroid +from sage.matroids.flats_matroid cimport FlatsMatroid from sage.matroids.dual_matroid import DualMatroid from sage.matroids.graphic_matroid import GraphicMatroid from sage.matroids.lean_matrix cimport GenericMatrix, BinaryMatrix, TernaryMatrix, QuaternaryMatrix, PlusMinusOneMatrix, RationalMatrix @@ -173,6 +175,50 @@ def unpickle_circuit_closures_matroid(version, data): return M +############################################################################# +# FlatsMatroid +############################################################################# + +def unpickle_flats_matroid(version, data): + """ + Unpickle a FlatsMatroid. + + *Pickling* is Python's term for the loading and saving of objects. + Functions like these serve to reconstruct a saved object. This all happens + transparently through the ``load`` and ``save`` commands, and you should + never have to call this function directly. + + INPUT: + + - ``version`` -- an integer, expected to be 0 + - ``data`` -- a tuple ``(E, F, name)`` in which ``E`` is the groundset of + the matroid, ``F`` is the dictionary of flats, and ``name`` is a custom + name. + + OUTPUT: + + A matroid. + + .. WARNING:: + + Users should never call this function directly. + + EXAMPLES:: + + sage: from sage.matroids.flats_matroid import FlatsMatroid + sage: M = FlatsMatroid(matroids.catalog.Vamos()) + sage: M == loads(dumps(M)) # indirect doctest + True + """ + cdef FlatsMatroid M + if version != 0: + raise TypeError("object was created with newer version of Sage. Please upgrade.") + M = FlatsMatroid(groundset=data[0], flats=data[1]) + if data[2] is not None: + M.rename(data[2]) + return M + + ############################################################################# # DualMatroid ############################################################################# From c68aec538497b99364b4588b2c246dc5627cbdbf Mon Sep 17 00:00:00 2001 From: Giorgos Mousa Date: Thu, 15 Feb 2024 12:45:09 +0200 Subject: [PATCH 012/518] Few optimizations --- src/sage/matroids/flats_matroid.pyx | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/sage/matroids/flats_matroid.pyx b/src/sage/matroids/flats_matroid.pyx index 546526ee300..1456a952d89 100644 --- a/src/sage/matroids/flats_matroid.pyx +++ b/src/sage/matroids/flats_matroid.pyx @@ -76,14 +76,14 @@ cdef class FlatsMatroid(Matroid): self._F[i].add(frozenset(F)) else: self._groundset = frozenset(groundset) - for i in flats: + for i in sorted(flats): for F in flats[i]: try: self._F[i].add(frozenset(F)) except KeyError: self._F[i] = set() self._F[i].add(frozenset(F)) - self._matroid_rank = self.rank(self._groundset) + self._matroid_rank = max([0] + list(self._F)) cpdef groundset(self) noexcept: """ @@ -121,16 +121,20 @@ cdef class FlatsMatroid(Matroid): sage: M = FlatsMatroid(matroids.Theta(3)) sage: M._rank(['x1', 'y0', 'y2']) 2 + + TESTS:: + + sage: from sage.matroids.flats_matroid import FlatsMatroid + sage: M = matroids.catalog.NonDesargues() + sage: F = FlatsMatroid(M) + sage: for S in powerset(M.groundset()): + ....: assert M.rank(S) == F.rank(S) """ cdef frozenset XX = frozenset(X) - cdef int min = len(self._groundset) - for i in self._F: - if i < min: + for i in range(self.rank() + 1): for f in self._F[i]: if f >= XX: - min = i - break - return min + return i # optional @@ -409,7 +413,7 @@ cdef class FlatsMatroid(Matroid): """ cdef list W = [] cdef int i - for i in sorted(self._F): + for i in self._F: W.append(len(self._F[i])) return W @@ -476,7 +480,7 @@ cdef class FlatsMatroid(Matroid): cdef bint flag # check flats dictionary for invalid ranks and repeated flats - ranks = sorted(self._F) + ranks = list(self._F) if ranks != list(range(len(ranks))): return False flats_lst = [F for i in self._F for F in self._F[i]] From a092c7ef9c62edaa28a0f6ce8a825279249eccc3 Mon Sep 17 00:00:00 2001 From: Giorgos Mousa Date: Thu, 15 Feb 2024 14:16:46 +0200 Subject: [PATCH 013/518] Add iterators, Whitney numbers, characteristic polynomial, etc --- src/sage/matroids/basis_exchange_matroid.pxd | 4 +- src/sage/matroids/basis_exchange_matroid.pyx | 33 +- src/sage/matroids/basis_matroid.pyx | 4 +- src/sage/matroids/database_matroids.py | 2 +- src/sage/matroids/matroid.pxd | 3 + src/sage/matroids/matroid.pyx | 1324 ++++++++++-------- src/sage/matroids/union_matroid.pyx | 2 +- 7 files changed, 770 insertions(+), 602 deletions(-) diff --git a/src/sage/matroids/basis_exchange_matroid.pxd b/src/sage/matroids/basis_exchange_matroid.pxd index 5877f62fc09..151af953299 100644 --- a/src/sage/matroids/basis_exchange_matroid.pxd +++ b/src/sage/matroids/basis_exchange_matroid.pxd @@ -61,8 +61,8 @@ cdef class BasisExchangeMatroid(Matroid): cpdef _augment(self, X, Y) noexcept cpdef _is_independent(self, F) noexcept - cpdef f_vector(self) noexcept - cdef _f_vector_rec(self, object f_vec, bitset_t* flats, bitset_t* todo, long elt, long rnk) noexcept + cpdef whitney_numbers2(self) noexcept + cdef _whitney_numbers2_rec(self, object f_vec, bitset_t* flats, bitset_t* todo, long elt, long rnk) noexcept cpdef flats(self, R) noexcept cdef _flats_rec(self, SetSystem Rflats, long R, bitset_t* flats, bitset_t* todo, long elt, long rnk) noexcept cpdef coflats(self, R) noexcept diff --git a/src/sage/matroids/basis_exchange_matroid.pyx b/src/sage/matroids/basis_exchange_matroid.pyx index 4839fd6c9d9..f336b1d77b8 100644 --- a/src/sage/matroids/basis_exchange_matroid.pyx +++ b/src/sage/matroids/basis_exchange_matroid.pyx @@ -1232,27 +1232,26 @@ cdef class BasisExchangeMatroid(Matroid): # enumeration - cpdef f_vector(self) noexcept: + cpdef whitney_numbers2(self) noexcept: r""" - Return the `f`-vector of the matroid. + Return the Whitney numbers of the second kind of the matroid. - The `f`-*vector* is a vector `(f_0, ..., f_r)`, where `f_i` is the - number of flats of rank `i`, and `r` is the rank of the matroid. + The Whitney numbers of the second kind are here encoded as a vector + `(W_0, ..., W_r)`, where `W_i` is the number of flats of rank `i`, and + `r` is the rank of the matroid. - OUTPUT: - - List of integers. + OUTPUT: a list of integers EXAMPLES:: sage: M = matroids.catalog.S8() - sage: M.f_vector() + sage: M.whitney_numbers2() [1, 8, 22, 14, 1] """ cdef bitset_t *flats cdef bitset_t *todo if self._matroid_rank == 0: - return [0] + return [1] flats = sig_malloc((self.full_rank() + 1) * sizeof(bitset_t)) todo = sig_malloc((self.full_rank() + 1) * sizeof(bitset_t)) @@ -1264,7 +1263,7 @@ cdef class BasisExchangeMatroid(Matroid): bitset_clear(todo[0]) self.__closure(flats[0], todo[0]) bitset_complement(todo[0], flats[0]) - self._f_vector_rec(f_vec, flats, todo, 0, 0) + self._whitney_numbers2_rec(f_vec, flats, todo, 0, 0) for i in range(self.full_rank() + 1): bitset_free(flats[i]) bitset_free(todo[i]) @@ -1272,9 +1271,9 @@ cdef class BasisExchangeMatroid(Matroid): sig_free(todo) return f_vec - cdef _f_vector_rec(self, object f_vec, bitset_t* flats, bitset_t* todo, long elt, long i) noexcept: + cdef _whitney_numbers2_rec(self, object f_vec, bitset_t* flats, bitset_t* todo, long elt, long i) noexcept: """ - Recursion for the f_vector method. + Recursion for the whitney_numbers2 method. """ cdef long e f_vec[i] += 1 @@ -1287,7 +1286,7 @@ cdef class BasisExchangeMatroid(Matroid): bitset_difference(todo[i + 1], flats[i + 1], flats[i]) if bitset_first(todo[i + 1]) == e: bitset_copy(todo[i + 1], todo[i]) - self._f_vector_rec(f_vec, flats, todo, e + 1, i + 1) + self._whitney_numbers2_rec(f_vec, flats, todo, e + 1, i + 1) e = bitset_next(todo[i], e) cpdef flats(self, r) noexcept: @@ -1311,7 +1310,7 @@ cdef class BasisExchangeMatroid(Matroid): EXAMPLES:: sage: M = matroids.catalog.S8() - sage: M.f_vector() + sage: M.whitney_numbers2() [1, 8, 22, 14, 1] sage: len(M.flats(2)) 22 @@ -1386,7 +1385,7 @@ cdef class BasisExchangeMatroid(Matroid): EXAMPLES:: sage: M = matroids.catalog.S8().dual() - sage: M.f_vector() + sage: M.whitney_numbers2() [1, 8, 22, 14, 1] sage: len(M.coflats(2)) 22 @@ -1957,8 +1956,8 @@ cdef class BasisExchangeMatroid(Matroid): else: k = min(self.full_rank() - 1, 2) fie, f_vec = self._flat_element_inv(k) - self._weak_invariant_var = hash(tuple([tuple([(f, len(fie[f])) for f in sorted(fie)]), f_vec])) - self._weak_partition_var = SetSystem(self._E, [fie[f] for f in sorted(fie)]) + self._weak_invariant_var = hash(tuple([tuple([(f, len(fie[f])) for f in sorted(fie, key=str)]), f_vec])) + self._weak_partition_var = SetSystem(self._E, [fie[f] for f in sorted(fie, key=str)]) return self._weak_invariant_var cpdef _weak_partition(self) noexcept: diff --git a/src/sage/matroids/basis_matroid.pyx b/src/sage/matroids/basis_matroid.pyx index 137edc21d94..63f5c72a02d 100644 --- a/src/sage/matroids/basis_matroid.pyx +++ b/src/sage/matroids/basis_matroid.pyx @@ -428,9 +428,9 @@ cdef class BasisMatroid(BasisExchangeMatroid): sage: M = Matroid(bases=matroids.catalog.N2().bases()) sage: M.truncation() Matroid of rank 5 on 12 elements with 702 bases - sage: M.f_vector() + sage: M.whitney_numbers2() [1, 12, 66, 190, 258, 99, 1] - sage: M.truncation().f_vector() + sage: M.truncation().whitney_numbers2() [1, 12, 66, 190, 258, 1] """ if self.full_rank() == 0: diff --git a/src/sage/matroids/database_matroids.py b/src/sage/matroids/database_matroids.py index 0a75ce22283..57fecb91514 100644 --- a/src/sage/matroids/database_matroids.py +++ b/src/sage/matroids/database_matroids.py @@ -541,7 +541,7 @@ def P7(): sage: M = matroids.catalog.P7(); M P7: Ternary matroid of rank 3 on 7 elements, type 1+ - sage: M.f_vector() + sage: M.whitney_numbers2() [1, 7, 11, 1] sage: M.has_minor(matroids.CompleteGraphic(4)) False diff --git a/src/sage/matroids/matroid.pxd b/src/sage/matroids/matroid.pxd index 7d2bc60078d..dae3e7d4368 100644 --- a/src/sage/matroids/matroid.pxd +++ b/src/sage/matroids/matroid.pxd @@ -124,6 +124,8 @@ cdef class Matroid(SageObject): cpdef coflats(self, r) noexcept cpdef hyperplanes(self) noexcept cpdef f_vector(self) noexcept + cpdef whitney_numbers(self) noexcept + cpdef whitney_numbers2(self) noexcept cpdef broken_circuits(self, ordering=*) noexcept cpdef no_broken_circuits_sets(self, ordering=*) noexcept @@ -213,6 +215,7 @@ cdef class Matroid(SageObject): cpdef _internal(self, B) noexcept cpdef _external(self, B) noexcept cpdef tutte_polynomial(self, x=*, y=*) noexcept + cpdef characteristic_polynomial(self, l=*) noexcept cpdef flat_cover(self, solver=*, verbose=*, integrality_tolerance=*) noexcept # misc diff --git a/src/sage/matroids/matroid.pyx b/src/sage/matroids/matroid.pyx index 2338c144190..caf6b6df3ee 100644 --- a/src/sage/matroids/matroid.pyx +++ b/src/sage/matroids/matroid.pyx @@ -19,7 +19,7 @@ methods (organized by category). Note that most subclasses (notably :mod:`LinearMatroids `) will implement additional functionality (e.g. linear extensions). -- Ground set: +- Groundset: - :meth:`groundset() ` - :meth:`size() ` @@ -71,6 +71,8 @@ additional functionality (e.g. linear extensions). - :meth:`coflats() ` - :meth:`hyperplanes() ` - :meth:`f_vector() ` + - :meth:`whitney_numbers() ` + - :meth:`whitney_numbers2() ` - :meth:`broken_circuits() ` - :meth:`no_broken_circuits_sets() ` @@ -128,6 +130,7 @@ additional functionality (e.g. linear extensions). - Invariants - :meth:`tutte_polynomial() ` + - :meth:`characteristic_polynomial() ` - :meth:`flat_cover() ` - Visualization @@ -174,9 +177,9 @@ A subclass should always override the underscored method, if available, and as a rule leave the regular method alone. These underscored methods are not documented in the reference manual. To see -them, within Sage you can create a matroid ``M`` and type ``M._``. Then -``M._rank?`` followed by ```` will bring up the documentation string of -the ``_rank()`` method. +them, within Sage you can create a matroid ``M`` and type ``M._`` followed by +:kbd:`Tab`. Then ``M._rank?`` followed by :kbd:`Tab` will bring up the +documentation string of the ``_rank()`` method. Creating new Matroid subclasses =============================== @@ -315,9 +318,10 @@ REFERENCES AUTHORS: -- Michael Welsh (2013-04-03): Changed flats() to use SetSystem -- Michael Welsh (2013-04-01): Added is_3connected(), using naive algorithm - Rudi Pendavingh, Stefan van Zwam (2013-04-01): initial version +- Michael Welsh (2013-04-01): Added is_3connected(), using naive algorithm +- Michael Welsh (2013-04-03): Changed flats() to use SetSystem +- Giorgos Mousa (2024-02-15): Add Whitney numbers, characteristic polynomial Methods ======= @@ -458,7 +462,6 @@ cdef class Matroid(SageObject): A subclass should always override the underscored method, if available, and as a rule leave the regular method alone. - """ # virtual methods @@ -469,9 +472,7 @@ cdef class Matroid(SageObject): The groundset is the set of elements that comprise the matroid. - OUTPUT: - - A set. + OUTPUT: a set .. NOTE:: @@ -499,9 +500,7 @@ cdef class Matroid(SageObject): - ``X`` -- an object with Python's ``frozenset`` interface. - OUTPUT: - - Integer. + OUTPUT: an integer .. NOTE:: @@ -531,9 +530,7 @@ cdef class Matroid(SageObject): - ``X`` -- An object with Python's ``frozenset`` interface containing a subset of ``self.groundset()``. - OUTPUT: - - ``frozenset`` instance containing a subset of the groundset. + OUTPUT: ``frozenset`` instance containing a subset of the groundset EXAMPLES:: @@ -603,9 +600,7 @@ cdef class Matroid(SageObject): - ``B`` -- a basis of the matroid. - ``e`` -- an element not in ``B``. - OUTPUT: - - A set of elements. + OUTPUT: a set of elements EXAMPLES:: @@ -624,9 +619,7 @@ cdef class Matroid(SageObject): - ``X`` -- An object with Python's ``frozenset`` interface containing a subset of ``self.groundset()``. - OUTPUT: - - ``frozenset`` instance containing a subset of the groundset. + OUTPUT: ``frozenset`` instance containing a subset of the groundset EXAMPLES:: @@ -652,9 +645,7 @@ cdef class Matroid(SageObject): - ``X`` -- An object with Python's ``frozenset`` interface containing a subset of ``self.groundset()``. - OUTPUT: - - Integer. + OUTPUT: an integer EXAMPLES:: @@ -673,9 +664,7 @@ cdef class Matroid(SageObject): - ``X`` -- An object with Python's ``frozenset`` interface containing a subset of ``self.groundset()``. - OUTPUT: - - ``frozenset`` instance containing a subset of the groundset. + OUTPUT: ``frozenset`` instance containing a subset of the groundset EXAMPLES:: @@ -745,9 +734,7 @@ cdef class Matroid(SageObject): - ``B`` -- a basis of the matroid. - ``e`` -- an element of ``B``. - OUTPUT: - - A set of elements. + OUTPUT: a set of elements EXAMPLES:: @@ -766,9 +753,7 @@ cdef class Matroid(SageObject): - ``X`` -- An object with Python's ``frozenset`` interface containing a subset of ``self.groundset()``. - OUTPUT: - - ``frozenset`` instance containing a subset of the groundset. + OUTPUT: ``frozenset`` instance containing a subset of the groundset EXAMPLES:: @@ -799,9 +784,7 @@ cdef class Matroid(SageObject): - ``Y`` -- An object with Python's ``frozenset`` interface containing a subset of ``self.groundset()``, and disjoint from ``X``. - OUTPUT: - - ``frozenset`` instance containing a subset of the groundset. + OUTPUT: ``frozenset`` instance containing a subset of the groundset EXAMPLES:: @@ -834,9 +817,7 @@ cdef class Matroid(SageObject): - ``X`` -- An object with Python's ``frozenset`` interface containing a subset of ``self.groundset()``. - OUTPUT: - - Boolean. + OUTPUT: boolean EXAMPLES:: @@ -863,9 +844,7 @@ cdef class Matroid(SageObject): i.e. ``len(X) == self.full_rank()``. Otherwise its behavior is undefined. - OUTPUT: - - Boolean. + OUTPUT: boolean EXAMPLES:: @@ -892,9 +871,7 @@ cdef class Matroid(SageObject): - ``X`` -- An object with Python's ``frozenset`` interface containing a subset of ``self.groundset()``. - OUTPUT: - - Boolean. + OUTPUT: boolean EXAMPLES:: @@ -926,9 +903,7 @@ cdef class Matroid(SageObject): - ``X`` -- An object with Python's ``frozenset`` interface containing a subset of ``self.groundset()``. - OUTPUT: - - Boolean. + OUTPUT: boolean EXAMPLES:: @@ -957,9 +932,7 @@ cdef class Matroid(SageObject): - ``X`` -- An object with Python's ``frozenset`` interface containing a subset of ``self.groundset()``. - OUTPUT: - - Boolean. + OUTPUT: boolean EXAMPLES:: @@ -980,9 +953,7 @@ cdef class Matroid(SageObject): - ``X`` -- An object with Python's ``frozenset`` interface containing a subset of ``self.groundset()``. - OUTPUT: - - Boolean. + OUTPUT: boolean .. WARNING:: @@ -1010,9 +981,7 @@ cdef class Matroid(SageObject): - ``X`` -- An object with Python's ``frozenset`` interface containing a subset of ``self.groundset()``. - OUTPUT: - - Boolean. + OUTPUT: boolean EXAMPLES:: @@ -1044,9 +1013,7 @@ cdef class Matroid(SageObject): - ``X`` -- An object with Python's ``frozenset`` interface containing a subset of ``self.groundset()``. - OUTPUT: - - Boolean. + OUTPUT: boolean EXAMPLES:: @@ -1086,9 +1053,7 @@ cdef class Matroid(SageObject): - ``deletions`` is coindependent - ``contractions`` and ``deletions`` are disjoint. - OUTPUT: - - A matroid. + OUTPUT: a matroid EXAMPLES:: @@ -1107,19 +1072,18 @@ cdef class Matroid(SageObject): cpdef _has_minor(self, N, bint certificate=False) noexcept: """ - Test if matroid has the specified minor, - and optionally return frozensets ``X`` and ``Y`` so that ``N`` is isomorphic to ``self.minor(X, Y)``. + Test if matroid has the specified minor, and optionally return + frozensets ``X`` and ``Y`` so that ``N`` is isomorphic to + ``self.minor(X, Y)``. INPUT: - ``N`` -- An instance of a ``Matroid`` object, - - ``certificate`` -- Boolean (Default: ``False``) If ``True``, returns + - ``certificate`` -- boolean (Default: ``False``) If ``True``, returns ``True, (X, Y, dic) where ``N`` is isomorphic to ``self.minor(X, Y)``, and ``dic`` is an isomorphism between ``N`` and ``self.minor(X, Y)``. - OUTPUT: - - boolean or tuple. + OUTPUT: boolean or tuple EXAMPLES:: @@ -1172,9 +1136,7 @@ cdef class Matroid(SageObject): - ``F`` -- a subset of the groundset, assumed to be a closed set of rank `r(M) - 2`. - OUTPUT: - - Integer. + OUTPUT: an integer EXAMPLES:: @@ -1201,9 +1163,7 @@ cdef class Matroid(SageObject): - ``hyperplanes`` -- the set of hyperplanes of a linear subclass of ``self``. - OUTPUT: - - A matroid. + OUTPUT: a matroid EXAMPLES:: @@ -1259,9 +1219,7 @@ cdef class Matroid(SageObject): """ Return the size of the groundset. - OUTPUT: - - Integer. + OUTPUT: an integer EXAMPLES:: @@ -1346,9 +1304,7 @@ cdef class Matroid(SageObject): - ``X`` -- (default: the groundset) a subset (or any iterable) of the groundset - OUTPUT: - - Integer. + OUTPUT: an integer EXAMPLES:: @@ -1373,9 +1329,7 @@ cdef class Matroid(SageObject): The *rank* of the matroid is the size of the largest independent subset of the groundset. - OUTPUT: - - Integer. + OUTPUT: an integer EXAMPLES:: @@ -1399,9 +1353,7 @@ cdef class Matroid(SageObject): The output of this method can change in between calls. - OUTPUT: - - Set of elements. + OUTPUT: a set of elements EXAMPLES:: @@ -1426,9 +1378,7 @@ cdef class Matroid(SageObject): - ``X`` -- a subset (or any iterable) of the groundset - OUTPUT: - - Subset of ``X``. + OUTPUT: a subset of ``X`` EXAMPLES:: @@ -1456,9 +1406,7 @@ cdef class Matroid(SageObject): - ``X`` -- (default: the groundset) a subset (or any iterable) of the groundset - OUTPUT: - - Set of elements. + OUTPUT: a set of elements - If ``X`` is not ``None``, the output is a circuit contained in ``X`` if such a circuit exists. Otherwise an error is raised. @@ -1500,9 +1448,7 @@ cdef class Matroid(SageObject): - ``B`` -- a basis of the matroid. - ``e`` -- an element not in ``B``. - OUTPUT: - - A set of elements. + OUTPUT: a set of elements .. SEEALSO:: @@ -1534,9 +1480,7 @@ cdef class Matroid(SageObject): - ``X`` -- a subset (or any iterable) of the groundset - OUTPUT: - - Set of elements containing ``X``. + OUTPUT: a superset of ``X`` EXAMPLES:: @@ -1611,9 +1555,7 @@ cdef class Matroid(SageObject): - ``Y`` -- (default: the groundset) a subset (or any iterable) of the groundset - OUTPUT: - - A subset of `Y - X`. + OUTPUT: a subset of `Y - X` EXAMPLES:: @@ -1652,9 +1594,7 @@ cdef class Matroid(SageObject): - ``X`` -- (default: the groundset) a subset (or any iterable) of the groundset - OUTPUT: - - Integer. + OUTPUT: an integer .. SEEALSO:: @@ -1682,9 +1622,7 @@ cdef class Matroid(SageObject): The *corank* of the matroid equals the rank of the dual matroid. It is given by ``M.size() - M.full_rank()``. - OUTPUT: - - Integer. + OUTPUT: an integer .. SEEALSO:: @@ -1710,9 +1648,7 @@ cdef class Matroid(SageObject): Output can change between calls. - OUTPUT: - - A set of elements. + OUTPUT: a set of elements .. SEEALSO:: @@ -1746,9 +1682,7 @@ cdef class Matroid(SageObject): - ``X`` -- a subset (or any iterable) of the groundset - OUTPUT: - - A subset of ``X``. + OUTPUT: a subset of ``X`` .. SEEALSO:: @@ -1783,9 +1717,7 @@ cdef class Matroid(SageObject): - ``X`` -- a subset (or any iterable) of the groundset - OUTPUT: - - A set of elements containing ``X``. + OUTPUT: a superset of ``X`` .. SEEALSO:: @@ -1816,9 +1748,7 @@ cdef class Matroid(SageObject): - ``X`` -- (default: the groundset) a subset (or any iterable) of the groundset - OUTPUT: - - A set of elements. + OUTPUT: a set of elements - If ``X`` is not ``None``, the output is a cocircuit contained in ``X`` if such a cocircuit exists. Otherwise an error is raised. @@ -1868,9 +1798,7 @@ cdef class Matroid(SageObject): - ``B`` -- a basis of the matroid. - ``e`` -- an element of ``B``. - OUTPUT: - - A set of elements. + OUTPUT: a set of elements .. SEEALSO:: @@ -1898,9 +1826,7 @@ cdef class Matroid(SageObject): A *loop* is an element `u` of the groundset such that the one-element set `\{ u \}` is dependent. - OUTPUT: - - A set of elements. + OUTPUT: a set of elements EXAMPLES:: @@ -1920,9 +1846,7 @@ cdef class Matroid(SageObject): - ``X`` -- a subset (or any iterable) of the groundset - OUTPUT: - - Boolean. + OUTPUT: boolean EXAMPLES:: @@ -1946,9 +1870,7 @@ cdef class Matroid(SageObject): - ``X`` -- a subset (or any iterable) of the groundset - OUTPUT: - - Boolean. + OUTPUT: boolean EXAMPLES:: @@ -1972,9 +1894,7 @@ cdef class Matroid(SageObject): - ``X`` -- a subset (or any iterable) of the groundset - OUTPUT: - - Boolean. + OUTPUT: boolean EXAMPLES:: @@ -2004,9 +1924,7 @@ cdef class Matroid(SageObject): - ``X`` -- a subset (or any iterable) of the groundset - OUTPUT: - - Boolean. + OUTPUT: boolean .. SEEALSO:: @@ -2038,9 +1956,7 @@ cdef class Matroid(SageObject): - ``X`` -- a subset (or any iterable) of the groundset - ``k`` -- a positive integer - OUTPUT: - - Boolean. + OUTPUT: boolean .. SEEALSO:: @@ -2091,9 +2007,7 @@ cdef class Matroid(SageObject): - ``X`` -- a subset (or any iterable) of the groundset - OUTPUT: - - Boolean. + OUTPUT: boolean EXAMPLES:: @@ -2117,9 +2031,7 @@ cdef class Matroid(SageObject): one-element set `\{ u \}` is a cocircuit. In other words, a coloop is a loop of the dual of the matroid. - OUTPUT: - - A set of elements. + OUTPUT: a set of elements .. SEEALSO:: @@ -2146,9 +2058,7 @@ cdef class Matroid(SageObject): - ``X`` -- a subset (or any iterable) of the groundset - OUTPUT: - - Boolean. + OUTPUT: boolean .. SEEALSO:: @@ -2179,9 +2089,7 @@ cdef class Matroid(SageObject): - ``X`` -- a subset (or any iterable) of the groundset - OUTPUT: - - Boolean. + OUTPUT: boolean .. SEEALSO:: @@ -2213,9 +2121,7 @@ cdef class Matroid(SageObject): - ``X`` -- a subset (or any iterable) of the groundset - OUTPUT: - - Boolean. + OUTPUT: boolean .. SEEALSO:: @@ -2250,9 +2156,7 @@ cdef class Matroid(SageObject): - ``X`` -- a subset (or any iterable) of the groundset - OUTPUT: - - Boolean. + OUTPUT: boolean .. SEEALSO:: @@ -2283,9 +2187,7 @@ cdef class Matroid(SageObject): - ``X`` -- a subset (or any iterable) of the groundset - OUTPUT: - - Boolean. + OUTPUT: boolean .. SEEALSO:: @@ -2322,9 +2224,7 @@ cdef class Matroid(SageObject): Certain subclasses may check other axioms instead. - OUTPUT: - - Boolean. + OUTPUT: boolean EXAMPLES:: @@ -2339,13 +2239,21 @@ cdef class Matroid(SageObject): ....: 3: [[1, 2, 3, 4, 5], [1, 2, 3, 6, 7], [1, 4, 5, 6, 7]]}) sage: M.is_valid() False + + TESTS:: + + sage: def r(X): + ....: return -1 + sage: M = Matroid(groundset=[0,1,2], rank_function=r) + sage: M.is_valid() + False """ E = list(self.groundset()) for i in range(len(E) + 1): for X in combinations(E, i): XX = frozenset(X) rX = self._rank(XX) - if rX > i: + if rX > i or rX < 0: return False for j in range(i, len(E) + 1): for Y in combinations(E, j): @@ -2362,11 +2270,9 @@ cdef class Matroid(SageObject): cpdef circuits(self) noexcept: """ - Return the list of circuits of the matroid. - - OUTPUT: + Return the circuits of the matroid. - An iterable containing all circuits. + OUTPUT: a SetSystem .. SEEALSO:: @@ -2383,21 +2289,52 @@ cdef class Matroid(SageObject): ['c', 'f', 'g'], ['d', 'e', 'f']] """ C = set() - for B in self.bases(): - C.update([self._circuit(B.union(set([e]))) - for e in self.groundset().difference(B)]) - return list(C) + B_ext = set() + for B in self.bases_iterator(): + for e in B ^ self.groundset(): + B_ext.add(B | set([e])) + for S in B_ext: + C.add(self._circuit(S)) + return SetSystem(list(self.groundset()), C) + + def circuits_iterator(self, k=None): + """ + Return an iterator over the circuits of the matroid. + + .. SEEALSO:: + + :meth:`M.circuit() ` + + EXAMPLES:: + + sage: M = matroids.catalog.Fano() + sage: sorted([sorted(C) for C in M.circuits_iterator()]) + [['a', 'b', 'c', 'g'], ['a', 'b', 'd', 'e'], ['a', 'b', 'f'], + ['a', 'c', 'd', 'f'], ['a', 'c', 'e'], ['a', 'd', 'g'], + ['a', 'e', 'f', 'g'], ['b', 'c', 'd'], ['b', 'c', 'e', 'f'], + ['b', 'd', 'f', 'g'], ['b', 'e', 'g'], ['c', 'd', 'e', 'g'], + ['c', 'f', 'g'], ['d', 'e', 'f']] + """ + if k and k < self.rank() + 2: + for X in combinations(self.groundset(), k): + X = frozenset(X) + if self._is_circuit(X): + yield X + elif not k: + for k in range(self.rank() + 2): + for X in combinations(self.groundset(), k): + X = frozenset(X) + if self._is_circuit(X): + yield X cpdef nonspanning_circuits(self) noexcept: """ - Return the list of nonspanning circuits of the matroid. + Return the nonspanning circuits of the matroid. A *nonspanning circuit* is a circuit whose rank is strictly smaller than the rank of the matroid. - OUTPUT: - - An iterable containing all nonspanning circuits. + OUTPUT: a SetSystem .. SEEALSO:: @@ -2412,19 +2349,44 @@ cdef class Matroid(SageObject): ['b', 'c', 'd'], ['b', 'e', 'g'], ['c', 'f', 'g'], ['d', 'e', 'f']] """ - C = set() - for N in self.nonbases(): + cdef SetSystem C + C = SetSystem(list(self.groundset())) + for N in self.nonbases_iterator(): if self._rank(N) == self.full_rank() - 1: - C.add(self._circuit(N)) - return list(C) + C.append(self._circuit(N)) + return C - cpdef cocircuits(self) noexcept: + def nonspanning_circuits_iterator(self): """ - Return the list of cocircuits of the matroid. + Return an iterator over the nonspanning circuits of the matroid. - OUTPUT: + A *nonspanning circuit* is a circuit whose rank is strictly smaller + than the rank of the matroid. + + .. SEEALSO:: - An iterable containing all cocircuits. + :meth:`M.circuit() `, + :meth:`M.rank() ` + + EXAMPLES:: + + sage: M = matroids.catalog.Fano() + sage: sorted([sorted(C) for C in M.nonspanning_circuits_iterator()]) + [['a', 'b', 'f'], ['a', 'c', 'e'], ['a', 'd', 'g'], + ['b', 'c', 'd'], ['b', 'e', 'g'], ['c', 'f', 'g'], + ['d', 'e', 'f']] + """ + for k in range(self.rank() + 1): + for X in combinations(self.groundset(), k): + X = frozenset(X) + if self._is_circuit(X): + yield X + + cpdef cocircuits(self) noexcept: + """ + Return the cocircuits of the matroid. + + OUTPUT: a SetSystem .. SEEALSO:: @@ -2439,20 +2401,39 @@ cdef class Matroid(SageObject): ['c', 'd', 'e', 'g']] """ C = set() - for B in self.bases(): + for B in self.bases_iterator(): + C.update([self._cocircuit(self.groundset().difference(B).union(set([e]))) for e in B]) + return list(C) + + def cocircuits_iterator(self): + """ + Return an iterator over the cocircuits of the matroid. + + .. SEEALSO:: + + :meth:`M.cocircuit() ` + + EXAMPLES:: + + sage: M = matroids.catalog.Fano() + sage: sorted([sorted(C) for C in M.cocircuits_iterator()]) + [['a', 'b', 'c', 'g'], ['a', 'b', 'd', 'e'], ['a', 'c', 'd', 'f'], + ['a', 'e', 'f', 'g'], ['b', 'c', 'e', 'f'], ['b', 'd', 'f', 'g'], + ['c', 'd', 'e', 'g']] + """ + C = set() + for B in self.bases_iterator(): C.update([self._cocircuit(self.groundset().difference(B).union(set([e]))) for e in B]) return list(C) cpdef noncospanning_cocircuits(self) noexcept: """ - Return the list of noncospanning cocircuits of the matroid. + Return the noncospanning cocircuits of the matroid. A *noncospanning cocircuit* is a cocircuit whose corank is strictly smaller than the corank of the matroid. - OUTPUT: - - An iterable containing all nonspanning circuits. + OUTPUT: a SetSystem .. SEEALSO:: @@ -2471,14 +2452,12 @@ cdef class Matroid(SageObject): cpdef circuit_closures(self) noexcept: """ - Return the list of closures of circuits of the matroid. + Return the closures of circuits of the matroid. A *circuit closure* is a closed set containing a circuit. - OUTPUT: - - A dictionary containing the circuit closures of the matroid, indexed - by their ranks. + OUTPUT: a dictionary containing the circuit closures of the matroid, + indexed by their ranks .. SEEALSO:: @@ -2501,21 +2480,19 @@ cdef class Matroid(SageObject): [['a', 'b', 'c', 'd', 'e', 'f', 'g']] """ CC = [set([]) for r in range(self.rank() + 1)] - for C in self.circuits(): + for C in self.circuits_iterator(): CC[len(C) - 1].add(self.closure(C)) return {r: CC[r] for r in range(self.rank() + 1) if CC[r]} cpdef nonspanning_circuit_closures(self) noexcept: """ - Return the list of closures of nonspanning circuits of the matroid. + Return the closures of nonspanning circuits of the matroid. A *nonspanning circuit closure* is a closed set containing a nonspanning circuit. - OUTPUT: - - A dictionary containing the nonspanning circuit closures of the - matroid, indexed by their ranks. + OUTPUT: a dictionary containing the nonspanning circuit closures of the + matroid, indexed by their ranks .. SEEALSO:: @@ -2534,20 +2511,18 @@ cdef class Matroid(SageObject): KeyError: 3 """ CC = [set([]) for r in range(self.rank() + 1)] - for C in self.nonspanning_circuits(): + for C in self.nonspanning_circuits_iterator(): CC[len(C) - 1].add(self.closure(C)) return {r: CC[r] for r in range(self.rank() + 1) if CC[r]} cpdef nonbases(self) noexcept: r""" - Return the list of nonbases of the matroid. + Return the nonbases of the matroid. A *nonbasis* is a set with cardinality ``self.full_rank()`` that is not a basis. - OUTPUT: - - An iterable containing the nonbases of the matroid. + OUTPUT: a SetSystem .. SEEALSO:: @@ -2572,6 +2547,33 @@ cdef class Matroid(SageObject): res.append(X) return res + def nonbases_iterator(self): + r""" + Return an iterator over the nonbases of the matroid. + + A *nonbasis* is a set with cardinality ``self.full_rank()`` that is + not a basis. + + .. SEEALSO:: + + :meth:`M.basis() ` + + EXAMPLES:: + + sage: M = matroids.Uniform(2, 4) + sage: list(M.nonbases_iterator()) + [] + sage: [sorted(X) for X in matroids.catalog.P6().nonbases_iterator()] + [['a', 'b', 'c']] + + ALGORITHM: + + Test all subsets of the groundset of cardinality ``self.full_rank()`` + """ + for X in combinations(self.groundset(), self.full_rank()): + if self._rank(X) < len(X): + yield frozenset(X) + cpdef dependent_r_sets(self, long r) noexcept: r""" Return the list of dependent subsets of fixed size. @@ -2580,10 +2582,6 @@ cdef class Matroid(SageObject): - ``r`` -- a nonnegative integer. - OUTPUT: - - An iterable containing all dependent subsets of size ``r``. - EXAMPLES:: sage: M = matroids.catalog.Vamos() @@ -2605,15 +2603,40 @@ cdef class Matroid(SageObject): res.append(X) return res + def dependent_r_sets_iterator(self, long r): + r""" + Return an iterator over the dependent subsets of fixed size. + + INPUT: + + - ``r`` -- a nonnegative integer. + + EXAMPLES:: + + sage: M = matroids.catalog.Vamos() + sage: list(M.dependent_r_sets_iterator(3)) + [] + sage: sorted([sorted(X) for X in + ....: matroids.catalog.Vamos().dependent_r_sets_iterator(4)]) + [['a', 'b', 'c', 'd'], ['a', 'b', 'e', 'f'], ['a', 'b', 'g', 'h'], + ['c', 'd', 'e', 'f'], ['e', 'f', 'g', 'h']] + + ALGORITHM: + + Test all subsets of the groundset of cardinality ``r`` + """ + for X in combinations(self.groundset(), r): + X = frozenset(X) + if self._rank(X) < len(X): + yield X + cpdef bases(self) noexcept: r""" - Return the list of bases of the matroid. + Return the bases of the matroid. A *basis* is a maximal independent set. - OUTPUT: - - An iterable containing all bases of the matroid. + OUTPUT: a SetSystem EXAMPLES:: @@ -2636,13 +2659,33 @@ cdef class Matroid(SageObject): res.append(X) return res - cpdef independent_sets(self) noexcept: + def bases_iterator(self): r""" - Return the list of independent subsets of the matroid. + Return an iterator over the bases of the matroid. - OUTPUT: + A *basis* is a maximal independent set. + + EXAMPLES:: - An iterable containing all independent subsets of the matroid. + sage: M = matroids.Uniform(2, 4) + sage: sorted([sorted(X) for X in M.bases_iterator()]) + [[0, 1], [0, 2], [0, 3], [1, 2], [1, 3], [2, 3]] + + ALGORITHM: + + Test all subsets of the groundset of cardinality ``self.full_rank()`` + + .. SEEALSO:: + + :meth:`M.independent_r_sets() ` + """ + for X in combinations(self.groundset(), self.full_rank()): + if self._rank(frozenset(X)) == len(X): + yield frozenset(X) + + cpdef independent_sets(self) noexcept: + r""" + Return the list of independent subsets of the matroid. EXAMPLES:: @@ -2672,12 +2715,50 @@ cdef class Matroid(SageObject): r -= 1 elif T[r]: I[r+1] = I[r].union([T[r].pop()]) - res.append(I[r+1]) + res.append(I[r+1]) + T[r+1] = T[r] - self._closure(I[r+1]) + r += 1 + else: + r -= 1 + return res + + def independent_sets_iterator(self): + r""" + Return an iterator over the independent subsets of the matroid. + + EXAMPLES:: + + sage: M = matroids.catalog.Pappus() + sage: I = list(M.independent_sets_iterator()) + sage: len(I) + 121 + + .. SEEALSO:: + + :meth:`M.independent_r_sets() ` + """ + cdef int r + cdef int full_rank = self.full_rank() + cdef list T = [set() for r in range(full_rank)] + cdef list I = [frozenset()] * (full_rank+1) + r = 0 + + yield frozenset() + T[0] = set(self.groundset()) - self.closure([]) + while r >= 0: + if r + 1 == full_rank: + for x in T[r]: + I[r+1] = I[r].union([x]) + yield I[r+1] + T[r] = set() + r -= 1 + elif T[r]: + I[r+1] = I[r].union([T[r].pop()]) + yield I[r+1] T[r+1] = T[r] - self._closure(I[r+1]) r += 1 else: r -= 1 - return res cpdef independent_r_sets(self, long r) noexcept: r""" @@ -2687,11 +2768,6 @@ cdef class Matroid(SageObject): - ``r`` -- a nonnegative integer. - OUTPUT: - - An iterable containing all independent subsets of the matroid of - cardinality ``r``. - EXAMPLES:: sage: M = matroids.catalog.Pappus() @@ -2719,6 +2795,40 @@ cdef class Matroid(SageObject): res.append(X) return res + def independent_r_sets_iterator(self, r): + r""" + Return an iterator over the size-``r`` independent subsets of the + matroid. + + INPUT: + + - ``r`` -- a nonnegative integer. + + EXAMPLES:: + + sage: M = matroids.catalog.Pappus() + sage: list(M.independent_r_sets_iterator(4)) + [] + sage: S = list(M.independent_r_sets_iterator(3)) + sage: len(S) + 75 + sage: frozenset({'a', 'c', 'e'}) in S + True + + ALGORITHM: + + Test all subsets of the groundset of cardinality ``r`` + + .. SEEALSO:: + + :meth:`M.independent_sets() ` + :meth:`M.bases() ` + """ + for X in combinations(self.groundset(), r): + X = frozenset(X) + if self._rank(X) == len(X): + yield X + cpdef _extend_flags(self, flags) noexcept: r""" Recursion for the ``self._flags(r)`` method. @@ -2793,9 +2903,7 @@ cdef class Matroid(SageObject): - ``r`` -- A natural number. - OUTPUT: - - An iterable containing all flats of rank ``r``. + OUTPUT: a SetSystem .. SEEALSO:: @@ -2809,8 +2917,7 @@ cdef class Matroid(SageObject): ['b', 'c', 'd'], ['b', 'e', 'g'], ['c', 'f', 'g'], ['d', 'e', 'f']] """ - return SetSystem(list(self.groundset()), - subsets=[f[0] for f in self._flags(r)]) + return SetSystem(list(self.groundset()), subsets=[f[0] for f in self._flags(r)]) cpdef coflats(self, r) noexcept: r""" @@ -2822,9 +2929,7 @@ cdef class Matroid(SageObject): - ``r`` -- a nonnegative integer. - OUTPUT: - - An iterable containing all coflats of corank ``r``. + OUTPUT: a SetSystem .. SEEALSO:: @@ -2862,9 +2967,7 @@ cdef class Matroid(SageObject): A *hyperplane* is a flat of rank ``self.full_rank() - 1``. A *flat* is a closed set. - OUTPUT: - - An iterable containing all hyperplanes of the matroid. + OUTPUT: an iterable containing all hyperplanes of the matroid .. SEEALSO:: @@ -2882,32 +2985,90 @@ cdef class Matroid(SageObject): r""" Return the `f`-vector of the matroid. - The `f`-*vector* is a vector `(f_0, ..., f_r)`, where `f_i` is the - number of flats of rank `i`, and `r` is the rank of the matroid. + The `f`-*vector* is a vector `(f_0, \dots, f_r)`, where `f_i` is the + number of independent sets of rank `i`, and `r` is the rank of the + matroid. - OUTPUT: + OUTPUT: a list of integers + + EXAMPLES:: + + sage: M = matroids.catalog.Vamos() + sage: M.f_vector() + [1, 8, 28, 56, 65] + + TESTS:: - List of integers. + sage: for M in matroids.AllMatroids(5): # optional - matroid_database + ....: assert M.f_vector() == SimplicialComplex(M.bases()).f_vector() + """ + cdef list f = [] + cdef int i, sum + for i in range(self.full_rank() + 1): + sum = 0 + for _ in self.independent_r_sets_iterator(i): + sum += 1 + f.append(sum) + return f + + cpdef whitney_numbers(self) noexcept: + r""" + Return the Whitney numbers of the first kind of the matroid. + + The Whitney numbers of the first kind -- here encoded as a vector + `(w_0=1, ..., w_r)` -- are numbers of alternating sign, where `w_i` is + the value of the coefficient of the `(r-i)`-th degree term of the + matroid's characteristic polynomial. Moreover, `|w_i|` is the number of + `i`-faces of the broken circuit complex of the matroid. + + OUTPUT: a list of integers EXAMPLES:: sage: M = matroids.catalog.BetsyRoss() - sage: M.f_vector() + sage: M.whitney_numbers() + [1, -11, 35, -25] + + TESTS:: + + sage: M = Matroid(groundset=[0,1,2], circuits=[[0]]) + sage: M.whitney_numbers() + [] + """ + cdef list abs_w = [0] * (self.rank()+1) + for S in self.no_broken_circuits_sets_iterator(): + abs_w[len(S)] += 1 + return [(-1)**i * abs_w[i] for i in range(len(abs_w)) if abs_w[i] != 0] + + cpdef whitney_numbers2(self) noexcept: + r""" + Return the Whitney numbers of the second kind of the matroid. + + The Whitney numbers of the second kind are here encoded as a vector + `(W_0, ..., W_r)`, where `W_i` is the number of flats of rank `i`, and + `r` is the rank of the matroid. + + OUTPUT: a list of integers + + EXAMPLES:: + + sage: M = matroids.catalog.BetsyRoss() + sage: M.whitney_numbers2() [1, 11, 20, 1] """ loops = self._closure(set()) flags = [[loops, set(), self.groundset() - loops]] - f_vec = [1] + W = [1] for r in range(self.full_rank()): flags = self._extend_flags(flags) - f_vec.append(len(flags)) - return f_vec + W.append(len(flags)) + return W cpdef broken_circuits(self, ordering=None) noexcept: r""" Return the list of broken circuits of ``self``. - Let `M` be a matroid with ground set `E`, and let `<` be a total + Let `M` be a matroid with groundset `E`, and let `<` be a total ordering on `E`. A *broken circuit* for `M` means a subset `B` of `E` such that there exists a `u \in E` for which `B \cup \{ u \}` is a circuit of `M` and `u < b` for all `b \in B`. @@ -2931,17 +3092,17 @@ cdef class Matroid(SageObject): [[1, 2], [1, 4], [2, 3, 4]] """ if ordering is None: - ordering = sorted(self.groundset()) + ordering = sorted(self.groundset(), key=str) else: orderset = frozenset(ordering) if len(orderset) != len(self.groundset()) or orderset != self.groundset(): - raise ValueError("not an ordering of the ground set") + raise ValueError("not an ordering of the groundset") ordering = list(ordering) ret = set() - for C in self.circuits(): + for C in self.circuits_iterator(): for k in ordering: if k in C: - ret.add(C.difference([k])) + ret.add(frozenset(C).difference([k])) break return frozenset(ret) @@ -2949,7 +3110,7 @@ cdef class Matroid(SageObject): r""" Return the no broken circuits (NBC) sets of ``self``. - An NBC set is a subset `A` of the ground set under some total + An NBC set is a subset `A` of the groundset under some total ordering `<` such that `A` contains no broken circuit. INPUT: @@ -2990,10 +3151,12 @@ cdef class Matroid(SageObject): minimal-removal convention, while the implementation is not modified from the published algorithm. """ - cdef list rev_order + if len(self.loops()) > 0: + return [] + cdef list rev_order if ordering is None: - rev_order = sorted(self.groundset(), reverse=True) + rev_order = sorted(self.groundset(), key=str, reverse=True) else: if frozenset(ordering) != self.groundset(): raise ValueError("not an ordering of the groundset") @@ -3006,8 +3169,7 @@ cdef class Matroid(SageObject): cdef dict reverse_dict = {value: key for key, value in enumerate(rev_order)} cdef list H, Ht, temp, B = [frozenset()] - cdef frozenset loops = self.loops() - cdef list next_level = [[val] for val in rev_order if val not in loops] + cdef list next_level = [[val] for val in rev_order] cdef list cur_level cdef Py_ssize_t i = 0 cdef Py_ssize_t tp @@ -3032,6 +3194,68 @@ cdef class Matroid(SageObject): next_level.extend(Ht) return B + def no_broken_circuits_sets_iterator(self, ordering=None): + r""" + Return an iterator over no broken circuits (NBC) sets of ``self``. + + An NBC set is a subset `A` of the groundset under some total + ordering `<` such that `A` contains no broken circuit. + + INPUT: + + - ``ordering`` -- a total ordering of the groundset given as a list + + EXAMPLES:: + + sage: M = Matroid(circuits=[[1,2,3], [3,4,5], [1,2,4,5]]) + sage: SimplicialComplex(list(M.no_broken_circuits_sets_iterator())) + Simplicial complex with vertex set (1, 2, 3, 4, 5) + and facets {(1, 2, 4), (1, 2, 5), (1, 3, 4), (1, 3, 5)} + sage: SimplicialComplex(list(M.no_broken_circuits_sets_iterator([5,4,3,2,1]))) + Simplicial complex with vertex set (1, 2, 3, 4, 5) + and facets {(1, 3, 5), (1, 4, 5), (2, 3, 5), (2, 4, 5)} + + :: + + sage: M = Matroid(circuits=[[1,2,3], [1,4,5], [2,3,4,5]]) + sage: SimplicialComplex(list(M.no_broken_circuits_sets_iterator([5,4,3,2,1]))) + Simplicial complex with vertex set (1, 2, 3, 4, 5) + and facets {(1, 3, 5), (2, 3, 5), (2, 4, 5), (3, 4, 5)} + """ + if len(self.loops()) == 0: + + if ordering is None: + rev_order = sorted(self.groundset(), key=str, reverse=True) + else: + if frozenset(ordering) != self.groundset(): + raise ValueError("not an ordering of the groundset") + rev_order = list(reversed(ordering)) + + Tmax = len(rev_order) + reverse_dict = {value: key for key, value in enumerate(rev_order)} + + yield frozenset() + next_level = [[val] for val in rev_order] + i = 0 + level = -1 + while next_level: + cur_level = next_level + next_level = [] + level += 1 + for H in cur_level: + tp = ( reverse_dict[H[level]]) + 1 + is_indep = True + Ht = [None] * (Tmax-tp) + for i in range(tp, Tmax): + temp = H + [rev_order[i]] + if not self._is_independent(frozenset(temp)): + is_indep = False + break + Ht[i-tp] = temp + if is_indep: + yield frozenset(H) + next_level.extend(Ht) + def orlik_solomon_algebra(self, R, ordering=None, **kwargs): """ Return the Orlik-Solomon algebra of ``self``. @@ -3039,13 +3263,13 @@ cdef class Matroid(SageObject): INPUT: - ``R`` -- the base ring - - ``ordering`` -- (optional) an ordering of the ground set - - optional parameter ``invariant`` -- (optional, default: None) either - a semigroup ``G`` whose ``__call__`` acts on the groundset, or pair - ``(G, action)`` where ``G`` is a semigroup and ``action`` - is a function ``action(g,e)`` which takes a pair of a group - element and a grounset element and returns the groundset - element which is the result of ``e`` acted upon by ``g`` + - ``ordering`` -- (optional) an ordering of the groundset + - ``invariant`` -- (optional, default: None) either a semigroup ``G`` + whose ``__call__`` acts on the groundset, or pair ``(G, action)`` + where ``G`` is a semigroup and ``action`` is a function + ``action(g,e)`` which takes a pair of a group element and a grounset + element and returns the groundset element which is the result of + ``e`` acted upon by ``g`` .. SEEALSO:: @@ -3133,8 +3357,7 @@ cdef class Matroid(SageObject): n = self.size() vector_e = FreeModule(ZZ, n).basis() convert = {ind: i for i, ind in enumerate(self.groundset())} - vertices = [sum(vector_e[convert[i]] for i in B) - for B in self.bases()] + vertices = [sum(vector_e[convert[i]] for i in B) for B in self.bases_iterator()] return Polyhedron(vertices) def independence_matroid_polytope(self): @@ -3177,8 +3400,7 @@ cdef class Matroid(SageObject): ambient = FreeModule(ZZ, n) vector_e = ambient.basis() convert = {ind: i for i, ind in enumerate(self.groundset())} - vertices = [ambient.sum(vector_e[convert[i]] for i in IS) - for IS in self.independent_sets()] + vertices = [ambient.sum(vector_e[convert[i]] for i in IS) for IS in self.independent_sets_iterator()] return Polyhedron(vertices) # isomorphism and equality @@ -3193,13 +3415,11 @@ cdef class Matroid(SageObject): INPUT: - - ``other`` -- A matroid, - - optional parameter ``certificate`` -- Boolean. - - OUTPUT: + - ``other`` -- a matroid, + - ``certificate`` -- boolean (default: ``False``) - Boolean, - and, if certificate = True, a dictionary or None + OUTPUT: boolean, and, if ``certificate = True``, a dictionary or + ``None`` EXAMPLES:: @@ -3236,12 +3456,10 @@ cdef class Matroid(SageObject): INPUT: - ``other`` -- A matroid, - - optional parameter ``certificate`` -- Boolean. + - ``certificate`` -- boolean (default: ``False``). - OUTPUT: - - Boolean, - and, if ``certificate=True``, a dictionary giving the isomorphism or ``None`` + OUTPUT: boolean, and, if ``certificate=True``, a dictionary giving the + isomorphism or ``None`` .. NOTE:: @@ -3265,7 +3483,7 @@ cdef class Matroid(SageObject): return self._is_isomorphic(other), self._isomorphism(other) if self is other: return True - return (self.full_rank() == other.full_rank() and self.nonbases()._isomorphism(other.nonbases()) is not None) + return (self.full_rank() == other.full_rank() and SetSystem(list(self.groundset()), list(self.nonbases()))._isomorphism(SetSystem(list(other.groundset()), list(other.nonbases()))) is not None) cpdef isomorphism(self, other) noexcept: r""" @@ -3278,11 +3496,9 @@ cdef class Matroid(SageObject): INPUT: - - ``other`` -- A matroid. - - OUTPUT: + - ``other`` -- a matroid - A dictionary, or ``None``. + OUTPUT: a dictionary, or ``None`` EXAMPLES:: @@ -3314,11 +3530,9 @@ cdef class Matroid(SageObject): INPUT: - - ``other`` -- A matroid. + - ``other`` -- a matroid - OUTPUT: - - A dictionary, or ``None`` + OUTPUT: a dictionary, or ``None`` EXAMPLES:: @@ -3335,7 +3549,7 @@ cdef class Matroid(SageObject): if self is other: return {e:e for e in self.groundset()} if self.full_rank() == other.full_rank(): - return self.nonbases()._isomorphism(other.nonbases()) + return SetSystem(list(self.groundset()), list(self.nonbases()))._isomorphism(SetSystem(list(other.groundset()), list(other.nonbases()))) else: return None @@ -3349,11 +3563,9 @@ cdef class Matroid(SageObject): INPUT: - - ``other`` -- A matroid. - - OUTPUT: + - ``other`` -- a matroid - Boolean. + OUTPUT: boolean .. NOTE:: @@ -3435,13 +3647,11 @@ cdef class Matroid(SageObject): INPUT: - - ``other`` -- A matroid. - - ``morphism`` -- A map. Can be, for instance, - a dictionary, function, or permutation. + - ``other`` -- a matroid + - ``morphism`` -- a map; can be, for instance, a dictionary, function, + or permutation - OUTPUT: - - Boolean. + OUTPUT: boolean .. SEEALSO:: @@ -3574,13 +3784,11 @@ cdef class Matroid(SageObject): INPUT: - - ``other`` -- A matroid instance. + - ``other`` -- a matroid - ``morphism`` -- a dictionary mapping the groundset of ``self`` to the groundset of ``other``. - OUTPUT: - - Boolean. + OUTPUT: boolean EXAMPLES:: @@ -3699,13 +3907,11 @@ cdef class Matroid(SageObject): INPUT: - ``contractions`` -- (default: ``None``) an element or set of - elements to be contracted. + elements to be contracted - ``deletions`` -- (default: ``None``) an element or set of elements - to be deleted. + to be deleted - OUTPUT: - - A matroid. + OUTPUT: a matroid .. NOTE:: @@ -3820,11 +4026,9 @@ cdef class Matroid(SageObject): INPUT: - ``X`` -- Either a single element of the groundset, or a collection - of elements. - - OUTPUT: + of elements - The matroid obtained by contracting the element(s) in ``X``. + OUTPUT: the matroid obtained by contracting the element(s) in ``X`` .. SEEALSO:: @@ -3899,11 +4103,9 @@ cdef class Matroid(SageObject): INPUT: - ``X`` -- Either a single element of the groundset, or a collection - of elements. + of elements - OUTPUT: - - The matroid obtained by deleting the element(s) in ``X``. + OUTPUT: the matroid obtained by deleting the element(s) in ``X`` .. SEEALSO:: @@ -3950,7 +4152,7 @@ cdef class Matroid(SageObject): r""" Shorthand for ``self.delete(X)``. - DEPRECATED + Deprecated. EXAMPLES:: @@ -3967,7 +4169,7 @@ cdef class Matroid(SageObject): r""" Return the dual of the matroid. - Let `M` be a matroid with ground set `E`. If `B` is the set of bases + Let `M` be a matroid with groundset `E`. If `B` is the set of bases of `M`, then the set `\{E - b : b \in B\}` is the set of bases of another matroid, the *dual* of `M`. @@ -3976,10 +4178,6 @@ cdef class Matroid(SageObject): This function wraps ``self`` in a ``DualMatroid`` object. For more efficiency, subclasses that can, should override this method. - OUTPUT: - - The dual matroid. - EXAMPLES:: sage: M = matroids.catalog.Pappus() @@ -4006,9 +4204,7 @@ cdef class Matroid(SageObject): can be obtained by adding an element freely to the span of the matroid and then contracting that element. - OUTPUT: - - A matroid. + OUTPUT: a matroid .. SEEALSO:: @@ -4036,13 +4232,11 @@ cdef class Matroid(SageObject): INPUT: - ``N`` -- An instance of a ``Matroid`` object, - - ``certificate`` -- Boolean (Default: ``False``) If ``True``, returns + - ``certificate`` -- boolean (Default: ``False``) If ``True``, returns ``True, (X, Y, dic) where ``N`` is isomorphic to ``self.minor(X, Y)``, and ``dic`` is an isomorphism between ``N`` and ``self.minor(X, Y)``. - OUTPUT: - - boolean or tuple. + OUTPUT: boolean or tuple .. SEEALSO:: @@ -4096,9 +4290,7 @@ cdef class Matroid(SageObject): where ``F`` is a flat and ``self.minor(contractions=F)`` has a `U_{2,k}` restriction or ``(False, None)``. - OUTPUT: - - Boolean or tuple. + OUTPUT: boolean or tuple .. SEEALSO:: @@ -4161,9 +4353,7 @@ cdef class Matroid(SageObject): - ``hyperlines`` -- (default: None) a set of flats of codimension 2. The flats are assumed to be ``frozenset`` compatible. - OUTPUT: - - Boolean or tuple. + OUTPUT: boolean or tuple EXAMPLES:: @@ -4213,9 +4403,7 @@ cdef class Matroid(SageObject): each of these. If not specified, the element is assumed to be in the span of the full groundset. - OUTPUT: - - A matroid. + OUTPUT: a matroid .. NOTE:: @@ -4292,9 +4480,7 @@ cdef class Matroid(SageObject): of each of these. If not specified, the element is assumed to be in the cospan of the full groundset. - OUTPUT: - - A matroid. + OUTPUT: a matroid .. SEEALSO:: @@ -4363,11 +4549,9 @@ cdef class Matroid(SageObject): INPUT: - - ``subsets`` -- A collection of subsets of the groundset. - - OUTPUT: + - ``subsets`` -- a collection of subsets of the groundset - A collection of subsets. + OUTPUT: a collection of subsets .. SEEALSO:: @@ -4446,12 +4630,10 @@ cdef class Matroid(SageObject): `e` that does not contain a minor `N` isomorphic to `U_{2, k}`, where ``k > line_length``, and such that `e \in E(N)`. - ``subsets`` -- (default: ``None``) a collection of subsets of the - ground set. If given, restricts the output to linear subclasses such + groundset. If given, restricts the output to linear subclasses such that each hyperplane contains an element of ``subsets``. - OUTPUT: - - An iterable collection of linear subclasses. + OUTPUT: an iterable collection of linear subclasses .. NOTE:: @@ -4513,14 +4695,12 @@ cdef class Matroid(SageObject): - ``line_length`` -- (optional) a natural number. If given, restricts the output to extensions that do not contain a `U_{2, k}` minor where ``k > line_length``. - - ``subsets`` -- (optional) a collection of subsets of the ground set. + - ``subsets`` -- (optional) a collection of subsets of the groundset. If given, restricts the output to extensions where the new element is contained in all hyperplanes that contain an element of ``subsets``. - OUTPUT: - - An iterable containing matroids. + OUTPUT: an iterable containing matroids .. NOTE:: @@ -4579,14 +4759,12 @@ cdef class Matroid(SageObject): - ``coline_length`` -- (optional) a natural number. If given, restricts the output to coextensions that do not contain a `U_{k - 2, k}` minor where ``k > coline_length``. - - ``subsets`` -- (optional) a collection of subsets of the ground set. + - ``subsets`` -- (optional) a collection of subsets of the groundset. If given, restricts the output to extensions where the new element is contained in all cohyperplanes that contain an element of ``subsets``. - OUTPUT: - - An iterable containing matroids. + OUTPUT: an iterable containing matroids .. NOTE:: @@ -4631,9 +4809,7 @@ cdef class Matroid(SageObject): parallel class (a closed set of rank 1, that is, each pair in it forms a circuit of length 2). - OUTPUT: - - A matroid. + OUTPUT: a matroid .. SEEALSO:: @@ -4669,9 +4845,7 @@ cdef class Matroid(SageObject): element from each series class (a coclosed set of rank 1, that is, each pair in it forms a cocircuit of length 2). - OUTPUT: - - A matroid. + OUTPUT: a matroid .. SEEALSO:: @@ -4703,9 +4877,7 @@ cdef class Matroid(SageObject): A matroid is *simple* if it contains no circuits of length 1 or 2. - OUTPUT: - - Boolean. + OUTPUT: boolean .. SEEALSO:: @@ -4739,9 +4911,7 @@ cdef class Matroid(SageObject): Dual method of :meth:`M.is_simple() `. - OUTPUT: - - Boolean. + OUTPUT: boolean .. SEEALSO:: @@ -4775,9 +4945,7 @@ cdef class Matroid(SageObject): deleting the complement of that subset is :meth:`connected `. - OUTPUT: - - A list of subsets. + OUTPUT: a list of subsets .. SEEALSO:: @@ -4815,9 +4983,7 @@ cdef class Matroid(SageObject): groundset with `X, Y` nonempty and `r(X) + r(Y) = r(X\cup Y)`. A matroid is *connected* if it has no separations. - OUTPUT: - - Boolean. + OUTPUT: boolean .. SEEALSO:: @@ -4867,9 +5033,7 @@ cdef class Matroid(SageObject): - ``T`` -- (optional) a subset (or any iterable) of the groundset disjoint from ``S`` - OUTPUT: - - An integer. + OUTPUT: an integer EXAMPLES:: @@ -4906,12 +5070,10 @@ cdef class Matroid(SageObject): INPUT: - - ``S`` -- a subset of the ground set - - ``T`` -- (optional) a subset of the ground set disjoint from ``S`` - - OUTPUT: + - ``S`` -- a subset of the groundset + - ``T`` -- (optional) a subset of the groundset disjoint from ``S`` - An integer. + OUTPUT: an integer ALGORITHM: @@ -4945,9 +5107,8 @@ cdef class Matroid(SageObject): - ``T`` -- a subset (or any iterable) of the groundset disjoint from ``S`` - OUTPUT: - - A tuple ``(I, X)`` containing a frozenset ``I`` and a frozenset ``X``. + OUTPUT: a tuple ``(I, X)`` containing a frozenset ``I`` and a frozenset + ``X`` ALGORITHM: @@ -4991,12 +5152,11 @@ cdef class Matroid(SageObject): INPUT: - - ``S`` -- a subset of the ground set - - ``T`` -- a subset of the ground set disjoint from ``S`` - - OUTPUT: + - ``S`` -- a subset of the groundset + - ``T`` -- a subset of the groundset disjoint from ``S`` - A tuple ``(I, X)`` containing a frozenset ``I`` and a frozenset ``X``. + OUTPUT: a tuple ``(I, X)`` containing a frozenset ``I`` and a frozenset + ``X`` ALGORITHM: @@ -5063,14 +5223,12 @@ cdef class Matroid(SageObject): INPUT: - - ``k`` -- a integer greater or equal to 1. - - ``certificate`` -- (default: ``False``) a boolean; if ``True``, + - ``k`` -- an integer greater or equal to 1 + - ``certificate`` -- (default: ``False``) boolean; if ``True``, then return ``True, None`` if the matroid is k-connected, and ``False, X`` otherwise, where ``X`` is a `= 2 + # there is no partition A,B of the groundset such that |A|, |B| >= 2 else: return True # now there exist two disjoint pairs @@ -5449,13 +5601,11 @@ cdef class Matroid(SageObject): INPUT: - - ``certificate`` -- (default: ``False``) a boolean; if ``True``, + - ``certificate`` -- boolean (default: ``False``); if ``True``, then return ``True, None`` if the matroid is 3-connected, and ``False,`` `X` otherwise, where `X` is a `<3`-separation - OUTPUT: - - boolean, or a tuple ``(boolean, frozenset)`` + OUTPUT: boolean, or a tuple ``(boolean, frozenset)`` ALGORITHM: @@ -5475,7 +5625,7 @@ cdef class Matroid(SageObject): ....: groundset='abcdef') sage: N._is_3connected_shifting() # needs sage.graphs False - sage: matroids.catalog.BetsyRoss()._is_3connected_shifting() # needs sage.graphs + sage: matroids.catalog.BetsyRoss()._is_3connected_shifting() # needs sage.graphs True sage: M = matroids.catalog.R6() sage: M._is_3connected_shifting() # needs sage.graphs @@ -5527,13 +5677,11 @@ cdef class Matroid(SageObject): INPUT: - - ``certificate`` -- (default: ``False``) a boolean; if ``True``, + - ``certificate`` -- boolean (default: ``False``); if ``True``, then return ``True, None`` if the matroid is 4-connected, and ``False,`` `X` otherwise, where `X` is a `<4`-separation - OUTPUT: - - boolean, or a tuple ``(boolean, frozenset)`` + OUTPUT: boolean, or a tuple ``(boolean, frozenset)`` ALGORITHM: @@ -5639,7 +5787,7 @@ cdef class Matroid(SageObject): INPUT: - - ``X`` -- A basis + - ``X`` -- a basis - ``P_rows`` -- a set of row indices of the first submatrix - ``P_cols`` -- a set of column indices of the first submatrix - ``Q_rows`` -- a set of row indices of the second submatrix @@ -5648,8 +5796,8 @@ cdef class Matroid(SageObject): OUTPUT: - - `False, None` -- if there is no ``m``-separator. - - `True, E` -- if there exist a ``m``-separator ``E``. + - `False, None` -- if there is no ``m``-separator + - `True, E` -- if there exist an ``m``-separator ``E`` EXAMPLES:: @@ -5700,7 +5848,7 @@ cdef class Matroid(SageObject): INPUT: - - ``X`` -- A basis + - ``X`` -- a basis - ``X_1`` -- set of row indices of the first submatrix - ``Y_2`` -- set of column indices of the first submatrix - ``X_2`` -- set of row indices of the second submatrix @@ -5709,8 +5857,8 @@ cdef class Matroid(SageObject): OUTPUT: - - ``False, None`` -- if there is no ``m``-separator. - - ``True, E`` -- if there exist a ``m``-separator ``E``. + - ``False, None`` -- if there is no ``m``-separator + - ``True, E`` -- if there exist an ``m``-separator ``E`` EXAMPLES:: @@ -5783,13 +5931,11 @@ cdef class Matroid(SageObject): INPUT: - - ``certificate`` -- (default: ``False``) a boolean; if ``True``, + - ``certificate`` -- boolean (default: ``False``); if ``True``, then return ``True, None`` if the matroid is 3-connected, and ``False,`` `X` otherwise, where `X` is a `<3`-separation - OUTPUT: - - boolean, or a tuple ``(boolean, frozenset)`` + OUTPUT: boolean, or a tuple ``(boolean, frozenset)`` ALGORITHM: @@ -5808,7 +5954,7 @@ cdef class Matroid(SageObject): ....: groundset='abcdef') sage: N._is_3connected_BC() False - sage: matroids.catalog.BetsyRoss()._is_3connected_BC() # needs sage.graphs + sage: matroids.catalog.BetsyRoss()._is_3connected_BC() # needs sage.graphs True sage: M = matroids.catalog.R6() sage: M._is_3connected_BC() # needs sage.graphs @@ -5845,9 +5991,7 @@ cdef class Matroid(SageObject): - ``fund_cocircuits`` -- a iterable of some fundamental cocircuits with respect to ``basis``. It must contain all separating fundamental cocircuits. - OUTPUT: - - boolean + OUTPUT: boolean EXAMPLES:: @@ -5937,19 +6081,21 @@ cdef class Matroid(SageObject): A matroid is paving if each of its circuits has size `r` or `r+1`. - OUTPUT: - - boolean + OUTPUT: boolean EXAMPLES:: sage: M = matroids.catalog.Vamos() sage: M.is_paving() True + sage: M = matroids.Theta(4) + sage: M.is_paving() + False """ - for C in self.circuits(): - if len(C) < self.rank(): - return False + if self.rank() >= 2: + for X in combinations(self.groundset(), self.rank()-1): + if not self._is_independent(frozenset(X)): + return False return True cpdef is_sparse_paving(self) noexcept: @@ -5959,9 +6105,7 @@ cdef class Matroid(SageObject): A matroid is sparse-paving if the symmetric difference of every pair of circuits is greater than 2. - OUTPUT: - - boolean + OUTPUT: boolean EXAMPLES:: @@ -5975,7 +6119,7 @@ cdef class Matroid(SageObject): if not self.is_paving(): return False from itertools import combinations - for (C1, C2) in combinations(self.circuits(), 2): + for (C1, C2) in combinations(self.circuits_iterator(), 2): if len(C1 ^ C2) <= 2: return False return True @@ -6014,7 +6158,7 @@ cdef class Matroid(SageObject): r""" Return a binary matroid `M` so that relative to a fixed basis `B`, `X` is a basis of ``self`` if and only if `X` is a basis of `M` - for all subsets `X` of the ground set such that + for all subsets `X` of the groundset such that `|X \setminus B| \leq 1`. INPUT: @@ -6061,14 +6205,12 @@ cdef class Matroid(SageObject): - ``randomized_tests`` -- (default: 1) an integer; the number of times a certain necessary condition for being binary is tested, using randomization - - ``verify`` -- (default: ``True``), a Boolean; if ``True``, + - ``verify`` -- boolean (default: ``True``); if ``True``, any output will be a binary matroid representing ``self``; if ``False``, any output will represent ``self`` if and only if the matroid is binary - OUTPUT: - - Either a BinaryMatroid, or ``None`` + OUTPUT: either a ``BinaryMatroid``, or ``None`` ALGORITHM: @@ -6118,9 +6260,7 @@ cdef class Matroid(SageObject): times a certain necessary condition for being binary is tested, using randomization - OUTPUT: - - A Boolean. + OUTPUT: boolean ALGORITHM: @@ -6189,9 +6329,9 @@ cdef class Matroid(SageObject): if basis is None: basis = self.basis() - basis = sorted(basis) + basis = sorted(basis, key=str) bdx = {basis[i]: i for i in range(len(basis))} - E = sorted(self.groundset()) + E = sorted(self.groundset(), key=str) idx = {Ei: i for i, Ei in enumerate(E)} A = TernaryMatrix(len(basis), len(E)) for e in basis: @@ -6246,14 +6386,14 @@ cdef class Matroid(SageObject): - ``randomized_tests`` -- (default: 1) an integer; the number of times a certain necessary condition for being ternary is tested, using randomization - - ``verify`` -- (default: ``True``), a Boolean; if ``True``, + - ``verify`` -- boolean (default: ``True``); if ``True``, any output will be a ternary matroid representing ``self``; if ``False``, any output will represent ``self`` if and only if the matroid is ternary - OUTPUT: - - Either a :class:`TernaryMatroid `, or ``None`` + OUTPUT: Either a + :class:`TernaryMatroid `, + or ``None`` ALGORITHM: @@ -6303,9 +6443,7 @@ cdef class Matroid(SageObject): times a certain necessary condition for being ternary is tested, using randomization - OUTPUT: - - A Boolean. + OUTPUT: boolean ALGORITHM: @@ -6376,14 +6514,12 @@ cdef class Matroid(SageObject): INPUT: - ``C`` -- a circuit - - ``certificate`` -- (default: ``False``) a boolean, if ``True`` + - ``certificate`` -- boolean (default: ``False``), if ``True`` return ``True, (x, Ax, Bx)``, where ``x`` is a chord and ``Ax`` and ``Bx`` are circuits whose union is the elements of ``C`` together with ``x``, if ``False`` return ``False, None`` - OUTPUT: - - - boolean or tuple + OUTPUT: boolean or tuple EXAMPLES:: @@ -6428,14 +6564,12 @@ cdef class Matroid(SageObject): INPUT: - ``C`` -- a circuit - - ``certificate`` -- (default: ``False``) a boolean, if ``True`` + - ``certificate`` -- boolean (default: ``False``), if ``True`` return ``True, (x, Ax, Bx)``, where ``x`` is a chord and ``Ax`` and ``Bx`` are circuits whose union is the elements of ``C`` together with ``x``, if ``False`` return ``False, None`` - OUTPUT: - - - boolean or tuple + OUTPUT: boolean or tuple EXAMPLES:: @@ -6478,9 +6612,7 @@ cdef class Matroid(SageObject): ``True`` return ``True, C``, where ``C`` is a non ``k1`` ``k2`` circuit - Output: - - - boolean or tuple + OUTPUT: boolean or tuple .. SEEALSO:: @@ -6557,11 +6689,9 @@ cdef class Matroid(SageObject): - ``X`` -- (default: the groundset) a subset (or any iterable) of the groundset - ``weights`` -- a dictionary or function mapping the elements of - ``X`` to nonnegative weights. - - OUTPUT: + ``X`` to nonnegative weights - A subset of ``X``. + OUTPUT: a subset of ``X`` ALGORITHM: @@ -6644,11 +6774,9 @@ cdef class Matroid(SageObject): - ``X`` -- (default: the groundset) a subset (or any iterable) of the groundset - ``weights`` -- a dictionary or function mapping the elements of - ``X`` to nonnegative weights. - - OUTPUT: + ``X`` to nonnegative weights - A subset of ``X``. + OUTPUT: a subset of ``X`` ALGORITHM: @@ -6732,15 +6860,14 @@ cdef class Matroid(SageObject): The *weight* of a subset ``S`` is ``sum(weights(e) for e in S)``. - INPUT: - ``X`` -- (default: the groundset) a subset (or any iterable) of the groundset - ``weights`` -- a dictionary or function mapping the elements of - ``X`` to nonnegative weights. + ``X`` to nonnegative weights - OUTPUT: Boolean. + OUTPUT: boolean ALGORITHM: @@ -6751,7 +6878,6 @@ cdef class Matroid(SageObject): previously selected elements, then we check if it is independent with the previously selected elements with higher weight. - EXAMPLES:: sage: from sage.matroids.advanced import setprint @@ -6886,11 +7012,9 @@ cdef class Matroid(SageObject): - ``X`` -- (default: the groundset) a subset (or any iterable) of the groundset - ``weights`` -- a dictionary or function mapping the elements of - ``X`` to nonnegative weights. - - OUTPUT: + ``X`` to nonnegative weights - Boolean. + OUTPUT: boolean ALGORITHM: @@ -7042,14 +7166,12 @@ cdef class Matroid(SageObject): INPUT: - ``other`` -- a second matroid with the same groundset as this - matroid. + matroid - ``weights`` -- (default: ``None``) a dictionary which specifies a - weight for each element of the common groundset. Defaults to the - all-1 weight function. - - OUTPUT: + weight for each element of the common groundset; defaults to the + all-1 weight function - A subset of the groundset. + OUTPUT: a subset of the groundset EXAMPLES:: @@ -7096,13 +7218,11 @@ cdef class Matroid(SageObject): INPUT: - ``other`` -- a second matroid with the same groundset as this - matroid. + matroid - ``weights`` -- a dictionary which must specify a weight for each - element of the common groundset. - - OUTPUT: + element of the common groundset - A subset of the groundset. + OUTPUT: a subset of the groundset .. NOTE:: @@ -7135,12 +7255,12 @@ cdef class Matroid(SageObject): INPUT: - - ``other`` -- a matroid with the same ground set as ``self``. + - ``other`` -- a matroid with the same groundset as ``self`` - ``weights`` -- a dictionary specifying a weight for each element of - the common ground set ``E``. + the common groundset ``E`` - ``Y`` -- an extremal common independent set of ``self`` and - ``other`` of size `k`. That is, a common independent set of maximum - weight among common independent sets of size `k`. + ``other`` of size `k`; that is, a common independent set of maximum + weight among common independent sets of size `k` OUTPUT: @@ -7231,11 +7351,9 @@ cdef class Matroid(SageObject): INPUT: - ``other`` -- a second matroid with the same groundset as this - matroid. - - OUTPUT: + matroid - A subset of the groundset. + OUTPUT: subset of the groundset EXAMPLES:: @@ -7263,11 +7381,9 @@ cdef class Matroid(SageObject): INPUT: - ``other`` -- a second matroid with the same groundset as this - matroid. - - OUTPUT: + matroid - A subset of the groundset. + OUTPUT: a subset of the groundset .. NOTE:: @@ -7298,8 +7414,9 @@ cdef class Matroid(SageObject): INPUT: - - ``other`` -- a matroid with the same ground set as ``self``. - - ``Y`` -- an common independent set of ``self`` and ``other`` of size `k`. + - ``other`` -- a matroid with the same groundset as ``self`` + - ``Y`` -- an common independent set of ``self`` and ``other`` of size + `k` OUTPUT: @@ -7433,9 +7550,7 @@ cdef class Matroid(SageObject): Return a minimum number of disjoint independent sets that covers the groundset. - OUTPUT: - - A list of disjoint independent sets that covers the groundset. + OUTPUT: a list of disjoint independent sets that covers the groundset EXAMPLES:: @@ -7508,11 +7623,9 @@ cdef class Matroid(SageObject): INPUT: - ``B`` -- a basis of the matroid, assumed to have Python's - ``frozenset`` interface. - - OUTPUT: + ``frozenset`` interface - A subset of ``B``. + OUTPUT: a subset of ``B`` .. SEEALSO:: @@ -7529,7 +7642,7 @@ cdef class Matroid(SageObject): N = self.groundset() - B A = set() for e in B: - if min(self._cocircuit(N | set([e]))) == e: + if min(self._cocircuit(N | set([e])), key=str) == e: A.add(e) return A @@ -7547,11 +7660,9 @@ cdef class Matroid(SageObject): INPUT: - ``B`` -- a basis of the matroid, assumed to have Python's - ``frozenset`` interface. - - OUTPUT: + ``frozenset`` interface - A subset of ``self.groundset() - B``. + OUTPUT: a subset of ``self.groundset() - B`` .. SEEALSO:: @@ -7568,7 +7679,7 @@ cdef class Matroid(SageObject): N = self.groundset() - B A = set() for e in N: - if min(self._circuit(B | set([e]))) == e: + if min(self._circuit(B | set([e])), key=str) == e: A.add(e) return A @@ -7596,8 +7707,8 @@ cdef class Matroid(SageObject): INPUT: - - ``x`` -- (optional) a variable or numerical argument. - - ``y`` -- (optional) a variable or numerical argument. + - ``x`` -- (optional) a variable or numerical argument + - ``y`` -- (optional) a variable or numerical argument OUTPUT: @@ -7627,15 +7738,63 @@ cdef class Matroid(SageObject): R = ZZ['x, y'] x, y = R._first_ngens(2) T = R(0) - for B in self.bases(): + for B in self.bases_iterator(): T += x ** len(self._internal(B)) * y ** len(self._external(B)) if a is not None and b is not None: T = T(a, b) return T + cpdef characteristic_polynomial(self, l=None) noexcept: + r""" + Return the characteristic polynomial of the matroid. + + The *characteristic polynomial* of a matroid `M` is the polynomial + + .. MATH:: + + \chi_M(\lambda) = \sum_{S \subseteq E} (-1)^{|S|}\lambda^{r(E)-r(S)}, + + where `E` is the groundset and`r` is the matroid's rank function. The + characteristic polynomial is also equal to + \sum_{i = 0}^r w_i\lambda^{r-i}, where `\{w_i\}_{i=0}^r` are the + Whitney numbers of the first kind. + + INPUT: + + - ``l`` -- a variable or numerical argument (optional) + + OUTPUT: the characteristic polynomial, `\chi_M(\lambda)`, where + `\lambda` is substituted with any value provided as input. + + EXAMPLES:: + + sage: M = matroids.CompleteGraphic(5) + sage: M.characteristic_polynomial() + l^4 - 10*l^3 + 35*l^2 - 50*l + 24 + sage: M.characteristic_polynomial().factor() + (l - 4) * (l - 3) * (l - 2) * (l - 1) + sage: M.characteristic_polynomial(5) + 24 + + .. SEEALSO:: + + :meth:`whitney_numbers() ` + """ + val = l + R = ZZ['l'] + l = R._first_ngens(1)[0] + chi = R(0) + cdef int r = self.rank() + cdef list w = self.whitney_numbers() + for i in range(len(w)): + chi += w[i] * l**(r-i) + if val is not None: + return chi(val) + return chi + cpdef flat_cover(self, solver=None, verbose=0, integrality_tolerance=1e-3) noexcept: """ - Return a minimum-size cover of the nonbases by non-spanning flats. + Return a minimum-size cover of the nonbases by nonspanning flats. A *nonbasis* is a subset that has the size of a basis, yet is dependent. A *flat* is a closed set. @@ -7665,9 +7824,15 @@ cdef class Matroid(SageObject): {'b', 'c', 'd'}, {'b', 'e', 'g'}, {'c', 'f', 'g'}, {'d', 'e', 'f'}] """ - NB = self.nonbases() - if not NB: + flag = False + NB = self.nonbases_iterator() + for S in NB: + flag = True + break + if not flag: # if empty return [] + + NB = self.nonbases_iterator() FF = [] for r in range(self.full_rank()): FF.extend(self.flats(r)) @@ -7770,7 +7935,7 @@ cdef class Matroid(SageObject): # Create the ambient polynomial ring from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing try: - names = ['A{}'.format(''.join(str(x) for x in sorted(F))) for F in flats] + names = ['A{}'.format(''.join(str(x) for x in sorted(F, key=str))) for F in flats] P = PolynomialRing(R, names) except ValueError: # variables are not proper names P = PolynomialRing(R, 'A', len(flats)) @@ -7796,22 +7961,22 @@ cdef class Matroid(SageObject): - ``B`` -- (optional) a list containing a basis. If internal point placement is used, these elements will be placed as vertices of a triangle. - ``lineorders`` -- (optional) A list of lists where each of the inner lists - specify ground set elements in a certain order which will be used to draw the + specify groundset elements in a certain order which will be used to draw the corresponding line in geometric representation (if it exists). - ``pos_method`` -- An integer specifying positioning method. - ``0``: default positioning - ``1``: use pos_dict if it is not ``None`` - ``2``: Force directed (Not yet implemented). - - ``pos_dict``: A dictionary mapping ground set elements to their (x,y) positions. - - ``save_pos``: A boolean indicating that point placements (either internal or user provided) and + - ``pos_dict``: A dictionary mapping groundset elements to their (x,y) positions. + - ``save_pos``: boolean indicating that point placements (either internal or user provided) and line orders (if provided) will be cached in the matroid (``M._cached_info``) and can be used for reproducing the geometric representation during the same session OUTPUT: A sage graphics object of type that - corresponds to the geometric representation of the matroid + corresponds to the geometric representation of the matroid. EXAMPLES:: @@ -7847,27 +8012,31 @@ cdef class Matroid(SageObject): lineorders2=matroids_plot_helpers.lineorders_union(self._cached_info['lineorders'],lineorders) return matroids_plot_helpers.geomrep(self,B,lineorders2,pd=pos_dict, sp=save_pos) - cpdef show(self,B=None,lineorders=None,pos_method=None,pos_dict=None,save_pos=False,lims=None) noexcept: + cpdef show(self, B=None, lineorders=None, pos_method=None, pos_dict=None, save_pos=False, lims=None) noexcept: """ Show the geometric representation of the matroid. INPUT: - - ``B`` -- (optional) a list containing elements of the groundset not in any particular order. - If internal point placement is used, these elements will be placed as vertices of a triangle. - - ``lineorders`` -- (optional) A list of lists where each of the inner lists - specify ground set elements in a certain order which will be used to draw the - corresponding line in geometric representation (if it exists). - - ``pos_method`` -- An integer specifying positioning method + - ``B`` -- (optional) a list containing elements of the groundset not + in any particular order. If internal point placement is used, these + elements will be placed as vertices of a triangle. + - ``lineorders`` -- (optional) a list of lists where each of the inner + lists specify groundset elements in a certain order which will be + used to draw the corresponding line in geometric representation (if + it exists). + - ``pos_method`` -- an integer specifying the positioning method - ``0``: default positioning - ``1``: use pos_dict if it is not ``None`` - ``2``: Force directed (Not yet implemented). - - ``pos_dict`` -- A dictionary mapping ground set elements to their (x,y) positions. - - ``save_pos`` -- A boolean indicating that point placements (either internal or user provided) and - line orders (if provided) will be cached in the matroid (``M._cached_info``) and can be used for + - ``pos_dict`` -- a dictionary mapping groundset elements to their + (x, y) positions + - ``save_pos`` -- boolean indicating that point placements (either + internal or user provided) and line orders (if provided) will be + cached in the matroid (``M._cached_info``) and can be used for reproducing the geometric representation during the same session - - ``lims`` -- A list of 4 elements ``[xmin,xmax,ymin,ymax]`` + - ``lims`` -- a list of 4 elements ``[xmin,xmax,ymin,ymax]`` EXAMPLES:: @@ -7896,16 +8065,18 @@ cdef class Matroid(SageObject): G.show(xmin=lims[0], xmax=lims[1], ymin=lims[2], ymax=lims[3]) return - cpdef _fix_positions(self,pos_dict=None,lineorders=None) noexcept: + cpdef _fix_positions(self, pos_dict=None, lineorders=None) noexcept: """ Cache point positions and line orders without actually plotting INPUT: - - ``pos_dict`` -- (optional) A dictionary mapping ground set elements to their (x,y) positions. - - ``lineorders`` -- (optional) A list of lists where each of the inner lists - specify ground set elements in a certain order which will be used to draw the - corresponding line in geometric representation (if it exists). + - ``pos_dict`` -- (optional) A dictionary mapping groundset elements to + their (x, y) positions + - ``lineorders`` -- (optional) a list of lists where each of the inner + lists specify groundset elements in a certain order which will be + used to draw the corresponding line in geometric representation (if + it exists). EXAMPLES:: @@ -7940,16 +8111,14 @@ cdef class Matroid(SageObject): Return the broken circuit complex of ``self``. The broken circuit complex of a matroid with a total ordering `<` - on the ground set is obtained from the + on the groundset is obtained from the :meth:`NBC sets ` under subset inclusion. INPUT: - ``ordering`` -- a total ordering of the groundset given as a list - OUTPUT: - - A simplicial complex of the NBC sets under inclusion. + OUTPUT: a simplicial complex of the NBC sets under inclusion EXAMPLES:: @@ -7962,7 +8131,12 @@ cdef class Matroid(SageObject): and facets {(1, 3, 5), (1, 4, 5), (2, 3, 5), (2, 4, 5)} """ from sage.topology.simplicial_complex import SimplicialComplex - return SimplicialComplex(self.no_broken_circuits_sets(ordering)) + cdef int r = self.rank() + cdef list facets = [] + for S in self.no_broken_circuits_sets_iterator(ordering): + if len(S) == r: + facets += [S] + return SimplicialComplex(facets, maximality_check=False) cpdef automorphism_group(self) noexcept: r""" @@ -8007,9 +8181,7 @@ cdef class Matroid(SageObject): maximum elements removed. The *Bergman complex* of a matroid `M` is the order complex of `L`. - OUTPUT: - - A simplicial complex as just described. + OUTPUT: a simplicial complex EXAMPLES:: @@ -8028,10 +8200,10 @@ cdef class Matroid(SageObject): r""" Return the augmented Bergman complex of ``self``. - Given a matroid `M` with ground set `E=\{1,2,\ldots,n\}`, + Given a matroid `M` with groundset `E=\{1,2,\ldots,n\}`, the *augmented Bergman complex* can be seen as a hybrid of the complex - of independent sets of `M` and the Bergman complex of `M`. It is defined - as the simplicial complex on vertex set + of independent sets of `M` and the Bergman complex of `M`. It is + defined as the simplicial complex on vertex set .. MATH:: @@ -8046,9 +8218,7 @@ cdef class Matroid(SageObject): for which `I` is an independent set and `I\subseteq F_1\subsetneq F_2 \subsetneq\cdots\subsetneq F_\ell`. - OUTPUT: - - A simplicial complex as just described. + OUTPUT: a simplicial complex EXAMPLES:: @@ -8114,12 +8284,12 @@ cdef class Matroid(SageObject): r = self.rank() - len(c) # get candidate independent_sets - for I in self.independent_r_sets(r): + for I in self.independent_r_sets_iterator(r): if I.issubset(c[0]): # add the facet DM.add_face([f'L{i}' for i in I] + - [f'R{sorted(F)}' for F in c]) + [f'R{sorted(F, key=str)}' for F in c]) return DM def union(self, matroids): @@ -8127,19 +8297,17 @@ cdef class Matroid(SageObject): Return the matroid union with another matroid or a list of matroids. Let `(M_1, M_2, \ldots, M_k)` be a list of matroids where each `M_i` - has ground set `E_i`. The *matroid - union* `M` of `(M_1, M_2, \ldots, M_k)` has ground set `E = \cup E_i`. - Moreover, a set `I \subseteq E` is independent in `M` if and only if the - restriction of `I` to `E_i` is independent in `M_i` for every `i`. + has groundset `E_i`. The *matroid + union* `M` of `(M_1, M_2, \ldots, M_k)` has groundset `E = \cup E_i`. + Moreover, a set `I \subseteq E` is independent in `M` if and only if + the restriction of `I` to `E_i` is independent in `M_i` for every `i`. INPUT: - ``matroids`` - a matroid or a list of matroids - OUTPUT: - - An instance of - :class:`MatroidUnion `. + OUTPUT: an instance of + :class:`MatroidUnion ` EXAMPLES:: @@ -8170,7 +8338,7 @@ cdef class Matroid(SageObject): matroids. Let `(M_1, M_2, \ldots, M_k)` be a list of matroids where each `M_i` - has ground set `E_i`. The matroid sum `(E_1,I_1),\ldots,(E_n,I_n)` + has groundset `E_i`. The matroid sum `(E_1,I_1),\ldots,(E_n,I_n)` is a matroid `(E,I)` where `E= \bigsqcup_{i=1}^n E_i` and `I= \bigsqcup_{i=1}^n I_i`. @@ -8178,9 +8346,7 @@ cdef class Matroid(SageObject): - ``matroids`` - a matroid or list of matroids - OUTPUT: - - An instance of + OUTPUT: an instance of :class:`MatroidSum ` EXAMPLES:: diff --git a/src/sage/matroids/union_matroid.pyx b/src/sage/matroids/union_matroid.pyx index 04f7eee4402..cbcc89763f4 100644 --- a/src/sage/matroids/union_matroid.pyx +++ b/src/sage/matroids/union_matroid.pyx @@ -22,7 +22,7 @@ cdef class MatroidUnion(Matroid): Matroid of rank 1 on 2 elements with 2 bases sage: M.bases() Iterator over a system of subsets - sage: M.circuits() + sage: list(M.circuits()) [frozenset({3, 4})] INPUT: From bc92c7ee3ab15fe90e05504da1fc119f4764794a Mon Sep 17 00:00:00 2001 From: Giorgos Mousa Date: Thu, 15 Feb 2024 14:52:13 +0200 Subject: [PATCH 014/518] Set "default=-1" for rank of FlatsMatroid This is for an invalid input with no flats --- src/sage/matroids/flats_matroid.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/matroids/flats_matroid.pyx b/src/sage/matroids/flats_matroid.pyx index 1456a952d89..3c69920717b 100644 --- a/src/sage/matroids/flats_matroid.pyx +++ b/src/sage/matroids/flats_matroid.pyx @@ -83,7 +83,7 @@ cdef class FlatsMatroid(Matroid): except KeyError: self._F[i] = set() self._F[i].add(frozenset(F)) - self._matroid_rank = max([0] + list(self._F)) + self._matroid_rank = max(self._F, default=-1) cpdef groundset(self) noexcept: """ From 4ffa2826ef56faec139b17cdc0b5fbef0dd0eec6 Mon Sep 17 00:00:00 2001 From: Giorgos Mousa Date: Fri, 16 Feb 2024 14:58:04 +0200 Subject: [PATCH 015/518] More internal iterators and cythonization --- src/sage/matroids/matroid.pxd | 9 +++++ src/sage/matroids/matroid.pyx | 69 +++++++++++++++-------------------- 2 files changed, 38 insertions(+), 40 deletions(-) diff --git a/src/sage/matroids/matroid.pxd b/src/sage/matroids/matroid.pxd index dae3e7d4368..7ab91af018f 100644 --- a/src/sage/matroids/matroid.pxd +++ b/src/sage/matroids/matroid.pxd @@ -129,6 +129,10 @@ cdef class Matroid(SageObject): cpdef broken_circuits(self, ordering=*) noexcept cpdef no_broken_circuits_sets(self, ordering=*) noexcept + # polytopes + cpdef matroid_polytope(self) noexcept + cpdef independence_matroid_polytope(self) noexcept + # isomorphism cpdef is_isomorphic(self, other, certificate=*) noexcept cpdef _is_isomorphic(self, other, certificate=*) noexcept @@ -155,6 +159,7 @@ cdef class Matroid(SageObject): cpdef modular_cut(self, subsets) noexcept cpdef linear_subclasses(self, line_length=*, subsets=*) noexcept cpdef extensions(self, element=*, line_length=*, subsets=*) noexcept + cpdef coextensions(self, element=*, coline_length=*, subsets=*) noexcept # connectivity cpdef simplify(self) noexcept @@ -222,8 +227,12 @@ cdef class Matroid(SageObject): cpdef automorphism_group(self) noexcept cpdef bergman_complex(self) noexcept cpdef augmented_bergman_complex(self) noexcept + cpdef broken_circuit_complex(self, ordering=*) noexcept # visualization cpdef plot(self,B=*,lineorders=*,pos_method=*,pos_dict=*,save_pos=*) noexcept cpdef show(self,B=*,lineorders=*,pos_method=*,pos_dict=*,save_pos=*,lims=*) noexcept cpdef _fix_positions(self,pos_dict=*,lineorders=*) noexcept + + # construction + cpdef direct_sum(self, matroids) noexcept diff --git a/src/sage/matroids/matroid.pyx b/src/sage/matroids/matroid.pyx index caf6b6df3ee..42468f02761 100644 --- a/src/sage/matroids/matroid.pyx +++ b/src/sage/matroids/matroid.pyx @@ -147,6 +147,7 @@ additional functionality (e.g. linear extensions). - :meth:`chow_ring() ` - :meth:`matroid_polytope() ` - :meth:`independence_matroid_polytope() ` + - :meth:`lattice_of_flats() ` - :meth:`orlik_solomon_algebra() ` - :meth:`bergman_complex() ` - :meth:`augmented_bergman_complex() ` @@ -1113,9 +1114,9 @@ cdef class Matroid(SageObject): if certificate: return False, None return False - YY = self.dual().independent_r_sets(cd) - for X in self.independent_r_sets(rd): - for Y in YY: + D = self.dual() + for X in self.independent_r_sets_iterator(rd): + for Y in D.independent_r_sets_iterator(cd): if X.isdisjoint(Y): if N._is_isomorphic(self._minor(contractions=X, deletions=Y)): if certificate: @@ -2405,27 +2406,6 @@ cdef class Matroid(SageObject): C.update([self._cocircuit(self.groundset().difference(B).union(set([e]))) for e in B]) return list(C) - def cocircuits_iterator(self): - """ - Return an iterator over the cocircuits of the matroid. - - .. SEEALSO:: - - :meth:`M.cocircuit() ` - - EXAMPLES:: - - sage: M = matroids.catalog.Fano() - sage: sorted([sorted(C) for C in M.cocircuits_iterator()]) - [['a', 'b', 'c', 'g'], ['a', 'b', 'd', 'e'], ['a', 'c', 'd', 'f'], - ['a', 'e', 'f', 'g'], ['b', 'c', 'e', 'f'], ['b', 'd', 'f', 'g'], - ['c', 'd', 'e', 'g']] - """ - C = set() - for B in self.bases_iterator(): - C.update([self._cocircuit(self.groundset().difference(B).union(set([e]))) for e in B]) - return list(C) - cpdef noncospanning_cocircuits(self) noexcept: """ Return the noncospanning cocircuits of the matroid. @@ -3019,7 +2999,7 @@ cdef class Matroid(SageObject): `(w_0=1, ..., w_r)` -- are numbers of alternating sign, where `w_i` is the value of the coefficient of the `(r-i)`-th degree term of the matroid's characteristic polynomial. Moreover, `|w_i|` is the number of - `i`-faces of the broken circuit complex of the matroid. + `(i-1)`-dimensional faces of the broken circuit complex of the matroid. OUTPUT: a list of integers @@ -3318,7 +3298,7 @@ cdef class Matroid(SageObject): # polytopes - def matroid_polytope(self): + cpdef matroid_polytope(self) noexcept: r""" Return the matroid polytope of ``self``. @@ -3357,10 +3337,15 @@ cdef class Matroid(SageObject): n = self.size() vector_e = FreeModule(ZZ, n).basis() convert = {ind: i for i, ind in enumerate(self.groundset())} - vertices = [sum(vector_e[convert[i]] for i in B) for B in self.bases_iterator()] + vertices = [] + for B in self.bases_iterator(): + sum = 0 + for i in B: + sum += vector_e[convert[i]] + vertices += [sum] return Polyhedron(vertices) - def independence_matroid_polytope(self): + cpdef independence_matroid_polytope(self) noexcept: r""" Return the independence matroid polytope of ``self``. @@ -3400,7 +3385,12 @@ cdef class Matroid(SageObject): ambient = FreeModule(ZZ, n) vector_e = ambient.basis() convert = {ind: i for i, ind in enumerate(self.groundset())} - vertices = [ambient.sum(vector_e[convert[i]] for i in IS) for IS in self.independent_sets_iterator()] + vertices = [] + for IS in self.independent_sets_iterator(): + lst = [] + for i in IS: + lst += [vector_e[convert[i]]] + vertices += [ambient.sum(lst)] return Polyhedron(vertices) # isomorphism and equality @@ -4256,8 +4246,7 @@ cdef class Matroid(SageObject): sage: matroids.catalog.NonFano().has_minor(M) True sage: matroids.catalog.NonFano().has_minor(M, certificate=True) - (True, (frozenset(), frozenset({'g'}), - {0: 'b', 1: 'c', 2: 'a', 3: 'd', 4: 'e', 5: 'f'})) + (True, (frozenset(), frozenset({...}), {...})) sage: M = matroids.catalog.Fano() sage: M.has_minor(M, True) (True, @@ -4735,7 +4724,7 @@ cdef class Matroid(SageObject): raise ValueError("cannot extend by element already in groundset") return extension.MatroidExtensions(self, element, line_length=line_length, subsets=subsets) # return enumerator - def coextensions(self, element=None, coline_length=None, subsets=None): + cpdef coextensions(self, element=None, coline_length=None, subsets=None) noexcept: r""" Return an iterable set of single-element coextensions of the matroid. @@ -6632,12 +6621,12 @@ cdef class Matroid(SageObject): sage: M.is_chordal(4, 5) False sage: M.is_chordal(4, 5, certificate=True) - (False, frozenset({'a', 'b', 'e', 'f', 'g'})) + (False, frozenset({...})) """ cdef frozenset C if k2 is None: k2 = len(self.groundset()) + 1 # This is always larger than the rank - for C in self.circuits(): + for C in self.circuits_iterator(): if len(C) < k1 or len(C) > k2: continue if not self._is_circuit_chordal(C): @@ -7754,9 +7743,9 @@ cdef class Matroid(SageObject): \chi_M(\lambda) = \sum_{S \subseteq E} (-1)^{|S|}\lambda^{r(E)-r(S)}, - where `E` is the groundset and`r` is the matroid's rank function. The + where `E` is the groundset and `r` is the matroid's rank function. The characteristic polynomial is also equal to - \sum_{i = 0}^r w_i\lambda^{r-i}, where `\{w_i\}_{i=0}^r` are the + `\sum_{i = 0}^r w_i\lambda^{r-i}`, where `\{w_i\}_{i=0}^r` are the Whitney numbers of the first kind. INPUT: @@ -8106,7 +8095,7 @@ cdef class Matroid(SageObject): self._cached_info = {'plot_positions': pos_dict, 'lineorders': lineorders} return - def broken_circuit_complex(self, ordering=None): + cpdef broken_circuit_complex(self, ordering=None) noexcept: r""" Return the broken circuit complex of ``self``. @@ -8171,7 +8160,7 @@ cdef class Matroid(SageObject): [Oxl2011]_, p. 189. """ from sage.topology.simplicial_complex import SimplicialComplex - return SimplicialComplex(self.bases()).automorphism_group() + return SimplicialComplex(self.bases(), maximality_check=False).automorphism_group() cpdef bergman_complex(self) noexcept: r""" @@ -8265,7 +8254,7 @@ cdef class Matroid(SageObject): """ # Construct independent set complex from bases from sage.topology.simplicial_complex import SimplicialComplex - IM = SimplicialComplex(self.bases()) + IM = SimplicialComplex(self.bases(), maximality_check=False) LM = self.lattice_of_flats() @@ -8332,7 +8321,7 @@ cdef class Matroid(SageObject): matroids.insert(0, self) return union_matroid.MatroidUnion(iter(matroids)) - def direct_sum(self, matroids): + cpdef direct_sum(self, matroids) noexcept: r""" Return the matroid direct sum with another matroid or list of matroids. From e7763d013b3cd51b86caa3c4196c265b9741f745 Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Fri, 16 Feb 2024 15:00:33 +0000 Subject: [PATCH 016/518] Faster scalar multiplication for python types --- src/sage/schemes/elliptic_curves/ell_point.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/sage/schemes/elliptic_curves/ell_point.py b/src/sage/schemes/elliptic_curves/ell_point.py index bcfe8d5c1d2..bf86995daa8 100644 --- a/src/sage/schemes/elliptic_curves/ell_point.py +++ b/src/sage/schemes/elliptic_curves/ell_point.py @@ -3877,6 +3877,19 @@ def _acted_upon_(self, other, side): return Q + def __mul__(self, other): + r""" + For all sage types, _acted_upon_ is called, but for + multiplication by a Python int, the slower __mul__ + is called. Here we bind __mul__ to acted upon so that + the fast Pari multiplication is called when possible, + even when the scalar is a Python int rather than Sage + Integer. + """ + return self._acted_upon_(other, None) + + __rmul__ = __mul__ + def log(self, base): r""" Return the discrete logarithm of this point to the given ``base``. From 427ca29c02ba95c6054897597c6bba7f9a7210ff Mon Sep 17 00:00:00 2001 From: Giorgos Mousa Date: Fri, 16 Feb 2024 17:30:32 +0200 Subject: [PATCH 017/518] Add FlatsMatroid to doc --- src/doc/en/reference/matroids/index.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/src/doc/en/reference/matroids/index.rst b/src/doc/en/reference/matroids/index.rst index 692d798d222..b52c5b3420a 100644 --- a/src/doc/en/reference/matroids/index.rst +++ b/src/doc/en/reference/matroids/index.rst @@ -29,6 +29,7 @@ Concrete implementations sage/matroids/basis_matroid sage/matroids/circuits_matroid sage/matroids/circuit_closures_matroid + sage/matroids/flats_matroid sage/matroids/linear_matroid sage/matroids/rank_matroid sage/matroids/graphic_matroid From 9ea8e050534c7676680baf473d0806e5f4052298 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Mon, 19 Feb 2024 21:04:35 +0100 Subject: [PATCH 018/518] minor ruff fixes in quadratic forms (C4, UP, PERF) --- src/sage/quadratic_forms/binary_qf.py | 26 ++--- src/sage/quadratic_forms/genera/genus.py | 97 +++++++++---------- .../quadratic_forms/genera/spinor_genus.py | 10 +- .../quadratic_form__neighbors.py | 30 +++--- 4 files changed, 81 insertions(+), 82 deletions(-) diff --git a/src/sage/quadratic_forms/binary_qf.py b/src/sage/quadratic_forms/binary_qf.py index 555fe01b3ef..8286b639f4e 100755 --- a/src/sage/quadratic_forms/binary_qf.py +++ b/src/sage/quadratic_forms/binary_qf.py @@ -177,7 +177,7 @@ def _pari_init_(self): sage: type(gp(f)) """ - return 'Qfb(%s,%s,%s)' % (self._a, self._b, self._c) + return f'Qfb({self._a},{self._b},{self._c})' @staticmethod def principal(D): @@ -218,7 +218,7 @@ def principal(D): """ D = ZZ(D) D4 = D % 4 - if D4 not in (0,1): + if D4 not in (0, 1): raise ValueError('discriminant must be congruent to 0 or 1 modulo 4') return BinaryQF([1, D4, (D4-D)//4]) @@ -475,7 +475,7 @@ def __neg__(self): """ return BinaryQF([-self._a, -self._b, -self._c]) - def _repr_(self): + def _repr_(self) -> str: """ Display the quadratic form. @@ -492,7 +492,7 @@ def _repr_(self): """ return repr(self.polynomial()) - def _latex_(self): + def _latex_(self) -> str: """ Return latex representation of this binary quadratic form. @@ -637,7 +637,7 @@ def determinant(self): det = determinant @cached_method - def has_fundamental_discriminant(self): + def has_fundamental_discriminant(self) -> bool: """ Return whether the discriminant `D` of this form is a fundamental discriminant (i.e. `D` is the smallest element @@ -659,7 +659,7 @@ def has_fundamental_discriminant(self): """ return self.discriminant().is_fundamental_discriminant() - def is_primitive(self): + def is_primitive(self) -> bool: r""" Return whether the form `ax^2 + bxy + cy^2` satisfies `\gcd(a, b, c) = 1`, i.e., is primitive. @@ -705,7 +705,7 @@ def is_primitive(self): """ return self.content().is_one() - def is_zero(self): + def is_zero(self) -> bool: """ Return whether ``self`` is identically zero. @@ -721,7 +721,7 @@ def is_zero(self): return self.content().is_zero() @cached_method - def is_weakly_reduced(self): + def is_weakly_reduced(self) -> bool: r""" Check if the form `ax^2 + bxy + cy^2` satisfies `|b| \leq a \leq c`, i.e., is weakly reduced. @@ -745,7 +745,7 @@ def is_weakly_reduced(self): return (abs(self._b) <= self._a) and (self._a <= self._c) @cached_method - def is_reducible(self): + def is_reducible(self) -> bool: r""" Return whether this form is reducible and cache the result. @@ -1930,9 +1930,11 @@ def BinaryQF_reduced_representatives(D, primitive_only=False, proper=True): b = D.sqrt() c = ZZ.zero() # -b/2 < a <= b/2 - for a in xsrange((-b/2).floor() + 1, (b/2).floor() + 1): - if not primitive_only or (gcd([a, b, c]) == 1): - form_list.append(BinaryQF(a, b, c)) + form_list.extend(BinaryQF(a, b, c) + for a in xsrange((-b / 2).floor() + 1, + (b / 2).floor() + 1) + if not primitive_only or (gcd([a, b, c]) == 1)) + # We follow the description of Buchmann/Vollmer 6.7.1. They # enumerate all reduced forms. We only want representatives. else: diff --git a/src/sage/quadratic_forms/genera/genus.py b/src/sage/quadratic_forms/genera/genus.py index 8946683a988..2db66a93653 100644 --- a/src/sage/quadratic_forms/genera/genus.py +++ b/src/sage/quadratic_forms/genera/genus.py @@ -408,7 +408,7 @@ def LocalGenusSymbol(A, p): return Genus_Symbol_p_adic_ring(p, symbol) -def is_GlobalGenus(G): +def is_GlobalGenus(G) -> bool: r""" Return if `G` represents the genus of a global quadratic form or lattice. @@ -445,13 +445,13 @@ def is_GlobalGenus(G): verbose(mesg="False in is_2_adic_genus(sym)", level=2) return False if (a*b).kronecker(p) != 1: - verbose(mesg="False in (%s*%s).kronecker(%s)" % (a, b, p), + verbose(mesg=f"False in ({a}*{b}).kronecker({p})", level=2) return False oddity -= loc.excess() else: if a.kronecker(p) != b: - verbose(mesg="False in %s.kronecker(%s) != *%s" % (a, p, b), + verbose(mesg=f"False in {a}.kronecker({p}) != *{b}", level=2) return False oddity += loc.excess() @@ -461,7 +461,7 @@ def is_GlobalGenus(G): return True -def is_2_adic_genus(genus_symbol_quintuple_list): +def is_2_adic_genus(genus_symbol_quintuple_list) -> bool: r""" Given a `2`-adic local symbol (as the underlying list of quintuples) check whether it is the `2`-adic symbol of a `2`-adic form. @@ -806,7 +806,7 @@ def basis_complement(B): F = B.parent().base_ring() m = B.nrows() n = B.ncols() - C = MatrixSpace(F, n - m, n, sparse=True)(0) + C = MatrixSpace(F, n - m, n, sparse=True).zero() k = 0 l = 0 for i in range(m): @@ -1023,7 +1023,7 @@ def split_odd(A): return A[0, 0], MatrixSpace(ZZ, 0, A.ncols())([]) even, i = is_even_matrix(A) R = A.parent().base_ring() - C = MatrixSpace(R, n0 - 1, n0)(0) + C = MatrixSpace(R, n0 - 1, n0).zero() u = A[i, i] for j in range(n0-1): if j < i: @@ -1035,7 +1035,7 @@ def split_odd(A): B = C*A*C.transpose() even, j = is_even_matrix(B) if even: - I = A.parent()(1) + I = A.parent().one() # TODO: we could manually (re)construct the kernel here... if i == 0: I[1, 0] = 1 - A[1, 0]*u @@ -1045,7 +1045,7 @@ def split_odd(A): i = 0 A = I*A*I.transpose() u = A[i, i] - C = MatrixSpace(R, n0-1, n0)(0) + C = MatrixSpace(R, n0-1, n0).zero() for j in range(n0-1): if j < i: C[j, j] = 1 @@ -1355,7 +1355,7 @@ def __repr__(self): # mark the beginning of this compartment with [ CS_string += "[" block = CS[block_index] - block_string = "%s^%s " % (p**block[0], block[2] * block[1]) + block_string = f"{p**block[0]}^{block[2] * block[1]} " CS_string += block_string if block_index in compartment_ends: # close this compartment with ] and remove a space @@ -1373,8 +1373,8 @@ def __repr__(self): else: for s in self._symbol: - CS_string += " %s^%s" % (p**s[0], s[2] * s[1]) - rep = "Genus symbol at %s: %s" % (p, CS_string) + CS_string += f" {p**s[0]}^{s[2] * s[1]}" + rep = f"Genus symbol at {p}: {CS_string}" return rep.rstrip() def _latex_(self): @@ -1409,7 +1409,7 @@ def _latex_(self): # mark the beginning of this compartment with [ CS_string += "[" block = CS[block_index] - block_string = "%s^{%s} " % (p**block[0], block[2] * block[1]) + block_string = f"{p**block[0]}^{{{block[2] * block[1]}}} " CS_string += block_string if block_index in compartment_ends: # close this compartment with ] and remove a space @@ -1425,8 +1425,8 @@ def _latex_(self): else: for s in self._symbol: - CS_string += " {%s}^{%s}" % (p**s[0], s[2]*s[1]) - return r"\mbox{Genus symbol at } %s\mbox{: }%s" % (p, CS_string) + CS_string += f" {{{p**s[0]}}}^{{{s[2]*s[1]}}}" + return fr"\mbox{{Genus symbol at }} {p}\mbox{{: }}{CS_string}" def __eq__(self, other): r""" @@ -1587,9 +1587,8 @@ def automorphous_numbers(self): if I.count(r) > 2: I.remove(r) # products of all pairs - for r1 in I: - for r2 in I: - automorphs.append(r1*r2) + automorphs.extend(r1*r2 for r1 in I for r2 in I) + # supplement (i) for block in sym: if block[1] >= 2: @@ -1635,7 +1634,7 @@ def automorphous_numbers(self): for k in range(len(sym)): s = sym[k:k+3] if sum([b[1] for b in s if b[0] - s[0][0] < 4]) >= 3: - automorphs += [ZZ(1), ZZ(3), ZZ(5), ZZ(7)] + automorphs += [ZZ.one(), ZZ(3), ZZ(5), ZZ(7)] break # supplement (ii) @@ -1759,11 +1758,9 @@ def gram_matrix(self, check=True): [---+-+-] [0 0|0|8] """ - G = [] p = self._prime symbol = self.symbol_tuple_list() - for block in symbol: - G.append(_gram_from_jordan_block(p, block)) + G = [_gram_from_jordan_block(p, block) for block in symbol] G = matrix.block_diagonal(G) # check calculation if check: @@ -1816,11 +1813,11 @@ def mass(self): # type factors nII = ZZ.sum(fq[1] for fq in sym if fq[3] == 0) - nI_I = ZZ(0) # the total number of pairs of adjacent constituents f_q, + nI_I = ZZ.zero() # the total number of pairs of adjacent constituents f_q, # f_2q that are both of type I (odd) for k in range(r-1): if sym[k][3] == sym[k+1][3] == 1 and sym[k][0] + 1 == sym[k+1][0]: - nI_I += ZZ(1) + nI_I += ZZ.one() return m_p * ZZ(2)**(nI_I - nII) def _standard_mass(self): @@ -1844,7 +1841,7 @@ def _standard_mass(self): D = ZZ(-1)**s * self.determinant() epsilon = (4*D).kronecker(p) std *= (1 - epsilon*p**(-s)) - return QQ(1) / std + return QQ.one() / std def _species_list(self): r""" @@ -1896,7 +1893,7 @@ def _species_list(self): if sym[k][3] == 0 or n % 2 == 1: t = n // ZZ(2) else: - t = (n // ZZ(2)) - ZZ(1) + t = (n // ZZ(2)) - ZZ.one() if free and (o == 0 or o == 1 or o == 7): species = 2*t elif free and (o == 3 or o == 5 or o == 4): @@ -2223,7 +2220,7 @@ def scale(self): 1 """ if self.rank() == 0: - return ZZ(0) + return ZZ.zero() return self.prime()**self._symbol[0][0] def norm(self): @@ -2244,7 +2241,7 @@ def norm(self): 2 """ if self.rank() == 0: - return ZZ(0) + return ZZ.zero() p = self.prime() if p == 2: fq = self._symbol[0] @@ -2263,10 +2260,10 @@ def level(self): 4 """ if self.rank() == 0: - return ZZ(1) + return ZZ.one() return self.prime()**self._symbol[-1][0] - def trains(self): + def trains(self) -> list: r""" Compute the indices for each of the trains in this local genus symbol if it is associated to the prime `p=2` (and raise an @@ -2293,7 +2290,7 @@ def trains(self): symbol = self._symbol return canonical_2_adic_trains(symbol) - def compartments(self): + def compartments(self) -> list: r""" Compute the indices for each of the compartments in this local genus symbol if it is associated to the prime `p=2` (and raise an @@ -2393,7 +2390,7 @@ def __init__(self, signature_pair, local_symbols, representative=None, check=Tru self._signature = signature_pair self._local_symbols = local_symbols - def __repr__(self): + def __repr__(self) -> str: r""" Return a string representing the global genus symbol. @@ -2426,12 +2423,12 @@ def __repr__(self): rep = "Genus" if self.dimension() <= 20: rep += " of\n%s" % self._representative - rep += "\nSignature: %s" % (self._signature,) + rep += f"\nSignature: {self._signature}" for s in self._local_symbols: rep += "\n" + s.__repr__() return rep - def _latex_(self): + def _latex_(self) -> str: r""" The Latex representation of this lattice. @@ -2452,12 +2449,12 @@ def _latex_(self): rep += r" of}\\ %s" % self._representative._latex_() else: rep += r"}" - rep += r"\\ \mbox{Signature: } %s" % (self._signature,) + rep += fr"\\ \mbox{{Signature: }} {self._signature}" for s in self._local_symbols: rep += r"\\ " + s._latex_() return rep - def __eq__(self, other): + def __eq__(self, other) -> bool: r""" Determines if two global genus symbols are equal (not just equivalent!). @@ -2507,7 +2504,7 @@ def __eq__(self, other): return False return True - def __ne__(self, other): + def __ne__(self, other) -> bool: r""" Determine if two global genus symbols are unequal (not just inequivalent!). @@ -2539,7 +2536,7 @@ def __ne__(self, other): """ return not self == other - def is_even(self): + def is_even(self) -> bool: r""" Return if this genus is even. @@ -2606,11 +2603,10 @@ def _proper_spinor_kernel(self): sig = self.signature_pair_of_matrix() if sig[0] * sig[1] > 1: kernel_gens.append(A.delta(-1, prime=-1)) - for sym in syms: - for r in sym.automorphous_numbers(): - kernel_gens.append(A.delta(r, prime=sym.prime())) - K = A.subgroup(kernel_gens) - return A, K + kernel_gens.extend(A.delta(r, prime=sym.prime()) + for sym in syms + for r in sym.automorphous_numbers()) + return A, A.subgroup(kernel_gens) def _improper_spinor_kernel(self): r""" @@ -2651,7 +2647,7 @@ def _improper_spinor_kernel(self): K = A.subgroup(K.gens() + (j,)) return A, K - def spinor_generators(self, proper): + def spinor_generators(self, proper) -> list: r""" Return the spinor generators. @@ -2853,8 +2849,9 @@ def discriminant_form(self): qL = [] for gs in self._local_symbols: p = gs._prime - for block in gs.symbol_tuple_list(): - qL.append(_gram_from_jordan_block(p, block, True)) + qL.extend(_gram_from_jordan_block(p, block, True) + for block in gs.symbol_tuple_list()) + q = matrix.block_diagonal(qL) return TorsionQuadraticForm(q) @@ -3070,7 +3067,7 @@ def representatives(self, backend=None, algorithm=None): return [self.representative()] if n == 2: # Binary forms are considered positive definite take care of that. - e = ZZ(1) + e = ZZ.one() if self.signature_pair()[0] == 0: e = ZZ(-1) d = - 4 * self.determinant() @@ -3083,7 +3080,7 @@ def representatives(self, backend=None, algorithm=None): if n > 2: from sage.quadratic_forms.quadratic_form import QuadraticForm from sage.quadratic_forms.quadratic_form__neighbors import neighbor_iteration - e = ZZ(1) + e = ZZ.one() if not self.is_even(): e = ZZ(2) if self.signature_pair()[0] == 0: @@ -3256,7 +3253,7 @@ def mass(self, backend='sage'): if pos * neg != 0: raise ValueError("the genus must be definite.") if pos + neg == 1: - return QQ(1) / QQ(2) + return QQ((1, 2)) if backend == 'sage': mass = self._standard_mass() for sym in self._local_symbols: @@ -3504,10 +3501,10 @@ def M_p(species, p): 625/1152 """ if species == 0: - return QQ(1) + return QQ.one() n = species.abs() s = (n + 1) // ZZ(2) - mp = ZZ(2) * ZZ.prod(ZZ(1) - p**(-2 * k) for k in range(1, s)) + mp = ZZ(2) * ZZ.prod(ZZ.one() - p**(-2 * k) for k in range(1, s)) if n % 2 == 0: mp *= ZZ.one() - species.sign() * p**(-s) return QQ.one() / mp diff --git a/src/sage/quadratic_forms/genera/spinor_genus.py b/src/sage/quadratic_forms/genera/spinor_genus.py index f049496965d..4f318a64d44 100644 --- a/src/sage/quadratic_forms/genera/spinor_genus.py +++ b/src/sage/quadratic_forms/genera/spinor_genus.py @@ -61,16 +61,16 @@ def _repr_(self) -> str: e = self.exponents() p = self.parent()._primes s = "[2:" - if e[0] == 0 and e[1] == 0: + if e[0] == 0 == e[1]: s += "1" elif e[0] == 1 and e[1] == 0: s += "3" elif e[0] == 0 and e[1] == 1: s += "5" - elif e[0] == 1 and e[1] == 1: + elif e[0] == 1 == e[1]: s += "7" for k in range(1, len(p)): - s += ", %s:%s" % (p[k], (-1)**e[k + 1]) + s += f", {p[k]}:{(-1)**e[k + 1]}" s += "]" return s @@ -116,7 +116,7 @@ def __reduce__(self): OUTPUT: - - a tuple ``f`` such that this element is ``f[0](*f[1])`` + a tuple ``f`` such that this element is ``f[0](*f[1])`` EXAMPLES:: @@ -139,7 +139,7 @@ def _repr_(self) -> str: sage: SpinorOperators((2, 3, 7)) Group of SpinorOperators at primes (2, 3, 7) """ - return "Group of SpinorOperators at primes %s" % (self._primes,) + return f"Group of SpinorOperators at primes {self._primes}" def to_square_class(self, x, p): r""" diff --git a/src/sage/quadratic_forms/quadratic_form__neighbors.py b/src/sage/quadratic_forms/quadratic_form__neighbors.py index ec853cd52ef..0486e0a4acb 100644 --- a/src/sage/quadratic_forms/quadratic_form__neighbors.py +++ b/src/sage/quadratic_forms/quadratic_form__neighbors.py @@ -188,7 +188,7 @@ def find_p_neighbor_from_vec(self, p, y, return_matrix=False): """ p = ZZ(p) if not p.divides(self(y)): - raise ValueError("y=%s must be of square divisible by p=%s" % (y, p)) + raise ValueError(f"y={y} must be of square divisible by p={p}") if self.base_ring() not in [ZZ, QQ]: raise NotImplementedError("the base ring of this form must be the integers or the rationals") @@ -202,10 +202,10 @@ def find_p_neighbor_from_vec(self, p, y, return_matrix=False): odd = True if G.denominator() != 1: raise ValueError("the associated bilinear form q(x+y)-q(x)-q(y) must be integral.") - b = y*G*y + b = y * G * y if not b % p == 0: raise ValueError("y^2 must be divisible by p=%s" % p) - y_dual = y*G + y_dual = y * G if p != 2 and b % p**2 != 0: for k in range(n): if y_dual[k] % p != 0: @@ -213,8 +213,8 @@ def find_p_neighbor_from_vec(self, p, y, return_matrix=False): break else: raise ValueError("either y is not primitive or self is not maximal at %s" % p) - z *= (2*y*G*z).inverse_mod(p) - y = y - b*z + z *= (2 * y * G * z).inverse_mod(p) + y = y - b * z # assert y*G*y % p^2 == 0 if p == 2: val = b.valuation(p) @@ -228,14 +228,14 @@ def find_p_neighbor_from_vec(self, p, y, return_matrix=False): break else: raise ValueError("either y is not primitive or self is not even, maximal at 2") - y += 2*z + y += 2 * z # assert y*G*y % 8 == 0 - y_dual = G*y + y_dual = G * y Ly = y_dual.change_ring(GF(p)).column().kernel().matrix().lift() B = Ly.stack(p * matrix.identity(n)) # the rows of B now generate L_y = { x in L | (x,y)=0 mod p} - B = y.row().stack(p*B) + B = y.row().stack(p * B) B = B.hermite_form()[:n, :] / p # the rows of B generate ZZ * y/p + L_y # by definition this is the p-neighbor of L at y @@ -243,10 +243,10 @@ def find_p_neighbor_from_vec(self, p, y, return_matrix=False): if return_matrix: return B.T - else: - QF = self.parent() - Gnew = (B*G*B.T).change_ring(R) - return QF(Gnew) + + QF = self.parent() + Gnew = (B * G * B.T).change_ring(R) + return QF(Gnew) def neighbor_iteration(seeds, p, mass=None, max_classes=ZZ(10)**3, @@ -346,9 +346,9 @@ def p_divisible_vectors(Q, max_neighbors): raise ValueError("unknown algorithm") waiting_list = list(seeds) isom_classes = [] - mass_count = QQ(0) - n_isom_classes = ZZ(0) - while len(waiting_list) > 0 and mass != mass_count and n_isom_classes < max_classes: + mass_count = QQ.zero() + n_isom_classes = ZZ.zero() + while waiting_list and mass != mass_count and n_isom_classes < max_classes: # find all p-neighbors of Q Q = waiting_list.pop() for v in p_divisible_vectors(Q, max_neighbors): From d67041a126b3aef0a07f06a3efbb1a2363ec6e24 Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Mon, 19 Feb 2024 21:19:46 +0000 Subject: [PATCH 019/518] Add a check for action after precomposing with map to the Integers --- src/sage/schemes/elliptic_curves/ell_point.py | 13 ------------- src/sage/structure/parent.pyx | 19 +++++++++++++++++++ 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/src/sage/schemes/elliptic_curves/ell_point.py b/src/sage/schemes/elliptic_curves/ell_point.py index bf86995daa8..bcfe8d5c1d2 100644 --- a/src/sage/schemes/elliptic_curves/ell_point.py +++ b/src/sage/schemes/elliptic_curves/ell_point.py @@ -3877,19 +3877,6 @@ def _acted_upon_(self, other, side): return Q - def __mul__(self, other): - r""" - For all sage types, _acted_upon_ is called, but for - multiplication by a Python int, the slower __mul__ - is called. Here we bind __mul__ to acted upon so that - the fast Pari multiplication is called when possible, - even when the scalar is a Python int rather than Sage - Integer. - """ - return self._acted_upon_(other, None) - - __rmul__ = __mul__ - def log(self, base): r""" Return the discrete logarithm of this point to the given ``base``. diff --git a/src/sage/structure/parent.pyx b/src/sage/structure/parent.pyx index 6ac939db3c8..86ad87b5a49 100644 --- a/src/sage/structure/parent.pyx +++ b/src/sage/structure/parent.pyx @@ -2678,6 +2678,25 @@ cdef class Parent(sage.structure.category_object.CategoryObject): return action if parent_is_integers(S) and not self.has_coerce_map_from(S): + # Try the above again, but first coerce integer-like type to Integer + # with a connecting coersion + R_el = _Integer(S_el) # Map integer-like to Integer + R = parent(R_el) # Is this the best way to get the Integer parent? + + # Compute the coercsion from whatever S is to the Integer class + # This should always work because parent_is_integers(S) is True + connecting = R._internal_coerce_map_from(S) + + # Now we check if there's an element action from Integers + action = detect_element_action(self, R, self_on_left, self_el, R_el) + # When this is not None, we can do the Precomposed action + if action is not None: + if self_on_left: + return PrecomposedAction(action, None, connecting) + else: + return PrecomposedAction(action, connecting, None) + + # Otherwise, we do the most basic IntegerMulAction from sage.structure.coerce_actions import IntegerMulAction try: return IntegerMulAction(S, self, not self_on_left, self_el) From 0fb4d629be540893331a4714b7eceb853f2be90f Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Mon, 19 Feb 2024 23:28:19 +0000 Subject: [PATCH 020/518] Ensure that connecting is not None, does this help with the CI failure? --- src/sage/structure/parent.pyx | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/sage/structure/parent.pyx b/src/sage/structure/parent.pyx index 86ad87b5a49..a30f3ff6b80 100644 --- a/src/sage/structure/parent.pyx +++ b/src/sage/structure/parent.pyx @@ -2687,14 +2687,17 @@ cdef class Parent(sage.structure.category_object.CategoryObject): # This should always work because parent_is_integers(S) is True connecting = R._internal_coerce_map_from(S) - # Now we check if there's an element action from Integers - action = detect_element_action(self, R, self_on_left, self_el, R_el) - # When this is not None, we can do the Precomposed action - if action is not None: - if self_on_left: - return PrecomposedAction(action, None, connecting) - else: - return PrecomposedAction(action, connecting, None) + # TODO: is this ever not None? + if connecting is not None: + # Now we check if there's an element action from Integers + action = detect_element_action(self, R, self_on_left, self_el, R_el) + # When this is not None, we can do the Precomposed action + if action is not None: + if self_on_left: + action = PrecomposedAction(action, None, connecting) + else: + action = PrecomposedAction(action, connecting, None) + return action # Otherwise, we do the most basic IntegerMulAction from sage.structure.coerce_actions import IntegerMulAction From a9812a5293642f5033449a8f43d74cbaef568835 Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Mon, 19 Feb 2024 23:37:37 +0000 Subject: [PATCH 021/518] whitespace --- src/sage/structure/parent.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/structure/parent.pyx b/src/sage/structure/parent.pyx index a30f3ff6b80..4fff67cc60b 100644 --- a/src/sage/structure/parent.pyx +++ b/src/sage/structure/parent.pyx @@ -2694,7 +2694,7 @@ cdef class Parent(sage.structure.category_object.CategoryObject): # When this is not None, we can do the Precomposed action if action is not None: if self_on_left: - action = PrecomposedAction(action, None, connecting) + action = PrecomposedAction(action, None, connecting) else: action = PrecomposedAction(action, connecting, None) return action From 839b6bd5e17954efd8ae3b7f9aa619d5227517c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Tue, 20 Feb 2024 08:07:09 +0100 Subject: [PATCH 022/518] fix some wrong changes --- src/sage/quadratic_forms/genera/genus.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/sage/quadratic_forms/genera/genus.py b/src/sage/quadratic_forms/genera/genus.py index 2db66a93653..8ab98bc4d06 100644 --- a/src/sage/quadratic_forms/genera/genus.py +++ b/src/sage/quadratic_forms/genera/genus.py @@ -806,7 +806,7 @@ def basis_complement(B): F = B.parent().base_ring() m = B.nrows() n = B.ncols() - C = MatrixSpace(F, n - m, n, sparse=True).zero() + C = MatrixSpace(F, n - m, n, sparse=True)(0) k = 0 l = 0 for i in range(m): @@ -1023,7 +1023,7 @@ def split_odd(A): return A[0, 0], MatrixSpace(ZZ, 0, A.ncols())([]) even, i = is_even_matrix(A) R = A.parent().base_ring() - C = MatrixSpace(R, n0 - 1, n0).zero() + C = MatrixSpace(R, n0 - 1, n0)(0) u = A[i, i] for j in range(n0-1): if j < i: @@ -1035,7 +1035,7 @@ def split_odd(A): B = C*A*C.transpose() even, j = is_even_matrix(B) if even: - I = A.parent().one() + I = A.parent()(1) # TODO: we could manually (re)construct the kernel here... if i == 0: I[1, 0] = 1 - A[1, 0]*u @@ -1045,7 +1045,7 @@ def split_odd(A): i = 0 A = I*A*I.transpose() u = A[i, i] - C = MatrixSpace(R, n0-1, n0).zero() + C = MatrixSpace(R, n0-1, n0)(0) for j in range(n0-1): if j < i: C[j, j] = 1 @@ -1835,15 +1835,15 @@ def _standard_mass(self): """ n = self.dimension() p = self.prime() - s = (n + 1) // ZZ(2) - std = 2 * QQ.prod(1-p**(-2*k) for k in range(1, s)) - if n % 2 == 0: + s = (n + 1) // 2 + std = 2 * QQ.prod(1 - p**(-2 * k) for k in range(1, s)) + if not n % 2: D = ZZ(-1)**s * self.determinant() - epsilon = (4*D).kronecker(p) - std *= (1 - epsilon*p**(-s)) + epsilon = (4 * D).kronecker(p) + std *= (1 - epsilon * p**(-s)) return QQ.one() / std - def _species_list(self): + def _species_list(self) -> list: r""" Return the species list. From 0c4e46dc8e93a5b8089d60dbafc0645b9439ae65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Tue, 20 Feb 2024 09:54:56 +0100 Subject: [PATCH 023/518] add a polytope method to hypergeometric motives --- src/doc/en/reference/references/index.rst | 7 ++++ src/sage/modular/hypergeometric_motive.py | 50 +++++++++++++++++++++-- 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index e68bdbc2d57..0a85c8cbc7b 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -5471,6 +5471,10 @@ REFERENCES: Hecke algebras of type A*. J. Algebraic Combin. **6** (1997), 59-87. +.. [[RRV2022] David P. Roberts and Fernando Rodriguez Villegas, + *Hypergeometric motives*, Notices Amer. Math. Soc., **69** vol. 6 (2022). + :doi:`10.1090/noti2491`. + .. [RSS] :wikipedia:`Residual_sum_of_squares`, accessed 13th October 2009. @@ -5520,6 +5524,9 @@ REFERENCES: .. [RV2007] Fernando Rodriguez Villegas. Experimental Number Theory. Oxford Graduate Texts in Mathematics 13, 2007. +.. [RV2019] Fernando Rodriguez Villegas. *Mixed Hodge numbers and + factorial ratios*. :arxiv:`1907.02722`, 2019. + .. [RW2008] Alexander Raichev and Mark C. Wilson. *Asymptotics of coefficients of multivariate generating functions: improvements for smooth points*, Electronic Journal of diff --git a/src/sage/modular/hypergeometric_motive.py b/src/sage/modular/hypergeometric_motive.py index eb7dfaafe75..ad76993989c 100644 --- a/src/sage/modular/hypergeometric_motive.py +++ b/src/sage/modular/hypergeometric_motive.py @@ -42,6 +42,8 @@ - [Roberts2015]_ +- [RRV2022]_ + - [BeCoMe]_ - [Watkins]_ @@ -60,27 +62,31 @@ from collections import defaultdict from itertools import combinations + from sage.arith.misc import divisors, gcd, euler_phi, moebius, is_prime from sage.arith.misc import gauss_sum, kronecker_symbol from sage.combinat.integer_vector_weighted import WeightedIntegerVectors from sage.functions.generalized import sgn from sage.functions.log import log from sage.functions.other import floor, ceil, frac +from sage.geometry.lattice_polytope import LatticePolytope +from sage.matrix.constructor import matrix from sage.misc.cachefunc import cached_method from sage.misc.functional import cyclotomic_polynomial from sage.misc.misc_c import prod from sage.modular.hypergeometric_misc import hgm_coeffs -from sage.rings.fraction_field import FractionField +from sage.modules.free_module_element import vector +from sage.rings.finite_rings.finite_field_constructor import GF from sage.rings.finite_rings.integer_mod_ring import IntegerModRing +from sage.rings.fraction_field import FractionField from sage.rings.integer_ring import ZZ from sage.rings.padics.padic_generic_element import gauss_table from sage.rings.polynomial.polynomial_ring import polygen, polygens from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.rings.power_series_ring import PowerSeriesRing from sage.rings.rational_field import QQ -from sage.schemes.generic.spec import Spec -from sage.rings.finite_rings.finite_field_constructor import GF from sage.rings.universal_cyclotomic_field import UniversalCyclotomicField +from sage.schemes.generic.spec import Spec def characteristic_polynomial_from_traces(traces, d, q, i, sign): @@ -1131,6 +1137,44 @@ def canonical_scheme(self, t=None): ideal = ring.ideal([eq0, eq1, self.M_value() * eq2_neg - t * eq2_pos]) return Spec(ring.quotient(ideal)) + def polytope(self): + """ + Return the associated lattice polytope. + + This uses the matrix defined in section 3 of [RRV2022]_ and + section 3 of [RV2019]_. + + EXAMPLES:: + + sage: from sage.modular.hypergeometric_motive import HypergeometricData as Hyp + sage: H = Hyp(gamma_list=[-5, -2, 3, 4]) + sage: P = H.polytope(); P + 2-d lattice polytope in 2-d lattice M + sage: P.polyhedron().f_vector() + (1, 4, 4, 1) + sage: len(P.points()) + 7 + + The Chebyshev example from [RV2019]_:: + + sage: H = Hyp(gamma_list=[-30, -1, 6, 10, 15]) + sage: P = H.polytope(); P + 3-d lattice polytope in 3-d lattice M + sage: len(P.points()) + 19 + sage: P.polyhedron().f_vector() + (1, 5, 9, 6, 1) + """ + l = len(self.gamma_list()) + m = matrix(ZZ, l, 1, self.gamma_list()) + ext_ker = m.kernel().basis_matrix().insert_row(0, vector(ZZ, [1] * l)) + unique_relation = ext_ker.kernel().basis()[0] + removed = next(i for i, ci in enumerate(unique_relation) + if i and abs(ci) == 1) + mat = matrix(ZZ, [v for i, v in enumerate(ext_ker) + if i and i != removed]) + return LatticePolytope(mat.transpose()) + # --- Operations on data --- def twist(self): r""" From 87cc7614878c49069ad73b9782b542aa6fe32b43 Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Tue, 20 Feb 2024 14:50:24 +0000 Subject: [PATCH 024/518] Explicitly import ZZ for the coercion --- src/sage/structure/parent.pyx | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/src/sage/structure/parent.pyx b/src/sage/structure/parent.pyx index 4fff67cc60b..0f0ad0ae308 100644 --- a/src/sage/structure/parent.pyx +++ b/src/sage/structure/parent.pyx @@ -2680,24 +2680,25 @@ cdef class Parent(sage.structure.category_object.CategoryObject): if parent_is_integers(S) and not self.has_coerce_map_from(S): # Try the above again, but first coerce integer-like type to Integer # with a connecting coersion - R_el = _Integer(S_el) # Map integer-like to Integer - R = parent(R_el) # Is this the best way to get the Integer parent? + # TODO: is this the best way to gain access to ZZ + # before I used _Integer as defined above but sometimes this was None + from sage.rings.integer_ring import ZZ # Compute the coercsion from whatever S is to the Integer class # This should always work because parent_is_integers(S) is True - connecting = R._internal_coerce_map_from(S) - - # TODO: is this ever not None? - if connecting is not None: - # Now we check if there's an element action from Integers - action = detect_element_action(self, R, self_on_left, self_el, R_el) - # When this is not None, we can do the Precomposed action - if action is not None: - if self_on_left: - action = PrecomposedAction(action, None, connecting) - else: - action = PrecomposedAction(action, connecting, None) - return action + connecting = ZZ._internal_coerce_map_from(S) + ZZ_el = ZZ(S_el) + + # Now we check if there's an element action from Integers + action = detect_element_action(self, ZZ, self_on_left, self_el, ZZ_el) + + # When this is not None, we can do the Precomposed action + if action is not None: + if self_on_left: + action = PrecomposedAction(action, None, connecting) + else: + action = PrecomposedAction(action, connecting, None) + return action # Otherwise, we do the most basic IntegerMulAction from sage.structure.coerce_actions import IntegerMulAction From 387f705df2c42b42f9b03e90d4c5dc2ea0117b00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Tue, 20 Feb 2024 17:24:00 +0100 Subject: [PATCH 025/518] fix mistake in ref --- src/doc/en/reference/references/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index 0a85c8cbc7b..049812bdaad 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -5471,7 +5471,7 @@ REFERENCES: Hecke algebras of type A*. J. Algebraic Combin. **6** (1997), 59-87. -.. [[RRV2022] David P. Roberts and Fernando Rodriguez Villegas, +.. [RRV2022] David P. Roberts and Fernando Rodriguez Villegas, *Hypergeometric motives*, Notices Amer. Math. Soc., **69** vol. 6 (2022). :doi:`10.1090/noti2491`. From 391a72154749cd310335006d1e6c071c8a231c22 Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Tue, 20 Feb 2024 17:52:29 +0000 Subject: [PATCH 026/518] Avoid the buggy case when connecting is None --- src/sage/structure/parent.pyx | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/src/sage/structure/parent.pyx b/src/sage/structure/parent.pyx index 0f0ad0ae308..e654c85a6a3 100644 --- a/src/sage/structure/parent.pyx +++ b/src/sage/structure/parent.pyx @@ -2679,26 +2679,30 @@ cdef class Parent(sage.structure.category_object.CategoryObject): if parent_is_integers(S) and not self.has_coerce_map_from(S): # Try the above again, but first coerce integer-like type to Integer - # with a connecting coersion - # TODO: is this the best way to gain access to ZZ - # before I used _Integer as defined above but sometimes this was None - from sage.rings.integer_ring import ZZ - - # Compute the coercsion from whatever S is to the Integer class - # This should always work because parent_is_integers(S) is True - connecting = ZZ._internal_coerce_map_from(S) - ZZ_el = ZZ(S_el) + # with a connecting coercion + global _Integer + if _Integer is None: + from sage.rings.integer import Integer as _Integer + ZZ_el = _Integer(S_el) + ZZ = ZZ_el.parent() # Now we check if there's an element action from Integers action = detect_element_action(self, ZZ, self_on_left, self_el, ZZ_el) # When this is not None, we can do the Precomposed action if action is not None: - if self_on_left: - action = PrecomposedAction(action, None, connecting) - else: - action = PrecomposedAction(action, connecting, None) - return action + # Compute the coercion from whatever S is to the Integer class + # This should always work because parent_is_integers(S) is True + # but it fails when S is gmpy2.mpz. + # TODO: should we also patch _internal_coerce_map_from so that + # there's a map from gmpy2.mpz to ZZ? + connecting = ZZ._internal_coerce_map_from(S) + if connecting is not None: + if self_on_left: + action = PrecomposedAction(action, None, connecting) + else: + action = PrecomposedAction(action, connecting, None) + return action # Otherwise, we do the most basic IntegerMulAction from sage.structure.coerce_actions import IntegerMulAction From b481d62e87df22f672e796beff14efe4e7a0b6ce Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 20 Feb 2024 18:39:51 -0800 Subject: [PATCH 027/518] src/doc/en/installation/source.rst: Add section on upgrading --- src/doc/en/developer/git_basic.rst | 6 +- src/doc/en/installation/source.rst | 140 +++++++++++++++++++++++++++++ 2 files changed, 143 insertions(+), 3 deletions(-) diff --git a/src/doc/en/developer/git_basic.rst b/src/doc/en/developer/git_basic.rst index 524181438ee..47d8029f4a8 100644 --- a/src/doc/en/developer/git_basic.rst +++ b/src/doc/en/developer/git_basic.rst @@ -40,11 +40,11 @@ or the following brief instructions:: [alice@localhost ~]$ ssh-keygen Generating public/private rsa key pair. - Enter file in which to save the key (/home/user/.ssh/id_rsa): + Enter file in which to save the key (/home/alice/.ssh/id_rsa): Enter passphrase (empty for no passphrase): Enter same passphrase again: - Your identification has been saved in /home/user/.ssh/id_rsa. - Your public key has been saved in /home/user/.ssh/id_rsa.pub. + Your identification has been saved in /home/alice/.ssh/id_rsa. + Your public key has been saved in /home/alice/.ssh/id_rsa.pub. The key fingerprint is: ce:32:b3:de:38:56:80:c9:11:f0:b3:88:f2:1c:89:0a alice@localhost The key's randomart image is: diff --git a/src/doc/en/installation/source.rst b/src/doc/en/installation/source.rst index 26454e4d6d3..9064779510e 100644 --- a/src/doc/en/installation/source.rst +++ b/src/doc/en/installation/source.rst @@ -1139,3 +1139,143 @@ a single copy of Sage in a multi-user computer network. the installation by yourself:: $ sudo chown -R root SAGE_LOCAL + + +Upgrading the system and upgrading Sage +--------------------------------------- + +Caveats when upgrading system packages +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When Sage has been installed from source, it will make use of various system +packages; in particular, it will link to shared libraries provided by +the system. + +The system's package manager does not keep track of the applications that +make use of the shared libraries. Therefore indiscriminate upgrades of +system packages can break a Sage installation. + +This can always be fixed by a full rebuild:: + + $ make distclean && make build + +But this time-consuming step can often be avoided by just reinstalling a +few packages. The command ``make -j list-broken-packages`` assists with +this:: + + $ make -j list-broken-packages + make --no-print-directory auditwheel_or_delocate-no-deps + ... + # Checking .../local/var/lib/sage/installed/bliss-0.73+debian-1+sage-2016-08-02.p0 + ... + Checking shared library file '.../local/lib/libumfpack.dylib' + Checking shared library file '.../local/var/tmp/sage/build/suitesparse-5.10.1/src/lib/libsliplu.1.0.2.dylib' + Error during installcheck of 'suitesparse': .../local/var/tmp/sage/build/suitesparse-5.10.1/src/lib/libsliplu.1.0.2.dylib + ... + Uninstall broken packages by typing: + + make lcalc-SAGE_LOCAL-uninstall; + make ratpoints-SAGE_LOCAL-uninstall; + make r-SAGE_LOCAL-uninstall; + make suitesparse-SAGE_LOCAL-uninstall; + +After running the suggested commands, run:: + + $ make build + + +Upgrading Sage using a separate worktree +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When you have a working installation of Sage built from source and wish to +try out a new version, we strongly recommend to use a separate +`git worktree `_, so that you +can keep using your existing installation when something goes wrong. + +Start from the directory created when you used ``git clone``, perhaps +``~/sage/sage/``. Let's verify that this is indeed a git repository by +looking at the hidden ``.git`` subdirectory. It will looks like this, +but the exact contents can vary:: + + [alice@localhost sage]$ ls .git + COMMIT_EDITMSG HEAD branches description gitk.cache + index logs packed-refs FETCH_HEAD ORIG_HEAD + config hooks info objects refs + +Good. Now let's see what worktrees already exist:: + + [alice@localhost sage]$ git worktree list + /home/alice/sage/sage c0ffeefe10 [master] + +We see just one line, the directory created when you used ``git clone``. +We will call this the "main worktree" from now on. Next to the directory, +you can see the abbreviated commit sha and the name of the branch that +we're on (``master``). + +To try out a new version of Sage, let's fetch it first from the main +repository:: + + [alice@localhost sage]$ git fetch upstream 10.3.beta8 + From https://github.com/sagemath/sage + * tag 10.3.beta8 -> FETCH_HEAD + +Now let's create a new worktree. We need a name for it; it should +start with ``worktree-`` but can be anything after that. Experience +shows that worktrees are often repurposed later, and because a +directory containing a Sage installation cannot be moved without +breaking the installation in it, it may be a good idea to choose +a memorable name without much meaning:: + + [alice@localhost sage]$ git worktree add worktree-purple FETCH_HEAD + Preparing worktree (detached HEAD 30b3d78fac) + Updating files: 100% (11191/11191), done. + HEAD is now at 30b3d78fac Updated SageMath version to 10.3.beta8 + +We now have a subdirectory ``worktree-purple``. This is a +"linked worktree":: + + [alice@localhost sage]$ git worktree list + /home/alice/sage/sage c0ffeefe10 [master] + /home/alice/sage/sage/worktree-purple 30b3d78fac (detached HEAD) + [alice@localhost sage]$ cd worktree-purple + [alice@localhost worktree-purple]$ cat VERSION.txt + SageMath version 10.3.beta8, Release Date: 2024-02-13 + +All worktrees created in this way share the same repository, +so they have access to all branches:: + + [alice@localhost worktree-purple]$ git --no-pager branch -v + * (no branch) 30b3d78fac Updated SageMath version to 10.3.beta8 + + master 2a9a4267f9 Updated SageMath version to 10.2 + +In fact, ``.git`` here is not a directory, just a hidden +file:: + + [alice@localhost worktree-purple]$ ls -l .git + -rw-r--r-- 1 alice staff 59 Feb 20 18:16 .git + +In the new worktree, we now build Sage from scratch. This +is completely independent of and will not disrupt your +existing working installation in the main worktree. + +We will refer again to the step-by-step instructions +from the file +`README.md `_. +Our worktree ``worktree-purple`` is the ``SAGE_ROOT`` +for this purpose. + +One thing that we can share between worktrees without +worry is the directory ``upstream``, where Sage caches +downloaded archives of packages. To have the new worktree +share it with the main worktree, let's create a symbolic +link. This is an optional step that will avoid +re-downloading files that you already have:: + + [alice@localhost worktree-purple]$ ln -s ../upstream/ . + +Now let's build Sage, starting with the step:: + + [alice@localhost worktree-purple]$ make configure + +Refer to the file `README.md `_. +for the following steps. From 913d8c6f38a60f96bd4be92349f018f17f5fa8c8 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 20 Feb 2024 23:46:12 -0800 Subject: [PATCH 028/518] src/doc/en/installation/source.rst: Fix section markup --- src/doc/en/installation/source.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/doc/en/installation/source.rst b/src/doc/en/installation/source.rst index 9064779510e..17c4328621e 100644 --- a/src/doc/en/installation/source.rst +++ b/src/doc/en/installation/source.rst @@ -1145,7 +1145,7 @@ Upgrading the system and upgrading Sage --------------------------------------- Caveats when upgrading system packages -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ When Sage has been installed from source, it will make use of various system packages; in particular, it will link to shared libraries provided by @@ -1184,8 +1184,8 @@ After running the suggested commands, run:: $ make build -Upgrading Sage using a separate worktree -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Upgrading Sage using a separate git worktree +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ When you have a working installation of Sage built from source and wish to try out a new version, we strongly recommend to use a separate From 81aff8c65e4682299257c1e278c8a9fa631f5617 Mon Sep 17 00:00:00 2001 From: Kwankyu Lee Date: Wed, 21 Feb 2024 15:58:35 +0900 Subject: [PATCH 029/518] Add as_subscheme() method for algebraic points --- src/sage/schemes/affine/affine_point.py | 55 +++++++++----- .../schemes/projective/projective_point.py | 75 +++++++++++++------ 2 files changed, 88 insertions(+), 42 deletions(-) diff --git a/src/sage/schemes/affine/affine_point.py b/src/sage/schemes/affine/affine_point.py index dabcaa134f9..32bfb3a42cf 100644 --- a/src/sage/schemes/affine/affine_point.py +++ b/src/sage/schemes/affine/affine_point.py @@ -1,27 +1,19 @@ r""" Points on affine varieties -Scheme morphism for points on affine varieties. - +This module implements scheme morphism for points on affine varieties. AUTHORS: -- David Kohel, William Stein - -- Volker Braun (2011-08-08): Renamed classes, more documentation, misc - cleanups. - -- Ben Hutz (2013) +- David Kohel, William Stein (2006): initial version +- Volker Braun (2011-08-08): renamed classes, more documentation, misc cleanups +- Ben Hutz (2013): many improvements """ -# Historical note: in trac #11599, V.B. renamed -# * _point_morphism_class -> _morphism -# * _homset_class -> _point_homset - # **************************************************************************** -# Copyright (C) 2011 Volker Braun # Copyright (C) 2006 David Kohel # Copyright (C) 2006 William Stein +# Copyright (C) 2011 Volker Braun # # Distributed under the terms of the GNU General Public License (GPL) # as published by the Free Software Foundation; either version 2 of @@ -34,13 +26,11 @@ from sage.schemes.generic.morphism import SchemeMorphism_point, SchemeMorphism, is_SchemeMorphism from sage.structure.sequence import Sequence -_NumberFields = NumberFields() - -############################################################################ -# Rational points on schemes, which we view as morphisms determined -# by coordinates. -############################################################################ +# -------------------------------------------------------------------- +# Rational points on schemes, which we view as morphisms determined by +# coordinates. +# -------------------------------------------------------------------- class SchemeMorphism_point_affine(SchemeMorphism_point): """ @@ -211,7 +201,7 @@ def global_height(self, prec=None): R = RealField(prec) H = max([self[i].abs() for i in range(self.codomain().ambient_space().dimension_relative())]) return R(max(H,1)).log() - if self.domain().base_ring() in _NumberFields or isinstance(self.domain().base_ring(), sage.rings.abc.Order): + if self.domain().base_ring() in NumberFields() or isinstance(self.domain().base_ring(), sage.rings.abc.Order): return max([self[i].global_height(prec) for i in range(self.codomain().ambient_space().dimension_relative())]) else: raise NotImplementedError("must be over a number field or a number field Order") @@ -410,6 +400,31 @@ def multiplicity(self): raise TypeError("this point must be a point on an affine subscheme") return self.codomain().multiplicity(self) + def as_subscheme(self): + r""" + Return the subscheme associated with this rational point. + + EXAMPLES:: + + sage: A2. = AffineSpace(QQ, 2) + sage: p1 = A2.point([0,0]).as_subscheme(); p1 + Closed subscheme of Affine Space of dimension 2 over Rational Field defined by: + x, y + sage: p2 = A2.point([1,1]).as_subscheme(); p2 + Closed subscheme of Affine Space of dimension 2 over Rational Field defined by: + x - 1, y - 1 + sage: p1 + p2 + Closed subscheme of Affine Space of dimension 2 over Rational Field defined by: + x - y, y^2 - y + """ + A = self.codomain().ambient_space() + g = A.gens() + v = self._coords + n = len(v) + if n != len(g): + raise ValueError('not a point of the ambient space') + return A.subscheme([g[i] - v[i] for i in range(n)]) + class SchemeMorphism_point_affine_finite_field(SchemeMorphism_point_affine_field): diff --git a/src/sage/schemes/projective/projective_point.py b/src/sage/schemes/projective/projective_point.py index dea15db15e5..d90b2a440fd 100644 --- a/src/sage/schemes/projective/projective_point.py +++ b/src/sage/schemes/projective/projective_point.py @@ -1,29 +1,23 @@ r""" Points on projective varieties -Scheme morphism for points on projective varieties - - +This module implements scheme morphism for points on projective varieties. AUTHORS: -- David Kohel, William Stein - -- William Stein (2006-02-11): fixed bug where P(0,0,0) was allowed as - a projective point. - -- Volker Braun (2011-08-08): Renamed classes, more documentation, misc - cleanups. - -- Ben Hutz (June 2012) added support for projective ring; - (March 2013) iteration functionality and new directory structure +- David Kohel, William Stein (2006): initial version +- William Stein (2006-02-11): fixed bug where P(0,0,0) was allowed as a + projective point +- Volker Braun (2011-08-08): Renamed classes, more documentation, misc cleanups +- Ben Hutz (2012-06): added support for projective ring +- Ben Hutz (2013-03): added iteration functionality and new directory structure for affine/projective, height functionality """ # **************************************************************************** -# Copyright (C) 2011 Volker Braun # Copyright (C) 2006 David Kohel # Copyright (C) 2006 William Stein +# Copyright (C) 2011 Volker Braun # # 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 @@ -34,7 +28,6 @@ from sage.categories.integral_domains import IntegralDomains from sage.categories.number_fields import NumberFields -_NumberFields = NumberFields() from sage.rings.fraction_field import FractionField from sage.rings.number_field.order import is_NumberFieldOrder, Order as NumberFieldOrder from sage.rings.qqbar import number_field_elements_from_algebraics @@ -52,9 +45,11 @@ from sage.structure.sequence import Sequence from sage.structure.richcmp import richcmp, op_EQ, op_NE -#******************************************************************* + +# -------------------- # Projective varieties -#******************************************************************* +# -------------------- + class SchemeMorphism_point_projective_ring(SchemeMorphism_point): """ A rational point of projective space over a ring. @@ -752,7 +747,7 @@ def global_height(self, prec=None): if prec is None: prec = 53 K = self.codomain().base_ring() - if K in _NumberFields or is_NumberFieldOrder(K): + if K in NumberFields() or is_NumberFieldOrder(K): P = self else: try: @@ -807,7 +802,7 @@ def local_height(self, v, prec=None): 0.693147180559945 """ K = FractionField(self.domain().base_ring()) - if K not in _NumberFields: + if K not in NumberFields(): raise TypeError("must be over a number field or a number field order") return max([K(c).local_height(v, prec=prec) for c in self]) @@ -842,7 +837,7 @@ def local_height_arch(self, i, prec=None): 3.401197381662155375413236691607 """ K = FractionField(self.domain().base_ring()) - if K not in _NumberFields: + if K not in NumberFields(): raise TypeError("must be over a number field or a number field order") if K == QQ: return max(K(c).local_height_arch(prec=prec) for c in self) @@ -1054,6 +1049,7 @@ def is_preperiodic(self, f, err=0.1, return_period=False): except AttributeError: raise TypeError("map must be a dynamical system") + class SchemeMorphism_point_projective_field(SchemeMorphism_point_projective_ring): """ A rational point of projective space over a field. @@ -1401,6 +1397,39 @@ def multiplicity(self): raise TypeError("this point must be a point on a projective subscheme") return self.codomain().multiplicity(self) + def as_subscheme(self): + r""" + Return the subscheme associated with this rational point. + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(QQ,2) + sage: p1 = P2.point([0,0,1]).as_subscheme(); p1 + Closed subscheme of Projective Space of dimension 2 over Rational Field defined by: + x, y + sage: p2 = P2.point([1,1,1]).as_subscheme(); p2 + Closed subscheme of Projective Space of dimension 2 over Rational Field defined by: + x - z, y - z + sage: p1 + p2 + Closed subscheme of Projective Space of dimension 2 over Rational Field defined by: + x - y, y^2 - y*z + """ + P = self.codomain().ambient_space() + g = P.gens() + v = self._coords + n = len(v) + if n != len(g): + raise ValueError('not a point of the ambient space') + for i in range(n - 1, -1, -1): + if v[i]: + break + else: + raise ValueError('invalid homogeneous coordinates') + a = v[i] + x = g[i] + return P.subscheme([a*g[j] - v[j]*x for j in range(n) if j != i]) + + class SchemeMorphism_point_projective_finite_field(SchemeMorphism_point_projective_field): def __hash__(self): @@ -1438,9 +1467,11 @@ def __hash__(self): N = self.codomain().ambient_space().dimension_relative() return hash(sum(hash(self[i]) * p**i for i in range(N + 1))) -#******************************************************************* + +# ----------------- # Abelian varieties -#******************************************************************* +# ----------------- + class SchemeMorphism_point_abelian_variety_field(AdditiveGroupElement, SchemeMorphism_point_projective_field): """ A rational point of an abelian variety over a field. From 9ffa23f09f6520d608960322440b88c05d970ddf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Wed, 21 Feb 2024 09:21:14 +0100 Subject: [PATCH 030/518] suggested name, full pep8, type annotation for hypergeometric motives --- src/sage/modular/hypergeometric_motive.py | 83 +++++++++++------------ 1 file changed, 41 insertions(+), 42 deletions(-) diff --git a/src/sage/modular/hypergeometric_motive.py b/src/sage/modular/hypergeometric_motive.py index ad76993989c..49e13394129 100644 --- a/src/sage/modular/hypergeometric_motive.py +++ b/src/sage/modular/hypergeometric_motive.py @@ -207,7 +207,7 @@ def formule(u): yield H -def possible_hypergeometric_data(d, weight=None): +def possible_hypergeometric_data(d, weight=None) -> list: """ Return the list of possible parameters of hypergeometric motives (up to swapping). @@ -226,7 +226,7 @@ def possible_hypergeometric_data(d, weight=None): return list(enumerate_hypergeometric_data(d, weight)) -def cyclotomic_to_alpha(cyclo): +def cyclotomic_to_alpha(cyclo) -> list: """ Convert a list of indices of cyclotomic polynomials to a list of rational numbers. @@ -252,14 +252,12 @@ def cyclotomic_to_alpha(cyclo): sage: cyclotomic_to_alpha([2,3]) [1/3, 1/2, 2/3] """ - alpha = [] - for d in cyclo: - for k in ZZ(d).coprime_integers(d): - alpha.append(QQ((k, d))) + alpha = [QQ((k, d)) for d in cyclo + for k in ZZ(d).coprime_integers(d)] return sorted(alpha) -def alpha_to_cyclotomic(alpha): +def alpha_to_cyclotomic(alpha) -> list: """ Convert from a list of rationals arguments to a list of integers. @@ -320,10 +318,10 @@ def capital_M(n): [1, 4, 27, 64, 3125, 432, 823543] """ n = ZZ(n) - return QQ.prod(d ** (d * moebius(n / d)) for d in divisors(n)) + return QQ.prod(d ** (d * moebius(n // d)) for d in divisors(n)) -def cyclotomic_to_gamma(cyclo_up, cyclo_down): +def cyclotomic_to_gamma(cyclo_up, cyclo_down) -> dict: """ Convert a quotient of products of cyclotomic polynomials to a quotient of products of polynomials `x^n - 1`. @@ -398,7 +396,7 @@ def gamma_list_to_cyclotomic(galist): sorted(d for d in resu for k in range(-resu[d]))) -class HypergeometricData(): +class HypergeometricData: _gauss_table = {} def __init__(self, cyclotomic=None, alpha_beta=None, gamma_list=None): @@ -450,7 +448,7 @@ def __init__(self, cyclotomic=None, alpha_beta=None, gamma_list=None): deg = sum(euler_phi(x) for x in cyclo_down) up_deg = sum(euler_phi(x) for x in cyclo_up) if up_deg != deg: - msg = 'not the same degree: {} != {}'.format(up_deg, deg) + msg = f'not the same degree: {up_deg} != {deg}' raise ValueError(msg) cyclo_up.sort() cyclo_down.sort() @@ -489,7 +487,7 @@ def __init__(self, cyclotomic=None, alpha_beta=None, gamma_list=None): for v in cyclo_up) # --- Internals --- - def __repr__(self): + def __repr__(self) -> str: """ Return the string representation. @@ -504,7 +502,7 @@ def __repr__(self): txt = "Hypergeometric data for {} and {}" return txt.format(list(self._alpha), list(self._beta)) - def __eq__(self, other): + def __eq__(self, other) -> bool: """ Return whether two data are equal. @@ -521,7 +519,7 @@ def __eq__(self, other): return (self._alpha == other._alpha and self._beta == other._beta) - def __ne__(self, other): + def __ne__(self, other) -> bool: """ Return whether two data are unequal. @@ -550,7 +548,7 @@ def __hash__(self): return hash((self._alpha, self._beta)) # --- Parameters and invariants --- - def cyclotomic_data(self): + def cyclotomic_data(self) -> tuple: """ Return the pair of tuples of indices of cyclotomic polynomials. @@ -562,7 +560,7 @@ def cyclotomic_data(self): """ return (list(self._cyclo_up), list(self._cyclo_down)) - def alpha_beta(self): + def alpha_beta(self) -> tuple: """ Return the pair of lists of rational arguments. @@ -574,7 +572,7 @@ def alpha_beta(self): """ return (list(self._alpha), list(self._beta)) - def alpha(self): + def alpha(self) -> list: """ Return the first tuple of rational arguments. @@ -586,7 +584,7 @@ def alpha(self): """ return list(self._alpha) - def beta(self): + def beta(self) -> list: """ Return the second tuple of rational arguments. @@ -598,7 +596,7 @@ def beta(self): """ return list(self._beta) - def defining_polynomials(self): + def defining_polynomials(self) -> tuple: """ Return the pair of products of cyclotomic polynomials. @@ -612,7 +610,7 @@ def defining_polynomials(self): down = prod(cyclotomic_polynomial(d) for d in self._cyclo_down) return (up, down) - def gamma_array(self): + def gamma_array(self) -> dict: r""" Return the dictionary `\{v: \gamma_v\}` for the expression @@ -630,7 +628,7 @@ def gamma_array(self): """ return dict(self._gamma_array) - def gamma_list(self): + def gamma_list(self) -> list: r""" Return a list of integers describing the `x^n - 1` factors. @@ -654,7 +652,7 @@ def gamma_list(self): resu += [sgn(n) * v] * abs(n) return resu - def wild_primes(self): + def wild_primes(self) -> list: r""" Return the wild primes. @@ -667,14 +665,15 @@ def wild_primes(self): [2, 3, 5] """ gamma = self.gamma_array() - return sorted({p for n in gamma.keys() for (p, _) in n.factor()}) + return sorted({p for n in gamma for p, _ in n.factor()}) def zigzag(self, x, flip_beta=False): r""" Count ``alpha``'s at most ``x`` minus ``beta``'s at most ``x``. This function is used to compute the weight and the Hodge numbers. - With `flip_beta` set to True, replace each `b` in `\beta` with `1-b`. + With `flip_beta` set to ``True``, replace each `b` in `\beta` + with `1-b`. .. SEEALSO:: @@ -771,7 +770,7 @@ def degree(self): """ return self._deg - def hodge_numbers(self): + def hodge_numbers(self) -> list: """ Return the Hodge numbers. @@ -882,7 +881,7 @@ def hodge_function(self, x): return j return j - (i - x) * (k - 1) - def hodge_polygon_vertices(self): + def hodge_polygon_vertices(self) -> list: """ Return the vertices of the Hodge polygon. @@ -948,7 +947,7 @@ def E_polynomial(self, vars=None): gamma_plus = [g for g in gamma if g > 0] gamma_minus = [g for g in gamma if g < 0] - domain = set(d for g in gamma for d in divisors(g.abs())) + domain = {d for g in gamma for d in divisors(g.abs())} m_plus = {d: len([1 for g in gamma_plus if not g % d]) for d in domain} @@ -1003,7 +1002,7 @@ def M_value(self): """ return self._M_value - def is_primitive(self): + def is_primitive(self) -> bool: """ Return whether this data is primitive. @@ -1043,7 +1042,7 @@ def primitive_index(self): """ return gcd(self.gamma_list()) - def has_symmetry_at_one(self): + def has_symmetry_at_one(self) -> bool: """ If ``True``, the motive H(t=1) is a direct sum of two motives. @@ -1123,8 +1122,8 @@ def canonical_scheme(self, t=None): gamma_neg = [u for u in self.gamma_list() if u < 0] N_pos = len(gamma_pos) N_neg = len(gamma_neg) - varX = ['X{}'.format(i) for i in range(N_pos)] - varY = ['Y{}'.format(i) for i in range(N_neg)] + varX = [f'X{i}' for i in range(N_pos)] + varY = [f'Y{i}' for i in range(N_neg)] ring = PolynomialRing(basering, varX + varY) gens = ring.gens() X = gens[:N_pos] @@ -1137,7 +1136,7 @@ def canonical_scheme(self, t=None): ideal = ring.ideal([eq0, eq1, self.M_value() * eq2_neg - t * eq2_pos]) return Spec(ring.quotient(ideal)) - def polytope(self): + def lattice_polytope(self): """ Return the associated lattice polytope. @@ -1148,7 +1147,7 @@ def polytope(self): sage: from sage.modular.hypergeometric_motive import HypergeometricData as Hyp sage: H = Hyp(gamma_list=[-5, -2, 3, 4]) - sage: P = H.polytope(); P + sage: P = H.lattice_polytope(); P 2-d lattice polytope in 2-d lattice M sage: P.polyhedron().f_vector() (1, 4, 4, 1) @@ -1158,7 +1157,7 @@ def polytope(self): The Chebyshev example from [RV2019]_:: sage: H = Hyp(gamma_list=[-30, -1, 6, 10, 15]) - sage: P = H.polytope(); P + sage: P = H.lattice_polytope(); P 3-d lattice polytope in 3-d lattice M sage: len(P.points()) 19 @@ -1308,7 +1307,7 @@ def padic_H_value(self, p, f, t, prec=None, cache_p=False): If left unspecified, `prec` is set to the minimum `p`-adic precision needed to recover the Euler factor. - If `cache_p` is True, then the function caches an intermediate + If `cache_p` is ``True``, then the function caches an intermediate result which depends only on `p` and `f`. This leads to a significant speedup when iterating over `t`. @@ -1401,7 +1400,7 @@ def padic_H_value(self, p, f, t, prec=None, cache_p=False): raise ValueError('p not prime') if not all(x.denominator() % p for x in self._alpha + self._beta): raise NotImplementedError('p is wild') - if (t.numerator()*t.denominator() % p == 0 or (t-1) % p == 0): + if (t.numerator() * t.denominator() % p == 0 or (t - 1) % p == 0): raise NotImplementedError('p is tame') if 0 in alpha: @@ -1420,7 +1419,7 @@ def padic_H_value(self, p, f, t, prec=None, cache_p=False): # also: D = (self.weight() + 1 - m[0]) // 2 if prec is None: - prec = ceil((self.weight() * f) / 2 + log(2*self.degree()+1, p)) + prec = ceil((self.weight() * f) / 2 + log(2 * self.degree() + 1, p)) use_longs = (p ** prec < 2 ** 31) gamma = self._gamma_array @@ -1434,10 +1433,10 @@ def padic_H_value(self, p, f, t, prec=None, cache_p=False): else: gtab = gauss_table(p, f, prec, use_longs) trcoeffs = hgm_coeffs(p, f, prec, gamma, m, D, gtab, prec, use_longs) - sigma = trcoeffs[p-2] + sigma = trcoeffs[p - 2] p_ring = sigma.parent() - teich = p_ring.teichmuller(M/t) - for i in range(p-3, -1, -1): + teich = p_ring.teichmuller(M / t) + for i in range(p - 3, -1, -1): sigma = sigma * teich + trcoeffs[i] resu = ZZ(-1) ** m[0] * sigma / (1 - q) return IntegerModRing(p**prec)(resu).lift_centered() @@ -1542,7 +1541,7 @@ def H_value(self, p, f, t, ring=None): raise ValueError('p not prime') if not all(x.denominator() % p for x in self._alpha + self._beta): raise NotImplementedError('p is wild') - if (t.numerator()*t.denominator() % p == 0 or (t-1) % p == 0): + if (t.numerator() * t.denominator() % p == 0 or (t - 1) % p == 0): raise NotImplementedError('p is tame') if 0 in alpha: @@ -1754,7 +1753,7 @@ def euler_factor(self, t, p, cache_p=False): raise ValueError('p not prime') if not all(x.denominator() % p for x in self._alpha + self._beta): raise NotImplementedError('p is wild') - if (t.numerator()*t.denominator() % p == 0 or (t-1) % p == 0): + if (t.numerator() * t.denominator() % p == 0 or (t - 1) % p == 0): raise NotImplementedError('p is tame') # now p is good d = self.degree() From 0ffd40d373e5eeac105c616c6d73ae4a30e8c510 Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Wed, 21 Feb 2024 10:31:01 +0000 Subject: [PATCH 031/518] fix doctest --- src/sage/rings/polynomial/polynomial_template.pxi | 4 +++- src/sage/rings/ring.pyx | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/sage/rings/polynomial/polynomial_template.pxi b/src/sage/rings/polynomial/polynomial_template.pxi index 863f642754d..076a9753b29 100644 --- a/src/sage/rings/polynomial/polynomial_template.pxi +++ b/src/sage/rings/polynomial/polynomial_template.pxi @@ -376,7 +376,9 @@ cdef class Polynomial_template(Polynomial): if celement_is_zero(&other.x, (self)._cparent): return self if celement_equal(&self.x, &other.x, (self)._cparent): - return self + # note: gcd(g, g) "canonicalizes" the generator i.e. make polynomials monic + # c.f. ring/ring.pyx:445 + return self.monic() cdef type T = type(self) cdef Polynomial_template r = T.__new__(T) diff --git a/src/sage/rings/ring.pyx b/src/sage/rings/ring.pyx index 2356eb44981..be9fa3354b2 100644 --- a/src/sage/rings/ring.pyx +++ b/src/sage/rings/ring.pyx @@ -441,6 +441,7 @@ cdef class Ring(ParentWithGens): g = gens[0] if len(gens) == 1: try: + # TODO: Rewrite this properly g = g.gcd(g) # note: we set g = gcd(g, g) to "canonicalize" the generator: make polynomials monic, etc. except (AttributeError, NotImplementedError): pass From 8766c9bd6c3f2ae47f1d6f0115994ec1eb6b33a9 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 21 Feb 2024 09:12:45 -0800 Subject: [PATCH 032/518] src/doc/en/installation/source.rst: Fix typo --- src/doc/en/installation/source.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/doc/en/installation/source.rst b/src/doc/en/installation/source.rst index 17c4328621e..5fdc573f2e4 100644 --- a/src/doc/en/installation/source.rst +++ b/src/doc/en/installation/source.rst @@ -1277,5 +1277,5 @@ Now let's build Sage, starting with the step:: [alice@localhost worktree-purple]$ make configure -Refer to the file `README.md `_. +Refer to the file `README.md `_ for the following steps. From 30fecca1981087a88eb8db2cf05e18edbb50d16f Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Wed, 21 Feb 2024 22:21:06 +0000 Subject: [PATCH 033/518] Include a doctest for precomposition --- src/sage/structure/parent.pyx | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/sage/structure/parent.pyx b/src/sage/structure/parent.pyx index e654c85a6a3..7a134d770e9 100644 --- a/src/sage/structure/parent.pyx +++ b/src/sage/structure/parent.pyx @@ -2635,6 +2635,25 @@ cdef class Parent(sage.structure.category_object.CategoryObject): sage: print(coercion_model.get_action(E, ZZ, operator.pow)) # needs sage.schemes None + + :: + + With Pull Request #37369, registered multiplication actions by + `ZZ` are also discovered and used when a Python ``int`` is multiplied. + Previously, it was only discovering the generic Integer Multiplication + Action that all additive groups have. As a result, optimised + implementations, such as the use of Pari for scalar multiplication of points + on elliptic curves over Finite Fields, was not used if an ``int`` + multiplied a point, resulting in a 10x slowdown for large characteristic:: + + sage: E = EllipticCurve(GF(17),[1,1]) + sage: coercion_model.discover_action(ZZ, E, operator.mul) + Left action by Integer Ring on Elliptic Curve defined by y^2 = x^3 + x + 1 over Finite Field of size 17 + sage: coercion_model.discover_action(int, E, operator.mul) + Left action by Integer Ring on Elliptic Curve defined by y^2 = x^3 + x + 1 over Finite Field of size 17 + with precomposition on left by Native morphism: + From: Set of Python objects of class 'int' + To: Integer Ring """ # G acts on S, G -> G', R -> S => G' acts on R (?) # NO! ZZ[x,y] acts on Matrices(ZZ[x]) but ZZ[y] does not. From 81a71ae73cc6c60101fb583b87b5ba0085e164bb Mon Sep 17 00:00:00 2001 From: nbruin Date: Wed, 21 Feb 2024 20:20:46 -0800 Subject: [PATCH 034/518] Remove long/int relic in py2, long and int were different types. In py3, only int remains, with long a synonym in cython. --- src/sage/rings/integer_ring.pyx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/sage/rings/integer_ring.pyx b/src/sage/rings/integer_ring.pyx index 559de2065e2..f4da4a8c961 100644 --- a/src/sage/rings/integer_ring.pyx +++ b/src/sage/rings/integer_ring.pyx @@ -586,10 +586,8 @@ cdef class IntegerRing_class(PrincipalIdealDomain): sage: f(-7r) -7 """ - if S is long: + if S is int: return sage.rings.integer.long_to_Z() - elif S is int: - return sage.rings.integer.int_to_Z() elif S is bool: return True elif is_numpy_type(S): From 0da20b8199f3e0090b307162faf04e40e6db5c22 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 21 Feb 2024 21:59:36 -0800 Subject: [PATCH 035/518] replace platform-specific code in sage.parallel.ncpus.ncpus() by os.cpu_count() --- src/sage/parallel/ncpus.py | 58 ++++---------------------------------- 1 file changed, 5 insertions(+), 53 deletions(-) diff --git a/src/sage/parallel/ncpus.py b/src/sage/parallel/ncpus.py index dfb6c3c6734..b386d06666c 100644 --- a/src/sage/parallel/ncpus.py +++ b/src/sage/parallel/ncpus.py @@ -1,46 +1,19 @@ """ CPU Detection """ -# Parallel Python Software: http://www.parallelpython.com -# Copyright (c) 2005-2008, Vitalii Vanovschi -# All rights reserved. -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# * Redistributions of source code must retain the above copyright notice, -# this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# * Neither the name of the author nor the names of its contributors -# may be used to endorse or promote products derived from this software -# without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF -# THE POSSIBILITY OF SUCH DAMAGE. - -###### -# This is from ParallelPython (the pp.py file). import os -import subprocess def ncpus(): """ - Detects the number of effective CPUs in the system. + Detects the number of available CPUs in the system. + + ALGORITHM: :func:`os.cpu_count` EXAMPLES:: - sage: sage.parallel.ncpus.ncpus() # random output -- depends on machine. + sage: sage.parallel.ncpus.ncpus() # random output -- depends on machine 2 """ # Support Sage environment variable SAGE_NUM_THREADS @@ -53,25 +26,4 @@ def ncpus(): else: return int(n) - # for Linux, Unix and MacOS - if hasattr(os, "sysconf"): - if "SC_NPROCESSORS_ONLN" in os.sysconf_names: - # Linux and Unix - ncpus = os.sysconf("SC_NPROCESSORS_ONLN") - if isinstance(ncpus, int) and ncpus > 0: - return ncpus - else: - # MacOS X - # deprecated: return int(os.popen2("sysctl -n hw.ncpu")[1].read()) - process = subprocess.Popen("sysctl -n hw.ncpu", shell=True, - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, close_fds=True) - return int(process.stdout.read()) - # for Windows - if "NUMBER_OF_PROCESSORS" in os.environ: - ncpus = int(os.environ["NUMBER_OF_PROCESSORS"]) - if ncpus > 0: - return ncpus - # return the default value - return 1 + return os.cpu_count() or 1 From f987fdafa3056fed3cb83e3a9d0eb56e634a17a2 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 21 Feb 2024 22:00:18 -0800 Subject: [PATCH 036/518] prefer os.sched_getaffinity() when available --- src/sage/parallel/ncpus.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/sage/parallel/ncpus.py b/src/sage/parallel/ncpus.py index b386d06666c..dda3cde6516 100644 --- a/src/sage/parallel/ncpus.py +++ b/src/sage/parallel/ncpus.py @@ -26,4 +26,9 @@ def ncpus(): else: return int(n) - return os.cpu_count() or 1 + n = None + + if hasattr(os, 'sched_getaffinity'): + n = len(os.sched_getaffinity(0)) + + return n or os.cpu_count() or 1 From a72cb9944e7e97b31ae458619729b92e2d29920c Mon Sep 17 00:00:00 2001 From: Marco Perin <30049458+marco-perin@users.noreply.github.com> Date: Wed, 21 Feb 2024 22:03:43 -0800 Subject: [PATCH 037/518] Update constructor.py In the documentation for polyhedron constructor, there was a typo where `Polyhedron(o)` was used when `Polyhedron(p)` should have been used instead ( the `o` var was from the same code piece, but from the very previous example ). The result of the lines of code have been updated accordingly --- src/sage/geometry/polyhedron/constructor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/geometry/polyhedron/constructor.py b/src/sage/geometry/polyhedron/constructor.py index f18492f584f..8a86eddec66 100644 --- a/src/sage/geometry/polyhedron/constructor.py +++ b/src/sage/geometry/polyhedron/constructor.py @@ -505,9 +505,9 @@ def Polyhedron(vertices=None, rays=None, lines=None, sage: p.add_constraint(x >= -1) sage: p.add_constraint(y <= 1) sage: p.add_constraint(y >= -1) - sage: Polyhedron(o) + sage: Polyhedron(p, base_ring=ZZ) A 2-dimensional polyhedron in ZZ^2 defined as the convex hull of 4 vertices - sage: Polyhedron(o, base_ring=QQ) + sage: Polyhedron(p) A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 4 vertices sage: # needs sage.combinat From 015e3e630bf5d4f251f031addfc40b70857e1c7f Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 21 Feb 2024 22:05:00 -0800 Subject: [PATCH 038/518] Reviewer edits --- src/sage/parallel/ncpus.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/parallel/ncpus.py b/src/sage/parallel/ncpus.py index dda3cde6516..24e8c0c5743 100644 --- a/src/sage/parallel/ncpus.py +++ b/src/sage/parallel/ncpus.py @@ -7,9 +7,9 @@ def ncpus(): """ - Detects the number of available CPUs in the system. + Return the number of available CPUs in the system. - ALGORITHM: :func:`os.cpu_count` + ALGORITHM: :func:`os.sched_getaffinity` or :func:`os.cpu_count` EXAMPLES:: From b1c4b32121430754274048aa89f7e07e7afe9c64 Mon Sep 17 00:00:00 2001 From: Kwankyu Lee Date: Thu, 22 Feb 2024 15:58:35 +0900 Subject: [PATCH 039/518] Edit hypellfrob.pyx --- src/doc/en/reference/references/index.rst | 8 ++ .../hyperelliptic_curves/hypellfrob.pyx | 73 +++++++------------ .../hyperelliptic_curves/hypellfrob/README | 5 +- 3 files changed, 35 insertions(+), 51 deletions(-) diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index e68bdbc2d57..790aecf20f8 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -3137,6 +3137,14 @@ REFERENCES: .. [HarPri] F. Harary and G. Prins. The block-cutpoint-tree of a graph. Publ. Math. Debrecen 13 1966 103-107. +.. [Harv2007] David Harvey. *Kedlaya's algorithm in larger characteristic*, + :arxiv:`math/0610973`. + +.. [BGS2007] Alin Bostan, Pierrick Gaudry, and Eric Schost, *Linear recurrences + with polynomial coefficients and application to integer factorization and + Cartier-Manin operator*, SIAM Journal on Computing 36 (2007), no. 6, + 1777-1806 + .. [Hat2002] Allen Hatcher, "Algebraic Topology", Cambridge University Press (2002). diff --git a/src/sage/schemes/hyperelliptic_curves/hypellfrob.pyx b/src/sage/schemes/hyperelliptic_curves/hypellfrob.pyx index b4144780c1d..a6b31812187 100644 --- a/src/sage/schemes/hyperelliptic_curves/hypellfrob.pyx +++ b/src/sage/schemes/hyperelliptic_curves/hypellfrob.pyx @@ -9,17 +9,22 @@ # sage.doctest: needs sage.libs.ntl sage.modules sage.rings.padics r""" -Frobenius on Monsky-Washnitzer cohomology of a hyperelliptic curve over a -largish prime finite field +Frobenius on Monsky-Washnitzer cohomology of a hyperelliptic curve -This is a wrapper for the matrix() function in hypellfrob.cpp. +This module provides :func:`hypellfrob`, that is a wrapper for the ``matrix()`` +function in ``hypellfrob.cpp``. -AUTHOR: +``hypellfrob.cpp`` is a C++ program for computing the zeta function of a +hyperelliptic curve over a largish prime finite field, based on the method +described in the paper [Harv2007]_. More precisely, it computes the matrix of +Frobenius on the Monsky-Washnitzer cohomology of the curve; the zeta function +can be recovered via the characteristic polynomial of the matrix. -- David Harvey (2007-05) +AUTHORS: +- David Harvey (2007-05): initial version - David Harvey (2007-12): rewrote for ``hypellfrob`` version 2.0 - +- Alex J. Best (2018-02): added wrapper """ # ***************************************************************************** @@ -68,21 +73,17 @@ def interval_products(M0, M1, target): r""" Given a matrix `M` with coefficients linear polynomials over `\ZZ/N\ZZ` and a list of integers `a_0 < b_0 \le a_1 < b_1 \le \cdots \le a_n < b_n` - compute the matrices - ``\prod_{t = a_i + 1}^{b_i} M(t)`` - for `i = 0` to `n`. - - This is a wrapper for code in the ``hypellfrob`` package. + compute the matrices `\prod_{t = a_i + 1}^{b_i} M(t)` for `i = 0` to `n`. INPUT: - - ``M0``, ``M1`` -- matrices over `\ZZ/N\ZZ`, so that `M = M0 + M1*x` + - ``M0``, ``M1`` -- matrices over `\ZZ/N\ZZ`, so that `M = M0 + M1\times x` - ``target`` -- a list of integers ALGORITHM: - Described in [Harv2007]_. - Based on the work of Bostan-Gaudry-Schost [BGS2007]_. + Described in [Harv2007]_. Based on the work of Bostan-Gaudry-Schost + [BGS2007]_. EXAMPLES:: @@ -116,20 +117,6 @@ def interval_products(M0, M1, target): sage: [prod(Matrix(Integers(3^18), 1, 1, [t + 1]) for t in range(3,5))] [[20]] - AUTHORS: - - - David Harvey (2007-12): Original code - - Alex J. Best (2018-02): Wrapper - - REFERENCES: - - .. [Harv2007] David Harvey. *Kedlaya's algorithm in larger characteristic*, - :arxiv:`math/0610973`. - - .. [BGS2007] Alin Bostan, Pierrick Gaudry, and Eric Schost, - *Linear recurrences with polynomial coefficients and application - to integer factorization and Cartier-Manin operator*, SIAM - Journal on Computing 36 (2007), no. 6, 1777-1806 """ # Sage objects that wrap the NTL objects cdef mat_ZZ_p_c mm0, mm1 @@ -175,24 +162,17 @@ def hypellfrob(p, N, Q): INPUT: - ``p`` -- a prime - - ``Q`` -- a monic polynomial in `\ZZ[x]` of odd degree. - Must have no multiple roots mod `p`. - - ``N`` -- precision parameter; the output matrix will be correct modulo `p^N`. - - PRECONDITIONS: + - ``Q`` -- a monic polynomial in `\ZZ[x]` of odd degree; must have no + multiple roots mod `p`. + - ``N`` -- precision parameter; the output matrix will be correct modulo `p^N` - Must have `p > (2g+1)(2N-1)`, where `g = (\deg(Q)-1)/2` is the genus - of the curve. + The prime `p` should satisfy `p > (2g+1)(2N-1)`, where `g = + \frac{\deg(Q)-1}{2}` is the genus of the curve. ALGORITHM: - Described in "Kedlaya's algorithm in larger characteristic" by David - Harvey. Running time is theoretically soft-`O(p^{1/2} N^{5/2} g^3)`. - - .. TODO:: - - Remove the restriction on `p`. Probably by merging in Robert's code, - which eventually needs a fast C++/NTL implementation. + Described in [Harv2007]_. Running time is theoretically soft-`O(p^{1/2} + N^{5/2} g^3)`. EXAMPLES:: @@ -216,10 +196,11 @@ def hypellfrob(p, N, Q): [ O(101) O(101) 65 + O(101) 42 + O(101)] [ O(101) O(101) 89 + O(101) 29 + O(101)] - AUTHORS: + .. TODO:: + + Remove the restriction on `p`. Probably by merging in Robert's code, + which eventually needs a fast C++/NTL implementation. - - David Harvey (2007-05) - - David Harvey (2007-12): updated for hypellfrob version 2.0 """ # Sage objects that wrap the NTL objects cdef ntl_ZZ pp @@ -268,5 +249,3 @@ def hypellfrob(p, N, Q): data = [[mm[j, i]._integer_() + prec for i in range(2 * g)] for j in range(2 * g)] return Matrix(R, data) - -# end of file diff --git a/src/sage/schemes/hyperelliptic_curves/hypellfrob/README b/src/sage/schemes/hyperelliptic_curves/hypellfrob/README index 710d6f0021e..d03da976b04 100644 --- a/src/sage/schemes/hyperelliptic_curves/hypellfrob/README +++ b/src/sage/schemes/hyperelliptic_curves/hypellfrob/README @@ -2,10 +2,7 @@ This directory contains the main source files for hypellfrob version 2.1.1, by David Harvey. hypellfrob is also maintained as a separate project, see -http://cims.nyu.edu/~harvey/hypellfrob/ - -At that web site you'll find a standalone demo program and -a test suite. +https://web.maths.unsw.edu.au/~davidharvey/code/hypellfrob hypellfrob 2.1.1 is Copyright (C) 2007, 2008 David Harvey and is licensed under the GPL (version 2 or later). From 33e14a90c6a4579bcf60d91e8c1c87b19a53550f Mon Sep 17 00:00:00 2001 From: nbruin Date: Thu, 22 Feb 2024 08:58:33 -0800 Subject: [PATCH 040/518] remove int_to_Z because it's buggy int_to_Z used to work in Py2 on the type "int" there which were integers that fit in a word. In Py3 there is no such type anymore and "int" now is what "long" used to be -- -arbitrary length integers. In Py3 int_to_Z is buggy: sage: itoZ=sage.rings.integer.int_to_Z() sage: itoZ(1000000000000000000000) OverflowError --- src/sage/rings/integer.pyx | 81 -------------------------------------- 1 file changed, 81 deletions(-) diff --git a/src/sage/rings/integer.pyx b/src/sage/rings/integer.pyx index aa7d4855bbb..d9f9deede99 100644 --- a/src/sage/rings/integer.pyx +++ b/src/sage/rings/integer.pyx @@ -7389,87 +7389,6 @@ def make_integer(s): return r -cdef class int_to_Z(Morphism): - """ - Morphism from Python ints to Sage integers. - - EXAMPLES:: - - sage: f = ZZ.coerce_map_from(int) - sage: type(f) - - sage: f(5r) - 5 - sage: type(f(5r)) - - sage: 1 + 2r - 3 - sage: type(1 + 2r) - - - This is intended for internal use by the coercion system, - to facilitate fast expressions mixing ints and more complex - Python types. Note that (as with all morphisms) the input - is forcably coerced to the domain ``int`` if it is not - already of the correct type which may have undesirable results:: - - sage: f.domain() - Set of Python objects of class 'int' - sage: f(1/3) - 0 - sage: f(1.7) - 1 - sage: f("10") - 10 - - A pool is used for small integers:: - - sage: f(10) is f(10) - True - sage: f(-2) is f(-2) - True - """ - - def __init__(self): - """ - TESTS:: - - sage: f = ZZ.coerce_map_from(int) - sage: f.parent() - Set of Morphisms from Set of Python objects of class 'int' to Integer Ring in Category of sets - """ - import sage.categories.homset - from sage.sets.pythonclass import Set_PythonType - Morphism.__init__(self, sage.categories.homset.Hom(Set_PythonType(int), integer_ring.ZZ)) - - cpdef Element _call_(self, a) noexcept: - """ - Return a new integer with the same value as ``a``. - - TESTS:: - - sage: f = ZZ.coerce_map_from(int) - sage: f(100r) - 100 - """ - if type(a) is not int: - raise TypeError("must be a Python int object") - - return smallInteger(PyLong_AsLong(a)) - - def _repr_type(self): - """ - TESTS:: - - sage: f = ZZ.coerce_map_from(int) - sage: print(f) - Native morphism: - From: Set of Python objects of class 'int' - To: Integer Ring - """ - return "Native" - - cdef class long_to_Z(Morphism): """ EXAMPLES:: From fa1905c556e570060574f9a6acf98be72c62afed Mon Sep 17 00:00:00 2001 From: nbruin Date: Thu, 22 Feb 2024 09:00:00 -0800 Subject: [PATCH 041/518] remove forward declaration of int_to_Z --- src/sage/rings/integer.pxd | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/sage/rings/integer.pxd b/src/sage/rings/integer.pxd index 2f81c164943..b831f095eb1 100644 --- a/src/sage/rings/integer.pxd +++ b/src/sage/rings/integer.pxd @@ -42,6 +42,3 @@ cdef inline Integer _Integer_from_mpz(mpz_t e) noexcept: cdef Integer z = Integer.__new__(Integer) mpz_set(z.value, e) return z - -cdef class int_to_Z(Morphism): - pass From d8298b98692e249ea0575b6f19351b9e9b79d53d Mon Sep 17 00:00:00 2001 From: nbruin Date: Thu, 22 Feb 2024 10:48:37 -0800 Subject: [PATCH 042/518] rename long_to_Z to int_to_Z --- src/sage/rings/integer.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/integer.pyx b/src/sage/rings/integer.pyx index d9f9deede99..4b837947047 100644 --- a/src/sage/rings/integer.pyx +++ b/src/sage/rings/integer.pyx @@ -7389,7 +7389,7 @@ def make_integer(s): return r -cdef class long_to_Z(Morphism): +cdef class int_to_Z(Morphism): """ EXAMPLES:: From 18ace4ee14b6402f68b8819d21350130c9054c09 Mon Sep 17 00:00:00 2001 From: nbruin Date: Thu, 22 Feb 2024 10:49:41 -0800 Subject: [PATCH 043/518] adapt call site to int_to_Z --- src/sage/rings/integer_ring.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/integer_ring.pyx b/src/sage/rings/integer_ring.pyx index f4da4a8c961..e35559d68d8 100644 --- a/src/sage/rings/integer_ring.pyx +++ b/src/sage/rings/integer_ring.pyx @@ -587,7 +587,7 @@ cdef class IntegerRing_class(PrincipalIdealDomain): -7 """ if S is int: - return sage.rings.integer.long_to_Z() + return sage.rings.integer.int_to_Z() elif S is bool: return True elif is_numpy_type(S): From e69260248bb0b17f22c48fc81d4c1ed33e0a37b1 Mon Sep 17 00:00:00 2001 From: Kwankyu Lee Date: Fri, 23 Feb 2024 04:57:29 +0900 Subject: [PATCH 044/518] Requested changes --- .../hyperelliptic_curves/hypellfrob.pyx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/sage/schemes/hyperelliptic_curves/hypellfrob.pyx b/src/sage/schemes/hyperelliptic_curves/hypellfrob.pyx index a6b31812187..9f9bb04d0ea 100644 --- a/src/sage/schemes/hyperelliptic_curves/hypellfrob.pyx +++ b/src/sage/schemes/hyperelliptic_curves/hypellfrob.pyx @@ -71,18 +71,18 @@ cdef extern from "hypellfrob.h": def interval_products(M0, M1, target): r""" - Given a matrix `M` with coefficients linear polynomials over `\ZZ/N\ZZ` and - a list of integers `a_0 < b_0 \le a_1 < b_1 \le \cdots \le a_n < b_n` - compute the matrices `\prod_{t = a_i + 1}^{b_i} M(t)` for `i = 0` to `n`. + Given matrices `M(t)` with entries linear in `t` over `\ZZ/N\ZZ` and a list + of integers `a_0 < b_0 \le a_1 < b_1 \le \cdots \le a_n < b_n`, compute the + matrices `\prod_{t = a_i + 1}^{b_i} M(t)` for `i = 0` to `n`. INPUT: - - ``M0``, ``M1`` -- matrices over `\ZZ/N\ZZ`, so that `M = M0 + M1\times x` - - ``target`` -- a list of integers + - ``M0``, ``M1`` -- matrices over `\ZZ/N\ZZ`, so that `M(t) = M_0 + M_1t` + - ``target`` -- a list of integers `a_0, b_0, \dots, a_n, b_n` ALGORITHM: - Described in [Harv2007]_. Based on the work of Bostan-Gaudry-Schost + Described in [Harv2007]_, Theorem 10. Based on the work of Bostan-Gaudry-Schost [BGS2007]_. EXAMPLES:: @@ -167,12 +167,12 @@ def hypellfrob(p, N, Q): - ``N`` -- precision parameter; the output matrix will be correct modulo `p^N` The prime `p` should satisfy `p > (2g+1)(2N-1)`, where `g = - \frac{\deg(Q)-1}{2}` is the genus of the curve. + \left(\deg Q - 1\right) / 2` is the genus of the curve. ALGORITHM: - Described in [Harv2007]_. Running time is theoretically soft-`O(p^{1/2} - N^{5/2} g^3)`. + Described in [Harv2007]_, Section 7. Running time is theoretically + `\widetilde{O}(p^{1/2} N^{5/2} g^3)`. EXAMPLES:: From b3a8da4baab5370ddf4d200e16913a8cdb4aa981 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 22 Feb 2024 21:34:36 -0800 Subject: [PATCH 045/518] src/sage/misc/lazy_import.pyx: Deprecate star imports --- src/sage/misc/lazy_import.pyx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/sage/misc/lazy_import.pyx b/src/sage/misc/lazy_import.pyx index c9b09dc1ef5..61a073557d2 100644 --- a/src/sage/misc/lazy_import.pyx +++ b/src/sage/misc/lazy_import.pyx @@ -1117,6 +1117,11 @@ def lazy_import(module, names, as_=None, *, if namespace is None: namespace = inspect.currentframe().f_locals if "*" in names: + from sage.misc.superseded import deprecation_cython + + deprecation_cython(37433, + 'lazy_import of * is deprecated; provide the names to be imported explicitly') + ix = names.index("*") all = get_star_imports(module) names[ix:ix+1] = all From bae605552c3f061f97d28cf470598023a97b00f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Fri, 23 Feb 2024 09:54:56 +0100 Subject: [PATCH 046/518] small cleanup in monoids --- src/sage/monoids/automatic_semigroup.py | 5 +- src/sage/monoids/free_abelian_monoid.py | 11 +++-- .../monoids/free_abelian_monoid_element.pyx | 2 +- src/sage/monoids/free_monoid.py | 2 +- src/sage/monoids/free_monoid_element.py | 23 +++++---- src/sage/monoids/hecke_monoid.py | 8 ++-- src/sage/monoids/indexed_free_monoid.py | 22 ++++----- src/sage/monoids/monoid.py | 6 ++- src/sage/monoids/string_monoid.py | 32 +++++++------ src/sage/monoids/string_monoid_element.py | 34 ++++++++------ src/sage/monoids/string_ops.py | 47 ++++++++++--------- src/sage/monoids/trace_monoid.py | 20 ++++---- 12 files changed, 110 insertions(+), 102 deletions(-) diff --git a/src/sage/monoids/automatic_semigroup.py b/src/sage/monoids/automatic_semigroup.py index b6e96688cf8..8c06f205acc 100644 --- a/src/sage/monoids/automatic_semigroup.py +++ b/src/sage/monoids/automatic_semigroup.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Automatic Semigroups @@ -418,7 +417,7 @@ def _repr_(self): else: of = "" - return "%s%s with %s generators" % (typ, of, len(self._generators)) + return f"{typ}{of} with {len(self._generators)} generators" def repr_element_method(self, style="ambient"): """ @@ -514,7 +513,7 @@ def retract(self, ambient_element, check=True): if element not in self._elements_set: cache = self._retract.cache del cache[((ambient_element,), ())] - raise ValueError("%s not in %s" % (ambient_element, self)) + raise ValueError(f"{ambient_element} not in {self}") return element @cached_method diff --git a/src/sage/monoids/free_abelian_monoid.py b/src/sage/monoids/free_abelian_monoid.py index a4b2465386b..590a20eaa1a 100644 --- a/src/sage/monoids/free_abelian_monoid.py +++ b/src/sage/monoids/free_abelian_monoid.py @@ -44,15 +44,15 @@ sage: x.list() [7, 2, 0, 1, 1] """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2005 David Kohel # # 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.category_object import normalize_names @@ -141,7 +141,7 @@ def FreeAbelianMonoid(index_set=None, names=None, **kwds): sage: FreeAbelianMonoid(names='x,y') Free abelian monoid on 2 generators (x, y) """ - if isinstance(index_set, str): # Swap args (this works if names is None as well) + if isinstance(index_set, str): # Swap args (this works if names is None as well) names, index_set = index_set, names if index_set is None and names is not None: @@ -179,6 +179,7 @@ def is_FreeAbelianMonoid(x): """ return isinstance(x, FreeAbelianMonoid_class) + class FreeAbelianMonoid_class(Parent): """ Free abelian monoid on `n` generators. @@ -204,7 +205,7 @@ def __init__(self, n, names): def __repr__(self): n = self.__ngens - return "Free abelian monoid on %s generators %s" % (n,self.gens()) + return f"Free abelian monoid on {n} generators {self.gens()}" def __call__(self, x): """ diff --git a/src/sage/monoids/free_abelian_monoid_element.pyx b/src/sage/monoids/free_abelian_monoid_element.pyx index 66bd3a15ed5..a9eec5f9065 100644 --- a/src/sage/monoids/free_abelian_monoid_element.pyx +++ b/src/sage/monoids/free_abelian_monoid_element.pyx @@ -321,7 +321,7 @@ cdef class FreeAbelianMonoidElement(MonoidElement): def __pow__(self, n, modulus): """ - Raises self to the power of ``n``. + Raise ``self`` to the power of ``n``. AUTHORS: diff --git a/src/sage/monoids/free_monoid.py b/src/sage/monoids/free_monoid.py index 12688312b9c..c3abc1b8169 100644 --- a/src/sage/monoids/free_monoid.py +++ b/src/sage/monoids/free_monoid.py @@ -196,7 +196,7 @@ def __init__(self, n, names=None): Monoid_class.__init__(self, names) def _repr_(self): - return "Free monoid on %s generators %s" % (self.__ngens, self.gens()) + return f"Free monoid on {self.__ngens} generators {self.gens()}" def _element_constructor_(self, x, check=True): """ diff --git a/src/sage/monoids/free_monoid_element.py b/src/sage/monoids/free_monoid_element.py index 341a88a9ebe..d2cf92c4ba8 100644 --- a/src/sage/monoids/free_monoid_element.py +++ b/src/sage/monoids/free_monoid_element.py @@ -9,7 +9,7 @@ pairs of integers. """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2005 David Kohel # # Distributed under the terms of the GNU General Public License (GPL) @@ -22,7 +22,7 @@ # is available at: # # https://www.gnu.org/licenses/ -#***************************************************************************** +# **************************************************************************** from sage.rings.integer import Integer from sage.structure.element import MonoidElement @@ -123,7 +123,7 @@ def _repr_(self): if e == 1: s += "%s" % g else: - s += "%s^%s" % (g,e) + s += f"{g}^{e}" if len(s) == 0: s = "1" return s @@ -159,9 +159,9 @@ def _latex_(self): g = x[int(v[i][0])] e = v[i][1] if e == 1: - s += "%s " % (g,) + s += f"{g} " else: - s += "%s^{%s}" % (g, e) + s += f"{g}^{{{e}}}" s = s.rstrip(" ") # strip the trailing whitespace caused by adding a space after each element name if len(s) == 0: s = "1" @@ -212,7 +212,7 @@ def __call__(self, *x, **kwds): raise ValueError("must specify as many values as generators in parent") # I don't start with 0, because I don't want to preclude evaluation with - #arbitrary objects (e.g. matrices) because of funny coercion. + # arbitrary objects (e.g. matrices) because of funny coercion. one = P.one() result = None for var_index, exponent in self._element_list: @@ -262,7 +262,7 @@ def _mul_(self, y): z._element_list = x_elt + y_elt else: m = (y_elt[0][0], x_elt[k][1]+y_elt[0][1]) - z._element_list = x_elt[:k] + [ m ] + y_elt[1:] + z._element_list = x_elt[:k] + [m] + y_elt[1:] return z def __invert__(self): @@ -334,8 +334,7 @@ def _richcmp_(self, other, op): def _acted_upon_(self, x, self_on_left): """ - Currently, returns the action of the integer 1 on this - element. + Return the action of the integer 1 on this element. EXAMPLES:: @@ -373,9 +372,9 @@ def to_word(self, alph=None): gens = self.parent().gens() if alph is None: alph = gens - alph = [str(_) for _ in alph] - W = Words(alph) - return W(sum([ [alph[gens.index(i[0])]] * i[1] for i in list(self) ], [])) + alph = [str(c) for c in alph] + W = Words(alph, infinite=False) + return W(sum([[alph[gens.index(i[0])]] * i[1] for i in iter(self)], [])) def to_list(self, indices=False): r""" diff --git a/src/sage/monoids/hecke_monoid.py b/src/sage/monoids/hecke_monoid.py index 98f41dc4552..179446d24e6 100644 --- a/src/sage/monoids/hecke_monoid.py +++ b/src/sage/monoids/hecke_monoid.py @@ -1,16 +1,16 @@ -# -*- coding: utf-8 -*- """ Hecke Monoids """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2015 Nicolas M. Thiéry # # 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_function from sage.sets.finite_set_maps import FiniteSetMaps + @cached_function def HeckeMonoid(W): r""" diff --git a/src/sage/monoids/indexed_free_monoid.py b/src/sage/monoids/indexed_free_monoid.py index 58910533a9a..5467ab1eed7 100644 --- a/src/sage/monoids/indexed_free_monoid.py +++ b/src/sage/monoids/indexed_free_monoid.py @@ -111,7 +111,7 @@ def _repr_(self): scalar_mult = P._print_options['scalar_mult'] - exp = lambda v: '^{}'.format(v) if v != 1 else '' + exp = lambda v: f'^{v}' if v != 1 else '' return scalar_mult.join(P._repr_generator(g) + exp(v) for g,v in monomial) def _ascii_art_(self): @@ -180,7 +180,7 @@ def _latex_(self): if scalar_mult == "*": scalar_mult = " " - exp = lambda v: '^{{{}}}'.format(v) if v != 1 else '' + exp = lambda v: f'^{{{v}}}' if v != 1 else '' return scalar_mult.join(P._latex_generator(g) + exp(v) for g,v in monomial) def __iter__(self): @@ -287,7 +287,7 @@ def support(self): sage: (a*c^3).support() [0, 2] """ - supp = set(key for key, exp in self._sorted_items() if exp != 0) + supp = {key for key, exp in self._sorted_items() if exp != 0} return sorted(supp) def leading_support(self): @@ -544,9 +544,9 @@ def __pow__(self, n): 1 """ if not isinstance(n, (int, Integer)): - raise TypeError("Argument n (= {}) must be an integer".format(n)) + raise TypeError(f"Argument n (= {n}) must be an integer") if n < 0: - raise ValueError("Argument n (= {}) must be positive".format(n)) + raise ValueError(f"Argument n (= {n}) must be positive") if n == 1: return self if n == 0: @@ -733,7 +733,7 @@ def _element_constructor_(self, x=None): if x is None: return self.one() if x in self._indices: - raise TypeError("unable to convert {!r}, use gen() instead".format(x)) + raise TypeError(f"unable to convert {x!r}, use gen() instead") return self.element_class(self, x) def _an_element_(self): @@ -844,7 +844,7 @@ def _repr_(self): sage: FreeMonoid(index_set=ZZ) Free monoid indexed by Integer Ring """ - return "Free monoid indexed by {}".format(self._indices) + return f"Free monoid indexed by {self._indices}" Element = IndexedFreeMonoidElement @@ -884,7 +884,7 @@ def gen(self, x): IndexError: 0 is not in the index set """ if x not in self._indices: - raise IndexError("{} is not in the index set".format(x)) + raise IndexError(f"{x} is not in the index set") try: return self.element_class(self, ((self._indices(x),1),)) except (TypeError, NotImplementedError): # Backup (e.g., if it is a string) @@ -924,7 +924,7 @@ def _repr_(self): sage: FreeAbelianMonoid(index_set=ZZ) Free abelian monoid indexed by Integer Ring """ - return "Free abelian monoid indexed by {}".format(self._indices) + return f"Free abelian monoid indexed by {self._indices}" def _element_constructor_(self, x=None): """ @@ -951,7 +951,7 @@ def _element_constructor_(self, x=None): 1 """ if isinstance(x, (list, tuple)): - d = dict() + d = {} for k, v in x: if k in d: d[k] += v @@ -1000,7 +1000,7 @@ def gen(self, x): IndexError: 0 is not in the index set """ if x not in self._indices: - raise IndexError("{} is not in the index set".format(x)) + raise IndexError(f"{x} is not in the index set") try: return self.element_class(self, {self._indices(x): 1}) except (TypeError, NotImplementedError): # Backup (e.g., if it is a string) diff --git a/src/sage/monoids/monoid.py b/src/sage/monoids/monoid.py index 7af83b5fe4b..97c0dfa6ad3 100644 --- a/src/sage/monoids/monoid.py +++ b/src/sage/monoids/monoid.py @@ -5,9 +5,10 @@ from sage.structure.parent import Parent from sage.misc.cachefunc import cached_method -def is_Monoid(x): + +def is_Monoid(x) -> bool: r""" - Returns True if ``x`` is of type ``Monoid_class``. + Return ``True`` if ``x`` is of type ``Monoid_class``. EXAMPLES:: @@ -27,6 +28,7 @@ def is_Monoid(x): """ return isinstance(x, Monoid_class) + class Monoid_class(Parent): def __init__(self, names): r""" diff --git a/src/sage/monoids/string_monoid.py b/src/sage/monoids/string_monoid.py index 55045fd7f14..7aa51ad9776 100644 --- a/src/sage/monoids/string_monoid.py +++ b/src/sage/monoids/string_monoid.py @@ -8,21 +8,22 @@ Sage supports a wide range of specific free string monoids. """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2007 David Kohel # # 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 .free_monoid import FreeMonoid from .string_monoid_element import StringMonoidElement from .string_ops import strip_encoding + class StringMonoid_class(FreeMonoid): r""" A free string monoid on `n` generators. @@ -108,12 +109,13 @@ def gen(self, i=0): n = self.ngens() if i < 0 or not i < n: raise IndexError( - "Argument i (= %s) must be between 0 and %s." % (i, n-1)) + f"Argument i (= {i}) must be between 0 and {n-1}.") return StringMonoidElement(self, [int(i)]) -#***************************************************************************** + +# **************************************************************************** # Specific global string monoids -#***************************************************************************** +# **************************************************************************** class BinaryStringMonoid(StringMonoid_class): r""" @@ -182,7 +184,7 @@ def __call__(self, x, check=True): sage: S.gen(1) 1 """ - ## There should really some careful type checking here... + # There should really some careful type checking here... if isinstance(x, StringMonoidElement) and x.parent() == self: return x elif isinstance(x, list): @@ -276,7 +278,7 @@ def __init__(self): sage: x[0] * x[3]^3 * x[5]^4 * x[6] 033355556 """ - StringMonoid_class.__init__(self, 8, [ str(i) for i in range(8) ]) + StringMonoid_class.__init__(self, 8, [str(i) for i in range(8)]) def __repr__(self): return "Free octal string monoid" @@ -300,7 +302,7 @@ def __call__(self, x, check=True): sage: S([ i for i in range(8) ]) 01234567 """ - ## There should really some careful type checking here... + # There should really some careful type checking here... if isinstance(x, StringMonoidElement) and x.parent() == self: return x elif isinstance(x, list): @@ -344,7 +346,7 @@ def __init__(self): 0aaaf """ alph = '0123456789abcdef' - StringMonoid_class.__init__(self, 16, [ alph[i] for i in range(16) ]) + StringMonoid_class.__init__(self, 16, [alph[i] for i in range(16)]) def __repr__(self): return "Free hexadecimal string monoid" @@ -368,7 +370,7 @@ def __call__(self, x, check=True): sage: S([ i for i in range(16) ]) 0123456789abcdef """ - ## There should really some careful type checking here... + # There should really some careful type checking here... if isinstance(x, StringMonoidElement) and x.parent() == self: return x elif isinstance(x, list): @@ -453,7 +455,7 @@ def __init__(self): / """ alph = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' - StringMonoid_class.__init__(self, 64, [ alph[i] for i in range(64) ]) + StringMonoid_class.__init__(self, 64, [alph[i] for i in range(64)]) def __repr__(self): return "Free radix 64 string monoid" @@ -480,7 +482,7 @@ def __call__(self, x, check=True): sage: S([ i for i in range(64) ]) ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/ """ - ## There should really some careful type checking here... + # There should really some careful type checking here... if isinstance(x, StringMonoidElement) and x.parent() == self: return x elif isinstance(x, list): @@ -560,7 +562,7 @@ def __init__(self): "W": RR(0.023), "X": RR(0.001), "Y": RR(0.020), "Z": RR(0.001)} alph = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' - StringMonoid_class.__init__(self, 26, [ alph[i] for i in range(26) ]) + StringMonoid_class.__init__(self, 26, [alph[i] for i in range(26)]) def __repr__(self): return "Free alphabetic string monoid on A-Z" @@ -584,7 +586,7 @@ def __call__(self, x, check=True): sage: S([ i for i in range(26) ]) ABCDEFGHIJKLMNOPQRSTUVWXYZ """ - ## There should really some careful type checking here... + # There should really some careful type checking here... if isinstance(x, StringMonoidElement) and x.parent() == self: return x elif isinstance(x, list): diff --git a/src/sage/monoids/string_monoid_element.py b/src/sage/monoids/string_monoid_element.py index 185aa901a07..0299359c7f7 100644 --- a/src/sage/monoids/string_monoid_element.py +++ b/src/sage/monoids/string_monoid_element.py @@ -12,13 +12,13 @@ The internal representation of elements does not use the exponential compression of FreeMonoid elements (a feature), and could be packed into words. """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2007 David Kohel # # Distributed under the terms of the GNU General Public License (GPL) # -# http://www.gnu.org/licenses/ -#***************************************************************************** +# https://www.gnu.org/licenses/ +# **************************************************************************** # import operator from sage.rings.integer import Integer @@ -32,19 +32,21 @@ def is_StringMonoidElement(x): """ return isinstance(x, StringMonoidElement) + def is_AlphabeticStringMonoidElement(x): r""" """ from .string_monoid import AlphabeticStringMonoid return isinstance(x, StringMonoidElement) and \ - isinstance(x.parent(), AlphabeticStringMonoid) + isinstance(x.parent(), AlphabeticStringMonoid) + def is_BinaryStringMonoidElement(x): r""" """ from .string_monoid import BinaryStringMonoid return isinstance(x, StringMonoidElement) and \ - isinstance(x.parent(), BinaryStringMonoid) + isinstance(x.parent(), BinaryStringMonoid) def is_OctalStringMonoidElement(x): @@ -52,7 +54,7 @@ def is_OctalStringMonoidElement(x): """ from .string_monoid import OctalStringMonoid return isinstance(x, StringMonoidElement) and \ - isinstance(x.parent(), OctalStringMonoid) + isinstance(x.parent(), OctalStringMonoid) def is_HexadecimalStringMonoidElement(x): @@ -60,7 +62,7 @@ def is_HexadecimalStringMonoidElement(x): """ from .string_monoid import HexadecimalStringMonoid return isinstance(x, StringMonoidElement) and \ - isinstance(x.parent(), HexadecimalStringMonoid) + isinstance(x.parent(), HexadecimalStringMonoid) def is_Radix64StringMonoidElement(x): @@ -68,7 +70,7 @@ def is_Radix64StringMonoidElement(x): """ from .string_monoid import Radix64StringMonoid return isinstance(x, StringMonoidElement) and \ - isinstance(x.parent(), Radix64StringMonoid) + isinstance(x.parent(), Radix64StringMonoid) class StringMonoidElement(FreeMonoidElement): @@ -89,7 +91,7 @@ def __init__(self, S, x, check=True): if not isinstance(b, (int, Integer)): raise TypeError( "x (= %s) must be a list of integers." % x) - self._element_list = list(x) # make copy + self._element_list = list(x) # make copy elif isinstance(x, str): alphabet = list(self.parent().alphabet()) self._element_list = [] @@ -312,15 +314,15 @@ def decoding(self, padic=False): if isinstance(S, BinaryStringMonoid): if not n % 8 == 0: "String %s must have even length 0 mod 8 to determine a byte character string." % str(self) - pows = [ 2**i for i in range(8) ] + pows = [2**i for i in range(8)] s = [] x = self._element_list for k in range(n//8): m = 8*k if padic: - c = chr(sum([ x[m+i]*pows[i] for i in range(8) ])) + c = chr(sum([x[m+i] * pows[i] for i in range(8)])) else: - c = chr(sum([ x[m+7-i]*pows[i] for i in range(8) ])) + c = chr(sum([x[m+7-i] * pows[i] for i in range(8)])) s.append(c) return ''.join(s) raise TypeError( @@ -328,7 +330,7 @@ def decoding(self, padic=False): def coincidence_index(self, prec=0): """ - Returns the probability of two randomly chosen characters being equal. + Return the probability of two randomly chosen characters being equal. """ if prec == 0: RR = RealField() @@ -424,10 +426,12 @@ def character_count(self): def frequency_distribution(self, length=1, prec=0): """ - Returns the probability space of character frequencies. The output - of this method is different from that of the method + Return the probability space of character frequencies. + + The output of this method is different from that of the method :func:`characteristic_frequency() `. + One can think of the characteristic frequency probability of an element in an alphabet `A` as the expected probability of that element occurring. Let `S` be a string encoded using elements of `A`. The diff --git a/src/sage/monoids/string_ops.py b/src/sage/monoids/string_ops.py index 8f822f98cc8..bdfabe79cdd 100644 --- a/src/sage/monoids/string_ops.py +++ b/src/sage/monoids/string_ops.py @@ -1,16 +1,17 @@ "Utility functions on strings" -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2007 David Kohel # # Distributed under the terms of the GNU General Public License (GPL) # -# http://www.gnu.org/licenses/ -#***************************************************************************** +# https://www.gnu.org/licenses/ +# **************************************************************************** from sage.rings.real_mpfr import RealField from .string_monoid_element import StringMonoidElement + def strip_encoding(S): """ The upper case string of S stripped of all non-alphabetic characters. @@ -21,7 +22,7 @@ def strip_encoding(S): sage: strip_encoding(S) 'THECATINTHEHAT' """ - if not isinstance(S,str): + if not isinstance(S, str): raise TypeError("Argument S (= %s) must be a string.") X = '' for i in range(len(S)): @@ -30,17 +31,18 @@ def strip_encoding(S): X += S[i].upper() return X + def frequency_distribution(S, n=1, field=None): """ The probability space of frequencies of n-character substrings of S. """ - if isinstance(S,tuple): + if isinstance(S, tuple): S = list(S) - elif isinstance(S,(str,StringMonoidElement)): - S = [ S[i:i+n] for i in range(len(S)-n+1) ] + elif isinstance(S, (str, StringMonoidElement)): + S = [S[i:i+n] for i in range(len(S)-n+1)] if field is None: field = RealField() - if isinstance(S,list): + if isinstance(S, list): P = {} N = len(S) eps = field(1)/N @@ -51,10 +53,11 @@ def frequency_distribution(S, n=1, field=None): else: P[c] = eps from sage.probability.random_variable import DiscreteProbabilitySpace - return DiscreteProbabilitySpace(S,P,field) + return DiscreteProbabilitySpace(S, P, field) raise TypeError("Argument S (= %s) must be a string, list, or tuple.") -def coincidence_index(S,n=1): + +def coincidence_index(S, n=1): """ The coincidence index of the string S. @@ -64,7 +67,7 @@ def coincidence_index(S,n=1): sage: coincidence_index(S) 0.120879120879121 """ - if not isinstance(S,str): + if not isinstance(S, str): try: S.coincidence_index(n) except AttributeError: @@ -79,9 +82,10 @@ def coincidence_index(S,n=1): else: X[c] = 1 RR = RealField() - return RR(sum([ m*(m-1) for m in X.values() ]))/RR(N*(N-1)) + return RR(sum([m*(m-1) for m in X.values()]))/RR(N*(N-1)) + -def coincidence_discriminant(S,n=2): +def coincidence_discriminant(S, n=2): """ Input: A tuple of strings, e.g. produced as decimation of transposition ciphertext, or a sample plaintext. @@ -94,24 +98,21 @@ def coincidence_discriminant(S,n=2): sage: coincidence_discriminant([ S[i:i+2] for i in range(len(S)-1) ]) 0.0827001855677322 """ - if not isinstance(S,(list,tuple)): + if not isinstance(S, (list, tuple)): raise TypeError("Argument S (= %s) must be a list or tuple" % S) if n != 2: raise ValueError("Argument n (= %s) is only implemented for n = 2" % n) - truth = True - for bool in ( isinstance(c,(str,StringMonoidElement)) for c in S ): - truth = truth and bool + truth = all(isinstance(c, (str, StringMonoidElement)) for c in S) if not truth: raise TypeError("Argument S (= %s) must be a list of strings.") - for bool in ( len(c) == n for c in S ): - truth = truth and bool + truth = all(len(c) == n for c in S) if not truth: raise ValueError("Argument S (= %s) must be a list of strings of length 2" % S) - X1 = [ frequency_distribution([ s[i] for s in S]) for i in range(2) ] + X1 = [frequency_distribution([s[i] for s in S]) for i in range(2)] XX = frequency_distribution(S) - if isinstance(S[0],StringMonoidElement): + if isinstance(S[0], StringMonoidElement): M = S[0].parent() n = M.ngens() - return sum([ (XX(M([i,j]))-X1[0](M([i]))*X1[1](M([j])))**2 for i in range(n) for j in range(n) ]) + return sum([(XX(M([i, j]))-X1[0](M([i]))*X1[1](M([j])))**2 for i in range(n) for j in range(n)]) AZ = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' - return sum([ (XX(AZ[i]+AZ[j])-X1[0](AZ[i])*X1[1](AZ[j]))**2 for i in range(26) for j in range(26) ]) + return sum([(XX(AZ[i]+AZ[j])-X1[0](AZ[i])*X1[1](AZ[j]))**2 for i in range(26) for j in range(26)]) diff --git a/src/sage/monoids/trace_monoid.py b/src/sage/monoids/trace_monoid.py index f8176a1e1f2..d13b405421b 100644 --- a/src/sage/monoids/trace_monoid.py +++ b/src/sage/monoids/trace_monoid.py @@ -106,7 +106,7 @@ def _repr_(self): """ if self == self.parent().one(): return "1" - return "[{}]".format(self.value) + return f"[{self.value}]" def _richcmp_(self, other, op): r""" @@ -285,7 +285,7 @@ def hasse_diagram(self, algorithm="naive"): elif algorithm == "min": return self.min_hasse_diagram() raise ValueError("`alg` option must be `naive` " - "or `min`, got `{}`.".format(algorithm)) + f"or `min`, got `{algorithm}`.") def min_hasse_diagram(self): r""" @@ -312,7 +312,7 @@ def min_hasse_diagram(self): elements = self._flat_elements() elements.reverse() independence = self.parent()._independence - reachable = dict() + reachable = {} min = set() graph = DiGraph({}) @@ -331,7 +331,7 @@ def min_hasse_diagram(self): min.remove(j) used.add(j) forbidden = set(chain.from_iterable(reachable[v] for v in used)) - front = set(dest for _, dest in graph.outgoing_edges(front, labels=False)) + front = {dest for _, dest in graph.outgoing_edges(front, labels=False)} front = front - forbidden min.add(i) @@ -638,7 +638,7 @@ def _compute_dependence_stack(self, x): d: [False, False, True, False]}) """ independence = self._independence - generators_set = set(e for e, _ in x) + generators_set = {e for e, _ in x} stacks = dict(sorted((g, []) for g in generators_set)) for generator, times in reversed(list(x)): stacks[generator].extend(repeat(True, times)) @@ -723,7 +723,7 @@ def _compute_foata_normal_form(self, x): (b, a*d, a, b, a, b*c, c, a) """ if not x._element_list: - return tuple() + return () generators_set, stacks = self._compute_dependence_stack(x) independence = self._independence @@ -818,8 +818,8 @@ def dependence_graph(self): sage: M.dependence_graph() == Graph({a:[a,b], b:[b], c:[c,b]}) True """ - return Graph(set(frozenset((e1, e2)) if e1 != e2 else (e1, e2) - for e1, e2 in self.dependence()), loops=True, + return Graph({frozenset((e1, e2)) if e1 != e2 else (e1, e2) + for e1, e2 in self.dependence()}, loops=True, format="list_of_edges", immutable=True) @@ -979,7 +979,7 @@ def _repr_(self): """ return ("Trace monoid on {!s} generators {!s} " "with independence relation {{{}}}").format(self.ngens(), self.gens(), - ", ".join("{{{}, {}}}".format(x, y) + ", ".join(f"{{{x}, {y}}}" for (x, y) in self._sorted_independence())) def _latex_(self): @@ -996,7 +996,7 @@ def _latex_(self): return "\\langle {} \\mid {} \\rangle".format( repr(self._free_monoid.gens())[1:-1], ",".join( - "{0!r}{1!r}={1!r}{0!r}".format(v1, v2) + f"{v1!r}{v2!r}={v2!r}{v1!r}" for v1, v2 in self._sorted_independence() ) ) From 4a38b34aa3de396628669f3cb2bf6a3244378a4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Fri, 23 Feb 2024 15:09:33 +0100 Subject: [PATCH 047/518] a few details in cython files in symbolic --- src/sage/symbolic/constants.py | 2 +- src/sage/symbolic/constants_c_impl.pxi | 6 +++--- src/sage/symbolic/expression.pyx | 10 +++++----- src/sage/symbolic/function.pyx | 14 ++++++------- src/sage/symbolic/getitem_impl.pxi | 13 ++++++------ src/sage/symbolic/ginac/function_info.cpp | 2 +- src/sage/symbolic/ginac/sum.cpp | 2 +- src/sage/symbolic/pynac.pxi | 22 ++++++++++----------- src/sage/symbolic/pynac_constant_impl.pxi | 6 +++--- src/sage/symbolic/pynac_impl.pxi | 20 +++++++++---------- src/sage/symbolic/substitution_map_impl.pxi | 15 +++++++------- 11 files changed, 55 insertions(+), 57 deletions(-) diff --git a/src/sage/symbolic/constants.py b/src/sage/symbolic/constants.py index c87bef3471b..351cc5cecc1 100644 --- a/src/sage/symbolic/constants.py +++ b/src/sage/symbolic/constants.py @@ -212,7 +212,7 @@ # 2009 Mike Hansen # Distributed under the terms of the GNU General Public License (GPL), # version 2 or any later version. The full text of the GPL is available at: -# http://www.gnu.org/licenses/ +# https://www.gnu.org/licenses/ ############################################################################### import math diff --git a/src/sage/symbolic/constants_c_impl.pxi b/src/sage/symbolic/constants_c_impl.pxi index 3818926a9a2..520c5e91a53 100644 --- a/src/sage/symbolic/constants_c_impl.pxi +++ b/src/sage/symbolic/constants_c_impl.pxi @@ -2,7 +2,7 @@ The constant `e` """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2008 William Stein # Copyright (C) 2008-2009 Burcin Erocal # Copyright (C) 2009-2012 Mike Hansen @@ -13,8 +13,8 @@ The constant `e` # 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/ +# **************************************************************************** # keep exp(1) for fast access # this is initialized in the constructor of the class E below to prevent diff --git a/src/sage/symbolic/expression.pyx b/src/sage/symbolic/expression.pyx index ab19b46c181..c2a47f3773f 100644 --- a/src/sage/symbolic/expression.pyx +++ b/src/sage/symbolic/expression.pyx @@ -12377,11 +12377,11 @@ cdef class Expression(Expression_abc): """ from sage.symbolic.relation import solve return solve(self, x, multiplicities=multiplicities, - solution_dict=solution_dict, - explicit_solutions=explicit_solutions, - to_poly_solve=to_poly_solve, - algorithm=algorithm, - domain=domain) + solution_dict=solution_dict, + explicit_solutions=explicit_solutions, + to_poly_solve=to_poly_solve, + algorithm=algorithm, + domain=domain) def solve_diophantine(self, x=None, solution_dict=False): """ diff --git a/src/sage/symbolic/function.pyx b/src/sage/symbolic/function.pyx index e349fe20100..3dc59574fba 100644 --- a/src/sage/symbolic/function.pyx +++ b/src/sage/symbolic/function.pyx @@ -652,8 +652,8 @@ cdef class Function(SageObject): if isinstance(x, (float, complex)): return True if isinstance(x, Element): - parent = (x)._parent - return hasattr(parent, 'precision') and parent._is_numerical() + xparent = (x)._parent + return hasattr(xparent, 'precision') and xparent._is_numerical() return False def _interface_init_(self, I=None): @@ -1058,8 +1058,8 @@ cdef class BuiltinFunction(Function): return res from sage.rings.polynomial.polynomial_ring import PolynomialRing_commutative from sage.rings.polynomial.multi_polynomial_ring import MPolynomialRing_polydict_domain - if (isinstance(arg_parent, PolynomialRing_commutative) - or isinstance(arg_parent, MPolynomialRing_polydict_domain)): + if isinstance(arg_parent, (PolynomialRing_commutative, + MPolynomialRing_polydict_domain)): try: return SR(res).polynomial(ring=arg_parent) except TypeError: @@ -1223,8 +1223,8 @@ cdef class SymbolicFunction(Function): slist = [self._nargs, self._name, str(self._latex_name), self._evalf_params_first] for fname in sfunctions_funcs: - real_fname = '_%s_'%fname - if hasattr(self, '%s'%real_fname): + real_fname = '_%s_' % fname + if hasattr(self, '%s' % real_fname): slist.append(hash(getattr(self, real_fname).__code__)) else: slist.append(' ') @@ -1398,7 +1398,7 @@ cdef class SymbolicFunction(Function): for pickle, fname in zip(function_pickles, sfunctions_funcs): if pickle: - real_fname = '_%s_'%fname + real_fname = '_%s_' % fname setattr(self, real_fname, unpickle_function(pickle)) SymbolicFunction.__init__(self, name, nargs, latex_name, diff --git a/src/sage/symbolic/getitem_impl.pxi b/src/sage/symbolic/getitem_impl.pxi index 7bbb2dfe852..c20cf1e6e31 100644 --- a/src/sage/symbolic/getitem_impl.pxi +++ b/src/sage/symbolic/getitem_impl.pxi @@ -1,14 +1,14 @@ "Operands" -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2011 Burcin Erocal # # 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.structure.sage_object cimport SageObject @@ -46,9 +46,10 @@ cdef inline int normalize_index(object arg, int nops, object err_msg) except -1: if i < 0: i = nops + i if i >= nops or i < 0: - raise IndexError("operand index out of range, got %s, expect between %s and %s"%(arg,-nops,nops-1)) + raise IndexError("operand index out of range, got %s, expect between %s and %s" % (arg, -nops, nops-1)) return i + def normalize_index_for_doctests(arg, nops): """ Wrapper function to test ``normalize_index``. @@ -165,7 +166,7 @@ cdef class OperandsWrapper(SageObject): sage: (x^2).op Operands of x^2 """ - return "Operands of %s"%(self._expr) + return "Operands of %s" % (self._expr) def _latex_(self): r""" @@ -174,7 +175,7 @@ cdef class OperandsWrapper(SageObject): sage: latex((x^2).op) \text{Operands wrapper for expression }x^{2} """ - return r"\text{Operands wrapper for expression }%s"%(self._expr._latex_()) + return r"\text{Operands wrapper for expression }%s" % (self._expr._latex_()) def __reduce__(self): """ diff --git a/src/sage/symbolic/ginac/function_info.cpp b/src/sage/symbolic/ginac/function_info.cpp index e12941fc644..4da26c06e53 100644 --- a/src/sage/symbolic/ginac/function_info.cpp +++ b/src/sage/symbolic/ginac/function_info.cpp @@ -1,7 +1,7 @@ // Logic for info flags of function expressions // // (c) 2016,2017 Ralf Stephan -// Distributed under GPL2, see http://www.gnu.org +// Distributed under GPL2, see https://www.gnu.org #include diff --git a/src/sage/symbolic/ginac/sum.cpp b/src/sage/symbolic/ginac/sum.cpp index ede1b216f1e..d8ca1056f18 100644 --- a/src/sage/symbolic/ginac/sum.cpp +++ b/src/sage/symbolic/ginac/sum.cpp @@ -1,7 +1,7 @@ // Algorithms for indefinite and definite summation // // (c) 2016 Ralf Stephan -// Distributed under GPL2, see http://www.gnu.org +// Distributed under GPL2, see https://www.gnu.org // // Ref.: 1. W. Koepf, Algorithms for m-fold Hypergeometric Summation, // Journal of Symbolic Computation (1995) 20, 399-417 diff --git a/src/sage/symbolic/pynac.pxi b/src/sage/symbolic/pynac.pxi index 09f0ad032d5..fe021f891da 100644 --- a/src/sage/symbolic/pynac.pxi +++ b/src/sage/symbolic/pynac.pxi @@ -9,7 +9,7 @@ Check that we can externally cimport this (:trac:`18825`):: ....: ''') """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2008 William Stein # Copyright (C) 2008 Burcin Erocal # Copyright (C) 2017 Jeroen Demeyer @@ -18,8 +18,8 @@ Check that we can externally cimport this (:trac:`18825`):: # 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 cpython.object cimport PyObject from libcpp.vector cimport vector @@ -323,7 +323,6 @@ cdef extern from "pynac_wrap.h": GEx unarchive_ex(GExList sym_lst, unsigned ind) except + void printraw "printraw(std::cout); " (int t) - GEx g_abs "GiNaC::abs" (GEx x) except + # absolute value GEx g_step "GiNaC::unit_step" (GEx x) except + # step function GEx g_csgn "GiNaC::csgn" (GEx x) except + # complex sign @@ -415,10 +414,9 @@ cdef extern from "pynac_wrap.h": void g_foptions_assign "ASSIGN_WRAP" (GFunctionOpt, GFunctionOpt) - GFunctionOpt g_function_options "GiNaC::function_options" \ - (char *m) + GFunctionOpt g_function_options "GiNaC::function_options" (char *m) GFunctionOpt g_function_options_args "GiNaC::function_options" \ - (char *m, unsigned nargs) + (char *m, unsigned nargs) unsigned g_register_new "GiNaC::function::register_new" (GFunctionOpt opt) unsigned find_function "GiNaC::function::find_function" (char* name, @@ -428,7 +426,7 @@ cdef extern from "pynac_wrap.h": bint has_symbol_or_function "GiNaC::has_symbol_or_function" (GEx ex) GFunctionOptVector g_registered_functions \ - "GiNaC::function::registered_functions" () + "GiNaC::function::registered_functions" () # these serials allow us to map pynac function objects to # Sage special functions for the .operator() method of expressions @@ -456,7 +454,7 @@ cdef extern from "pynac_wrap.h": unsigned Li2_serial "GiNaC::Li2_SERIAL::serial" # dilogarithm unsigned Li_serial "GiNaC::Li_SERIAL::serial" # classical polylogarithm as well as multiple polylogarithm unsigned G_serial "GiNaC::G_SERIAL::serial" # multiple polylogarithm - #unsigned G2_serial "GiNaC::G_SERIAL::serial" # multiple polylogarithm with explicit signs for the imaginary parts + # unsigned G2_serial "GiNaC::G_SERIAL::serial" # multiple polylogarithm with explicit signs for the imaginary parts unsigned S_serial "GiNaC::S_SERIAL::serial" # Nielsen's generalized polylogarithm unsigned H_serial "GiNaC::H_SERIAL::serial" # harmonic polylogarithm unsigned zeta1_serial "GiNaC::zeta1_SERIAL::serial" # Riemann's zeta function as well as multiple zeta value @@ -467,7 +465,7 @@ cdef extern from "pynac_wrap.h": unsigned lgamma_serial "GiNaC::lgamma_SERIAL::serial" # logarithm of gamma function unsigned beta_serial "GiNaC::beta_SERIAL::serial" # beta function (tgamma(x)*tgamma(y)/tgamma(x+y)) unsigned psi_serial "GiNaC::psi_SERIAL::serial" # psi (digamma) function - #unsigned psi2_serial "GiNaC::psi_SERIAL::serial" # derivatives of psi function (polygamma functions) + # unsigned psi2_serial "GiNaC::psi_SERIAL::serial" # derivatives of psi function (polygamma functions) unsigned factorial_serial "GiNaC::factorial_SERIAL::serial" # factorial function n! unsigned binomial_serial "GiNaC::binomial_SERIAL::serial" # binomial coefficients unsigned Order_serial "GiNaC::Order_SERIAL::serial" # order term function in truncated power series @@ -567,6 +565,6 @@ cdef extern from "pynac_wrap.h": cdef extern from "ginac/order.h": bint print_order_compare "GiNaC::print_order().compare" \ - (GEx left, GEx right) except + + (GEx left, GEx right) except + bint print_order_compare_mul "GiNaC::print_order_mul().compare" \ - (GEx left, GEx right) except + + (GEx left, GEx right) except + diff --git a/src/sage/symbolic/pynac_constant_impl.pxi b/src/sage/symbolic/pynac_constant_impl.pxi index 9dd5366dcaa..442afbafbc3 100644 --- a/src/sage/symbolic/pynac_constant_impl.pxi +++ b/src/sage/symbolic/pynac_constant_impl.pxi @@ -2,7 +2,7 @@ Wrapper around Pynac's constants """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2008 William Stein # Copyright (C) 2008 Burcin Erocal # Copyright (C) 2009 Mike Hansen @@ -11,8 +11,8 @@ Wrapper around Pynac's constants # 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.cpython.string cimport str_to_bytes diff --git a/src/sage/symbolic/pynac_impl.pxi b/src/sage/symbolic/pynac_impl.pxi index b2091522c49..29f62d102fe 100644 --- a/src/sage/symbolic/pynac_impl.pxi +++ b/src/sage/symbolic/pynac_impl.pxi @@ -707,20 +707,20 @@ cdef stdstring* py_latex_fderivative(unsigned id, params, nv += 1 else: if nv == 1: - diff_args.append(r"\partial %s"%(args[v]._latex_(),)) + diff_args.append(r"\partial %s" % (args[v]._latex_(),)) else: - diff_args.append(r"(\partial %s)^{%s}"%(args[v]._latex_(),nv)) + diff_args.append(r"(\partial %s)^{%s}" % (args[v]._latex_(),nv)) v=next_v nv=1 if nv == 1: - diff_args.append(r"\partial %s"%(args[v]._latex_(),)) + diff_args.append(r"\partial %s" % (args[v]._latex_(),)) else: - diff_args.append(r"(\partial %s)^{%s}"%(args[v]._latex_(),nv)) + diff_args.append(r"(\partial %s)^{%s}" % (args[v]._latex_(),nv)) if len(params) == 1: - operator_string=r"\frac{\partial}{%s}"%(''.join(diff_args),) + operator_string=r"\frac{\partial}{%s}" % (''.join(diff_args),) else: - operator_string=r"\frac{\partial^{%s}}{%s}"%(len(params),''.join(diff_args)) - py_res = operator_string+py_latex_function_pystring(id,args,False) + operator_string=r"\frac{\partial^{%s}}{%s}" % (len(params),''.join(diff_args)) + py_res = operator_string+py_latex_function_pystring(id, args, False) else: ostr = ''.join([r'\mathrm{D}_{', ', '.join(repr(int(x)) for x in params), '}']) @@ -886,7 +886,7 @@ cdef py_binomial_int(int n, unsigned int k) noexcept: cdef bint sign if n < 0: n = -n + (k-1) - sign = k%2 + sign = k % 2 else: sign = 0 cdef Integer ans = PY_NEW(Integer) @@ -903,7 +903,7 @@ cdef py_binomial(n, k) noexcept: cdef bint sign if n < 0: n = k-n-1 - sign = k%2 + sign = k % 2 else: sign = 0 # Convert n and k to unsigned ints. @@ -931,7 +931,7 @@ def test_binomial(n, k): OUTPUT: - integer + integer EXAMPLES:: diff --git a/src/sage/symbolic/substitution_map_impl.pxi b/src/sage/symbolic/substitution_map_impl.pxi index c1dc3be748b..79d8d4491cf 100644 --- a/src/sage/symbolic/substitution_map_impl.pxi +++ b/src/sage/symbolic/substitution_map_impl.pxi @@ -7,15 +7,15 @@ Pynac's ``subs()`` methods and pass a wrapper for the substitution map back to Python. """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2013 Volker Braun # # 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.structure.sage_object cimport SageObject @@ -26,7 +26,7 @@ cdef class SubstitutionMap(SageObject): cpdef Expression apply_to(self, Expression expr, unsigned options) noexcept: """ - Apply the substitution to a symbolic expression + Apply the substitution to a symbolic expression. EXAMPLES:: @@ -40,7 +40,7 @@ cdef class SubstitutionMap(SageObject): def _repr_(self): """ - Return the string representation + Return the string representation. EXAMPLES:: @@ -53,7 +53,7 @@ cdef class SubstitutionMap(SageObject): cdef SubstitutionMap new_SubstitutionMap_from_GExMap(const GExMap& smap) noexcept: """ - Wrap a Pynac object into a Python object + Wrap a Pynac object into a Python object. INPUT: @@ -77,7 +77,7 @@ cdef SubstitutionMap new_SubstitutionMap_from_GExMap(const GExMap& smap) noexcep cpdef SubstitutionMap make_map(subs_dict) noexcept: """ - Construct a new substitution map + Construct a new substitution map. OUTPUT: @@ -94,4 +94,3 @@ cpdef SubstitutionMap make_map(subs_dict) noexcept: smap.insert(make_pair((k)._gobj, (v)._gobj)) return new_SubstitutionMap_from_GExMap(smap) - From dec62ed3ece663ed25c78bd02e36e398781d8283 Mon Sep 17 00:00:00 2001 From: Marc Mezzarobba Date: Tue, 20 Feb 2024 17:09:24 +0100 Subject: [PATCH 048/518] remove most references to Arb as an independent library --- COPYING.txt | 1 - build/bin/sage-package | 1 - build/sage_bootstrap/app.py | 10 ++-- build/sage_bootstrap/cmdline.py | 5 +- src/doc/en/developer/portability_testing.rst | 17 +++--- .../en/reference/rings_numerical/index.rst | 2 +- src/sage/arith/misc.py | 6 ++- src/sage/features/sagemath.py | 2 +- src/sage/matrix/matrix_complex_ball_dense.pxd | 2 +- src/sage/matrix/matrix_complex_ball_dense.pyx | 26 ++++----- src/sage/misc/package.py | 2 - src/sage/numerical/gauss_legendre.pyx | 4 +- src/sage/rings/complex_arb.pxd | 2 +- src/sage/rings/complex_arb.pyx | 53 +++++++++---------- .../number_field_element_quadratic.pxd | 2 +- .../number_field_element_quadratic.pyx | 4 +- .../polynomial/polynomial_complex_arb.pxd | 2 +- .../polynomial/polynomial_complex_arb.pyx | 18 +++---- .../polynomial_integer_dense_flint.pyx | 2 +- .../polynomial/polynomial_rational_flint.pyx | 10 ++-- src/sage/rings/real_arb.pxd | 2 +- src/sage/rings/real_arb.pyx | 33 ++++++------ src/sage/schemes/elliptic_curves/cm.py | 3 +- 23 files changed, 102 insertions(+), 107 deletions(-) diff --git a/COPYING.txt b/COPYING.txt index d5c28eacead..90ef02345b8 100644 --- a/COPYING.txt +++ b/COPYING.txt @@ -31,7 +31,6 @@ the licenses of the components of Sage are included below as well. SOFTWARE LICENSE ----------------------------------------------------------------------- -arb GPLv2+ boehm_gc MIT-like license (see below) backports_ssl_match_hostname Python License boost_cropped Boost Software License (see below) diff --git a/build/bin/sage-package b/build/bin/sage-package index 02a07140875..f4e4201f9d7 100755 --- a/build/bin/sage-package +++ b/build/bin/sage-package @@ -15,7 +15,6 @@ # # $ sage-package list # 4ti2 -# arb # autotools # [...] # zlib diff --git a/build/sage_bootstrap/app.py b/build/sage_bootstrap/app.py index 488b9b9edbd..9d88b6d960e 100644 --- a/build/sage_bootstrap/app.py +++ b/build/sage_bootstrap/app.py @@ -57,8 +57,8 @@ def list_cls(self, *package_classes, **filters): $ sage --package list 4ti2 - arb - autotools + _bootstrap + _develop [...] zlib @@ -66,9 +66,9 @@ def list_cls(self, *package_classes, **filters): perl_term_readline_gnu $ sage -package list --has-file=spkg-configure.m4 --has-file=distros/debian.txt - arb - boost_cropped - brial + 4ti2 + _develop + _prereq [...] zlib """ diff --git a/build/sage_bootstrap/cmdline.py b/build/sage_bootstrap/cmdline.py index f11c26adb28..ca37455c678 100644 --- a/build/sage_bootstrap/cmdline.py +++ b/build/sage_bootstrap/cmdline.py @@ -70,14 +70,11 @@ $ sage --package list 4ti2 - arb - autotools [...] zlib $ sage --package list :standard: - arb - backports_ssl_match_hostname + _prereq [...] zlib """ diff --git a/src/doc/en/developer/portability_testing.rst b/src/doc/en/developer/portability_testing.rst index d6460e835d8..b230378c5ee 100644 --- a/src/doc/en/developer/portability_testing.rst +++ b/src/doc/en/developer/portability_testing.rst @@ -243,21 +243,22 @@ At the end of the ``./configure`` run, Sage issued a message like the following:: configure: notice: the following SPKGs did not find equivalent system packages: - arb boost_cropped bzip2 ... zeromq zlib + boost_cropped bzip2 ... zeromq zlib checking for the package system in use... debian configure: hint: installing the following system packages is recommended and may avoid building some of the above SPKGs from source: - configure: $ sudo apt-get install libflint-arb-dev ... libzmq3-dev libz-dev + configure: $ sudo apt-get install ... libzmq3-dev libz-dev configure: After installation, re-run configure using: configure: $ make reconfigure This information comes from Sage's database of equivalent system packages. For example:: - root@39d693b2a75d:/sage# ls build/pkgs/arb/distros/ - arch.txt conda.txt debian.txt gentoo.txt - root@39d693b2a75d:/sage# cat build/pkgs/arb/distros/debian.txt - libflint-arb-dev + $ ls build/pkgs/flint/distros/ + alpine.txt cygwin.txt fedora.txt gentoo.txt macports.txt opensuse.txt void.txt + conda.txt debian.txt freebsd.txt homebrew.txt nix.txt repology.txt + $ cat build/pkgs/flint/distros/debian.txt + libflint-dev Note that these package equivalencies are based on a current stable or testing version of the distribution; the packages are not guaranteed @@ -885,7 +886,7 @@ an isolated copy of Homebrew with all prerequisites for bootstrapping:: checking for a BSD-compatible install... /usr/bin/install -c checking whether build environment is sane... yes ... - configure: notice: the following SPKGs did not find equivalent system packages: arb cbc cliquer ... tachyon xz zeromq + configure: notice: the following SPKGs did not find equivalent system packages: cbc cliquer ... tachyon xz zeromq checking for the package system in use... homebrew configure: hint: installing the following system packages is recommended and may avoid building some of the above SPKGs from source: configure: $ brew install cmake gcc gsl mpfi ninja openblas gpatch r readline xz zeromq @@ -1085,7 +1086,7 @@ Scrolling down in the right pane shows "Annotations": docker (fedora-31, standard) artifacts/logs-commit-8ca1c2df8f1fb4c6d54b44b34b4d8320ebecb164-tox-docker-fedora-31-standard/config.log#L1 - configure: notice: the following SPKGs did not find equivalent system packages: arb cbc cddlib cmake eclib ecm fflas_ffpack flint fplll givaro gp + configure: notice: the following SPKGs did not find equivalent system packages: cbc cddlib cmake eclib ecm fflas_ffpack flint fplll givaro gp Clicking on the annotations does not take you to a very useful place. To view details, click on one of the items in the pane. This diff --git a/src/doc/en/reference/rings_numerical/index.rst b/src/doc/en/reference/rings_numerical/index.rst index a20066c2ec7..c728ac57d88 100644 --- a/src/doc/en/reference/rings_numerical/index.rst +++ b/src/doc/en/reference/rings_numerical/index.rst @@ -28,7 +28,7 @@ Interval Arithmetic ------------------- Sage implements real and complex interval arithmetic using MPFI -(RealIntervalField, ComplexIntervalField) and arb (RealBallField, +(RealIntervalField, ComplexIntervalField) and FLINT (RealBallField, ComplexBallField). .. toctree:: diff --git a/src/sage/arith/misc.py b/src/sage/arith/misc.py index 8fd46542236..b0c7610ed1e 100644 --- a/src/sage/arith/misc.py +++ b/src/sage/arith/misc.py @@ -291,8 +291,10 @@ def bernoulli(n, algorithm='default', num_threads=1): - ``'default'`` -- use 'flint' for n <= 20000, then 'arb' for n <= 300000 and 'bernmm' for larger values (this is just a heuristic, and not guaranteed to be optimal on all hardware) - - ``'arb'`` -- use the arb library - - ``'flint'`` -- use the FLINT library + - ``'arb'`` -- use the ``bernoulli_fmpq_ui`` function (formerly part of + Arb) of the FLINT library + - ``'flint'`` -- use the ``arith_bernoulli_number`` function of the FLINT + library - ``'pari'`` -- use the PARI C library - ``'gap'`` -- use GAP - ``'gp'`` -- use PARI/GP interpreter diff --git a/src/sage/features/sagemath.py b/src/sage/features/sagemath.py index d78c95e38b2..75a925895c5 100644 --- a/src/sage/features/sagemath.py +++ b/src/sage/features/sagemath.py @@ -360,7 +360,7 @@ def __init__(self): class sage__libs__flint(JoinFeature): r""" A :class:`sage.features.Feature` describing the presence of :mod:`sage.libs.flint` - and other modules depending on FLINT and arb. + and other modules depending on FLINT. In addition to the modularization purposes that this tag serves, it also provides attribution to the upstream project. diff --git a/src/sage/matrix/matrix_complex_ball_dense.pxd b/src/sage/matrix/matrix_complex_ball_dense.pxd index 9a17089a1c7..6b34affb338 100644 --- a/src/sage/matrix/matrix_complex_ball_dense.pxd +++ b/src/sage/matrix/matrix_complex_ball_dense.pxd @@ -1,4 +1,4 @@ -from sage.libs.arb.types cimport acb_mat_t +from sage.libs.flint.types cimport acb_mat_t from sage.matrix.matrix_dense cimport Matrix_dense from sage.matrix.matrix_generic_dense cimport Matrix_generic_dense from sage.structure.parent cimport Parent diff --git a/src/sage/matrix/matrix_complex_ball_dense.pyx b/src/sage/matrix/matrix_complex_ball_dense.pyx index 2f0131f4064..4d4c3b2d6bd 100644 --- a/src/sage/matrix/matrix_complex_ball_dense.pyx +++ b/src/sage/matrix/matrix_complex_ball_dense.pyx @@ -1,14 +1,14 @@ # distutils: libraries = flint r""" -Arbitrary precision complex ball matrices using Arb +Arbitrary precision complex ball matrices AUTHORS: - Clemens Heuberger (2014-10-25): Initial version. -This is a rudimentary binding to the `Arb library -`_; it may be useful to refer to its -documentation for more details. +This is an incomplete interface to the `acb_mat module +`_ of FLINT; it may be useful to refer +to its documentation for more details. TESTS:: @@ -36,8 +36,8 @@ from cpython.object cimport Py_EQ, Py_NE from cysignals.signals cimport sig_on, sig_str, sig_off from sage.arith.power cimport generic_power_pos -from sage.libs.arb.acb cimport * -from sage.libs.arb.acb_mat cimport * +from sage.libs.flint.acb cimport * +from sage.libs.flint.acb_mat cimport * from sage.libs.gmp.mpz cimport mpz_fits_ulong_p, mpz_get_ui from sage.matrix.constructor import matrix from sage.matrix.args cimport SparseEntry, MatrixArgs_init @@ -120,7 +120,7 @@ cdef inline long prec(Matrix_complex_ball_dense mat) noexcept: cdef class Matrix_complex_ball_dense(Matrix_dense): """ Matrix over a complex ball field. Implemented using the - ``acb_mat`` type of the Arb library. + ``acb_mat`` type of the FLINT library. EXAMPLES:: @@ -143,7 +143,7 @@ cdef class Matrix_complex_ball_dense(Matrix_dense): sage: type(a) """ - sig_str("Arb exception") + sig_str("FLINT exception") acb_mat_init(self.value, self._nrows, self._ncols) sig_off() @@ -695,7 +695,7 @@ cdef class Matrix_complex_ball_dense(Matrix_dense): There is currently no guarantee that the algorithm converges as the working precision is increased. - See the `Arb documentation `__ + See the `FLINT documentation `__ for more information. EXAMPLES:: @@ -764,7 +764,7 @@ cdef class Matrix_complex_ball_dense(Matrix_dense): No guarantees are made about the accuracy of the output. - See the `Arb documentation `__ + See the `FLINT documentation `__ for more information. EXAMPLES:: @@ -822,7 +822,7 @@ cdef class Matrix_complex_ball_dense(Matrix_dense): Additionally, there is currently no guarantee that the algorithm converges as the working precision is increased. - See the `Arb documentation `__ + See the `FLINT documentation `__ for more information. EXAMPLES:: @@ -882,7 +882,7 @@ cdef class Matrix_complex_ball_dense(Matrix_dense): No guarantees are made about the accuracy of the output. - See the `Arb documentation `__ + See the `FLINT documentation `__ for more information. EXAMPLES:: @@ -921,7 +921,7 @@ cdef class Matrix_complex_ball_dense(Matrix_dense): Additionally, there is currently no guarantee that the algorithm converges as the working precision is increased. - See the `Arb documentation `__ + See the `FLINT documentation `__ for more information. EXAMPLES:: diff --git a/src/sage/misc/package.py b/src/sage/misc/package.py index d64c53ac3e8..0cc5344bced 100644 --- a/src/sage/misc/package.py +++ b/src/sage/misc/package.py @@ -24,7 +24,6 @@ sage: sorted(pkgs.keys()) # optional - sage_spkg, random ['4ti2', 'alabaster', - 'arb', ... 'zlib'] @@ -299,7 +298,6 @@ def list_packages(*pkg_types: str, pkg_sources: List[str] = ['normal', 'pip', 's sage: L = list_packages('standard') sage: sorted(L.keys()) # random ['alabaster', - 'arb', 'babel', ... 'zlib'] diff --git a/src/sage/numerical/gauss_legendre.pyx b/src/sage/numerical/gauss_legendre.pyx index 81bb5f36af5..fceda721c53 100644 --- a/src/sage/numerical/gauss_legendre.pyx +++ b/src/sage/numerical/gauss_legendre.pyx @@ -103,8 +103,8 @@ def nodes_uncached(degree, prec): .. TODO:: - It may be worth testing if using the Arb algorithm for finding the - nodes and weights in ``arb/acb_calc/integrate_gl_auto_deg.c`` has better + It may be worth testing if using the FLINT/Arb algorithm for finding the + nodes and weights in ``src/acb_calc/integrate_gl_auto_deg.c`` has better performance. """ cdef long j,j1,n diff --git a/src/sage/rings/complex_arb.pxd b/src/sage/rings/complex_arb.pxd index d985745c1f5..a0836e8876c 100644 --- a/src/sage/rings/complex_arb.pxd +++ b/src/sage/rings/complex_arb.pxd @@ -1,4 +1,4 @@ -from sage.libs.arb.acb cimport acb_t +from sage.libs.flint.acb cimport acb_t from sage.rings.complex_interval cimport ComplexIntervalFieldElement from sage.rings.real_arb cimport RealBall from sage.structure.element cimport RingElement diff --git a/src/sage/rings/complex_arb.pyx b/src/sage/rings/complex_arb.pyx index b7384f628dc..298db794f09 100644 --- a/src/sage/rings/complex_arb.pyx +++ b/src/sage/rings/complex_arb.pyx @@ -1,17 +1,17 @@ # -*- coding: utf-8 r""" -Arbitrary precision complex balls using Arb +Arbitrary precision complex balls -This is a binding to the `Arb library `_; it -may be useful to refer to its documentation for more details. +This is an incomplete interface to the `acb module of FLINT `_; +it may be useful to refer to its documentation for more details. -Parts of the documentation for this module are copied or adapted from -Arb's own documentation, licenced under the GNU General Public License -version 2, or later. +Parts of the documentation for this module are copied or adapted from Arb's +(now FLINT's) own documentation, licenced at the time under the GNU General +Public License version 2, or later. .. SEEALSO:: - - :mod:`Real balls using Arb ` + - :mod:`Real balls ` - :mod:`Complex interval field (using MPFI) ` - :mod:`Complex intervals (using MPFI) ` @@ -19,12 +19,12 @@ Data Structure ============== A :class:`ComplexBall` represents a complex number with error bounds. It wraps -an Arb object of type ``acb_t``, which consists of a pair of real number balls +an object of type ``acb_t``, which consists of a pair of real number balls representing the real and imaginary part with separate error bounds. (See the documentation of :mod:`sage.rings.real_arb` for more information.) A :class:`ComplexBall` thus represents a rectangle `[m_1-r_1, m_1+r_1] + -[m_2-r_2, m_2+r_2] i` in the complex plane. This is used in Arb instead of a +[m_2-r_2, m_2+r_2] i` in the complex plane. This is used instead of a disk or square representation (consisting of a complex floating-point midpoint with a single radius), since it allows implementing many operations more conveniently by splitting into ball operations on the real and imaginary parts. @@ -43,7 +43,7 @@ Comparison .. WARNING:: - In accordance with the semantics of Arb, identical :class:`ComplexBall` + In accordance with the semantics of FLINT/Arb, identical :class:`ComplexBall` objects are understood to give permission for algebraic simplification. This assumption is made to improve performance. For example, setting ``z = x*x`` sets `z` to a ball enclosing the set `\{t^2 : t \in x\}` and not the @@ -161,16 +161,16 @@ from cpython.complex cimport PyComplex_FromDoubles from sage.ext.stdsage cimport PY_NEW from sage.libs.mpfr cimport MPFR_RNDU, MPFR_RNDD, MPFR_PREC_MIN, mpfr_get_d_2exp -from sage.libs.arb.types cimport ARF_RND_NEAR, arf_t, mag_t -from sage.libs.arb.arb cimport * -from sage.libs.arb.acb cimport * -from sage.libs.arb.acb_calc cimport * -from sage.libs.arb.acb_hypgeom cimport * -from sage.libs.arb.acb_elliptic cimport * -from sage.libs.arb.acb_modular cimport * -from sage.libs.arb.acb_poly cimport * -from sage.libs.arb.arf cimport arf_init, arf_get_d, arf_get_mpfr, arf_clear, arf_set, arf_is_nan -from sage.libs.arb.mag cimport (mag_init, mag_clear, mag_set_d, +from sage.libs.flint.types cimport ARF_RND_NEAR, arf_t, mag_t +from sage.libs.flint.arb cimport * +from sage.libs.flint.acb cimport * +from sage.libs.flint.acb_calc cimport * +from sage.libs.flint.acb_hypgeom cimport * +from sage.libs.flint.acb_elliptic cimport * +from sage.libs.flint.acb_modular cimport * +from sage.libs.flint.acb_poly cimport * +from sage.libs.flint.arf cimport arf_init, arf_get_d, arf_get_mpfr, arf_clear, arf_set, arf_is_nan +from sage.libs.flint.mag cimport (mag_init, mag_clear, mag_set_d, MAG_BITS, mag_zero, mag_set_ui_2exp_si, mag_mul_2exp_si) from sage.libs.flint.fmpz cimport fmpz_t, fmpz_init, fmpz_get_mpz, fmpz_set_mpz, fmpz_clear @@ -371,7 +371,7 @@ class ComplexBallField(UniqueRepresentation, sage.rings.abc.ComplexBallField): sage: CBF.base_ring() Real ball field with 53 bits of precision - There are direct coercions from ZZ and QQ (for which arb provides + There are direct coercions from ZZ and QQ (for which FLINT provides construction functions):: sage: CBF.coerce_map_from(ZZ) @@ -1029,7 +1029,7 @@ class ComplexBallField(UniqueRepresentation, sage.rings.abc.ComplexBallField): the ball field) -- absolute accuracy goal Additionally, the following optional parameters can be used to control - the integration algorithm. See the `Arb documentation `_ + the integration algorithm. See the `FLINT documentation `_ for more information. - ``deg_limit`` -- maximum quadrature degree for each @@ -1150,8 +1150,8 @@ class ComplexBallField(UniqueRepresentation, sage.rings.abc.ComplexBallField): ALGORITHM: - Uses the `acb_calc `_ module of the Arb - library. + Uses the `acb_calc `_ module of + the FLINT library. TESTS:: @@ -1256,7 +1256,7 @@ class ComplexBallField(UniqueRepresentation, sage.rings.abc.ComplexBallField): cdef inline bint _do_sig(long prec) noexcept: """ - Whether signal handlers should be installed for calls to arb. + Whether signal handlers should be installed for calls to FLINT. """ return (prec > 1000) @@ -1298,8 +1298,7 @@ cdef inline real_ball_field(ComplexBall ball) noexcept: cdef class ComplexBall(RingElement): """ - Hold one ``acb_t`` of the `Arb library - `_ + Hold one ``acb_t`` of the `FLINT library `_ EXAMPLES:: diff --git a/src/sage/rings/number_field/number_field_element_quadratic.pxd b/src/sage/rings/number_field/number_field_element_quadratic.pxd index b9fbb294a54..cf679098a2f 100644 --- a/src/sage/rings/number_field/number_field_element_quadratic.pxd +++ b/src/sage/rings/number_field/number_field_element_quadratic.pxd @@ -1,5 +1,5 @@ from sage.libs.gmp.types cimport mpz_t -from sage.libs.arb.types cimport arb_t +from sage.libs.flint.types cimport arb_t from sage.rings.integer cimport Integer from sage.rings.rational cimport Rational from sage.rings.number_field.number_field_element cimport NumberFieldElement, NumberFieldElement_absolute 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 0212d85b6b5..ad6a31d5e87 100644 --- a/src/sage/rings/number_field/number_field_element_quadratic.pyx +++ b/src/sage/rings/number_field/number_field_element_quadratic.pyx @@ -43,8 +43,8 @@ from cysignals.signals cimport sig_on, sig_off from sage.libs.gmp.mpz cimport * from sage.libs.gmp.mpq cimport * from sage.libs.flint.fmpz cimport * -from sage.libs.arb.arb cimport * -from sage.libs.arb.acb cimport * +from sage.libs.flint.arb cimport * +from sage.libs.flint.acb cimport * from sage.libs.ntl.ntl_ZZ cimport ntl_ZZ from sage.libs.ntl.ntl_ZZX cimport ntl_ZZX from sage.libs.mpfi cimport * diff --git a/src/sage/rings/polynomial/polynomial_complex_arb.pxd b/src/sage/rings/polynomial/polynomial_complex_arb.pxd index 29e3cc4fd61..cffe352e2cf 100644 --- a/src/sage/rings/polynomial/polynomial_complex_arb.pxd +++ b/src/sage/rings/polynomial/polynomial_complex_arb.pxd @@ -1,4 +1,4 @@ -from sage.libs.arb.acb_poly cimport * +from sage.libs.flint.acb_poly cimport * from sage.rings.polynomial.polynomial_element cimport Polynomial cdef class Polynomial_complex_arb(Polynomial): diff --git a/src/sage/rings/polynomial/polynomial_complex_arb.pyx b/src/sage/rings/polynomial/polynomial_complex_arb.pyx index 81e799b7a8d..ed52bb01b56 100644 --- a/src/sage/rings/polynomial/polynomial_complex_arb.pyx +++ b/src/sage/rings/polynomial/polynomial_complex_arb.pyx @@ -1,13 +1,13 @@ # -*- coding: utf-8 r""" -Univariate polynomials over `\CC` with interval coefficients using Arb. +Univariate polynomials over `\CC` with Arb ball coefficients. -This is a binding to the `Arb library `_; it -may be useful to refer to its documentation for more details. +This is a binding to the `acb_poly module of FLINT `_; +it may be useful to refer to its documentation for more details. -Parts of the documentation for this module are copied or adapted from -Arb's own documentation, licenced under the GNU General Public License -version 2, or later. +Parts of the documentation for this module are copied or adapted from Arb's +(now FLINT's) own documentation, licenced (at the time) under the GNU General +Public License version 2, or later. .. SEEALSO:: @@ -25,7 +25,7 @@ TESTS: from cysignals.signals cimport sig_on, sig_off -from sage.libs.arb.acb cimport * +from sage.libs.flint.acb cimport * from sage.libs.flint.fmpz cimport * from sage.rings.integer cimport Integer, smallInteger from sage.rings.complex_arb cimport ComplexBall @@ -39,7 +39,7 @@ cdef inline long prec(Polynomial_complex_arb pol) noexcept: cdef class Polynomial_complex_arb(Polynomial): r""" - Wrapper for `Arb `_ polynomials of type + Wrapper for `FLINT `_ polynomials of type ``acb_poly_t`` EXAMPLES:: @@ -806,7 +806,7 @@ cdef class Polynomial_complex_arb(Polynomial): For ``a = 1``, this computes the usual Riemann zeta function. - If ``deflate`` is True, evaluate ζ(s,a) + 1/(1-s), see the Arb + If ``deflate`` is True, evaluate ζ(s,a) + 1/(1-s), see the FLINT documentation for details. EXAMPLES:: diff --git a/src/sage/rings/polynomial/polynomial_integer_dense_flint.pyx b/src/sage/rings/polynomial/polynomial_integer_dense_flint.pyx index 8498f3dd760..5db98ce257c 100644 --- a/src/sage/rings/polynomial/polynomial_integer_dense_flint.pyx +++ b/src/sage/rings/polynomial/polynomial_integer_dense_flint.pyx @@ -61,7 +61,7 @@ from sage.structure.factorization import Factorization from sage.rings.fraction_field_element import FractionFieldElement from sage.arith.functions import lcm -from sage.libs.arb.arb_fmpz_poly cimport arb_fmpz_poly_evaluate_arb, arb_fmpz_poly_evaluate_acb +from sage.libs.flint.arb_fmpz_poly cimport arb_fmpz_poly_evaluate_arb, arb_fmpz_poly_evaluate_acb from sage.libs.flint.fmpz cimport * from sage.libs.flint.fmpz_poly cimport * from sage.libs.flint.fmpz_poly_sage cimport * diff --git a/src/sage/rings/polynomial/polynomial_rational_flint.pyx b/src/sage/rings/polynomial/polynomial_rational_flint.pyx index 1ef3cbaf681..8c779b7786c 100644 --- a/src/sage/rings/polynomial/polynomial_rational_flint.pyx +++ b/src/sage/rings/polynomial/polynomial_rational_flint.pyx @@ -28,16 +28,16 @@ from cysignals.signals cimport sig_on, sig_str, sig_off from cpython.long cimport PyLong_AsLong from sage.arith.long cimport pyobject_to_long -from sage.libs.arb.acb cimport acb_div_fmpz -from sage.libs.arb.arb cimport arb_div_fmpz -from sage.libs.arb.arb_fmpz_poly cimport _arb_fmpz_poly_evaluate_arb, _arb_fmpz_poly_evaluate_acb -from sage.libs.gmp.mpz cimport * -from sage.libs.gmp.mpq cimport * +from sage.libs.flint.acb cimport acb_div_fmpz +from sage.libs.flint.arb cimport arb_div_fmpz +from sage.libs.flint.arb_fmpz_poly cimport _arb_fmpz_poly_evaluate_arb, _arb_fmpz_poly_evaluate_acb from sage.libs.flint.fmpz cimport * from sage.libs.flint.fmpq cimport * from sage.libs.flint.fmpz_poly cimport * from sage.libs.flint.fmpq_poly cimport * from sage.libs.flint.fmpq_poly_sage cimport * +from sage.libs.gmp.mpz cimport * +from sage.libs.gmp.mpq cimport * from sage.interfaces.singular import singular as singular_default diff --git a/src/sage/rings/real_arb.pxd b/src/sage/rings/real_arb.pxd index 0a3a68d1057..263a411ee10 100644 --- a/src/sage/rings/real_arb.pxd +++ b/src/sage/rings/real_arb.pxd @@ -1,4 +1,4 @@ -from sage.libs.arb.arb cimport arb_t +from sage.libs.flint.arb cimport arb_t from sage.libs.mpfi.types cimport mpfi_t from sage.rings.real_mpfi cimport RealIntervalField_class, RealIntervalFieldElement from sage.structure.parent cimport Parent diff --git a/src/sage/rings/real_arb.pyx b/src/sage/rings/real_arb.pyx index 8509b19cc66..46fafb0a552 100644 --- a/src/sage/rings/real_arb.pyx +++ b/src/sage/rings/real_arb.pyx @@ -1,17 +1,17 @@ # -*- coding: utf-8 r""" -Arbitrary precision real balls using Arb +Arbitrary precision real balls -This is a binding to the `Arb library `_ for ball -arithmetic. It may be useful to refer to its documentation for more details. +This is a binding to the `arb module of FLINT `_. +It may be useful to refer to its documentation for more details. -Parts of the documentation for this module are copied or adapted from -Arb's own documentation, licenced under the GNU General Public License -version 2, or later. +Parts of the documentation for this module are copied or adapted from Arb's +(now FLINT's) own documentation, licenced (at the time) under the GNU General +Public License version 2, or later. .. SEEALSO:: - - :mod:`Complex balls using Arb ` + - :mod:`Complex balls ` - :mod:`Real intervals using MPFI ` Data Structure @@ -24,7 +24,7 @@ the overhead of traditional (inf-sup) interval arithmetic at high precision, and eliminating much of the need for time-consuming and bug-prone manual error analysis associated with standard floating-point arithmetic. -Sage :class:`RealBall` objects wrap Arb objects of type ``arb_t``. A real +Sage :class:`RealBall` objects wrap FLINT objects of type ``arb_t``. A real ball represents a ball over the real numbers, that is, an interval `[m-r,m+r]` where the midpoint `m` and the radius `r` are (extended) real numbers:: @@ -62,7 +62,7 @@ Comparison .. WARNING:: - In accordance with the semantics of Arb, identical :class:`RealBall` + In accordance with the semantics of FLINT/Arb, identical :class:`RealBall` objects are understood to give permission for algebraic simplification. This assumption is made to improve performance. For example, setting ``z = x*x`` may set `z` to a ball enclosing the set `\{t^2 : t \in x\}` and not @@ -204,11 +204,11 @@ from cpython.long cimport PyLong_AsLong from cpython.object cimport Py_LT, Py_LE, Py_EQ, Py_NE, Py_GT, Py_GE from libc.stdlib cimport abort -from sage.libs.arb.arb cimport * -from sage.libs.arb.arb_hypgeom cimport * -from sage.libs.arb.arf cimport * -from sage.libs.arb.arf cimport * -from sage.libs.arb.mag cimport * +from sage.libs.flint.arb cimport * +from sage.libs.flint.arb_hypgeom cimport * +from sage.libs.flint.arf cimport * +from sage.libs.flint.arf cimport * +from sage.libs.flint.mag cimport * from sage.libs.flint.flint cimport flint_free from sage.libs.flint.fmpz cimport * from sage.libs.flint.fmpq cimport * @@ -1147,7 +1147,7 @@ class RealBallField(UniqueRepresentation, sage.rings.abc.RealBallField): cdef inline bint _do_sig(long prec) noexcept: """ - Whether signal handlers should be installed for calls to arb. + Whether signal handlers should be installed for calls to FLINT. TESTS:: @@ -1185,8 +1185,7 @@ def create_RealBall(parent, serialized): cdef class RealBall(RingElement): """ - Hold one ``arb_t`` of the `Arb library - `_ + Hold one ``arb_t`` EXAMPLES:: diff --git a/src/sage/schemes/elliptic_curves/cm.py b/src/sage/schemes/elliptic_curves/cm.py index 1f0fd8bd34e..10ca75edb41 100644 --- a/src/sage/schemes/elliptic_curves/cm.py +++ b/src/sage/schemes/elliptic_curves/cm.py @@ -65,7 +65,8 @@ def hilbert_class_polynomial(D, algorithm=None): ALGORITHM: - - If ``algorithm`` = "arb" (default): Use Arb's implementation which uses complex interval arithmetic. + - If ``algorithm`` = "arb" (default): Use FLINT's implementation inherited + from Arb which uses complex interval arithmetic. - If ``algorithm`` = "sage": Use complex approximations to the roots. From af2e89265f1c77b1c29b0514d9cdcff1d760a7d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Fri, 23 Feb 2024 20:03:26 +0100 Subject: [PATCH 049/518] fix suggested details and some more --- src/sage/monoids/free_monoid_element.py | 15 ++++------ src/sage/monoids/string_ops.py | 38 ++++++++++++++----------- src/sage/monoids/trace_monoid.py | 8 +++--- 3 files changed, 32 insertions(+), 29 deletions(-) diff --git a/src/sage/monoids/free_monoid_element.py b/src/sage/monoids/free_monoid_element.py index d2cf92c4ba8..9b22ca68cca 100644 --- a/src/sage/monoids/free_monoid_element.py +++ b/src/sage/monoids/free_monoid_element.py @@ -128,7 +128,7 @@ def _repr_(self): s = "1" return s - def _latex_(self): + def _latex_(self) -> str: r""" Return latex representation of self. @@ -278,7 +278,7 @@ def __invert__(self): """ raise NotImplementedError - def __len__(self): + def __len__(self) -> int: """ Return the degree of the monoid element ``self``, where each generator of the free monoid is given degree `1`. @@ -296,12 +296,9 @@ def __len__(self): sage: len(a[0]**2 * a[1]) 3 """ - s = 0 - for x in self._element_list: - s += x[1] - return s + return sum(x[1] for x in self._element_list) - def _richcmp_(self, other, op): + def _richcmp_(self, other, op) -> bool: """ Compare two free monoid elements with the same parents. @@ -374,9 +371,9 @@ def to_word(self, alph=None): alph = gens alph = [str(c) for c in alph] W = Words(alph, infinite=False) - return W(sum([[alph[gens.index(i[0])]] * i[1] for i in iter(self)], [])) + return W(sum([[alph[gens.index(i[0])]] * i[1] for i in self], [])) - def to_list(self, indices=False): + def to_list(self, indices=False) -> list: r""" Return ``self`` as a list of generators. diff --git a/src/sage/monoids/string_ops.py b/src/sage/monoids/string_ops.py index bdfabe79cdd..e34164712fb 100644 --- a/src/sage/monoids/string_ops.py +++ b/src/sage/monoids/string_ops.py @@ -12,24 +12,27 @@ from .string_monoid_element import StringMonoidElement -def strip_encoding(S): +def strip_encoding(S) -> str: """ - The upper case string of S stripped of all non-alphabetic characters. + Return the upper case string of S stripped of all non-alphabetic characters. EXAMPLES:: sage: S = "The cat in the hat." sage: strip_encoding(S) 'THECATINTHEHAT' + + TESTS:: + + sage: S = "The cat in the hat." + sage: strip_encoding(44) + Traceback (most recent call last): + ... + TypeError: argument S (= 44) must be a string """ if not isinstance(S, str): - raise TypeError("Argument S (= %s) must be a string.") - X = '' - for i in range(len(S)): - C = S[i] - if C.isalpha(): - X += S[i].upper() - return X + raise TypeError(f"argument S (= {S}) must be a string") + return ''.join(letter.upper() for letter in S if letter.isalpha()) def frequency_distribution(S, n=1, field=None): @@ -59,7 +62,7 @@ def frequency_distribution(S, n=1, field=None): def coincidence_index(S, n=1): """ - The coincidence index of the string S. + Return the coincidence index of the string ``S``. EXAMPLES:: @@ -87,9 +90,14 @@ def coincidence_index(S, n=1): def coincidence_discriminant(S, n=2): """ - Input: A tuple of strings, e.g. produced as decimation of transposition + INPUT: + + A tuple of strings, e.g. produced as decimation of transposition ciphertext, or a sample plaintext. - Output: A measure of the difference of probability of association of + + OUTPUT: + + A measure of the difference of probability of association of character pairs, relative to their independent one-character probabilities. EXAMPLES:: @@ -102,11 +110,9 @@ def coincidence_discriminant(S, n=2): raise TypeError("Argument S (= %s) must be a list or tuple" % S) if n != 2: raise ValueError("Argument n (= %s) is only implemented for n = 2" % n) - truth = all(isinstance(c, (str, StringMonoidElement)) for c in S) - if not truth: + if not all(isinstance(c, (str, StringMonoidElement)) for c in S): raise TypeError("Argument S (= %s) must be a list of strings.") - truth = all(len(c) == n for c in S) - if not truth: + if not all(len(c) == n for c in S): raise ValueError("Argument S (= %s) must be a list of strings of length 2" % S) X1 = [frequency_distribution([s[i] for s in S]) for i in range(2)] XX = frequency_distribution(S) diff --git a/src/sage/monoids/trace_monoid.py b/src/sage/monoids/trace_monoid.py index d13b405421b..bc7edc7ab08 100644 --- a/src/sage/monoids/trace_monoid.py +++ b/src/sage/monoids/trace_monoid.py @@ -88,7 +88,7 @@ class TraceMonoidElement(ElementWrapper, MonoidElement): sage: x.foata_normal_form() (b, a*d, a, b*c) """ - def _repr_(self): + def _repr_(self) -> str: """ Textual representation of ``self``. @@ -819,7 +819,7 @@ def dependence_graph(self): True """ return Graph({frozenset((e1, e2)) if e1 != e2 else (e1, e2) - for e1, e2 in self.dependence()}, loops=True, + for e1, e2 in self.dependence()}, loops=True, format="list_of_edges", immutable=True) @@ -963,9 +963,9 @@ def _sorted_independence(self): [[a, c]] """ return sorted(sorted(x_y) - for x_y in sorted(self.independence())) + for x_y in self.independence()) - def _repr_(self): + def _repr_(self) -> str: r""" Textual representation of trace monoids. From 7c033b600241fd6dd1781c358a8bda7550ff998d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Sat, 24 Feb 2024 13:16:53 +0100 Subject: [PATCH 050/518] formal deprecation of old group class --- .../additive_abelian_group.py | 12 +++--- src/sage/groups/group.pyx | 5 +-- src/sage/groups/old.pyx | 40 ++++--------------- .../modular/arithgroup/arithgroup_generic.py | 8 ++-- 4 files changed, 21 insertions(+), 44 deletions(-) diff --git a/src/sage/groups/additive_abelian/additive_abelian_group.py b/src/sage/groups/additive_abelian/additive_abelian_group.py index c0a90f0c110..f913115f576 100644 --- a/src/sage/groups/additive_abelian/additive_abelian_group.py +++ b/src/sage/groups/additive_abelian/additive_abelian_group.py @@ -6,10 +6,11 @@ major differences are in the way elements are printed. """ -from sage.groups.old import AbelianGroup +from sage.structure.parent import Parent from sage.modules.fg_pid.fgp_module import FGP_Module_class from sage.modules.fg_pid.fgp_element import FGP_Element from sage.rings.integer_ring import ZZ +from sage.categories.commutative_additive_groups import CommutativeAdditiveGroups def AdditiveAbelianGroup(invs, remember_generators=True): @@ -173,12 +174,12 @@ def _hermite_lift(self): for i in range(H.nrows()): if i in pivot_rows: j = pivots[i] - N = H[i,j] + N = H[i, j] a = (y[j] - (y[j] % N)) // N - y = y - a*H.row(i) + y = y - a * H.row(i) return y - def _repr_(self): + def _repr_(self) -> str: r""" String representation. This uses a canonical lifting of elements of this group (represented as a quotient `G/H` of free abelian groups) to @@ -201,7 +202,7 @@ def _repr_(self): # since we want to inherit things like __hash__ from there rather than the # hyper-generic implementation for abstract abelian groups. -class AdditiveAbelianGroup_class(FGP_Module_class, AbelianGroup): +class AdditiveAbelianGroup_class(FGP_Module_class, Parent): r""" An additive abelian group, implemented using the `\ZZ`-module machinery. @@ -225,6 +226,7 @@ def __init__(self, cover, relations): True """ FGP_Module_class.__init__(self, cover, relations) + Parent.__init__(self, category=CommutativeAdditiveGroups()) def _repr_(self): r""" diff --git a/src/sage/groups/group.pyx b/src/sage/groups/group.pyx index ef639a0c367..fdfe79bd9eb 100644 --- a/src/sage/groups/group.pyx +++ b/src/sage/groups/group.pyx @@ -21,7 +21,7 @@ from sage.structure.parent cimport Parent from sage.rings.infinity import infinity -def is_Group(x): +def is_Group(x) -> bool: """ Return whether ``x`` is a group object. @@ -42,8 +42,7 @@ def is_Group(x): sage: is_Group("a string") False """ - from sage.groups.old import Group as OldGroup - return isinstance(x, (Group, OldGroup)) + return isinstance(x, Group) cdef class Group(Parent): diff --git a/src/sage/groups/old.pyx b/src/sage/groups/old.pyx index 761034cff65..c831d1273dd 100644 --- a/src/sage/groups/old.pyx +++ b/src/sage/groups/old.pyx @@ -1,5 +1,5 @@ """ -Base class for groups +Deprecated Base class for groups """ # **************************************************************************** @@ -17,11 +17,11 @@ Base class for groups # https://www.gnu.org/licenses/ # **************************************************************************** -doc=""" -Base class for all groups +doc = """ +Deprecated Base class for all groups """ -from sage.rings.infinity import infinity import sage.rings.integer_ring +from sage.misc.superseded import deprecation cdef class Group(sage.structure.parent.Parent): @@ -35,6 +35,9 @@ cdef class Group(sage.structure.parent.Parent): sage: from sage.groups.old import Group sage: G = Group() + doctest:warning...: + DeprecationWarning: do not use the old Group class + See https://github.com/sagemath/sage/issues/34567 for details. sage: G.category() Category of groups sage: G = Group(category = Groups()) # todo: do the same test with some subcategory of Groups when there will exist one @@ -54,6 +57,7 @@ cdef class Group(sage.structure.parent.Parent): sage: h == hash(G) True """ + deprecation(34567, 'do not use the old Group class') from sage.categories.basic import Groups if category is None: category = Groups() @@ -63,12 +67,6 @@ cdef class Group(sage.structure.parent.Parent): sage.structure.parent.Parent.__init__(self, base=sage.rings.integer_ring.ZZ, category=category) - #def __call__(self, x): # this gets in the way of the coercion mechanism - # """ - # Coerce x into this group. - # """ - # raise NotImplementedError - def __contains__(self, x): r""" True if coercion of `x` into self is defined. @@ -88,13 +86,6 @@ cdef class Group(sage.structure.parent.Parent): return False return True -# def category(self): -# """ -# The category of all groups -# """ -# import sage.categories.all -# return sage.categories.all.Groups() - def is_abelian(self): """ Return True if this group is abelian. @@ -142,21 +133,6 @@ cdef class Group(sage.structure.parent.Parent): """ raise NotImplementedError - def is_finite(self): - """ - Returns True if this group is finite. - - EXAMPLES:: - - sage: from sage.groups.old import Group - sage: G = Group() - sage: G.is_finite() - Traceback (most recent call last): - ... - NotImplementedError - """ - return self.order() != infinity - def is_multiplicative(self): r""" Returns True if the group operation is given by \* (rather than diff --git a/src/sage/modular/arithgroup/arithgroup_generic.py b/src/sage/modular/arithgroup/arithgroup_generic.py index c647f8d0e50..08990c1b7d5 100644 --- a/src/sage/modular/arithgroup/arithgroup_generic.py +++ b/src/sage/modular/arithgroup/arithgroup_generic.py @@ -14,12 +14,12 @@ # ################################################################################ -from sage.groups.old import Group +from sage.groups.group import Group from sage.categories.groups import Groups from sage.rings.integer_ring import ZZ from sage.arith.functions import lcm from sage.misc.cachefunc import cached_method -from copy import copy # for making copies of lists of cusps +from copy import copy # for making copies of lists of cusps from sage.modular.modsym.p1list import lift_to_sl2z from sage.modular.cusps import Cusp @@ -30,7 +30,7 @@ from .arithgroup_element import ArithmeticSubgroupElement, M2Z as Mat2Z -def is_ArithmeticSubgroup(x): +def is_ArithmeticSubgroup(x) -> bool: r""" Return ``True`` if ``x`` is of type :class:`ArithmeticSubgroup`. @@ -67,7 +67,7 @@ def __init__(self): """ Group.__init__(self, category=Groups().Infinite()) - def _repr_(self): + def _repr_(self) -> str: r""" Return the string representation of self. From 39252f445745c6cf01a212c8d3cf31ba7325683c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Sat, 24 Feb 2024 13:18:58 +0100 Subject: [PATCH 051/518] fix issue number --- src/sage/groups/old.pyx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/groups/old.pyx b/src/sage/groups/old.pyx index c831d1273dd..43b63062c84 100644 --- a/src/sage/groups/old.pyx +++ b/src/sage/groups/old.pyx @@ -37,7 +37,7 @@ cdef class Group(sage.structure.parent.Parent): sage: G = Group() doctest:warning...: DeprecationWarning: do not use the old Group class - See https://github.com/sagemath/sage/issues/34567 for details. + See https://github.com/sagemath/sage/issues/37449 for details. sage: G.category() Category of groups sage: G = Group(category = Groups()) # todo: do the same test with some subcategory of Groups when there will exist one @@ -57,7 +57,7 @@ cdef class Group(sage.structure.parent.Parent): sage: h == hash(G) True """ - deprecation(34567, 'do not use the old Group class') + deprecation(37449, 'do not use the old Group class') from sage.categories.basic import Groups if category is None: category = Groups() From 4cff0fef2e859812dc3d375a531f6365a1a8d2cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Sat, 24 Feb 2024 20:52:42 +0100 Subject: [PATCH 052/518] fix suggested details --- src/sage/categories/action.pyx | 4 ++-- .../additive_abelian/additive_abelian_group.py | 12 +++++++----- src/sage/groups/group.pyx | 9 ++++++--- src/sage/groups/old.pyx | 4 ++-- 4 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/sage/categories/action.pyx b/src/sage/categories/action.pyx index 0e54ceffa27..730cfff4786 100644 --- a/src/sage/categories/action.pyx +++ b/src/sage/categories/action.pyx @@ -381,10 +381,10 @@ cdef class InverseAction(Action): def __init__(self, Action action): G = action.G try: - from sage.groups.group import is_Group + from sage.groups.group import Group # We must be in the case that parent(~a) == parent(a) # so we can invert in _call_ code below. - if (is_Group(G) and G.is_multiplicative()) or G.is_field(): + if (isinstance(G, Group) and G.is_multiplicative()) or G.is_field(): Action.__init__(self, G, action.underlying_set(), action._is_left) self._action = action return diff --git a/src/sage/groups/additive_abelian/additive_abelian_group.py b/src/sage/groups/additive_abelian/additive_abelian_group.py index f913115f576..71af80acdf9 100644 --- a/src/sage/groups/additive_abelian/additive_abelian_group.py +++ b/src/sage/groups/additive_abelian/additive_abelian_group.py @@ -6,11 +6,10 @@ major differences are in the way elements are printed. """ -from sage.structure.parent import Parent -from sage.modules.fg_pid.fgp_module import FGP_Module_class +from sage.categories.commutative_additive_groups import CommutativeAdditiveGroups from sage.modules.fg_pid.fgp_element import FGP_Element +from sage.modules.fg_pid.fgp_module import FGP_Module_class from sage.rings.integer_ring import ZZ -from sage.categories.commutative_additive_groups import CommutativeAdditiveGroups def AdditiveAbelianGroup(invs, remember_generators=True): @@ -202,7 +201,7 @@ def _repr_(self) -> str: # since we want to inherit things like __hash__ from there rather than the # hyper-generic implementation for abstract abelian groups. -class AdditiveAbelianGroup_class(FGP_Module_class, Parent): +class AdditiveAbelianGroup_class(FGP_Module_class): r""" An additive abelian group, implemented using the `\ZZ`-module machinery. @@ -224,9 +223,12 @@ def __init__(self, cover, relations): Additive abelian group isomorphic to Z sage: G == loads(dumps(G)) True + sage: G.category() + Category of modules over Integer Ring + sage: G in CommutativeAdditiveGroups() + True """ FGP_Module_class.__init__(self, cover, relations) - Parent.__init__(self, category=CommutativeAdditiveGroups()) def _repr_(self): r""" diff --git a/src/sage/groups/group.pyx b/src/sage/groups/group.pyx index fdfe79bd9eb..8fa6d3dec3c 100644 --- a/src/sage/groups/group.pyx +++ b/src/sage/groups/group.pyx @@ -16,12 +16,12 @@ Base class for groups # # https://www.gnu.org/licenses/ # **************************************************************************** - -from sage.structure.parent cimport Parent +from sage.misc.superseded import deprecation from sage.rings.infinity import infinity +from sage.structure.parent cimport Parent -def is_Group(x) -> bool: +def is_Group(x): """ Return whether ``x`` is a group object. @@ -38,10 +38,13 @@ def is_Group(x) -> bool: sage: F. = FreeGroup() # needs sage.groups sage: from sage.groups.group import is_Group sage: is_Group(F) # needs sage.groups + doctest:warning...DeprecationWarning: use instead G in Groups() + See https://github.com/sagemath/sage/issues/37449 for details. True sage: is_Group("a string") False """ + deprecation(37449, 'use instead G in Groups()') return isinstance(x, Group) diff --git a/src/sage/groups/old.pyx b/src/sage/groups/old.pyx index 43b63062c84..2d76c9bc66d 100644 --- a/src/sage/groups/old.pyx +++ b/src/sage/groups/old.pyx @@ -1,5 +1,5 @@ """ -Deprecated Base class for groups +Deprecated base class for groups """ # **************************************************************************** @@ -18,7 +18,7 @@ Deprecated Base class for groups # **************************************************************************** doc = """ -Deprecated Base class for all groups +Deprecated base class for all groups """ import sage.rings.integer_ring from sage.misc.superseded import deprecation From 1a147c1d61765f361507ff6178aa0d6d4b5de222 Mon Sep 17 00:00:00 2001 From: Kwankyu Lee Date: Sun, 25 Feb 2024 19:16:16 +0900 Subject: [PATCH 053/518] Restore _NumberFields and remove unnecessary checks --- src/sage/schemes/affine/affine_point.py | 6 +++--- src/sage/schemes/projective/projective_point.py | 11 ++++------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/sage/schemes/affine/affine_point.py b/src/sage/schemes/affine/affine_point.py index 32bfb3a42cf..00779dbb2d6 100644 --- a/src/sage/schemes/affine/affine_point.py +++ b/src/sage/schemes/affine/affine_point.py @@ -26,6 +26,8 @@ from sage.schemes.generic.morphism import SchemeMorphism_point, SchemeMorphism, is_SchemeMorphism from sage.structure.sequence import Sequence +_NumberFields = NumberFields() + # -------------------------------------------------------------------- # Rational points on schemes, which we view as morphisms determined by @@ -201,7 +203,7 @@ def global_height(self, prec=None): R = RealField(prec) H = max([self[i].abs() for i in range(self.codomain().ambient_space().dimension_relative())]) return R(max(H,1)).log() - if self.domain().base_ring() in NumberFields() or isinstance(self.domain().base_ring(), sage.rings.abc.Order): + if self.domain().base_ring() in _NumberFields or isinstance(self.domain().base_ring(), sage.rings.abc.Order): return max([self[i].global_height(prec) for i in range(self.codomain().ambient_space().dimension_relative())]) else: raise NotImplementedError("must be over a number field or a number field Order") @@ -421,8 +423,6 @@ def as_subscheme(self): g = A.gens() v = self._coords n = len(v) - if n != len(g): - raise ValueError('not a point of the ambient space') return A.subscheme([g[i] - v[i] for i in range(n)]) diff --git a/src/sage/schemes/projective/projective_point.py b/src/sage/schemes/projective/projective_point.py index d90b2a440fd..7208b70294b 100644 --- a/src/sage/schemes/projective/projective_point.py +++ b/src/sage/schemes/projective/projective_point.py @@ -28,6 +28,7 @@ from sage.categories.integral_domains import IntegralDomains from sage.categories.number_fields import NumberFields +_NumberFields = NumberFields() from sage.rings.fraction_field import FractionField from sage.rings.number_field.order import is_NumberFieldOrder, Order as NumberFieldOrder from sage.rings.qqbar import number_field_elements_from_algebraics @@ -747,7 +748,7 @@ def global_height(self, prec=None): if prec is None: prec = 53 K = self.codomain().base_ring() - if K in NumberFields() or is_NumberFieldOrder(K): + if K in _NumberFields or is_NumberFieldOrder(K): P = self else: try: @@ -802,7 +803,7 @@ def local_height(self, v, prec=None): 0.693147180559945 """ K = FractionField(self.domain().base_ring()) - if K not in NumberFields(): + if K not in _NumberFields: raise TypeError("must be over a number field or a number field order") return max([K(c).local_height(v, prec=prec) for c in self]) @@ -837,7 +838,7 @@ def local_height_arch(self, i, prec=None): 3.401197381662155375413236691607 """ K = FractionField(self.domain().base_ring()) - if K not in NumberFields(): + if K not in _NumberFields: raise TypeError("must be over a number field or a number field order") if K == QQ: return max(K(c).local_height_arch(prec=prec) for c in self) @@ -1418,13 +1419,9 @@ def as_subscheme(self): g = P.gens() v = self._coords n = len(v) - if n != len(g): - raise ValueError('not a point of the ambient space') for i in range(n - 1, -1, -1): if v[i]: break - else: - raise ValueError('invalid homogeneous coordinates') a = v[i] x = g[i] return P.subscheme([a*g[j] - v[j]*x for j in range(n) if j != i]) From fadbcae1ff1c9ef3f5810e3401aa1dd020cc9f97 Mon Sep 17 00:00:00 2001 From: Giorgos Mousa Date: Sun, 25 Feb 2024 14:43:03 +0200 Subject: [PATCH 054/518] Minor improvements in testing --- src/sage/matroids/flats_matroid.pyx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/sage/matroids/flats_matroid.pyx b/src/sage/matroids/flats_matroid.pyx index 3c69920717b..14816ddd7b5 100644 --- a/src/sage/matroids/flats_matroid.pyx +++ b/src/sage/matroids/flats_matroid.pyx @@ -150,9 +150,9 @@ cdef class FlatsMatroid(Matroid): EXAMPLES:: sage: from sage.matroids.flats_matroid import FlatsMatroid - sage: M = FlatsMatroid(matroids.Theta(7)) + sage: M = FlatsMatroid(matroids.Theta(6)) sage: M.full_rank() - 7 + 6 """ return self._matroid_rank @@ -410,6 +410,12 @@ cdef class FlatsMatroid(Matroid): sage: M = FlatsMatroid(matroids.catalog.XY13()) sage: M.whitney_numbers2() [1, 13, 78, 250, 394, 191, 1] + + TESTS:: + + sage: from sage.matroids.flats_matroid import FlatsMatroid + sage: for M in matroids.AllMatroids(4): # optional - matroid_database + ....: assert M.whitney_numbers2() == FlatsMatroid(M).whitney_numbers2() """ cdef list W = [] cdef int i From 057e73d1de82439b750ce1b180db1e4c8c3dbeb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Sun, 25 Feb 2024 15:58:18 +0100 Subject: [PATCH 055/518] formal deprecation of old CommutativeAlgebra class --- src/sage/rings/commutative_algebra.py | 11 ++--- src/sage/rings/ring.pyx | 67 +++++++-------------------- 2 files changed, 21 insertions(+), 57 deletions(-) diff --git a/src/sage/rings/commutative_algebra.py b/src/sage/rings/commutative_algebra.py index 7f6b2ba5dfa..1e209e453c4 100644 --- a/src/sage/rings/commutative_algebra.py +++ b/src/sage/rings/commutative_algebra.py @@ -14,7 +14,7 @@ # # The full text of the GPL is available at: # -# http://www.gnu.org/licenses/ +# https://www.gnu.org/licenses/ #***************************************************************************** from sage.categories.commutative_algebras import CommutativeAlgebras @@ -22,18 +22,17 @@ def is_CommutativeAlgebra(x): """ - Check to see if ``x`` is a :class:`CommutativeAlgebra`. + Check to see if ``x`` is in the category of ``CommutativeAlgebras``. EXAMPLES:: sage: from sage.rings.commutative_algebra import is_CommutativeAlgebra - sage: from sage.rings.ring import CommutativeAlgebra - sage: is_CommutativeAlgebra(CommutativeAlgebra(ZZ)) + sage: is_CommutativeAlgebra(QQ['x']) doctest:warning... DeprecationWarning: the function is_CommutativeAlgebra is deprecated; use '... in Algebras(base_ring).Commutative()' instead - See https://github.com/sagemath/sage/issues/35253 for details. + See https://github.com/sagemath/sage/issues/35999 for details. True """ from sage.misc.superseded import deprecation - deprecation(35253, "the function is_CommutativeAlgebra is deprecated; use '... in Algebras(base_ring).Commutative()' instead") + deprecation(35999, "the function is_CommutativeAlgebra is deprecated; use '... in Algebras(base_ring).Commutative()' instead") return x in CommutativeAlgebras(x.base_ring()) diff --git a/src/sage/rings/ring.pyx b/src/sage/rings/ring.pyx index 4585881eaad..9ee95ec8fd3 100644 --- a/src/sage/rings/ring.pyx +++ b/src/sage/rings/ring.pyx @@ -8,7 +8,7 @@ specific base classes. .. WARNING:: Those classes, except maybe for the lowest ones like - :class:`CommutativeRing` and :class:`CommutativeAlgebra`, + :class:`CommutativeRing` and :class:`Field`, are being progressively deprecated in favor of the corresponding categories. which are more flexible, in particular with respect to multiple inheritance. @@ -21,7 +21,7 @@ The class inheritance hierarchy is: - :class:`CommutativeRing` - :class:`NoetherianRing` (deprecated) - - :class:`CommutativeAlgebra` (to be deprecated) + - :class:`CommutativeAlgebra` (deprecated and essentially removed) - :class:`IntegralDomain` (deprecated) - :class:`DedekindDomain` (deprecated and essentially removed) @@ -58,7 +58,7 @@ TESTS: This is to test a deprecation:: - sage: from sage.rings.ring import DedekindDomain + sage: from sage.rings.ring import DedekindDomain, CommutativeAlgebra sage: class No(DedekindDomain): ....: pass sage: F = No(QQ) @@ -67,6 +67,15 @@ This is to test a deprecation:: See https://github.com/sagemath/sage/issues/37234 for details. sage: F.category() Category of Dedekind domains + + sage: class Nein(CommutativeAlgebra): + ....: pass + sage: F = Nein(QQ, QQ) + ...: + DeprecationWarning: use the category CommutativeAlgebras + See https://github.com/sagemath/sage/issues/37999 for details. + sage: F.category() + Category of commutative rings """ # **************************************************************************** @@ -2150,55 +2159,11 @@ cdef class Algebra(Ring): cdef class CommutativeAlgebra(CommutativeRing): - """ - Generic commutative algebra - """ - def __init__(self, base_ring, names=None, normalize=True, category=None): - r""" - Standard init function. This just checks that the base is a commutative - ring and then passes the buck. - - EXAMPLES:: - - sage: sage.rings.ring.CommutativeAlgebra(QQ) - - - sage: sage.rings.ring.CommutativeAlgebra(QuaternionAlgebra(QQ, -1, -1)) # needs sage.combinat sage.modules - Traceback (most recent call last): - ... - TypeError: base ring must be a commutative ring - """ - # TODO: use the idiom base_ring in CommutativeRings() - try: - if not base_ring.is_commutative(): - raise TypeError("base ring must be a commutative ring") - except (AttributeError, NotImplementedError): - raise TypeError("base ring must be a commutative ring") - # This is a low-level class. For performance, we trust that - # the category is fine, if it is provided. If it isn't, we use - # the category of commutative algebras. - if category is None: - category = CommutativeAlgebras(base_ring) - CommutativeRing.__init__(self, base_ring, names=names, normalize=normalize, category=category) - - def is_commutative(self): - """ - Return ``True`` since this algebra is commutative. - - EXAMPLES: - - Any commutative ring is a commutative algebra over itself:: - - sage: A = sage.rings.ring.CommutativeAlgebra - sage: A(ZZ).is_commutative() - True - sage: A(QQ).is_commutative() - True + __default_category = CommutativeRings() - Trying to create a commutative algebra over a non-commutative ring - will result in a ``TypeError``. - """ - return True + def __init__(self, base_ring, *args, **kwds): + deprecation(37999, "use the category CommutativeAlgebras") + super().__init__(*args, **kwds) def is_Ring(x): From 26408a828d87f67881426ead9f5748da77c597dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Sun, 25 Feb 2024 16:47:00 +0100 Subject: [PATCH 056/518] remove almost all calls to is_Ring --- src/sage/matrix/matrix2.pyx | 12 ++-- src/sage/matrix/matrix_rational_dense.pyx | 4 +- src/sage/matrix/matrix_sparse.pyx | 4 +- src/sage/matrix/special.py | 56 +++++++++--------- src/sage/modular/abvar/abvar.py | 8 +-- src/sage/modular/dirichlet.py | 4 +- src/sage/modules/free_module_element.pyx | 20 +++---- .../quadratic_forms/random_quadraticform.py | 4 +- src/sage/quadratic_forms/ternary_qf.py | 58 +++++++++---------- src/sage/rings/fraction_field.py | 10 ++-- src/sage/rings/polynomial/polynomial_ring.py | 7 ++- .../polynomial/polynomial_ring_constructor.py | 6 +- .../schemes/elliptic_curves/constructor.py | 6 +- 13 files changed, 99 insertions(+), 100 deletions(-) diff --git a/src/sage/matrix/matrix2.pyx b/src/sage/matrix/matrix2.pyx index 3f0ddb6d84a..db3197ae532 100644 --- a/src/sage/matrix/matrix2.pyx +++ b/src/sage/matrix/matrix2.pyx @@ -75,10 +75,15 @@ AUTHORS: # the License, or (at your option) any later version. # https://www.gnu.org/licenses/ # **************************************************************************** +from copy import copy from cpython cimport * from cysignals.signals cimport sig_check +from sage.categories.fields import Fields +from sage.categories.integral_domains import IntegralDomains +from sage.categories.principal_ideal_domains import PrincipalIdealDomains +from sage.categories.rings import Rings from sage.misc.lazy_string import lazy_string from sage.misc.randstate cimport current_randstate from sage.structure.coerce cimport py_scalar_parent @@ -87,16 +92,11 @@ from sage.structure.coerce cimport coercion_model from sage.structure.element import is_Vector from sage.structure.element cimport have_same_parent from sage.misc.verbose import verbose -from sage.categories.fields import Fields -from sage.categories.integral_domains import IntegralDomains -from sage.categories.principal_ideal_domains import PrincipalIdealDomains -from sage.rings.ring import is_Ring from sage.rings.number_field.number_field_base import NumberField from sage.rings.integer_ring import ZZ, is_IntegerRing from sage.rings.rational_field import QQ, is_RationalField import sage.rings.abc from sage.arith.numerical_approx cimport digits_to_bits -from copy import copy import sage.modules.free_module from sage.matrix import berlekamp_massey @@ -2787,7 +2787,7 @@ cdef class Matrix(Matrix1): if isinstance(R, type): R = py_scalar_parent(R) - if not is_Ring(R): + if R not in Rings(): raise TypeError("unable to find a common ring for all elements") if sparse is None or sparse is self.is_sparse(): diff --git a/src/sage/matrix/matrix_rational_dense.pyx b/src/sage/matrix/matrix_rational_dense.pyx index 6a039b077e3..275dc3c5253 100644 --- a/src/sage/matrix/matrix_rational_dense.pyx +++ b/src/sage/matrix/matrix_rational_dense.pyx @@ -72,6 +72,7 @@ Test hashing:: from libc.string cimport strcpy, strlen +from sage.categories.rings import Rings from sage.cpython.string cimport char_to_str, str_to_bytes from sage.modules.vector_rational_dense cimport Vector_rational_dense @@ -109,7 +110,6 @@ from sage.matrix.args cimport SparseEntry, MatrixArgs_init from sage.matrix.matrix_integer_dense cimport Matrix_integer_dense, _lift_crt from sage.structure.element cimport Element, Vector from sage.rings.integer cimport Integer -from sage.rings.ring import is_Ring from sage.rings.integer_ring import ZZ, is_IntegerRing import sage.rings.abc from sage.rings.rational_field import QQ @@ -1459,7 +1459,7 @@ cdef class Matrix_rational_dense(Matrix_dense): [-+---] [0|1 2] """ - if not is_Ring(R): + if R not in Rings(): raise TypeError("R must be a ring") if R == self._base_ring: if self._is_immutable: diff --git a/src/sage/matrix/matrix_sparse.pyx b/src/sage/matrix/matrix_sparse.pyx index ef707ebe3f8..c537798ba92 100644 --- a/src/sage/matrix/matrix_sparse.pyx +++ b/src/sage/matrix/matrix_sparse.pyx @@ -15,9 +15,9 @@ from cysignals.signals cimport sig_check cimport sage.matrix.matrix as matrix cimport sage.matrix.matrix0 as matrix0 +from sage.categories.rings import Rings from sage.structure.element cimport Element, Vector from sage.structure.richcmp cimport richcmp_item, rich_to_bool -from sage.rings.ring import is_Ring from cpython cimport * from cpython.object cimport Py_EQ, Py_NE @@ -62,7 +62,7 @@ cdef class Matrix_sparse(matrix.Matrix): [2.00000000000000*x -2.00000000000000] [-------------------------------------] """ - if not is_Ring(ring): + if R not in Rings(): raise TypeError("input must be a ring") if ring is self._base_ring: if self._is_immutable: diff --git a/src/sage/matrix/special.py b/src/sage/matrix/special.py index 7233892ce2f..205c5f11497 100644 --- a/src/sage/matrix/special.py +++ b/src/sage/matrix/special.py @@ -64,8 +64,8 @@ # **************************************************************************** from copy import copy -from sage.rings.ring import is_Ring import sage.matrix.matrix_space as matrix_space +from sage.categories.rings import Rings from sage.modules.free_module_element import vector from sage.structure.element import is_Matrix, parent from sage.structure.sequence import Sequence @@ -845,7 +845,7 @@ def diagonal_matrix(arg0=None, arg1=None, arg2=None, sparse=True): # Leads with a ring? # Formats 3, 4, else remains None ring = None - if is_Ring(arg0): + if arg0 in Rings(): ring = arg0 arg0 = arg1 arg1 = arg2 @@ -1414,10 +1414,10 @@ def elementary_matrix(arg0, arg1=None, **kwds): """ import sage.structure.element # determine ring and matrix size - if arg1 is not None and not is_Ring(arg0): + if arg1 is not None and arg0 not in Rings(): raise TypeError('optional first parameter must be a ring, not {0}'.format(arg0)) scale = kwds.pop('scale', None) - if is_Ring(arg0): + if arg0 in Rings(): R = arg0 arg0 = arg1 elif scale is not None: @@ -1602,7 +1602,7 @@ def _determine_block_matrix_grid(sub_matrices): if is_Matrix(M): sub_width = M.ncols() sub_height = M.nrows() - elif M: # non-zero scalar is interpreted as a square matrix + elif M: # non-zero scalar is interpreted as a square matrix if row_heights[i] is None: sub_width = col_widths[j] else: @@ -1939,7 +1939,7 @@ def block_matrix(*args, **kwds): else: return matrix_space.MatrixSpace(ZZ, 0, 0)([]) - if len(args) >= 1 and is_Ring(args[0]): + if len(args) >= 1 and args[0] in Rings(): # A ring is specified if kwds.get('ring', args[0]) != args[0]: raise ValueError("base ring specified twice and they are different") @@ -2092,7 +2092,7 @@ def block_matrix(*args, **kwds): M = matrix(ring, row_heights[i], zero_widths[i], 0, sparse=sparse) zero_widths[i] = 0 else: - continue # zero-width matrix + continue # zero-width matrix else: if zero_widths is not None: M = matrix(ring, row_heights[i], row_heights[i], M, sparse=sparse) @@ -2722,7 +2722,7 @@ def random_echelonizable_matrix(parent, rank, upper_bound=None, max_tries=100): if ring is QQ or ring is ZZ: # If upper_bound is not set, don't control entry size. if upper_bound is None: - # If size control is not desired, the routine will run slightly faster, particularly with large matrices. + # If size control is not desired, the routine will run slightly faster, particularly with large matrices. for pivots in range(rank-1, -1, -1): row_index = 0 while row_index < rows: @@ -2741,30 +2741,30 @@ def random_echelonizable_matrix(parent, rank, upper_bound=None, max_tries=100): while max(abs(c) for c in matrix.list()) >= upper_bound: matrix = random_rref_matrix(parent, rank) tries += 1 - if tries > max_tries: # to prevent endless attempts + if tries > max_tries: # to prevent endless attempts raise ValueError("tried "+str(max_tries)+" times to get a rank 1 random matrix. Try bigger upper_bound?") matrix_copy = matrix for pivots in range(len(matrix.pivots()) - 1, -1, -1): - # keep track of the pivot column positions from the pivot column with the largest index to - # the one with the smallest. + # keep track of the pivot column positions from the pivot column with the largest index to + # the one with the smallest. row_index = 0 tries = 0 while row_index < rows: # To each row in a pivot column add a scalar multiple of the pivot row. # for full rank, square matrices, using only this row operation preserves the determinant of 1. if pivots != row_index: - # To ensure a leading one is not removed by the addition of the pivot row by its - # additive inverse. + # To ensure a leading one is not removed by the addition of the pivot row by its + # additive inverse. matrix_copy = matrix.with_added_multiple_of_row(row_index,matrix.pivot_rows()[pivots],randint(-5,5)) tries += 1 # Range for scalar multiples determined experimentally. - if max(map(abs,matrix_copy.list())) < upper_bound: - # Continue if the largest entry after a row operation is within the bound. + if max(map(abs, matrix_copy.list())) < upper_bound: + # Continue if the largest entry after a row operation is within the bound. matrix = matrix_copy row_index += 1 tries = 0 - if tries > max_tries: # to prevent endless unsuccessful row adding + if tries > max_tries: # to prevent endless unsuccessful row adding raise ValueError("tried "+str(max_tries)+" times to get row number "+str(row_index)+". Try bigger upper_bound?") # The leading one in row one has not been altered, so add a scalar multiple of a random row # to row one. @@ -3192,19 +3192,19 @@ def random_diagonalizable_matrix(parent,eigenvalues=None,dimensions=None): raise ValueError("the list of dimensions must have a list of corresponding eigenvalues.") if eigenvalues is None and dimensions is None: values = [] - #create a list with "size" number of entries + # create a list with "size" number of entries for eigen_index in range(size): eigenvalue = randint(-10, 10) values.append(eigenvalue) values.sort() dimensions = [] eigenvalues = [] - #create a list with no duplicate values to be the eigenvalues + # create a list with no duplicate values to be the eigenvalues for eigenvalue in range(size): if values[eigenvalue] not in eigenvalues: eigenvalues.append(values[eigenvalue]) for dimension in range(len(eigenvalues)): - #dimension is equal to how many times an eigenvalue was generated in the 'values' list + # dimension is equal to how many times an eigenvalue was generated in the 'values' list dimensions.append(values.count(eigenvalues[dimension])) size_check = 0 for check in range(len(dimensions)): @@ -3217,11 +3217,11 @@ def random_diagonalizable_matrix(parent,eigenvalues=None,dimensions=None): raise ValueError("eigenspaces must have a dimension of at least 1.") if len(eigenvalues) != len(dimensions): raise ValueError("each eigenvalue must have a corresponding dimension and each dimension a corresponding eigenvalue.") - #sort the dimensions in order of increasing size, and sort the eigenvalues list in an identical fashion, to maintain corresponding values. + # sort the dimensions in order of increasing size, and sort the eigenvalues list in an identical fashion, to maintain corresponding values. dimensions_sort = sorted(zip(dimensions, eigenvalues)) dimensions = [x[0] for x in dimensions_sort] eigenvalues = [x[1] for x in dimensions_sort] - #Create the matrix of eigenvalues on the diagonal. Use a lower limit and upper limit determined by the eigenvalue dimensions. + # Create the matrix of eigenvalues on the diagonal. Use a lower limit and upper limit determined by the eigenvalue dimensions. diagonal_matrix = matrix(QQ, size) up_bound = 0 low_bound = 0 @@ -3235,33 +3235,33 @@ def random_diagonalizable_matrix(parent,eigenvalues=None,dimensions=None): eigenvector_matrix = matrix(QQ, size, size, 1) upper_limit = 0 lower_limit = 0 - #run the routine over the necessary number of columns corresponding eigenvalue dimension. + # run the routine over the necessary number of columns corresponding eigenvalue dimension. for dimension_index in range(len(dimensions)-1): upper_limit = upper_limit+dimensions[dimension_index] lowest_index_row_with_one = size-dimensions[dimension_index] - #assign a one to the row that is the eigenvalue dimension rows up from the bottom row then assign ones diagonally down to the right. + # assign a one to the row that is the eigenvalue dimension rows up from the bottom row then assign ones diagonally down to the right. for eigen_ones in range(lower_limit,upper_limit): eigenvector_matrix[lowest_index_row_with_one,eigen_ones] = 1 lowest_index_row_with_one += 1 lower_limit = lower_limit+dimensions[dimension_index] - #Create a list to give the eigenvalue dimension corresponding to each column. + # Create a list to give the eigenvalue dimension corresponding to each column. dimension_check = [] for i in range(len(dimensions)): for k in range(dimensions[i]): dimension_check.append(dimensions[i]) - #run routine over the rows that are in the range of the protected ones. Use addition of column multiples to fill entries. + # run routine over the rows that are in the range of the protected ones. Use addition of column multiples to fill entries. for dimension_multiplicity in range(max(dimensions),min(dimensions),-1): highest_one_row = size-dimension_multiplicity highest_one_column = 0 - #find the column with the protected one in the lowest indexed row. + # find the column with the protected one in the lowest indexed row. while eigenvector_matrix[highest_one_row,highest_one_column] == 0: highest_one_column += 1 - #dimension_check determines if column has a low enough eigenvalue dimension to take a column multiple. + # dimension_check determines if column has a low enough eigenvalue dimension to take a column multiple. for bottom_entry_filler in range(len(dimension_check)): if dimension_check[bottom_entry_filler] < dimension_multiplicity and eigenvector_matrix[highest_one_row,bottom_entry_filler] == 0: # randint range determined experimentally to keep entries manageable. eigenvector_matrix.add_multiple_of_column(bottom_entry_filler,highest_one_column,randint(-4,4)) - #Fill remaining rows using scalar row addition. + # Fill remaining rows using scalar row addition. for row in range(size-max(dimensions),size): for upper_row in range(size-max(dimensions)): # range of multiplier determined experimentally so that entries stay manageable for small matrices diff --git a/src/sage/modular/abvar/abvar.py b/src/sage/modular/abvar/abvar.py index f47e1fc7448..d431c24267e 100644 --- a/src/sage/modular/abvar/abvar.py +++ b/src/sage/modular/abvar/abvar.py @@ -37,6 +37,7 @@ from sage.arith.functions import lcm as LCM from sage.arith.misc import divisors, next_prime, is_prime +from sage.categories.fields import Fields from sage.categories.modular_abelian_varieties import ModularAbelianVarieties from sage.matrix.constructor import matrix from sage.matrix.special import block_diagonal_matrix, identity_matrix @@ -58,7 +59,6 @@ from sage.rings.integer_ring import ZZ from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.rings.rational_field import QQ -from sage.rings.ring import is_Ring from sage.schemes.elliptic_curves.constructor import EllipticCurve from sage.sets.primes import Primes from sage.structure.parent import Parent @@ -172,7 +172,7 @@ def __init__(self, groups, base_field, is_simple=None, newform_level=None, self.__degen_t = number if isogeny_number is not None: self.__isogeny_number = isogeny_number - if check and not is_Ring(base_field) and base_field.is_field(): + if check and base_field not in Fields(): raise TypeError("base_field must be a field") Parent.__init__(self, base=base_field, category=ModularAbelianVarieties(base_field)) @@ -2060,8 +2060,8 @@ def newform_level(self, none_if_not_known=False): if none_if_not_known: return None level = LCM([f.level() for f in self.newform_decomposition('a')]) - groups = sorted({f.group() for f in - self.newform_decomposition('a')}) + groups = sorted({f.group() + for f in self.newform_decomposition('a')}) if len(groups) == 1: groups = groups[0] self.__newform_level = level, groups diff --git a/src/sage/modular/dirichlet.py b/src/sage/modular/dirichlet.py index b8d988b3112..16e83811ffd 100644 --- a/src/sage/modular/dirichlet.py +++ b/src/sage/modular/dirichlet.py @@ -65,6 +65,7 @@ from sage.arith.misc import bernoulli, binomial, factorial, kronecker, factor, gcd, fundamental_discriminant, euler_phi, valuation from sage.categories.map import Map from sage.categories.objects import Objects +from sage.categories.rings import Rings from sage.misc.cachefunc import cached_method from sage.misc.fast_methods import WithEqualityById from sage.misc.functional import round @@ -77,7 +78,6 @@ from sage.rings.integer_ring import ZZ from sage.rings.power_series_ring import PowerSeriesRing from sage.rings.rational_field import QQ, is_RationalField -from sage.rings.ring import is_Ring from sage.structure.element import MultiplicativeGroupElement from sage.structure.factory import UniqueFactory from sage.structure.gens_py import multiplicative_iterator @@ -2481,7 +2481,7 @@ def create_key(self, N, base_ring=None, zeta=None, zeta_order=None, if integral: base_ring = base_ring.ring_of_integers() - if not is_Ring(base_ring): + if base_ring not in Rings(): raise TypeError("base_ring (= %s) must be a ring" % base_ring) # If either zeta or zeta_order is given, compute the other. diff --git a/src/sage/modules/free_module_element.pyx b/src/sage/modules/free_module_element.pyx index 3492dbf2eb1..82d93179154 100644 --- a/src/sage/modules/free_module_element.pyx +++ b/src/sage/modules/free_module_element.pyx @@ -115,13 +115,13 @@ This is a test from :trac:`20211`:: cimport cython from cpython.slice cimport PySlice_GetIndicesEx +from sage.categories.rings import Rings from sage.structure.sequence import Sequence from sage.structure.element cimport Element, RingElement, Vector from sage.structure.element import canonical_coercion from sage.structure.richcmp cimport richcmp_not_equal, richcmp, rich_to_bool import sage.rings.abc -from sage.rings.ring import is_Ring from sage.rings.infinity import Infinity, AnInfinity from sage.rings.integer_ring import ZZ from sage.rings.abc import RealDoubleField, ComplexDoubleField @@ -483,9 +483,8 @@ def vector(arg0, arg1=None, arg2=None, sparse=None, immutable=False): # over a ring. See trac 11657. # !! PLEASE DO NOT MOVE THIS CODE LOWER IN THIS FUNCTION !! arg1_integer = isinstance(arg1, (int, Integer)) - if arg2 is None and is_Ring(arg0) and arg1_integer: - M = FreeModule(arg0, arg1, bool(sparse)) - v = M.zero_vector() + if arg2 is None and arg1_integer and arg0 in Rings(): + v = FreeModule(arg0, arg1, bool(sparse)).zero_vector() if immutable: v.set_immutable() return v @@ -527,7 +526,7 @@ def vector(arg0, arg1=None, arg2=None, sparse=None, immutable=False): # else we size-check arg2 and slide it into arg1 degree = arg1 if arg2 is None: - if not is_Ring(arg0): + if arg0 not in Rings(): msg = "first argument must be base ring of zero vector, not {0}" raise TypeError(msg.format(arg0)) else: @@ -536,10 +535,10 @@ def vector(arg0, arg1=None, arg2=None, sparse=None, immutable=False): arg1 = arg2 # Analyze arg0 and arg1 to create a ring (R) and entries (v) - if is_Ring(arg0): + if arg0 in Rings(): R = arg0 v = arg1 - elif is_Ring(arg1): + elif arg1 in Rings(): R = arg1 v = arg0 else: @@ -686,10 +685,11 @@ def prepare(v, R, degree=None): pass v = Sequence(v, universe=R, use_sage_types=True) ring = v.universe() - if not is_Ring(ring): + if ring not in Rings(): raise TypeError("unable to find a common ring for all elements") return v, ring + def zero_vector(arg0, arg1=None): r""" Returns a vector or free module element with a specified number of zeros. @@ -763,7 +763,7 @@ def zero_vector(arg0, arg1=None): arg0 = ZZ(arg0) # default to a zero vector over the integers (ZZ) if no ring given return (ZZ**arg0).zero_vector() - if is_Ring(arg0): + if arg0 in Rings(): return (arg0**arg1).zero_vector() raise TypeError("first argument must be a ring") @@ -922,7 +922,7 @@ def random_vector(ring, degree=None, *args, **kwds): raise TypeError("degree of a random vector must be an integer, not %s" % degree) if degree < 0: raise ValueError("degree of a random vector must be non-negative, not %s" % degree) - if not is_Ring(ring): + if ring not in Rings(): raise TypeError("elements of a vector, or module element, must come from a ring, not %s" % ring) if not hasattr(ring, "random_element"): raise AttributeError("cannot create a random vector since there is no random_element() method for %s" % ring ) diff --git a/src/sage/quadratic_forms/random_quadraticform.py b/src/sage/quadratic_forms/random_quadraticform.py index aa6a069a8b5..e372c625f57 100644 --- a/src/sage/quadratic_forms/random_quadraticform.py +++ b/src/sage/quadratic_forms/random_quadraticform.py @@ -3,9 +3,9 @@ This file contains a set of routines to create a random quadratic form. """ +from sage.categories.rings import Rings from sage.quadratic_forms.quadratic_form import QuadraticForm from sage.quadratic_forms.ternary_qf import TernaryQF -from sage.rings.ring import is_Ring from sage.rings.integer_ring import ZZ @@ -60,7 +60,7 @@ def random_quadraticform(R, n, rand_arg_list=[]): if len(rand_arg_list) > 3: raise TypeError("the list of randomness arguments can have " "at most 3 elements") - if not is_Ring(R): + if R not in Rings(): raise TypeError("the first argument must be a ring") # Create a list of upper-triangular entries for the quadratic form n2 = (n * (n + 1)) // 2 diff --git a/src/sage/quadratic_forms/ternary_qf.py b/src/sage/quadratic_forms/ternary_qf.py index 2808c04548f..d5384632694 100644 --- a/src/sage/quadratic_forms/ternary_qf.py +++ b/src/sage/quadratic_forms/ternary_qf.py @@ -27,6 +27,7 @@ # **************************************************************************** from sage.arith.misc import gcd, kronecker as kronecker_symbol +from sage.categories.rings import Rings from sage.matrix.constructor import matrix, identity_matrix from sage.misc.prandom import randint from sage.quadratic_forms.quadratic_form import QuadraticForm @@ -42,7 +43,6 @@ from sage.rings.finite_rings.integer_mod import mod from sage.rings.integer_ring import ZZ from sage.rings.polynomial.polynomial_ring import polygens -from sage.rings.ring import is_Ring from sage.structure.element import is_Vector, is_Matrix from sage.structure.sage_object import SageObject @@ -166,7 +166,7 @@ def polynomial(self, names='x,y,z'): sage: p.parent() Multivariate Polynomial Ring in x, y, z over Integer Ring """ - (x,y,z) = polygens(ZZ,names) + x, y, z = polygens(ZZ, names) return self._a * x**2 + self._b * y**2 + self._c * z**2 + self._t * x*y + self._s * x*z + self._r * y*z def _repr_(self): @@ -233,12 +233,13 @@ def __call__(self, v): # Check if v has 3 cols if v.ncols() == 3: M = v.transpose() * self.matrix() * v - return TernaryQF([M[0,0]//2, M[1,1]//2, M[2,2]//2, M[1,2], M[0,2], M[0,1]]) + return TernaryQF([M[0, 0]//2, M[1, 1]//2, M[2, 2]//2, + M[1, 2], M[0, 2], M[0, 1]]) else: return QuadraticForm(ZZ, v.transpose() * self.matrix() * v) elif (is_Vector(v) or isinstance(v, (list, tuple))): # Check that v has length 3 - if not (len(v) == 3): + if len(v) != 3: raise TypeError("your vector needs to have length 3") v0, v1, v2 = v a, b, c, r, s, t = self.coefficients() @@ -521,19 +522,16 @@ def scale_by_factor(self, k): [ * 2/3 0 ] [ * * 4/3 ] """ - if k*self.content() in ZZ: + if k * self.content() in ZZ: + return TernaryQF([ZZ(k*self._a), ZZ(k*self._b), ZZ(k*self._c), + ZZ(k*self._r), ZZ(k*self._s), ZZ(k*self._t)]) - return TernaryQF([ZZ(k*self._a), ZZ(k*self._b), ZZ(k*self._c), ZZ(k*self._r), ZZ(k*self._s), ZZ(k*self._t)]) + R = k.parent() + if R not in Rings(): + raise TypeError(f"{k} does not belong to a ring") - else: - # arreglar con un try? - R = k.parent() - if is_Ring(R): - - return QuadraticForm(R, 3, [k*self._a, k*self._t, k*self._s, k*self._b, k*self._r, k*self._c]) - - else: - raise TypeError(f"{k} does not belong to a Ring") + return QuadraticForm(R, 3, [k * self._a, k * self._t, k * self._s, + k * self._b, k * self._r, k * self._c]) def reciprocal(self): """ @@ -735,7 +733,8 @@ def is_eisenstein_reduced(self) -> bool: sage: Q.is_eisenstein_reduced() False """ - [a,b,c,r,s,t] = [self._a,self._b,self._c,self._r,self._s,self._t] + a, b, c, r, s, t = [self._a, self._b, self._c, + self._r, self._s, self._t] # cond 2 if not (r > 0 and t > 0 and s > 0): @@ -809,11 +808,11 @@ def reduced_form_eisenstein(self, matrix=True): [3 2 1] """ if matrix: - [v,M] = _reduced_ternary_form_eisenstein_with_matrix(self._a,self._b,self._c,self._r,self._s,self._t) + v, M = _reduced_ternary_form_eisenstein_with_matrix(self._a, self._b, self._c, self._r, self._s, self._t) return TernaryQF(v), M - else: - v = _reduced_ternary_form_eisenstein_without_matrix(self._a,self._b,self._c,self._r,self._s,self._t) - return TernaryQF(v) + + v = _reduced_ternary_form_eisenstein_without_matrix(self._a, self._b, self._c, self._r, self._s, self._t) + return TernaryQF(v) def pseudorandom_primitive_zero_mod_p(self, p): """ @@ -835,7 +834,7 @@ def pseudorandom_primitive_zero_mod_p(self, p): sage: v[2] # needs sage.libs.pari 1 """ - [a,b,c,r,s,t] = self.coefficients() + a, b, c, r, s, t = self.coefficients() while True: r1 = randint(0,p-1) @@ -848,7 +847,7 @@ def pseudorandom_primitive_zero_mod_p(self, p): disc = beta**2-4*alpha*gamma if mod(disc, p).is_square(): - z = (-beta+mod(disc,p).sqrt().lift())*(2*alpha).inverse_mod(p) + z = (-beta+mod(disc, p).sqrt().lift())*(2*alpha).inverse_mod(p) # return vector((z,r1*z+r2,1))%p return z % p, (r1*z+r2) % p, 1 @@ -871,16 +870,13 @@ def find_zeros_mod_p(self, p): sage: [Q(v)%17 for v in zeros_17] # needs sage.libs.pari [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] """ - if p == 2: + return _find_zeros_mod_p_2(self._a, self._b, self._c, + self._r, self._s, self._t) - return _find_zeros_mod_p_2(self._a, self._b, self._c, self._r, self._s, self._t) - - else: - - v = self.pseudorandom_primitive_zero_mod_p(p) - [a, b, c, r, s, t] = self.coefficients() - return _find_zeros_mod_p_odd(a, b, c, r, s, t, p, v) + v = self.pseudorandom_primitive_zero_mod_p(p) + a, b, c, r, s, t = self.coefficients() + return _find_zeros_mod_p_odd(a, b, c, r, s, t, p, v) def find_p_neighbor_from_vec(self, p, v, mat=False): r""" @@ -1775,7 +1771,7 @@ def automorphisms(self, slow=True): auts = self._automorphisms_reduced_fast() self._automorphisms = [matrix(ZZ, 3, A) for A in auts] else: - [Qr, M] = self.reduced_form_eisenstein() + Qr, M = self.reduced_form_eisenstein() auts = Qr.automorphisms(slow) M_inv = M.inverse() self._automorphisms = [M*m*M_inv for m in auts] diff --git a/src/sage/rings/fraction_field.py b/src/sage/rings/fraction_field.py index bfa690cec0e..38af4f084e8 100644 --- a/src/sage/rings/fraction_field.py +++ b/src/sage/rings/fraction_field.py @@ -80,7 +80,7 @@ # 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.misc.latex as latex @@ -135,16 +135,16 @@ def FractionField(R, names=None): sage: Frac(Integers(4)) Traceback (most recent call last): ... - TypeError: R must be an integral domain. + TypeError: R must be an integral domain """ - if not ring.is_Ring(R): + if R not in Rings(): raise TypeError("R must be a ring") if not R.is_integral_domain(): - raise TypeError("R must be an integral domain.") + raise TypeError("R must be an integral domain") return R.fraction_field() -def is_FractionField(x): +def is_FractionField(x) -> bool: """ Test whether or not ``x`` inherits from :class:`FractionField_generic`. diff --git a/src/sage/rings/polynomial/polynomial_ring.py b/src/sage/rings/polynomial/polynomial_ring.py index c38fd158f53..9649765449c 100644 --- a/src/sage/rings/polynomial/polynomial_ring.py +++ b/src/sage/rings/polynomial/polynomial_ring.py @@ -147,9 +147,9 @@ import sage.categories as categories from sage.categories.morphism import IdentityMorphism +from sage.categories.rings import Rings -from sage.rings.ring import (Ring, IntegralDomain, - PrincipalIdealDomain, is_Ring) +from sage.rings.ring import (Ring, IntegralDomain, PrincipalIdealDomain) from sage.structure.element import is_RingElement import sage.rings.rational_field as rational_field from sage.rings.rational_field import QQ @@ -3596,7 +3596,7 @@ def polygen(ring_or_element, name="x"): """ if is_RingElement(ring_or_element): base_ring = ring_or_element.parent() - elif is_Ring(ring_or_element): + elif ring_or_element in Rings(): base_ring = ring_or_element else: raise TypeError("input must be a ring or ring element") @@ -3607,6 +3607,7 @@ def polygen(ring_or_element, name="x"): return t.gens() return t.gen() + def polygens(base_ring, names="x", *args): """ Return indeterminates over the given base ring with the given diff --git a/src/sage/rings/polynomial/polynomial_ring_constructor.py b/src/sage/rings/polynomial/polynomial_ring_constructor.py index 42391e37f61..38f1c5e8211 100644 --- a/src/sage/rings/polynomial/polynomial_ring_constructor.py +++ b/src/sage/rings/polynomial/polynomial_ring_constructor.py @@ -20,8 +20,9 @@ # (at your option) any later version. # https://www.gnu.org/licenses/ # **************************************************************************** + from sage.structure.category_object import normalize_names -from sage.rings.ring import is_Ring, IntegralDomain +from sage.rings.ring import IntegralDomain try: import sage.rings.padics.padic_base_leaves as padic_base_leaves @@ -39,6 +40,7 @@ class padic_base_leaves: from sage.misc.cachefunc import weak_cached_function import sage.misc.weak_dict +from sage.categories.rings import Rings from sage.categories.fields import Fields from sage.categories.commutative_rings import CommutativeRings from sage.categories.domains import Domains @@ -621,7 +623,7 @@ def PolynomialRing(base_ring, *args, **kwds): sage: R. = PolynomialRing(RIF,2) sage: TestSuite(R).run(skip=['_test_elements', '_test_elements_eq_transitive']) """ - if not is_Ring(base_ring): + if base_ring not in Rings(): raise TypeError("base_ring {!r} must be a ring".format(base_ring)) n = -1 # Unknown number of variables diff --git a/src/sage/schemes/elliptic_curves/constructor.py b/src/sage/schemes/elliptic_curves/constructor.py index 396a2ab2797..0a9e9b3eea9 100644 --- a/src/sage/schemes/elliptic_curves/constructor.py +++ b/src/sage/schemes/elliptic_curves/constructor.py @@ -32,8 +32,8 @@ from sage.rings.number_field.number_field_base import NumberField from sage.rings.finite_rings.finite_field_base import FiniteField from sage.rings.polynomial.multi_polynomial import MPolynomial -from sage.rings.ring import is_Ring +from sage.categories.rings import Rings from sage.categories.fields import Fields _Fields = Fields() @@ -405,8 +405,8 @@ def create_key_and_extra_args(self, x=None, y=None, j=None, minimal_twist=True, True """ R = None - if is_Ring(x): - (R, x) = (x, y) + if x in Rings(): + R, x = (x, y) if j is not None: if R is not None: From 559174e8da4329690c9f00dcf488497350c66b4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Sun, 25 Feb 2024 18:31:21 +0100 Subject: [PATCH 057/518] parent for pari --- src/sage/rings/pari_ring.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/sage/rings/pari_ring.py b/src/sage/rings/pari_ring.py index e11dcf4b123..f34a45829a0 100644 --- a/src/sage/rings/pari_ring.py +++ b/src/sage/rings/pari_ring.py @@ -14,10 +14,11 @@ # # The full text of the GPL is available at: # -# http://www.gnu.org/licenses/ +# https://www.gnu.org/licenses/ # **************************************************************************** import sage.libs.pari.all as pari -from sage.rings.ring import Ring +from sage.categories.rings import Rings +from sage.structure.parent import Parent from sage.structure.element import RingElement from sage.structure.richcmp import richcmp from sage.misc.fast_methods import Singleton @@ -27,7 +28,7 @@ class Pari(RingElement): """ Element of Pari pseudo-ring. """ - def __init__(self, x, parent=None): + def __init__(self, x, parent=None) -> None: """ EXAMPLES:: @@ -45,7 +46,7 @@ def __init__(self, x, parent=None): RingElement.__init__(self, parent) self.__x = pari.pari(x) - def __repr__(self): + def __repr__(self) -> str: """ EXAMPLES:: @@ -138,7 +139,7 @@ def __invert__(self): """ return self.__class__(~self.__x, parent=_inst) - def _richcmp_(self, other, op): + def _richcmp_(self, other, op) -> bool: """ EXAMPLES:: @@ -154,11 +155,11 @@ def _richcmp_(self, other, op): """ return richcmp(self.__x, other.__x, op) - def __int__(self): + def __int__(self) -> int: return int(self.__x) -class PariRing(Singleton, Ring): +class PariRing(Singleton, Parent): """ EXAMPLES:: @@ -170,9 +171,9 @@ class PariRing(Singleton, Ring): Element = Pari def __init__(self): - Ring.__init__(self, self) + Parent.__init__(self, self, category=Rings()) - def __repr__(self): + def __repr__(self) -> str: return 'Pseudoring of all PARI objects.' def _element_constructor_(self, x): @@ -180,7 +181,7 @@ def _element_constructor_(self, x): return x return self.element_class(x, parent=self) - def is_field(self, proof=True): + def is_field(self, proof=True) -> bool: return False def characteristic(self): From 95b4ad360bf2bdbd6f3cb95f2595379d213ac6f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Sun, 25 Feb 2024 20:31:55 +0100 Subject: [PATCH 058/518] some list comprehensions in rings (ruff PERF) --- ...ptotics_multivariate_generating_functions.py | 16 +++++----------- src/sage/rings/derivation.py | 12 +++--------- src/sage/rings/fraction_field.py | 9 ++++----- src/sage/rings/function_field/divisor.py | 7 ++++--- src/sage/rings/invariants/invariant_theory.py | 17 +++++++---------- src/sage/rings/number_field/S_unit_solver.py | 10 ++++------ src/sage/rings/number_field/bdd_height.py | 11 ++++------- src/sage/rings/number_field/number_field.py | 11 ++++------- src/sage/rings/number_field/totallyreal_rel.py | 8 +++----- src/sage/rings/padics/generic_nodes.py | 7 +++---- src/sage/rings/qqbar.py | 7 +++---- 11 files changed, 44 insertions(+), 71 deletions(-) diff --git a/src/sage/rings/asymptotic/asymptotics_multivariate_generating_functions.py b/src/sage/rings/asymptotic/asymptotics_multivariate_generating_functions.py index c91ea7a6e0a..039c0e8b805 100644 --- a/src/sage/rings/asymptotic/asymptotics_multivariate_generating_functions.py +++ b/src/sage/rings/asymptotic/asymptotics_multivariate_generating_functions.py @@ -2647,10 +2647,8 @@ def is_convenient_multiple_point(self, p): # Test 4: Is p convenient? M = matrix(self.log_grads(p)) - convenient_coordinates = [] - for j in range(d): - if 0 not in M.columns()[j]: - convenient_coordinates.append(j) + convenient_coordinates = [j for j in range(d) + if 0 not in M.columns()[j]] if not convenient_coordinates: return (False, 'multiple point but not convenient') @@ -2746,10 +2744,8 @@ def smooth_critical_ideal(self, alpha): d = self.dimension() # Expand K by the variables of alpha if there are any. - indets = [] - for a in alpha: - if a not in K and a in SR: - indets.append(a) + indets = [a for a in alpha if a not in K and a in SR] + indets = sorted(set(indets), key=str) # Delete duplicates in indets. if indets: L = PolynomialRing(K, indets).fraction_field() @@ -2845,9 +2841,7 @@ def maclaurin_coefficients(self, multi_indices, numerical=0): return coeffs # Create biggest multi-index needed. - alpha = [] - for i in range(d): - alpha.append(max(nu[i] for nu in multi_indices)) + alpha = [max(nu[i] for nu in multi_indices) for i in range(d)] # Compute Maclaurin expansion of self up to index alpha. # Use iterated univariate expansions. diff --git a/src/sage/rings/derivation.py b/src/sage/rings/derivation.py index d7d4fe6ebdf..2702f497d66 100644 --- a/src/sage/rings/derivation.py +++ b/src/sage/rings/derivation.py @@ -1133,9 +1133,7 @@ def _bracket_(self, other): parent = self.parent() if parent.domain() is not parent.codomain(): raise TypeError("the bracket is only defined for derivations with same domain and codomain") - arg = [ ] - for x in parent.dual_basis(): - arg.append(self(other(x)) - other(self(x))) + arg = [self(other(x)) - other(self(x)) for x in parent.dual_basis()] return parent(arg) def pth_power(self): @@ -1268,9 +1266,7 @@ def precompose(self, morphism): elif not (isinstance(morphism, Map) and morphism.category_for().is_subcategory(Rings())): raise TypeError("you must give a homomorphism of rings") M = RingDerivationModule(morphism.domain(), parent.defining_morphism() * morphism) - arg = [ ] - for x in M.dual_basis(): - arg.append(self(morphism(x))) + arg = [self(morphism(x)) for x in M.dual_basis()] return M(arg) def postcompose(self, morphism): @@ -1328,9 +1324,7 @@ def postcompose(self, morphism): elif not (isinstance(morphism, Map) and morphism.category_for().is_subcategory(Rings())): raise TypeError("you must give a homomorphism of rings") M = RingDerivationModule(parent.domain(), morphism * parent.defining_morphism()) - arg = [ ] - for x in M.dual_basis(): - arg.append(morphism(self(x))) + arg = [morphism(self(x)) for x in M.dual_basis()] return M(arg) def extend_to_fraction_field(self): diff --git a/src/sage/rings/fraction_field.py b/src/sage/rings/fraction_field.py index bfa690cec0e..92d53b9048e 100644 --- a/src/sage/rings/fraction_field.py +++ b/src/sage/rings/fraction_field.py @@ -917,13 +917,12 @@ def some_elements(self): (2*x^2 + 2)/x^3, (2*x^2 + 2)/(x^2 - 1), 2] - """ ret = [self.zero(), self.one()] - for a in self._R.some_elements(): - for b in self._R.some_elements(): - if a != b and self(a) and self(b): - ret.append(self(a) / self(b)) + ret.extend(self(a) / self(b) + for a in self._R.some_elements() + for b in self._R.some_elements() + if a != b and self(a) and self(b)) return ret def _gcd_univariate_polynomial(self, f, g): diff --git a/src/sage/rings/function_field/divisor.py b/src/sage/rings/function_field/divisor.py index 84c837ffb09..79d952e2247 100644 --- a/src/sage/rings/function_field/divisor.py +++ b/src/sage/rings/function_field/divisor.py @@ -871,9 +871,10 @@ def _basis(self): # invariants of M. basis = [] for j in range(n): - i,ideg = pivot_row[j][0] - for k in range( den.degree() - ideg + 1 ): - basis.append(one.shift(k) * gens[i]) + i, ideg = pivot_row[j][0] + gi = gens[i] + basis.extend(one.shift(k) * gi + for k in range(den.degree() - ideg + 1)) # Done! return basis diff --git a/src/sage/rings/invariants/invariant_theory.py b/src/sage/rings/invariants/invariant_theory.py index 6d5e8646186..1420192a67a 100644 --- a/src/sage/rings/invariants/invariant_theory.py +++ b/src/sage/rings/invariants/invariant_theory.py @@ -1038,18 +1038,15 @@ def monomials(self): def prod(a, b): if a is None and b is None: return self._ring.one() - elif a is None: + if a is None: return b - elif b is None: + if b is None: return a - else: - return a * b - squares = tuple( prod(x,x) for x in var ) - mixed = [] - for i in range(self._n): - for j in range(i+1, self._n): - mixed.append(prod(var[i], var[j])) - mixed = tuple(mixed) + return a * b + + squares = tuple(prod(x, x) for x in var) + mixed = tuple([prod(var[i], var[j]) for i in range(self._n) + for j in range(i + 1, self._n)]) return squares + mixed @cached_method diff --git a/src/sage/rings/number_field/S_unit_solver.py b/src/sage/rings/number_field/S_unit_solver.py index 4b6b201375f..1bc9e07d9c0 100644 --- a/src/sage/rings/number_field/S_unit_solver.py +++ b/src/sage/rings/number_field/S_unit_solver.py @@ -150,23 +150,21 @@ def c3_func(SUK, prec=106): - [AKMRVW]_ :arxiv:`1903.00977` """ - R = RealField(prec) all_places = list(SUK.primes()) + SUK.number_field().places(prec) Possible_U = Combinations(all_places, SUK.rank()) - c1 = R(1) # guarantees final c1 >= 1 + c1 = R(1) # guarantees final c1 >= 1 for U in Possible_U: # first, build the matrix C_{i,U} - columns_of_C = [] - for unit in SUK.fundamental_units(): - columns_of_C.append(column_Log(SUK, unit, U, prec)) + columns_of_C = [column_Log(SUK, unit, U, prec) + for unit in SUK.fundamental_units()] C = matrix(SUK.rank(), SUK.rank(), columns_of_C) # Is it invertible? if abs(C.determinant()) > 10**(-10): poss_c1 = C.inverse().apply_map(abs).norm(Infinity) c1 = R(max(poss_c1, c1)) - return R(0.9999999) / (c1*SUK.rank()) + return R(0.9999999) / (c1 * SUK.rank()) def c4_func(SUK, v, A, prec=106): diff --git a/src/sage/rings/number_field/bdd_height.py b/src/sage/rings/number_field/bdd_height.py index 2b3d18a6578..262f7f8a2ad 100644 --- a/src/sage/rings/number_field/bdd_height.py +++ b/src/sage/rings/number_field/bdd_height.py @@ -188,7 +188,7 @@ def bdd_height_iq(K, height_bound): possible_norm_set = set() for n in range(class_number): for m in range(1, int(height_bound + 1)): - possible_norm_set.add(m*class_group_rep_norms[n]) + possible_norm_set.add(m * class_group_rep_norms[n]) bdd_ideals = bdd_norm_pr_gens_iq(K, possible_norm_set) # Distribute the principal ideals @@ -196,11 +196,8 @@ def bdd_height_iq(K, height_bound): for n in range(class_number): this_ideal = class_group_reps[n] this_ideal_norm = class_group_rep_norms[n] - gens = [] - for i in range(1, int(height_bound + 1)): - for g in bdd_ideals[i*this_ideal_norm]: - if g in this_ideal: - gens.append(g) + gens = [g for i in range(1, int(height_bound + 1)) + for g in bdd_ideals[i * this_ideal_norm] if g in this_ideal] generator_lists.append(gens) # Build all the output numbers @@ -210,7 +207,7 @@ def bdd_height_iq(K, height_bound): for i in range(s): for j in range(i + 1, s): if K.ideal(gens[i], gens[j]) == class_group_reps[n]: - new_number = gens[i]/gens[j] + new_number = gens[i] / gens[j] for zeta in roots_of_unity: yield zeta * new_number yield zeta / new_number diff --git a/src/sage/rings/number_field/number_field.py b/src/sage/rings/number_field/number_field.py index 5fd638f9396..2ba3a012abe 100644 --- a/src/sage/rings/number_field/number_field.py +++ b/src/sage/rings/number_field/number_field.py @@ -10438,9 +10438,8 @@ def hilbert_symbol_negative_at_S(self, S, b, check=True): L.append(P) # This adds some infinite places to L - for sigma in self.real_places(): - if sigma(b) < 0 and sigma not in S: - L.append(sigma) + L.extend(sigma for sigma in self.real_places() + if sigma(b) < 0 and sigma not in S) Cl = self.class_group(proof=False) U = self.unit_group(proof=False).gens() SL = S + L @@ -10462,10 +10461,8 @@ def hilbert_symbol_negative_at_S(self, S, b, check=True): # on the set of generators def phi(x): - v = [] - for p in SL: - v.append((1-self.hilbert_symbol(x, b, p))//2) - return V(v) + return V([(1 - self.hilbert_symbol(x, b, p)) // 2 for p in SL]) + M = matrix([phi(g) for g in U]) # we have to work around the inconvenience that multiplicative diff --git a/src/sage/rings/number_field/totallyreal_rel.py b/src/sage/rings/number_field/totallyreal_rel.py index 9b416d2ad02..baf5aca9f68 100644 --- a/src/sage/rings/number_field/totallyreal_rel.py +++ b/src/sage/rings/number_field/totallyreal_rel.py @@ -453,15 +453,13 @@ def incr(self, f_out, verbose=False, haltk=0): # Enumerate all elements of Z_F with T_2 <= br T2s = [] trace_elts_found = False - for i in range(len(self.trace_elts)): - tre = self.trace_elts[i] + for tre in self.trace_elts: if tre[0] <= bl and tre[1] >= br: trace_elts_found = True if verbose >= 2: print(" found copy!") - for theta in tre[2]: - if theta.trace() >= bl and theta.trace() <= br: - T2s.append(theta) + T2s.extend(theta for theta in tre[2] + if bl <= theta.trace() <= br) break if not trace_elts_found: T2s = self.F._positive_integral_elements_with_trace([bl,br]) diff --git a/src/sage/rings/padics/generic_nodes.py b/src/sage/rings/padics/generic_nodes.py index 3a135548a32..44cd8b7af4e 100644 --- a/src/sage/rings/padics/generic_nodes.py +++ b/src/sage/rings/padics/generic_nodes.py @@ -683,10 +683,8 @@ def convert_multiple(self, *elts): raise NotImplementedError("multiple conversion of a set of variables for which the module precision is not a lattice is not implemented yet") for j in range(len(L)): x = L[j] - dx = [] - for i in range(j): - dx.append([L[i], lattice[i,j]]) - prec = lattice[j,j].valuation(p) + dx = [[L[i], lattice[i, j]] for i in range(j)] + prec = lattice[j, j].valuation(p) y = self._element_class(self, x.value(), prec, dx=dx, dx_mode='values', check=False, reduce=False) for i in indices[id(x)]: ans[i] = y @@ -700,6 +698,7 @@ def convert_multiple(self, *elts): # We return the created elements return ans + class pAdicRelaxedGeneric(pAdicGeneric): r""" Generic class for relaxed `p`-adics. diff --git a/src/sage/rings/qqbar.py b/src/sage/rings/qqbar.py index 4d92d64d80f..2c0f5dbc6d8 100644 --- a/src/sage/rings/qqbar.py +++ b/src/sage/rings/qqbar.py @@ -4801,11 +4801,10 @@ def radical_expression(self): roots = poly.roots(SR, multiplicities=False) if len(roots) != poly.degree(): return self + itv = interval_field(self._value) while True: - candidates = [] - for root in roots: - if interval_field(root).overlaps(interval_field(self._value)): - candidates.append(root) + candidates = [root for root in roots + if interval_field(root).overlaps(itv)] if len(candidates) == 1: return candidates[0] roots = candidates From 42ded7cdf16c51052c896949ab1c19157a788e28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Sun, 25 Feb 2024 21:02:39 +0100 Subject: [PATCH 059/518] various details in permutation.py (pep8 and ruff PERF) --- src/sage/combinat/permutation.py | 270 ++++++++++++++----------------- 1 file changed, 124 insertions(+), 146 deletions(-) diff --git a/src/sage/combinat/permutation.py b/src/sage/combinat/permutation.py index 92e187287fd..def83c9750f 100644 --- a/src/sage/combinat/permutation.py +++ b/src/sage/combinat/permutation.py @@ -451,20 +451,17 @@ def __classcall_private__(cls, l, check=True): return l elif isinstance(l, PermutationGroupElement): l = l.domain() - #if l is a string, then assume it is in cycle notation + # if l is a string, then assume it is in cycle notation elif isinstance(l, str): if l == "()" or l == "": return from_cycles(0, []) cycles = l.split(")(") cycles[0] = cycles[0][1:] cycles[-1] = cycles[-1][:-1] - cycle_list = [] - for c in cycles: - cycle_list.append([int(_) for _ in c.split(",")]) - + cycle_list = [[int(k) for k in c.split(",")] for c in cycles] return from_cycles(max(max(c) for c in cycle_list), cycle_list) - #if l is a pair of standard tableaux or a pair of lists + # if l is a pair of standard tableaux or a pair of lists elif isinstance(l, (tuple, list)) and len(l) == 2 and \ all(isinstance(x, Tableau) for x in l): return RSK_inverse(*l, output='permutation') @@ -692,7 +689,7 @@ def _latex_(self): return repr(self._list) if display == "cycle": ret = self.cycle_string() - else: # Must be cycles with singletons + else: # Must be cycles with singletons ret = self.cycle_string(singletons=True) return ret.replace(",", " \\; ") @@ -772,26 +769,26 @@ def __next__(self): n = len(self) first = -1 - #Starting from the end, find the first o such that - #p[o] < p[o+1] + # Starting from the end, find the first o such that + # p[o] < p[o+1] for i in reversed(range(n - 1)): if p[i] < p[i + 1]: first = i break - #If first is still -1, then we are already at the last permutation + # if first is still -1, then we are already at the last permutation if first == -1: return False - #Starting from the end, find the first j such that p[j] > p[first] + # Starting from the end, find the first j such that p[j] > p[first] j = n - 1 while p[j] < p[first]: j -= 1 - #Swap positions first and j + # Swap positions first and j (p[j], p[first]) = (p[first], p[j]) - #Reverse the list between first and the end + # Reverse the list between first and the end first_half = p[:first+1] last_half = p[first+1:] last_half.reverse() @@ -832,15 +829,15 @@ def prev(self): n = len(self) first = -1 - #Starting from the end, find the first o such that - #p[o] > p[o+1] + # Starting from the end, find the first o such that + # p[o] > p[o+1] for i in reversed(range(n - 1)): if p[i] > p[i + 1]: first = i break - #If first is still -1, that is we didn't find any descents, - #then we are already at the last permutation + # if first is still -1, that is we didn't find any descents, + # then we are already at the last permutation if first == -1: return False @@ -1008,31 +1005,31 @@ def _to_cycles_orig(self, singletons=True): l = [i + 1 for i in range(len(p))] cycle = [] - #Go through until we've considered every number between - #1 and len(p) + # Go through until we've considered every number between + # 1 and len(p) while l: - #If we are at the end of a cycle - #then we want to add it to the cycles list + # if we are at the end of a cycle + # then we want to add it to the cycles list if toConsider == -1: - #Add the cycle to the list of cycles + # Add the cycle to the list of cycles if singletons: if cycle: cycles.append(tuple(cycle)) else: if len(cycle) > 1: cycles.append(tuple(cycle)) - #Start with the first element in the list + # Start with the first element in the list toConsider = l[0] l.remove(toConsider) cycle = [toConsider] cycleFirst = toConsider - #Figure out where the element under consideration - #gets mapped to. + # Figure out where the element under consideration + # gets mapped to. next = p[toConsider - 1] - #If the next element is the first one in the list - #then we've reached the end of the cycle + # if the next element is the first one in the list + # then we've reached the end of the cycle if next == cycleFirst: toConsider = -1 else: @@ -1070,12 +1067,12 @@ def _to_cycles_set(self, singletons=True): cycles = [] if not singletons: - #remove the fixed points - L = set(i+1 for i,pi in enumerate(p) if pi != i+1) + # remove the fixed points + L = {i+1 for i,pi in enumerate(p) if pi != i+1} else: L = set(range(1,len(p)+1)) - #Go through until we've considered every remaining number + # Go through until we've considered every remaining number while L: # take the first remaining element cycleFirst = L.pop() @@ -1111,14 +1108,14 @@ def _to_cycles_list(self, singletons=True): cycles = [] if not singletons: - #remove the fixed points - L = [i+1 for i,pi in enumerate(p) if pi != i+1] + # remove the fixed points + L = [i + 1 for i, pi in enumerate(p) if pi != i + 1] else: L = list(range(1, len(p) + 1)) from bisect import bisect_left - #Go through until we've considered every remaining number + # Go through until we've considered every remaining number while L: # take the first remaining element cycleFirst = L.pop(0) @@ -1169,7 +1166,7 @@ def signature(self) -> Integer: """ return (-1)**(len(self)-len(self.to_cycles())) - #one can also use sign as an alias for signature + # one can also use sign as an alias for signature sign = signature def is_even(self) -> bool: @@ -1228,7 +1225,7 @@ def to_matrix(self): """ # build the dictionary of entries since the matrix is # extremely sparse - entries = { (v-1, i): 1 for i, v in enumerate(self) } + entries = {(v - 1, i): 1 for i, v in enumerate(self)} M = MatrixSpace(ZZ, len(self), sparse=True) return M(entries) @@ -1599,7 +1596,7 @@ def merge_and_countv(ivA_A, ivB_B): def base_case(L): s = sorted(L) - d = dict((j, i) for i, j in enumerate(s)) + d = {j: i for i, j in enumerate(s)} iv = [0]*len(L) checked = [1]*len(L) for pi in reversed(L): @@ -1631,7 +1628,7 @@ def inversions(self) -> list: """ p = self[:] n = len(p) - return [tuple([i+1,j+1]) for i in range(n-1) for j in range(i+1,n) + return [(i+1,j+1) for i in range(n-1) for j in range(i+1,n) if p[i] > p[j]] def stack_sort(self) -> Permutation: @@ -1753,9 +1750,9 @@ def show(self, representation="cycles", orientation="landscape", **args): from sage.plot.text import text if orientation == "landscape": - r = lambda x,y : (x,y) + r = lambda x, y: (x, y) elif orientation == "portrait": - r = lambda x,y : (-y,x) + r = lambda x, y: (-y, x) else: raise ValueError("The value of 'orientation' must be either " + "'landscape' or 'portrait'.") @@ -1828,7 +1825,8 @@ def noninversions(self, k) -> list: """ if k > len(self): return [] - return [list(pos) for pos in itertools.combinations(self, k) if all( pos[i] < pos[i+1] for i in range(k-1) )] + return [list(pos) for pos in itertools.combinations(self, k) + if all(pos[i] < pos[i + 1] for i in range(k - 1))] def number_of_noninversions(self, k) -> Integer: r""" @@ -2569,7 +2567,7 @@ def foata_bijection_inverse(self) -> Permutation: [1] """ L = list(self) - Mrev = [] # The resulting permutation, in reverse. + Mrev = [] # The resulting permutation, in reverse. while L: e = L.pop() Mrev.append(e) @@ -2721,7 +2719,7 @@ def fundamental_transformation_inverse(self): record_value = 0 previous_record = None previous_entry = None - res = [0] * (n+1) # We'll use res[1], res[2], ..., res[n] only. + res = [0] * (n + 1) # We'll use res[1], res[2], ..., res[n] only. for entry in self: if entry > record_value: record_value = entry @@ -3193,13 +3191,8 @@ def recoils(self) -> list: sage: Permutation([]).recoils() [] """ - p = self - recoils = [] - for i in range(len(p)): - if p[i] != len(self) and self.index(p[i] + 1) < i: - recoils.append(i) - - return recoils + return [i for i, pi in enumerate(self) + if pi != len(self) and self.index(pi + 1) < i] def number_of_recoils(self) -> Integer: r""" @@ -3606,13 +3599,12 @@ def to_major_code(self, final_descent=False): P = Permutations() for i in range(n): major_indices[i] = P(smaller).major_index(final_descent) - #Create the permutation that "erases" all the numbers - #smaller than i+1 + # Create the permutation that "erases" all the numbers + # smaller than i+1 smaller.remove(1) - smaller = [i-1 for i in smaller] + smaller = [i - 1 for i in smaller] - major_code = [ major_indices[i] - major_indices[i+1] for i in range(n) ] - return major_code + return [major_indices[i] - major_indices[i + 1] for i in range(n)] ######### # Peaks # @@ -3635,12 +3627,8 @@ def peaks(self) -> list: [] """ p = self - peaks = [] - for i in range(1,len(p)-1): - if p[i-1] <= p[i] and p[i] > p[i+1]: - peaks.append(i) - - return peaks + return [i for i in range(1, len(p) - 1) + if p[i - 1] <= p[i] and p[i] > p[i + 1]] def number_of_peaks(self) -> Integer: r""" @@ -3790,11 +3778,7 @@ def weak_excedences(self) -> list: sage: Permutation([1,4,3,2,5]).weak_excedences() [1, 4, 3, 5] """ - res = [] - for i in range(len(self)): - if self[i] >= i + 1: - res.append(self[i]) - return res + return [pi for i, pi in enumerate(self) if pi >= i + 1] def bruhat_inversions(self) -> list: r""" @@ -4621,7 +4605,7 @@ def simion_schmidt(self, avoid=[1,2,3]): targetPermutation[i] = nonMinima.pop() return Permutations()(targetPermutation) - @combinatorial_map(order=2,name='reverse') + @combinatorial_map(order=2, name='reverse') def reverse(self): """ Return the permutation obtained by reversing the list. @@ -4633,9 +4617,9 @@ def reverse(self): sage: Permutation([1,2,3,4,5]).reverse() [5, 4, 3, 2, 1] """ - return self.__class__(self.parent(), [i for i in reversed(self)] ) + return self.__class__(self.parent(), list(reversed(self))) - @combinatorial_map(order=2,name='complement') + @combinatorial_map(order=2, name='complement') def complement(self): r""" Return the complement of the permutation ``self``. @@ -4651,7 +4635,7 @@ def complement(self): [3, 1, 2] """ n = len(self) - return self.__class__(self.parent(), [n - x + 1 for x in self] ) + return self.__class__(self.parent(), [n - x + 1 for x in self]) @combinatorial_map(name='permutation poset') def permutation_poset(self): @@ -5074,8 +5058,8 @@ def remove_extra_fixed_points(self): """ if not self: return Permutations()([1]) - #Strip off all extra fixed points at the end of - #the permutation. + # Strip off all extra fixed points at the end of + # the permutation. i = len(self)-1 while i >= 1: if i != self[i] - 1: @@ -5849,11 +5833,11 @@ def __classcall_private__(cls, n=None, k=None, **kwargs): elif k is not None: number_of_arguments += 1 - #Make sure that exactly one keyword was passed + # Make sure that exactly one keyword was passed for key in kwargs: if key not in valid_args: raise ValueError("unknown keyword argument: %s" % key) - if key not in [ 'avoiding' ]: + if key != 'avoiding': number_of_arguments += 1 if number_of_arguments == 0: @@ -5927,13 +5911,13 @@ def __classcall_private__(cls, n=None, k=None, **kwargs): else: return Permutations_msetk(n, k) elif 'descents' in kwargs: - #Descent positions specified + # Descent positions specified if isinstance(kwargs['descents'], tuple): - #Descent positions and size specified + # Descent positions and size specified args = kwargs['descents'] return StandardPermutations_descents(tuple(args[0]), args[1]) else: - #Size not specified + # Size not specified return StandardPermutations_descents(kwargs['descents']) elif 'bruhat_smaller' in kwargs: return StandardPermutations_bruhat_smaller(Permutation(kwargs['bruhat_smaller'])) @@ -5992,37 +5976,37 @@ class options(GlobalOptions): """ NAME = 'Permutations' module = 'sage.combinat.permutation' - display = dict(default="list", - description="Specifies how the permutations should be printed", - values=dict(list="the permutations are displayed in list notation" + display = {'default': "list", + 'description': "Specifies how the permutations should be printed", + 'values': {'list': "the permutations are displayed in list notation" " (aka 1-line notation)", - cycle="the permutations are displayed in cycle notation" + 'cycle': "the permutations are displayed in cycle notation" " (i. e., as products of disjoint cycles)", - singleton="the permutations are displayed in cycle notation" + 'singleton': "the permutations are displayed in cycle notation" " with singleton cycles shown as well", - reduced_word="the permutations are displayed as reduced words"), - alias=dict(word="reduced_word", reduced_expression="reduced_word"), - case_sensitive=False) - latex = dict(default="list", - description="Specifies how the permutations should be latexed", - values=dict(list="latex as a list in one-line notation", - twoline="latex in two-line notation", - cycle="latex in cycle notation", - singleton="latex in cycle notation with singleton cycles shown as well", - reduced_word="latex as reduced words"), - alias=dict(word="reduced_word", reduced_expression="reduced_word", oneline="list"), - case_sensitive=False) - latex_empty_str = dict(default="1", - description='The LaTeX representation of a reduced word when said word is empty', - checker=lambda char: isinstance(char,str)) - generator_name = dict(default="s", - description="the letter used in latexing the reduced word", - checker=lambda char: isinstance(char,str)) - mult = dict(default="l2r", - description="The multiplication of permutations", - values=dict(l2r=r"left to right: `(p_1 \cdot p_2)(x) = p_2(p_1(x))`", - r2l=r"right to left: `(p_1 \cdot p_2)(x) = p_1(p_2(x))`"), - case_sensitive=False) + 'reduced_word': "the permutations are displayed as reduced words"}, + 'alias': {'word': "reduced_word", 'reduced_expression': "reduced_word"}, + 'case_sensitive': False} + latex = {'default': "list", + 'description': "Specifies how the permutations should be latexed", + 'values': {'list': "latex as a list in one-line notation", + 'twoline': "latex in two-line notation", + 'cycle': "latex in cycle notation", + 'singleton': "latex in cycle notation with singleton cycles shown as well", + 'reduced_word': "latex as reduced words"}, + 'alias': {'word': "reduced_word", 'reduced_expression': "reduced_word", 'oneline': "list"}, + 'case_sensitive': False} + latex_empty_str = {'default': "1", + 'description': 'The LaTeX representation of a reduced word when said word is empty', + 'checker': lambda char: isinstance(char,str)} + generator_name = {'default': "s", + 'description': "the letter used in latexing the reduced word", + 'checker': lambda char: isinstance(char,str)} + mult = {'default': "l2r", + 'description': "The multiplication of permutations", + 'values': {'l2r': r"left to right: `(p_1 \cdot p_2)(x) = p_2(p_1(x))`", + 'r2l': r"right to left: `(p_1 \cdot p_2)(x) = p_1(p_2(x))`"}, + 'case_sensitive': False} class Permutations_nk(Permutations): @@ -6287,7 +6271,7 @@ def __iter__(self): return while next_perm(mset_list): - #Yield the permutation + # Yield the permutation yield self.element_class(self, map_to_list(mset_list, mset, n), check=False) def cardinality(self): @@ -6477,7 +6461,7 @@ def unrank(self, r): # ``self.mset`` and always kept sorted. ``m`` represents ``mset`` in # element→count form. One element is removed from both representations # of the multiset in each loop iteration. - mset = list(sorted(self.mset)) + mset = sorted(self.mset) m = {} for x in mset: m[x] = m.get(x, 0) + 1 @@ -7413,12 +7397,10 @@ def element_in_conjugacy_classes(self, nu): l = [] i = 0 for nui in nu: - for j in range(nui-1): - l.append(i+j+2) - l.append(i+1) + l.extend(i + j + 2 for j in range(nui - 1)) + l.append(i + 1) i += nui - for i in range(nu.size(), self.n): - l.append(i+1) + l.extend(i + 1 for i in range(nu.size(), self.n)) return self.element_class(self, l, check=False) def conjugacy_classes_representatives(self): @@ -7455,8 +7437,8 @@ def conjugacy_classes_representatives(self): [[1]] """ from sage.combinat.partition import Partitions_n - return [ self.element_in_conjugacy_classes(la) - for la in reversed(Partitions_n(self.n)) ] + return [self.element_in_conjugacy_classes(la) + for la in reversed(Partitions_n(self.n))] def conjugacy_classes_iterator(self): """ @@ -7834,7 +7816,7 @@ def from_permutation_group_element(pge, parent=None): raise TypeError("pge (= %s) must be a PermutationGroupElement" % pge) if parent is None: - parent = Permutations( len(pge.domain()) ) + parent = Permutations(len(pge.domain())) return parent(pge.domain()) @@ -7868,7 +7850,7 @@ def from_rank(n, rank): sage: permutation.from_rank(6,10) [1, 2, 4, 6, 3, 5] """ - #Find the factoradic of rank + # Find the factoradic of rank factoradic = [None] * n for j in range(1,n+1): factoradic[n-j] = Integer(rank % j) @@ -8003,14 +7985,10 @@ def from_lehmer_code(lehmer, parent=None): sage: permutation.from_lehmer_code(lc) [2, 1, 5, 4, 3] """ - p = [] - open_spots = list(range(1,len(lehmer)+1)) - for ivi in lehmer: - p.append(open_spots.pop(ivi)) - + open_spots = list(range(1, len(lehmer) + 1)) if parent is None: parent = Permutations() - return parent(p) + return parent([open_spots.pop(ivi) for ivi in lehmer]) def from_lehmer_cocode(lehmer, parent=Permutations()): @@ -8459,7 +8437,7 @@ def descents_composition_first(dc): except TypeError: raise TypeError("The argument must be of type Composition") - cpl = [x for x in reversed(dc.conjugate())] + cpl = list(reversed(dc.conjugate())) res = [] s = 0 for cpli in cpl: @@ -8488,7 +8466,7 @@ def descents_composition_last(dc): s = 0 res = [] for i in reversed(range(len(dc))): - res = [j for j in range(s+1,s+dc[i]+1)] + res + res = list(range(s+1,s+dc[i]+1)) + res s += dc[i] return Permutations()(res) @@ -8546,11 +8524,11 @@ def __iter__(self): recoils = self.recoils dag = DiGraph() - #Add the nodes + # Add the nodes for i in range(1, sum(recoils)+1): dag.add_vertex(i) - #Add the edges to guarantee a finer recoil composition + # Add the edges to guarantee a finer recoil composition pos = 1 for part in recoils: for i in range(part-1): @@ -8620,11 +8598,11 @@ def __iter__(self): recoils = self.recoils dag = DiGraph() - #Add the nodes + # Add the nodes for i in range(1, sum(recoils)+1): dag.add_vertex(i) - #Add the edges to guarantee a fatter recoil composition + # Add the edges to guarantee a fatter recoil composition pos = 0 for i in range(len(recoils)-1): pos += recoils[i] @@ -8684,11 +8662,11 @@ def __iter__(self): recoils = self.recoils dag = DiGraph() - #Add all the nodes + # Add all the nodes for i in range(1, sum(recoils)+1): dag.add_vertex(i) - #Add the edges which guarantee a finer recoil comp. + # Add the edges which guarantee a finer recoil comp. pos = 1 for part in recoils: for i in range(part-1): @@ -8696,7 +8674,7 @@ def __iter__(self): pos += 1 pos += 1 - #Add the edges which guarantee a fatter recoil comp. + # Add the edges which guarantee a fatter recoil comp. pos = 0 for i in range(len(recoils)-1): pos += recoils[i] @@ -8755,24 +8733,24 @@ def from_major_code(mc, final_descent=False): if not mc: w = [] else: - #define w^(n) to be the one-letter word n + # define w^(n) to be the one-letter word n w = [len(mc)] - #for i=n-1,..,1 let w^i be the unique word obtained by inserting - #the letter i into the word w^(i+1) in such a way that - #maj(w^i)-maj(w^(i+1)) = mc[i] + # for i=n-1,..,1 let w^i be the unique word obtained by inserting + # the letter i into the word w^(i+1) in such a way that + # maj(w^i)-maj(w^(i+1)) = mc[i] for i in reversed(range(1,len(mc))): - #Lemma 2.2 in Skandera + # Lemma 2.2 in Skandera - #Get the descents of w and place them in reverse order + # Get the descents of w and place them in reverse order d = Permutation(w, check=False).descents(final_descent=final_descent) d.reverse() - #a is the list of all positions which are not descents + # a is the list of all positions which are not descents a = [x for x in range(1, len(w) + 1) if x not in d] - #d_k = -1 -- 0 in the lemma, but -1 due to 0-based indexing + # d_k = -1 -- 0 in the lemma, but -1 due to 0-based indexing d.append(0) l = mc[i-1] indices = d + a @@ -9308,7 +9286,7 @@ def list(self, distinct=False): ############################################### -#Avoiding +# Avoiding class StandardPermutations_all_avoiding(StandardPermutations_all): """ @@ -9642,12 +9620,12 @@ def __iter__(self): yield self.element_class(self, p, check=False) return - #Yield all the 132 avoiding permutations to the right. + # Yield all the 132 avoiding permutations to the right. for right in StandardPermutations_avoiding_132(self.n - 1): yield self.element_class(self, [self.n] + list(right), check=False) - #yi + # yi for i in range(1, self.n-1): for left in StandardPermutations_avoiding_132(i): for right in StandardPermutations_avoiding_132(self.n-i-1): @@ -9656,7 +9634,7 @@ def __iter__(self): + [self.n] + list(right), check=False) - #Yield all the 132 avoiding permutations to the left + # Yield all the 132 avoiding permutations to the left for left in StandardPermutations_avoiding_132(self.n - 1): yield self.element_class(self, list(left) + [self.n], check=False) @@ -9709,8 +9687,8 @@ def __iter__(self): return for p in StandardPermutations_avoiding_132(self.n): - #Convert p to a 123 avoiding permutation by - m = self.n+1 + # Convert p to a 123 avoiding permutation by + m = self.n + 1 minima_pos = [] minima = [] for i in range(self.n): @@ -9725,10 +9703,10 @@ def __iter__(self): b = 0 for i in range(self.n): if i in minima_pos: - new_p.append( minima[a] ) + new_p.append(minima[a]) a += 1 else: - new_p.append( non_minima[b] ) + new_p.append(non_minima[b]) b += 1 yield self.element_class(self, new_p, check=False) From a77ed12c577b37b79886465e934a696284aabf97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Sun, 25 Feb 2024 21:06:59 +0100 Subject: [PATCH 060/518] suggested detail --- ...symptotics_multivariate_generating_functions.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/sage/rings/asymptotic/asymptotics_multivariate_generating_functions.py b/src/sage/rings/asymptotic/asymptotics_multivariate_generating_functions.py index 039c0e8b805..814adfcfb47 100644 --- a/src/sage/rings/asymptotic/asymptotics_multivariate_generating_functions.py +++ b/src/sage/rings/asymptotic/asymptotics_multivariate_generating_functions.py @@ -540,7 +540,7 @@ def quotient(self): """ return self.numerator() / self.denominator() - def _repr_(self): + def _repr_(self) -> str: r""" Return a string representation of ``self``. @@ -2647,8 +2647,8 @@ def is_convenient_multiple_point(self, p): # Test 4: Is p convenient? M = matrix(self.log_grads(p)) - convenient_coordinates = [j for j in range(d) - if 0 not in M.columns()[j]] + cols = M.columns() + convenient_coordinates = [j for j in range(d) if 0 not in cols[j]] if not convenient_coordinates: return (False, 'multiple point but not convenient') @@ -3079,7 +3079,7 @@ def __init__(self, denominator_ring, numerator_ring=None, category=None): self._denominator_ring = denominator_ring Parent.__init__(self, denominator_ring, category=category) - def _repr_(self): + def _repr_(self) -> str: r""" Return a representation. @@ -3333,7 +3333,7 @@ class FractionWithFactoredDenominatorSum(list): - Daniel Krenn (2014-12-01) """ - def __repr__(self): + def __repr__(self) -> str: r""" Return a string representation of ``self``. @@ -3353,7 +3353,7 @@ def __repr__(self): """ return ' + '.join(repr(r) for r in self) - def __eq__(self, other): + def __eq__(self, other) -> bool: r""" Return ``True`` if ``self`` is equal to ``other``. @@ -3379,7 +3379,7 @@ def __eq__(self, other): return (sorted(self, key=methodcaller('_total_order_key_')) == sorted(other, key=methodcaller('_total_order_key_'))) - def __ne__(self, other): + def __ne__(self, other) -> bool: r""" Return ``True`` if ``self`` is not equal to ``other``. From c1051740747e47cc3cfd375fe4922527b3400425 Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Mon, 26 Feb 2024 04:31:45 +0000 Subject: [PATCH 061/518] =?UTF-8?q?=F0=9F=8E=89=20fix=20failing=20doctests?= =?UTF-8?q?=20=F0=9F=8E=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/sage/rings/polynomial/polynomial_template.pxi | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/sage/rings/polynomial/polynomial_template.pxi b/src/sage/rings/polynomial/polynomial_template.pxi index 076a9753b29..f02ede09890 100644 --- a/src/sage/rings/polynomial/polynomial_template.pxi +++ b/src/sage/rings/polynomial/polynomial_template.pxi @@ -360,7 +360,9 @@ cdef class Polynomial_template(Polynomial): sage: R. = Zmod(4)[] sage: f = R(2 * x) sage: f.gcd(f) - 2*x + Traceback (most recent call last): + ... + ValueError: leading coefficient must be invertible :: From 7243f216c33800464598e6eae0d0ecb9618de8a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Mon, 26 Feb 2024 08:33:34 +0100 Subject: [PATCH 062/518] fix mistake --- src/sage/matrix/matrix_sparse.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/matrix/matrix_sparse.pyx b/src/sage/matrix/matrix_sparse.pyx index c537798ba92..efe90dbb066 100644 --- a/src/sage/matrix/matrix_sparse.pyx +++ b/src/sage/matrix/matrix_sparse.pyx @@ -62,7 +62,7 @@ cdef class Matrix_sparse(matrix.Matrix): [2.00000000000000*x -2.00000000000000] [-------------------------------------] """ - if R not in Rings(): + if ring not in Rings(): raise TypeError("input must be a ring") if ring is self._base_ring: if self._is_immutable: From 8988fda0ed9753caeae2ff48e57c17e9e8c17f04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Mon, 26 Feb 2024 09:04:12 +0100 Subject: [PATCH 063/518] really use suggestion --- .../asymptotic/asymptotics_multivariate_generating_functions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/asymptotic/asymptotics_multivariate_generating_functions.py b/src/sage/rings/asymptotic/asymptotics_multivariate_generating_functions.py index 814adfcfb47..d8c268e5ad7 100644 --- a/src/sage/rings/asymptotic/asymptotics_multivariate_generating_functions.py +++ b/src/sage/rings/asymptotic/asymptotics_multivariate_generating_functions.py @@ -2648,7 +2648,7 @@ def is_convenient_multiple_point(self, p): # Test 4: Is p convenient? M = matrix(self.log_grads(p)) cols = M.columns() - convenient_coordinates = [j for j in range(d) if 0 not in cols[j]] + convenient_coordinates = [j for j, c in enumerate(cols) if 0 not in c] if not convenient_coordinates: return (False, 'multiple point but not convenient') From d4a56b42096aa6ae89d4b065ab10bebba939fa65 Mon Sep 17 00:00:00 2001 From: Giorgos Mousa Date: Mon, 26 Feb 2024 20:32:47 +0200 Subject: [PATCH 064/518] Add `# long time` to `topology` --- src/sage/topology/moment_angle_complex.py | 8 ++++---- src/sage/topology/simplicial_complex.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/sage/topology/moment_angle_complex.py b/src/sage/topology/moment_angle_complex.py index 4d900e3ae15..15bf84f94db 100644 --- a/src/sage/topology/moment_angle_complex.py +++ b/src/sage/topology/moment_angle_complex.py @@ -410,7 +410,7 @@ def components(self): sage: product_of_spheres = S3.product(S3) sage: Z.cohomology() {0: 0, 1: 0, 2: 0, 3: Z x Z, 4: 0, 5: 0, 6: Z} - sage: Z.cohomology() == product_of_spheres.cohomology() + sage: Z.cohomology() == product_of_spheres.cohomology() # long time True """ return self._components @@ -592,7 +592,7 @@ def homology(self, dim=None, base_ring=ZZ, cohomology=False, sage: Z = MomentAngleComplex([[0,1,2,3,4,5], [0,1,2,3,4,6], ....: [0,1,2,3,5,7], [0,1,2,3,6,8,9]]) - sage: Z.homology() + sage: Z.homology() # long time {0: 0, 1: 0, 2: 0, @@ -664,7 +664,7 @@ def cohomology(self, dim=None, base_ring=ZZ, algorithm='pari', sage: product_of_spheres = S3.product(S3) sage: Z.cohomology() {0: 0, 1: 0, 2: 0, 3: Z x Z, 4: 0, 5: 0, 6: Z} - sage: Z.cohomology() == product_of_spheres.cohomology() + sage: Z.cohomology() == product_of_spheres.cohomology() # long time True """ return self.homology(dim=dim, cohomology=True, base_ring=base_ring, @@ -718,7 +718,7 @@ def euler_characteristic(self): sage: X = SimplicialComplex([[0,1,2,3,4,5], [0,1,2,3,4,6], ....: [0,1,2,3,5,7], [0,1,2,3,6,8,9]]) sage: M = MomentAngleComplex(X) - sage: M.euler_characteristic() + sage: M.euler_characteristic() # long time 0 sage: Z = MomentAngleComplex([[0,1,2,3,4]]) sage: Z.euler_characteristic() diff --git a/src/sage/topology/simplicial_complex.py b/src/sage/topology/simplicial_complex.py index 36128a5405f..fc21f1bcaf1 100644 --- a/src/sage/topology/simplicial_complex.py +++ b/src/sage/topology/simplicial_complex.py @@ -917,7 +917,7 @@ class SimplicialComplex(Parent, GenericCellComplex): sage: l = designs.ProjectiveGeometryDesign(2, 1, GF(4,name='a')) # needs sage.rings.finite_rings sage: f = lambda S: not any(len(set(S).intersection(x))>2 for x in l) - sage: SimplicialComplex(from_characteristic_function=(f, l.ground_set())) # needs sage.rings.finite_rings + sage: SimplicialComplex(from_characteristic_function=(f, l.ground_set())) # needs sage.rings.finite_rings, long time Simplicial complex with 21 vertices and 168 facets TESTS: @@ -4776,7 +4776,7 @@ def is_partitionable(self, certificate=False, Shellable complexes are partitionable:: - sage: # needs sage.numerical.mip + sage: # needs sage.numerical.mip, long time sage: X = SimplicialComplex([[1,3,5], [1,3,6], [1,4,5], [1,4,6], ....: [2,3,5], [2,3,6], [2,4,5]]) sage: X.is_partitionable() From 3aedb1879d7c3f1947fd5fe552860b5060fcb679 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 26 Feb 2024 20:20:05 -0800 Subject: [PATCH 065/518] src/doc/en/developer/review.rst: Update section 'Release Process' to current practice --- src/doc/en/developer/review.rst | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/doc/en/developer/review.rst b/src/doc/en/developer/review.rst index b2bc38353cb..2dc134248ba 100644 --- a/src/doc/en/developer/review.rst +++ b/src/doc/en/developer/review.rst @@ -180,14 +180,15 @@ Sage Release Manager uses to make releases. Here it is as of 2023: **Beta Release Stage**: For preparing a new beta release or the first release candidate, all positively reviewed PRs with the forthcoming release milestone are considered. PRs that have dependencies not merged yet are ignored. -The Release Manager merges PRs in batches of 10 to 20 PRs, taking the +The Release Manager merges PRs in batches of 10 to 20 PRs, without taking the PR priority into account. If a merge conflict of a PR to the Release Manager's branch occurs, the PR is set back to "needs work" status by the -Release Manager, and the list of the PRs already merged to the Release -Manager's branch is posted. The author of the PR needs to identify -conflicting PRs in the list, make merge commits and declare them as -dependencies, before setting back to "positive review" status. Each batch of -merged PRs then undergoes integration testing. If problems are detected, a +Release Manager. (The author of the PR can try to guess which other +PRs may be causing the conflict, make merge commits and declare them as +dependencies, before setting back to "positive review" status. +Alternatively, the PR author can wait until the next beta release and +resolve the conflict then.) Each batch of +merged PRs undergoes integration testing. If problems are detected, a PR will be set back to "needs work" status and unmerged. When a batch of PRs is ready, the Release Manager closes these PRs and proceeds to the next batch. After a few batches, a new beta release is tagged, pushed to the @@ -206,7 +207,8 @@ release of high quality. Be aware that there is a risk/benefit trade-off in merging a PR. The benefit of merging a PR is the improvement that the PR brings, such as fixing a bug. However, any code change has a risk of introducing unforeseen new problems and thus delaying the release: If a new -issue triggers another release candidate, it delays the release by 1-2 weeks. +issue triggers another release candidate, it can delay the release by up to +2 weeks. Hence developers should use "blocker" priority sparingly and should indicate the rationale on the PR. Though there is no one fixed rule or authority that determines what is appropriate for "blocker" status, From 6280d4d561b22f7b41814d91adbfec942da0dbb3 Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Tue, 27 Feb 2024 05:55:30 +0000 Subject: [PATCH 066/518] apply review changes --- src/sage/rings/ring.pyx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/rings/ring.pyx b/src/sage/rings/ring.pyx index 6e48778625c..15a798ada01 100644 --- a/src/sage/rings/ring.pyx +++ b/src/sage/rings/ring.pyx @@ -441,8 +441,8 @@ cdef class Ring(ParentWithGens): g = gens[0] if len(gens) == 1: try: - # TODO: Rewrite this properly - g = g.gcd(g) # note: we set g = gcd(g, g) to "canonicalize" the generator: make polynomials monic, etc. + # note: we set g = gcd(g, g) to "canonicalize" the generator: make polynomials monic, etc. + g = g.gcd(g) except (AttributeError, NotImplementedError): pass else: From ba37c38a6c1d1a2608052bfd35816f8c2b235906 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 26 Feb 2024 22:16:30 -0800 Subject: [PATCH 067/518] src/doc/en/developer/review.rst: Add link to https://github.com/sagemath/sage-release-management --- src/doc/en/developer/review.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/doc/en/developer/review.rst b/src/doc/en/developer/review.rst index 2dc134248ba..3d9d7b7fef6 100644 --- a/src/doc/en/developer/review.rst +++ b/src/doc/en/developer/review.rst @@ -175,7 +175,7 @@ The release process =================== It is good for developers and reviewers to be aware of the procedure that the -Sage Release Manager uses to make releases. Here it is as of 2023: +Sage Release Manager uses to make releases. Here it is as of 2024: **Beta Release Stage**: For preparing a new beta release or the first release candidate, all positively reviewed PRs with the forthcoming release @@ -224,3 +224,5 @@ that determines what is appropriate for "blocker" status, the Release Manager turns it to the final release. It is tagged with the release milestone, and announced on ``sage-release``. +Release management scripts are maintained in the repository +`sagemath/sage-release-management `_. From a34a3a4dcc11163949dfaa151f433b1c99c217c9 Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Tue, 27 Feb 2024 11:44:01 +0000 Subject: [PATCH 068/518] Start working on random element --- .../polynomial/laurent_polynomial_ring_base.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py index bbb6b9e1fc9..6a6ec585ce7 100644 --- a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py +++ b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py @@ -488,7 +488,7 @@ def krull_dimension(self): """ raise NotImplementedError - def random_element(self, low_degree=-2, high_degree=2, terms=5, choose_degree=False,*args, **kwds): + def random_element(self, low_degree=-2, high_degree=2, terms=5, choose_degree=False, *args, **kwds): """ EXAMPLES:: @@ -497,6 +497,20 @@ def random_element(self, low_degree=-2, high_degree=2, terms=5, choose_degree=Fa ... NotImplementedError """ + # Univariate case we sample a random polynomial of degree + # (high_degree + low_degree) in a polynomial ring over the + # base field, then we shift this polynomial by low_degree. + if self._n == 1: + abs_deg = (high_degree + abs(low_degree)) + f_rand = self._R.random_element(degree=abs_deg, terms=terms) + + # Coerce back to ``self`` and then shift down + f = self(f_rand) + x = self.gen() + f *= x**low_degree + + return f + raise NotImplementedError def is_exact(self): From 54a1376ad4b5a3241eb2f41f80ba453b8146728b Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Tue, 27 Feb 2024 11:55:05 +0000 Subject: [PATCH 069/518] Fix issue 37488 --- 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 e85cf86846f..fb88893ca07 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -2165,7 +2165,7 @@ def isomorphism_to(self, other, *, conjugator=False): ....: break sage: O1 = (b * O0).left_order() sage: iso = O0.isomorphism_to(O1); iso - Ring morphism: ... + Ring ... sage: iso.domain() == O0 True sage: iso.codomain() == O1 From a0bd3e9fa261e3b24359ea1a68e60a23e72d96c4 Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Tue, 27 Feb 2024 12:48:27 +0000 Subject: [PATCH 070/518] implement random sampling for laurent polynomials --- .../laurent_polynomial_ring_base.py | 104 +++++++++++++++--- 1 file changed, 86 insertions(+), 18 deletions(-) diff --git a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py index 6a6ec585ce7..4be6a338bb8 100644 --- a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py +++ b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py @@ -28,6 +28,7 @@ from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.rings.ring import CommutativeRing from sage.structure.parent import Parent +from sage.misc.misc_c import prod class LaurentPolynomialRing_generic(CommutativeRing, Parent): @@ -490,28 +491,95 @@ def krull_dimension(self): def random_element(self, low_degree=-2, high_degree=2, terms=5, choose_degree=False, *args, **kwds): """ + Return a random polynomial of degree at most ``high_degree`` with + lowest degree at most ``low_degree``. + + Internally uses the random sampling from + :meth:`sage.rings.polynomial.multi_polynomial_ring_base.MPolynomialRing_base.random_element` + then shifts this polynomial down to compute the correct degrees. + + INPUT: + + - ``low_degree`` -- non-positive integer (default: -2). + The smallest valuation of monomial in the polynomial + + - ``high_degree`` -- non-negative integer (default: 2). + The maximal valuation of monomial in the polynomial + + - ``terms`` -- number of terms requested (default: 5). If more + terms are requested than exist, then this parameter is + silently reduced to the maximum number of available terms. + + - ``choose_degree`` -- choose degrees of monomials randomly first + rather than monomials uniformly random. + + - ``**kwargs`` -- passed to the random element generator of the base + ring + EXAMPLES:: - sage: LaurentPolynomialRing(QQ, 2, 'x').random_element() - Traceback (most recent call last): - ... - NotImplementedError - """ - # Univariate case we sample a random polynomial of degree - # (high_degree + low_degree) in a polynomial ring over the - # base field, then we shift this polynomial by low_degree. - if self._n == 1: - abs_deg = (high_degree + abs(low_degree)) - f_rand = self._R.random_element(degree=abs_deg, terms=terms) - - # Coerce back to ``self`` and then shift down - f = self(f_rand) - x = self.gen() - f *= x**low_degree + sage: R = LaurentPolynomialRing(QQ, 1, 'x') + sage: f = R.random_element() + sage: f.degree() <= 2 + True + sage: f.monomials()[-1].degree() >= -2 + True - return f + :: - raise NotImplementedError + sage: R = LaurentPolynomialRing(QQ, 1, 'x') + sage: f = R.random_element(-10, 20) + sage: f.degree() <= 20 + True + sage: f.monomials()[-1].degree() >= -10 + True + + :: + + sage: R = LaurentPolynomialRing(ZZ, 3, 'x')p + sage: x = R.gen() + sage: f = R.random_element(-5, 5) + sage: f = R.random_element(-5, 10) + sage: f.degree(x) <= 10 + True + sage: f.degree(x) >= -5 + True + + TESTS:: + + sage: rings = [QQ, ZZ, GF(13), GF(7^3)] + sage: for ring in rings: + ....: R = LaurentPolynomialRing(ring, 3, 'x') + ....: for _ in range(100): + ....: f = R.random_element(-3, 7) + ....: for x in R.gens(): + ....: assert f.degree(x) <= 7 + ....: assert f.degree(x) >= -3 + """ + # Ensure the low_degree is non-positive + if low_degree > 0: + raise ValueError("`low_degree` is expected to be a non-positive integer") + + # Ensure the high_degree is non-negative + if high_degree < 0: + raise ValueError("`low_degree` is expected to be a non-negative integer") + + # First sample a polynomial from the associated polynomial + # ring of `self` of degree `(high_degree + abs(low_degree))` + abs_deg = (high_degree + abs(low_degree)) + f_rand = self._R.random_element(degree=abs_deg, terms=terms, choose_degree=choose_degree, *args, **kwds) + + # Coerce back to ``self``. We now have a polynomial of only + # positive valuation monomials + f = self(f_rand) + + # Finally, shift the entire polynomial down by low_degree + # which will result in a polynomial with highest degree + # high_degree and lowest degree low_degree + monomial = prod(self.gens()) + f *= monomial**low_degree + + return f def is_exact(self): """ From 0e88a871a71c0eb93083b73dd2b2d2de1a65a82f Mon Sep 17 00:00:00 2001 From: Giorgos Mousa Date: Tue, 27 Feb 2024 15:31:13 +0200 Subject: [PATCH 071/518] Suggestions by tscrim --- src/sage/matroids/basis_exchange_matroid.pyx | 9 +- src/sage/matroids/circuits_matroid.pyx | 3 +- src/sage/matroids/matroid.pxd | 2 +- src/sage/matroids/matroid.pyx | 143 +++++++++---------- src/sage/matroids/utilities.py | 14 ++ 5 files changed, 92 insertions(+), 79 deletions(-) diff --git a/src/sage/matroids/basis_exchange_matroid.pyx b/src/sage/matroids/basis_exchange_matroid.pyx index f336b1d77b8..4f0a657cff1 100644 --- a/src/sage/matroids/basis_exchange_matroid.pyx +++ b/src/sage/matroids/basis_exchange_matroid.pyx @@ -1237,8 +1237,8 @@ cdef class BasisExchangeMatroid(Matroid): Return the Whitney numbers of the second kind of the matroid. The Whitney numbers of the second kind are here encoded as a vector - `(W_0, ..., W_r)`, where `W_i` is the number of flats of rank `i`, and - `r` is the rank of the matroid. + `(W_0, \ldots, W_r)`, where `W_i` is the number of flats of rank `i`, + and `r` is the rank of the matroid. OUTPUT: a list of integers @@ -1949,6 +1949,7 @@ cdef class BasisExchangeMatroid(Matroid): sage: M._weak_invariant() == N._weak_invariant() False """ + from sage.matroids.utilities import cmp_elements_key if self._weak_invariant_var is None: if self.full_rank() == 0 or self.full_corank() == 0: self._weak_invariant_var = 0 @@ -1956,8 +1957,8 @@ cdef class BasisExchangeMatroid(Matroid): else: k = min(self.full_rank() - 1, 2) fie, f_vec = self._flat_element_inv(k) - self._weak_invariant_var = hash(tuple([tuple([(f, len(fie[f])) for f in sorted(fie, key=str)]), f_vec])) - self._weak_partition_var = SetSystem(self._E, [fie[f] for f in sorted(fie, key=str)]) + self._weak_invariant_var = hash(tuple([tuple([(f, len(fie[f])) for f in sorted(fie, key=cmp_elements_key)]), f_vec])) + self._weak_partition_var = SetSystem(self._E, [fie[f] for f in sorted(fie, key=cmp_elements_key)]) return self._weak_invariant_var cpdef _weak_partition(self) noexcept: diff --git a/src/sage/matroids/circuits_matroid.pyx b/src/sage/matroids/circuits_matroid.pyx index bbcf6ce8319..77744a2b888 100644 --- a/src/sage/matroids/circuits_matroid.pyx +++ b/src/sage/matroids/circuits_matroid.pyx @@ -612,8 +612,9 @@ cdef class CircuitsMatroid(Matroid): sage: C1 == C2 True """ + from sage.matroids.utilities import cmp_elements_key if ordering is None: - ordering = sorted(self.groundset(), key=str) + ordering = sorted(self.groundset(), key=cmp_elements_key) else: if frozenset(ordering) != self.groundset(): raise ValueError("not an ordering of the groundset") diff --git a/src/sage/matroids/matroid.pxd b/src/sage/matroids/matroid.pxd index 7ab91af018f..12d748a3cba 100644 --- a/src/sage/matroids/matroid.pxd +++ b/src/sage/matroids/matroid.pxd @@ -220,7 +220,7 @@ cdef class Matroid(SageObject): cpdef _internal(self, B) noexcept cpdef _external(self, B) noexcept cpdef tutte_polynomial(self, x=*, y=*) noexcept - cpdef characteristic_polynomial(self, l=*) noexcept + cpdef characteristic_polynomial(self, la=*) noexcept cpdef flat_cover(self, solver=*, verbose=*, integrality_tolerance=*) noexcept # misc diff --git a/src/sage/matroids/matroid.pyx b/src/sage/matroids/matroid.pyx index cf6ea63943a..c5a2280b6f9 100644 --- a/src/sage/matroids/matroid.pyx +++ b/src/sage/matroids/matroid.pyx @@ -179,7 +179,7 @@ a rule leave the regular method alone. These underscored methods are not documented in the reference manual. To see them, within Sage you can create a matroid ``M`` and type ``M._`` followed by -:kbd:`Tab`. Then ``M._rank?`` followed by :kbd:`Tab` will bring up the +:kbd:`Tab`. Then ``M._rank?`` followed by :kbd:`Enter` will bring up the documentation string of the ``_rank()`` method. Creating new Matroid subclasses @@ -355,7 +355,7 @@ MixedIntegerLinearProgram = LazyImport('sage.numerical.mip', 'MixedIntegerLinear from sage.matroids.lean_matrix cimport BinaryMatrix, TernaryMatrix from sage.matroids.set_system cimport SetSystem -from sage.matroids.utilities import newlabel, sanitize_contractions_deletions, spanning_forest, spanning_stars +from sage.matroids.utilities import newlabel, sanitize_contractions_deletions, spanning_forest, spanning_stars, cmp_elements_key # On some systems, macros "minor()" and "major()" are defined in system header @@ -1079,10 +1079,11 @@ cdef class Matroid(SageObject): INPUT: - - ``N`` -- An instance of a ``Matroid`` object, - - ``certificate`` -- boolean (Default: ``False``) If ``True``, returns - ``True, (X, Y, dic) where ``N`` is isomorphic to ``self.minor(X, Y)``, - and ``dic`` is an isomorphism between ``N`` and ``self.minor(X, Y)``. + - ``N`` -- an instance of a :class:`Matroid` object + - ``certificate`` -- boolean (default: ``False``); if ``True``, returns + ``True, (X, Y, dic)`` where ``N`` is isomorphic to + ``self.minor(X, Y)``, and ``dic`` is an isomorphism between ``N`` and + ``self.minor(X, Y)`` OUTPUT: boolean or tuple @@ -2304,7 +2305,7 @@ cdef class Matroid(SageObject): .. SEEALSO:: - :meth:`M.circuit() ` + :meth:`~sage.matroids.matroid.Matroid.circuit` EXAMPLES:: @@ -2538,6 +2539,10 @@ cdef class Matroid(SageObject): :meth:`M.basis() ` + ALGORITHM: + + Test all subsets of the groundset of cardinality ``self.full_rank()``. + EXAMPLES:: sage: M = matroids.Uniform(2, 4) @@ -2545,10 +2550,6 @@ cdef class Matroid(SageObject): [] sage: [sorted(X) for X in matroids.catalog.P6().nonbases_iterator()] [['a', 'b', 'c']] - - ALGORITHM: - - Test all subsets of the groundset of cardinality ``self.full_rank()`` """ for X in combinations(self.groundset(), self.full_rank()): if self._rank(X) < len(X): @@ -2560,7 +2561,7 @@ cdef class Matroid(SageObject): INPUT: - - ``r`` -- a nonnegative integer. + - ``r`` -- a nonnegative integer EXAMPLES:: @@ -2589,7 +2590,11 @@ cdef class Matroid(SageObject): INPUT: - - ``r`` -- a nonnegative integer. + - ``r`` -- a nonnegative integer + + ALGORITHM: + + Test all subsets of the groundset of cardinality ``r``. EXAMPLES:: @@ -2600,10 +2605,6 @@ cdef class Matroid(SageObject): ....: matroids.catalog.Vamos().dependent_r_sets_iterator(4)]) [['a', 'b', 'c', 'd'], ['a', 'b', 'e', 'f'], ['a', 'b', 'g', 'h'], ['c', 'd', 'e', 'f'], ['e', 'f', 'g', 'h']] - - ALGORITHM: - - Test all subsets of the groundset of cardinality ``r`` """ for X in combinations(self.groundset(), r): X = frozenset(X) @@ -2645,16 +2646,16 @@ cdef class Matroid(SageObject): A *basis* is a maximal independent set. + ALGORITHM: + + Test all subsets of the groundset of cardinality ``self.full_rank()``. + EXAMPLES:: sage: M = matroids.Uniform(2, 4) sage: sorted([sorted(X) for X in M.bases_iterator()]) [[0, 1], [0, 2], [0, 3], [1, 2], [1, 3], [2, 3]] - ALGORITHM: - - Test all subsets of the groundset of cardinality ``self.full_rank()`` - .. SEEALSO:: :meth:`M.independent_r_sets() ` @@ -2746,7 +2747,11 @@ cdef class Matroid(SageObject): INPUT: - - ``r`` -- a nonnegative integer. + - ``r`` -- a nonnegative integer + + ALGORITHM: + + Test all subsets of the groundset of cardinality ``r``. EXAMPLES:: @@ -2759,10 +2764,6 @@ cdef class Matroid(SageObject): sage: frozenset({'a', 'c', 'e'}) in S True - ALGORITHM: - - Test all subsets of the groundset of cardinality ``r`` - .. SEEALSO:: :meth:`M.independent_sets() ` @@ -2782,7 +2783,7 @@ cdef class Matroid(SageObject): INPUT: - - ``r`` -- a nonnegative integer. + - ``r`` -- a nonnegative integer EXAMPLES:: @@ -2907,7 +2908,7 @@ cdef class Matroid(SageObject): INPUT: - - ``r`` -- a nonnegative integer. + - ``r`` -- a nonnegative integer OUTPUT: a SetSystem @@ -3072,7 +3073,7 @@ cdef class Matroid(SageObject): [[1, 2], [1, 4], [2, 3, 4]] """ if ordering is None: - ordering = sorted(self.groundset(), key=str) + ordering = sorted(self.groundset(), key=cmp_elements_key) else: orderset = frozenset(ordering) if len(orderset) != len(self.groundset()) or orderset != self.groundset(): @@ -3131,12 +3132,12 @@ cdef class Matroid(SageObject): minimal-removal convention, while the implementation is not modified from the published algorithm. """ - if len(self.loops()) > 0: + if self.loops(): return [] cdef list rev_order if ordering is None: - rev_order = sorted(self.groundset(), key=str, reverse=True) + rev_order = sorted(self.groundset(), key=cmp_elements_key, reverse=True) else: if frozenset(ordering) != self.groundset(): raise ValueError("not an ordering of the groundset") @@ -3202,10 +3203,10 @@ cdef class Matroid(SageObject): Simplicial complex with vertex set (1, 2, 3, 4, 5) and facets {(1, 3, 5), (2, 3, 5), (2, 4, 5), (3, 4, 5)} """ - if len(self.loops()) == 0: + if not self.loops(): if ordering is None: - rev_order = sorted(self.groundset(), key=str, reverse=True) + rev_order = sorted(self.groundset(), key=cmp_elements_key, reverse=True) else: if frozenset(ordering) != self.groundset(): raise ValueError("not an ordering of the groundset") @@ -3244,12 +3245,12 @@ cdef class Matroid(SageObject): - ``R`` -- the base ring - ``ordering`` -- (optional) an ordering of the groundset - - ``invariant`` -- (optional, default: None) either a semigroup ``G`` - whose ``__call__`` acts on the groundset, or pair ``(G, action)`` - where ``G`` is a semigroup and ``action`` is a function - ``action(g,e)`` which takes a pair of a group element and a grounset - element and returns the groundset element which is the result of - ``e`` acted upon by ``g`` + - ``invariant`` -- (optional) either a semigroup ``G`` whose + ``__call__`` acts on the groundset, or pair ``(G, action)`` where + ``G`` is a semigroup and ``action`` is a function ``action(g,e)`` + which takes a pair of a group element and a grounset element and + returns the groundset element which is the result of ``e`` acted upon + by ``g`` .. SEEALSO:: @@ -3339,10 +3340,10 @@ cdef class Matroid(SageObject): convert = {ind: i for i, ind in enumerate(self.groundset())} vertices = [] for B in self.bases_iterator(): - sum = 0 + total = 0 for i in B: - sum += vector_e[convert[i]] - vertices += [sum] + total += vector_e[convert[i]] + vertices += [total] return Polyhedron(vertices) cpdef independence_matroid_polytope(self) noexcept: @@ -3385,12 +3386,12 @@ cdef class Matroid(SageObject): ambient = FreeModule(ZZ, n) vector_e = ambient.basis() convert = {ind: i for i, ind in enumerate(self.groundset())} - vertices = [] + cdef list lst, vertices = [] for IS in self.independent_sets_iterator(): lst = [] for i in IS: - lst += [vector_e[convert[i]]] - vertices += [ambient.sum(lst)] + lst.append(vector_e[convert[i]]) + vertices.append(ambient.sum(lst)) return Polyhedron(vertices) # isomorphism and equality @@ -3405,7 +3406,7 @@ cdef class Matroid(SageObject): INPUT: - - ``other`` -- a matroid, + - ``other`` -- a matroid - ``certificate`` -- boolean (default: ``False``) OUTPUT: boolean, and, if ``certificate = True``, a dictionary or @@ -3445,8 +3446,8 @@ cdef class Matroid(SageObject): INPUT: - - ``other`` -- A matroid, - - ``certificate`` -- boolean (default: ``False``). + - ``other`` -- a matroid + - ``certificate`` -- boolean (default: ``False``) OUTPUT: boolean, and, if ``certificate=True``, a dictionary giving the isomorphism or ``None`` @@ -4216,10 +4217,11 @@ cdef class Matroid(SageObject): INPUT: - - ``N`` -- An instance of a ``Matroid`` object, - - ``certificate`` -- boolean (Default: ``False``) If ``True``, returns - ``True, (X, Y, dic) where ``N`` is isomorphic to ``self.minor(X, Y)``, - and ``dic`` is an isomorphism between ``N`` and ``self.minor(X, Y)``. + - ``N`` -- an instance of a :class:`Matroid` object + - ``certificate`` -- boolean (default: ``False``); if ``True``, returns + ``True, (X, Y, dic)`` where ``N`` is isomorphic to + ``self.minor(X, Y)``, and ``dic`` is an isomorphism between ``N`` and + ``self.minor(X, Y)`` OUTPUT: boolean or tuple @@ -6313,9 +6315,9 @@ cdef class Matroid(SageObject): if basis is None: basis = self.basis() - basis = sorted(basis, key=str) + basis = sorted(basis, key=cmp_elements_key) bdx = {basis[i]: i for i in range(len(basis))} - E = sorted(self.groundset(), key=str) + E = sorted(self.groundset(), key=cmp_elements_key) idx = {Ei: i for i, Ei in enumerate(E)} A = TernaryMatrix(len(basis), len(E)) for e in basis: @@ -7399,8 +7401,7 @@ cdef class Matroid(SageObject): INPUT: - ``other`` -- a matroid with the same groundset as ``self`` - - ``Y`` -- an common independent set of ``self`` and ``other`` of size - `k` + - ``Y`` -- an common independent set of ``self`` and ``other`` of size `k` OUTPUT: @@ -7626,7 +7627,7 @@ cdef class Matroid(SageObject): N = self.groundset() - B A = set() for e in B: - if min(self._cocircuit(N | set([e])), key=str) == e: + if min(self._cocircuit(N | set([e])), key=cmp_elements_key) == e: A.add(e) return A @@ -7663,7 +7664,7 @@ cdef class Matroid(SageObject): N = self.groundset() - B A = set() for e in N: - if min(self._circuit(B | set([e])), key=str) == e: + if min(self._circuit(B | set([e])), key=cmp_elements_key) == e: A.add(e) return A @@ -7728,7 +7729,7 @@ cdef class Matroid(SageObject): T = T(a, b) return T - cpdef characteristic_polynomial(self, l=None) noexcept: + cpdef characteristic_polynomial(self, la=None) noexcept: r""" Return the characteristic polynomial of the matroid. @@ -7745,10 +7746,10 @@ cdef class Matroid(SageObject): INPUT: - - ``l`` -- a variable or numerical argument (optional) + - ``la`` -- a variable or numerical argument (optional) OUTPUT: the characteristic polynomial, `\chi_M(\lambda)`, where - `\lambda` is substituted with any value provided as input. + `\lambda` is substituted with any value provided as input EXAMPLES:: @@ -7762,18 +7763,14 @@ cdef class Matroid(SageObject): .. SEEALSO:: - :meth:`whitney_numbers() ` + :meth:`~sage.matroids.matroid.Matroid.whitney_numbers` """ - val = l R = ZZ['l'] - l = R._first_ngens(1)[0] - chi = R(0) - cdef int r = self.rank() cdef list w = self.whitney_numbers() - for i in range(len(w)): - chi += w[i] * l**(r-i) - if val is not None: - return chi(val) + w.reverse() + chi = R(w) + if la is not None: + return chi(la) return chi cpdef flat_cover(self, solver=None, verbose=0, integrality_tolerance=1e-3) noexcept: @@ -7919,7 +7916,7 @@ cdef class Matroid(SageObject): # Create the ambient polynomial ring from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing try: - names = ['A{}'.format(''.join(str(x) for x in sorted(F, key=str))) for F in flats] + names = ['A{}'.format(''.join(str(x) for x in sorted(F, key=cmp_elements_key))) for F in flats] P = PolynomialRing(R, names) except ValueError: # variables are not proper names P = PolynomialRing(R, 'A', len(flats)) @@ -8119,7 +8116,7 @@ cdef class Matroid(SageObject): cdef list facets = [] for S in self.no_broken_circuits_sets_iterator(ordering): if len(S) == r: - facets += [S] + facets.append(S) return SimplicialComplex(facets, maximality_check=False) cpdef automorphism_group(self) noexcept: @@ -8273,7 +8270,7 @@ cdef class Matroid(SageObject): # add the facet DM.add_face([f'L{i}' for i in I] + - [f'R{sorted(F, key=str)}' for F in c]) + [f'R{sorted(F)}' for F in c]) return DM def union(self, matroids): diff --git a/src/sage/matroids/utilities.py b/src/sage/matroids/utilities.py index b42011533ca..a0cf14f5276 100644 --- a/src/sage/matroids/utilities.py +++ b/src/sage/matroids/utilities.py @@ -800,3 +800,17 @@ def split_vertex(G, u, v=None, edges=None): # This modifies the graph without needing to return anything return + + +def cmp_elements_key(x): + """ + A helper function to compare elements which may be integers or strings. + + EXAMPLES:: + + sage: from sage.matroids.utilities import cmp_elements_key + sage: l = ['a', 'b', 1, 3, 2, 10, 111, 100, 'c', 'aa'] + sage: sorted(l, key=cmp_elements_key) + [1, 2, 3, 10, 100, 111, 'a', 'aa', 'b', 'c'] + """ + return (isinstance(x, str), x) From df4980f15369a298efde1799e3709c1771260dcd Mon Sep 17 00:00:00 2001 From: Giacomo Pope <44242839+GiacomoPope@users.noreply.github.com> Date: Tue, 27 Feb 2024 15:05:08 +0100 Subject: [PATCH 072/518] Fix stupid typo --- src/sage/rings/polynomial/laurent_polynomial_ring_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py index 4be6a338bb8..b8c7ba49298 100644 --- a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py +++ b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py @@ -536,7 +536,7 @@ def random_element(self, low_degree=-2, high_degree=2, terms=5, choose_degree=Fa :: - sage: R = LaurentPolynomialRing(ZZ, 3, 'x')p + sage: R = LaurentPolynomialRing(ZZ, 3, 'x') sage: x = R.gen() sage: f = R.random_element(-5, 5) sage: f = R.random_element(-5, 10) From 8cda862d750e4fd0bdbe8c49fff251ccd7b38d60 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 27 Feb 2024 07:46:01 -0800 Subject: [PATCH 073/518] src/doc/en/developer/review.rst: Remove mention of dependencies (not handled in current scripts), remove mention of priorities in beta stage --- src/doc/en/developer/review.rst | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/doc/en/developer/review.rst b/src/doc/en/developer/review.rst index 3d9d7b7fef6..0a55393955b 100644 --- a/src/doc/en/developer/review.rst +++ b/src/doc/en/developer/review.rst @@ -179,10 +179,9 @@ Sage Release Manager uses to make releases. Here it is as of 2024: **Beta Release Stage**: For preparing a new beta release or the first release candidate, all positively reviewed PRs with the forthcoming release -milestone are considered. PRs that have dependencies not merged yet are ignored. -The Release Manager merges PRs in batches of 10 to 20 PRs, without taking the -PR priority into account. If a merge conflict of a PR to the Release -Manager's branch occurs, the PR is set back to "needs work" status by the +milestone are considered. The Release Manager merges PRs in batches of +10 to 20 PRs. If a merge conflict of a PR to the Release Manager's +branch occurs, the PR is set back to "needs work" status by the Release Manager. (The author of the PR can try to guess which other PRs may be causing the conflict, make merge commits and declare them as dependencies, before setting back to "positive review" status. From 70453764c6b551b8460a954f5ba5361ae9d0b602 Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Tue, 27 Feb 2024 16:51:22 +0000 Subject: [PATCH 074/518] More randomness in random testing --- .../rings/polynomial/laurent_polynomial_ring_base.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py index b8c7ba49298..c9bc9d72001 100644 --- a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py +++ b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py @@ -549,12 +549,14 @@ def random_element(self, low_degree=-2, high_degree=2, terms=5, choose_degree=Fa sage: rings = [QQ, ZZ, GF(13), GF(7^3)] sage: for ring in rings: - ....: R = LaurentPolynomialRing(ring, 3, 'x') + ....: d = randint(1, 5) + ....: R = LaurentPolynomialRing(ring, d, 'x') ....: for _ in range(100): - ....: f = R.random_element(-3, 7) + ....: n, m = randint(0, 10), randint(0, 10) + ....: f = R.random_element(-n, m) ....: for x in R.gens(): - ....: assert f.degree(x) <= 7 - ....: assert f.degree(x) >= -3 + ....: assert f.degree(x) <= m + ....: assert f.degree(x) >= -n """ # Ensure the low_degree is non-positive if low_degree > 0: From fe312a1d7d1990907b4e3375913bc943054d5913 Mon Sep 17 00:00:00 2001 From: Giacomo Pope <44242839+GiacomoPope@users.noreply.github.com> Date: Tue, 27 Feb 2024 17:53:47 +0000 Subject: [PATCH 075/518] Apply suggestions from code review Co-authored-by: grhkm21 <83517584+grhkm21@users.noreply.github.com> --- .../polynomial/laurent_polynomial_ring_base.py | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py index c9bc9d72001..e1621e35cf2 100644 --- a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py +++ b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py @@ -491,8 +491,8 @@ def krull_dimension(self): def random_element(self, low_degree=-2, high_degree=2, terms=5, choose_degree=False, *args, **kwds): """ - Return a random polynomial of degree at most ``high_degree`` with - lowest degree at most ``low_degree``. + Return a random polynomial of degree at most ``high_degree`` and + lowest degree at least ``low_degree``. Internally uses the random sampling from :meth:`sage.rings.polynomial.multi_polynomial_ring_base.MPolynomialRing_base.random_element` @@ -506,14 +506,7 @@ def random_element(self, low_degree=-2, high_degree=2, terms=5, choose_degree=Fa - ``high_degree`` -- non-negative integer (default: 2). The maximal valuation of monomial in the polynomial - - ``terms`` -- number of terms requested (default: 5). If more - terms are requested than exist, then this parameter is - silently reduced to the maximum number of available terms. - - - ``choose_degree`` -- choose degrees of monomials randomly first - rather than monomials uniformly random. - - - ``**kwargs`` -- passed to the random element generator of the base + - ``*args, **kwds`` -- passed to the random element generator of the base ring EXAMPLES:: @@ -568,7 +561,7 @@ def random_element(self, low_degree=-2, high_degree=2, terms=5, choose_degree=Fa # First sample a polynomial from the associated polynomial # ring of `self` of degree `(high_degree + abs(low_degree))` - abs_deg = (high_degree + abs(low_degree)) + abs_deg = (high_degree - low_degree) f_rand = self._R.random_element(degree=abs_deg, terms=terms, choose_degree=choose_degree, *args, **kwds) # Coerce back to ``self``. We now have a polynomial of only From 7eaddc4bb38b69ca90f70e7eab5475fb7b1bdcb2 Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Tue, 27 Feb 2024 18:38:03 +0000 Subject: [PATCH 076/518] Review suggestions and more doctests --- .../laurent_polynomial_ring_base.py | 61 ++++++++++++------- 1 file changed, 39 insertions(+), 22 deletions(-) diff --git a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py index e1621e35cf2..db110eefa08 100644 --- a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py +++ b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py @@ -500,14 +500,22 @@ def random_element(self, low_degree=-2, high_degree=2, terms=5, choose_degree=Fa INPUT: - - ``low_degree`` -- non-positive integer (default: -2). + - ``low_degree`` -- integer (default: ``-2``). The smallest valuation of monomial in the polynomial - - ``high_degree`` -- non-negative integer (default: 2). + - ``high_degree`` -- integer (default: ``2``). The maximal valuation of monomial in the polynomial - - ``*args, **kwds`` -- passed to the random element generator of the base - ring + - ``terms`` -- number of terms requested (default: ``5``). + If more terms are requested than exist, then this parameter is + silently reduced to the maximum number of available terms. + + - ``choose_degree`` -- bool(default: ``False``) + choose degrees of monomials randomly first rather than monomials + uniformly random. + + - ``*args, **kwds`` -- passed to the random element generator + of the underlying polynomial ring and respective base ring EXAMPLES:: @@ -515,27 +523,37 @@ def random_element(self, low_degree=-2, high_degree=2, terms=5, choose_degree=Fa sage: f = R.random_element() sage: f.degree() <= 2 True - sage: f.monomials()[-1].degree() >= -2 + sage: f.degree() >= -2 + True + sage: f.parent() is R True :: - sage: R = LaurentPolynomialRing(QQ, 1, 'x') + sage: R = LaurentPolynomialRing(ZZ, 2, 'x') sage: f = R.random_element(-10, 20) sage: f.degree() <= 20 True - sage: f.monomials()[-1].degree() >= -10 - True + sage: tuple(f.degree(x) >= -10 for x in R.gens()) + (True, True) :: - sage: R = LaurentPolynomialRing(ZZ, 3, 'x') - sage: x = R.gen() + sage: R = LaurentPolynomialRing(GF(13), 3, 'x') sage: f = R.random_element(-5, 5) sage: f = R.random_element(-5, 10) - sage: f.degree(x) <= 10 + sage: tuple(f.degree(x) <= 10 for x in R.gens()) + (True, True, True) + sage: tuple(f.degree(x) >= -5 for x in R.gens()) + (True, True, True) + + :: + + sage: R = LaurentPolynomialRing(QQ, 5, 'x') + sage: f = R.random_element(-3, 5, terms=6, choose_degree=True) + sage: f.parent() is R True - sage: f.degree(x) >= -5 + sage: len(list(f)) <= 6 True TESTS:: @@ -545,19 +563,18 @@ def random_element(self, low_degree=-2, high_degree=2, terms=5, choose_degree=Fa ....: d = randint(1, 5) ....: R = LaurentPolynomialRing(ring, d, 'x') ....: for _ in range(100): - ....: n, m = randint(0, 10), randint(0, 10) - ....: f = R.random_element(-n, m) + ....: n, m = randint(-10, -1), randint(1, 10) + ....: if m < n: + ....: m, n = n, m + ....: f = R.random_element(n, m) + ....: if f.is_zero(): continue # the zero polynomial is defined to have degree -1 ....: for x in R.gens(): ....: assert f.degree(x) <= m - ....: assert f.degree(x) >= -n + ....: assert f.degree(x) >= n """ - # Ensure the low_degree is non-positive - if low_degree > 0: - raise ValueError("`low_degree` is expected to be a non-positive integer") - - # Ensure the high_degree is non-negative - if high_degree < 0: - raise ValueError("`low_degree` is expected to be a non-negative integer") + # Ensure the degree parameters are sensible + if high_degree < low_degree: + raise ValueError("`high_degree` must be greater than or equal to `low_degree`") # First sample a polynomial from the associated polynomial # ring of `self` of degree `(high_degree + abs(low_degree))` From 3c911d21c0769265ea67557e3c459787c3d14fa3 Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Tue, 27 Feb 2024 18:40:19 +0000 Subject: [PATCH 077/518] space --- src/sage/rings/polynomial/laurent_polynomial_ring_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py index db110eefa08..8c7b8b05aff 100644 --- a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py +++ b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py @@ -510,7 +510,7 @@ def random_element(self, low_degree=-2, high_degree=2, terms=5, choose_degree=Fa If more terms are requested than exist, then this parameter is silently reduced to the maximum number of available terms. - - ``choose_degree`` -- bool(default: ``False``) + - ``choose_degree`` -- bool (default: ``False``) choose degrees of monomials randomly first rather than monomials uniformly random. From 4ed19cd6d8348a9ba93dbc46e5d45843f0cd1585 Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Tue, 27 Feb 2024 18:56:45 +0000 Subject: [PATCH 078/518] more randomness in testing --- src/sage/rings/polynomial/laurent_polynomial_ring_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py index 8c7b8b05aff..77619ffa99e 100644 --- a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py +++ b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py @@ -563,7 +563,7 @@ def random_element(self, low_degree=-2, high_degree=2, terms=5, choose_degree=Fa ....: d = randint(1, 5) ....: R = LaurentPolynomialRing(ring, d, 'x') ....: for _ in range(100): - ....: n, m = randint(-10, -1), randint(1, 10) + ....: n, m = randint(-10, 10), randint(1, 10) ....: if m < n: ....: m, n = n, m ....: f = R.random_element(n, m) From 0495d4efc58528a9fd84035559b8966cff285448 Mon Sep 17 00:00:00 2001 From: Giacomo Pope <44242839+GiacomoPope@users.noreply.github.com> Date: Wed, 28 Feb 2024 10:42:14 +0000 Subject: [PATCH 079/518] Apply suggestions from code review Co-authored-by: Travis Scrimshaw --- .../laurent_polynomial_ring_base.py | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py index 77619ffa99e..d9a5657841d 100644 --- a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py +++ b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py @@ -494,25 +494,26 @@ def random_element(self, low_degree=-2, high_degree=2, terms=5, choose_degree=Fa Return a random polynomial of degree at most ``high_degree`` and lowest degree at least ``low_degree``. - Internally uses the random sampling from - :meth:`sage.rings.polynomial.multi_polynomial_ring_base.MPolynomialRing_base.random_element` - then shifts this polynomial down to compute the correct degrees. + Internally uses the random sampling from the corresponding + polynomial ring then shifts this polynomial down to compute + the correct degrees. INPUT: - - ``low_degree`` -- integer (default: ``-2``). - The smallest valuation of monomial in the polynomial + - ``min_valuation`` -- integer (default: ``-2``); the + minimal allowed valuation of the polynomial - - ``high_degree`` -- integer (default: ``2``). - The maximal valuation of monomial in the polynomial + - ``max_degree`` -- integer (default: ``2``); the + maximal allowed degree of the polynomial - - ``terms`` -- number of terms requested (default: ``5``). - If more terms are requested than exist, then this parameter is - silently reduced to the maximum number of available terms. + - ``terms`` -- (default: ``5``) number of terms requested; + if more terms are requested than exist (from specifying + the valuation and degree), then this parameter is silently + reduced to the maximum number of available terms - - ``choose_degree`` -- bool (default: ``False``) - choose degrees of monomials randomly first rather than monomials - uniformly random. + - ``choose_degree`` -- bool (default: ``False``) choose + degrees of monomials randomly first rather than monomials + uniformly random - ``*args, **kwds`` -- passed to the random element generator of the underlying polynomial ring and respective base ring From 7b2165a97cd112c09f4ab3b7c7a8ba247ad01762 Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Wed, 28 Feb 2024 10:57:33 +0000 Subject: [PATCH 080/518] Add more doctests --- .../laurent_polynomial_ring_base.py | 84 ++++++++++--------- 1 file changed, 43 insertions(+), 41 deletions(-) diff --git a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py index d9a5657841d..19eafdb9b90 100644 --- a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py +++ b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py @@ -489,10 +489,10 @@ def krull_dimension(self): """ raise NotImplementedError - def random_element(self, low_degree=-2, high_degree=2, terms=5, choose_degree=False, *args, **kwds): + def random_element(self, min_valuation=-2, high_degree=2, *args, **kwds): """ Return a random polynomial of degree at most ``high_degree`` and - lowest degree at least ``low_degree``. + lowest valuation at least ``min_valuation``. Internally uses the random sampling from the corresponding polynomial ring then shifts this polynomial down to compute @@ -506,91 +506,93 @@ def random_element(self, low_degree=-2, high_degree=2, terms=5, choose_degree=Fa - ``max_degree`` -- integer (default: ``2``); the maximal allowed degree of the polynomial - - ``terms`` -- (default: ``5``) number of terms requested; - if more terms are requested than exist (from specifying - the valuation and degree), then this parameter is silently - reduced to the maximum number of available terms - - - ``choose_degree`` -- bool (default: ``False``) choose - degrees of monomials randomly first rather than monomials - uniformly random - - ``*args, **kwds`` -- passed to the random element generator of the underlying polynomial ring and respective base ring EXAMPLES:: - sage: R = LaurentPolynomialRing(QQ, 1, 'x') - sage: f = R.random_element() + sage: L. = LaurentPolynomialRing(QQ) + sage: f = L.random_element() sage: f.degree() <= 2 True sage: f.degree() >= -2 True - sage: f.parent() is R + sage: f.parent() is L True :: - sage: R = LaurentPolynomialRing(ZZ, 2, 'x') - sage: f = R.random_element(-10, 20) + sage: L = LaurentPolynomialRing(ZZ, 2, 'x') + sage: f = L.random_element(-10, 20) sage: f.degree() <= 20 True - sage: tuple(f.degree(x) >= -10 for x in R.gens()) + sage: tuple(f.degree(x) >= -10 for x in L.gens()) (True, True) :: - sage: R = LaurentPolynomialRing(GF(13), 3, 'x') - sage: f = R.random_element(-5, 5) - sage: f = R.random_element(-5, 10) - sage: tuple(f.degree(x) <= 10 for x in R.gens()) + sage: L = LaurentPolynomialRing(GF(13), 3, 'x') + sage: f = L.random_element(-5, 5) + sage: f = L.random_element(-5, 10) + sage: tuple(f.degree(x) <= 10 for x in L.gens()) (True, True, True) - sage: tuple(f.degree(x) >= -5 for x in R.gens()) + sage: tuple(f.degree(x) >= -5 for x in L.gens()) (True, True, True) :: - sage: R = LaurentPolynomialRing(QQ, 5, 'x') - sage: f = R.random_element(-3, 5, terms=6, choose_degree=True) - sage: f.parent() is R - True - sage: len(list(f)) <= 6 - True + sage: L = LaurentPolynomialRing(RR, 2, 'x') + sage: f = L.random_element() + sage: f = L.random_element() + sage: tuple(f.degree(x) <= 2 for x in L.gens()) + (True, True) + sage: tuple(f.degree(x) >= -2 for x in L.gens()) + (True, True) + + :: + + sage: L = LaurentPolynomialRing(RR, 5, 'x') + sage: f = L.random_element(-1, 1) + sage: f = L.random_element(-1, 1) + sage: tuple(f.degree(x) <= 1 for x in L.gens()) + (True, True, True, True, True) + sage: tuple(f.degree(x) >= -1 for x in L.gens()) + (True, True, True, True, True) TESTS:: - sage: rings = [QQ, ZZ, GF(13), GF(7^3)] + sage: rings = [RR, QQ, ZZ, GF(13), GF(7^3)] sage: for ring in rings: ....: d = randint(1, 5) - ....: R = LaurentPolynomialRing(ring, d, 'x') + ....: L = LaurentPolynomialRing(ring, d, 'x') ....: for _ in range(100): ....: n, m = randint(-10, 10), randint(1, 10) ....: if m < n: ....: m, n = n, m - ....: f = R.random_element(n, m) + ....: f = L.random_element(n, m) ....: if f.is_zero(): continue # the zero polynomial is defined to have degree -1 - ....: for x in R.gens(): + ....: for x in L.gens(): ....: assert f.degree(x) <= m ....: assert f.degree(x) >= n """ # Ensure the degree parameters are sensible - if high_degree < low_degree: - raise ValueError("`high_degree` must be greater than or equal to `low_degree`") + if high_degree < min_valuation: + raise ValueError("`high_degree` must be greater than or equal to `min_valuation`") # First sample a polynomial from the associated polynomial - # ring of `self` of degree `(high_degree + abs(low_degree))` - abs_deg = (high_degree - low_degree) - f_rand = self._R.random_element(degree=abs_deg, terms=terms, choose_degree=choose_degree, *args, **kwds) + # ring of `self` of degree `(high_degree - min_valuation)` + abs_deg = (high_degree - min_valuation) + f_rand = self._R.random_element(degree=abs_deg, *args, **kwds) - # Coerce back to ``self``. We now have a polynomial of only + # Coerce back to `self`. We now have a polynomial of only # positive valuation monomials f = self(f_rand) - # Finally, shift the entire polynomial down by low_degree + # Finally, shift the entire polynomial down by min_valuation # which will result in a polynomial with highest degree - # high_degree and lowest degree low_degree + # high_degree and lowest valuation min_valuation monomial = prod(self.gens()) - f *= monomial**low_degree + f *= monomial**min_valuation return f From 5cad57b6c1d387b045a3aabbbaf5596c1e35df95 Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Wed, 28 Feb 2024 11:01:27 +0000 Subject: [PATCH 081/518] QQbar instead of RR --- src/sage/rings/polynomial/laurent_polynomial_ring_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py index 19eafdb9b90..311042380af 100644 --- a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py +++ b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py @@ -551,7 +551,7 @@ def random_element(self, min_valuation=-2, high_degree=2, *args, **kwds): :: - sage: L = LaurentPolynomialRing(RR, 5, 'x') + sage: L = LaurentPolynomialRing(QQbar, 5, 'x') sage: f = L.random_element(-1, 1) sage: f = L.random_element(-1, 1) sage: tuple(f.degree(x) <= 1 for x in L.gens()) From ba91c00d9daf7240faa331b6950a7d0ca300a04b Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Wed, 28 Feb 2024 15:01:57 +0100 Subject: [PATCH 082/518] remove zombie code --- src/sage/matrix/matrix_modn_sparse.pyx | 299 ------------------------- 1 file changed, 299 deletions(-) diff --git a/src/sage/matrix/matrix_modn_sparse.pyx b/src/sage/matrix/matrix_modn_sparse.pyx index f6c505b5965..841f870c2bd 100644 --- a/src/sage/matrix/matrix_modn_sparse.pyx +++ b/src/sage/matrix/matrix_modn_sparse.pyx @@ -870,302 +870,3 @@ cdef class Matrix_modn_sparse(Matrix_sparse): return d else: raise ValueError("no algorithm '%s'"%algorithm) - - def _solve_right_nonsingular_square(self, B, algorithm=None, check_rank=False): - r""" - If self is a matrix `A`, then this function returns a - vector or matrix `X` such that `A X = B`. If - `B` is a vector then `X` is a vector and if - `B` is a matrix, then `X` is a matrix. - - .. NOTE:: - - DEPRECATED. In Sage one can also write ``A \ B`` for - ``A.solve_right(B)``, i.e., Sage implements the "the - MATLAB/Octave backslash operator". - - INPUT: - - - - ``B`` - a matrix or vector - - - ``algorithm`` - one of the following: - - - ``'linbox'`` or ``'linbox_default'`` - (default) use LinBox - and let it chooses the appropriate algorithm - - - ``linbox_dense_elimination'`` - use LinBox dense elimination - - - ``'linbox_sparse_elimination'`` - use LinBox sparse elimination - - - ``'linbox_ blackbox'`` - LinBox via a Blackbox algorithm - - - ``'linbox_wiedemann'`` - use LinBox implementation of - Wiedemann's algorithm - - - ``'generic'`` - use the Sage generic implementation - (via inversion) - - - ``check_rank`` - whether to check that the rank is maximal - - OUTPUT: a matrix or vector - - EXAMPLES:: - - sage: A = matrix(ZZ, 3, [1,2,3,-1,2,5,2,3,1], sparse=True) - sage: b = vector(ZZ, [1,2,3]) - sage: x = A.solve_right(b) - sage: x - (-13/12, 23/12, -7/12) - sage: A * x - (1, 2, 3) - - sage: u = matrix(ZZ, 3, 2, [0,1,1,1,0,2]) - sage: x = A.solve_right(u) - sage: x - [-7/12 -1/6] - [ 5/12 5/6] - [-1/12 -1/6] - sage: A * x - [0 1] - [1 1] - [0 2] - """ - if check_rank and self.rank() < self.nrows(): - from sage.matrix.matrix2 import NotFullRankError - raise NotFullRankError - - if self.base_ring() != B.base_ring(): - B = B.change_ring(self.base_ring()) - if self.nrows() != B.nrows(): - raise ValueError("input matrices must have the same number of rows.") - - if algorithm == "generic": - return Matrix_sparse.solve_right(self, B) - else: - if isinstance(B, Matrix): - from sage.matrix.special import diagonal_matrix - m, d = self._solve_matrix_linbox(B, algorithm) - return m * diagonal_matrix([QQ((1,x)) for x in d]) - else: - v, d = self._solve_vector_linbox(B, algorithm) - return v / d - - def _solve_vector_linbox(self, v, algorithm=None): - r""" - Return a pair ``(a, d)`` so that ``d * b = m * a`` - - If there is no solution a ``ValueError`` is raised. - - INPUT: - - - ``b`` -- a dense integer vector - - - ``algorithm`` -- (optional) either ``None``, ``'dense_elimination'``, - ``'sparse_elimination'``, ``'wiedemann'`` or ``'blackbox'``. - - OUTPUT: a pair ``(a, d)`` consisting of - - - ``a`` -- a dense integer vector - - - ``d`` -- an integer - - EXAMPLES:: - - sage: m = matrix(ZZ, 4, sparse=True) - sage: m[0,0] = m[1,2] = m[2,0] = m[3,3] = 2 - sage: m[0,2] = m[1,1] = -1 - sage: m[2,3] = m[3,0] = -3 - - sage: b0 = vector((1,1,1,1)) - sage: m._solve_vector_linbox(b0) - ((-1, -7, -3, -1), 1) - sage: m._solve_vector_linbox(b0, 'dense_elimination') - ((-1, -7, -3, -1), 1) - sage: m._solve_vector_linbox(b0, 'sparse_elimination') - ((-1, -7, -3, -1), 1) - sage: m._solve_vector_linbox(b0, 'wiedemann') - ((-1, -7, -3, -1), 1) - sage: m._solve_vector_linbox(b0, 'blackbox') - ((-1, -7, -3, -1), 1) - - sage: b1 = vector((1,1,-1,1)) - sage: a1, d1 = m._solve_vector_linbox(b1) - sage: d1 * b1 == m * a1 - True - - TESTS:: - - sage: algos = ["default", "dense_elimination", "sparse_elimination", - ....: "blackbox", "wiedemann"] - sage: for i in range(20): - ....: dim = randint(1, 30) - ....: M = MatrixSpace(ZZ, dim, sparse=True) - ....: density = min(1, 4/dim) - ....: m = M.random_element(density=density) - ....: while m.rank() != dim: - ....: m = M.random_element(density=density) - ....: U = m.column_space().dense_module() - ....: for algo in algos: - ....: u, d = m._solve_vector_linbox(U.zero(), algorithm=algo) - ....: assert u.is_zero() - ....: b = U.random_element() - ....: x, d = m._solve_vector_linbox(b, algorithm=algo) - ....: assert m * x == d * b - """ - Vin = self.column_ambient_module(base_ring=None, sparse=False) - v = Vin(v) - - if self._nrows == 0 or self._ncols == 0: - raise ValueError("not implemented for nrows=0 or ncols=0") - - # LinBox "solve" is mostly broken for nonsquare or singular matrices. - # The conditions below could be removed once all LinBox issues has - # been solved. - if self._nrows != self._ncols or self.rank() != self._nrows: - raise ValueError("only available for full rank square matrices") - - cdef givaro.ZRing givZZ - cdef linbox.SparseMatrix_integer * A = new_linbox_matrix_integer_sparse(givZZ, self) - cdef linbox.DenseVector_integer * b = new_linbox_vector_integer_dense(givZZ, v) - cdef linbox.DenseVector_integer * res = new linbox.DenseVector_integer(givZZ, self._ncols) - cdef givaro.Integer D - - method = get_method(algorithm) - - if method == METHOD_DEFAULT: - linbox.solve(res[0], D, A[0], b[0]) - elif method == METHOD_WIEDEMANN: - linbox.solve(res[0], D, A[0], b[0], linbox.Method.Wiedemann()) - elif method == METHOD_DENSE_ELIMINATION: - linbox.solve(res[0], D, A[0], b[0], linbox.Method.DenseElimination()) - elif method == METHOD_SPARSE_ELIMINATION: - linbox.solve(res[0], D, A[0], b[0], linbox.Method.SparseElimination()) - elif method == METHOD_BLACKBOX: - linbox.solve(res[0], D, A[0], b[0], linbox.Method.Blackbox()) - - Vout = self.row_ambient_module(base_ring=None, sparse=False) - res_sage = new_sage_vector_integer_dense(Vout, res[0]) - cdef Integer d = PY_NEW(Integer) - mpz_set(d.value, D.get_mpz_const()) - - del A - del b - del res - - return (res_sage, d) - - def _solve_matrix_linbox(self, mat, algorithm=None): - r""" - Solve the equation ``A x = mat`` where ``A`` is this matrix. - - EXAMPLES:: - - sage: m = matrix(ZZ, [[1,2],[1,0]], sparse=True) - sage: b = matrix(ZZ, 2, 4, [1,0,2,0,1,1,2,0], sparse=False) - sage: u, d = m._solve_matrix_linbox(b) - sage: u - [ 1 2 2 0] - [ 0 -1 0 0] - sage: m * u == b * diagonal_matrix(d) - True - - sage: u, d = m._solve_matrix_linbox([[1,3,4],[0,1,0]]) - sage: u - [0 1 0] - [1 1 2] - sage: d - (2, 1, 1) - - Test input:: - - sage: m = matrix(ZZ, [[1,2],[1,0]], sparse=True) - sage: b = matrix(ZZ, 3, 3, range(9)) - sage: m._solve_matrix_linbox(b) - Traceback (most recent call last): - ... - ValueError: wrong matrix dimension - - sage: m._solve_matrix_linbox([[1,1],[2,3]], algorithm='hop') - Traceback (most recent call last): - ... - ValueError: unknown algorithm - - TESTS:: - - sage: algos = ["default", "dense_elimination", "sparse_elimination", - ....: "blackbox", "wiedemann"] - - sage: for _ in range(10): - ....: dim = randint(2, 10) - ....: M = MatrixSpace(ZZ, dim, sparse=True) - ....: m = M.random_element(density=min(1,10/dim)) - ....: while m.rank() != dim: - ....: m = M.random_element(density=min(1,10/dim)) - ....: b = random_matrix(ZZ, dim, 7) - ....: Mb = b.parent() - ....: for algo in algos: - ....: u, d = m._solve_matrix_linbox(b, algo) - ....: assert m * u == b * diagonal_matrix(d) - """ - if self._nrows == 0 or self._ncols == 0: - raise ValueError("not implemented for nrows=0 or ncols=0") - - from sage.matrix.constructor import matrix - from sage.modules.free_module_element import vector - - cdef Matrix_integer_dense B - if not isinstance(mat, Matrix2): - B = matrix(ZZ, mat, sparse=False) - else: - B = mat.change_ring(ZZ).dense_matrix() - if B._nrows != self._nrows: - raise ValueError("wrong matrix dimension") - - # LinBox "solve" is mostly broken for singular matrices. The - # conditions below could be removed once all LinBox issues - # have been solved. - if self._nrows != self._ncols or self.rank() != self._nrows: - raise ValueError("only available for full rank square matrices") - - cdef givaro.ZRing givZZ - cdef linbox.SparseMatrix_integer * A = new_linbox_matrix_integer_sparse(givZZ, self) - cdef linbox.DenseVector_integer * b = new linbox.DenseVector_integer(givZZ, self._nrows) - cdef linbox.DenseVector_integer * res = new linbox.DenseVector_integer(givZZ, self._ncols) - cdef givaro.Integer D - - cdef int algo = get_method(algorithm) - - cdef Matrix_integer_dense X = matrix(ZZ, A.coldim(), B.ncols(), sparse=False) # solution - cdef Vector_integer_dense d = vector(ZZ, X.ncols(), sparse=False) # multipliers - - cdef size_t i, j - for i in range(X.ncols()): - # set b to the i-th column of B - for j in range(A.coldim()): - fmpz_get_mpz( b.getEntry(j).get_mpz(), fmpz_mat_entry(B._matrix, j, i)) - - # solve the current row - if algo == METHOD_DEFAULT: - linbox.solve(res[0], D, A[0], b[0]) - elif algo == METHOD_DENSE_ELIMINATION: - linbox.solve(res[0], D, A[0], b[0], linbox.Method.DenseElimination()) - elif algo == METHOD_SPARSE_ELIMINATION: - linbox.solve(res[0], D, A[0], b[0], linbox.Method.SparseElimination()) - elif algo == METHOD_BLACKBOX: - linbox.solve(res[0], D, A[0], b[0], linbox.Method.Blackbox()) - elif algo == METHOD_WIEDEMANN: - linbox.solve(res[0], D, A[0], b[0], linbox.Method.Wiedemann()) - - # set i-th column of X to be res - for j in range(A.coldim()): - fmpz_set_mpz(fmpz_mat_entry(X._matrix, j, i), res[0].getEntry(j).get_mpz()) - - # compute common gcd - mpz_set(d._entries[i], D.get_mpz_const()) - - del A - del b - del res - - return X, d From f59975351340ede91383607f4d1b8f22c8b2631e Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Wed, 28 Feb 2024 15:11:24 +0100 Subject: [PATCH 083/518] provide doctest --- src/sage/matrix/matrix2.pyx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/sage/matrix/matrix2.pyx b/src/sage/matrix/matrix2.pyx index 3f0ddb6d84a..3cdd910b22a 100644 --- a/src/sage/matrix/matrix2.pyx +++ b/src/sage/matrix/matrix2.pyx @@ -869,6 +869,13 @@ cdef class Matrix(Matrix1): Traceback (most recent call last): ... ValueError: matrix equation has no solutions + + Check that :issue:`28586` is fixed:: + + sage: m = matrix(GF(3), 2, 2, [1,2,2,0], sparse=True) + sage: v = vector(GF(3), [1,1]) + sage: m.solve_right(v) + (2, 1) """ try: L = B.base_ring() From a53271be5dba3a781130edc350ec04c6653ec423 Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Wed, 28 Feb 2024 15:54:43 +0000 Subject: [PATCH 084/518] Add valuation for LaurentPolynomial_mpair --- .../polynomial/laurent_polynomial_mpair.pyx | 41 ++++++++++++++++++- .../laurent_polynomial_ring_base.py | 10 ++--- 2 files changed, 45 insertions(+), 6 deletions(-) diff --git a/src/sage/rings/polynomial/laurent_polynomial_mpair.pyx b/src/sage/rings/polynomial/laurent_polynomial_mpair.pyx index 893b81c87af..598f57a39dd 100644 --- a/src/sage/rings/polynomial/laurent_polynomial_mpair.pyx +++ b/src/sage/rings/polynomial/laurent_polynomial_mpair.pyx @@ -16,7 +16,7 @@ from sage.structure.factorization import Factorization from sage.misc.derivative import multi_derivative from sage.rings.polynomial.polydict cimport monomial_exponent from sage.matrix.matrix0 cimport Matrix - +from sage.rings.infinity import Infinity cdef class LaurentPolynomial_mpair(LaurentPolynomial): """ @@ -1183,6 +1183,45 @@ cdef class LaurentPolynomial_mpair(LaurentPolynomial): raise TypeError("x must be a generator of parent") return self._poly.degree(self._parent._R.gens()[i]) + self._mon[i] + def valuation(self, x=None): + """ + Return the valuation of ``x`` in ``self``. + + If ``x`` is ``None``, return the minimal valuation of ``self``. + + EXAMPLES:: + + sage: R. = LaurentPolynomialRing(ZZ) + sage: f = 2*x^2*y^-3 - 13*x^-1*y^-3 + 2*x^2*y^-5 - 2*x^-3*y^2 + sage: f.valuation() + -4 + sage: f.valuation(x) + -3 + sage: f.valuation(y) + -5 + """ + # Valuation of zero polynomial is defined to be +Infinity + if self.is_zero(): + return Infinity + + # TODO: is there a faster cython-way to do this? + if x is None: + return min(sum(e) for e in self.exponents()) + + # Get the index of the gen + cdef tuple g = self._parent.gens() + cdef Py_ssize_t i + cdef bint no_generator_found = True + for i in range(len(g)): + if g[i] is x: + no_generator_found = False + break + if no_generator_found: + raise TypeError("x must be a generator of parent") + + # TODO: is there a faster cython-way to do this? + return min(e[i] for e in self.exponents()) + def has_inverse_of(self, i): """ INPUT: diff --git a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py index 311042380af..521cea3bd66 100644 --- a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py +++ b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py @@ -526,7 +526,7 @@ def random_element(self, min_valuation=-2, high_degree=2, *args, **kwds): sage: f = L.random_element(-10, 20) sage: f.degree() <= 20 True - sage: tuple(f.degree(x) >= -10 for x in L.gens()) + sage: tuple(f.valuation(x) >= -10 for x in L.gens()) (True, True) :: @@ -536,7 +536,7 @@ def random_element(self, min_valuation=-2, high_degree=2, *args, **kwds): sage: f = L.random_element(-5, 10) sage: tuple(f.degree(x) <= 10 for x in L.gens()) (True, True, True) - sage: tuple(f.degree(x) >= -5 for x in L.gens()) + sage: tuple(f.valuation(x) >= -5 for x in L.gens()) (True, True, True) :: @@ -546,7 +546,7 @@ def random_element(self, min_valuation=-2, high_degree=2, *args, **kwds): sage: f = L.random_element() sage: tuple(f.degree(x) <= 2 for x in L.gens()) (True, True) - sage: tuple(f.degree(x) >= -2 for x in L.gens()) + sage: tuple(f.valuation(x) >= -2 for x in L.gens()) (True, True) :: @@ -556,7 +556,7 @@ def random_element(self, min_valuation=-2, high_degree=2, *args, **kwds): sage: f = L.random_element(-1, 1) sage: tuple(f.degree(x) <= 1 for x in L.gens()) (True, True, True, True, True) - sage: tuple(f.degree(x) >= -1 for x in L.gens()) + sage: tuple(f.valuation(x) >= -1 for x in L.gens()) (True, True, True, True, True) TESTS:: @@ -573,7 +573,7 @@ def random_element(self, min_valuation=-2, high_degree=2, *args, **kwds): ....: if f.is_zero(): continue # the zero polynomial is defined to have degree -1 ....: for x in L.gens(): ....: assert f.degree(x) <= m - ....: assert f.degree(x) >= n + ....: assert f.valuation(x) >= n """ # Ensure the degree parameters are sensible if high_degree < min_valuation: From 835a1411db7612982fe1f9bf090b18346ae7b8e6 Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Wed, 28 Feb 2024 15:57:27 +0000 Subject: [PATCH 085/518] Add doctest for zero polynomial --- src/sage/rings/polynomial/laurent_polynomial_mpair.pyx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/sage/rings/polynomial/laurent_polynomial_mpair.pyx b/src/sage/rings/polynomial/laurent_polynomial_mpair.pyx index 598f57a39dd..769a7c27e7d 100644 --- a/src/sage/rings/polynomial/laurent_polynomial_mpair.pyx +++ b/src/sage/rings/polynomial/laurent_polynomial_mpair.pyx @@ -1199,6 +1199,8 @@ cdef class LaurentPolynomial_mpair(LaurentPolynomial): -3 sage: f.valuation(y) -5 + sage: R.zero().valuation() + +Infinity """ # Valuation of zero polynomial is defined to be +Infinity if self.is_zero(): From 01363c99a66a9e5b0e6eda0995e6c96c6cde1bbb Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 28 Feb 2024 09:53:28 -0800 Subject: [PATCH 086/518] build/pkgs/gnumake_tokenpool: Update to 0.0.7 --- build/pkgs/gnumake_tokenpool/checksums.ini | 6 +++--- build/pkgs/gnumake_tokenpool/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/gnumake_tokenpool/checksums.ini b/build/pkgs/gnumake_tokenpool/checksums.ini index e79058d27e3..76c234c4404 100644 --- a/build/pkgs/gnumake_tokenpool/checksums.ini +++ b/build/pkgs/gnumake_tokenpool/checksums.ini @@ -1,5 +1,5 @@ tarball=gnumake_tokenpool-VERSION-py3-none-any.whl -sha1=3dfcc8c466c17f974d90694f81f4481c3d84aecc -md5=5dae4c65e9609853085ae1970d4fe143 -cksum=612213211 +sha1=882c694dc3c0a935275a8d2acd9e766399719754 +md5=cc18b6c7a339d8140f5b2dc248ef24bc +cksum=2989045922 upstream_url=https://pypi.io/packages/py3/g/gnumake_tokenpool/gnumake_tokenpool-VERSION-py3-none-any.whl diff --git a/build/pkgs/gnumake_tokenpool/package-version.txt b/build/pkgs/gnumake_tokenpool/package-version.txt index 81340c7e72d..5a5831ab6bf 100644 --- a/build/pkgs/gnumake_tokenpool/package-version.txt +++ b/build/pkgs/gnumake_tokenpool/package-version.txt @@ -1 +1 @@ -0.0.4 +0.0.7 From 9b933f006f5b4b0304d8c3c11991641fb2fa39e8 Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Wed, 28 Feb 2024 18:02:58 +0000 Subject: [PATCH 087/518] rewrite random sampling for multivariate polynomials --- .../laurent_polynomial_ring_base.py | 127 +++++++++++++----- 1 file changed, 95 insertions(+), 32 deletions(-) diff --git a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py index 521cea3bd66..dd596414538 100644 --- a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py +++ b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py @@ -28,8 +28,7 @@ from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.rings.ring import CommutativeRing from sage.structure.parent import Parent -from sage.misc.misc_c import prod - +from sage.misc.prandom import randint class LaurentPolynomialRing_generic(CommutativeRing, Parent): """ @@ -489,14 +488,19 @@ def krull_dimension(self): """ raise NotImplementedError - def random_element(self, min_valuation=-2, high_degree=2, *args, **kwds): + def random_element(self, min_valuation=-2, max_degree=2, terms=5, **kwds): """ - Return a random polynomial of degree at most ``high_degree`` and - lowest valuation at least ``min_valuation``. + Return a random polynomial with degree at most ``max_degree`` and + lowest valuation at least ``min_valuation`` for each generator of + ``self``. + + For the univariate case, uses the random sampling from the + polynomial ring then shifts this polynomial down to ensure + correct ``max_degree`` and ``min_valuation``. - Internally uses the random sampling from the corresponding - polynomial ring then shifts this polynomial down to compute - the correct degrees. + For the multivariate case, samples ``terms`` elements which + respectively have degree at most ``max_degree`` and valuation + at least ``min_valuation`` and returns their sum. INPUT: @@ -506,8 +510,13 @@ def random_element(self, min_valuation=-2, high_degree=2, *args, **kwds): - ``max_degree`` -- integer (default: ``2``); the maximal allowed degree of the polynomial - - ``*args, **kwds`` -- passed to the random element generator - of the underlying polynomial ring and respective base ring + - ``terms`` -- number of terms requested (default: 5). + Only used for multivariate polynomial rings. If more + terms are requested than exist, then this parameter is + silently reduced to the maximum number of available terms. + + - ``**kwargs`` -- passed to the random element generator of the base + ring EXAMPLES:: @@ -515,19 +524,43 @@ def random_element(self, min_valuation=-2, high_degree=2, *args, **kwds): sage: f = L.random_element() sage: f.degree() <= 2 True - sage: f.degree() >= -2 + sage: f.valuation() >= -2 True sage: f.parent() is L True :: - sage: L = LaurentPolynomialRing(ZZ, 2, 'x') + sage: L. = LaurentPolynomialRing(QQ) sage: f = L.random_element(-10, 20) sage: f.degree() <= 20 True + sage: f.valuation() >= -10 + True + sage: f.parent() is L + True + + :: + + sage: L = LaurentPolynomialRing(ZZ, 2, 'x') + sage: f = L.random_element(-10, 20) + sage: tuple(f.degree(x) <= 20 for x in L.gens()) + (True, True) + sage: tuple(f.valuation(x) >= -10 for x in L.gens()) + (True, True) + sage: len(list(f)) <= 5 + True + + :: + + sage: L = LaurentPolynomialRing(ZZ, 2, 'x') + sage: f = L.random_element(-10, 20, terms=20) + sage: tuple(f.degree(x) <= 20 for x in L.gens()) + (True, True) sage: tuple(f.valuation(x) >= -10 for x in L.gens()) (True, True) + sage: len(list(f)) <= 20 + True :: @@ -563,7 +596,8 @@ def random_element(self, min_valuation=-2, high_degree=2, *args, **kwds): sage: rings = [RR, QQ, ZZ, GF(13), GF(7^3)] sage: for ring in rings: - ....: d = randint(1, 5) + ....: d = randint(2, 6) + ....: t = randint(5, 20) ....: L = LaurentPolynomialRing(ring, d, 'x') ....: for _ in range(100): ....: n, m = randint(-10, 10), randint(1, 10) @@ -571,30 +605,59 @@ def random_element(self, min_valuation=-2, high_degree=2, *args, **kwds): ....: m, n = n, m ....: f = L.random_element(n, m) ....: if f.is_zero(): continue # the zero polynomial is defined to have degree -1 + ....: assert len(list(f)) <= t ....: for x in L.gens(): ....: assert f.degree(x) <= m ....: assert f.valuation(x) >= n - """ - # Ensure the degree parameters are sensible - if high_degree < min_valuation: - raise ValueError("`high_degree` must be greater than or equal to `min_valuation`") - - # First sample a polynomial from the associated polynomial - # ring of `self` of degree `(high_degree - min_valuation)` - abs_deg = (high_degree - min_valuation) - f_rand = self._R.random_element(degree=abs_deg, *args, **kwds) - - # Coerce back to `self`. We now have a polynomial of only - # positive valuation monomials - f = self(f_rand) - # Finally, shift the entire polynomial down by min_valuation - # which will result in a polynomial with highest degree - # high_degree and lowest valuation min_valuation - monomial = prod(self.gens()) - f *= monomial**min_valuation + Test for constructions which use univariate polynomial rings:: - return f + sage: rings = [RR, QQ, ZZ, GF(13), GF(7^3)] + sage: for ring in rings: + ....: L. = LaurentPolynomialRing(ring) + ....: for _ in range(100): + ....: n, m = randint(-10, 10), randint(1, 10) + ....: if m < n: + ....: m, n = n, m + ....: f = L.random_element(n, m) + ....: if f.is_zero(): continue # the zero polynomial is defined to have degree -1 + ....: for x in L.gens(): + ....: assert f.degree() <= m + ....: assert f.valuation() >= n + """ + # Ensure the degree parameters are sensible + if max_degree < min_valuation: + raise ValueError("`max_degree` must be greater than or equal to `min_valuation`") + + # Handle univariate case + if self._n == 1: + # First sample a polynomial from the associated polynomial + # ring of `self` of degree `(max_degree - min_valuation)` + abs_deg = (max_degree - min_valuation) + f_rand = self._R.random_element(degree=abs_deg, **kwds) + + # Case the polynomial base to self and scale to ensure + # that min_valuation is satisfied + s = self.gen() ** min_valuation + return self(f_rand) * s + + # Ensure terms is set correctly + if terms < 0: + raise TypeError("cannot compute polynomial with a negative number of terms.") + elif terms == 0: + return self._zero_element + + # We now sample `terms`` terms with exponents picked randomly + # with degree at most `max_degree` and valuation greater or + # equal to min_valuation scaled by an element of the base ring + k = self.base_ring() + n = self._n + res = self.zero() + for _ in range(terms): + s = k.random_element(**kwds) + ele = [randint(min_valuation, max_degree) for _ in range(n)] + res += s * self.monomial(*ele) + return res def is_exact(self): """ From 4e64e9f0e89d7c856719b9bedcde8b652f1d8ecb Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Wed, 28 Feb 2024 19:28:06 +0100 Subject: [PATCH 088/518] use sparse polynomials for generating functions --- src/sage/databases/findstat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/databases/findstat.py b/src/sage/databases/findstat.py index 657eaf13dad..f3ea19d6994 100644 --- a/src/sage/databases/findstat.py +++ b/src/sage/databases/findstat.py @@ -841,7 +841,7 @@ def _generating_functions_from_dict(gfs, style): if style == "polynomial": from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.rings.integer_ring import ZZ - P = PolynomialRing(ZZ, "q") + P = PolynomialRing(ZZ, "q", sparse=True) q = P.gen() return {level: sum(coefficient * q**exponent for exponent, coefficient in gen_dict.items()) From 6fe8e919ef124dd8b9da7b8300edb81e6d1d9fa4 Mon Sep 17 00:00:00 2001 From: "John H. Palmieri" Date: Wed, 28 Feb 2024 10:31:28 -0800 Subject: [PATCH 089/518] Revise the code of conduct. - Add a section describing more unacceptable behavior - Add a diversity section - Add reporting guidelines - Add information on how reports are resolved Also add a new document with audience the Sage Code of Conduct Committee (new name for the sage-abuse committee) --- CODE_OF_CONDUCT.md | 170 ++++++++++++++++++++++++++++- HANDLING_VIOLATIONS.md | 241 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 407 insertions(+), 4 deletions(-) create mode 100644 HANDLING_VIOLATIONS.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index eca34dc7e4c..e4ccbb356ac 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,6 +1,11 @@ # Code of Conduct # -Approved by the Sage community by a vote which ended on November 24, 2014 +This code was approved by the Sage community by a vote which ended on +November 24, 2014. + +**The date needs to be updated if these changes are approved.** + +## Introduction ## The Sage community is comprised of an international mixture of mathematicians, computer scientists, engineers, researchers, teachers, amateurs, and others @@ -8,6 +13,8 @@ with varied backgrounds. This diversity is one of our strengths, but it can also lead to communication problems and unhappiness. People who love working on Sage can more effectively collaborate with others if they follow this code. +## Guidelines ## + 1. Be friendly and patient. 2. Be welcoming. We strive to be a community that welcomes and supports people @@ -35,6 +42,161 @@ Sage can more effectively collaborate with others if they follow this code. forget that it is human to err. Blame alone gets us nowhere, it is better to help resolve issues so we can all learn from our mistakes. -If you believe someone is violating the code of conduct, we ask that you report -it to https://groups.google.com/g/sage-abuse. The group administrators will -consider the issue and explore resolutions. +5. We will not accept harassment or other exclusionary behavior, such as: + + 1. Violent or intimidating threats or language directed against another person. + 2. Sexist, racist, or otherwise discriminatory jokes and language. + 3. Posting sexually explicit or violent material. + 4. Posting (or threatening to post) other people’s personally identifying information (“doxing”). + 5. Personal insults, especially those using racist or sexist terms. + 6. Unwelcome sexual attention or comments + 7. Excessive profanity. Please avoid swear words; people differ greatly in their sensitivity to swearing. + 8. Repeated harassment of others. In general, if someone asks you to stop, then stop. + 9. Advocating for, or encouraging, any of the above behavior. + +This code of conduct applies to all spaces managed by the Sage +project, including all public and private mailing lists, issue +trackers, wikis, and any other communication channel used by our +community. It also applies to Sage Days and any other in-person or +virtual events. + +This code of conduct should be honored by everyone who participates in +the Sage community formally or informally, or claims any affiliation +with the project, in any project-related activities, and, especially, +when representing the project, in any role. + +This code is neither exhaustive nor complete. It serves to distill our +common understanding of a collaborative, shared environment and +goals. Please try to follow this code in spirit as much as in letter, +to create a friendly and productive environment that enriches the +surrounding community. + +## Diversity statement ## + +Sage welcomes and encourages participation in our community by people +of all backgrounds and identities. We are committed to promoting and +sustaining a culture that values mutual respect, tolerance, and +learning, and we work together as a community to help each other live +out these values. + +No matter how you identify yourself or how others perceive you: we +welcome you. Though no list can hope to be comprehensive, we +explicitly honor diversity in: age, culture, ethnicity, genotype, +gender identity or expression, language, national origin, neurotype, +phenotype, political beliefs, profession, race, religion, sexual +orientation, socioeconomic status, subculture, and technical ability, +to the extent that these do not conflict with this code of conduct. + +Though we welcome people fluent in all languages, Sage development is +conducted in English. + +Standards for behavior in the Sage community are detailed in the Code +of Conduct above. Participants in our community should uphold these +standards in all their interactions and help others to do so as well. + +## Reporting guidelines ## + +We know that it is painfully common for internet communication to +start at or devolve into obvious and flagrant abuse. We also recognize +that sometimes people may have a bad day, or be unaware of some of the +guidelines in this Code of Conduct. Please keep this in mind when +deciding on how to respond to a breach of this Code. + +For clearly intentional breaches, report those to the Sage Code of +Conduct Committee (see below). For possibly unintentional breaches, +you may reply to the person and point out this Code of Conduct (either +in public or in private, whatever is most appropriate). If you would +prefer not to do that, please feel free to report to the Sage Code of +Conduct Committee directly, or ask the committee for advice, in +confidence. + +You can report issues to the Sage Code of Conduct Committee at +sage-conduct@googlegroups.com. Currently, the committee consists of: + +- Vincent Delecroix +- David Joyner +- John Palmieri +- David Roe +- William Stein + +If your report involves any members of the committee, or if they feel +they have a conflict of interest in handling it, then they will recuse +themselves from considering your report. Alternatively, if, for any +reason, you feel uncomfortable making a report to the whole committee, +then you can also contact individual committee members. + +## Incident reporting resolution & Code of Conduct enforcement ## + +This section summarizes the most important points; more details can be +found in the Code of Conduct enforcement guide. + +We will investigate and respond to all complaints. The Sage Code of +Conduct Committee will protect the identity of the reporter, and treat +the content of complaints as confidential (unless the reporter agrees +otherwise). + +In case of severe and obvious breaches, e.g., personal threat or +violent, sexist or racist language, we will immediately disconnect the +originator from Sage communication channels; please see the manual +for details. + +In cases not involving clear severe and obvious breaches of this code +of conduct, the process for acting on any received code of conduct +violation report will be: + +- acknowledgement that the report has been received +- discussion within the committee +- discussion with and/or feedback provided to the reportee +- mediation (if feedback didn’t help, and only if both reporter and + reportee agree to this) +- enforcement via transparent decision by the sasge-abuse committee + +The committee will respond to any report as soon as possible, and our +goal will be to respond within 72 hours. + +Potential consequences for violating the Sage code of conduct include: + +- Nothing (if we determine that no violation occurred) +- Private feedback or reprimand to the individual(s) involved +- Warning the person to cease their behavior and that any further + reports will result in sanctions +- A public announcement that an incident occurred +- Mediation (only if both reporter and reportee agree) +- An imposed vacation (e.g. asking someone to "take a week off" from a mailing list) +- A permanent or temporary ban from some or all Sage spaces (mailing + lists, GitHub repos, in-person events, etc.) +- Assistance to the reporter with a report to other bodies, for + example, institutional offices or appropriate law enforcement + agencies +- Publishing an account of the harassment and calling for the + resignation of the alleged harasser from their responsibilities + (usually pursued by people without formal authority: may be called + for if the person is the event leader, or refuses to stand aside + from the conflict of interest, or similar) +- Any other response that the Sage Code of Conduct Committee deems necessary and + appropriate to the situation + +## Policies and procedures of the project's pertinent platforms ## + +[GitHub Community Guidelines](https://docs.github.com/en/site-policy/github-terms/github-community-guidelines) apply +- [Report abuse to GitHub](https://github.com/contact/report-abuse), + [Block a user from your personal account](https://docs.github.com/en/communities/maintaining-your-safety-on-github/blocking-a-user-from-your-personal-account) +- [Manage disruptive comments](https://docs.github.com/en/communities/moderating-comments-and-conversations/managing-disruptive-comments), + [Lock conversations](https://docs.github.com/en/communities/moderating-comments-and-conversations/locking-conversations) + (requires Maintainer role or [Organization Moderator role](https://docs.github.com/en/organizations/managing-peoples-access-to-your-organization-with-roles/managing-moderators-in-your-organization)) + +## Amending this document ## + +This document may be amended by a vote of the Sage community in the +sage-devel Google group, with the exception of facts like the +membership of the Sage Code of Conduct Committee, changes to URLs, or +changes to email addresses: changes like that can be done via a normal +pull request. Any pull requests involving this document should list +the committee members as reviewers. + +## Credits ## + +Portions of this are adapted from the [SciPy code of +conduct](https://docs.scipy.org/doc/scipy/dev/conduct/code_of_conduct.html) +and the [NumFOCUS code of +conduct](https://numfocus.org/code-of-conduct). diff --git a/HANDLING_VIOLATIONS.md b/HANDLING_VIOLATIONS.md new file mode 100644 index 00000000000..1f04217f2c9 --- /dev/null +++ b/HANDLING_VIOLATIONS.md @@ -0,0 +1,241 @@ +# Guide for handling violations to Sage's Code of Conduct # + +## Introduction ## + +This is the manual followed by the Sage Code of Conduct Committee. It is used +when we respond to an issue to make sure we’re consistent and fair. + +Enforcing the Code of Conduct impacts our community today and for the +future. It’s an action that we do not take lightly. When reviewing +enforcement measures, the Sage Code of Conduct Committee will keep the +following values and guidelines in mind: + +- Act in a personal manner rather than impersonal. The committee can + engage the parties to understand the situation, while respecting the + privacy and any necessary confidentiality of reporters. However, + it is sometimes necessary to communicate with one or more + individuals directly: the committee’s goal is to improve the health + of our community rather than only produce a formal decision. + +- Emphasize empathy for individuals rather than judging behavior, + avoiding binary labels of “good” and “bad/evil”. Overt, clear-cut + aggression and harassment exists and we will address that + firmly. But many scenarios that can prove challenging to resolve are + those where normal disagreements devolve into unhelpful or harmful + behavior from multiple parties. Understanding the full context and + finding a path that re-engages all is hard, but ultimately the most + productive for our community. + +- We understand that email is a difficult medium and can be + isolating. Receiving criticism over email, without personal contact, + can be particularly painful. This makes it especially important to + keep an atmosphere of open-minded respect of the views of others. It + also means that we must be transparent in our actions, and that we + will do everything in our power to make sure that all our members + are treated fairly and with sympathy. + +- Discrimination can be subtle and it can be unconscious. It can show + itself as unfairness and hostility in otherwise ordinary + interactions. We know that this does occur, and we will take care to + look out for it. We would very much like to hear from you if you + feel you have been treated unfairly, and we will use these + procedures to make sure that your complaint is heard and addressed. + +- Help increase engagement in good discussion practice: try to + identify where discussion may have broken down and provide + actionable information, pointers and resources that can lead to + positive change on these points. + +- Be mindful of the needs of new members: provide them with explicit + support and consideration, with the aim of increasing participation + from underrepresented groups in particular. + +Individuals come from different cultural backgrounds and native +languages. Try to identify any honest misunderstandings caused by a +non-native speaker and help them understand the issue and what they +can change to avoid causing offense. Complex discussion in a foreign +language can be very intimidating, and we want to grow our diversity +also across nationalities and cultures. + +Mediation: voluntary, informal mediation is a tool at our disposal. In +some contexts, such as when two or more parties have escalated to the +point of inappropriate behavior (something sadly common in human +conflict), it may be useful to facilitate a mediation process. This is +only an example: the committee can consider mediation in any case, +mindful that the process is meant to be strictly voluntary and no +party can be pressured to participate. If the committee suggests +mediation, it should: + +- Find a candidate who can serve as a mediator. + +- Obtain the agreement of the reporter(s). The reporter(s) have + complete freedom to decline the mediation idea, or to propose an + alternate mediator. + +- Obtain the agreement of the reported person(s). + +- Settle on the mediator: while parties can propose a different + mediator than the suggested candidate, only if common agreement is + reached on all terms can the process move forward. + +- Establish a timeline for mediation to complete, ideally within two weeks. + +The mediator will engage with all the parties and seek a resolution +that is satisfactory to all. Upon completion, the mediator will +provide a report (vetted by all parties to the process) to the +committee, with recommendations on further steps. The committee will +then evaluate these results (whether satisfactory resolution was +achieved or not) and decide on any additional action deemed necessary. + +## How the committee will respond to reports ## + +When the committee (or a committee member) receives a report, they +will first determine whether the report is about a clear and severe +breach (as defined below). If so, immediate action needs to be taken +in addition to the regular report-handling process. + +### Clear and severe breach actions ### + +We know that it is painfully common for internet communication to +start at or devolve into obvious and flagrant abuse. We will deal +quickly with clear and severe breaches like personal threats, violent, +sexist, or racist language. + +When a member of the Sage Code of Conduct Committee becomes aware of a +clear and severe breach, they will do the following: + +- Immediately disconnect the originator from all Sage communication channels. + +- Reply to the reporter that their report has been received and that + the originator has been disconnected. + +- In every case, the moderator should make a reasonable effort to + contact the originator, and tell them specifically how their + language or actions qualify as a “clear and severe breach”. The + moderator should also say that, if the originator believes this is + unfair or they want to be reconnected to Sage, they have the right + to ask for a review, as below, by the Sage Code of Conduct Committee. The + moderator should copy this explanation to the Sage Code of Conduct + Committee. + +The Sage Code of Conduct Committee will formally review and sign off on all +cases where this mechanism has been applied to make sure it is not +being used to control ordinary heated disagreement. + +### Report handling ### + +When a report is sent to the committee, they will immediately reply to +the reporter to confirm receipt. This reply must be sent within 72 +hours, and the group should strive to respond much quicker than that. + +If a report doesn’t contain enough information, the committee will +obtain all relevant data before acting. The committee is empowered to +contact any individuals involved to get a more complete account of +events. + +The committee will then review the incident and determine, to the best of their ability: + +- What happened. + +- Whether this event constitutes a Code of Conduct violation. + +- Who are the responsible party/parties. + +- Whether this is an ongoing situation, and whether there is a threat to anyone’s physical safety. + +This information will be collected in writing, and whenever possible +the group’s deliberations will be recorded and retained (i.e., chat +transcripts, email discussions, recorded conference calls, summaries +of voice conversations, etc.). + +It is important to retain an archive of all activities of this +committee to ensure consistency in behavior and provide institutional +memory for the project. To assist in this, the default channel of +discussion for this committee will be a private mailing list +accessible to current and future members of the committee. If the +committee finds the need to use off-list communications (e.g., phone +calls for early/rapid response), it should, in all cases, summarize +these back to the list so there’s a good record of the process. + +The Sage Code of Conduct Committee should aim to have a resolution agreed upon +within two weeks. In the event that a resolution can’t be determined +in that time, the committee will respond to the reporter(s) with an +update and projected timeline for the resolution. + +## Resolutions ## + +The committee must agree on a resolution by consensus. If the group +cannot reach consensus and deadlocks for over a week, the committee is +empowered to consult as needed to try to reach consensus. + +Possible responses may include: + +- Taking no further action: + + - if we determine no violations have occurred. + + - if the matter has been resolved publicly while the committee was considering responses. + +- Coordinating voluntary mediation: if all involved parties agree, the + committee may facilitate a mediation process as detailed above. + +- Remind publicly, and point out that some behavior/actions/language + have been judged inappropriate and why in the current context, or + can but hurtful to some people, requesting the community to + self-adjust. + +- A private reprimand from the committee to the individual(s) + involved. In this case, a representative of the committee will + deliver that reprimand to the individual(s) over email, cc’ing the + group. + +- A public reprimand. In this case, a committee representative will deliver + that reprimand in the same venue that the violation occurred, within + the limits of practicality. E.g., the original mailing list for an + email violation, but for a chat room discussion where the + person/context may be gone, they can be reached by other means. The + group may choose to publish this message elsewhere for documentation + purposes. + +- A request for a public or private apology, assuming the reporter + agrees to this idea: they may, at their discretion, refuse further + contact with the violator. A committee representative will deliver + this request. The committee may, if it chooses, attach “strings” to + this request: for example, the group may ask a violator to + apologize, in order to retain one’s membership on a mailing list. + +- A “mutually agreed upon hiatus” where the committee asks the + individual to temporarily refrain from community participation. If + the individual chooses not to take a temporary break voluntarily, + the committee may issue a “mandatory cooling off period”. + +- A permanent or temporary ban from some or all Sage spaces (mailing + lists, github, etc.). The group will maintain records of all such + bans so that they may be reviewed in the future or otherwise + maintained. + +Once a resolution is agreed upon, the committee will contact the +original reporter and any other affected parties and explain that the +committee has taken action. Depending on the situation, the committee +may or may not choose to provide further details about what actions +were taken and how they might affect the reporter. + +The committee will never publicly discuss the issue; all public +statements will be made by a representative of the Sage Code of Conduct +Committee. + +## Conflicts of interest ## + +In the event of any conflict of interest, a committee member must +immediately notify the other members, and recuse themselves if +necessary. + +## Amending this document ## + +This document may be amended by a vote of the Sage Code of Conduct +Committee. + +## Credits ## + +This is largely adapted from the [SciPy report handling +manual](https://docs.scipy.org/doc/scipy/dev/conduct/report_handling_manual.html). From 107d705fd7b36411fa724211c5e0feb8c2cadda4 Mon Sep 17 00:00:00 2001 From: "John H. Palmieri" Date: Wed, 28 Feb 2024 17:30:19 -0800 Subject: [PATCH 090/518] capitalize "Code" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Matthias Köppe --- CODE_OF_CONDUCT.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index e4ccbb356ac..8433408727a 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,6 +1,6 @@ # Code of Conduct # -This code was approved by the Sage community by a vote which ended on +This Code was approved by the Sage community by a vote which ended on November 24, 2014. **The date needs to be updated if these changes are approved.** From 16273f5a9d46076319237367d4c888242d083bb4 Mon Sep 17 00:00:00 2001 From: "John H. Palmieri" Date: Wed, 28 Feb 2024 17:31:33 -0800 Subject: [PATCH 091/518] More capitalizations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Matthias Köppe --- CODE_OF_CONDUCT.md | 10 +++++----- HANDLING_VIOLATIONS.md | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 8433408727a..1c74cbf1093 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -11,7 +11,7 @@ The Sage community is comprised of an international mixture of mathematicians, computer scientists, engineers, researchers, teachers, amateurs, and others with varied backgrounds. This diversity is one of our strengths, but it can also lead to communication problems and unhappiness. People who love working on -Sage can more effectively collaborate with others if they follow this code. +Sage can more effectively collaborate with others if they follow this Code. ## Guidelines ## @@ -85,7 +85,7 @@ explicitly honor diversity in: age, culture, ethnicity, genotype, gender identity or expression, language, national origin, neurotype, phenotype, political beliefs, profession, race, religion, sexual orientation, socioeconomic status, subculture, and technical ability, -to the extent that these do not conflict with this code of conduct. +to the extent that these do not conflict with this Code of Conduct. Though we welcome people fluent in all languages, Sage development is conducted in English. @@ -140,8 +140,8 @@ violent, sexist or racist language, we will immediately disconnect the originator from Sage communication channels; please see the manual for details. -In cases not involving clear severe and obvious breaches of this code -of conduct, the process for acting on any received code of conduct +In cases not involving clear severe and obvious breaches of this Code +of Conduct, the process for acting on any received Code of Conduct violation report will be: - acknowledgement that the report has been received @@ -154,7 +154,7 @@ violation report will be: The committee will respond to any report as soon as possible, and our goal will be to respond within 72 hours. -Potential consequences for violating the Sage code of conduct include: +Potential consequences for violating the Sage Code of Conduct include: - Nothing (if we determine that no violation occurred) - Private feedback or reprimand to the individual(s) involved diff --git a/HANDLING_VIOLATIONS.md b/HANDLING_VIOLATIONS.md index 1f04217f2c9..6f0e72cf161 100644 --- a/HANDLING_VIOLATIONS.md +++ b/HANDLING_VIOLATIONS.md @@ -210,7 +210,7 @@ Possible responses may include: the committee may issue a “mandatory cooling off period”. - A permanent or temporary ban from some or all Sage spaces (mailing - lists, github, etc.). The group will maintain records of all such + lists, GitHub, etc.). The group will maintain records of all such bans so that they may be reviewed in the future or otherwise maintained. From a681f24b7a3e568c24ef664efc340f841935977b Mon Sep 17 00:00:00 2001 From: "John H. Palmieri" Date: Wed, 28 Feb 2024 17:32:24 -0800 Subject: [PATCH 092/518] add a missing period MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Matthias Köppe --- CODE_OF_CONDUCT.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 1c74cbf1093..4943285b955 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -49,7 +49,7 @@ Sage can more effectively collaborate with others if they follow this Code. 3. Posting sexually explicit or violent material. 4. Posting (or threatening to post) other people’s personally identifying information (“doxing”). 5. Personal insults, especially those using racist or sexist terms. - 6. Unwelcome sexual attention or comments + 6. Unwelcome sexual attention or comments. 7. Excessive profanity. Please avoid swear words; people differ greatly in their sensitivity to swearing. 8. Repeated harassment of others. In general, if someone asks you to stop, then stop. 9. Advocating for, or encouraging, any of the above behavior. From afb62c4943edb0a3854e5c62b3945ee02d3bc3f5 Mon Sep 17 00:00:00 2001 From: "John H. Palmieri" Date: Wed, 28 Feb 2024 17:44:54 -0800 Subject: [PATCH 093/518] Change "we" to "Sage Code of Conduct Committee" as appropriate. Add links to Google groups conduct documents. --- CODE_OF_CONDUCT.md | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 4943285b955..7bb46dea139 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -130,15 +130,15 @@ then you can also contact individual committee members. This section summarizes the most important points; more details can be found in the Code of Conduct enforcement guide. -We will investigate and respond to all complaints. The Sage Code of -Conduct Committee will protect the identity of the reporter, and treat -the content of complaints as confidential (unless the reporter agrees -otherwise). +The Sage Code of Conduct Committee will investigate and respond to all +complaints. The committee will protect the identity of the reporter, +and treat the content of complaints as confidential (unless the +reporter agrees otherwise). In case of severe and obvious breaches, e.g., personal threat or -violent, sexist or racist language, we will immediately disconnect the -originator from Sage communication channels; please see the manual -for details. +violent, sexist or racist language, the committee will immediately +disconnect the originator from Sage communication channels; please see +the manual for details. In cases not involving clear severe and obvious breaches of this Code of Conduct, the process for acting on any received Code of Conduct @@ -149,14 +149,14 @@ violation report will be: - discussion with and/or feedback provided to the reportee - mediation (if feedback didn’t help, and only if both reporter and reportee agree to this) -- enforcement via transparent decision by the sasge-abuse committee +- enforcement via transparent decision by the Sage Code of Conduct Committee The committee will respond to any report as soon as possible, and our goal will be to respond within 72 hours. Potential consequences for violating the Sage Code of Conduct include: -- Nothing (if we determine that no violation occurred) +- Nothing (if the committee determines that no violation occurred) - Private feedback or reprimand to the individual(s) involved - Warning the person to cease their behavior and that any further reports will result in sanctions @@ -176,14 +176,18 @@ Potential consequences for violating the Sage Code of Conduct include: - Any other response that the Sage Code of Conduct Committee deems necessary and appropriate to the situation -## Policies and procedures of the project's pertinent platforms ## +## Policies and procedures of common project platforms ## -[GitHub Community Guidelines](https://docs.github.com/en/site-policy/github-terms/github-community-guidelines) apply +[GitHub Community Guidelines](https://docs.github.com/en/site-policy/github-terms/github-community-guidelines) apply: - [Report abuse to GitHub](https://github.com/contact/report-abuse), [Block a user from your personal account](https://docs.github.com/en/communities/maintaining-your-safety-on-github/blocking-a-user-from-your-personal-account) - [Manage disruptive comments](https://docs.github.com/en/communities/moderating-comments-and-conversations/managing-disruptive-comments), - [Lock conversations](https://docs.github.com/en/communities/moderating-comments-and-conversations/locking-conversations) - (requires Maintainer role or [Organization Moderator role](https://docs.github.com/en/organizations/managing-peoples-access-to-your-organization-with-roles/managing-moderators-in-your-organization)) +- [Lock conversations](https://docs.github.com/en/communities/moderating-comments-and-conversations/locking-conversations) +(requires Maintainer role or [Organization Moderator role](https://docs.github.com/en/organizations/managing-peoples-access-to-your-organization-with-roles/managing-moderators-in-your-organization)) + +Google groups: +- [Content policy](https://support.google.com/groups/answer/4561696) +- [Reporting procedures](https://support.google.com/groups/answer/81275) ## Amending this document ## From b4ccf0e6d600d72abb330e71e0dce4d7cd0b4bef Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Thu, 29 Feb 2024 10:34:09 +0000 Subject: [PATCH 094/518] reviewer feedback --- .../polynomial/laurent_polynomial_mpair.pyx | 20 +++++++++++--- .../laurent_polynomial_ring_base.py | 27 +++++++++++++++++-- 2 files changed, 41 insertions(+), 6 deletions(-) diff --git a/src/sage/rings/polynomial/laurent_polynomial_mpair.pyx b/src/sage/rings/polynomial/laurent_polynomial_mpair.pyx index 769a7c27e7d..c58dbd16b3a 100644 --- a/src/sage/rings/polynomial/laurent_polynomial_mpair.pyx +++ b/src/sage/rings/polynomial/laurent_polynomial_mpair.pyx @@ -1201,27 +1201,39 @@ cdef class LaurentPolynomial_mpair(LaurentPolynomial): -5 sage: R.zero().valuation() +Infinity + + TESTS: + + If supplied, ``x`` must be a generator:: + + sage: R. = LaurentPolynomialRing(ZZ) + sage: f = 1 + x + x^2*y^-1 + sage: f.valuation(1) + Traceback (most recent call last): + ... + TypeError: x must be a generator of parent """ # Valuation of zero polynomial is defined to be +Infinity if self.is_zero(): return Infinity - # TODO: is there a faster cython-way to do this? + # When x is None find the minimal valuation by checking all terms if x is None: - return min(sum(e) for e in self.exponents()) + val = min(e.unweighted_degree() for e in self.exponents()) + return Integer(val) # Get the index of the gen cdef tuple g = self._parent.gens() cdef Py_ssize_t i cdef bint no_generator_found = True for i in range(len(g)): - if g[i] is x: + if g[i] == x: no_generator_found = False break if no_generator_found: raise TypeError("x must be a generator of parent") - # TODO: is there a faster cython-way to do this? + # Find the minimal valuation of x by checking each term return min(e[i] for e in self.exponents()) def has_inverse_of(self, i): diff --git a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py index dd596414538..0a7ae996ea4 100644 --- a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py +++ b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py @@ -624,6 +624,29 @@ def random_element(self, min_valuation=-2, max_degree=2, terms=5, **kwds): ....: for x in L.gens(): ....: assert f.degree() <= m ....: assert f.valuation() >= n + + The ``max_degree`` must be greater than or equal to ``min_valuation``:: + + sage: L. = LaurentPolynomialRing(QQ) + sage: f = L.random_element(1, -1) + Traceback (most recent call last): + ... + ValueError: `max_degree` must be greater than or equal to `min_valuation` + + When terms is set to zero, we only expect the zero polynomial:: + + sage: L. = LaurentPolynomialRing(QQ) + sage: f = L.random_element(-10, 20, terms=0) + sage: f.is_zero() + True + + Terms must always be negative:: + + sage: L. = LaurentPolynomialRing(QQ) + sage: f = L.random_element(-10, 20, terms=-1) + Traceback (most recent call last): + ... + TypeError: cannot compute polynomial with a negative number of terms """ # Ensure the degree parameters are sensible if max_degree < min_valuation: @@ -643,9 +666,9 @@ def random_element(self, min_valuation=-2, max_degree=2, terms=5, **kwds): # Ensure terms is set correctly if terms < 0: - raise TypeError("cannot compute polynomial with a negative number of terms.") + raise TypeError("cannot compute polynomial with a negative number of terms") elif terms == 0: - return self._zero_element + return self.zero() # We now sample `terms`` terms with exponents picked randomly # with degree at most `max_degree` and valuation greater or From b99b2b475fe51d0ee5c97c91805e9a82f7cae57f Mon Sep 17 00:00:00 2001 From: Giacomo Pope <44242839+GiacomoPope@users.noreply.github.com> Date: Thu, 29 Feb 2024 10:46:30 +0000 Subject: [PATCH 095/518] Update laurent_polynomial_ring_base.py --- src/sage/rings/polynomial/laurent_polynomial_ring_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py index 0a7ae996ea4..970514e99b1 100644 --- a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py +++ b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py @@ -640,7 +640,7 @@ def random_element(self, min_valuation=-2, max_degree=2, terms=5, **kwds): sage: f.is_zero() True - Terms must always be negative:: + Terms must always be non-negative:: sage: L. = LaurentPolynomialRing(QQ) sage: f = L.random_element(-10, 20, terms=-1) From fc6aee2e24aa7ebd24f6ec20d3b427973f813ade Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Thu, 29 Feb 2024 10:48:35 +0000 Subject: [PATCH 096/518] add missing arg in randomised testing --- src/sage/rings/polynomial/laurent_polynomial_ring_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py index 970514e99b1..6544d905559 100644 --- a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py +++ b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py @@ -603,7 +603,7 @@ def random_element(self, min_valuation=-2, max_degree=2, terms=5, **kwds): ....: n, m = randint(-10, 10), randint(1, 10) ....: if m < n: ....: m, n = n, m - ....: f = L.random_element(n, m) + ....: f = L.random_element(n, m, terms=t) ....: if f.is_zero(): continue # the zero polynomial is defined to have degree -1 ....: assert len(list(f)) <= t ....: for x in L.gens(): From 2db1329ba9d691b2379f58a84749a7906b8e1150 Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Thu, 29 Feb 2024 10:49:22 +0000 Subject: [PATCH 097/518] Allow the degree to be one to test the case of 'univariate' multivariate rings --- src/sage/rings/polynomial/laurent_polynomial_ring_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py index 6544d905559..d770e7956de 100644 --- a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py +++ b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py @@ -596,7 +596,7 @@ def random_element(self, min_valuation=-2, max_degree=2, terms=5, **kwds): sage: rings = [RR, QQ, ZZ, GF(13), GF(7^3)] sage: for ring in rings: - ....: d = randint(2, 6) + ....: d = randint(1, 6) ....: t = randint(5, 20) ....: L = LaurentPolynomialRing(ring, d, 'x') ....: for _ in range(100): From 1aeccd6972c9412f4e03ba15150a5e1272f85391 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 29 Feb 2024 17:17:54 -0800 Subject: [PATCH 098/518] src/doc/en/developer/coding_basics.rst: Recommend raw strings for docstrings --- src/doc/en/developer/coding_basics.rst | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/doc/en/developer/coding_basics.rst b/src/doc/en/developer/coding_basics.rst index 764d3781d33..1272a2b1f24 100644 --- a/src/doc/en/developer/coding_basics.rst +++ b/src/doc/en/developer/coding_basics.rst @@ -667,6 +667,9 @@ You are strongly encouraged to: - Use LaTeX typesetting (see :ref:`section-latex-typeset`). +- Use raw strings (``r"""..."""``), regardless of whether the docstring + currently contains any backslashes or not. + - Liberally describe what the examples do. .. NOTE:: @@ -778,9 +781,9 @@ In Sage's documentation LaTeX code is allowed and is marked with **backticks**: ```x^2 + y^2 = 1``` yields `x^2 + y^2 = 1`. -**Backslashes:** For LaTeX commands containing backslashes, either use double -backslashes or begin the docstring with a ``r"""`` instead of ``"""``. Both of -the following are valid:: +**Backslashes:** For LaTeX commands containing backslashes, it is equivalent to +use either double backslashes or begin the docstring with a ``r"""`` instead +of ``"""``, but we strongly suggest to use the latter:: def cos(x): """ From a77a23601e6653245f006051eaab7f4cfb04118b Mon Sep 17 00:00:00 2001 From: "Linden Disney-Hogg (Work)" Date: Fri, 23 Feb 2024 14:11:55 +0000 Subject: [PATCH 099/518] Strengthened inequality required in the splitting process of rigorous_line_integral to avoid errors caused by floating point addition. --- src/sage/schemes/riemann_surfaces/riemann_surface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/schemes/riemann_surfaces/riemann_surface.py b/src/sage/schemes/riemann_surfaces/riemann_surface.py index a7760fa7101..a85a8cbc974 100644 --- a/src/sage/schemes/riemann_surfaces/riemann_surface.py +++ b/src/sage/schemes/riemann_surfaces/riemann_surface.py @@ -2230,7 +2230,7 @@ def local_N(ct, rt): rho_z = min(distances) rho_t = rho_z / (z1_minus_z0).abs() - if rho_t <= rt: + if rho_t <= rt + E_global: ball_stack.append((ncts[0], nrt, 0)) ball_stack.append((ncts[1], nrt, 0)) continue From 8a287ad56ae23c2a321c345318773826016ff0cd Mon Sep 17 00:00:00 2001 From: "Linden Disney-Hogg (Work)" Date: Mon, 26 Feb 2024 13:18:45 +0000 Subject: [PATCH 100/518] Added comment about sentinel value of 0 --- src/sage/schemes/riemann_surfaces/riemann_surface.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/sage/schemes/riemann_surfaces/riemann_surface.py b/src/sage/schemes/riemann_surfaces/riemann_surface.py index a85a8cbc974..1c1001e38ed 100644 --- a/src/sage/schemes/riemann_surfaces/riemann_surface.py +++ b/src/sage/schemes/riemann_surfaces/riemann_surface.py @@ -2224,7 +2224,12 @@ def local_N(ct, rt): ncts = [ct - rt / 2, ct + rt / 2] nrt = rt / 2 - if not lN: + # lN == 0 is a placeholder value used to indicate that a value of + # N corresponding to a segment has not yet been computed. + # Because the output of local_N is always >= 3, we have no worries + # about 0 being the output misleadingly. + # As pointed out, 0 Should perhaps be replaced as a sentinel value + if lN == 0: cz = (1 - ct) * z0 + ct * z1 distances = [(cz - b).abs() for b in self.branch_locus] rho_z = min(distances) From d39654758f8867db4dd44c07519fc4ad7c998648 Mon Sep 17 00:00:00 2001 From: "Linden Disney-Hogg (Work)" Date: Mon, 26 Feb 2024 13:24:06 +0000 Subject: [PATCH 101/518] Removed unnecessary introduction of notation --- src/sage/schemes/riemann_surfaces/riemann_surface.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/sage/schemes/riemann_surfaces/riemann_surface.py b/src/sage/schemes/riemann_surfaces/riemann_surface.py index 1c1001e38ed..f1124353a68 100644 --- a/src/sage/schemes/riemann_surfaces/riemann_surface.py +++ b/src/sage/schemes/riemann_surfaces/riemann_surface.py @@ -2208,10 +2208,9 @@ def local_N(ct, rt): ) cg = g(cz, cw) cdgdz = dgdz(cz, cg) - Delta = delta_z * cdgdz.abs() + (delta_z**2) * M_tilde / ( + M = delta_z * cdgdz.abs() + (delta_z**2) * M_tilde / ( rho_z * (rho_z - delta_z) ) - M = Delta N_required = ( (M * (self._RR.pi() + 64 / (15 * (expr**2 - 1))) / E_global).log() / (2 * expr.log()) From 5cc14487005c08fb03becc28bbe05235f3e93aaa Mon Sep 17 00:00:00 2001 From: "Linden Disney-Hogg (Work)" Date: Mon, 26 Feb 2024 13:37:33 +0000 Subject: [PATCH 102/518] Rewrote the prefactor in the computation on M --- .../riemann_surfaces/riemann_surface.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/sage/schemes/riemann_surfaces/riemann_surface.py b/src/sage/schemes/riemann_surfaces/riemann_surface.py index f1124353a68..9aa3930bd56 100644 --- a/src/sage/schemes/riemann_surfaces/riemann_surface.py +++ b/src/sage/schemes/riemann_surfaces/riemann_surface.py @@ -2186,8 +2186,21 @@ def local_N(ct, rt): rho_z = min(distances) rho_t = rho_z / (z1_minus_z0).abs() rho_t = alpha * rho_t + (1 - alpha) * rt # sqrt(rho_t*rt) could also work - rho_z = rho_t * (z1 - z0).abs() + rho_z = rho_t * (z1_minus_z0).abs() delta_z = (alpha * rho_t + (1 - alpha) * rt) * (z1_minus_z0).abs() + # delta_z and delta_z^2 / (rho_z * (rho_z - delta_z)) are the two + # prefactors that occur in the computation of the magnitude bound + # M. delta_z should never be infinite, but the second factor could + # be if rho_z - delta_z is 0. Mathematically it would never be 0 + # as we ensure rho_t > rt before running local_N, but the + # floating point operations can ruin this. + # The second prefactor is actually homogeneous in + # z1_minus_z0.abs(), so we shall compute this factor without those + # multiplications as a function of rho_t / rt which should thus be + # more resistance to floating-point errors. + pf2 = (alpha + (1 - alpha) * (rt / rho_t))**2 / ( + (1 - alpha) * (1 - rt / rho_t) + ) expr = ( rho_t / rt + ((rho_t / rt)**2 - 1).sqrt() ) # Note this is really exp(arcosh(rho_t/rt)) @@ -2208,9 +2221,7 @@ def local_N(ct, rt): ) cg = g(cz, cw) cdgdz = dgdz(cz, cg) - M = delta_z * cdgdz.abs() + (delta_z**2) * M_tilde / ( - rho_z * (rho_z - delta_z) - ) + M = delta_z * cdgdz.abs() + pf2 * M_tilde N_required = ( (M * (self._RR.pi() + 64 / (15 * (expr**2 - 1))) / E_global).log() / (2 * expr.log()) From e673dc9516265aa0721f481fb897eb1210b0edb4 Mon Sep 17 00:00:00 2001 From: "Linden Disney-Hogg (Work)" Date: Mon, 26 Feb 2024 13:43:50 +0000 Subject: [PATCH 103/518] Changed method of handling division by 0 in local_N --- src/sage/schemes/riemann_surfaces/riemann_surface.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/sage/schemes/riemann_surfaces/riemann_surface.py b/src/sage/schemes/riemann_surfaces/riemann_surface.py index 9aa3930bd56..172700bbefd 100644 --- a/src/sage/schemes/riemann_surfaces/riemann_surface.py +++ b/src/sage/schemes/riemann_surfaces/riemann_surface.py @@ -2225,8 +2225,10 @@ def local_N(ct, rt): N_required = ( (M * (self._RR.pi() + 64 / (15 * (expr**2 - 1))) / E_global).log() / (2 * expr.log()) - ).ceil() - Ni = max(Ni, N_required) + ) + if N_required == Infinity: + return 2**(self._prec) + Ni = max(Ni, N_required.ceil()) return Ni while ball_stack: @@ -2238,14 +2240,14 @@ def local_N(ct, rt): # N corresponding to a segment has not yet been computed. # Because the output of local_N is always >= 3, we have no worries # about 0 being the output misleadingly. - # As pointed out, 0 Should perhaps be replaced as a sentinel value + # As pointed out, 0 Should perhaps be replaced as a sentinel value if lN == 0: cz = (1 - ct) * z0 + ct * z1 distances = [(cz - b).abs() for b in self.branch_locus] rho_z = min(distances) rho_t = rho_z / (z1_minus_z0).abs() - if rho_t <= rt + E_global: + if rho_t <= rt: ball_stack.append((ncts[0], nrt, 0)) ball_stack.append((ncts[1], nrt, 0)) continue From ec4cb19f0c8081ab51b6a3a03b8f2dae610a7b38 Mon Sep 17 00:00:00 2001 From: "Linden Disney-Hogg (Work)" Date: Tue, 27 Feb 2024 10:48:41 +0000 Subject: [PATCH 104/518] Altered large N value to ensure it is sufficiently robust --- .../riemann_surfaces/riemann_surface.py | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/sage/schemes/riemann_surfaces/riemann_surface.py b/src/sage/schemes/riemann_surfaces/riemann_surface.py index 172700bbefd..dae285bffcd 100644 --- a/src/sage/schemes/riemann_surfaces/riemann_surface.py +++ b/src/sage/schemes/riemann_surfaces/riemann_surface.py @@ -2188,18 +2188,18 @@ def local_N(ct, rt): rho_t = alpha * rho_t + (1 - alpha) * rt # sqrt(rho_t*rt) could also work rho_z = rho_t * (z1_minus_z0).abs() delta_z = (alpha * rho_t + (1 - alpha) * rt) * (z1_minus_z0).abs() - # delta_z and delta_z^2 / (rho_z * (rho_z - delta_z)) are the two - # prefactors that occur in the computation of the magnitude bound + # delta_z and delta_z^2 / (rho_z * (rho_z - delta_z)) are the two + # prefactors that occur in the computation of the magnitude bound # M. delta_z should never be infinite, but the second factor could - # be if rho_z - delta_z is 0. Mathematically it would never be 0 + # be if rho_z - delta_z is 0. Mathematically it would never be 0 # as we ensure rho_t > rt before running local_N, but the - # floating point operations can ruin this. - # The second prefactor is actually homogeneous in - # z1_minus_z0.abs(), so we shall compute this factor without those + # floating point operations can ruin this. + # The second prefactor is actually homogeneous in + # z1_minus_z0.abs(), so we shall compute this factor without those # multiplications as a function of rho_t / rt which should thus be # more resistance to floating-point errors. pf2 = (alpha + (1 - alpha) * (rt / rho_t))**2 / ( - (1 - alpha) * (1 - rt / rho_t) + (1 - alpha) * (1 - rt / rho_t) ) expr = ( rho_t / rt + ((rho_t / rt)**2 - 1).sqrt() @@ -2227,7 +2227,7 @@ def local_N(ct, rt): / (2 * expr.log()) ) if N_required == Infinity: - return 2**(self._prec) + return 2**max(60, self._prec) Ni = max(Ni, N_required.ceil()) return Ni @@ -2236,10 +2236,10 @@ def local_N(ct, rt): ncts = [ct - rt / 2, ct + rt / 2] nrt = rt / 2 - # lN == 0 is a placeholder value used to indicate that a value of - # N corresponding to a segment has not yet been computed. + # lN == 0 is a placeholder value used to indicate that a value of + # N corresponding to a segment has not yet been computed. # Because the output of local_N is always >= 3, we have no worries - # about 0 being the output misleadingly. + # about 0 being the output misleadingly. # As pointed out, 0 Should perhaps be replaced as a sentinel value if lN == 0: cz = (1 - ct) * z0 + ct * z1 From 6155605f23b0dd3f7a830304c6d0e2ff74ce5fa2 Mon Sep 17 00:00:00 2001 From: "Linden Disney-Hogg (Work)" Date: Tue, 27 Feb 2024 10:55:58 +0000 Subject: [PATCH 105/518] Changed sentinel value from 0 to None --- .../schemes/riemann_surfaces/riemann_surface.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/sage/schemes/riemann_surfaces/riemann_surface.py b/src/sage/schemes/riemann_surfaces/riemann_surface.py index dae285bffcd..d840c7d7026 100644 --- a/src/sage/schemes/riemann_surfaces/riemann_surface.py +++ b/src/sage/schemes/riemann_surfaces/riemann_surface.py @@ -2152,7 +2152,10 @@ def rigorous_line_integral(self, upstairs_edge, differentials, bounding_data): z1 = zwt(1)[0] # list of (centre, radius) pairs that still need to be processed - ball_stack = [(self._RR(1 / 2), self._RR(1 / 2), 0)] + # None is a sentinel value to indicate that the minimum number of + # nodes required to integrate on the corresponding segment within + # the required error tolerance is not yet known. + ball_stack = [(self._RR(1 / 2), self._RR(1 / 2), None)] alpha = self._RR(912 / 1000) # alpha set manually for scaling purposes. Basic benchmarking shows # that ~0.9 is a sensible value. @@ -2236,20 +2239,15 @@ def local_N(ct, rt): ncts = [ct - rt / 2, ct + rt / 2] nrt = rt / 2 - # lN == 0 is a placeholder value used to indicate that a value of - # N corresponding to a segment has not yet been computed. - # Because the output of local_N is always >= 3, we have no worries - # about 0 being the output misleadingly. - # As pointed out, 0 Should perhaps be replaced as a sentinel value - if lN == 0: + if lN is None: cz = (1 - ct) * z0 + ct * z1 distances = [(cz - b).abs() for b in self.branch_locus] rho_z = min(distances) rho_t = rho_z / (z1_minus_z0).abs() if rho_t <= rt: - ball_stack.append((ncts[0], nrt, 0)) - ball_stack.append((ncts[1], nrt, 0)) + ball_stack.append((ncts[0], nrt, None)) + ball_stack.append((ncts[1], nrt, None)) continue lN = local_N(ct, rt) From 6c908d85f04806f3fcd06777d89a2563703a5325 Mon Sep 17 00:00:00 2001 From: "Linden Disney-Hogg (Work)" Date: Wed, 28 Feb 2024 09:14:02 +0000 Subject: [PATCH 106/518] changed method of checking if N_required is infinite --- src/sage/schemes/riemann_surfaces/riemann_surface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/schemes/riemann_surfaces/riemann_surface.py b/src/sage/schemes/riemann_surfaces/riemann_surface.py index d840c7d7026..6e36f071d5b 100644 --- a/src/sage/schemes/riemann_surfaces/riemann_surface.py +++ b/src/sage/schemes/riemann_surfaces/riemann_surface.py @@ -2229,7 +2229,7 @@ def local_N(ct, rt): (M * (self._RR.pi() + 64 / (15 * (expr**2 - 1))) / E_global).log() / (2 * expr.log()) ) - if N_required == Infinity: + if N_required.is_positive_infinity(): return 2**max(60, self._prec) Ni = max(Ni, N_required.ceil()) return Ni From 73ac14d99175b069afeb92875e5f520174f850e2 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 18 Feb 2024 19:53:25 -0800 Subject: [PATCH 107/518] build/pkgs/pyscipopt: Update to 4.4.0 --- build/pkgs/pyscipopt/checksums.ini | 6 +++--- build/pkgs/pyscipopt/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/pyscipopt/checksums.ini b/build/pkgs/pyscipopt/checksums.ini index a7c0dad6706..022bf3873fd 100644 --- a/build/pkgs/pyscipopt/checksums.ini +++ b/build/pkgs/pyscipopt/checksums.ini @@ -1,5 +1,5 @@ tarball=PySCIPOpt_no_C-VERSION.tar.gz -sha1=cd8a7a5ee2f3d72eb0505b050ab8ffcf3acba409 -md5=b8a846432a7a1e6d5c6dcc547e7a6380 -cksum=710591360 +sha1=5ae7f3d7e9d8b344ee9a4413154ae80a5ee137de +md5=32a4dced7e74a1c290b32ef0da751129 +cksum=2258510906 upstream_url=https://github.com/scipopt/PySCIPOpt/archive/refs/tags/vVERSION.tar.gz diff --git a/build/pkgs/pyscipopt/package-version.txt b/build/pkgs/pyscipopt/package-version.txt index 80895903a15..fdc6698807a 100644 --- a/build/pkgs/pyscipopt/package-version.txt +++ b/build/pkgs/pyscipopt/package-version.txt @@ -1 +1 @@ -4.3.0 +4.4.0 From 1b67815be6d56ff81f7225ee40230c5e8acd5c95 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 18 Feb 2024 19:58:18 -0800 Subject: [PATCH 108/518] build/pkgs/pyscipopt: Add https://github.com/scipopt/PySCIPOpt/pull/792 as a patch --- build/pkgs/pyscipopt/patches/792.patch | 1217 ++++++++++++++++++++++++ 1 file changed, 1217 insertions(+) create mode 100644 build/pkgs/pyscipopt/patches/792.patch diff --git a/build/pkgs/pyscipopt/patches/792.patch b/build/pkgs/pyscipopt/patches/792.patch new file mode 100644 index 00000000000..faff6076eb4 --- /dev/null +++ b/build/pkgs/pyscipopt/patches/792.patch @@ -0,0 +1,1217 @@ +From 96acc6be39b6d153cfcfe7b14741956eaf7f5851 Mon Sep 17 00:00:00 2001 +From: Matthias Koeppe +Date: Sun, 18 Feb 2024 19:47:52 -0800 +Subject: [PATCH] src/pyscipopt: Add 'noexcept' to functions that use 'with + gil' + +--- + src/pyscipopt/benders.pxi | 30 ++++++++--------- + src/pyscipopt/benderscut.pxi | 14 ++++---- + src/pyscipopt/branchrule.pxi | 18 +++++----- + src/pyscipopt/conshdlr.pxi | 64 ++++++++++++++++++------------------ + src/pyscipopt/cutsel.pxi | 14 ++++---- + src/pyscipopt/event.pxi | 18 +++++----- + src/pyscipopt/heuristic.pxi | 14 ++++---- + src/pyscipopt/nodesel.pxi | 16 ++++----- + src/pyscipopt/presol.pxi | 14 ++++---- + src/pyscipopt/pricer.pxi | 16 ++++----- + src/pyscipopt/propagator.pxi | 22 ++++++------- + src/pyscipopt/reader.pxi | 8 ++--- + src/pyscipopt/relax.pxi | 14 ++++---- + src/pyscipopt/scip.pxd | 2 +- + src/pyscipopt/scip.pxi | 4 +-- + src/pyscipopt/sepa.pxi | 16 ++++----- + 16 files changed, 142 insertions(+), 142 deletions(-) + +diff --git a/src/pyscipopt/benders.pxi b/src/pyscipopt/benders.pxi +index 3e11db189..66a394d8d 100644 +--- a/src/pyscipopt/benders.pxi ++++ b/src/pyscipopt/benders.pxi +@@ -70,10 +70,10 @@ cdef Variable getPyVar(SCIP_VAR* var): + return vardata + + +-cdef SCIP_RETCODE PyBendersCopy (SCIP* scip, SCIP_BENDERS* benders, SCIP_Bool threadsafe) with gil: ++cdef SCIP_RETCODE PyBendersCopy (SCIP* scip, SCIP_BENDERS* benders, SCIP_Bool threadsafe) noexcept with gil: + return SCIP_OKAY + +-cdef SCIP_RETCODE PyBendersFree (SCIP* scip, SCIP_BENDERS* benders) with gil: ++cdef SCIP_RETCODE PyBendersFree (SCIP* scip, SCIP_BENDERS* benders) noexcept with gil: + cdef SCIP_BENDERSDATA* bendersdata + bendersdata = SCIPbendersGetData(benders) + PyBenders = bendersdata +@@ -81,56 +81,56 @@ cdef SCIP_RETCODE PyBendersFree (SCIP* scip, SCIP_BENDERS* benders) with gil: + Py_DECREF(PyBenders) + return SCIP_OKAY + +-cdef SCIP_RETCODE PyBendersInit (SCIP* scip, SCIP_BENDERS* benders) with gil: ++cdef SCIP_RETCODE PyBendersInit (SCIP* scip, SCIP_BENDERS* benders) noexcept with gil: + cdef SCIP_BENDERSDATA* bendersdata + bendersdata = SCIPbendersGetData(benders) + PyBenders = bendersdata + PyBenders.bendersinit() + return SCIP_OKAY + +-cdef SCIP_RETCODE PyBendersExit (SCIP* scip, SCIP_BENDERS* benders) with gil: ++cdef SCIP_RETCODE PyBendersExit (SCIP* scip, SCIP_BENDERS* benders) noexcept with gil: + cdef SCIP_BENDERSDATA* bendersdata + bendersdata = SCIPbendersGetData(benders) + PyBenders = bendersdata + PyBenders.bendersexit() + return SCIP_OKAY + +-cdef SCIP_RETCODE PyBendersInitpre (SCIP* scip, SCIP_BENDERS* benders) with gil: ++cdef SCIP_RETCODE PyBendersInitpre (SCIP* scip, SCIP_BENDERS* benders) noexcept with gil: + cdef SCIP_BENDERSDATA* bendersdata + bendersdata = SCIPbendersGetData(benders) + PyBenders = bendersdata + PyBenders.bendersinitpre() + return SCIP_OKAY + +-cdef SCIP_RETCODE PyBendersExitpre (SCIP* scip, SCIP_BENDERS* benders) with gil: ++cdef SCIP_RETCODE PyBendersExitpre (SCIP* scip, SCIP_BENDERS* benders) noexcept with gil: + cdef SCIP_BENDERSDATA* bendersdata + bendersdata = SCIPbendersGetData(benders) + PyBenders = bendersdata + PyBenders.bendersexitpre() + return SCIP_OKAY + +-cdef SCIP_RETCODE PyBendersInitsol (SCIP* scip, SCIP_BENDERS* benders) with gil: ++cdef SCIP_RETCODE PyBendersInitsol (SCIP* scip, SCIP_BENDERS* benders) noexcept with gil: + cdef SCIP_BENDERSDATA* bendersdata + bendersdata = SCIPbendersGetData(benders) + PyBenders = bendersdata + PyBenders.bendersinitsol() + return SCIP_OKAY + +-cdef SCIP_RETCODE PyBendersExitsol (SCIP* scip, SCIP_BENDERS* benders) with gil: ++cdef SCIP_RETCODE PyBendersExitsol (SCIP* scip, SCIP_BENDERS* benders) noexcept with gil: + cdef SCIP_BENDERSDATA* bendersdata + bendersdata = SCIPbendersGetData(benders) + PyBenders = bendersdata + PyBenders.bendersexitsol() + return SCIP_OKAY + +-cdef SCIP_RETCODE PyBendersCreatesub (SCIP* scip, SCIP_BENDERS* benders, int probnumber) with gil: ++cdef SCIP_RETCODE PyBendersCreatesub (SCIP* scip, SCIP_BENDERS* benders, int probnumber) noexcept with gil: + cdef SCIP_BENDERSDATA* bendersdata + bendersdata = SCIPbendersGetData(benders) + PyBenders = bendersdata + PyBenders.benderscreatesub(probnumber) + return SCIP_OKAY + +-cdef SCIP_RETCODE PyBendersPresubsolve (SCIP* scip, SCIP_BENDERS* benders, SCIP_SOL* sol, SCIP_BENDERSENFOTYPE type, SCIP_Bool checkint, SCIP_Bool* infeasible, SCIP_Bool* auxviol, SCIP_Bool* skipsolve, SCIP_RESULT* result) with gil: ++cdef SCIP_RETCODE PyBendersPresubsolve (SCIP* scip, SCIP_BENDERS* benders, SCIP_SOL* sol, SCIP_BENDERSENFOTYPE type, SCIP_Bool checkint, SCIP_Bool* infeasible, SCIP_Bool* auxviol, SCIP_Bool* skipsolve, SCIP_RESULT* result) noexcept with gil: + cdef SCIP_BENDERSDATA* bendersdata + bendersdata = SCIPbendersGetData(benders) + PyBenders = bendersdata +@@ -146,7 +146,7 @@ cdef SCIP_RETCODE PyBendersPresubsolve (SCIP* scip, SCIP_BENDERS* benders, SCIP_ + result[0] = result_dict.get("result", result[0]) + return SCIP_OKAY + +-cdef SCIP_RETCODE PyBendersSolvesubconvex (SCIP* scip, SCIP_BENDERS* benders, SCIP_SOL* sol, int probnumber, SCIP_Bool onlyconvex, SCIP_Real* objective, SCIP_RESULT* result) with gil: ++cdef SCIP_RETCODE PyBendersSolvesubconvex (SCIP* scip, SCIP_BENDERS* benders, SCIP_SOL* sol, int probnumber, SCIP_Bool onlyconvex, SCIP_Real* objective, SCIP_RESULT* result) noexcept with gil: + cdef SCIP_BENDERSDATA* bendersdata + bendersdata = SCIPbendersGetData(benders) + PyBenders = bendersdata +@@ -159,7 +159,7 @@ cdef SCIP_RETCODE PyBendersSolvesubconvex (SCIP* scip, SCIP_BENDERS* benders, SC + result[0] = result_dict.get("result", result[0]) + return SCIP_OKAY + +-cdef SCIP_RETCODE PyBendersSolvesub (SCIP* scip, SCIP_BENDERS* benders, SCIP_SOL* sol, int probnumber, SCIP_Real* objective, SCIP_RESULT* result) with gil: ++cdef SCIP_RETCODE PyBendersSolvesub (SCIP* scip, SCIP_BENDERS* benders, SCIP_SOL* sol, int probnumber, SCIP_Real* objective, SCIP_RESULT* result) noexcept with gil: + cdef SCIP_BENDERSDATA* bendersdata + bendersdata = SCIPbendersGetData(benders) + PyBenders = bendersdata +@@ -174,7 +174,7 @@ cdef SCIP_RETCODE PyBendersSolvesub (SCIP* scip, SCIP_BENDERS* benders, SCIP_SOL + + cdef SCIP_RETCODE PyBendersPostsolve (SCIP* scip, SCIP_BENDERS* benders, SCIP_SOL* sol, + SCIP_BENDERSENFOTYPE type, int* mergecands, int npriomergecands, int nmergecands, SCIP_Bool checkint, +- SCIP_Bool infeasible, SCIP_Bool* merged) with gil: ++ SCIP_Bool infeasible, SCIP_Bool* merged) noexcept with gil: + cdef SCIP_BENDERSDATA* bendersdata + bendersdata = SCIPbendersGetData(benders) + PyBenders = bendersdata +@@ -190,7 +190,7 @@ cdef SCIP_RETCODE PyBendersPostsolve (SCIP* scip, SCIP_BENDERS* benders, SCIP_SO + merged[0] = result_dict.get("merged", False) + return SCIP_OKAY + +-cdef SCIP_RETCODE PyBendersFreesub (SCIP* scip, SCIP_BENDERS* benders, int probnumber) with gil: ++cdef SCIP_RETCODE PyBendersFreesub (SCIP* scip, SCIP_BENDERS* benders, int probnumber) noexcept with gil: + cdef SCIP_BENDERSDATA* bendersdata + bendersdata = SCIPbendersGetData(benders) + PyBenders = bendersdata +@@ -198,7 +198,7 @@ cdef SCIP_RETCODE PyBendersFreesub (SCIP* scip, SCIP_BENDERS* benders, int probn + return SCIP_OKAY + + #TODO: Really need to ask about the passing and returning of variables +-cdef SCIP_RETCODE PyBendersGetvar (SCIP* scip, SCIP_BENDERS* benders, SCIP_VAR* var, SCIP_VAR** mappedvar, int probnumber) with gil: ++cdef SCIP_RETCODE PyBendersGetvar (SCIP* scip, SCIP_BENDERS* benders, SCIP_VAR* var, SCIP_VAR** mappedvar, int probnumber) noexcept with gil: + cdef SCIP_BENDERSDATA* bendersdata + bendersdata = SCIPbendersGetData(benders) + PyBenders = bendersdata +diff --git a/src/pyscipopt/benderscut.pxi b/src/pyscipopt/benderscut.pxi +index 506a6f065..1ce561a06 100644 +--- a/src/pyscipopt/benderscut.pxi ++++ b/src/pyscipopt/benderscut.pxi +@@ -24,10 +24,10 @@ cdef class Benderscut: + print("python error in benderscutexec: this method needs to be implemented") + return {} + +-cdef SCIP_RETCODE PyBenderscutCopy (SCIP* scip, SCIP_BENDERS* benders, SCIP_BENDERSCUT* benderscut) with gil: ++cdef SCIP_RETCODE PyBenderscutCopy (SCIP* scip, SCIP_BENDERS* benders, SCIP_BENDERSCUT* benderscut) noexcept with gil: + return SCIP_OKAY + +-cdef SCIP_RETCODE PyBenderscutFree (SCIP* scip, SCIP_BENDERSCUT* benderscut) with gil: ++cdef SCIP_RETCODE PyBenderscutFree (SCIP* scip, SCIP_BENDERSCUT* benderscut) noexcept with gil: + cdef SCIP_BENDERSCUTDATA* benderscutdata + benderscutdata = SCIPbenderscutGetData(benderscut) + PyBenderscut = benderscutdata +@@ -35,35 +35,35 @@ cdef SCIP_RETCODE PyBenderscutFree (SCIP* scip, SCIP_BENDERSCUT* benderscut) wit + Py_DECREF(PyBenderscut) + return SCIP_OKAY + +-cdef SCIP_RETCODE PyBenderscutInit (SCIP* scip, SCIP_BENDERSCUT* benderscut) with gil: ++cdef SCIP_RETCODE PyBenderscutInit (SCIP* scip, SCIP_BENDERSCUT* benderscut) noexcept with gil: + cdef SCIP_BENDERSCUTDATA* benderscutdata + benderscutdata = SCIPbenderscutGetData(benderscut) + PyBenderscut = benderscutdata + PyBenderscut.benderscutinit() + return SCIP_OKAY + +-cdef SCIP_RETCODE PyBenderscutExit (SCIP* scip, SCIP_BENDERSCUT* benderscut) with gil: ++cdef SCIP_RETCODE PyBenderscutExit (SCIP* scip, SCIP_BENDERSCUT* benderscut) noexcept with gil: + cdef SCIP_BENDERSCUTDATA* benderscutdata + benderscutdata = SCIPbenderscutGetData(benderscut) + PyBenderscut = benderscutdata + PyBenderscut.benderscutexit() + return SCIP_OKAY + +-cdef SCIP_RETCODE PyBenderscutInitsol (SCIP* scip, SCIP_BENDERSCUT* benderscut) with gil: ++cdef SCIP_RETCODE PyBenderscutInitsol (SCIP* scip, SCIP_BENDERSCUT* benderscut) noexcept with gil: + cdef SCIP_BENDERSCUTDATA* benderscutdata + benderscutdata = SCIPbenderscutGetData(benderscut) + PyBenderscut = benderscutdata + PyBenderscut.benderscutinitsol() + return SCIP_OKAY + +-cdef SCIP_RETCODE PyBenderscutExitsol (SCIP* scip, SCIP_BENDERSCUT* benderscut) with gil: ++cdef SCIP_RETCODE PyBenderscutExitsol (SCIP* scip, SCIP_BENDERSCUT* benderscut) noexcept with gil: + cdef SCIP_BENDERSCUTDATA* benderscutdata + benderscutdata = SCIPbenderscutGetData(benderscut) + PyBenderscut = benderscutdata + PyBenderscut.benderscutexitsol() + return SCIP_OKAY + +-cdef SCIP_RETCODE PyBenderscutExec (SCIP* scip, SCIP_BENDERS* benders, SCIP_BENDERSCUT* benderscut, SCIP_SOL* sol, int probnumber, SCIP_BENDERSENFOTYPE type, SCIP_RESULT* result) with gil: ++cdef SCIP_RETCODE PyBenderscutExec (SCIP* scip, SCIP_BENDERS* benders, SCIP_BENDERSCUT* benderscut, SCIP_SOL* sol, int probnumber, SCIP_BENDERSENFOTYPE type, SCIP_RESULT* result) noexcept with gil: + cdef SCIP_BENDERSCUTDATA* benderscutdata + benderscutdata = SCIPbenderscutGetData(benderscut) + PyBenderscut = benderscutdata +diff --git a/src/pyscipopt/branchrule.pxi b/src/pyscipopt/branchrule.pxi +index 251aa33b1..2d3411d2c 100644 +--- a/src/pyscipopt/branchrule.pxi ++++ b/src/pyscipopt/branchrule.pxi +@@ -39,10 +39,10 @@ cdef class Branchrule: + + + +-cdef SCIP_RETCODE PyBranchruleCopy (SCIP* scip, SCIP_BRANCHRULE* branchrule) with gil: ++cdef SCIP_RETCODE PyBranchruleCopy (SCIP* scip, SCIP_BRANCHRULE* branchrule) noexcept with gil: + return SCIP_OKAY + +-cdef SCIP_RETCODE PyBranchruleFree (SCIP* scip, SCIP_BRANCHRULE* branchrule) with gil: ++cdef SCIP_RETCODE PyBranchruleFree (SCIP* scip, SCIP_BRANCHRULE* branchrule) noexcept with gil: + cdef SCIP_BRANCHRULEDATA* branchruledata + branchruledata = SCIPbranchruleGetData(branchrule) + PyBranchrule = branchruledata +@@ -50,35 +50,35 @@ cdef SCIP_RETCODE PyBranchruleFree (SCIP* scip, SCIP_BRANCHRULE* branchrule) wit + Py_DECREF(PyBranchrule) + return SCIP_OKAY + +-cdef SCIP_RETCODE PyBranchruleInit (SCIP* scip, SCIP_BRANCHRULE* branchrule) with gil: ++cdef SCIP_RETCODE PyBranchruleInit (SCIP* scip, SCIP_BRANCHRULE* branchrule) noexcept with gil: + cdef SCIP_BRANCHRULEDATA* branchruledata + branchruledata = SCIPbranchruleGetData(branchrule) + PyBranchrule = branchruledata + PyBranchrule.branchinit() + return SCIP_OKAY + +-cdef SCIP_RETCODE PyBranchruleExit (SCIP* scip, SCIP_BRANCHRULE* branchrule) with gil: ++cdef SCIP_RETCODE PyBranchruleExit (SCIP* scip, SCIP_BRANCHRULE* branchrule) noexcept with gil: + cdef SCIP_BRANCHRULEDATA* branchruledata + branchruledata = SCIPbranchruleGetData(branchrule) + PyBranchrule = branchruledata + PyBranchrule.branchexit() + return SCIP_OKAY + +-cdef SCIP_RETCODE PyBranchruleInitsol (SCIP* scip, SCIP_BRANCHRULE* branchrule) with gil: ++cdef SCIP_RETCODE PyBranchruleInitsol (SCIP* scip, SCIP_BRANCHRULE* branchrule) noexcept with gil: + cdef SCIP_BRANCHRULEDATA* branchruledata + branchruledata = SCIPbranchruleGetData(branchrule) + PyBranchrule = branchruledata + PyBranchrule.branchinitsol() + return SCIP_OKAY + +-cdef SCIP_RETCODE PyBranchruleExitsol (SCIP* scip, SCIP_BRANCHRULE* branchrule) with gil: ++cdef SCIP_RETCODE PyBranchruleExitsol (SCIP* scip, SCIP_BRANCHRULE* branchrule) noexcept with gil: + cdef SCIP_BRANCHRULEDATA* branchruledata + branchruledata = SCIPbranchruleGetData(branchrule) + PyBranchrule = branchruledata + PyBranchrule.branchexitsol() + return SCIP_OKAY + +-cdef SCIP_RETCODE PyBranchruleExeclp (SCIP* scip, SCIP_BRANCHRULE* branchrule, SCIP_Bool allowaddcons, SCIP_RESULT* result) with gil: ++cdef SCIP_RETCODE PyBranchruleExeclp (SCIP* scip, SCIP_BRANCHRULE* branchrule, SCIP_Bool allowaddcons, SCIP_RESULT* result) noexcept with gil: + cdef SCIP_BRANCHRULEDATA* branchruledata + branchruledata = SCIPbranchruleGetData(branchrule) + PyBranchrule = branchruledata +@@ -86,7 +86,7 @@ cdef SCIP_RETCODE PyBranchruleExeclp (SCIP* scip, SCIP_BRANCHRULE* branchrule, S + result[0] = result_dict.get("result", result[0]) + return SCIP_OKAY + +-cdef SCIP_RETCODE PyBranchruleExecext(SCIP* scip, SCIP_BRANCHRULE* branchrule, SCIP_Bool allowaddcons, SCIP_RESULT* result) with gil: ++cdef SCIP_RETCODE PyBranchruleExecext(SCIP* scip, SCIP_BRANCHRULE* branchrule, SCIP_Bool allowaddcons, SCIP_RESULT* result) noexcept with gil: + cdef SCIP_BRANCHRULEDATA* branchruledata + branchruledata = SCIPbranchruleGetData(branchrule) + PyBranchrule = branchruledata +@@ -94,7 +94,7 @@ cdef SCIP_RETCODE PyBranchruleExecext(SCIP* scip, SCIP_BRANCHRULE* branchrule, S + result[0] = result_dict.get("result", result[0]) + return SCIP_OKAY + +-cdef SCIP_RETCODE PyBranchruleExecps(SCIP* scip, SCIP_BRANCHRULE* branchrule, SCIP_Bool allowaddcons, SCIP_RESULT* result) with gil: ++cdef SCIP_RETCODE PyBranchruleExecps(SCIP* scip, SCIP_BRANCHRULE* branchrule, SCIP_Bool allowaddcons, SCIP_RESULT* result) noexcept with gil: + cdef SCIP_BRANCHRULEDATA* branchruledata + branchruledata = SCIPbranchruleGetData(branchrule) + PyBranchrule = branchruledata +diff --git a/src/pyscipopt/conshdlr.pxi b/src/pyscipopt/conshdlr.pxi +index 80c60c17c..1299ad35c 100644 +--- a/src/pyscipopt/conshdlr.pxi ++++ b/src/pyscipopt/conshdlr.pxi +@@ -150,16 +150,16 @@ cdef Constraint getPyCons(SCIP_CONS* cons): + + + +-cdef SCIP_RETCODE PyConshdlrCopy (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_Bool* valid) with gil: ++cdef SCIP_RETCODE PyConshdlrCopy (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_Bool* valid) noexcept with gil: + return SCIP_OKAY + +-cdef SCIP_RETCODE PyConsFree (SCIP* scip, SCIP_CONSHDLR* conshdlr) with gil: ++cdef SCIP_RETCODE PyConsFree (SCIP* scip, SCIP_CONSHDLR* conshdlr) noexcept with gil: + PyConshdlr = getPyConshdlr(conshdlr) + PyConshdlr.consfree() + Py_DECREF(PyConshdlr) + return SCIP_OKAY + +-cdef SCIP_RETCODE PyConsInit (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** conss, int nconss) with gil: ++cdef SCIP_RETCODE PyConsInit (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** conss, int nconss) noexcept with gil: + PyConshdlr = getPyConshdlr(conshdlr) + cdef constraints = [] + for i in range(nconss): +@@ -167,7 +167,7 @@ cdef SCIP_RETCODE PyConsInit (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** c + PyConshdlr.consinit(constraints) + return SCIP_OKAY + +-cdef SCIP_RETCODE PyConsExit (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** conss, int nconss) with gil: ++cdef SCIP_RETCODE PyConsExit (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** conss, int nconss) noexcept with gil: + PyConshdlr = getPyConshdlr(conshdlr) + cdef constraints = [] + for i in range(nconss): +@@ -175,7 +175,7 @@ cdef SCIP_RETCODE PyConsExit (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** c + PyConshdlr.consexit(constraints) + return SCIP_OKAY + +-cdef SCIP_RETCODE PyConsInitpre (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** conss, int nconss) with gil: ++cdef SCIP_RETCODE PyConsInitpre (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** conss, int nconss) noexcept with gil: + PyConshdlr = getPyConshdlr(conshdlr) + cdef constraints = [] + for i in range(nconss): +@@ -183,7 +183,7 @@ cdef SCIP_RETCODE PyConsInitpre (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* + PyConshdlr.consinitpre(constraints) + return SCIP_OKAY + +-cdef SCIP_RETCODE PyConsExitpre (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** conss, int nconss) with gil: ++cdef SCIP_RETCODE PyConsExitpre (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** conss, int nconss) noexcept with gil: + PyConshdlr = getPyConshdlr(conshdlr) + cdef constraints = [] + for i in range(nconss): +@@ -191,7 +191,7 @@ cdef SCIP_RETCODE PyConsExitpre (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* + PyConshdlr.consexitpre(constraints) + return SCIP_OKAY + +-cdef SCIP_RETCODE PyConsInitsol (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** conss, int nconss) with gil: ++cdef SCIP_RETCODE PyConsInitsol (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** conss, int nconss) noexcept with gil: + PyConshdlr = getPyConshdlr(conshdlr) + cdef constraints = [] + for i in range(nconss): +@@ -199,7 +199,7 @@ cdef SCIP_RETCODE PyConsInitsol (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* + PyConshdlr.consinitsol(constraints) + return SCIP_OKAY + +-cdef SCIP_RETCODE PyConsExitsol (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** conss, int nconss, SCIP_Bool restart) with gil: ++cdef SCIP_RETCODE PyConsExitsol (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** conss, int nconss, SCIP_Bool restart) noexcept with gil: + PyConshdlr = getPyConshdlr(conshdlr) + cdef constraints = [] + for i in range(nconss): +@@ -207,7 +207,7 @@ cdef SCIP_RETCODE PyConsExitsol (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* + PyConshdlr.consexitsol(constraints, restart) + return SCIP_OKAY + +-cdef SCIP_RETCODE PyConsDelete (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* cons, SCIP_CONSDATA** consdata) with gil: ++cdef SCIP_RETCODE PyConsDelete (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* cons, SCIP_CONSDATA** consdata) noexcept with gil: + PyConshdlr = getPyConshdlr(conshdlr) + PyCons = getPyCons(cons) + assert consdata[0] == PyCons +@@ -216,7 +216,7 @@ cdef SCIP_RETCODE PyConsDelete (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* + Py_DECREF(PyCons) + return SCIP_OKAY + +-cdef SCIP_RETCODE PyConsTrans (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* sourcecons, SCIP_CONS** targetcons) with gil: ++cdef SCIP_RETCODE PyConsTrans (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* sourcecons, SCIP_CONS** targetcons) noexcept with gil: + cdef Constraint PyTargetCons + PyConshdlr = getPyConshdlr(conshdlr) + PySourceCons = getPyCons(sourcecons) +@@ -235,7 +235,7 @@ cdef SCIP_RETCODE PyConsTrans (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* s + PySourceCons.isRemovable(), PySourceCons.isStickingAtNode())) + return SCIP_OKAY + +-cdef SCIP_RETCODE PyConsInitlp (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** conss, int nconss, SCIP_Bool* infeasible) with gil: ++cdef SCIP_RETCODE PyConsInitlp (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** conss, int nconss, SCIP_Bool* infeasible) noexcept with gil: + PyConshdlr = getPyConshdlr(conshdlr) + cdef constraints = [] + for i in range(nconss): +@@ -244,7 +244,7 @@ cdef SCIP_RETCODE PyConsInitlp (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** + infeasible[0] = result_dict.get("infeasible", infeasible[0]) + return SCIP_OKAY + +-cdef SCIP_RETCODE PyConsSepalp (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** conss, int nconss, int nusefulconss, SCIP_RESULT* result) with gil: ++cdef SCIP_RETCODE PyConsSepalp (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** conss, int nconss, int nusefulconss, SCIP_RESULT* result) noexcept with gil: + PyConshdlr = getPyConshdlr(conshdlr) + cdef constraints = [] + for i in range(nconss): +@@ -254,7 +254,7 @@ cdef SCIP_RETCODE PyConsSepalp (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** + return SCIP_OKAY + + cdef SCIP_RETCODE PyConsSepasol (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** conss, int nconss, int nusefulconss, +- SCIP_SOL* sol, SCIP_RESULT* result) with gil: ++ SCIP_SOL* sol, SCIP_RESULT* result) noexcept with gil: + PyConshdlr = getPyConshdlr(conshdlr) + cdef constraints = [] + for i in range(nconss): +@@ -265,7 +265,7 @@ cdef SCIP_RETCODE PyConsSepasol (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* + return SCIP_OKAY + + cdef SCIP_RETCODE PyConsEnfolp (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** conss, int nconss, int nusefulconss, +- SCIP_Bool solinfeasible, SCIP_RESULT* result) with gil: ++ SCIP_Bool solinfeasible, SCIP_RESULT* result) noexcept with gil: + PyConshdlr = getPyConshdlr(conshdlr) + cdef constraints = [] + for i in range(nconss): +@@ -274,7 +274,7 @@ cdef SCIP_RETCODE PyConsEnfolp (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** + result[0] = result_dict.get("result", result[0]) + return SCIP_OKAY + +-cdef SCIP_RETCODE PyConsEnforelax (SCIP* scip, SCIP_SOL* sol, SCIP_CONSHDLR* conshdlr, SCIP_CONS** conss, int nconss, int nusefulconss, SCIP_Bool solinfeasible, SCIP_RESULT* result) with gil: ++cdef SCIP_RETCODE PyConsEnforelax (SCIP* scip, SCIP_SOL* sol, SCIP_CONSHDLR* conshdlr, SCIP_CONS** conss, int nconss, int nusefulconss, SCIP_Bool solinfeasible, SCIP_RESULT* result) noexcept with gil: + PyConshdlr = getPyConshdlr(conshdlr) + cdef constraints = [] + for i in range(nconss): +@@ -285,7 +285,7 @@ cdef SCIP_RETCODE PyConsEnforelax (SCIP* scip, SCIP_SOL* sol, SCIP_CONSHDLR* con + return SCIP_OKAY + + cdef SCIP_RETCODE PyConsEnfops (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** conss, int nconss, int nusefulconss, +- SCIP_Bool solinfeasible, SCIP_Bool objinfeasible, SCIP_RESULT* result) with gil: ++ SCIP_Bool solinfeasible, SCIP_Bool objinfeasible, SCIP_RESULT* result) noexcept with gil: + PyConshdlr = getPyConshdlr(conshdlr) + cdef constraints = [] + for i in range(nconss): +@@ -295,7 +295,7 @@ cdef SCIP_RETCODE PyConsEnfops (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** + return SCIP_OKAY + + cdef SCIP_RETCODE PyConsCheck (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** conss, int nconss, SCIP_SOL* sol, SCIP_Bool checkintegrality, +- SCIP_Bool checklprows, SCIP_Bool printreason, SCIP_Bool completely, SCIP_RESULT* result) with gil: ++ SCIP_Bool checklprows, SCIP_Bool printreason, SCIP_Bool completely, SCIP_RESULT* result) noexcept with gil: + PyConshdlr = getPyConshdlr(conshdlr) + cdef constraints = [] + for i in range(nconss): +@@ -306,7 +306,7 @@ cdef SCIP_RETCODE PyConsCheck (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** + return SCIP_OKAY + + cdef SCIP_RETCODE PyConsProp (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** conss, int nconss, int nusefulconss, int nmarkedconss, +- SCIP_PROPTIMING proptiming, SCIP_RESULT* result) with gil: ++ SCIP_PROPTIMING proptiming, SCIP_RESULT* result) noexcept with gil: + PyConshdlr = getPyConshdlr(conshdlr) + cdef constraints = [] + for i in range(nconss): +@@ -319,7 +319,7 @@ cdef SCIP_RETCODE PyConsPresol (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** + int nnewfixedvars, int nnewaggrvars, int nnewchgvartypes, int nnewchgbds, int nnewholes, + int nnewdelconss, int nnewaddconss, int nnewupgdconss, int nnewchgcoefs, int nnewchgsides, + int* nfixedvars, int* naggrvars, int* nchgvartypes, int* nchgbds, int* naddholes, +- int* ndelconss, int* naddconss, int* nupgdconss, int* nchgcoefs, int* nchgsides, SCIP_RESULT* result) with gil: ++ int* ndelconss, int* naddconss, int* nupgdconss, int* nchgcoefs, int* nchgsides, SCIP_RESULT* result) noexcept with gil: + PyConshdlr = getPyConshdlr(conshdlr) + cdef constraints = [] + for i in range(nconss): +@@ -354,12 +354,12 @@ cdef SCIP_RETCODE PyConsPresol (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** + return SCIP_OKAY + + cdef SCIP_RETCODE PyConsResprop (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* cons, SCIP_VAR* infervar, int inferinfo, +- SCIP_BOUNDTYPE boundtype, SCIP_BDCHGIDX* bdchgidx, SCIP_Real relaxedbd, SCIP_RESULT* result) with gil: ++ SCIP_BOUNDTYPE boundtype, SCIP_BDCHGIDX* bdchgidx, SCIP_Real relaxedbd, SCIP_RESULT* result) noexcept with gil: + PyConshdlr = getPyConshdlr(conshdlr) + PyConshdlr.consresprop() + return SCIP_OKAY + +-cdef SCIP_RETCODE PyConsLock (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* cons, SCIP_LOCKTYPE locktype, int nlockspos, int nlocksneg) with gil: ++cdef SCIP_RETCODE PyConsLock (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* cons, SCIP_LOCKTYPE locktype, int nlockspos, int nlocksneg) noexcept with gil: + PyConshdlr = getPyConshdlr(conshdlr) + if cons == NULL: + PyConshdlr.conslock(None, locktype, nlockspos, nlocksneg) +@@ -368,31 +368,31 @@ cdef SCIP_RETCODE PyConsLock (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* co + PyConshdlr.conslock(PyCons, locktype, nlockspos, nlocksneg) + return SCIP_OKAY + +-cdef SCIP_RETCODE PyConsActive (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* cons) with gil: ++cdef SCIP_RETCODE PyConsActive (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* cons) noexcept with gil: + PyConshdlr = getPyConshdlr(conshdlr) + PyCons = getPyCons(cons) + PyConshdlr.consactive(PyCons) + return SCIP_OKAY + +-cdef SCIP_RETCODE PyConsDeactive (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* cons) with gil: ++cdef SCIP_RETCODE PyConsDeactive (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* cons) noexcept with gil: + PyConshdlr = getPyConshdlr(conshdlr) + PyCons = getPyCons(cons) + PyConshdlr.consdeactive(PyCons) + return SCIP_OKAY + +-cdef SCIP_RETCODE PyConsEnable (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* cons) with gil: ++cdef SCIP_RETCODE PyConsEnable (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* cons) noexcept with gil: + PyConshdlr = getPyConshdlr(conshdlr) + PyCons = getPyCons(cons) + PyConshdlr.consenable(PyCons) + return SCIP_OKAY + +-cdef SCIP_RETCODE PyConsDisable (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* cons) with gil: ++cdef SCIP_RETCODE PyConsDisable (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* cons) noexcept with gil: + PyConshdlr = getPyConshdlr(conshdlr) + PyCons = getPyCons(cons) + PyConshdlr.consdisable(PyCons) + return SCIP_OKAY + +-cdef SCIP_RETCODE PyConsDelvars (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** conss, int nconss) with gil: ++cdef SCIP_RETCODE PyConsDelvars (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** conss, int nconss) noexcept with gil: + PyConshdlr = getPyConshdlr(conshdlr) + cdef constraints = [] + for i in range(nconss): +@@ -400,7 +400,7 @@ cdef SCIP_RETCODE PyConsDelvars (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* + PyConshdlr.consdelvars(constraints) + return SCIP_OKAY + +-cdef SCIP_RETCODE PyConsPrint (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* cons, FILE* file) with gil: ++cdef SCIP_RETCODE PyConsPrint (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* cons, FILE* file) noexcept with gil: + PyConshdlr = getPyConshdlr(conshdlr) + PyCons = getPyCons(cons) + # TODO: pass file +@@ -411,7 +411,7 @@ cdef SCIP_RETCODE PyConsCopy (SCIP* scip, SCIP_CONS** cons, const char* name, SC + SCIP_CONS* sourcecons, SCIP_HASHMAP* varmap, SCIP_HASHMAP* consmap, SCIP_Bool initial, + SCIP_Bool separate, SCIP_Bool enforce, SCIP_Bool check, SCIP_Bool propagate, SCIP_Bool local, + SCIP_Bool modifiable, SCIP_Bool dynamic, SCIP_Bool removable, SCIP_Bool stickingatnode, +- SCIP_Bool isglobal, SCIP_Bool* valid) with gil: ++ SCIP_Bool isglobal, SCIP_Bool* valid) noexcept with gil: + # TODO everything! + PyConshdlr = getPyConshdlr(sourceconshdlr) + PyConshdlr.conscopy() +@@ -421,14 +421,14 @@ cdef SCIP_RETCODE PyConsCopy (SCIP* scip, SCIP_CONS** cons, const char* name, SC + cdef SCIP_RETCODE PyConsParse (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** cons, const char* name, const char* str, + SCIP_Bool initial, SCIP_Bool separate, SCIP_Bool enforce, SCIP_Bool check, SCIP_Bool propagate, + SCIP_Bool local, SCIP_Bool modifiable, SCIP_Bool dynamic, SCIP_Bool removable, +- SCIP_Bool stickingatnode, SCIP_Bool* success) with gil: ++ SCIP_Bool stickingatnode, SCIP_Bool* success) noexcept with gil: + # TODO everything! + PyConshdlr = getPyConshdlr(conshdlr) + PyConshdlr.consparse() + success[0] = False + return SCIP_OKAY + +-cdef SCIP_RETCODE PyConsGetvars (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* cons, SCIP_VAR** vars, int varssize, SCIP_Bool* success) with gil: ++cdef SCIP_RETCODE PyConsGetvars (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* cons, SCIP_VAR** vars, int varssize, SCIP_Bool* success) noexcept with gil: + # TODO + PyConshdlr = getPyConshdlr(conshdlr) + PyCons = getPyCons(cons) +@@ -436,7 +436,7 @@ cdef SCIP_RETCODE PyConsGetvars (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* + success[0] = False + return SCIP_OKAY + +-cdef SCIP_RETCODE PyConsGetnvars (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* cons, int* nvars, SCIP_Bool* success) with gil: ++cdef SCIP_RETCODE PyConsGetnvars (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* cons, int* nvars, SCIP_Bool* success) noexcept with gil: + PyConshdlr = getPyConshdlr(conshdlr) + PyCons = getPyCons(cons) + result_dict = PyConshdlr.consgetnvars(PyCons) +@@ -445,7 +445,7 @@ cdef SCIP_RETCODE PyConsGetnvars (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS + return SCIP_OKAY + + cdef SCIP_RETCODE PyConsGetdivebdchgs (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_DIVESET* diveset, SCIP_SOL* sol, +- SCIP_Bool* success, SCIP_Bool* infeasible) with gil: ++ SCIP_Bool* success, SCIP_Bool* infeasible) noexcept with gil: + # TODO + PyConshdlr = getPyConshdlr(conshdlr) + PyConshdlr.consgetdivebdchgs() +diff --git a/src/pyscipopt/cutsel.pxi b/src/pyscipopt/cutsel.pxi +index e953cb1e9..d259fb28e 100644 +--- a/src/pyscipopt/cutsel.pxi ++++ b/src/pyscipopt/cutsel.pxi +@@ -29,10 +29,10 @@ cdef class Cutsel: + return {} + + +-cdef SCIP_RETCODE PyCutselCopy (SCIP* scip, SCIP_CUTSEL* cutsel) with gil: ++cdef SCIP_RETCODE PyCutselCopy (SCIP* scip, SCIP_CUTSEL* cutsel) noexcept with gil: + return SCIP_OKAY + +-cdef SCIP_RETCODE PyCutselFree (SCIP* scip, SCIP_CUTSEL* cutsel) with gil: ++cdef SCIP_RETCODE PyCutselFree (SCIP* scip, SCIP_CUTSEL* cutsel) noexcept with gil: + cdef SCIP_CUTSELDATA* cutseldata + cutseldata = SCIPcutselGetData(cutsel) + PyCutsel = cutseldata +@@ -40,7 +40,7 @@ cdef SCIP_RETCODE PyCutselFree (SCIP* scip, SCIP_CUTSEL* cutsel) with gil: + Py_DECREF(PyCutsel) + return SCIP_OKAY + +-cdef SCIP_RETCODE PyCutselInit (SCIP* scip, SCIP_CUTSEL* cutsel) with gil: ++cdef SCIP_RETCODE PyCutselInit (SCIP* scip, SCIP_CUTSEL* cutsel) noexcept with gil: + cdef SCIP_CUTSELDATA* cutseldata + cutseldata = SCIPcutselGetData(cutsel) + PyCutsel = cutseldata +@@ -48,21 +48,21 @@ cdef SCIP_RETCODE PyCutselInit (SCIP* scip, SCIP_CUTSEL* cutsel) with gil: + return SCIP_OKAY + + +-cdef SCIP_RETCODE PyCutselExit (SCIP* scip, SCIP_CUTSEL* cutsel) with gil: ++cdef SCIP_RETCODE PyCutselExit (SCIP* scip, SCIP_CUTSEL* cutsel) noexcept with gil: + cdef SCIP_CUTSELDATA* cutseldata + cutseldata = SCIPcutselGetData(cutsel) + PyCutsel = cutseldata + PyCutsel.cutselexit() + return SCIP_OKAY + +-cdef SCIP_RETCODE PyCutselInitsol (SCIP* scip, SCIP_CUTSEL* cutsel) with gil: ++cdef SCIP_RETCODE PyCutselInitsol (SCIP* scip, SCIP_CUTSEL* cutsel) noexcept with gil: + cdef SCIP_CUTSELDATA* cutseldata + cutseldata = SCIPcutselGetData(cutsel) + PyCutsel = cutseldata + PyCutsel.cutselinitsol() + return SCIP_OKAY + +-cdef SCIP_RETCODE PyCutselExitsol (SCIP* scip, SCIP_CUTSEL* cutsel) with gil: ++cdef SCIP_RETCODE PyCutselExitsol (SCIP* scip, SCIP_CUTSEL* cutsel) noexcept with gil: + cdef SCIP_CUTSELDATA* cutseldata + cutseldata = SCIPcutselGetData(cutsel) + PyCutsel = cutseldata +@@ -71,7 +71,7 @@ cdef SCIP_RETCODE PyCutselExitsol (SCIP* scip, SCIP_CUTSEL* cutsel) with gil: + + cdef SCIP_RETCODE PyCutselSelect (SCIP* scip, SCIP_CUTSEL* cutsel, SCIP_ROW** cuts, int ncuts, + SCIP_ROW** forcedcuts, int nforcedcuts, SCIP_Bool root, int maxnselectedcuts, +- int* nselectedcuts, SCIP_RESULT* result) with gil: ++ int* nselectedcuts, SCIP_RESULT* result) noexcept with gil: + cdef SCIP_CUTSELDATA* cutseldata + cdef SCIP_ROW* scip_row + cutseldata = SCIPcutselGetData(cutsel) +diff --git a/src/pyscipopt/event.pxi b/src/pyscipopt/event.pxi +index 95c8bc1f4..914e882ed 100644 +--- a/src/pyscipopt/event.pxi ++++ b/src/pyscipopt/event.pxi +@@ -39,48 +39,48 @@ cdef class Eventhdlr: + + + # local helper functions for the interface +-cdef Eventhdlr getPyEventhdlr(SCIP_EVENTHDLR* eventhdlr) with gil: ++cdef Eventhdlr getPyEventhdlr(SCIP_EVENTHDLR* eventhdlr) noexcept with gil: + cdef SCIP_EVENTHDLRDATA* eventhdlrdata + eventhdlrdata = SCIPeventhdlrGetData(eventhdlr) + return eventhdlrdata + +-cdef SCIP_RETCODE PyEventCopy (SCIP* scip, SCIP_EVENTHDLR* eventhdlr) with gil: ++cdef SCIP_RETCODE PyEventCopy (SCIP* scip, SCIP_EVENTHDLR* eventhdlr) noexcept with gil: + PyEventhdlr = getPyEventhdlr(eventhdlr) + PyEventhdlr.eventcopy() + return SCIP_OKAY + +-cdef SCIP_RETCODE PyEventFree (SCIP* scip, SCIP_EVENTHDLR* eventhdlr) with gil: ++cdef SCIP_RETCODE PyEventFree (SCIP* scip, SCIP_EVENTHDLR* eventhdlr) noexcept with gil: + PyEventhdlr = getPyEventhdlr(eventhdlr) + PyEventhdlr.eventfree() + Py_DECREF(PyEventhdlr) + return SCIP_OKAY + +-cdef SCIP_RETCODE PyEventInit (SCIP* scip, SCIP_EVENTHDLR* eventhdlr) with gil: ++cdef SCIP_RETCODE PyEventInit (SCIP* scip, SCIP_EVENTHDLR* eventhdlr) noexcept with gil: + PyEventhdlr = getPyEventhdlr(eventhdlr) + PyEventhdlr.eventinit() + return SCIP_OKAY + +-cdef SCIP_RETCODE PyEventExit (SCIP* scip, SCIP_EVENTHDLR* eventhdlr) with gil: ++cdef SCIP_RETCODE PyEventExit (SCIP* scip, SCIP_EVENTHDLR* eventhdlr) noexcept with gil: + PyEventhdlr = getPyEventhdlr(eventhdlr) + PyEventhdlr.eventexit() + return SCIP_OKAY + +-cdef SCIP_RETCODE PyEventInitsol (SCIP* scip, SCIP_EVENTHDLR* eventhdlr) with gil: ++cdef SCIP_RETCODE PyEventInitsol (SCIP* scip, SCIP_EVENTHDLR* eventhdlr) noexcept with gil: + PyEventhdlr = getPyEventhdlr(eventhdlr) + PyEventhdlr.eventinitsol() + return SCIP_OKAY + +-cdef SCIP_RETCODE PyEventExitsol (SCIP* scip, SCIP_EVENTHDLR* eventhdlr) with gil: ++cdef SCIP_RETCODE PyEventExitsol (SCIP* scip, SCIP_EVENTHDLR* eventhdlr) noexcept with gil: + PyEventhdlr = getPyEventhdlr(eventhdlr) + PyEventhdlr.eventexitsol() + return SCIP_OKAY + +-cdef SCIP_RETCODE PyEventDelete (SCIP* scip, SCIP_EVENTHDLR* eventhdlr, SCIP_EVENTDATA** eventdata) with gil: ++cdef SCIP_RETCODE PyEventDelete (SCIP* scip, SCIP_EVENTHDLR* eventhdlr, SCIP_EVENTDATA** eventdata) noexcept with gil: + PyEventhdlr = getPyEventhdlr(eventhdlr) + PyEventhdlr.eventdelete() + return SCIP_OKAY + +-cdef SCIP_RETCODE PyEventExec (SCIP* scip, SCIP_EVENTHDLR* eventhdlr, SCIP_EVENT* event, SCIP_EVENTDATA* eventdata) with gil: ++cdef SCIP_RETCODE PyEventExec (SCIP* scip, SCIP_EVENTHDLR* eventhdlr, SCIP_EVENT* event, SCIP_EVENTDATA* eventdata) noexcept with gil: + PyEventhdlr = getPyEventhdlr(eventhdlr) + PyEvent = Event() + PyEvent.event = event +diff --git a/src/pyscipopt/heuristic.pxi b/src/pyscipopt/heuristic.pxi +index 2980a1aee..930315630 100644 +--- a/src/pyscipopt/heuristic.pxi ++++ b/src/pyscipopt/heuristic.pxi +@@ -31,10 +31,10 @@ cdef class Heur: + + + +-cdef SCIP_RETCODE PyHeurCopy (SCIP* scip, SCIP_HEUR* heur) with gil: ++cdef SCIP_RETCODE PyHeurCopy (SCIP* scip, SCIP_HEUR* heur) noexcept with gil: + return SCIP_OKAY + +-cdef SCIP_RETCODE PyHeurFree (SCIP* scip, SCIP_HEUR* heur) with gil: ++cdef SCIP_RETCODE PyHeurFree (SCIP* scip, SCIP_HEUR* heur) noexcept with gil: + cdef SCIP_HEURDATA* heurdata + heurdata = SCIPheurGetData(heur) + PyHeur = heurdata +@@ -42,35 +42,35 @@ cdef SCIP_RETCODE PyHeurFree (SCIP* scip, SCIP_HEUR* heur) with gil: + Py_DECREF(PyHeur) + return SCIP_OKAY + +-cdef SCIP_RETCODE PyHeurInit (SCIP* scip, SCIP_HEUR* heur) with gil: ++cdef SCIP_RETCODE PyHeurInit (SCIP* scip, SCIP_HEUR* heur) noexcept with gil: + cdef SCIP_HEURDATA* heurdata + heurdata = SCIPheurGetData(heur) + PyHeur = heurdata + PyHeur.heurinit() + return SCIP_OKAY + +-cdef SCIP_RETCODE PyHeurExit (SCIP* scip, SCIP_HEUR* heur) with gil: ++cdef SCIP_RETCODE PyHeurExit (SCIP* scip, SCIP_HEUR* heur) noexcept with gil: + cdef SCIP_HEURDATA* heurdata + heurdata = SCIPheurGetData(heur) + PyHeur = heurdata + PyHeur.heurexit() + return SCIP_OKAY + +-cdef SCIP_RETCODE PyHeurInitsol (SCIP* scip, SCIP_HEUR* heur) with gil: ++cdef SCIP_RETCODE PyHeurInitsol (SCIP* scip, SCIP_HEUR* heur) noexcept with gil: + cdef SCIP_HEURDATA* heurdata + heurdata = SCIPheurGetData(heur) + PyHeur = heurdata + PyHeur.heurinitsol() + return SCIP_OKAY + +-cdef SCIP_RETCODE PyHeurExitsol (SCIP* scip, SCIP_HEUR* heur) with gil: ++cdef SCIP_RETCODE PyHeurExitsol (SCIP* scip, SCIP_HEUR* heur) noexcept with gil: + cdef SCIP_HEURDATA* heurdata + heurdata = SCIPheurGetData(heur) + PyHeur = heurdata + PyHeur.heurexitsol() + return SCIP_OKAY + +-cdef SCIP_RETCODE PyHeurExec (SCIP* scip, SCIP_HEUR* heur, SCIP_HEURTIMING heurtiming, SCIP_Bool nodeinfeasible, SCIP_RESULT* result) with gil: ++cdef SCIP_RETCODE PyHeurExec (SCIP* scip, SCIP_HEUR* heur, SCIP_HEURTIMING heurtiming, SCIP_Bool nodeinfeasible, SCIP_RESULT* result) noexcept with gil: + cdef SCIP_HEURDATA* heurdata + heurdata = SCIPheurGetData(heur) + PyHeur = heurdata +diff --git a/src/pyscipopt/nodesel.pxi b/src/pyscipopt/nodesel.pxi +index 4d795bc9d..a3e832f15 100644 +--- a/src/pyscipopt/nodesel.pxi ++++ b/src/pyscipopt/nodesel.pxi +@@ -42,10 +42,10 @@ cdef class Nodesel: + return 0 + + +-cdef SCIP_RETCODE PyNodeselCopy (SCIP* scip, SCIP_NODESEL* nodesel) with gil: ++cdef SCIP_RETCODE PyNodeselCopy (SCIP* scip, SCIP_NODESEL* nodesel) noexcept with gil: + return SCIP_OKAY + +-cdef SCIP_RETCODE PyNodeselFree (SCIP* scip, SCIP_NODESEL* nodesel) with gil: ++cdef SCIP_RETCODE PyNodeselFree (SCIP* scip, SCIP_NODESEL* nodesel) noexcept with gil: + cdef SCIP_NODESELDATA* nodeseldata + nodeseldata = SCIPnodeselGetData(nodesel) + PyNodesel = nodeseldata +@@ -53,7 +53,7 @@ cdef SCIP_RETCODE PyNodeselFree (SCIP* scip, SCIP_NODESEL* nodesel) with gil: + Py_DECREF(PyNodesel) + return SCIP_OKAY + +-cdef SCIP_RETCODE PyNodeselInit (SCIP* scip, SCIP_NODESEL* nodesel) with gil: ++cdef SCIP_RETCODE PyNodeselInit (SCIP* scip, SCIP_NODESEL* nodesel) noexcept with gil: + cdef SCIP_NODESELDATA* nodeseldata + nodeseldata = SCIPnodeselGetData(nodesel) + PyNodesel = nodeseldata +@@ -61,28 +61,28 @@ cdef SCIP_RETCODE PyNodeselInit (SCIP* scip, SCIP_NODESEL* nodesel) with gil: + return SCIP_OKAY + + +-cdef SCIP_RETCODE PyNodeselExit (SCIP* scip, SCIP_NODESEL* nodesel) with gil: ++cdef SCIP_RETCODE PyNodeselExit (SCIP* scip, SCIP_NODESEL* nodesel) noexcept with gil: + cdef SCIP_NODESELDATA* nodeseldata + nodeseldata = SCIPnodeselGetData(nodesel) + PyNodesel = nodeseldata + PyNodesel.nodeexit() + return SCIP_OKAY + +-cdef SCIP_RETCODE PyNodeselInitsol (SCIP* scip, SCIP_NODESEL* nodesel) with gil: ++cdef SCIP_RETCODE PyNodeselInitsol (SCIP* scip, SCIP_NODESEL* nodesel) noexcept with gil: + cdef SCIP_NODESELDATA* nodeseldata + nodeseldata = SCIPnodeselGetData(nodesel) + PyNodesel = nodeseldata + PyNodesel.nodeinitsol() + return SCIP_OKAY + +-cdef SCIP_RETCODE PyNodeselExitsol (SCIP* scip, SCIP_NODESEL* nodesel) with gil: ++cdef SCIP_RETCODE PyNodeselExitsol (SCIP* scip, SCIP_NODESEL* nodesel) noexcept with gil: + cdef SCIP_NODESELDATA* nodeseldata + nodeseldata = SCIPnodeselGetData(nodesel) + PyNodesel = nodeseldata + PyNodesel.nodeexitsol() + return SCIP_OKAY + +-cdef SCIP_RETCODE PyNodeselSelect (SCIP* scip, SCIP_NODESEL* nodesel, SCIP_NODE** selnode) with gil: ++cdef SCIP_RETCODE PyNodeselSelect (SCIP* scip, SCIP_NODESEL* nodesel, SCIP_NODE** selnode) noexcept with gil: + cdef SCIP_NODESELDATA* nodeseldata + nodeseldata = SCIPnodeselGetData(nodesel) + PyNodesel = nodeseldata +@@ -91,7 +91,7 @@ cdef SCIP_RETCODE PyNodeselSelect (SCIP* scip, SCIP_NODESEL* nodesel, SCIP_NODE* + selnode[0] = selected_node.scip_node + return SCIP_OKAY + +-cdef int PyNodeselComp (SCIP* scip, SCIP_NODESEL* nodesel, SCIP_NODE* node1, SCIP_NODE* node2) with gil: ++cdef int PyNodeselComp (SCIP* scip, SCIP_NODESEL* nodesel, SCIP_NODE* node1, SCIP_NODE* node2) noexcept with gil: + cdef SCIP_NODESELDATA* nodeseldata + nodeseldata = SCIPnodeselGetData(nodesel) + PyNodesel = nodeseldata +diff --git a/src/pyscipopt/presol.pxi b/src/pyscipopt/presol.pxi +index d2b9115a5..13bd9a623 100644 +--- a/src/pyscipopt/presol.pxi ++++ b/src/pyscipopt/presol.pxi +@@ -30,10 +30,10 @@ cdef class Presol: + + + +-cdef SCIP_RETCODE PyPresolCopy (SCIP* scip, SCIP_PRESOL* presol) with gil: ++cdef SCIP_RETCODE PyPresolCopy (SCIP* scip, SCIP_PRESOL* presol) noexcept with gil: + return SCIP_OKAY + +-cdef SCIP_RETCODE PyPresolFree (SCIP* scip, SCIP_PRESOL* presol) with gil: ++cdef SCIP_RETCODE PyPresolFree (SCIP* scip, SCIP_PRESOL* presol) noexcept with gil: + cdef SCIP_PRESOLDATA* presoldata + presoldata = SCIPpresolGetData(presol) + PyPresol = presoldata +@@ -41,14 +41,14 @@ cdef SCIP_RETCODE PyPresolFree (SCIP* scip, SCIP_PRESOL* presol) with gil: + Py_DECREF(PyPresol) + return SCIP_OKAY + +-cdef SCIP_RETCODE PyPresolInit (SCIP* scip, SCIP_PRESOL* presol) with gil: ++cdef SCIP_RETCODE PyPresolInit (SCIP* scip, SCIP_PRESOL* presol) noexcept with gil: + cdef SCIP_PRESOLDATA* presoldata + presoldata = SCIPpresolGetData(presol) + PyPresol = presoldata + PyPresol.presolinit() + return SCIP_OKAY + +-cdef SCIP_RETCODE PyPresolExit (SCIP* scip, SCIP_PRESOL* presol) with gil: ++cdef SCIP_RETCODE PyPresolExit (SCIP* scip, SCIP_PRESOL* presol) noexcept with gil: + cdef SCIP_PRESOLDATA* presoldata + presoldata = SCIPpresolGetData(presol) + PyPresol = presoldata +@@ -56,14 +56,14 @@ cdef SCIP_RETCODE PyPresolExit (SCIP* scip, SCIP_PRESOL* presol) with gil: + return SCIP_OKAY + + +-cdef SCIP_RETCODE PyPresolInitpre (SCIP* scip, SCIP_PRESOL* presol) with gil: ++cdef SCIP_RETCODE PyPresolInitpre (SCIP* scip, SCIP_PRESOL* presol) noexcept with gil: + cdef SCIP_PRESOLDATA* presoldata + presoldata = SCIPpresolGetData(presol) + PyPresol = presoldata + PyPresol.presolinitpre() + return SCIP_OKAY + +-cdef SCIP_RETCODE PyPresolExitpre (SCIP* scip, SCIP_PRESOL* presol) with gil: ++cdef SCIP_RETCODE PyPresolExitpre (SCIP* scip, SCIP_PRESOL* presol) noexcept with gil: + cdef SCIP_PRESOLDATA* presoldata + presoldata = SCIPpresolGetData(presol) + PyPresol = presoldata +@@ -74,7 +74,7 @@ cdef SCIP_RETCODE PyPresolExec (SCIP* scip, SCIP_PRESOL* presol, int nrounds, SC + int nnewfixedvars, int nnewaggrvars, int nnewchgvartypes, int nnewchgbds, int nnewholes, + int nnewdelconss, int nnewaddconss, int nnewupgdconss, int nnewchgcoefs, int nnewchgsides, + int* nfixedvars, int* naggrvars, int* nchgvartypes, int* nchgbds, int* naddholes, +- int* ndelconss, int* naddconss, int* nupgdconss, int* nchgcoefs, int* nchgsides, SCIP_RESULT* result) with gil: ++ int* ndelconss, int* naddconss, int* nupgdconss, int* nchgcoefs, int* nchgsides, SCIP_RESULT* result) noexcept with gil: + cdef SCIP_PRESOLDATA* presoldata + presoldata = SCIPpresolGetData(presol) + PyPresol = presoldata +diff --git a/src/pyscipopt/pricer.pxi b/src/pyscipopt/pricer.pxi +index 1368572de..a218b254c 100644 +--- a/src/pyscipopt/pricer.pxi ++++ b/src/pyscipopt/pricer.pxi +@@ -33,10 +33,10 @@ cdef class Pricer: + + + +-cdef SCIP_RETCODE PyPricerCopy (SCIP* scip, SCIP_PRICER* pricer, SCIP_Bool* valid) with gil: ++cdef SCIP_RETCODE PyPricerCopy (SCIP* scip, SCIP_PRICER* pricer, SCIP_Bool* valid) noexcept with gil: + return SCIP_OKAY + +-cdef SCIP_RETCODE PyPricerFree (SCIP* scip, SCIP_PRICER* pricer) with gil: ++cdef SCIP_RETCODE PyPricerFree (SCIP* scip, SCIP_PRICER* pricer) noexcept with gil: + cdef SCIP_PRICERDATA* pricerdata + pricerdata = SCIPpricerGetData(pricer) + PyPricer = pricerdata +@@ -44,35 +44,35 @@ cdef SCIP_RETCODE PyPricerFree (SCIP* scip, SCIP_PRICER* pricer) with gil: + Py_DECREF(PyPricer) + return SCIP_OKAY + +-cdef SCIP_RETCODE PyPricerInit (SCIP* scip, SCIP_PRICER* pricer) with gil: ++cdef SCIP_RETCODE PyPricerInit (SCIP* scip, SCIP_PRICER* pricer) noexcept with gil: + cdef SCIP_PRICERDATA* pricerdata + pricerdata = SCIPpricerGetData(pricer) + PyPricer = pricerdata + PyPricer.pricerinit() + return SCIP_OKAY + +-cdef SCIP_RETCODE PyPricerExit (SCIP* scip, SCIP_PRICER* pricer) with gil: ++cdef SCIP_RETCODE PyPricerExit (SCIP* scip, SCIP_PRICER* pricer) noexcept with gil: + cdef SCIP_PRICERDATA* pricerdata + pricerdata = SCIPpricerGetData(pricer) + PyPricer = pricerdata + PyPricer.pricerexit() + return SCIP_OKAY + +-cdef SCIP_RETCODE PyPricerInitsol (SCIP* scip, SCIP_PRICER* pricer) with gil: ++cdef SCIP_RETCODE PyPricerInitsol (SCIP* scip, SCIP_PRICER* pricer) noexcept with gil: + cdef SCIP_PRICERDATA* pricerdata + pricerdata = SCIPpricerGetData(pricer) + PyPricer = pricerdata + PyPricer.pricerinitsol() + return SCIP_OKAY + +-cdef SCIP_RETCODE PyPricerExitsol (SCIP* scip, SCIP_PRICER* pricer) with gil: ++cdef SCIP_RETCODE PyPricerExitsol (SCIP* scip, SCIP_PRICER* pricer) noexcept with gil: + cdef SCIP_PRICERDATA* pricerdata + pricerdata = SCIPpricerGetData(pricer) + PyPricer = pricerdata + PyPricer.pricerexitsol() + return SCIP_OKAY + +-cdef SCIP_RETCODE PyPricerRedcost (SCIP* scip, SCIP_PRICER* pricer, SCIP_Real* lowerbound, SCIP_Bool* stopearly, SCIP_RESULT* result) with gil: ++cdef SCIP_RETCODE PyPricerRedcost (SCIP* scip, SCIP_PRICER* pricer, SCIP_Real* lowerbound, SCIP_Bool* stopearly, SCIP_RESULT* result) noexcept with gil: + cdef SCIP_PRICERDATA* pricerdata + pricerdata = SCIPpricerGetData(pricer) + PyPricer = pricerdata +@@ -82,7 +82,7 @@ cdef SCIP_RETCODE PyPricerRedcost (SCIP* scip, SCIP_PRICER* pricer, SCIP_Real* l + stopearly[0] = result_dict.get("stopearly", stopearly[0]) + return SCIP_OKAY + +-cdef SCIP_RETCODE PyPricerFarkas (SCIP* scip, SCIP_PRICER* pricer, SCIP_RESULT* result) with gil: ++cdef SCIP_RETCODE PyPricerFarkas (SCIP* scip, SCIP_PRICER* pricer, SCIP_RESULT* result) noexcept with gil: + cdef SCIP_PRICERDATA* pricerdata + pricerdata = SCIPpricerGetData(pricer) + PyPricer = pricerdata +diff --git a/src/pyscipopt/propagator.pxi b/src/pyscipopt/propagator.pxi +index d792577d9..4508efe78 100644 +--- a/src/pyscipopt/propagator.pxi ++++ b/src/pyscipopt/propagator.pxi +@@ -47,10 +47,10 @@ cdef class Prop: + + + +-cdef SCIP_RETCODE PyPropCopy (SCIP* scip, SCIP_PROP* prop) with gil: ++cdef SCIP_RETCODE PyPropCopy (SCIP* scip, SCIP_PROP* prop) noexcept with gil: + return SCIP_OKAY + +-cdef SCIP_RETCODE PyPropFree (SCIP* scip, SCIP_PROP* prop) with gil: ++cdef SCIP_RETCODE PyPropFree (SCIP* scip, SCIP_PROP* prop) noexcept with gil: + cdef SCIP_PROPDATA* propdata + propdata = SCIPpropGetData(prop) + PyProp = propdata +@@ -58,42 +58,42 @@ cdef SCIP_RETCODE PyPropFree (SCIP* scip, SCIP_PROP* prop) with gil: + Py_DECREF(PyProp) + return SCIP_OKAY + +-cdef SCIP_RETCODE PyPropInit (SCIP* scip, SCIP_PROP* prop) with gil: ++cdef SCIP_RETCODE PyPropInit (SCIP* scip, SCIP_PROP* prop) noexcept with gil: + cdef SCIP_PROPDATA* propdata + propdata = SCIPpropGetData(prop) + PyProp = propdata + PyProp.propinit() + return SCIP_OKAY + +-cdef SCIP_RETCODE PyPropExit (SCIP* scip, SCIP_PROP* prop) with gil: ++cdef SCIP_RETCODE PyPropExit (SCIP* scip, SCIP_PROP* prop) noexcept with gil: + cdef SCIP_PROPDATA* propdata + propdata = SCIPpropGetData(prop) + PyProp = propdata + PyProp.propexit() + return SCIP_OKAY + +-cdef SCIP_RETCODE PyPropInitpre (SCIP* scip, SCIP_PROP* prop) with gil: ++cdef SCIP_RETCODE PyPropInitpre (SCIP* scip, SCIP_PROP* prop) noexcept with gil: + cdef SCIP_PROPDATA* propdata + propdata = SCIPpropGetData(prop) + PyProp = propdata + PyProp.propinitpre() + return SCIP_OKAY + +-cdef SCIP_RETCODE PyPropExitpre (SCIP* scip, SCIP_PROP* prop) with gil: ++cdef SCIP_RETCODE PyPropExitpre (SCIP* scip, SCIP_PROP* prop) noexcept with gil: + cdef SCIP_PROPDATA* propdata + propdata = SCIPpropGetData(prop) + PyProp = propdata + PyProp.propexitpre() + return SCIP_OKAY + +-cdef SCIP_RETCODE PyPropInitsol (SCIP* scip, SCIP_PROP* prop) with gil: ++cdef SCIP_RETCODE PyPropInitsol (SCIP* scip, SCIP_PROP* prop) noexcept with gil: + cdef SCIP_PROPDATA* propdata + propdata = SCIPpropGetData(prop) + PyProp = propdata + PyProp.propinitsol() + return SCIP_OKAY + +-cdef SCIP_RETCODE PyPropExitsol (SCIP* scip, SCIP_PROP* prop, SCIP_Bool restart) with gil: ++cdef SCIP_RETCODE PyPropExitsol (SCIP* scip, SCIP_PROP* prop, SCIP_Bool restart) noexcept with gil: + cdef SCIP_PROPDATA* propdata + propdata = SCIPpropGetData(prop) + PyProp = propdata +@@ -104,7 +104,7 @@ cdef SCIP_RETCODE PyPropPresol (SCIP* scip, SCIP_PROP* prop, int nrounds, SCIP_P + int nnewfixedvars, int nnewaggrvars, int nnewchgvartypes, int nnewchgbds, int nnewholes, + int nnewdelconss, int nnewaddconss, int nnewupgdconss, int nnewchgcoefs, int nnewchgsides, + int* nfixedvars, int* naggrvars, int* nchgvartypes, int* nchgbds, int* naddholes, +- int* ndelconss, int* naddconss, int* nupgdconss, int* nchgcoefs, int* nchgsides, SCIP_RESULT* result) with gil: ++ int* ndelconss, int* naddconss, int* nupgdconss, int* nchgcoefs, int* nchgsides, SCIP_RESULT* result) noexcept with gil: + cdef SCIP_PROPDATA* propdata + propdata = SCIPpropGetData(prop) + PyProp = propdata +@@ -137,7 +137,7 @@ cdef SCIP_RETCODE PyPropPresol (SCIP* scip, SCIP_PROP* prop, int nrounds, SCIP_P + nchgsides[0] = result_dict["nchgsides"] + return SCIP_OKAY + +-cdef SCIP_RETCODE PyPropExec (SCIP* scip, SCIP_PROP* prop, SCIP_PROPTIMING proptiming, SCIP_RESULT* result) with gil: ++cdef SCIP_RETCODE PyPropExec (SCIP* scip, SCIP_PROP* prop, SCIP_PROPTIMING proptiming, SCIP_RESULT* result) noexcept with gil: + cdef SCIP_PROPDATA* propdata + propdata = SCIPpropGetData(prop) + PyProp = propdata +@@ -147,7 +147,7 @@ cdef SCIP_RETCODE PyPropExec (SCIP* scip, SCIP_PROP* prop, SCIP_PROPTIMING propt + return SCIP_OKAY + + cdef SCIP_RETCODE PyPropResProp (SCIP* scip, SCIP_PROP* prop, SCIP_VAR* infervar, int inferinfo, +- SCIP_BOUNDTYPE boundtype, SCIP_BDCHGIDX* bdchgidx, SCIP_Real relaxedbd, SCIP_RESULT* result) with gil: ++ SCIP_BOUNDTYPE boundtype, SCIP_BDCHGIDX* bdchgidx, SCIP_Real relaxedbd, SCIP_RESULT* result) noexcept with gil: + cdef SCIP_PROPDATA* propdata + cdef SCIP_VAR* tmp + tmp = infervar +diff --git a/src/pyscipopt/reader.pxi b/src/pyscipopt/reader.pxi +index df0b3a288..2c45585d6 100644 +--- a/src/pyscipopt/reader.pxi ++++ b/src/pyscipopt/reader.pxi +@@ -18,10 +18,10 @@ cdef class Reader: + return {} + + +-cdef SCIP_RETCODE PyReaderCopy (SCIP* scip, SCIP_READER* reader) with gil: ++cdef SCIP_RETCODE PyReaderCopy (SCIP* scip, SCIP_READER* reader) noexcept with gil: + return SCIP_OKAY + +-cdef SCIP_RETCODE PyReaderFree (SCIP* scip, SCIP_READER* reader) with gil: ++cdef SCIP_RETCODE PyReaderFree (SCIP* scip, SCIP_READER* reader) noexcept with gil: + cdef SCIP_READERDATA* readerdata + readerdata = SCIPreaderGetData(reader) + PyReader = readerdata +@@ -29,7 +29,7 @@ cdef SCIP_RETCODE PyReaderFree (SCIP* scip, SCIP_READER* reader) with gil: + Py_DECREF(PyReader) + return SCIP_OKAY + +-cdef SCIP_RETCODE PyReaderRead (SCIP* scip, SCIP_READER* reader, const char* filename, SCIP_RESULT* result) with gil: ++cdef SCIP_RETCODE PyReaderRead (SCIP* scip, SCIP_READER* reader, const char* filename, SCIP_RESULT* result) noexcept with gil: + cdef SCIP_READERDATA* readerdata + readerdata = SCIPreaderGetData(reader) + PyReader = readerdata +@@ -44,7 +44,7 @@ cdef SCIP_RETCODE PyReaderWrite (SCIP* scip, SCIP_READER* reader, FILE* file, + SCIP_VAR** vars, int nvars, int nbinvars, int nintvars, int nimplvars, int ncontvars, + SCIP_VAR** fixedvars, int nfixedvars, int startnvars, + SCIP_CONS** conss, int nconss, int maxnconss, int startnconss, +- SCIP_Bool genericnames, SCIP_RESULT* result) with gil: ++ SCIP_Bool genericnames, SCIP_RESULT* result) noexcept with gil: + cdef SCIP_READERDATA* readerdata + readerdata = SCIPreaderGetData(reader) + cdef int fd = fileno(file) +diff --git a/src/pyscipopt/relax.pxi b/src/pyscipopt/relax.pxi +index 2b52c2643..81695e8bb 100644 +--- a/src/pyscipopt/relax.pxi ++++ b/src/pyscipopt/relax.pxi +@@ -30,10 +30,10 @@ cdef class Relax: + return{} + + +-cdef SCIP_RETCODE PyRelaxCopy (SCIP* scip, SCIP_RELAX* relax) with gil: ++cdef SCIP_RETCODE PyRelaxCopy (SCIP* scip, SCIP_RELAX* relax) noexcept with gil: + return SCIP_OKAY + +-cdef SCIP_RETCODE PyRelaxFree (SCIP* scip, SCIP_RELAX* relax) with gil: ++cdef SCIP_RETCODE PyRelaxFree (SCIP* scip, SCIP_RELAX* relax) noexcept with gil: + cdef SCIP_RELAXDATA* relaxdata + relaxdata = SCIPrelaxGetData(relax) + PyRelax = relaxdata +@@ -41,35 +41,35 @@ cdef SCIP_RETCODE PyRelaxFree (SCIP* scip, SCIP_RELAX* relax) with gil: + Py_DECREF(PyRelax) + return SCIP_OKAY + +-cdef SCIP_RETCODE PyRelaxInit (SCIP* scip, SCIP_RELAX* relax) with gil: ++cdef SCIP_RETCODE PyRelaxInit (SCIP* scip, SCIP_RELAX* relax) noexcept with gil: + cdef SCIP_RELAXDATA* relaxdata + relaxdata = SCIPrelaxGetData(relax) + PyRelax = relaxdata + PyRelax.relaxinit() + return SCIP_OKAY + +-cdef SCIP_RETCODE PyRelaxExit (SCIP* scip, SCIP_RELAX* relax) with gil: ++cdef SCIP_RETCODE PyRelaxExit (SCIP* scip, SCIP_RELAX* relax) noexcept with gil: + cdef SCIP_RELAXDATA* relaxdata + relaxdata = SCIPrelaxGetData(relax) + PyRelax = relaxdata + PyRelax.relaxexit() + return SCIP_OKAY + +-cdef SCIP_RETCODE PyRelaxInitsol (SCIP* scip, SCIP_RELAX* relax) with gil: ++cdef SCIP_RETCODE PyRelaxInitsol (SCIP* scip, SCIP_RELAX* relax) noexcept with gil: + cdef SCIP_RELAXDATA* relaxdata + relaxdata = SCIPrelaxGetData(relax) + PyRelax = relaxdata + PyRelax.relaxinitsol() + return SCIP_OKAY + +-cdef SCIP_RETCODE PyRelaxExitsol (SCIP* scip, SCIP_RELAX* relax) with gil: ++cdef SCIP_RETCODE PyRelaxExitsol (SCIP* scip, SCIP_RELAX* relax) noexcept with gil: + cdef SCIP_RELAXDATA* relaxdata + relaxdata = SCIPrelaxGetData(relax) + PyRelax = relaxdata + PyRelax.relaxexitsol() + return SCIP_OKAY + +-cdef SCIP_RETCODE PyRelaxExec (SCIP* scip, SCIP_RELAX* relax, SCIP_Real* lowerbound, SCIP_RESULT* result) with gil: ++cdef SCIP_RETCODE PyRelaxExec (SCIP* scip, SCIP_RELAX* relax, SCIP_Real* lowerbound, SCIP_RESULT* result) noexcept with gil: + cdef SCIP_RELAXDATA* relaxdata + relaxdata = SCIPrelaxGetData(relax) + PyRelax = relaxdata +diff --git a/src/pyscipopt/scip.pxd b/src/pyscipopt/scip.pxd +index 12815dbc4..f35a42486 100644 +--- a/src/pyscipopt/scip.pxd ++++ b/src/pyscipopt/scip.pxd +@@ -501,7 +501,7 @@ cdef extern from "scip/scip.h": + ctypedef union SCIP_DOMCHG: + pass + +- ctypedef void (*messagecallback) (SCIP_MESSAGEHDLR *messagehdlr, FILE *file, const char *msg) ++ ctypedef void (*messagecallback) (SCIP_MESSAGEHDLR *messagehdlr, FILE *file, const char *msg) noexcept + ctypedef void (*errormessagecallback) (void *data, FILE *file, const char *msg) + ctypedef SCIP_RETCODE (*messagehdlrfree) (SCIP_MESSAGEHDLR *messagehdlr) + +diff --git a/src/pyscipopt/scip.pxi b/src/pyscipopt/scip.pxi +index 0b2332d88..552197785 100644 +--- a/src/pyscipopt/scip.pxi ++++ b/src/pyscipopt/scip.pxi +@@ -975,10 +975,10 @@ cdef class Constraint: + and self.scip_cons == (other).scip_cons) + + +-cdef void relayMessage(SCIP_MESSAGEHDLR *messagehdlr, FILE *file, const char *msg): ++cdef void relayMessage(SCIP_MESSAGEHDLR *messagehdlr, FILE *file, const char *msg) noexcept: + sys.stdout.write(msg.decode('UTF-8')) + +-cdef void relayErrorMessage(void *messagehdlr, FILE *file, const char *msg): ++cdef void relayErrorMessage(void *messagehdlr, FILE *file, const char *msg) noexcept: + sys.stderr.write(msg.decode('UTF-8')) + + # - remove create(), includeDefaultPlugins(), createProbBasic() methods +diff --git a/src/pyscipopt/sepa.pxi b/src/pyscipopt/sepa.pxi +index 271945db1..94355a7d2 100644 +--- a/src/pyscipopt/sepa.pxi ++++ b/src/pyscipopt/sepa.pxi +@@ -34,10 +34,10 @@ cdef class Sepa: + + + +-cdef SCIP_RETCODE PySepaCopy (SCIP* scip, SCIP_SEPA* sepa) with gil: ++cdef SCIP_RETCODE PySepaCopy (SCIP* scip, SCIP_SEPA* sepa) noexcept with gil: + return SCIP_OKAY + +-cdef SCIP_RETCODE PySepaFree (SCIP* scip, SCIP_SEPA* sepa) with gil: ++cdef SCIP_RETCODE PySepaFree (SCIP* scip, SCIP_SEPA* sepa) noexcept with gil: + cdef SCIP_SEPADATA* sepadata + sepadata = SCIPsepaGetData(sepa) + PySepa = sepadata +@@ -45,35 +45,35 @@ cdef SCIP_RETCODE PySepaFree (SCIP* scip, SCIP_SEPA* sepa) with gil: + Py_DECREF(PySepa) + return SCIP_OKAY + +-cdef SCIP_RETCODE PySepaInit (SCIP* scip, SCIP_SEPA* sepa) with gil: ++cdef SCIP_RETCODE PySepaInit (SCIP* scip, SCIP_SEPA* sepa) noexcept with gil: + cdef SCIP_SEPADATA* sepadata + sepadata = SCIPsepaGetData(sepa) + PySepa = sepadata + PySepa.sepainit() + return SCIP_OKAY + +-cdef SCIP_RETCODE PySepaExit (SCIP* scip, SCIP_SEPA* sepa) with gil: ++cdef SCIP_RETCODE PySepaExit (SCIP* scip, SCIP_SEPA* sepa) noexcept with gil: + cdef SCIP_SEPADATA* sepadata + sepadata = SCIPsepaGetData(sepa) + PySepa = sepadata + PySepa.sepaexit() + return SCIP_OKAY + +-cdef SCIP_RETCODE PySepaInitsol (SCIP* scip, SCIP_SEPA* sepa) with gil: ++cdef SCIP_RETCODE PySepaInitsol (SCIP* scip, SCIP_SEPA* sepa) noexcept with gil: + cdef SCIP_SEPADATA* sepadata + sepadata = SCIPsepaGetData(sepa) + PySepa = sepadata + PySepa.sepainitsol() + return SCIP_OKAY + +-cdef SCIP_RETCODE PySepaExitsol (SCIP* scip, SCIP_SEPA* sepa) with gil: ++cdef SCIP_RETCODE PySepaExitsol (SCIP* scip, SCIP_SEPA* sepa) noexcept with gil: + cdef SCIP_SEPADATA* sepadata + sepadata = SCIPsepaGetData(sepa) + PySepa = sepadata + PySepa.sepaexitsol() + return SCIP_OKAY + +-cdef SCIP_RETCODE PySepaExeclp (SCIP* scip, SCIP_SEPA* sepa, SCIP_RESULT* result, unsigned int allowlocal, int depth) with gil: ++cdef SCIP_RETCODE PySepaExeclp (SCIP* scip, SCIP_SEPA* sepa, SCIP_RESULT* result, unsigned int allowlocal, int depth) noexcept with gil: + cdef SCIP_SEPADATA* sepadata + sepadata = SCIPsepaGetData(sepa) + PySepa = sepadata +@@ -81,7 +81,7 @@ cdef SCIP_RETCODE PySepaExeclp (SCIP* scip, SCIP_SEPA* sepa, SCIP_RESULT* result + result[0] = result_dict.get("result", result[0]) + return SCIP_OKAY + +-cdef SCIP_RETCODE PySepaExecsol (SCIP* scip, SCIP_SEPA* sepa, SCIP_SOL* sol, SCIP_RESULT* result, unsigned int allowlocal, int depth) with gil: ++cdef SCIP_RETCODE PySepaExecsol (SCIP* scip, SCIP_SEPA* sepa, SCIP_SOL* sol, SCIP_RESULT* result, unsigned int allowlocal, int depth) noexcept with gil: + cdef SCIP_SEPADATA* sepadata + sepadata = SCIPsepaGetData(sepa) + solution = Solution.create(scip, sol) From c96322139cd062b178a7b62049fb9c0576a2a667 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 18 Feb 2024 23:11:11 -0800 Subject: [PATCH 109/518] build/pkgs/pyscipopt: Fix upstream_url --- build/pkgs/pyscipopt/SPKG.rst | 4 ---- build/pkgs/pyscipopt/checksums.ini | 4 ++-- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/build/pkgs/pyscipopt/SPKG.rst b/build/pkgs/pyscipopt/SPKG.rst index 28d6ee75aaa..555866ee6bd 100644 --- a/build/pkgs/pyscipopt/SPKG.rst +++ b/build/pkgs/pyscipopt/SPKG.rst @@ -16,7 +16,3 @@ Upstream Contact https://pypi.org/project/PySCIPOpt/ -Dependencies ------------- - -scipoptsuite diff --git a/build/pkgs/pyscipopt/checksums.ini b/build/pkgs/pyscipopt/checksums.ini index 022bf3873fd..5b537d4c57d 100644 --- a/build/pkgs/pyscipopt/checksums.ini +++ b/build/pkgs/pyscipopt/checksums.ini @@ -1,5 +1,5 @@ -tarball=PySCIPOpt_no_C-VERSION.tar.gz +tarball=PySCIPOpt-VERSION.tar.gz sha1=5ae7f3d7e9d8b344ee9a4413154ae80a5ee137de md5=32a4dced7e74a1c290b32ef0da751129 cksum=2258510906 -upstream_url=https://github.com/scipopt/PySCIPOpt/archive/refs/tags/vVERSION.tar.gz +upstream_url=https://pypi.io/packages/source/p/pyscipopt/PySCIPOpt-VERSION.tar.gz From 25e05bb5c1835c43335cad36213043483225127f Mon Sep 17 00:00:00 2001 From: Dima Pasechnik Date: Sat, 2 Mar 2024 17:21:56 +0000 Subject: [PATCH 110/518] fix debian.txt for qhull added missing package --- build/pkgs/qhull/distros/debian.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/build/pkgs/qhull/distros/debian.txt b/build/pkgs/qhull/distros/debian.txt index c4e91af6528..eb71577e83d 100644 --- a/build/pkgs/qhull/distros/debian.txt +++ b/build/pkgs/qhull/distros/debian.txt @@ -1 +1,2 @@ +qhull-bin libqhull-dev From 6f3793f2bb5f395569c165ddcd22270374a2b541 Mon Sep 17 00:00:00 2001 From: Eloi Torrents Date: Sat, 2 Mar 2024 19:53:48 +0100 Subject: [PATCH 111/518] Remove comment about python2 literals --- src/doc/en/developer/coding_in_python.rst | 6 +++--- src/sage/repl/preparse.py | 5 ++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/doc/en/developer/coding_in_python.rst b/src/doc/en/developer/coding_in_python.rst index 6b3b936662d..5defbe09b0f 100644 --- a/src/doc/en/developer/coding_in_python.rst +++ b/src/doc/en/developer/coding_in_python.rst @@ -259,9 +259,9 @@ replacements are made: 3 * 29 - Raw literals are not preparsed, which can be useful from an - efficiency point of view. Just like Python ints are denoted by an L, - in Sage raw integer and floating literals are followed by an "r" (or - "R") for raw, meaning not preparsed. For example:: + efficiency point of view. In Sage raw integer and floating + literals are followed by an "r" (or "R") for raw, meaning + not preparsed. For example:: sage: a = 393939r sage: a diff --git a/src/sage/repl/preparse.py b/src/sage/repl/preparse.py index 18ab18fdeaa..8e06be4f5d1 100644 --- a/src/sage/repl/preparse.py +++ b/src/sage/repl/preparse.py @@ -116,9 +116,8 @@ Raw literals: Raw literals are not preparsed, which can be useful from an efficiency -point of view. Just like Python ints are denoted by an L, in Sage raw -integer and floating literals are followed by an"r" (or "R") for raw, -meaning not preparsed. +point of view. In Sage raw integer and floating literals are followed +by an"r" (or "R") for raw, meaning not preparsed. We create a raw integer:: From 8b8185529570c80c5f7226d214785a095712242d Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 2 Mar 2024 11:14:40 -0800 Subject: [PATCH 112/518] src/mypy.ini: New --- src/mypy.ini | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 src/mypy.ini diff --git a/src/mypy.ini b/src/mypy.ini new file mode 100644 index 00000000000..e315151eee6 --- /dev/null +++ b/src/mypy.ini @@ -0,0 +1,7 @@ +# https://mypy.readthedocs.io/en/stable/config_file.html +[mypy] +python_version = 3.9 + +# https://github.com/sagemath/sage/issues/36198#issuecomment-1709521850 +follow_imports = skip +ignore_missing_imports = True From b16e1d3fb805ac98be0bbfa63b5162ac625f9f1f Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Tue, 27 Feb 2024 11:44:01 +0000 Subject: [PATCH 113/518] Start working on random element --- .../polynomial/laurent_polynomial_ring_base.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py index bbb6b9e1fc9..6a6ec585ce7 100644 --- a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py +++ b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py @@ -488,7 +488,7 @@ def krull_dimension(self): """ raise NotImplementedError - def random_element(self, low_degree=-2, high_degree=2, terms=5, choose_degree=False,*args, **kwds): + def random_element(self, low_degree=-2, high_degree=2, terms=5, choose_degree=False, *args, **kwds): """ EXAMPLES:: @@ -497,6 +497,20 @@ def random_element(self, low_degree=-2, high_degree=2, terms=5, choose_degree=Fa ... NotImplementedError """ + # Univariate case we sample a random polynomial of degree + # (high_degree + low_degree) in a polynomial ring over the + # base field, then we shift this polynomial by low_degree. + if self._n == 1: + abs_deg = (high_degree + abs(low_degree)) + f_rand = self._R.random_element(degree=abs_deg, terms=terms) + + # Coerce back to ``self`` and then shift down + f = self(f_rand) + x = self.gen() + f *= x**low_degree + + return f + raise NotImplementedError def is_exact(self): From ee7ed7252ab5eadae80f14a1b070f4f600e16166 Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Tue, 27 Feb 2024 12:48:27 +0000 Subject: [PATCH 114/518] implement random sampling for laurent polynomials --- .../laurent_polynomial_ring_base.py | 104 +++++++++++++++--- 1 file changed, 86 insertions(+), 18 deletions(-) diff --git a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py index 6a6ec585ce7..4be6a338bb8 100644 --- a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py +++ b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py @@ -28,6 +28,7 @@ from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.rings.ring import CommutativeRing from sage.structure.parent import Parent +from sage.misc.misc_c import prod class LaurentPolynomialRing_generic(CommutativeRing, Parent): @@ -490,28 +491,95 @@ def krull_dimension(self): def random_element(self, low_degree=-2, high_degree=2, terms=5, choose_degree=False, *args, **kwds): """ + Return a random polynomial of degree at most ``high_degree`` with + lowest degree at most ``low_degree``. + + Internally uses the random sampling from + :meth:`sage.rings.polynomial.multi_polynomial_ring_base.MPolynomialRing_base.random_element` + then shifts this polynomial down to compute the correct degrees. + + INPUT: + + - ``low_degree`` -- non-positive integer (default: -2). + The smallest valuation of monomial in the polynomial + + - ``high_degree`` -- non-negative integer (default: 2). + The maximal valuation of monomial in the polynomial + + - ``terms`` -- number of terms requested (default: 5). If more + terms are requested than exist, then this parameter is + silently reduced to the maximum number of available terms. + + - ``choose_degree`` -- choose degrees of monomials randomly first + rather than monomials uniformly random. + + - ``**kwargs`` -- passed to the random element generator of the base + ring + EXAMPLES:: - sage: LaurentPolynomialRing(QQ, 2, 'x').random_element() - Traceback (most recent call last): - ... - NotImplementedError - """ - # Univariate case we sample a random polynomial of degree - # (high_degree + low_degree) in a polynomial ring over the - # base field, then we shift this polynomial by low_degree. - if self._n == 1: - abs_deg = (high_degree + abs(low_degree)) - f_rand = self._R.random_element(degree=abs_deg, terms=terms) - - # Coerce back to ``self`` and then shift down - f = self(f_rand) - x = self.gen() - f *= x**low_degree + sage: R = LaurentPolynomialRing(QQ, 1, 'x') + sage: f = R.random_element() + sage: f.degree() <= 2 + True + sage: f.monomials()[-1].degree() >= -2 + True - return f + :: - raise NotImplementedError + sage: R = LaurentPolynomialRing(QQ, 1, 'x') + sage: f = R.random_element(-10, 20) + sage: f.degree() <= 20 + True + sage: f.monomials()[-1].degree() >= -10 + True + + :: + + sage: R = LaurentPolynomialRing(ZZ, 3, 'x')p + sage: x = R.gen() + sage: f = R.random_element(-5, 5) + sage: f = R.random_element(-5, 10) + sage: f.degree(x) <= 10 + True + sage: f.degree(x) >= -5 + True + + TESTS:: + + sage: rings = [QQ, ZZ, GF(13), GF(7^3)] + sage: for ring in rings: + ....: R = LaurentPolynomialRing(ring, 3, 'x') + ....: for _ in range(100): + ....: f = R.random_element(-3, 7) + ....: for x in R.gens(): + ....: assert f.degree(x) <= 7 + ....: assert f.degree(x) >= -3 + """ + # Ensure the low_degree is non-positive + if low_degree > 0: + raise ValueError("`low_degree` is expected to be a non-positive integer") + + # Ensure the high_degree is non-negative + if high_degree < 0: + raise ValueError("`low_degree` is expected to be a non-negative integer") + + # First sample a polynomial from the associated polynomial + # ring of `self` of degree `(high_degree + abs(low_degree))` + abs_deg = (high_degree + abs(low_degree)) + f_rand = self._R.random_element(degree=abs_deg, terms=terms, choose_degree=choose_degree, *args, **kwds) + + # Coerce back to ``self``. We now have a polynomial of only + # positive valuation monomials + f = self(f_rand) + + # Finally, shift the entire polynomial down by low_degree + # which will result in a polynomial with highest degree + # high_degree and lowest degree low_degree + monomial = prod(self.gens()) + f *= monomial**low_degree + + return f def is_exact(self): """ From 617b50b3c0f59b0192b8ad38623e6968e080db88 Mon Sep 17 00:00:00 2001 From: Giacomo Pope <44242839+GiacomoPope@users.noreply.github.com> Date: Tue, 27 Feb 2024 15:05:08 +0100 Subject: [PATCH 115/518] Fix stupid typo --- src/sage/rings/polynomial/laurent_polynomial_ring_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py index 4be6a338bb8..b8c7ba49298 100644 --- a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py +++ b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py @@ -536,7 +536,7 @@ def random_element(self, low_degree=-2, high_degree=2, terms=5, choose_degree=Fa :: - sage: R = LaurentPolynomialRing(ZZ, 3, 'x')p + sage: R = LaurentPolynomialRing(ZZ, 3, 'x') sage: x = R.gen() sage: f = R.random_element(-5, 5) sage: f = R.random_element(-5, 10) From e552a89e83e50698df56ba4a1ca771f60a8bc288 Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Tue, 27 Feb 2024 16:51:22 +0000 Subject: [PATCH 116/518] More randomness in random testing --- .../rings/polynomial/laurent_polynomial_ring_base.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py index b8c7ba49298..c9bc9d72001 100644 --- a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py +++ b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py @@ -549,12 +549,14 @@ def random_element(self, low_degree=-2, high_degree=2, terms=5, choose_degree=Fa sage: rings = [QQ, ZZ, GF(13), GF(7^3)] sage: for ring in rings: - ....: R = LaurentPolynomialRing(ring, 3, 'x') + ....: d = randint(1, 5) + ....: R = LaurentPolynomialRing(ring, d, 'x') ....: for _ in range(100): - ....: f = R.random_element(-3, 7) + ....: n, m = randint(0, 10), randint(0, 10) + ....: f = R.random_element(-n, m) ....: for x in R.gens(): - ....: assert f.degree(x) <= 7 - ....: assert f.degree(x) >= -3 + ....: assert f.degree(x) <= m + ....: assert f.degree(x) >= -n """ # Ensure the low_degree is non-positive if low_degree > 0: From 057fb80684a6241408661414021171866f669447 Mon Sep 17 00:00:00 2001 From: Giacomo Pope <44242839+GiacomoPope@users.noreply.github.com> Date: Tue, 27 Feb 2024 17:53:47 +0000 Subject: [PATCH 117/518] Apply suggestions from code review Co-authored-by: grhkm21 <83517584+grhkm21@users.noreply.github.com> --- .../polynomial/laurent_polynomial_ring_base.py | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py index c9bc9d72001..e1621e35cf2 100644 --- a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py +++ b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py @@ -491,8 +491,8 @@ def krull_dimension(self): def random_element(self, low_degree=-2, high_degree=2, terms=5, choose_degree=False, *args, **kwds): """ - Return a random polynomial of degree at most ``high_degree`` with - lowest degree at most ``low_degree``. + Return a random polynomial of degree at most ``high_degree`` and + lowest degree at least ``low_degree``. Internally uses the random sampling from :meth:`sage.rings.polynomial.multi_polynomial_ring_base.MPolynomialRing_base.random_element` @@ -506,14 +506,7 @@ def random_element(self, low_degree=-2, high_degree=2, terms=5, choose_degree=Fa - ``high_degree`` -- non-negative integer (default: 2). The maximal valuation of monomial in the polynomial - - ``terms`` -- number of terms requested (default: 5). If more - terms are requested than exist, then this parameter is - silently reduced to the maximum number of available terms. - - - ``choose_degree`` -- choose degrees of monomials randomly first - rather than monomials uniformly random. - - - ``**kwargs`` -- passed to the random element generator of the base + - ``*args, **kwds`` -- passed to the random element generator of the base ring EXAMPLES:: @@ -568,7 +561,7 @@ def random_element(self, low_degree=-2, high_degree=2, terms=5, choose_degree=Fa # First sample a polynomial from the associated polynomial # ring of `self` of degree `(high_degree + abs(low_degree))` - abs_deg = (high_degree + abs(low_degree)) + abs_deg = (high_degree - low_degree) f_rand = self._R.random_element(degree=abs_deg, terms=terms, choose_degree=choose_degree, *args, **kwds) # Coerce back to ``self``. We now have a polynomial of only From 0c04b22ae67f93f6fbe7e9bc1737245a323b8d0c Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Tue, 27 Feb 2024 18:38:03 +0000 Subject: [PATCH 118/518] Review suggestions and more doctests --- .../laurent_polynomial_ring_base.py | 61 ++++++++++++------- 1 file changed, 39 insertions(+), 22 deletions(-) diff --git a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py index e1621e35cf2..db110eefa08 100644 --- a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py +++ b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py @@ -500,14 +500,22 @@ def random_element(self, low_degree=-2, high_degree=2, terms=5, choose_degree=Fa INPUT: - - ``low_degree`` -- non-positive integer (default: -2). + - ``low_degree`` -- integer (default: ``-2``). The smallest valuation of monomial in the polynomial - - ``high_degree`` -- non-negative integer (default: 2). + - ``high_degree`` -- integer (default: ``2``). The maximal valuation of monomial in the polynomial - - ``*args, **kwds`` -- passed to the random element generator of the base - ring + - ``terms`` -- number of terms requested (default: ``5``). + If more terms are requested than exist, then this parameter is + silently reduced to the maximum number of available terms. + + - ``choose_degree`` -- bool(default: ``False``) + choose degrees of monomials randomly first rather than monomials + uniformly random. + + - ``*args, **kwds`` -- passed to the random element generator + of the underlying polynomial ring and respective base ring EXAMPLES:: @@ -515,27 +523,37 @@ def random_element(self, low_degree=-2, high_degree=2, terms=5, choose_degree=Fa sage: f = R.random_element() sage: f.degree() <= 2 True - sage: f.monomials()[-1].degree() >= -2 + sage: f.degree() >= -2 + True + sage: f.parent() is R True :: - sage: R = LaurentPolynomialRing(QQ, 1, 'x') + sage: R = LaurentPolynomialRing(ZZ, 2, 'x') sage: f = R.random_element(-10, 20) sage: f.degree() <= 20 True - sage: f.monomials()[-1].degree() >= -10 - True + sage: tuple(f.degree(x) >= -10 for x in R.gens()) + (True, True) :: - sage: R = LaurentPolynomialRing(ZZ, 3, 'x') - sage: x = R.gen() + sage: R = LaurentPolynomialRing(GF(13), 3, 'x') sage: f = R.random_element(-5, 5) sage: f = R.random_element(-5, 10) - sage: f.degree(x) <= 10 + sage: tuple(f.degree(x) <= 10 for x in R.gens()) + (True, True, True) + sage: tuple(f.degree(x) >= -5 for x in R.gens()) + (True, True, True) + + :: + + sage: R = LaurentPolynomialRing(QQ, 5, 'x') + sage: f = R.random_element(-3, 5, terms=6, choose_degree=True) + sage: f.parent() is R True - sage: f.degree(x) >= -5 + sage: len(list(f)) <= 6 True TESTS:: @@ -545,19 +563,18 @@ def random_element(self, low_degree=-2, high_degree=2, terms=5, choose_degree=Fa ....: d = randint(1, 5) ....: R = LaurentPolynomialRing(ring, d, 'x') ....: for _ in range(100): - ....: n, m = randint(0, 10), randint(0, 10) - ....: f = R.random_element(-n, m) + ....: n, m = randint(-10, -1), randint(1, 10) + ....: if m < n: + ....: m, n = n, m + ....: f = R.random_element(n, m) + ....: if f.is_zero(): continue # the zero polynomial is defined to have degree -1 ....: for x in R.gens(): ....: assert f.degree(x) <= m - ....: assert f.degree(x) >= -n + ....: assert f.degree(x) >= n """ - # Ensure the low_degree is non-positive - if low_degree > 0: - raise ValueError("`low_degree` is expected to be a non-positive integer") - - # Ensure the high_degree is non-negative - if high_degree < 0: - raise ValueError("`low_degree` is expected to be a non-negative integer") + # Ensure the degree parameters are sensible + if high_degree < low_degree: + raise ValueError("`high_degree` must be greater than or equal to `low_degree`") # First sample a polynomial from the associated polynomial # ring of `self` of degree `(high_degree + abs(low_degree))` From 5fd9fd48c583d08351af8755720807e6ee550032 Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Tue, 27 Feb 2024 18:40:19 +0000 Subject: [PATCH 119/518] space --- src/sage/rings/polynomial/laurent_polynomial_ring_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py index db110eefa08..8c7b8b05aff 100644 --- a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py +++ b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py @@ -510,7 +510,7 @@ def random_element(self, low_degree=-2, high_degree=2, terms=5, choose_degree=Fa If more terms are requested than exist, then this parameter is silently reduced to the maximum number of available terms. - - ``choose_degree`` -- bool(default: ``False``) + - ``choose_degree`` -- bool (default: ``False``) choose degrees of monomials randomly first rather than monomials uniformly random. From 72f58469449110cd40fdd46f782786869dcd7907 Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Tue, 27 Feb 2024 18:56:45 +0000 Subject: [PATCH 120/518] more randomness in testing --- src/sage/rings/polynomial/laurent_polynomial_ring_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py index 8c7b8b05aff..77619ffa99e 100644 --- a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py +++ b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py @@ -563,7 +563,7 @@ def random_element(self, low_degree=-2, high_degree=2, terms=5, choose_degree=Fa ....: d = randint(1, 5) ....: R = LaurentPolynomialRing(ring, d, 'x') ....: for _ in range(100): - ....: n, m = randint(-10, -1), randint(1, 10) + ....: n, m = randint(-10, 10), randint(1, 10) ....: if m < n: ....: m, n = n, m ....: f = R.random_element(n, m) From 76f2f204e09f9baa7f313873cda8a4ef655a89c5 Mon Sep 17 00:00:00 2001 From: Giacomo Pope <44242839+GiacomoPope@users.noreply.github.com> Date: Wed, 28 Feb 2024 10:42:14 +0000 Subject: [PATCH 121/518] Apply suggestions from code review Co-authored-by: Travis Scrimshaw --- .../laurent_polynomial_ring_base.py | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py index 77619ffa99e..d9a5657841d 100644 --- a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py +++ b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py @@ -494,25 +494,26 @@ def random_element(self, low_degree=-2, high_degree=2, terms=5, choose_degree=Fa Return a random polynomial of degree at most ``high_degree`` and lowest degree at least ``low_degree``. - Internally uses the random sampling from - :meth:`sage.rings.polynomial.multi_polynomial_ring_base.MPolynomialRing_base.random_element` - then shifts this polynomial down to compute the correct degrees. + Internally uses the random sampling from the corresponding + polynomial ring then shifts this polynomial down to compute + the correct degrees. INPUT: - - ``low_degree`` -- integer (default: ``-2``). - The smallest valuation of monomial in the polynomial + - ``min_valuation`` -- integer (default: ``-2``); the + minimal allowed valuation of the polynomial - - ``high_degree`` -- integer (default: ``2``). - The maximal valuation of monomial in the polynomial + - ``max_degree`` -- integer (default: ``2``); the + maximal allowed degree of the polynomial - - ``terms`` -- number of terms requested (default: ``5``). - If more terms are requested than exist, then this parameter is - silently reduced to the maximum number of available terms. + - ``terms`` -- (default: ``5``) number of terms requested; + if more terms are requested than exist (from specifying + the valuation and degree), then this parameter is silently + reduced to the maximum number of available terms - - ``choose_degree`` -- bool (default: ``False``) - choose degrees of monomials randomly first rather than monomials - uniformly random. + - ``choose_degree`` -- bool (default: ``False``) choose + degrees of monomials randomly first rather than monomials + uniformly random - ``*args, **kwds`` -- passed to the random element generator of the underlying polynomial ring and respective base ring From d813b087fd08db745237215b493eb42a95ec14fe Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Wed, 28 Feb 2024 10:57:33 +0000 Subject: [PATCH 122/518] Add more doctests --- .../laurent_polynomial_ring_base.py | 84 ++++++++++--------- 1 file changed, 43 insertions(+), 41 deletions(-) diff --git a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py index d9a5657841d..19eafdb9b90 100644 --- a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py +++ b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py @@ -489,10 +489,10 @@ def krull_dimension(self): """ raise NotImplementedError - def random_element(self, low_degree=-2, high_degree=2, terms=5, choose_degree=False, *args, **kwds): + def random_element(self, min_valuation=-2, high_degree=2, *args, **kwds): """ Return a random polynomial of degree at most ``high_degree`` and - lowest degree at least ``low_degree``. + lowest valuation at least ``min_valuation``. Internally uses the random sampling from the corresponding polynomial ring then shifts this polynomial down to compute @@ -506,91 +506,93 @@ def random_element(self, low_degree=-2, high_degree=2, terms=5, choose_degree=Fa - ``max_degree`` -- integer (default: ``2``); the maximal allowed degree of the polynomial - - ``terms`` -- (default: ``5``) number of terms requested; - if more terms are requested than exist (from specifying - the valuation and degree), then this parameter is silently - reduced to the maximum number of available terms - - - ``choose_degree`` -- bool (default: ``False``) choose - degrees of monomials randomly first rather than monomials - uniformly random - - ``*args, **kwds`` -- passed to the random element generator of the underlying polynomial ring and respective base ring EXAMPLES:: - sage: R = LaurentPolynomialRing(QQ, 1, 'x') - sage: f = R.random_element() + sage: L. = LaurentPolynomialRing(QQ) + sage: f = L.random_element() sage: f.degree() <= 2 True sage: f.degree() >= -2 True - sage: f.parent() is R + sage: f.parent() is L True :: - sage: R = LaurentPolynomialRing(ZZ, 2, 'x') - sage: f = R.random_element(-10, 20) + sage: L = LaurentPolynomialRing(ZZ, 2, 'x') + sage: f = L.random_element(-10, 20) sage: f.degree() <= 20 True - sage: tuple(f.degree(x) >= -10 for x in R.gens()) + sage: tuple(f.degree(x) >= -10 for x in L.gens()) (True, True) :: - sage: R = LaurentPolynomialRing(GF(13), 3, 'x') - sage: f = R.random_element(-5, 5) - sage: f = R.random_element(-5, 10) - sage: tuple(f.degree(x) <= 10 for x in R.gens()) + sage: L = LaurentPolynomialRing(GF(13), 3, 'x') + sage: f = L.random_element(-5, 5) + sage: f = L.random_element(-5, 10) + sage: tuple(f.degree(x) <= 10 for x in L.gens()) (True, True, True) - sage: tuple(f.degree(x) >= -5 for x in R.gens()) + sage: tuple(f.degree(x) >= -5 for x in L.gens()) (True, True, True) :: - sage: R = LaurentPolynomialRing(QQ, 5, 'x') - sage: f = R.random_element(-3, 5, terms=6, choose_degree=True) - sage: f.parent() is R - True - sage: len(list(f)) <= 6 - True + sage: L = LaurentPolynomialRing(RR, 2, 'x') + sage: f = L.random_element() + sage: f = L.random_element() + sage: tuple(f.degree(x) <= 2 for x in L.gens()) + (True, True) + sage: tuple(f.degree(x) >= -2 for x in L.gens()) + (True, True) + + :: + + sage: L = LaurentPolynomialRing(RR, 5, 'x') + sage: f = L.random_element(-1, 1) + sage: f = L.random_element(-1, 1) + sage: tuple(f.degree(x) <= 1 for x in L.gens()) + (True, True, True, True, True) + sage: tuple(f.degree(x) >= -1 for x in L.gens()) + (True, True, True, True, True) TESTS:: - sage: rings = [QQ, ZZ, GF(13), GF(7^3)] + sage: rings = [RR, QQ, ZZ, GF(13), GF(7^3)] sage: for ring in rings: ....: d = randint(1, 5) - ....: R = LaurentPolynomialRing(ring, d, 'x') + ....: L = LaurentPolynomialRing(ring, d, 'x') ....: for _ in range(100): ....: n, m = randint(-10, 10), randint(1, 10) ....: if m < n: ....: m, n = n, m - ....: f = R.random_element(n, m) + ....: f = L.random_element(n, m) ....: if f.is_zero(): continue # the zero polynomial is defined to have degree -1 - ....: for x in R.gens(): + ....: for x in L.gens(): ....: assert f.degree(x) <= m ....: assert f.degree(x) >= n """ # Ensure the degree parameters are sensible - if high_degree < low_degree: - raise ValueError("`high_degree` must be greater than or equal to `low_degree`") + if high_degree < min_valuation: + raise ValueError("`high_degree` must be greater than or equal to `min_valuation`") # First sample a polynomial from the associated polynomial - # ring of `self` of degree `(high_degree + abs(low_degree))` - abs_deg = (high_degree - low_degree) - f_rand = self._R.random_element(degree=abs_deg, terms=terms, choose_degree=choose_degree, *args, **kwds) + # ring of `self` of degree `(high_degree - min_valuation)` + abs_deg = (high_degree - min_valuation) + f_rand = self._R.random_element(degree=abs_deg, *args, **kwds) - # Coerce back to ``self``. We now have a polynomial of only + # Coerce back to `self`. We now have a polynomial of only # positive valuation monomials f = self(f_rand) - # Finally, shift the entire polynomial down by low_degree + # Finally, shift the entire polynomial down by min_valuation # which will result in a polynomial with highest degree - # high_degree and lowest degree low_degree + # high_degree and lowest valuation min_valuation monomial = prod(self.gens()) - f *= monomial**low_degree + f *= monomial**min_valuation return f From 484b93d319923e6783ace089c324ae0e09d3001d Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Wed, 28 Feb 2024 11:01:27 +0000 Subject: [PATCH 123/518] QQbar instead of RR --- src/sage/rings/polynomial/laurent_polynomial_ring_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py index 19eafdb9b90..311042380af 100644 --- a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py +++ b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py @@ -551,7 +551,7 @@ def random_element(self, min_valuation=-2, high_degree=2, *args, **kwds): :: - sage: L = LaurentPolynomialRing(RR, 5, 'x') + sage: L = LaurentPolynomialRing(QQbar, 5, 'x') sage: f = L.random_element(-1, 1) sage: f = L.random_element(-1, 1) sage: tuple(f.degree(x) <= 1 for x in L.gens()) From 6ba20d1c7f2684d8c50551ccabd14ffd1d494d85 Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Wed, 28 Feb 2024 15:54:43 +0000 Subject: [PATCH 124/518] Add valuation for LaurentPolynomial_mpair --- .../polynomial/laurent_polynomial_mpair.pyx | 41 ++++++++++++++++++- .../laurent_polynomial_ring_base.py | 10 ++--- 2 files changed, 45 insertions(+), 6 deletions(-) diff --git a/src/sage/rings/polynomial/laurent_polynomial_mpair.pyx b/src/sage/rings/polynomial/laurent_polynomial_mpair.pyx index 893b81c87af..598f57a39dd 100644 --- a/src/sage/rings/polynomial/laurent_polynomial_mpair.pyx +++ b/src/sage/rings/polynomial/laurent_polynomial_mpair.pyx @@ -16,7 +16,7 @@ from sage.structure.factorization import Factorization from sage.misc.derivative import multi_derivative from sage.rings.polynomial.polydict cimport monomial_exponent from sage.matrix.matrix0 cimport Matrix - +from sage.rings.infinity import Infinity cdef class LaurentPolynomial_mpair(LaurentPolynomial): """ @@ -1183,6 +1183,45 @@ cdef class LaurentPolynomial_mpair(LaurentPolynomial): raise TypeError("x must be a generator of parent") return self._poly.degree(self._parent._R.gens()[i]) + self._mon[i] + def valuation(self, x=None): + """ + Return the valuation of ``x`` in ``self``. + + If ``x`` is ``None``, return the minimal valuation of ``self``. + + EXAMPLES:: + + sage: R. = LaurentPolynomialRing(ZZ) + sage: f = 2*x^2*y^-3 - 13*x^-1*y^-3 + 2*x^2*y^-5 - 2*x^-3*y^2 + sage: f.valuation() + -4 + sage: f.valuation(x) + -3 + sage: f.valuation(y) + -5 + """ + # Valuation of zero polynomial is defined to be +Infinity + if self.is_zero(): + return Infinity + + # TODO: is there a faster cython-way to do this? + if x is None: + return min(sum(e) for e in self.exponents()) + + # Get the index of the gen + cdef tuple g = self._parent.gens() + cdef Py_ssize_t i + cdef bint no_generator_found = True + for i in range(len(g)): + if g[i] is x: + no_generator_found = False + break + if no_generator_found: + raise TypeError("x must be a generator of parent") + + # TODO: is there a faster cython-way to do this? + return min(e[i] for e in self.exponents()) + def has_inverse_of(self, i): """ INPUT: diff --git a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py index 311042380af..521cea3bd66 100644 --- a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py +++ b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py @@ -526,7 +526,7 @@ def random_element(self, min_valuation=-2, high_degree=2, *args, **kwds): sage: f = L.random_element(-10, 20) sage: f.degree() <= 20 True - sage: tuple(f.degree(x) >= -10 for x in L.gens()) + sage: tuple(f.valuation(x) >= -10 for x in L.gens()) (True, True) :: @@ -536,7 +536,7 @@ def random_element(self, min_valuation=-2, high_degree=2, *args, **kwds): sage: f = L.random_element(-5, 10) sage: tuple(f.degree(x) <= 10 for x in L.gens()) (True, True, True) - sage: tuple(f.degree(x) >= -5 for x in L.gens()) + sage: tuple(f.valuation(x) >= -5 for x in L.gens()) (True, True, True) :: @@ -546,7 +546,7 @@ def random_element(self, min_valuation=-2, high_degree=2, *args, **kwds): sage: f = L.random_element() sage: tuple(f.degree(x) <= 2 for x in L.gens()) (True, True) - sage: tuple(f.degree(x) >= -2 for x in L.gens()) + sage: tuple(f.valuation(x) >= -2 for x in L.gens()) (True, True) :: @@ -556,7 +556,7 @@ def random_element(self, min_valuation=-2, high_degree=2, *args, **kwds): sage: f = L.random_element(-1, 1) sage: tuple(f.degree(x) <= 1 for x in L.gens()) (True, True, True, True, True) - sage: tuple(f.degree(x) >= -1 for x in L.gens()) + sage: tuple(f.valuation(x) >= -1 for x in L.gens()) (True, True, True, True, True) TESTS:: @@ -573,7 +573,7 @@ def random_element(self, min_valuation=-2, high_degree=2, *args, **kwds): ....: if f.is_zero(): continue # the zero polynomial is defined to have degree -1 ....: for x in L.gens(): ....: assert f.degree(x) <= m - ....: assert f.degree(x) >= n + ....: assert f.valuation(x) >= n """ # Ensure the degree parameters are sensible if high_degree < min_valuation: From 27f8641b95dcd10350db17358b6b2aceb2ecc873 Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Wed, 28 Feb 2024 15:57:27 +0000 Subject: [PATCH 125/518] Add doctest for zero polynomial --- src/sage/rings/polynomial/laurent_polynomial_mpair.pyx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/sage/rings/polynomial/laurent_polynomial_mpair.pyx b/src/sage/rings/polynomial/laurent_polynomial_mpair.pyx index 598f57a39dd..769a7c27e7d 100644 --- a/src/sage/rings/polynomial/laurent_polynomial_mpair.pyx +++ b/src/sage/rings/polynomial/laurent_polynomial_mpair.pyx @@ -1199,6 +1199,8 @@ cdef class LaurentPolynomial_mpair(LaurentPolynomial): -3 sage: f.valuation(y) -5 + sage: R.zero().valuation() + +Infinity """ # Valuation of zero polynomial is defined to be +Infinity if self.is_zero(): From a49432bb8e83bc56a243170f727904b60190c3f8 Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Wed, 28 Feb 2024 18:02:58 +0000 Subject: [PATCH 126/518] rewrite random sampling for multivariate polynomials --- .../laurent_polynomial_ring_base.py | 127 +++++++++++++----- 1 file changed, 95 insertions(+), 32 deletions(-) diff --git a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py index 521cea3bd66..dd596414538 100644 --- a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py +++ b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py @@ -28,8 +28,7 @@ from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.rings.ring import CommutativeRing from sage.structure.parent import Parent -from sage.misc.misc_c import prod - +from sage.misc.prandom import randint class LaurentPolynomialRing_generic(CommutativeRing, Parent): """ @@ -489,14 +488,19 @@ def krull_dimension(self): """ raise NotImplementedError - def random_element(self, min_valuation=-2, high_degree=2, *args, **kwds): + def random_element(self, min_valuation=-2, max_degree=2, terms=5, **kwds): """ - Return a random polynomial of degree at most ``high_degree`` and - lowest valuation at least ``min_valuation``. + Return a random polynomial with degree at most ``max_degree`` and + lowest valuation at least ``min_valuation`` for each generator of + ``self``. + + For the univariate case, uses the random sampling from the + polynomial ring then shifts this polynomial down to ensure + correct ``max_degree`` and ``min_valuation``. - Internally uses the random sampling from the corresponding - polynomial ring then shifts this polynomial down to compute - the correct degrees. + For the multivariate case, samples ``terms`` elements which + respectively have degree at most ``max_degree`` and valuation + at least ``min_valuation`` and returns their sum. INPUT: @@ -506,8 +510,13 @@ def random_element(self, min_valuation=-2, high_degree=2, *args, **kwds): - ``max_degree`` -- integer (default: ``2``); the maximal allowed degree of the polynomial - - ``*args, **kwds`` -- passed to the random element generator - of the underlying polynomial ring and respective base ring + - ``terms`` -- number of terms requested (default: 5). + Only used for multivariate polynomial rings. If more + terms are requested than exist, then this parameter is + silently reduced to the maximum number of available terms. + + - ``**kwargs`` -- passed to the random element generator of the base + ring EXAMPLES:: @@ -515,19 +524,43 @@ def random_element(self, min_valuation=-2, high_degree=2, *args, **kwds): sage: f = L.random_element() sage: f.degree() <= 2 True - sage: f.degree() >= -2 + sage: f.valuation() >= -2 True sage: f.parent() is L True :: - sage: L = LaurentPolynomialRing(ZZ, 2, 'x') + sage: L. = LaurentPolynomialRing(QQ) sage: f = L.random_element(-10, 20) sage: f.degree() <= 20 True + sage: f.valuation() >= -10 + True + sage: f.parent() is L + True + + :: + + sage: L = LaurentPolynomialRing(ZZ, 2, 'x') + sage: f = L.random_element(-10, 20) + sage: tuple(f.degree(x) <= 20 for x in L.gens()) + (True, True) + sage: tuple(f.valuation(x) >= -10 for x in L.gens()) + (True, True) + sage: len(list(f)) <= 5 + True + + :: + + sage: L = LaurentPolynomialRing(ZZ, 2, 'x') + sage: f = L.random_element(-10, 20, terms=20) + sage: tuple(f.degree(x) <= 20 for x in L.gens()) + (True, True) sage: tuple(f.valuation(x) >= -10 for x in L.gens()) (True, True) + sage: len(list(f)) <= 20 + True :: @@ -563,7 +596,8 @@ def random_element(self, min_valuation=-2, high_degree=2, *args, **kwds): sage: rings = [RR, QQ, ZZ, GF(13), GF(7^3)] sage: for ring in rings: - ....: d = randint(1, 5) + ....: d = randint(2, 6) + ....: t = randint(5, 20) ....: L = LaurentPolynomialRing(ring, d, 'x') ....: for _ in range(100): ....: n, m = randint(-10, 10), randint(1, 10) @@ -571,30 +605,59 @@ def random_element(self, min_valuation=-2, high_degree=2, *args, **kwds): ....: m, n = n, m ....: f = L.random_element(n, m) ....: if f.is_zero(): continue # the zero polynomial is defined to have degree -1 + ....: assert len(list(f)) <= t ....: for x in L.gens(): ....: assert f.degree(x) <= m ....: assert f.valuation(x) >= n - """ - # Ensure the degree parameters are sensible - if high_degree < min_valuation: - raise ValueError("`high_degree` must be greater than or equal to `min_valuation`") - - # First sample a polynomial from the associated polynomial - # ring of `self` of degree `(high_degree - min_valuation)` - abs_deg = (high_degree - min_valuation) - f_rand = self._R.random_element(degree=abs_deg, *args, **kwds) - - # Coerce back to `self`. We now have a polynomial of only - # positive valuation monomials - f = self(f_rand) - # Finally, shift the entire polynomial down by min_valuation - # which will result in a polynomial with highest degree - # high_degree and lowest valuation min_valuation - monomial = prod(self.gens()) - f *= monomial**min_valuation + Test for constructions which use univariate polynomial rings:: - return f + sage: rings = [RR, QQ, ZZ, GF(13), GF(7^3)] + sage: for ring in rings: + ....: L. = LaurentPolynomialRing(ring) + ....: for _ in range(100): + ....: n, m = randint(-10, 10), randint(1, 10) + ....: if m < n: + ....: m, n = n, m + ....: f = L.random_element(n, m) + ....: if f.is_zero(): continue # the zero polynomial is defined to have degree -1 + ....: for x in L.gens(): + ....: assert f.degree() <= m + ....: assert f.valuation() >= n + """ + # Ensure the degree parameters are sensible + if max_degree < min_valuation: + raise ValueError("`max_degree` must be greater than or equal to `min_valuation`") + + # Handle univariate case + if self._n == 1: + # First sample a polynomial from the associated polynomial + # ring of `self` of degree `(max_degree - min_valuation)` + abs_deg = (max_degree - min_valuation) + f_rand = self._R.random_element(degree=abs_deg, **kwds) + + # Case the polynomial base to self and scale to ensure + # that min_valuation is satisfied + s = self.gen() ** min_valuation + return self(f_rand) * s + + # Ensure terms is set correctly + if terms < 0: + raise TypeError("cannot compute polynomial with a negative number of terms.") + elif terms == 0: + return self._zero_element + + # We now sample `terms`` terms with exponents picked randomly + # with degree at most `max_degree` and valuation greater or + # equal to min_valuation scaled by an element of the base ring + k = self.base_ring() + n = self._n + res = self.zero() + for _ in range(terms): + s = k.random_element(**kwds) + ele = [randint(min_valuation, max_degree) for _ in range(n)] + res += s * self.monomial(*ele) + return res def is_exact(self): """ From c5e0d4ffc3a4807cfd258c2c12ce2e4c5834ed9f Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Thu, 29 Feb 2024 10:34:09 +0000 Subject: [PATCH 127/518] reviewer feedback --- .../polynomial/laurent_polynomial_mpair.pyx | 20 +++++++++++--- .../laurent_polynomial_ring_base.py | 27 +++++++++++++++++-- 2 files changed, 41 insertions(+), 6 deletions(-) diff --git a/src/sage/rings/polynomial/laurent_polynomial_mpair.pyx b/src/sage/rings/polynomial/laurent_polynomial_mpair.pyx index 769a7c27e7d..c58dbd16b3a 100644 --- a/src/sage/rings/polynomial/laurent_polynomial_mpair.pyx +++ b/src/sage/rings/polynomial/laurent_polynomial_mpair.pyx @@ -1201,27 +1201,39 @@ cdef class LaurentPolynomial_mpair(LaurentPolynomial): -5 sage: R.zero().valuation() +Infinity + + TESTS: + + If supplied, ``x`` must be a generator:: + + sage: R. = LaurentPolynomialRing(ZZ) + sage: f = 1 + x + x^2*y^-1 + sage: f.valuation(1) + Traceback (most recent call last): + ... + TypeError: x must be a generator of parent """ # Valuation of zero polynomial is defined to be +Infinity if self.is_zero(): return Infinity - # TODO: is there a faster cython-way to do this? + # When x is None find the minimal valuation by checking all terms if x is None: - return min(sum(e) for e in self.exponents()) + val = min(e.unweighted_degree() for e in self.exponents()) + return Integer(val) # Get the index of the gen cdef tuple g = self._parent.gens() cdef Py_ssize_t i cdef bint no_generator_found = True for i in range(len(g)): - if g[i] is x: + if g[i] == x: no_generator_found = False break if no_generator_found: raise TypeError("x must be a generator of parent") - # TODO: is there a faster cython-way to do this? + # Find the minimal valuation of x by checking each term return min(e[i] for e in self.exponents()) def has_inverse_of(self, i): diff --git a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py index dd596414538..0a7ae996ea4 100644 --- a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py +++ b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py @@ -624,6 +624,29 @@ def random_element(self, min_valuation=-2, max_degree=2, terms=5, **kwds): ....: for x in L.gens(): ....: assert f.degree() <= m ....: assert f.valuation() >= n + + The ``max_degree`` must be greater than or equal to ``min_valuation``:: + + sage: L. = LaurentPolynomialRing(QQ) + sage: f = L.random_element(1, -1) + Traceback (most recent call last): + ... + ValueError: `max_degree` must be greater than or equal to `min_valuation` + + When terms is set to zero, we only expect the zero polynomial:: + + sage: L. = LaurentPolynomialRing(QQ) + sage: f = L.random_element(-10, 20, terms=0) + sage: f.is_zero() + True + + Terms must always be negative:: + + sage: L. = LaurentPolynomialRing(QQ) + sage: f = L.random_element(-10, 20, terms=-1) + Traceback (most recent call last): + ... + TypeError: cannot compute polynomial with a negative number of terms """ # Ensure the degree parameters are sensible if max_degree < min_valuation: @@ -643,9 +666,9 @@ def random_element(self, min_valuation=-2, max_degree=2, terms=5, **kwds): # Ensure terms is set correctly if terms < 0: - raise TypeError("cannot compute polynomial with a negative number of terms.") + raise TypeError("cannot compute polynomial with a negative number of terms") elif terms == 0: - return self._zero_element + return self.zero() # We now sample `terms`` terms with exponents picked randomly # with degree at most `max_degree` and valuation greater or From 1c2cd4d5f0a68c526ff2edf32e141e479eccbadf Mon Sep 17 00:00:00 2001 From: Giacomo Pope <44242839+GiacomoPope@users.noreply.github.com> Date: Thu, 29 Feb 2024 10:46:30 +0000 Subject: [PATCH 128/518] Update laurent_polynomial_ring_base.py --- src/sage/rings/polynomial/laurent_polynomial_ring_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py index 0a7ae996ea4..970514e99b1 100644 --- a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py +++ b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py @@ -640,7 +640,7 @@ def random_element(self, min_valuation=-2, max_degree=2, terms=5, **kwds): sage: f.is_zero() True - Terms must always be negative:: + Terms must always be non-negative:: sage: L. = LaurentPolynomialRing(QQ) sage: f = L.random_element(-10, 20, terms=-1) From 1744f3aee2ebdcfdc0cb19481200ae3ce7c65fd1 Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Thu, 29 Feb 2024 10:48:35 +0000 Subject: [PATCH 129/518] add missing arg in randomised testing --- src/sage/rings/polynomial/laurent_polynomial_ring_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py index 970514e99b1..6544d905559 100644 --- a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py +++ b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py @@ -603,7 +603,7 @@ def random_element(self, min_valuation=-2, max_degree=2, terms=5, **kwds): ....: n, m = randint(-10, 10), randint(1, 10) ....: if m < n: ....: m, n = n, m - ....: f = L.random_element(n, m) + ....: f = L.random_element(n, m, terms=t) ....: if f.is_zero(): continue # the zero polynomial is defined to have degree -1 ....: assert len(list(f)) <= t ....: for x in L.gens(): From 9412048bbdac1d1b5c0b5a75bc443ef7547d8a20 Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Thu, 29 Feb 2024 10:49:22 +0000 Subject: [PATCH 130/518] Allow the degree to be one to test the case of 'univariate' multivariate rings --- src/sage/rings/polynomial/laurent_polynomial_ring_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py index 6544d905559..d770e7956de 100644 --- a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py +++ b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py @@ -596,7 +596,7 @@ def random_element(self, min_valuation=-2, max_degree=2, terms=5, **kwds): sage: rings = [RR, QQ, ZZ, GF(13), GF(7^3)] sage: for ring in rings: - ....: d = randint(2, 6) + ....: d = randint(1, 6) ....: t = randint(5, 20) ....: L = LaurentPolynomialRing(ring, d, 'x') ....: for _ in range(100): From 2bc19b2672682be108698e7c33ac9a43d40aa41e Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Sat, 2 Mar 2024 22:16:22 +0000 Subject: [PATCH 131/518] remove stupid mistake --- .../rings/polynomial/laurent_polynomial_mpair.pyx | 8 ++------ .../rings/polynomial/laurent_polynomial_ring_base.py | 12 ++++-------- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/src/sage/rings/polynomial/laurent_polynomial_mpair.pyx b/src/sage/rings/polynomial/laurent_polynomial_mpair.pyx index 5b0a7622dff..fdd0671fd0d 100644 --- a/src/sage/rings/polynomial/laurent_polynomial_mpair.pyx +++ b/src/sage/rings/polynomial/laurent_polynomial_mpair.pyx @@ -1235,12 +1235,8 @@ cdef class LaurentPolynomial_mpair(LaurentPolynomial): raise TypeError("x must be a generator of parent") # Find the minimal valuation of x by checking each term - # if the polynomial has no terms containing the generator - # `min` will throw a value error and we return +Infinity - try: - return min(e[i] for e in self.exponents() if e[i] != 0) - except ValueError: - return Infinity + return min(e[i] for e in self.exponents()) + def has_inverse_of(self, i): """ diff --git a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py index 73959307556..c93220b41aa 100644 --- a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py +++ b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py @@ -584,7 +584,7 @@ def random_element(self, min_valuation=-2, max_degree=2, terms=5, **kwds): """ Return a random polynomial with degree at most ``max_degree`` and lowest valuation at least ``min_valuation``, for both the total degree - and valuation as well as for each generator of ``self``. + and valuation as well as for each generator of ``self``. For the univariate case, uses the random sampling from the polynomial ring then shifts this polynomial down to ensure @@ -607,7 +607,7 @@ def random_element(self, min_valuation=-2, max_degree=2, terms=5, **kwds): terms are requested than exist, then this parameter is silently reduced to the maximum number of available terms. - - ``**kwargs`` -- passed to the random element generator of the base + - ``**kwds`` -- passed to the random element generator of the base ring EXAMPLES:: @@ -710,9 +710,7 @@ def random_element(self, min_valuation=-2, max_degree=2, terms=5, **kwds): ....: t = randint(5, 20) ....: L = LaurentPolynomialRing(ring, d, 'x') ....: for _ in range(100): - ....: n, m = randint(-10, 10), randint(1, 10) - ....: if m < n: - ....: m, n = n, m + ....: n, m = randint(-10, 0), randint(0, 10) ....: f = L.random_element(n, m, terms=t) ....: if f.is_zero(): continue # the zero polynomial is defined to have degree -1 ....: assert len(list(f)) <= t @@ -728,9 +726,7 @@ def random_element(self, min_valuation=-2, max_degree=2, terms=5, **kwds): sage: for ring in rings: ....: L. = LaurentPolynomialRing(ring) ....: for _ in range(100): - ....: n, m = randint(-10, 10), randint(1, 10) - ....: if m < n: - ....: m, n = n, m + ....: n, m = randint(-10, 0), randint(0, 10) ....: f = L.random_element(n, m) ....: if f.is_zero(): continue # the zero polynomial is defined to have degree -1 ....: for x in L.gens(): From dfe35622512019b36a9452b3d917debd78db7add Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 19 Feb 2024 13:00:03 -0800 Subject: [PATCH 132/518] src/doc/en/developer/packaging.rst: Explain that package-version.txt needs to be an acceptable version according to install-requires.txt --- src/doc/en/developer/packaging.rst | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/doc/en/developer/packaging.rst b/src/doc/en/developer/packaging.rst index ea391941c9c..8720ab755ba 100644 --- a/src/doc/en/developer/packaging.rst +++ b/src/doc/en/developer/packaging.rst @@ -1114,8 +1114,10 @@ obtains most of the necessary information by querying PyPI. The ``dependencies`` file may need editing (watch out for warnings regarding ``--no-deps`` that Sage issues during installation of the package!). + Also you may want to set lower and upper bounds for acceptable package versions -in the file ``install-requires.txt``. +in the file ``install-requires.txt``. (Make sure that the version in +``package-version.txt`` falls within this acceptable version range!) By default, when the package is available as a platform-independent wheel, the ``sage --package`` creates a wheel package. To create a normal package @@ -1152,6 +1154,12 @@ For Python packages available from PyPI, there is another shortcut:: Downloading tarball to ...matplotlib-3.3.1.tar.bz2 [...............................................................] +When preparing the update, check that any lower and upper bounds for +acceptable package versions that may be declared in the file +``install-requires.txt`` are still correct, and update them as needed. +The version in ``package-version.txt`` always needs to fall within the +version range! + If you pass the switch ``--commit``, the script will run ``git commit`` for you. From d7413b439963ed2f4bc5be0e148b0435279a6a43 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 20 Feb 2024 00:59:11 -0800 Subject: [PATCH 133/518] src/doc/en/developer/packaging.rst: Explain what is done with install-requires.txt --- src/doc/en/developer/packaging.rst | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/doc/en/developer/packaging.rst b/src/doc/en/developer/packaging.rst index 8720ab755ba..fb70576d061 100644 --- a/src/doc/en/developer/packaging.rst +++ b/src/doc/en/developer/packaging.rst @@ -603,9 +603,14 @@ For example, the ``scipy`` ``spkg-check.in`` file contains the line exec python3 spkg-check.py +The install-requires.txt file +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + All normal Python packages and all wheel packages must have a file ``install-requires.txt``. If a Python package is available on PyPI, this file must contain the -name of the package as it is known to PyPI. Optionally, +name of the package as it is known to PyPI. + +Optionally, ``install-requires.txt`` can encode version constraints (such as lower and upper bounds). The constraints are in the format of the ``install_requires`` key of `setup.cfg @@ -613,6 +618,16 @@ and upper bounds). The constraints are in the format of the or `setup.py `_. +Sage uses these version constraints for two purposes: + +- As a source for generating the metadata of the Python + distribution packages in ``SAGE_ROOT/pkgs/``, see + :ref:`section_dependencies_distributions`. + +- When the experimental option ``configure --enable-system-site-packages`` is used, + then the ``configure`` script checks these constraints to determine whether + to accept an installation of this package in the system Python. + It is strongly recommended to include comments (starting with ``#``) in the file that explain why a particular lower or upper bound is warranted or why we wish to include or reject certain versions. From c6b56632227bd5b1e2dbc04800ef2472659066fa Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 23 Feb 2024 08:36:41 -0800 Subject: [PATCH 134/518] src/doc/en/developer/packaging.rst: More on package-version.txt for Python packages --- src/doc/en/developer/packaging.rst | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/doc/en/developer/packaging.rst b/src/doc/en/developer/packaging.rst index fb70576d061..541192fa60a 100644 --- a/src/doc/en/developer/packaging.rst +++ b/src/doc/en/developer/packaging.rst @@ -603,8 +603,8 @@ For example, the ``scipy`` ``spkg-check.in`` file contains the line exec python3 spkg-check.py -The install-requires.txt file -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Abstract requirements: The install-requires.txt file +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ All normal Python packages and all wheel packages must have a file ``install-requires.txt``. If a Python package is available on PyPI, this file must contain the @@ -660,6 +660,21 @@ Setting upper bounds to guard against incompatible future changes is a complex topic; see :trac:`33520`. +Concrete (pinned) requirements: The package-version.txt file +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Like normal non-Python packages, all normal Python packages and all wheel packages +must have a file ``package-version.txt``. + +Sage uses this version for two purposes: + +- This is the version that the Sage distribution ships. + +- As a source for generating the ``requirements.txt`` files of + the Python distribution packages in ``SAGE_ROOT/pkgs/``, see + :ref:`section_dependencies_distributions`. + + .. _section-spkg-SPKG-txt: The SPKG.rst file From c3dfcbec929dc3e55292fd5b92f70fd9b59e7581 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 23 Feb 2024 08:44:26 -0800 Subject: [PATCH 135/518] src/doc/en/developer/packaging.rst: Link to pip User Guide --- src/doc/en/developer/packaging.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/doc/en/developer/packaging.rst b/src/doc/en/developer/packaging.rst index 541192fa60a..33d88ac99ed 100644 --- a/src/doc/en/developer/packaging.rst +++ b/src/doc/en/developer/packaging.rst @@ -674,6 +674,9 @@ Sage uses this version for two purposes: the Python distribution packages in ``SAGE_ROOT/pkgs/``, see :ref:`section_dependencies_distributions`. + For the use of the generated ``requirements.txt`` files, see + the `pip User Guide `_. + .. _section-spkg-SPKG-txt: From 8c5710d14f0f9a0748dc2b3c3e5474f1639fd598 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 27 Feb 2024 18:32:03 -0800 Subject: [PATCH 136/518] src/doc/en/developer/packaging.rst: Add more material on version pinning etc. from sage-devel thread 'allow standard packages to be pip packages' --- src/doc/en/developer/packaging.rst | 90 ++++++++++++++++++++++++++---- 1 file changed, 79 insertions(+), 11 deletions(-) diff --git a/src/doc/en/developer/packaging.rst b/src/doc/en/developer/packaging.rst index 33d88ac99ed..028725ad8ce 100644 --- a/src/doc/en/developer/packaging.rst +++ b/src/doc/en/developer/packaging.rst @@ -579,7 +579,7 @@ and most Python-based packages will also have ``$(PYTHON_TOOLCHAIN)`` as an order-only dependency, which will ensure that fundamental packages such as ``pip`` and ``setuptools`` are available at the time of building the package. -The best way to install a Python-based package is to use ``pip``, in which +The best way to install a ``normal`` Python-based package is to use ``pip``, in which case the ``spkg-install.in`` script template might just consist of .. CODE-BLOCK:: bash @@ -603,12 +603,15 @@ For example, the ``scipy`` ``spkg-check.in`` file contains the line exec python3 spkg-check.py -Abstract requirements: The install-requires.txt file -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Abstract requirements: The ``install-requires.txt`` file +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -All normal Python packages and all wheel packages must have a file ``install-requires.txt``. -If a Python package is available on PyPI, this file must contain the -name of the package as it is known to PyPI. +All ``normal`` Python packages and all ``wheel`` packages must have a file +``install-requires.txt``. For ``pip`` packages, the file is optional; if +it is missing, the ``requirements.txt`` file is used instead. + +If a Python package is available on PyPI, the ``install-requires.txt`` file must +contain the name of the package as it is known to PyPI. Optionally, ``install-requires.txt`` can encode version constraints (such as lower @@ -660,11 +663,11 @@ Setting upper bounds to guard against incompatible future changes is a complex topic; see :trac:`33520`. -Concrete (pinned) requirements: The package-version.txt file -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Concrete (pinned) requirements of ``normal``, ``wheel``, ``script`` packages: The ``package-version.txt`` file +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Like normal non-Python packages, all normal Python packages and all wheel packages -must have a file ``package-version.txt``. +Like ``normal`` non-Python packages, all ``normal`` Python packages and all ``wheel`` packages +must have a file ``package-version.txt``. For ``script`` Python packages, the file is optional. Sage uses this version for two purposes: @@ -678,6 +681,69 @@ Sage uses this version for two purposes: the `pip User Guide `_. +Concrete requirements of ``pip`` packages: The ``requirements.txt`` file +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In contrast to ``normal``, ``wheel``, and ``script`` packages, the +``pip`` packages do not use a ``package-version.txt`` file. + +Instead, the concrete requirements are set in a ``requirements.txt`` +file, which is passed directly to ``pip`` at installation time. + +The ``requirements.txt`` file uses a very flexible format, defined +in the `pip User Guide +`_. +Through this format, the concrete requirements can either be +pinned to a specific version, or set acceptable version ranges, or be +entirely unconstrained. The format is even flexible enough to install +several distribution packages at the same time, and to conditionalize +on the operating system or Python version. + +Pinning a version has the potential benefit of stability, as it can +avoid retroactive breakage of the Sage distribution by new, +incompatible versions, and can also help achieve reproducibility +of computations. + +The cost is that updating the version requires +work by at least two Sage developers: One who prepares a PR and one +who reviews it. Moreover, when the package does not get the attention of +developers who upgrade it, there is the potential risk of missing out +on bugfixes made in newer versions, or missing out on features in +major new versions. + +Not pinning the version has the obvious potential benefit of always +being up to date, as ``pip`` contacts the index server (PyPI) to +obtain and install the package. (Note that ``normal`` and ``wheel`` +packages are always pinned and do not even have access to the index +server at the time of building and installing the package.) + +But this dynamism also brings a risk +of instability, either by the package itself being affected by bugs in +a new version, or by breaking compatibility with Sage. + +What policy is best for a package depends on various factors, +including the development velocity and quality control that the +upstream project uses, the interest by Sage developers in the package, +the depth of integration in Sage, whether it affects the mathematics, +etc. + + +Note about dependencies of ``pip`` packages +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Dependencies of a ``pip`` package do not need to be available as packages +in the Sage distribution, as the package can pull some of its build-time and +run-time dependencies directly from PyPI. That's a mild convenience for developers, +and can be important if one wants to leave the version range wide open. + +However, if a dependency is also a package of the Sage distribution, +then we must declare this dependency. Otherwise, various errors +can occur when building or upgrading. When new versions of ``pip`` +packages add dependencies that happen to be Sage packages, there is a +separate source of instability. + + + .. _section-spkg-SPKG-txt: The SPKG.rst file @@ -1143,7 +1209,9 @@ For Python packages available from PyPI, you can use:: --type optional This automatically downloads the most recent version from PyPI and also -obtains most of the necessary information by querying PyPI. +obtains most of the necessary information by querying PyPI. In particular, +the ``SPKG.rst`` file is created as a copy of the package's README file. + The ``dependencies`` file may need editing (watch out for warnings regarding ``--no-deps`` that Sage issues during installation of the package!). From 5b6ef259040d7a119768d4521c3020a957a37818 Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Sun, 3 Mar 2024 01:11:17 +0000 Subject: [PATCH 137/518] Simplify function: --- .../laurent_polynomial_ring_base.py | 34 ++++--------------- 1 file changed, 7 insertions(+), 27 deletions(-) diff --git a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py index c93220b41aa..0b79ad28f94 100644 --- a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py +++ b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py @@ -544,35 +544,15 @@ def _random_bounded_monomial(self, min_valuation=-2, max_degree=2): # To ensure the total degree/valuation bound is satisfied, we # need to know the sum of exponents s = sum(exponents) + lower_bound = max(min_valuation, min_valuation - s) + upper_bound = min(max_degree, max_degree - s) - # When both bounds are positive, we only need to worry about - # the degree getting too large - if min_valuation >= 0: - upper_bound = min(max_degree, max_degree - s) - if min_valuation <= upper_bound: - r = randint(min_valuation, upper_bound) - else: - r = 0 - - # When both bounds are negative we only need to worry about - # the valuation becoming too negative - elif max_degree <= 0: - lower_bound = max(min_valuation, min_valuation - s) - if max_degree >= lower_bound: - r = randint(lower_bound, max_degree) - else: - r = 0 - - # We assume max_degree > min_valuation so no we are in the - # position with positive max_degree and negative min_valuation - # we must ensure both upper and lower bounds are respected + # As long as the bounds are sensible pick a random exponent + # otherwise pick 0 + if upper_bound >= lower_bound: + r = randint(lower_bound, upper_bound) else: - upper_bound = min(max_degree, max_degree - s) - lower_bound = max(min_valuation, min_valuation - s) - if upper_bound >= lower_bound: - r = randint(lower_bound, upper_bound) - else: - r = 0 + r = 0 exponents.append(r) From e19045d3526fe8207874a8526390aa8af43b72d0 Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Sun, 3 Mar 2024 10:52:23 +0000 Subject: [PATCH 138/518] Move function to proper class and add more tests --- .../polynomial/laurent_polynomial_ring.py | 98 +++++++++++++++++++ .../laurent_polynomial_ring_base.py | 73 -------------- 2 files changed, 98 insertions(+), 73 deletions(-) diff --git a/src/sage/rings/polynomial/laurent_polynomial_ring.py b/src/sage/rings/polynomial/laurent_polynomial_ring.py index 812a4b3a351..c7f473508fd 100644 --- a/src/sage/rings/polynomial/laurent_polynomial_ring.py +++ b/src/sage/rings/polynomial/laurent_polynomial_ring.py @@ -47,6 +47,7 @@ from sage.rings.polynomial.laurent_polynomial_ring_base import LaurentPolynomialRing_generic from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.structure.element import parent +from sage.misc.prandom import randint, shuffle def is_LaurentPolynomialRing(R): @@ -611,6 +612,103 @@ def _repr_(self): """ return "Multivariate Laurent Polynomial Ring in %s over %s" % (", ".join(self._R.variable_names()), self._R.base_ring()) + def _random_bounded_monomial(self, min_valuation=-2, max_degree=2): + """ + Computes a random monomial which has total degree at most + ``max_degree``, total valuation at least ``min_valuation``. + Also has degree and valuation within these bounds for each + generator of the polynomial ring. + + .. NOTE:: + + When both bounds are negative (positive) it is + still possible for the monomial to have an exponent zero + in one of the generators. As such, the degree (valuation) + will be zero in this generator and the degree (valuation) + will be outside of the user supplied bounds. The total + degree or valuation is always respected though. + + Assumes that min_valuation <= max_degree, which is handled by + ``random_element()``. + + EXAMPLES:: + + sage: R. = LaurentPolynomialRing(ZZ) + sage: f = R._random_bounded_monomial(-5, 5) + sage: f.degree() <= 5 + True + sage: f.valuation() >= -5 + True + sage: tuple(f.degree(x) <= 5 for x in R.gens()) + (True, True, True) + sage: tuple(f.valuation(x) >= -5 for x in R.gens()) + (True, True, True) + + :: + + sage: R. = LaurentPolynomialRing(QQbar) + sage: f = R._random_bounded_monomial(7, 10) + sage: f.degree() <= 10 + True + sage: f.valuation() >= 7 + True + sage: tuple(f.degree(x) <= 10 for x in R.gens()) + (True, True) + sage: tuple(f.valuation(x) >= 7 or f.valuation(x) == 0 for x in R.gens()) + (True, True) + + :: + + sage: R. = LaurentPolynomialRing(GF(13^2)) + sage: f = R._random_bounded_monomial(-10, -8) + sage: f.degree() <= -8 + True + sage: f.valuation() >= -10 + True + sage: tuple(f.degree(x) <= -8 or f.degree(x) == 0 for x in R.gens()) + (True, True, True, True) + sage: tuple(f.valuation(x) >= -10 for x in R.gens()) + (True, True, True, True) + + TESTS:: + + sage: R. = LaurentPolynomialRing(QQ) + sage: for _ in range(10): + ....: high = randint(-10, 10) + ....: low = randint(-10, 10) + ....: if high < low: + ....: high, low = low, high + ....: for _ in range(100): + ....: f = R._random_bounded_monomial(low, high) + ....: assert f.degree() <= high + ....: assert f.valuation() >= low + ....: assert all(f.degree(x) <= high or f.degree(x) == 0 for x in R.gens()) + ....: assert all(f.valuation(x) >= low or f.valuation(x) == 0 for x in R.gens()) + """ + # To ensure the total degree/valuation bound is satisfied, we + # need to know the sum of exponents + exponents = [] + s = 0 + + for _ in range(self._n): + # Set lower and upper bound from the sum of exp so far + lower_bound = max(min_valuation, min_valuation - s) + upper_bound = min(max_degree, max_degree - s) + + # As long as the bounds are sensible pick a random exponent + # otherwise pick 0 + if upper_bound >= lower_bound: + r = randint(lower_bound, upper_bound) + s += r + else: + r = 0 + + exponents.append(r) + + # Shuffle the order of the exponents and create a monomial + shuffle(exponents) + return self.monomial(*exponents) + def monomial(self, *args): r""" Return the monomial whose exponents are given in argument. diff --git a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py index 0b79ad28f94..966059808a2 100644 --- a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py +++ b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py @@ -28,7 +28,6 @@ from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.rings.ring import CommutativeRing from sage.structure.parent import Parent -from sage.misc.prandom import randint, shuffle class LaurentPolynomialRing_generic(CommutativeRing, Parent): """ @@ -488,78 +487,6 @@ def krull_dimension(self): """ raise NotImplementedError - def _random_bounded_monomial(self, min_valuation=-2, max_degree=2): - """ - Helper function for ``random_element`` which computes a - random monomial which has total degree at most ``max_degree``, - total valuation at least ``min_valuation`` and also has degree - and valuation within these bounds for each generator of the - polynomial ring. - - Assumes that min_valuation <= max_degree, which is handled by - ``random_element()``. - - EXAMPLES:: - - sage: R. = LaurentPolynomialRing(ZZ) - sage: f = R._random_bounded_monomial(-5, 5) - sage: f.degree() <= 5 - True - sage: f.valuation() >= -5 - True - - :: - - sage: R. = LaurentPolynomialRing(ZZ) - sage: f = R._random_bounded_monomial(7, 10) - sage: f.degree() <= 10 - True - sage: f.valuation() >= 7 - True - - :: - - sage: R. = LaurentPolynomialRing(ZZ) - sage: f = R._random_bounded_monomial(-10, -8) - sage: f.degree() <= -8 - True - sage: f.valuation() >= -10 - True - - TESTS:: - - sage: R. = LaurentPolynomialRing(ZZ) - sage: for _ in range(10): - ....: high = randint(-10, 10) - ....: low = randint(-10, 10) - ....: if high < low: - ....: high, low = low, high - ....: for _ in range(100): - ....: f = R._random_bounded_monomial(low, high) - ....: assert f.degree() <= high - ....: assert f.valuation() >= low - """ - exponents = [] - for _ in range(self._n): - # To ensure the total degree/valuation bound is satisfied, we - # need to know the sum of exponents - s = sum(exponents) - lower_bound = max(min_valuation, min_valuation - s) - upper_bound = min(max_degree, max_degree - s) - - # As long as the bounds are sensible pick a random exponent - # otherwise pick 0 - if upper_bound >= lower_bound: - r = randint(lower_bound, upper_bound) - else: - r = 0 - - exponents.append(r) - - # Shuffle the order of the exponents and create a monomial - shuffle(exponents) - return self.monomial(*exponents) - def random_element(self, min_valuation=-2, max_degree=2, terms=5, **kwds): """ Return a random polynomial with degree at most ``max_degree`` and From 59eb8ec5aa9f98c52dac73fc999a80451d1dc928 Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Sun, 3 Mar 2024 11:12:57 +0000 Subject: [PATCH 139/518] add note about edge cases --- .../rings/polynomial/laurent_polynomial_ring_base.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py index 966059808a2..2249b529c65 100644 --- a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py +++ b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py @@ -517,6 +517,18 @@ def random_element(self, min_valuation=-2, max_degree=2, terms=5, **kwds): - ``**kwds`` -- passed to the random element generator of the base ring + .. NOTE:: + + For the multivariate case, it's possible to randomly sample + terms such that the resulting polynomial ``f`` does not contain + one or more of the generators ``x`` of the ring. When this is + the case, both ``f.degree(x)`` and ``f.valuation(x)`` will be + zero. If both ``min_valuation`` and ``max_degree`` are negative + (resp. positive) then we will have ``f.degree(x) > max_degree`` + (resp. ``f.valuation(x) < min_valuation``) and the bound assumptions + for this generator no longer hold. However, the total degree and + valuations always hold regardless of the signs of the bounds. + EXAMPLES:: sage: L. = LaurentPolynomialRing(QQ) From d3e3bc908ddc3de111a49cbd6b28aee713d65643 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 3 Mar 2024 20:15:03 -0800 Subject: [PATCH 140/518] src/doc/en/developer/coding_basics.rst: Raw-string recommendation: reviewer's rewording --- src/doc/en/developer/coding_basics.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/doc/en/developer/coding_basics.rst b/src/doc/en/developer/coding_basics.rst index 1272a2b1f24..fe668b5357d 100644 --- a/src/doc/en/developer/coding_basics.rst +++ b/src/doc/en/developer/coding_basics.rst @@ -781,9 +781,8 @@ In Sage's documentation LaTeX code is allowed and is marked with **backticks**: ```x^2 + y^2 = 1``` yields `x^2 + y^2 = 1`. -**Backslashes:** For LaTeX commands containing backslashes, it is equivalent to -use either double backslashes or begin the docstring with a ``r"""`` instead -of ``"""``, but we strongly suggest to use the latter:: +**Backslashes:** For LaTeX commands containing backslashes, either use double +backslashes or begin the docstring with a ``r"""`` instead of ``"""``:: def cos(x): """ @@ -795,6 +794,8 @@ of ``"""``, but we strongly suggest to use the latter:: Return `\sin(x)`. """ +We strongly suggest to use the latter. + **MATH block:** This is similar to the LaTeX syntax ``\[\]`` (or ``$$$$``). For instance: From a825e0ddbe542b5427358466ea753eb738838b56 Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Mon, 4 Mar 2024 10:37:19 +0000 Subject: [PATCH 141/518] Simplify function by removing restrictions --- .../polynomial/laurent_polynomial_ring.py | 97 ----------- .../laurent_polynomial_ring_base.py | 157 +++++------------- 2 files changed, 39 insertions(+), 215 deletions(-) diff --git a/src/sage/rings/polynomial/laurent_polynomial_ring.py b/src/sage/rings/polynomial/laurent_polynomial_ring.py index c7f473508fd..b103cc3dfce 100644 --- a/src/sage/rings/polynomial/laurent_polynomial_ring.py +++ b/src/sage/rings/polynomial/laurent_polynomial_ring.py @@ -612,103 +612,6 @@ def _repr_(self): """ return "Multivariate Laurent Polynomial Ring in %s over %s" % (", ".join(self._R.variable_names()), self._R.base_ring()) - def _random_bounded_monomial(self, min_valuation=-2, max_degree=2): - """ - Computes a random monomial which has total degree at most - ``max_degree``, total valuation at least ``min_valuation``. - Also has degree and valuation within these bounds for each - generator of the polynomial ring. - - .. NOTE:: - - When both bounds are negative (positive) it is - still possible for the monomial to have an exponent zero - in one of the generators. As such, the degree (valuation) - will be zero in this generator and the degree (valuation) - will be outside of the user supplied bounds. The total - degree or valuation is always respected though. - - Assumes that min_valuation <= max_degree, which is handled by - ``random_element()``. - - EXAMPLES:: - - sage: R. = LaurentPolynomialRing(ZZ) - sage: f = R._random_bounded_monomial(-5, 5) - sage: f.degree() <= 5 - True - sage: f.valuation() >= -5 - True - sage: tuple(f.degree(x) <= 5 for x in R.gens()) - (True, True, True) - sage: tuple(f.valuation(x) >= -5 for x in R.gens()) - (True, True, True) - - :: - - sage: R. = LaurentPolynomialRing(QQbar) - sage: f = R._random_bounded_monomial(7, 10) - sage: f.degree() <= 10 - True - sage: f.valuation() >= 7 - True - sage: tuple(f.degree(x) <= 10 for x in R.gens()) - (True, True) - sage: tuple(f.valuation(x) >= 7 or f.valuation(x) == 0 for x in R.gens()) - (True, True) - - :: - - sage: R. = LaurentPolynomialRing(GF(13^2)) - sage: f = R._random_bounded_monomial(-10, -8) - sage: f.degree() <= -8 - True - sage: f.valuation() >= -10 - True - sage: tuple(f.degree(x) <= -8 or f.degree(x) == 0 for x in R.gens()) - (True, True, True, True) - sage: tuple(f.valuation(x) >= -10 for x in R.gens()) - (True, True, True, True) - - TESTS:: - - sage: R. = LaurentPolynomialRing(QQ) - sage: for _ in range(10): - ....: high = randint(-10, 10) - ....: low = randint(-10, 10) - ....: if high < low: - ....: high, low = low, high - ....: for _ in range(100): - ....: f = R._random_bounded_monomial(low, high) - ....: assert f.degree() <= high - ....: assert f.valuation() >= low - ....: assert all(f.degree(x) <= high or f.degree(x) == 0 for x in R.gens()) - ....: assert all(f.valuation(x) >= low or f.valuation(x) == 0 for x in R.gens()) - """ - # To ensure the total degree/valuation bound is satisfied, we - # need to know the sum of exponents - exponents = [] - s = 0 - - for _ in range(self._n): - # Set lower and upper bound from the sum of exp so far - lower_bound = max(min_valuation, min_valuation - s) - upper_bound = min(max_degree, max_degree - s) - - # As long as the bounds are sensible pick a random exponent - # otherwise pick 0 - if upper_bound >= lower_bound: - r = randint(lower_bound, upper_bound) - s += r - else: - r = 0 - - exponents.append(r) - - # Shuffle the order of the exponents and create a monomial - shuffle(exponents) - return self.monomial(*exponents) - def monomial(self, *args): r""" Return the monomial whose exponents are given in argument. diff --git a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py index 2249b529c65..e7c1099a838 100644 --- a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py +++ b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py @@ -28,6 +28,7 @@ from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.rings.ring import CommutativeRing from sage.structure.parent import Parent +from sage.combinat.integer_vector import IntegerVectors class LaurentPolynomialRing_generic(CommutativeRing, Parent): """ @@ -487,19 +488,13 @@ def krull_dimension(self): """ raise NotImplementedError - def random_element(self, min_valuation=-2, max_degree=2, terms=5, **kwds): + def random_element(self, min_valuation=-2, max_degree=2, terms=5, *args, **kwds): """ Return a random polynomial with degree at most ``max_degree`` and - lowest valuation at least ``min_valuation``, for both the total degree - and valuation as well as for each generator of ``self``. + lowest valuation at least ``min_valuation``. - For the univariate case, uses the random sampling from the - polynomial ring then shifts this polynomial down to ensure - correct ``max_degree`` and ``min_valuation``. - - For the multivariate case, samples ``terms`` elements which - respectively have degree at most ``max_degree`` and valuation - at least ``min_valuation`` and returns their sum. + Uses the random sampling from the base polynomial ring then divides out + by a monomial to ensure correct ``max_degree`` and ``min_valuation``. INPUT: @@ -509,25 +504,8 @@ def random_element(self, min_valuation=-2, max_degree=2, terms=5, **kwds): - ``max_degree`` -- integer (default: ``2``); the maximal allowed degree of the polynomial - - ``terms`` -- number of terms requested (default: 5). - Only used for multivariate polynomial rings. If more - terms are requested than exist, then this parameter is - silently reduced to the maximum number of available terms. - - - ``**kwds`` -- passed to the random element generator of the base - ring - - .. NOTE:: - - For the multivariate case, it's possible to randomly sample - terms such that the resulting polynomial ``f`` does not contain - one or more of the generators ``x`` of the ring. When this is - the case, both ``f.degree(x)`` and ``f.valuation(x)`` will be - zero. If both ``min_valuation`` and ``max_degree`` are negative - (resp. positive) then we will have ``f.degree(x) > max_degree`` - (resp. ``f.valuation(x) < min_valuation``) and the bound assumptions - for this generator no longer hold. However, the total degree and - valuations always hold regardless of the signs of the bounds. + - ``*args, **kwds`` -- passed to the random element generator of the + base polynomial ring and base ring itself EXAMPLES:: @@ -540,17 +518,6 @@ def random_element(self, min_valuation=-2, max_degree=2, terms=5, **kwds): sage: f.parent() is L True - :: - - sage: L. = LaurentPolynomialRing(QQ) - sage: f = L.random_element(-10, 20) - sage: f.degree() <= 20 - True - sage: f.valuation() >= -10 - True - sage: f.parent() is L - True - :: sage: L = LaurentPolynomialRing(ZZ, 2, 'x') @@ -559,26 +526,7 @@ def random_element(self, min_valuation=-2, max_degree=2, terms=5, **kwds): True sage: f.valuation() >= -10 True - sage: tuple(f.degree(x) <= 20 for x in L.gens()) - (True, True) - sage: tuple(f.valuation(x) >= -10 for x in L.gens()) - (True, True) - sage: len(list(f)) <= 5 - True - - :: - - sage: L = LaurentPolynomialRing(ZZ, 2, 'x') - sage: f = L.random_element(-10, 20, terms=20) - sage: f.degree() <= 20 - True - sage: f.valuation() >= -10 - True - sage: tuple(f.degree(x) <= 20 for x in L.gens()) - (True, True) - sage: tuple(f.valuation(x) >= -10 for x in L.gens()) - (True, True) - sage: len(list(f)) <= 20 + sage: f.parent() is L True :: @@ -589,23 +537,19 @@ def random_element(self, min_valuation=-2, max_degree=2, terms=5, **kwds): True sage: f.valuation() >= -5 True - sage: tuple(f.degree(x) <= 10 for x in L.gens()) - (True, True, True) - sage: tuple(f.valuation(x) >= -5 for x in L.gens()) - (True, True, True) + sage: f.parent() is L + True :: - sage: L = LaurentPolynomialRing(RR, 2, 'x') + sage: L. = LaurentPolynomialRing(RR) sage: f = L.random_element() sage: f.degree() <= 2 True sage: f.valuation() >= -2 True - sage: tuple(f.degree(x) <= 2 for x in L.gens()) - (True, True) - sage: tuple(f.valuation(x) >= -2 for x in L.gens()) - (True, True) + sage: f.parent() is L + True :: @@ -616,10 +560,8 @@ def random_element(self, min_valuation=-2, max_degree=2, terms=5, **kwds): True sage: f.valuation() >= -1 True - sage: tuple(f.degree(x) <= 1 for x in L.gens()) - (True, True, True, True, True) - sage: tuple(f.valuation(x) >= -1 for x in L.gens()) - (True, True, True, True, True) + sage: f.parent() is L + True TESTS:: @@ -629,15 +571,14 @@ def random_element(self, min_valuation=-2, max_degree=2, terms=5, **kwds): ....: t = randint(5, 20) ....: L = LaurentPolynomialRing(ring, d, 'x') ....: for _ in range(100): - ....: n, m = randint(-10, 0), randint(0, 10) + ....: n, m = randint(-10, 10), randint(-10, 10) + ....: if n > m: + ....: n, m = m, n ....: f = L.random_element(n, m, terms=t) ....: if f.is_zero(): continue # the zero polynomial is defined to have degree -1 ....: assert len(list(f)) <= t ....: assert f.degree() <= m ....: assert f.valuation() >= n - ....: for x in L.gens(): - ....: assert f.degree(x) <= m, f"{f = }, {x = }, {m = }, {f.degree(x) = }" - ....: assert f.valuation(x) >= n, f"{f = }, {x = }, {n = }, {f.valuation(x) = }" Test for constructions which use univariate polynomial rings:: @@ -645,7 +586,9 @@ def random_element(self, min_valuation=-2, max_degree=2, terms=5, **kwds): sage: for ring in rings: ....: L. = LaurentPolynomialRing(ring) ....: for _ in range(100): - ....: n, m = randint(-10, 0), randint(0, 10) + ....: n, m = randint(-10, 10), randint(-10, 10) + ....: if n > m: + ....: n, m = m, n ....: f = L.random_element(n, m) ....: if f.is_zero(): continue # the zero polynomial is defined to have degree -1 ....: for x in L.gens(): @@ -659,53 +602,31 @@ def random_element(self, min_valuation=-2, max_degree=2, terms=5, **kwds): Traceback (most recent call last): ... ValueError: `max_degree` must be greater than or equal to `min_valuation` - - When terms is set to zero, we only expect the zero polynomial:: - - sage: L. = LaurentPolynomialRing(QQ) - sage: f = L.random_element(-10, 20, terms=0) - sage: f.is_zero() - True - - Terms must always be non-negative:: - - sage: L. = LaurentPolynomialRing(QQ) - sage: f = L.random_element(-10, 20, terms=-1) - Traceback (most recent call last): - ... - TypeError: cannot compute polynomial with a negative number of terms """ # Ensure the degree parameters are sensible if max_degree < min_valuation: raise ValueError("`max_degree` must be greater than or equal to `min_valuation`") - # Handle univariate case - if self._n == 1: - # First sample a polynomial from the associated polynomial - # ring of `self` of degree `(max_degree - min_valuation)` - abs_deg = (max_degree - min_valuation) - f_rand = self._R.random_element(degree=abs_deg, **kwds) + # Sample a polynomial in the base ring of degree `max_degree - min_valuation` + abs_deg = (max_degree - min_valuation) + f_rand = self._R.random_element(degree=abs_deg, *args, **kwds) + + # Case this polynomial back the `self`` + f = self(f_rand) - # Case the polynomial base to self and scale to ensure - # that min_valuation is satisfied + # For the univariate case we simply shift by x**min_valuation + if self._n == 1: s = self.gen() ** min_valuation - return self(f_rand) * s - - # Ensure terms is set correctly - if terms < 0: - raise TypeError("cannot compute polynomial with a negative number of terms") - elif terms == 0: - return self.zero() - - # We now sample `terms`` terms with exponents picked randomly - # with degree at most `max_degree` and valuation greater or - # equal to min_valuation scaled by an element of the base ring - k = self.base_ring() - res = self.zero() - for _ in range(terms): - s = k.random_element(**kwds) - res += s * self._random_bounded_monomial(min_valuation, max_degree) - return res + return f * s + + # For the multivariate case, we sample a single monomial of degree + # exactly min_valuation and then use this to shift the polynomial + # into the correct bounds + s = self.monomial(*IntegerVectors(abs(min_valuation), self._n).random_element()) + s = self(s) + if min_valuation < 0: + s = ~s + return f * s def is_exact(self): """ From 7d915a3dbb1c71d3a8244e0d3b9575891f3431a5 Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Mon, 4 Mar 2024 10:40:20 +0000 Subject: [PATCH 142/518] linter --- src/sage/rings/polynomial/laurent_polynomial_ring_base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py index e7c1099a838..28484434166 100644 --- a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py +++ b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py @@ -504,7 +504,7 @@ def random_element(self, min_valuation=-2, max_degree=2, terms=5, *args, **kwds) - ``max_degree`` -- integer (default: ``2``); the maximal allowed degree of the polynomial - - ``*args, **kwds`` -- passed to the random element generator of the + - ``*args, **kwds`` -- passed to the random element generator of the base polynomial ring and base ring itself EXAMPLES:: @@ -610,7 +610,7 @@ def random_element(self, min_valuation=-2, max_degree=2, terms=5, *args, **kwds) # Sample a polynomial in the base ring of degree `max_degree - min_valuation` abs_deg = (max_degree - min_valuation) f_rand = self._R.random_element(degree=abs_deg, *args, **kwds) - + # Case this polynomial back the `self`` f = self(f_rand) From 5c19e925f35d256827609704829ff131ba06b397 Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Mon, 4 Mar 2024 14:01:33 +0000 Subject: [PATCH 143/518] reviewer suggestions --- .../polynomial/laurent_polynomial_mpair.pyx | 22 +++++++++---------- .../polynomial/laurent_polynomial_ring.py | 1 - .../laurent_polynomial_ring_base.py | 15 ++++++++----- 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/src/sage/rings/polynomial/laurent_polynomial_mpair.pyx b/src/sage/rings/polynomial/laurent_polynomial_mpair.pyx index fdd0671fd0d..ac9f0971052 100644 --- a/src/sage/rings/polynomial/laurent_polynomial_mpair.pyx +++ b/src/sage/rings/polynomial/laurent_polynomial_mpair.pyx @@ -1185,9 +1185,11 @@ cdef class LaurentPolynomial_mpair(LaurentPolynomial): def valuation(self, x=None): """ - Return the valuation of ``x`` in ``self``. + Return the valuation of ``self``. - If ``x`` is ``None``, return the minimal valuation of ``self``. + INPUT: + + - ``x`` -- (optional) a generator; if given, return the valuation with respect to this generator EXAMPLES:: @@ -1220,19 +1222,15 @@ cdef class LaurentPolynomial_mpair(LaurentPolynomial): # When x is None find the minimal valuation by finding the minimal # valuation of the sum of exponents if x is None: - val = min(sum(e) for e in self.exponents()) - return val + return ZZ(min(sum(e) for e in self.exponents())) - # Get the index of the gen + # Get the index of the generator or error cdef tuple g = self._parent.gens() cdef Py_ssize_t i - cdef bint no_generator_found = True - for i in range(len(g)): - if g[i] == x: - no_generator_found = False - break - if no_generator_found: - raise TypeError("x must be a generator of parent") + try: + i = g.index(x) + except ValueError: # not in the tuple + raise TypeError(f"{x} is not a generator of parent") # Find the minimal valuation of x by checking each term return min(e[i] for e in self.exponents()) diff --git a/src/sage/rings/polynomial/laurent_polynomial_ring.py b/src/sage/rings/polynomial/laurent_polynomial_ring.py index b103cc3dfce..812a4b3a351 100644 --- a/src/sage/rings/polynomial/laurent_polynomial_ring.py +++ b/src/sage/rings/polynomial/laurent_polynomial_ring.py @@ -47,7 +47,6 @@ from sage.rings.polynomial.laurent_polynomial_ring_base import LaurentPolynomialRing_generic from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.structure.element import parent -from sage.misc.prandom import randint, shuffle def is_LaurentPolynomialRing(R): diff --git a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py index 28484434166..3dd2fe02723 100644 --- a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py +++ b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py @@ -488,7 +488,7 @@ def krull_dimension(self): """ raise NotImplementedError - def random_element(self, min_valuation=-2, max_degree=2, terms=5, *args, **kwds): + def random_element(self, min_valuation=-2, max_degree=2, *args, **kwds): """ Return a random polynomial with degree at most ``max_degree`` and lowest valuation at least ``min_valuation``. @@ -611,19 +611,24 @@ def random_element(self, min_valuation=-2, max_degree=2, terms=5, *args, **kwds) abs_deg = (max_degree - min_valuation) f_rand = self._R.random_element(degree=abs_deg, *args, **kwds) - # Case this polynomial back the `self`` + # Cast this polynomial back the `self`` f = self(f_rand) # For the univariate case we simply shift by x**min_valuation if self._n == 1: - s = self.gen() ** min_valuation - return f * s + # When there is one generator we either have a univariate class + # and can shift by min_valuation + try: + return f.shift(min_valuation) + # Or we have a multivariate class with one variable, which does not + # have the shift method + except AttributeError: + return f * self.gen() ** min_valuation # For the multivariate case, we sample a single monomial of degree # exactly min_valuation and then use this to shift the polynomial # into the correct bounds s = self.monomial(*IntegerVectors(abs(min_valuation), self._n).random_element()) - s = self(s) if min_valuation < 0: s = ~s return f * s From d09ab587568d46e09d1ba1b189435403c498446f Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Mon, 4 Mar 2024 14:09:12 +0000 Subject: [PATCH 144/518] simplification of degree and further doctests --- .../polynomial/laurent_polynomial_mpair.pyx | 16 +++++++--------- .../polynomial/laurent_polynomial_ring_base.py | 10 +++++----- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/sage/rings/polynomial/laurent_polynomial_mpair.pyx b/src/sage/rings/polynomial/laurent_polynomial_mpair.pyx index ac9f0971052..b40b2f85ab7 100644 --- a/src/sage/rings/polynomial/laurent_polynomial_mpair.pyx +++ b/src/sage/rings/polynomial/laurent_polynomial_mpair.pyx @@ -1172,15 +1172,13 @@ cdef class LaurentPolynomial_mpair(LaurentPolynomial): if not x: return self._poly.total_degree() + sum(self._mon) + # Get the index of the generator or error cdef tuple g = self._parent.gens() cdef Py_ssize_t i - cdef bint no_generator_found = True - for i in range(len(g)): - if g[i] is x: - no_generator_found = False - break - if no_generator_found: - raise TypeError("x must be a generator of parent") + try: + i = g.index(x) + except ValueError: # not in the tuple + raise TypeError(f"{x} is not a generator of parent") return self._poly.degree(self._parent._R.gens()[i]) + self._mon[i] def valuation(self, x=None): @@ -1213,7 +1211,7 @@ cdef class LaurentPolynomial_mpair(LaurentPolynomial): sage: f.valuation(1) Traceback (most recent call last): ... - TypeError: x must be a generator of parent + TypeError: 1 is not a generator of parent """ # Valuation of zero polynomial is defined to be +Infinity if self.is_zero(): @@ -1222,7 +1220,7 @@ cdef class LaurentPolynomial_mpair(LaurentPolynomial): # When x is None find the minimal valuation by finding the minimal # valuation of the sum of exponents if x is None: - return ZZ(min(sum(e) for e in self.exponents())) + return Integer(min(sum(e) for e in self.exponents())) # Get the index of the generator or error cdef tuple g = self._parent.gens() diff --git a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py index 3dd2fe02723..b591a860f97 100644 --- a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py +++ b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py @@ -521,10 +521,10 @@ def random_element(self, min_valuation=-2, max_degree=2, *args, **kwds): :: sage: L = LaurentPolynomialRing(ZZ, 2, 'x') - sage: f = L.random_element(-10, 20) + sage: f = L.random_element(10, 20) sage: f.degree() <= 20 True - sage: f.valuation() >= -10 + sage: f.valuation() >= 10 True sage: f.parent() is L True @@ -532,10 +532,10 @@ def random_element(self, min_valuation=-2, max_degree=2, *args, **kwds): :: sage: L = LaurentPolynomialRing(GF(13), 3, 'x') - sage: f = L.random_element(-5, 10) - sage: f.degree() <= 10 + sage: f = L.random_element(-10, -1) + sage: f.degree() <= -1 True - sage: f.valuation() >= -5 + sage: f.valuation() >= -10 True sage: f.parent() is L True From ea3c6e9bbccfc48a66f4498b59a3fedea0912e91 Mon Sep 17 00:00:00 2001 From: Giacomo Pope <44242839+GiacomoPope@users.noreply.github.com> Date: Mon, 4 Mar 2024 21:31:13 +0000 Subject: [PATCH 145/518] Apply suggestions from code review Co-authored-by: Travis Scrimshaw --- src/sage/rings/polynomial/laurent_polynomial_mpair.pyx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sage/rings/polynomial/laurent_polynomial_mpair.pyx b/src/sage/rings/polynomial/laurent_polynomial_mpair.pyx index b40b2f85ab7..4413688d935 100644 --- a/src/sage/rings/polynomial/laurent_polynomial_mpair.pyx +++ b/src/sage/rings/polynomial/laurent_polynomial_mpair.pyx @@ -1187,7 +1187,8 @@ cdef class LaurentPolynomial_mpair(LaurentPolynomial): INPUT: - - ``x`` -- (optional) a generator; if given, return the valuation with respect to this generator + - ``x`` -- (optional) a generator; if given, return the valuation + with respect to this generator EXAMPLES:: @@ -1231,8 +1232,7 @@ cdef class LaurentPolynomial_mpair(LaurentPolynomial): raise TypeError(f"{x} is not a generator of parent") # Find the minimal valuation of x by checking each term - return min(e[i] for e in self.exponents()) - + return Integer(min(e[i] for e in self.exponents())) def has_inverse_of(self, i): """ From 11b2a157e84c16e08e75f40c566f86dcede9d97d Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Mon, 4 Mar 2024 21:34:03 +0000 Subject: [PATCH 146/518] Add another test for the multivariate case with one generator --- .../polynomial/laurent_polynomial_ring_base.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py index b591a860f97..b99eb182bbc 100644 --- a/src/sage/rings/polynomial/laurent_polynomial_ring_base.py +++ b/src/sage/rings/polynomial/laurent_polynomial_ring_base.py @@ -563,7 +563,21 @@ def random_element(self, min_valuation=-2, max_degree=2, *args, **kwds): sage: f.parent() is L True - TESTS:: + TESTS: + + Ensure everything works for the multivariate case with only + one generator:: + + sage: L = LaurentPolynomialRing(ZZ, 1, 'x') + sage: f = L.random_element(10, 20) + sage: f.degree() <= 20 + True + sage: f.valuation() >= 10 + True + sage: f.parent() is L + True + + Test for constructions which use multivariate polynomial rings:: sage: rings = [RR, QQ, ZZ, GF(13), GF(7^3)] sage: for ring in rings: From 38c69a79594239f3a6c91cb9e17c56bfe5554d02 Mon Sep 17 00:00:00 2001 From: Kwankyu Lee Date: Wed, 28 Feb 2024 11:25:40 +0900 Subject: [PATCH 147/518] Many fixes related with genus --- .../function_field/function_field_polymod.py | 24 ++++++- .../polynomial/multi_polynomial_ideal.py | 41 +++++++---- src/sage/schemes/curves/affine_curve.py | 15 ++-- src/sage/schemes/curves/constructor.py | 70 +++++++++++++------ src/sage/schemes/curves/curve.py | 26 +++---- src/sage/schemes/curves/projective_curve.py | 51 +++++++++----- 6 files changed, 157 insertions(+), 70 deletions(-) diff --git a/src/sage/rings/function_field/function_field_polymod.py b/src/sage/rings/function_field/function_field_polymod.py index 44c1390bc86..d90890cec0c 100644 --- a/src/sage/rings/function_field/function_field_polymod.py +++ b/src/sage/rings/function_field/function_field_polymod.py @@ -34,12 +34,16 @@ from sage.rings.integer import Integer from sage.categories.homset import Hom from sage.categories.function_fields import FunctionFields +from sage.categories.number_fields import NumberFields from .element import FunctionFieldElement from .element_polymod import FunctionFieldElement_polymod from .function_field import FunctionField from .function_field_rational import RationalFunctionField +_FunctionFields = FunctionFields() +_NumberFields = NumberFields() + class FunctionField_polymod(FunctionField): """ @@ -160,7 +164,7 @@ def __init__(self, polynomial, names, category=None): self._polynomial = polynomial FunctionField.__init__(self, base_field, names=names, - category=FunctionFields().or_subcategory(category)) + category=_FunctionFields.or_subcategory(category)) from .place_polymod import FunctionFieldPlace_polymod self._place_class = FunctionFieldPlace_polymod @@ -1842,11 +1846,27 @@ def genus(self): sage: L.genus() 6 + sage: # needs sage.rings.number_field + sage: R. = QQ[] + sage: N. = NumberField(T^2 + 1) + sage: K. = FunctionField(N); K + Rational function field in x over Number Field in a with defining polynomial T^2 + 1 + sage: K.genus() + 0 + sage: S. = PolynomialRing(K) + sage: L. = K.extension(t^2 - x^3 + x) + sage: L.genus() + 1 + The genus is computed by the Hurwitz genus formula. """ k, _ = self.exact_constant_field() + if k in _NumberFields: + k_degree = k.relative_degree() + else: + k_degree = k.degree() different_degree = self.different().degree() # must be even - return Integer(different_degree // 2 - self.degree() / k.degree()) + 1 + return Integer(different_degree // 2 - self.degree() / k_degree) + 1 def residue_field(self, place, name=None): """ diff --git a/src/sage/rings/polynomial/multi_polynomial_ideal.py b/src/sage/rings/polynomial/multi_polynomial_ideal.py index 2b828fba51b..f4507b813db 100644 --- a/src/sage/rings/polynomial/multi_polynomial_ideal.py +++ b/src/sage/rings/polynomial/multi_polynomial_ideal.py @@ -1569,13 +1569,20 @@ def _groebner_basis_singular_raw(self, algorithm="groebner", singular=singular_d @handle_AA_and_QQbar def genus(self): r""" - Return the genus of the projective curve defined by this ideal, - which must be 1 dimensional. + Return the geometric genus of the projective curve defined by this + ideal. + + OUTPUT: + + If the ideal is homogeneous and defines a curve in a projective space, + then the genus of the curve is returned. If the ideal is not + homogeneous and defines a curve in an affine space, the genus of the + projective closure of the curve is returned. EXAMPLES: - Consider the hyperelliptic curve `y^2 = 4x^5 - 30x^3 + 45x - - 22` over `\QQ`, it has genus 2:: + Consider the hyperelliptic curve `y^2 = 4x^5 - 30x^3 + 45x - 22` over + `\QQ`, it has genus 2:: sage: P. = QQ[] sage: f = 4*x^5 - 30*x^3 + 45*x - 22 @@ -1592,18 +1599,25 @@ def genus(self): sage: I.genus() 2 - TESTS: - - Check that the answer is correct for reducible curves:: + Geometric genus is only defined for geometrically irreducible curves. + You may get a nonsensical answer if the condition is not met. A curve + reducible over a quadratic extension of `\QQ`:: sage: R. = QQ[] sage: C = Curve(x^2 - 2*y^2) - sage: C.is_singular() - True sage: C.genus() -1 - sage: Ideal(x^4+y^2*x+x).genus() + + TESTS: + + An ideal that does not define a curve but we get a result! :: + + sage: R. = QQ[] + sage: Ideal(x^4 + y^2*x + x).genus() 0 + + An ideal that defines a geometrically reducible affine curve:: + sage: T. = QQ[] sage: TJ = Ideal([t1^2 + u1^2 - 1,t2^2 + u2^2 - 1, (t1-t2)^2 + (u1-u2)^2 -1]) sage: TJ.genus() @@ -1611,9 +1625,10 @@ def genus(self): Check that this method works over QQbar (:trac:`25351`):: - sage: P. = QQbar[] # needs sage.rings.number_field - sage: I = ideal(y^3*z + x^3*y + x*z^3) # needs sage.rings.number_field - sage: I.genus() # needs sage.rings.number_field + sage: # needs sage.rings.number_field + sage: P. = QQbar[] + sage: I = ideal(y^3*z + x^3*y + x*z^3) + sage: I.genus() 3 """ from sage.libs.singular.function_factory import ff diff --git a/src/sage/schemes/curves/affine_curve.py b/src/sage/schemes/curves/affine_curve.py index d5ea150f98d..b8e62b61971 100644 --- a/src/sage/schemes/curves/affine_curve.py +++ b/src/sage/schemes/curves/affine_curve.py @@ -2065,13 +2065,20 @@ def _genus(self): sage: C = Curve(x^5 + y^5 + x*y + 1) sage: C.genus() # indirect doctest 1 - """ - k = self.base_ring() + TESTS:: + + sage: R. = QQ[] + sage: N. = NumberField(T^2 + 1) + sage: A2. = AffineSpace(N, 2) + sage: C = Curve(y^2 - x^3 + x, A2) + sage: C.genus() + 1 + """ # Singular's genus command is usually much faster than the genus method # of function fields in Sage. But unfortunately Singular's genus - # command does not yet work over non-prime finite fields. - if k.is_finite() and k.degree() > 1: + # command does not work over extension fields. + if self.base_ring().degree() > 1: return self._function_field.genus() # call Singular's genus command diff --git a/src/sage/schemes/curves/constructor.py b/src/sage/schemes/curves/constructor.py index 6fe15a2efc2..3b85fd96241 100644 --- a/src/sage/schemes/curves/constructor.py +++ b/src/sage/schemes/curves/constructor.py @@ -37,24 +37,20 @@ # ******************************************************************** from sage.categories.fields import Fields +from sage.categories.number_fields import NumberFields from sage.rings.polynomial.multi_polynomial import MPolynomial from sage.rings.polynomial.multi_polynomial_ring import is_MPolynomialRing from sage.rings.finite_rings.finite_field_base import FiniteField - from sage.rings.rational_field import QQ from sage.structure.all import Sequence -from sage.schemes.affine.affine_space import is_AffineSpace from sage.schemes.generic.ambient_space import is_AmbientSpace from sage.schemes.generic.algebraic_scheme import is_AlgebraicScheme -from sage.schemes.projective.projective_space import is_ProjectiveSpace - -from sage.schemes.affine.affine_space import AffineSpace - -from sage.schemes.projective.projective_space import ProjectiveSpace - +from sage.schemes.affine.affine_space import AffineSpace, is_AffineSpace +from sage.schemes.projective.projective_space import ProjectiveSpace, is_ProjectiveSpace +from sage.schemes.plane_conics.constructor import Conic from .projective_curve import (ProjectiveCurve, ProjectivePlaneCurve, @@ -76,8 +72,8 @@ IntegralAffinePlaneCurve, IntegralAffinePlaneCurve_finite_field) - -from sage.schemes.plane_conics.constructor import Conic +_Fields = Fields() +_NumberFields = NumberFields() def _is_irreducible_and_reduced(F) -> bool: @@ -113,11 +109,13 @@ def Curve(F, A=None): INPUT: - - ``F`` -- a multivariate polynomial, or a list or tuple of polynomials, or an algebraic scheme. + - ``F`` -- a multivariate polynomial, or a list or tuple of polynomials, or an algebraic scheme + + - ``A`` -- (default: None) an ambient space in which to create the curve - - ``A`` -- (default: None) an ambient space in which to create the curve. + EXAMPLES: - EXAMPLES: A projective plane curve. :: + A projective plane curve:: sage: x,y,z = QQ['x,y,z'].gens() sage: C = Curve(x^3 + y^3 + z^3); C @@ -215,7 +213,7 @@ def Curve(F, A=None): sage: Curve(P1) Projective Line over Finite Field of size 5 - :: + An affine line:: sage: A1. = AffineSpace(1, QQ) sage: R = A1.coordinate_ring() @@ -224,6 +222,18 @@ def Curve(F, A=None): sage: Curve(A1) Affine Line over Rational Field + A projective line:: + + sage: R. = QQ[] + sage: N. = NumberField(x^2 + 1) + sage: P1. = ProjectiveSpace(N, 1) + sage: C = Curve(P1) + sage: C + Projective Line over Number Field in a with defining polynomial x^2 + 1 + sage: C.geometric_genus() + 0 + sage: C.arithmetic_genus() + 0 """ if A is None: if is_AmbientSpace(F) and F.dimension() == 1: @@ -298,12 +308,20 @@ def Curve(F, A=None): k = A.base_ring() if is_AffineSpace(A): + if n == 1: + if A.coordinate_ring().ideal(F).is_zero(): + if isinstance(k, FiniteField): + return IntegralAffineCurve_finite_field(A, F) + if k in _Fields: + return IntegralAffineCurve(A, F) + return AffineCurve(A, F) + raise TypeError(f"{F} does not define a curve in one-dimensional affine space") if n != 2: if isinstance(k, FiniteField): if A.coordinate_ring().ideal(F).is_prime(): return IntegralAffineCurve_finite_field(A, F) - if k in Fields(): - if k == QQ and A.coordinate_ring().ideal(F).is_prime(): + if k in _Fields: + if (k == QQ or k in _NumberFields) and A.coordinate_ring().ideal(F).is_prime(): return IntegralAffineCurve(A, F) return AffineCurve_field(A, F) return AffineCurve(A, F) @@ -316,21 +334,29 @@ def Curve(F, A=None): if _is_irreducible_and_reduced(F): return IntegralAffinePlaneCurve_finite_field(A, F) return AffinePlaneCurve_finite_field(A, F) - if k in Fields(): - if k == QQ and _is_irreducible_and_reduced(F): + if k in _Fields: + if (k == QQ or k in _NumberFields) and _is_irreducible_and_reduced(F): return IntegralAffinePlaneCurve(A, F) return AffinePlaneCurve_field(A, F) return AffinePlaneCurve(A, F) elif is_ProjectiveSpace(A): + if n == 1: + if A.coordinate_ring().ideal(F).is_zero(): + if isinstance(k, FiniteField): + return IntegralProjectiveCurve_finite_field(A, F) + if k in _Fields: + return IntegralProjectiveCurve(A, F) + return ProjectiveCurve(A, F) + raise TypeError(f"{F} does not define a curve in one-dimensional projective space") if n != 2: if not all(f.is_homogeneous() for f in F): raise TypeError("polynomials defining a curve in a projective space must be homogeneous") if isinstance(k, FiniteField): if A.coordinate_ring().ideal(F).is_prime(): return IntegralProjectiveCurve_finite_field(A, F) - if k in Fields(): - if k == QQ and A.coordinate_ring().ideal(F).is_prime(): + if k in _Fields: + if (k == QQ or k in _NumberFields) and A.coordinate_ring().ideal(F).is_prime(): return IntegralProjectiveCurve(A, F) return ProjectiveCurve_field(A, F) return ProjectiveCurve(A, F) @@ -348,8 +374,8 @@ def Curve(F, A=None): if _is_irreducible_and_reduced(F): return IntegralProjectivePlaneCurve_finite_field(A, F) return ProjectivePlaneCurve_finite_field(A, F) - if k in Fields(): - if k == QQ and _is_irreducible_and_reduced(F): + if k in _Fields: + if (k == QQ or k in _NumberFields) and _is_irreducible_and_reduced(F): return IntegralProjectivePlaneCurve(A, F) return ProjectivePlaneCurve_field(A, F) return ProjectivePlaneCurve(A, F) diff --git a/src/sage/schemes/curves/curve.py b/src/sage/schemes/curves/curve.py index 0538db66fee..1d2d2221330 100644 --- a/src/sage/schemes/curves/curve.py +++ b/src/sage/schemes/curves/curve.py @@ -209,17 +209,9 @@ def geometric_genus(self): r""" Return the geometric genus of the curve. - This is by definition the genus of the normalization of the projective - closure of the curve over the algebraic closure of the base field; the - base field must be a prime field. - - .. NOTE:: - - This calls Singular's genus command. - EXAMPLES: - Examples of projective curves. :: + Examples of projective curves:: sage: P2 = ProjectiveSpace(2, GF(5), names=['x','y','z']) sage: x, y, z = P2.coordinate_ring().gens() @@ -233,7 +225,7 @@ def geometric_genus(self): sage: C.geometric_genus() 3 - Examples of affine curves. :: + Examples of affine curves:: sage: x, y = PolynomialRing(GF(5), 2, 'xy').gens() sage: C = Curve(y^2 - x^3 - 17*x + y) @@ -246,12 +238,22 @@ def geometric_genus(self): sage: C.geometric_genus() 3 + Note that the geometric genus is only defined for `geometrically + irreducible curve `_. This + method does not check the condition. Be warned that you may get a + nonsensical result if the curve is not geometrically irreducible. + + A curve that is not geometrically irreducible:: + + sage: P2. = ProjectiveSpace(QQ, 2) + sage: C = Curve(x^2 + y^2, P2) + sage: C.geometric_genus() # nonsense! + -1 """ try: return self._genus except AttributeError: - self._genus = self.defining_ideal().genus() - return self._genus + raise NotImplementedError def union(self, other): """ diff --git a/src/sage/schemes/curves/projective_curve.py b/src/sage/schemes/curves/projective_curve.py index 0b3f311eb62..399b1095ea5 100644 --- a/src/sage/schemes/curves/projective_curve.py +++ b/src/sage/schemes/curves/projective_curve.py @@ -1595,13 +1595,30 @@ def __init__(self, A, X, category=None): if not A.base_ring() in Fields(): raise TypeError("curve not defined over a field") + @lazy_attribute + def _genus(self): + """ + The geometric genus of this projective curve. + + TESTS: + + Geometric genus is not defined for geometrically reducible curves. You + may get a nonsensical answer if the condition is not met:: + + sage: P2. = ProjectiveSpace(QQ, 2) + sage: C = Curve(x^2 + y^2) + sage: C.genus() # indirect test + -1 + """ + return self.defining_ideal().genus() + def arithmetic_genus(self): r""" Return the arithmetic genus of this projective curve. - This is the arithmetic genus `g_a(C)` as defined in [Har1977]_. If `P` is the - Hilbert polynomial of the defining ideal of this curve, then the arithmetic genus - of this curve is `1 - P(0)`. This curve must be irreducible. + This is the arithmetic genus `p_a(C)` as defined in [Har1977]_. If `P` + is the Hilbert polynomial of the defining ideal of this curve, then the + arithmetic genus of this curve is `1 - P(0)`. EXAMPLES:: @@ -1617,8 +1634,6 @@ def arithmetic_genus(self): sage: C.arithmetic_genus() 10 """ - if not self.is_irreducible(): - raise TypeError("this curve must be irreducible") return 1 - self.defining_ideal().hilbert_polynomial()(0) def is_complete_intersection(self): @@ -1687,10 +1702,11 @@ def arithmetic_genus(self): r""" Return the arithmetic genus of this projective curve. - This is the arithmetic genus `g_a(C)` as defined in [Har1977]_. For a - projective plane curve of degree `d`, this is simply `(d-1)(d-2)/2`. It - need *not* equal the geometric genus (the genus of the normalization of - the curve). This curve must be irreducible. + This is the arithmetic genus `p_a(C)` as defined in [Har1977]_. + + For an irreducible projective plane curve of degree `d`, this is simply + `(d - 1)(d - 2)/2`. It need *not* equal the geometric genus (the genus + of the normalization of the curve). EXAMPLES:: @@ -1700,7 +1716,7 @@ def arithmetic_genus(self): defined by -x^9 + y^2*z^7 - x*z^8 sage: C.arithmetic_genus() 28 - sage: C.genus() + sage: C.genus() # geometric 4 :: @@ -1710,10 +1726,11 @@ def arithmetic_genus(self): sage: C.arithmetic_genus() 3 """ - if not self.is_irreducible(): - raise TypeError("this curve must be irreducible") - d = self.defining_polynomial().total_degree() - return Integer(d - 1).binomial(2) + if self.is_irreducible(): + # use genus-degree formula + d = self.defining_polynomial().total_degree() + return Integer(d - 1).binomial(2) + return super().arithmetic_genus() def fundamental_group(self): r""" @@ -2290,9 +2307,9 @@ def _genus(self): EXAMPLES:: - sage: P. = ProjectiveSpace(GF(4), 2) # needs sage.rings.finite_rings - sage: C = Curve(x^5 + y^5 + x*y*z^3 + z^5) # needs sage.rings.finite_rings - sage: C.genus() # indirect doctest # needs sage.rings.finite_rings + sage: P. = ProjectiveSpace(GF(4), 2) + sage: C = Curve(x^5 + y^5 + x*y*z^3 + z^5) + sage: C.genus() # indirect doctest 1 """ return self._open_affine.genus() From 89952f52dc9fb7dd46c4d692d41a2b67727c995e Mon Sep 17 00:00:00 2001 From: Kwankyu Lee Date: Tue, 5 Mar 2024 16:09:39 +0900 Subject: [PATCH 148/518] Update the year in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8add84575d8..f4e439652ac 100644 --- a/README.md +++ b/README.md @@ -668,7 +668,7 @@ information, patches, and build scripts are in the accompanying part of the Sage git repository.

- Copyright (C) 2005-2023 The Sage Development Team + Copyright (C) 2005-2024 The Sage Development Team

https://www.sagemath.org From 35f3c606874a66666a30b09af4f222ea40559dfa Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Tue, 5 Mar 2024 09:35:07 +0000 Subject: [PATCH 149/518] Add justification of valuation to docstring --- .../rings/polynomial/laurent_polynomial_mpair.pyx | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/sage/rings/polynomial/laurent_polynomial_mpair.pyx b/src/sage/rings/polynomial/laurent_polynomial_mpair.pyx index 4413688d935..0555346cb1d 100644 --- a/src/sage/rings/polynomial/laurent_polynomial_mpair.pyx +++ b/src/sage/rings/polynomial/laurent_polynomial_mpair.pyx @@ -1182,9 +1182,19 @@ cdef class LaurentPolynomial_mpair(LaurentPolynomial): return self._poly.degree(self._parent._R.gens()[i]) + self._mon[i] def valuation(self, x=None): - """ + r""" Return the valuation of ``self``. + If ``x`` is ``None``, the returned valuation is the minimal total degree + of the monomials occurring in ``self``. Geometrically, this is the order + of vanishing of ``self`` at the generic point of the blow-up of the + point `(0,0,\ldots,0)`. + + If ``x`` is not ``None``, then it must be a generator. In that case, the + minimum degree of that generator occurring in ``self`` is returned. + Geometrically, this is the order of vanishing of ``self`` at the generic + point of the curve `x = 0`. + INPUT: - ``x`` -- (optional) a generator; if given, return the valuation From 21ac678c2f94f9516ec489a129d911ccacbbfee0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Tue, 5 Mar 2024 21:49:34 +0100 Subject: [PATCH 150/518] various list-comprehension in combinat (ruff PERF) --- src/sage/combinat/chas/fsym.py | 7 +++---- .../cluster_algebra_quiver/mutation_type.py | 10 +++++----- src/sage/combinat/composition.py | 5 +---- src/sage/combinat/designs/covering_design.py | 17 +++++------------ src/sage/combinat/designs/database.py | 10 +++++----- src/sage/combinat/diagram_algebras.py | 6 +++--- src/sage/combinat/plane_partition.py | 10 ++++------ .../rigged_configuration_element.py | 9 +++------ src/sage/combinat/set_partition.py | 3 +-- src/sage/combinat/sf/new_kschur.py | 6 ++---- src/sage/combinat/sf/ns_macdonald.py | 17 +++++++---------- src/sage/combinat/tableau.py | 10 +++------- src/sage/combinat/words/morphism.py | 6 +++--- 13 files changed, 45 insertions(+), 71 deletions(-) diff --git a/src/sage/combinat/chas/fsym.py b/src/sage/combinat/chas/fsym.py index 7f63a24c798..10210028932 100644 --- a/src/sage/combinat/chas/fsym.py +++ b/src/sage/combinat/chas/fsym.py @@ -675,10 +675,9 @@ def product_on_basis(self, t1, t2): """ n = t1.size() m = n + t2.size() - tableaux = [] - for t in StandardTableaux(m): - if t.restrict(n) == t1 and standardize(t.anti_restrict(n).rectify()) == t2: - tableaux.append(t) + tableaux = [t for t in StandardTableaux(m) + if t.restrict(n) == t1 + and standardize(t.anti_restrict(n).rectify()) == t2] return self.sum_of_monomials(tableaux) @cached_method diff --git a/src/sage/combinat/cluster_algebra_quiver/mutation_type.py b/src/sage/combinat/cluster_algebra_quiver/mutation_type.py index e49bc3d25e1..5578e751b5d 100644 --- a/src/sage/combinat/cluster_algebra_quiver/mutation_type.py +++ b/src/sage/combinat/cluster_algebra_quiver/mutation_type.py @@ -1023,17 +1023,17 @@ def _connected_mutation_type_AAtildeD(dg, ret_conn_vert=False): multiple_trian_edges = list(set(multiple_trian_edges)) # test that there at most three edges appearing in exactly two oriented triangles - count = len( multiple_trian_edges ) + count = len(multiple_trian_edges) if count >= 4: return _false_return(321) # if two edges appearing in exactly two oriented triangles, test that the two edges together # determine a unique triangle elif count > 1: - test_triangles = [] - for edge in multiple_trian_edges: - test_triangles.append([ tuple(trian) for trian in oriented_trians if edge in trian ]) + test_triangles = [[tuple(trian) for trian in oriented_trians + if edge in trian] + for edge in multiple_trian_edges] unique_triangle = set(test_triangles[0]).intersection( *test_triangles[1:] ) - if len( unique_triangle ) != 1: + if len(unique_triangle) != 1: return _false_return(19) else: # if a long_cycle had previously been found, this unique oriented triangle is a second long_cycle, a contradiction. diff --git a/src/sage/combinat/composition.py b/src/sage/combinat/composition.py index 8d01e9ac081..86e9d8ec1ec 100644 --- a/src/sage/combinat/composition.py +++ b/src/sage/combinat/composition.py @@ -1415,10 +1415,7 @@ def specht_module(self, base_ring=None): from sage.rings.rational_field import QQ base_ring = QQ R = SymmetricGroupAlgebra(base_ring, sum(self)) - cells = [] - for i, row in enumerate(self): - for j in range(row): - cells.append((i, j)) + cells = [(i, j) for i, row in enumerate(self) for j in range(row)] return SpechtModule(R, cells) def specht_module_dimension(self, base_ring=None): diff --git a/src/sage/combinat/designs/covering_design.py b/src/sage/combinat/designs/covering_design.py index fd7172ac876..a855dc601dc 100644 --- a/src/sage/combinat/designs/covering_design.py +++ b/src/sage/combinat/designs/covering_design.py @@ -144,21 +144,14 @@ def trivial_covering_design(v, k, t): """ if t == 0: # single block [0, ..., k-1] - blk = [] - for i in range(k): - blk.append(i) + blk = list(range(k)) return CoveringDesign(v, k, t, 1, range(v), [blk], 1, "Trivial") if t == 1: # blocks [0, ..., k-1], [k, ..., 2k-1], ... size = Rational((v, k)).ceil() - blocks = [] - for i in range(size - 1): - blk = [] - for j in range(i * k, (i + 1) * k): - blk.append(j) - blocks.append(blk) - blk = [] # last block: if k does not divide v, wrap around - for j in range((size - 1) * k, v): - blk.append(j) + blocks = [list(range(i * k, (i + 1) * k)) + for i in range(size - 1)] + # last block: if k does not divide v, wrap around + blk = list(range((size - 1) * k, v)) for j in range(k - len(blk)): blk.append(j) blk.sort() diff --git a/src/sage/combinat/designs/database.py b/src/sage/combinat/designs/database.py index f3e1ef73350..e4d80442f3c 100644 --- a/src/sage/combinat/designs/database.py +++ b/src/sage/combinat/designs/database.py @@ -3663,18 +3663,18 @@ def DM_51_6_1(): [ 34, 32, 36, 26, 20] ] - Mb = [[0,0,0,0,0]] + Mb = [[0, 0, 0, 0, 0]] for R in zip(*M): for i in range(5): - for RR in [list(R), [-x for x in R]]: - Mb.append(RR) - R = cyclic_shift(R,1) + Mb.extend([list(R), [-x for x in R]]) + R = cyclic_shift(R, 1) for R in Mb: R.append(0) - return G,Mb + return G, Mb + def DM_52_6_1(): r""" diff --git a/src/sage/combinat/diagram_algebras.py b/src/sage/combinat/diagram_algebras.py index 15837e34128..9416bcaa387 100644 --- a/src/sage/combinat/diagram_algebras.py +++ b/src/sage/combinat/diagram_algebras.py @@ -4419,7 +4419,7 @@ def key_func(P): else: from sage.typeset.ascii_art import AsciiArt d = [".", ".", "`", "`", "-", "|"] - #db = [".", ".", "`", "`", "=", "|"] + # db = [".", ".", "`", "`", "=", "|"] blob = '0' ret = [" o" * n] char_art = AsciiArt @@ -4489,12 +4489,12 @@ def sgn(x): if x < 0: return -1 return 0 + l1 = [] # list of blocks l2 = [] # list of nodes for i in list(diagram): l1.append(list(i)) - for j in list(i): - l2.append(j) + l2.extend(list(i)) output = "\\begin{tikzpicture}[scale = 0.5,thick, baseline={(0,-1ex/2)}] \n\\tikzstyle{vertex} = [shape = circle, minimum size = 7pt, inner sep = 1pt] \n" #setup beginning of picture for i in l2: #add nodes output = output + "\\node[vertex] (G-{}) at ({}, {}) [shape = circle, draw{}] {{}}; \n".format(i, (abs(i)-1)*1.5, sgn(i), filled_str) diff --git a/src/sage/combinat/plane_partition.py b/src/sage/combinat/plane_partition.py index b4f897b64f3..bc523121623 100644 --- a/src/sage/combinat/plane_partition.py +++ b/src/sage/combinat/plane_partition.py @@ -324,12 +324,10 @@ def cells(self) -> list[list[int]]: sage: PP.cells() [[0, 0, 0], [0, 0, 1], [0, 0, 2], [0, 1, 0], [1, 0, 0], [1, 0, 1]] """ - L = [] - for r in range(len(self)): - for c in range(len(self[r])): - for h in range(self[r][c]): - L.append([r, c, h]) - return L + return [[r, c, h] + for r in range(len(self)) + for c in range(len(self[r])) + for h in range(self[r][c])] def number_of_boxes(self) -> Integer: r""" diff --git a/src/sage/combinat/rigged_configurations/rigged_configuration_element.py b/src/sage/combinat/rigged_configurations/rigged_configuration_element.py index 088dd64f20f..12950c78742 100644 --- a/src/sage/combinat/rigged_configurations/rigged_configuration_element.py +++ b/src/sage/combinat/rigged_configurations/rigged_configuration_element.py @@ -199,9 +199,7 @@ def __init__(self, parent, rigged_partitions=[], **options): if len(data) == 0: # Create a size n array of empty rigged tableau since no tableau # were given - nu = [] - for i in range(n): - nu.append(RiggedPartition()) + nu = [RiggedPartition() for _ in range(n)] else: if len(data) != n: # otherwise n should be equal to the number of tableaux raise ValueError("incorrect number of partitions") @@ -1308,9 +1306,8 @@ def __init__(self, parent, rigged_partitions=[], **options): shape_data = data[0] rigging_data = data[1] vac_data = data[2] - nu = [] - for i in range(n): - nu.append(RiggedPartition(shape_data[i], rigging_data[i], vac_data[i])) + nu = [RiggedPartition(a, b, c) + for a, b, c in zip(shape_data, rigging_data, vac_data)] # Special display case if parent.cartan_type().type() == 'B': nu[-1] = RiggedPartitionTypeB(nu[-1]) diff --git a/src/sage/combinat/set_partition.py b/src/sage/combinat/set_partition.py index c08e1c7aef3..ed227aa57df 100644 --- a/src/sage/combinat/set_partition.py +++ b/src/sage/combinat/set_partition.py @@ -1873,8 +1873,7 @@ def arcs(self): arcs = [] for p in self: p = sorted(p) - for i in range(len(p) - 1): - arcs.append((p[i], p[i + 1])) + arcs.extend((p[i], p[i + 1]) for i in range(len(p) - 1)) return arcs def plot(self, angle=None, color='black', base_set_dict=None): diff --git a/src/sage/combinat/sf/new_kschur.py b/src/sage/combinat/sf/new_kschur.py index e4c2abcc75a..7eca926406f 100644 --- a/src/sage/combinat/sf/new_kschur.py +++ b/src/sage/combinat/sf/new_kschur.py @@ -1595,10 +1595,8 @@ def _DualGrothMatrix(self, m): for i in range(m + 1): for x in Partitions(m - i, max_part=self.k): f = mon(G(x, m)) - vec = [] - for j in range(m + 1): - for y in Partitions(m - j, max_part=self.k): - vec.append(f.coefficient(y)) + vec = [f.coefficient(y) for j in range(m + 1) + for y in Partitions(m - j, max_part=self.k)] new_mat.append(vec) from sage.matrix.constructor import Matrix return Matrix(new_mat) diff --git a/src/sage/combinat/sf/ns_macdonald.py b/src/sage/combinat/sf/ns_macdonald.py index a2340ab5f68..582e2f99568 100644 --- a/src/sage/combinat/sf/ns_macdonald.py +++ b/src/sage/combinat/sf/ns_macdonald.py @@ -159,7 +159,7 @@ def flip(self): def boxes_same_and_lower_right(self, ii, jj): """ - Return a list of the boxes of ``self`` that are in row ``jj`` + Return an iterator of the boxes of ``self`` that are in row ``jj`` but not identical with ``(ii, jj)``, or lie in the row ``jj - 1`` (the row directly below ``jj``; this might be the basement) and strictly to the right of ``(ii, jj)``. @@ -168,26 +168,23 @@ def boxes_same_and_lower_right(self, ii, jj): sage: a = AugmentedLatticeDiagramFilling([[1,6],[2],[3,4,2],[],[],[5,5]]) sage: a = a.shape() - sage: a.boxes_same_and_lower_right(1,1) + sage: list(a.boxes_same_and_lower_right(1,1)) [(2, 1), (3, 1), (6, 1), (2, 0), (3, 0), (4, 0), (5, 0), (6, 0)] - sage: a.boxes_same_and_lower_right(1,2) + sage: list(a.boxes_same_and_lower_right(1,2)) [(3, 2), (6, 2), (2, 1), (3, 1), (6, 1)] - sage: a.boxes_same_and_lower_right(3,3) + sage: list(a.boxes_same_and_lower_right(3,3)) [(6, 2)] - sage: a.boxes_same_and_lower_right(2,3) + sage: list(a.boxes_same_and_lower_right(2,3)) [(3, 3), (3, 2), (6, 2)] """ - res = [] # Add all of the boxes in the same row for i in range(1, len(self) + 1): if self[i] >= jj and i != ii: - res.append((i, jj)) + yield (i, jj) for i in range(ii + 1, len(self) + 1): if self[i] >= jj - 1: - res.append((i, jj - 1)) - - return res + yield (i, jj - 1) class AugmentedLatticeDiagramFilling(CombinatorialObject): diff --git a/src/sage/combinat/tableau.py b/src/sage/combinat/tableau.py index c4009853da6..7613ef9a671 100644 --- a/src/sage/combinat/tableau.py +++ b/src/sage/combinat/tableau.py @@ -2613,16 +2613,12 @@ def slide_multiply(self, other): sage: t.slide_multiply(t2) [[1, 1, 2, 2, 3], [2, 2, 3, 5], [3, 4, 5], [4, 6, 6], [5]] """ - st = [] if len(self) == 0: return other - else: - l = len(self[0]) - for row in other: - st.append((None,)*l + row) - for row in self: - st.append(row) + l = len(self[0]) + st = [(None,) * l + row for row in other] + st.extend(row for row in self) from sage.combinat.skew_tableau import SkewTableau return SkewTableau(st).rectify() diff --git a/src/sage/combinat/words/morphism.py b/src/sage/combinat/words/morphism.py index abb99d9f685..4e842d19a0b 100644 --- a/src/sage/combinat/words/morphism.py +++ b/src/sage/combinat/words/morphism.py @@ -463,7 +463,7 @@ def _build_dict(self, s): def _build_codomain(self, data): r""" - Return a Words domain containing all the letter in the keys of + Return a Words domain containing all the letters in the values of data (which must be a dictionary). TESTS: @@ -489,10 +489,10 @@ def _build_codomain(self, data): Finite words over {0, 1, 2} """ codom_alphabet = set() - for key, val in data.items(): + for val in data.values(): try: it = iter(val) - except Exception: + except TypeError: it = [val] codom_alphabet.update(it) try: From cf6f44be85cac982d054ce1e13c490053da11958 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 25 Feb 2024 09:31:55 -0800 Subject: [PATCH 151/518] build/bin/sage-spkg-info: Do not handle SPKG.txt, only SPKG.rst --- build/bin/sage-spkg-info | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/build/bin/sage-spkg-info b/build/bin/sage-spkg-info index 533fa714dac..5f6456aa846 100755 --- a/build/bin/sage-spkg-info +++ b/build/bin/sage-spkg-info @@ -31,14 +31,12 @@ if ! props=$(sage-package properties --format=shell $PKG_BASE 2> /dev/null); the fi eval "$props" eval PKG_SCRIPTS=\$path_$PKG_BASE -for ext in rst txt; do - SPKG_FILE="$PKG_SCRIPTS/SPKG.$ext" - if [ -f "$SPKG_FILE" ]; then - # for sphinx 4.4 we need to replace all direct links by some "extlink" (issue 33272) - sed -e "1,3s/^ *Sage: Open Source Mathematics Software:/$PKG_BASE:/" -e "s|https://github.com/sagemath/sage/issues/\([0-9]*\)|:issue:\`\1\`|g" -e "s|https://arxiv.org/abs/cs/\([0-9]*\)|:arxiv:\`cs/\1\`|g" "$SPKG_FILE" - break - fi -done +SPKG_FILE="$PKG_SCRIPTS/SPKG.rst" +if [ -f "$SPKG_FILE" ]; then + # for sphinx 4.4 we need to replace all direct links by some "extlink" (issue 33272) + sed -e "1,3s/^ *Sage: Open Source Mathematics Software:/$PKG_BASE:/" -e "s|https://github.com/sagemath/sage/issues/\([0-9]*\)|:issue:\`\1\`|g" -e "s|https://arxiv.org/abs/cs/\([0-9]*\)|:arxiv:\`cs/\1\`|g" "$SPKG_FILE" + break +fi if [ -r "$PKG_SCRIPTS/type" ] ; then echo echo "Type" From 4e67bd5ba0a47d6a171d29a1f22f5c9158d7f464 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 23 Feb 2024 15:50:43 -0800 Subject: [PATCH 152/518] build/pkgs/glpk: Remove outdated comments --- build/pkgs/glpk/SPKG.rst | 9 --------- build/pkgs/glpk/spkg-install.in | 6 ++---- 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/build/pkgs/glpk/SPKG.rst b/build/pkgs/glpk/SPKG.rst index dde5e8ce8fc..cc7cfcb4521 100644 --- a/build/pkgs/glpk/SPKG.rst +++ b/build/pkgs/glpk/SPKG.rst @@ -51,12 +51,3 @@ Special Update/Build Instructions (Instead, we should perhaps use ``--enable-static --enable-shared`` to go safe.) - -Patches -~~~~~~~ - -- All patches below are currently used by spkg-src -- src/01-zlib.patch: don't build the included zlib library. - - The numbering reflect the order in which they have been created from - glpk pristine's sources diff --git a/build/pkgs/glpk/spkg-install.in b/build/pkgs/glpk/spkg-install.in index f592a562148..3ba870de928 100644 --- a/build/pkgs/glpk/spkg-install.in +++ b/build/pkgs/glpk/spkg-install.in @@ -6,14 +6,12 @@ cp "$SAGE_ROOT"/config/config.* . # Note: The following doesn't work with spaces in `$SAGE_LOCAL`, but we don't # support that anyway, since many upstream packages don't. # On the other hand, the only packages GLPK uses that Sage provides are -# GMP/MPIR and zlib, so we should just use `$SAGE_CONFIGURE_GMP` and +# GMP and zlib, so we should just use `$SAGE_CONFIGURE_GMP` and # `--with-zlib="$SAGE_LOCAL"` below (which is safe), and omit the fol- # lowing two lines. (TODO) # # Turns out that (as of version 4.55) -# 1) GLPK unconditionally uses its own copy of zlib (cf. `SPKG.txt`), -# and the `configure` option `--with-zlib` is no longer valid; -# 2) `configure` doesn't support specifying the location of the GMP +# `configure` doesn't support specifying the location of the GMP # library to use; only `--with-gmp[=yes]` or `--with-gmp=no` # are valid options. So we *have to* add Sage's include and # library directories to `CPPFLAGS` (done here) and `LDFLAGS` From 12f9fbe65c9713aa777626660a31af06deba1195 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 23 Feb 2024 15:51:03 -0800 Subject: [PATCH 153/518] src/doc/en/developer/packaging.rst: Remove mention of SPKG.txt --- src/doc/en/developer/packaging.rst | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/doc/en/developer/packaging.rst b/src/doc/en/developer/packaging.rst index ea391941c9c..f769341e3a3 100644 --- a/src/doc/en/developer/packaging.rst +++ b/src/doc/en/developer/packaging.rst @@ -688,8 +688,10 @@ The ``SPKG.rst`` file should follow this pattern: with ``PACKAGE_NAME`` replaced by the SPKG name (= the directory name in ``build/pkgs``). -Legacy ``SPKG.txt`` files have an additional changelog section, but this -information is now kept in the git repository. +Do not include changelogs in the ``SPKG.rst`` file. We keep track of +this information in the commit messages and the pull request +discussions on GitHub only. + .. _section-dependencies: @@ -1293,11 +1295,10 @@ above. License information ------------------- -If you are patching a standard Sage spkg, then you should make sure that -the license information for that package is up-to-date, both in its -``SPKG.rst`` or ``SPKG.txt`` file and in the file ``SAGE_ROOT/COPYING.txt``. For -example, if you are producing an spkg which upgrades the vanilla source -to a new version, check whether the license changed between versions. +License information for a package needs to be put both in its +``SPKG.rst`` file and in the file ``SAGE_ROOT/COPYING.txt``. +Whenever upgrading a package, check whether the license changed between +versions. If an upstream tarball of a package cannot be redistributed for license reasons, rename it to include the string ``do-not-distribute``. This From 2444082c0cc455ee541c1f8decac398689626f6b Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 23 Feb 2024 15:51:53 -0800 Subject: [PATCH 154/518] build/pkgs/glpk: Remove more outdated comments --- build/pkgs/glpk/spkg-check.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/pkgs/glpk/spkg-check.in b/build/pkgs/glpk/spkg-check.in index d4a910e493a..e2736f092b5 100644 --- a/build/pkgs/glpk/spkg-check.in +++ b/build/pkgs/glpk/spkg-check.in @@ -1,4 +1,4 @@ -# Let GLPK use Sage's GMP/MPIR (cf. comments in SPKG.txt and spkg-install): +# Let GLPK use Sage's GMP (cf. comments in spkg-install.in): CPPFLAGS="-I$SAGE_LOCAL/include $CPPFLAGS" LDFLAGS="-L$SAGE_LOCAL/lib $LDFLAGS" From f4a056caf2751041d65156a191ebd7662c0fea26 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 23 Feb 2024 15:52:58 -0800 Subject: [PATCH 155/518] build/bin/sage-site (--info): Reword, no more SPKG.rst --- build/bin/sage-site | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build/bin/sage-site b/build/bin/sage-site index f36eb4d415d..842ce89a81a 100755 --- a/build/bin/sage-site +++ b/build/bin/sage-site @@ -8,8 +8,8 @@ usage() { echo "Sage-the-distribution options:" echo " --optional -- list all optional packages that can be installed" echo " --experimental -- list all experimental packages that can be installed" - echo " --info [packages] -- print the SPKG.txt or SPKG.rst of the given packages," - echo " and some additional information." + echo " --info [packages] -- print the description (SPKG.rst) of the given packages" + echo " and some additional information" echo " -i [packages] -- install the given Sage packages" } From 3cab763b64fd5fe7cf1754b07fd72f4557989c60 Mon Sep 17 00:00:00 2001 From: "John H. Palmieri" Date: Tue, 5 Mar 2024 15:05:17 -0800 Subject: [PATCH 156/518] PR #37501: combine CODE_OF_CONDUCT.md and HANDLING_VIOLATIONS.md into a single file. --- CODE_OF_CONDUCT.md | 268 +++++++++++++++++++++++++++++++++++++++-- HANDLING_VIOLATIONS.md | 241 ------------------------------------ 2 files changed, 258 insertions(+), 251 deletions(-) delete mode 100644 HANDLING_VIOLATIONS.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 7bb46dea139..d3c21378bb2 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,5 +1,9 @@ # Code of Conduct # +This document has two parts: the current one --- the Code of Conduct +itself --- and a second part describing the role of and procedures for +the Sage Code of Conduct Committee. + This Code was approved by the Sage community by a vote which ended on November 24, 2014. @@ -156,7 +160,8 @@ goal will be to respond within 72 hours. Potential consequences for violating the Sage Code of Conduct include: -- Nothing (if the committee determines that no violation occurred) +- Nothing (for example if the matter has been resolved publicly while + the committee was considering responses) - Private feedback or reprimand to the individual(s) involved - Warning the person to cease their behavior and that any further reports will result in sanctions @@ -189,18 +194,261 @@ Google groups: - [Content policy](https://support.google.com/groups/answer/4561696) - [Reporting procedures](https://support.google.com/groups/answer/81275) -## Amending this document ## +## Amending the Code of Conduct ## + +The first part of this document may be amended by a vote of the Sage +community in the sage-devel Google group, with the exception of facts +like the membership of the Sage Code of Conduct Committee, changes to +URLs, or changes to email addresses: changes like that can be done via +a normal pull request. Any pull requests involving this document +should list the committee members as reviewers. + + + +# Guide for the Sage Code of Conduct Committee # + +## Introduction ## + +This is the manual followed by the Sage Code of Conduct Committee. It is used +when we respond to an issue to make sure we’re consistent and fair. + +Enforcing the Code of Conduct impacts our community today and for the +future. It’s an action that we do not take lightly. When reviewing +enforcement measures, the Sage Code of Conduct Committee will keep the +following values and guidelines in mind: + +- Act in a personal manner rather than impersonal. The committee can + engage the parties to understand the situation, while respecting the + privacy and any necessary confidentiality of reporters. However, + it is sometimes necessary to communicate with one or more + individuals directly: the committee’s goal is to improve the health + of our community rather than only produce a formal decision. + +- Emphasize empathy for individuals rather than judging behavior, + avoiding binary labels of “good” and “bad/evil”. Overt, clear-cut + aggression and harassment exists and we will address that + firmly. But many scenarios that can prove challenging to resolve are + those where normal disagreements devolve into unhelpful or harmful + behavior from multiple parties. Understanding the full context and + finding a path that re-engages all is hard, but ultimately the most + productive for our community. + +- We understand that email is a difficult medium and can be + isolating. Receiving criticism over email, without personal contact, + can be particularly painful. This makes it especially important to + keep an atmosphere of open-minded respect of the views of others. It + also means that we must be transparent in our actions, and that we + will do everything in our power to make sure that all our members + are treated fairly and with sympathy. + +- Discrimination can be subtle and it can be unconscious. It can show + itself as unfairness and hostility in otherwise ordinary + interactions. We know that this does occur, and we will take care to + look out for it. We would very much like to hear from you if you + feel you have been treated unfairly, and we will use these + procedures to make sure that your complaint is heard and addressed. + +- Help increase engagement in good discussion practice: try to + identify where discussion may have broken down and provide + actionable information, pointers and resources that can lead to + positive change on these points. + +- Be mindful of the needs of new members: provide them with explicit + support and consideration, with the aim of increasing participation + from underrepresented groups in particular. + +Individuals come from different cultural backgrounds and native +languages. Try to identify any honest misunderstandings caused by a +non-native speaker and help them understand the issue and what they +can change to avoid causing offense. Complex discussion in a foreign +language can be very intimidating, and we want to grow our diversity +also across nationalities and cultures. + +Mediation: voluntary, informal mediation is a tool at our disposal. In +some contexts, such as when two or more parties have escalated to the +point of inappropriate behavior (something sadly common in human +conflict), it may be useful to facilitate a mediation process. This is +only an example: the committee can consider mediation in any case, +mindful that the process is meant to be strictly voluntary and no +party can be pressured to participate. If the committee suggests +mediation, it should: + +- Find a candidate who can serve as a mediator. + +- Obtain the agreement of the reporter(s). The reporter(s) have + complete freedom to decline the mediation idea, or to propose an + alternate mediator. + +- Obtain the agreement of the reported person(s). + +- Settle on the mediator: while parties can propose a different + mediator than the suggested candidate, only if common agreement is + reached on all terms can the process move forward. + +- Establish a timeline for mediation to complete, ideally within two weeks. + +The mediator will engage with all the parties and seek a resolution +that is satisfactory to all. Upon completion, the mediator will +provide a report (vetted by all parties to the process) to the +committee, with recommendations on further steps. The committee will +then evaluate these results (whether satisfactory resolution was +achieved or not) and decide on any additional action deemed necessary. + +## How the committee will respond to reports ## + +When the committee (or a committee member) receives a report, they +will first determine whether the report is about a clear and severe +breach (as defined below). If so, immediate action needs to be taken +in addition to the regular report-handling process. + +### Clear and severe breach actions ### + +We know that it is painfully common for internet communication to +start at or devolve into obvious and flagrant abuse. We will deal +quickly with clear and severe breaches like personal threats, violent, +sexist, or racist language. + +When a member of the Sage Code of Conduct Committee becomes aware of a +clear and severe breach, they will do the following: + +- Immediately disconnect the originator from all Sage communication channels. + +- Reply to the reporter that their report has been received and that + the originator has been disconnected. + +- In every case, the moderator should make a reasonable effort to + contact the originator, and tell them specifically how their + language or actions qualify as a “clear and severe breach”. The + moderator should also say that, if the originator believes this is + unfair or they want to be reconnected to Sage, they have the right + to ask for a review, as below, by the Sage Code of Conduct Committee. The + moderator should copy this explanation to the Sage Code of Conduct + Committee. + +The Sage Code of Conduct Committee will formally review and sign off on all +cases where this mechanism has been applied to make sure it is not +being used to control ordinary heated disagreement. + +### Report handling ### + +When a report is sent to the committee, they will immediately reply to +the reporter to confirm receipt. This reply must be sent within 72 +hours, and the group should strive to respond much quicker than that. + +If a report doesn’t contain enough information, the committee will +obtain all relevant data before acting. The committee is empowered to +contact any individuals involved to get a more complete account of +events. + +The committee will then review the incident and determine, to the best of their ability: + +- What happened. + +- Whether this event constitutes a Code of Conduct violation. + +- Who are the responsible party/parties. + +- Whether this is an ongoing situation, and whether there is a threat to anyone’s physical safety. + +This information will be collected in writing, and whenever possible +the group’s deliberations will be recorded and retained (i.e., chat +transcripts, email discussions, recorded conference calls, summaries +of voice conversations, etc.). + +It is important to retain an archive of all activities of this +committee to ensure consistency in behavior and provide institutional +memory for the project. To assist in this, the default channel of +discussion for this committee will be a private mailing list +accessible to current and future members of the committee. If the +committee finds the need to use off-list communications (e.g., phone +calls for early/rapid response), it should, in all cases, summarize +these back to the list so there’s a good record of the process. + +The Sage Code of Conduct Committee should aim to have a resolution agreed upon +within two weeks. In the event that a resolution can’t be determined +in that time, the committee will respond to the reporter(s) with an +update and projected timeline for the resolution. + +## Resolutions ## + +The committee must agree on a resolution by consensus. If the group +cannot reach consensus and deadlocks for over a week, the committee is +empowered to consult as needed to try to reach consensus. + +Possible responses may include: + +- Taking no further action: + + - if we determine no violations have occurred. + + - if the matter has been resolved publicly while the committee was considering responses. + +- Coordinating voluntary mediation: if all involved parties agree, the + committee may facilitate a mediation process as detailed above. + +- Remind publicly, and point out that some behavior/actions/language + have been judged inappropriate and why in the current context, or + can but hurtful to some people, requesting the community to + self-adjust. + +- A private reprimand from the committee to the individual(s) + involved. In this case, a representative of the committee will + deliver that reprimand to the individual(s) over email, cc’ing the + group. + +- A public reprimand. In this case, a committee representative will deliver + that reprimand in the same venue that the violation occurred, within + the limits of practicality. E.g., the original mailing list for an + email violation, but for a chat room discussion where the + person/context may be gone, they can be reached by other means. The + group may choose to publish this message elsewhere for documentation + purposes. + +- A request for a public or private apology, assuming the reporter + agrees to this idea: they may, at their discretion, refuse further + contact with the violator. A committee representative will deliver + this request. The committee may, if it chooses, attach “strings” to + this request: for example, the group may ask a violator to + apologize, in order to retain one’s membership on a mailing list. + +- A “mutually agreed upon hiatus” where the committee asks the + individual to temporarily refrain from community participation. If + the individual chooses not to take a temporary break voluntarily, + the committee may issue a “mandatory cooling off period”. + +- A permanent or temporary ban from some or all Sage spaces (mailing + lists, GitHub, etc.). The group will maintain records of all such + bans so that they may be reviewed in the future or otherwise + maintained. + +Once a resolution is agreed upon, the committee will contact the +original reporter and any other affected parties and explain that the +committee has taken action. Depending on the situation, the committee +may or may not choose to provide further details about what actions +were taken and how they might affect the reporter. + +The committee will never publicly discuss the issue; all public +statements will be made by a representative of the Sage Code of Conduct +Committee. + +## Conflicts of interest ## + +In the event of any conflict of interest, a committee member must +immediately notify the other members, and recuse themselves if +necessary. + +## Amending the Code of Conduct Committee manual ## -This document may be amended by a vote of the Sage community in the -sage-devel Google group, with the exception of facts like the -membership of the Sage Code of Conduct Committee, changes to URLs, or -changes to email addresses: changes like that can be done via a normal -pull request. Any pull requests involving this document should list -the committee members as reviewers. +This part of the document may be amended by a vote of the Sage Code of +Conduct Committee. ## Credits ## -Portions of this are adapted from the [SciPy code of +Portions of the first part (the code itself) are adapted from the +[SciPy code of conduct](https://docs.scipy.org/doc/scipy/dev/conduct/code_of_conduct.html) and the [NumFOCUS code of -conduct](https://numfocus.org/code-of-conduct). +conduct](https://numfocus.org/code-of-conduct). The second part (the +guide for the Sage Code of Conduct Committee) is largely adapted from +the [SciPy report handling +manual](https://docs.scipy.org/doc/scipy/dev/conduct/report_handling_manual.html). diff --git a/HANDLING_VIOLATIONS.md b/HANDLING_VIOLATIONS.md deleted file mode 100644 index 6f0e72cf161..00000000000 --- a/HANDLING_VIOLATIONS.md +++ /dev/null @@ -1,241 +0,0 @@ -# Guide for handling violations to Sage's Code of Conduct # - -## Introduction ## - -This is the manual followed by the Sage Code of Conduct Committee. It is used -when we respond to an issue to make sure we’re consistent and fair. - -Enforcing the Code of Conduct impacts our community today and for the -future. It’s an action that we do not take lightly. When reviewing -enforcement measures, the Sage Code of Conduct Committee will keep the -following values and guidelines in mind: - -- Act in a personal manner rather than impersonal. The committee can - engage the parties to understand the situation, while respecting the - privacy and any necessary confidentiality of reporters. However, - it is sometimes necessary to communicate with one or more - individuals directly: the committee’s goal is to improve the health - of our community rather than only produce a formal decision. - -- Emphasize empathy for individuals rather than judging behavior, - avoiding binary labels of “good” and “bad/evil”. Overt, clear-cut - aggression and harassment exists and we will address that - firmly. But many scenarios that can prove challenging to resolve are - those where normal disagreements devolve into unhelpful or harmful - behavior from multiple parties. Understanding the full context and - finding a path that re-engages all is hard, but ultimately the most - productive for our community. - -- We understand that email is a difficult medium and can be - isolating. Receiving criticism over email, without personal contact, - can be particularly painful. This makes it especially important to - keep an atmosphere of open-minded respect of the views of others. It - also means that we must be transparent in our actions, and that we - will do everything in our power to make sure that all our members - are treated fairly and with sympathy. - -- Discrimination can be subtle and it can be unconscious. It can show - itself as unfairness and hostility in otherwise ordinary - interactions. We know that this does occur, and we will take care to - look out for it. We would very much like to hear from you if you - feel you have been treated unfairly, and we will use these - procedures to make sure that your complaint is heard and addressed. - -- Help increase engagement in good discussion practice: try to - identify where discussion may have broken down and provide - actionable information, pointers and resources that can lead to - positive change on these points. - -- Be mindful of the needs of new members: provide them with explicit - support and consideration, with the aim of increasing participation - from underrepresented groups in particular. - -Individuals come from different cultural backgrounds and native -languages. Try to identify any honest misunderstandings caused by a -non-native speaker and help them understand the issue and what they -can change to avoid causing offense. Complex discussion in a foreign -language can be very intimidating, and we want to grow our diversity -also across nationalities and cultures. - -Mediation: voluntary, informal mediation is a tool at our disposal. In -some contexts, such as when two or more parties have escalated to the -point of inappropriate behavior (something sadly common in human -conflict), it may be useful to facilitate a mediation process. This is -only an example: the committee can consider mediation in any case, -mindful that the process is meant to be strictly voluntary and no -party can be pressured to participate. If the committee suggests -mediation, it should: - -- Find a candidate who can serve as a mediator. - -- Obtain the agreement of the reporter(s). The reporter(s) have - complete freedom to decline the mediation idea, or to propose an - alternate mediator. - -- Obtain the agreement of the reported person(s). - -- Settle on the mediator: while parties can propose a different - mediator than the suggested candidate, only if common agreement is - reached on all terms can the process move forward. - -- Establish a timeline for mediation to complete, ideally within two weeks. - -The mediator will engage with all the parties and seek a resolution -that is satisfactory to all. Upon completion, the mediator will -provide a report (vetted by all parties to the process) to the -committee, with recommendations on further steps. The committee will -then evaluate these results (whether satisfactory resolution was -achieved or not) and decide on any additional action deemed necessary. - -## How the committee will respond to reports ## - -When the committee (or a committee member) receives a report, they -will first determine whether the report is about a clear and severe -breach (as defined below). If so, immediate action needs to be taken -in addition to the regular report-handling process. - -### Clear and severe breach actions ### - -We know that it is painfully common for internet communication to -start at or devolve into obvious and flagrant abuse. We will deal -quickly with clear and severe breaches like personal threats, violent, -sexist, or racist language. - -When a member of the Sage Code of Conduct Committee becomes aware of a -clear and severe breach, they will do the following: - -- Immediately disconnect the originator from all Sage communication channels. - -- Reply to the reporter that their report has been received and that - the originator has been disconnected. - -- In every case, the moderator should make a reasonable effort to - contact the originator, and tell them specifically how their - language or actions qualify as a “clear and severe breach”. The - moderator should also say that, if the originator believes this is - unfair or they want to be reconnected to Sage, they have the right - to ask for a review, as below, by the Sage Code of Conduct Committee. The - moderator should copy this explanation to the Sage Code of Conduct - Committee. - -The Sage Code of Conduct Committee will formally review and sign off on all -cases where this mechanism has been applied to make sure it is not -being used to control ordinary heated disagreement. - -### Report handling ### - -When a report is sent to the committee, they will immediately reply to -the reporter to confirm receipt. This reply must be sent within 72 -hours, and the group should strive to respond much quicker than that. - -If a report doesn’t contain enough information, the committee will -obtain all relevant data before acting. The committee is empowered to -contact any individuals involved to get a more complete account of -events. - -The committee will then review the incident and determine, to the best of their ability: - -- What happened. - -- Whether this event constitutes a Code of Conduct violation. - -- Who are the responsible party/parties. - -- Whether this is an ongoing situation, and whether there is a threat to anyone’s physical safety. - -This information will be collected in writing, and whenever possible -the group’s deliberations will be recorded and retained (i.e., chat -transcripts, email discussions, recorded conference calls, summaries -of voice conversations, etc.). - -It is important to retain an archive of all activities of this -committee to ensure consistency in behavior and provide institutional -memory for the project. To assist in this, the default channel of -discussion for this committee will be a private mailing list -accessible to current and future members of the committee. If the -committee finds the need to use off-list communications (e.g., phone -calls for early/rapid response), it should, in all cases, summarize -these back to the list so there’s a good record of the process. - -The Sage Code of Conduct Committee should aim to have a resolution agreed upon -within two weeks. In the event that a resolution can’t be determined -in that time, the committee will respond to the reporter(s) with an -update and projected timeline for the resolution. - -## Resolutions ## - -The committee must agree on a resolution by consensus. If the group -cannot reach consensus and deadlocks for over a week, the committee is -empowered to consult as needed to try to reach consensus. - -Possible responses may include: - -- Taking no further action: - - - if we determine no violations have occurred. - - - if the matter has been resolved publicly while the committee was considering responses. - -- Coordinating voluntary mediation: if all involved parties agree, the - committee may facilitate a mediation process as detailed above. - -- Remind publicly, and point out that some behavior/actions/language - have been judged inappropriate and why in the current context, or - can but hurtful to some people, requesting the community to - self-adjust. - -- A private reprimand from the committee to the individual(s) - involved. In this case, a representative of the committee will - deliver that reprimand to the individual(s) over email, cc’ing the - group. - -- A public reprimand. In this case, a committee representative will deliver - that reprimand in the same venue that the violation occurred, within - the limits of practicality. E.g., the original mailing list for an - email violation, but for a chat room discussion where the - person/context may be gone, they can be reached by other means. The - group may choose to publish this message elsewhere for documentation - purposes. - -- A request for a public or private apology, assuming the reporter - agrees to this idea: they may, at their discretion, refuse further - contact with the violator. A committee representative will deliver - this request. The committee may, if it chooses, attach “strings” to - this request: for example, the group may ask a violator to - apologize, in order to retain one’s membership on a mailing list. - -- A “mutually agreed upon hiatus” where the committee asks the - individual to temporarily refrain from community participation. If - the individual chooses not to take a temporary break voluntarily, - the committee may issue a “mandatory cooling off period”. - -- A permanent or temporary ban from some or all Sage spaces (mailing - lists, GitHub, etc.). The group will maintain records of all such - bans so that they may be reviewed in the future or otherwise - maintained. - -Once a resolution is agreed upon, the committee will contact the -original reporter and any other affected parties and explain that the -committee has taken action. Depending on the situation, the committee -may or may not choose to provide further details about what actions -were taken and how they might affect the reporter. - -The committee will never publicly discuss the issue; all public -statements will be made by a representative of the Sage Code of Conduct -Committee. - -## Conflicts of interest ## - -In the event of any conflict of interest, a committee member must -immediately notify the other members, and recuse themselves if -necessary. - -## Amending this document ## - -This document may be amended by a vote of the Sage Code of Conduct -Committee. - -## Credits ## - -This is largely adapted from the [SciPy report handling -manual](https://docs.scipy.org/doc/scipy/dev/conduct/report_handling_manual.html). From 7f990e01edb3231e48b85a97ff989fe9dd74d6cf Mon Sep 17 00:00:00 2001 From: Henry Ehrhard Date: Tue, 5 Mar 2024 16:18:59 -0800 Subject: [PATCH 157/518] Faster chromatic symmetric function computation --- src/sage/graphs/graph.py | 51 ++++++++++++++++++++++++++++++++++------ 1 file changed, 44 insertions(+), 7 deletions(-) diff --git a/src/sage/graphs/graph.py b/src/sage/graphs/graph.py index 0308fbca960..a2303323b3c 100644 --- a/src/sage/graphs/graph.py +++ b/src/sage/graphs/graph.py @@ -3963,18 +3963,55 @@ def chromatic_symmetric_function(self, R=None): sage: XG3 = G3.chromatic_symmetric_function() sage: XG == XG1 + XG2 - XG3 True + + TESTS:: + + sage: Graph([]).chromatic_symmetric_function() == 1 + True + + sage: e = SymmetricFunctions(ZZ).e() + sage: e(graphs.CompleteGraph(5).chromatic_symmetric_function()) + 120*e[5] """ from sage.combinat.sf.sf import SymmetricFunctions - from sage.combinat.partition import _Partitions - from sage.combinat.subset import powerset if R is None: R = ZZ p = SymmetricFunctions(R).p() - ret = p.zero() - for F in powerset(self.edges(sort=True)): - la = _Partitions(self.subgraph(edges=F).connected_components_sizes()) - ret += (-1)**len(F) * p[la] - return ret + + # Dict to store parent of each vertex in disjoint-set forest + # representing components of current induced subgraph. + dsf = {v: None for v in self.vertices()} + + # Dict to store size of tree rooted at each vertex. + sizes = {v: 1 for v in self.vertices()} + + def find(dsf, v): + # Find root of tree in disjoint-set forest + return v if dsf[v] is None else find(dsf, dsf[v]) + + def summand(queue, dsf, sizes): + # Compute powersum terms obtained by adding each subset of + # edges in queue to current subgraph. + if not queue: + return p(sorted([sizes[v] for v in sizes if dsf[v] is None], + reverse=True)) + else: + ret = p.zero() + e = queue.pop() + u = find(dsf, e[0]) + v = find(dsf, e[1]) + # Terms cancel if edge creates a cycle. + if u is not v: + ret = summand(queue, dsf, sizes) + dsf[v] = u + sizes[u] += sizes[v] + ret -= summand(queue, dsf, sizes) + dsf[v] = None + sizes[u] -= sizes[v] + queue.append(e) + return ret + + return summand(list(self.edges()), dsf, sizes) @doc_index("Coloring") def chromatic_quasisymmetric_function(self, t=None, R=None): From 32799a62b7e7d9dcc54bcd46f22e67b2d9bf6096 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Wed, 6 Mar 2024 08:17:31 +0100 Subject: [PATCH 158/518] fix suggested details --- .../cluster_algebra_quiver/mutation_type.py | 14 +++++++------- src/sage/combinat/plane_partition.py | 8 +++++--- src/sage/combinat/tableau.py | 2 +- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/sage/combinat/cluster_algebra_quiver/mutation_type.py b/src/sage/combinat/cluster_algebra_quiver/mutation_type.py index 5578e751b5d..ef8aaab5e54 100644 --- a/src/sage/combinat/cluster_algebra_quiver/mutation_type.py +++ b/src/sage/combinat/cluster_algebra_quiver/mutation_type.py @@ -149,7 +149,7 @@ def _triangles(dg): return trians -def _all_induced_cycles_iter( dg ): +def _all_induced_cycles_iter(dg): """ Return an iterator for all induced oriented cycles of length greater than or equal to 4 in the digraph ``dg``. @@ -356,9 +356,9 @@ def _connected_mutation_type(dg): for edge in edges: label = edge[2] if label not in [(1,-1),(2,-2),(1,-2),(2,-1),(4,-1),(1,-4)]: - # _false_return(i) is a simple function that simply returns 'unknown'. For debugging purposes, it - # can also output 'DEBUG: error i' if desired. - # this command is used many times in this code, something times without the argument i. + # _false_return(i) is a simple function that simply returns 'unknown'. For debugging purposes, it + # can also output 'DEBUG: error i' if desired. + # this command is used many times in this code, something times without the argument i. return _false_return(2) elif label == (2,-2): dg.set_edge_label( edge[0], edge[1], 1 ) @@ -1032,7 +1032,7 @@ def _connected_mutation_type_AAtildeD(dg, ret_conn_vert=False): test_triangles = [[tuple(trian) for trian in oriented_trians if edge in trian] for edge in multiple_trian_edges] - unique_triangle = set(test_triangles[0]).intersection( *test_triangles[1:] ) + unique_triangle = set.intersection(*map(set, test_triangles)) if len(unique_triangle) != 1: return _false_return(19) else: @@ -1299,7 +1299,7 @@ def load_data(n, user=True): return data -def _mutation_type_from_data( n, dig6, compute_if_necessary=True ): +def _mutation_type_from_data(n, dig6, compute_if_necessary=True): r""" Return the mutation type from the given dig6 data by looking into the precomputed mutation types @@ -1506,7 +1506,7 @@ def _random_tests(mt, k, mut_class=None, nr_mut=5): dg = dg_new -def _random_multi_tests( n, k, nr_mut=5 ): +def _random_multi_tests(n, k, nr_mut=5): """ Provide multiple random tests to find bugs in the mutation type methods. diff --git a/src/sage/combinat/plane_partition.py b/src/sage/combinat/plane_partition.py index bc523121623..8f4ded21d16 100644 --- a/src/sage/combinat/plane_partition.py +++ b/src/sage/combinat/plane_partition.py @@ -314,17 +314,19 @@ def x_tableau(self, tableau=True) -> Tableau: return Tableau(X) return X - def cells(self) -> list[list[int]]: + def cells(self) -> list[tuple[int, int, int]]: r""" Return the list of cells inside ``self``. + Each cell is a tuple. + EXAMPLES:: sage: PP = PlanePartition([[3,1],[2]]) sage: PP.cells() - [[0, 0, 0], [0, 0, 1], [0, 0, 2], [0, 1, 0], [1, 0, 0], [1, 0, 1]] + [(0, 0, 0), (0, 0, 1), (0, 0, 2), (0, 1, 0), (1, 0, 0), (1, 0, 1)] """ - return [[r, c, h] + return [(r, c, h) for r in range(len(self)) for c in range(len(self[r])) for h in range(self[r][c])] diff --git a/src/sage/combinat/tableau.py b/src/sage/combinat/tableau.py index 7613ef9a671..d5ebaee709c 100644 --- a/src/sage/combinat/tableau.py +++ b/src/sage/combinat/tableau.py @@ -2618,7 +2618,7 @@ def slide_multiply(self, other): l = len(self[0]) st = [(None,) * l + row for row in other] - st.extend(row for row in self) + st.extend(self) from sage.combinat.skew_tableau import SkewTableau return SkewTableau(st).rectify() From f8c60447b25afc825380b851f4261b48c25bdc66 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 29 Feb 2024 17:32:45 -0800 Subject: [PATCH 159/518] Replace broken links to sphinx material on pocoo.org --- pkgs/sage-sws2rst/bin/sage-sws2rst | 8 ++++---- src/sage/misc/sageinspect.py | 7 +++---- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/pkgs/sage-sws2rst/bin/sage-sws2rst b/pkgs/sage-sws2rst/bin/sage-sws2rst index b8f1816fc1e..d5723fa6a22 100755 --- a/pkgs/sage-sws2rst/bin/sage-sws2rst +++ b/pkgs/sage-sws2rst/bin/sage-sws2rst @@ -170,12 +170,12 @@ steps are one way to do so. REFERENCES: -.. [1] First Steps with Sphinx, - http://sphinx.pocoo.org/tutorial.html +.. [1] Getting Started, + https://www.sphinx-doc.org/en/master/usage/quickstart.html .. [2] MathJax, http://www.mathjax.org/ -.. [3] Defining Document Structure, First Steps with Sphinx, - http://sphinx.pocoo.org/tutorial.html#defining-document-structure""" +.. [3] Defining Document Structure, Getting Started, + https://www.sphinx-doc.org/en/master/usage/quickstart.html#defining-document-structure""" parser = OptionParser(usage=usage) parser.add_option("--sphinxify", diff --git a/src/sage/misc/sageinspect.py b/src/sage/misc/sageinspect.py index d6c5fa75e9c..f851e001e7d 100644 --- a/src/sage/misc/sageinspect.py +++ b/src/sage/misc/sageinspect.py @@ -2577,10 +2577,6 @@ def sage_getvariablename(self, omit_underscore_names=True): ``omit_underscore_names`` is True (the default) then omit names starting with an underscore "_". - This is a modified version of code taken from - http://pythonic.pocoo.org/2009/5/30/finding-objects-names, - written by Georg Brandl. - EXAMPLES:: sage: # needs sage.modules @@ -2597,6 +2593,9 @@ def sage_getvariablename(self, omit_underscore_names=True): sage: sage_getvariablename(random_matrix(ZZ, 60)) # needs sage.modules [] """ + # This is a modified version of code taken from + # https://web.archive.org/web/20100416095847/http://pythonic.pocoo.org/2009/5/30/finding-objects-names + # written by Georg Brandl. result = [] for frame in inspect.stack(): for name, obj in frame[0].f_globals.items(): From 8658d26f7c3e9209b58aff167f3739f689c790a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Wed, 6 Mar 2024 11:11:10 +0100 Subject: [PATCH 160/518] minor details + non-recursive algo in integer_valued_polynomials --- .../polynomial/integer_valued_polynomials.py | 54 +++++++++++-------- 1 file changed, 33 insertions(+), 21 deletions(-) diff --git a/src/sage/rings/polynomial/integer_valued_polynomials.py b/src/sage/rings/polynomial/integer_valued_polynomials.py index dd601dd5f8d..0fea588bc9d 100644 --- a/src/sage/rings/polynomial/integer_valued_polynomials.py +++ b/src/sage/rings/polynomial/integer_valued_polynomials.py @@ -281,7 +281,7 @@ def algebra_generators(self): class ElementMethods: def __call__(self, v): """ - Evaluation at some value ``v`` + Return the evaluation at some value ``v``. EXAMPLES:: @@ -590,8 +590,7 @@ def _element_constructor_(self, x): def _coerce_map_from_(self, R): r""" - Return ``True`` if there is a coercion from ``R`` into ``self`` - and ``False`` otherwise. + Return whether there is a coercion from ``R`` into ``self``. INPUT: @@ -690,6 +689,10 @@ def _poly(self, i): """ Convert the basis element `S[i]` to a polynomial. + INPUT: + + - ``i`` -- an integer + EXAMPLES:: sage: F = IntegerValuedPolynomialRing(ZZ).S() @@ -707,6 +710,8 @@ def umbra(self): This is the derivative at `-1` of the shift by one. + This is related to Bernoulli numbers. + .. SEEALSO:: :meth:`derivative_at_minus_one` EXAMPLES:: @@ -769,24 +774,23 @@ def variable_shift(self, k=1): S[5] sage: S[5].variable_shift().variable_shift(-1) S[5] + sage: S[5].variable_shift(2).variable_shift(-2) + S[5] """ if k == 0: return self A = self.parent() - if k > 0: - B = A.basis() - resu = A.linear_combination((B[j], c) for i, c in self - for j in range(i + 1)) - if k == 1: - return resu - return resu.variable_shift(k - 1) + def on_basis(n): + return {A._indices(j): binomial(k + n - 1 - j, n - j) + for j in range(n + 1)} - resu = self - A._from_dict({i - 1: c for i, c in self if i}) - if k == -1: - return resu - return resu.variable_shift(k + 1) + from sage.data_structures.blas_dict import linear_combination + mc = self._monomial_coefficients + ret = linear_combination((on_basis(index), coeff) + for (index, coeff) in mc.items()) + return A.element_class(A, ret) def derivative_at_minus_one(self): """ @@ -858,7 +862,7 @@ def fraction(self): sage: A = IntegerValuedPolynomialRing(ZZ).S() sage: ex = A.monomial(4) sage: f = ex.fraction();f - -1/(t^5 - 5*t^4 + 10*t^3 - 10*t^2 + 5*t - 1) + 1/(-t^5 + 5*t^4 - 10*t^3 + 10*t^2 - 5*t + 1) sage: F = LazyPowerSeriesRing(QQ, 't') sage: F(f) @@ -871,7 +875,7 @@ def fraction(self): sage: y = polygen(QQ, 'y') sage: penta = A.from_polynomial(7/2*y^2 + 7/2*y + 1) sage: penta.fraction() - (-t^2 - 5*t - 1)/(t^3 - 3*t^2 + 3*t - 1) + (t^2 + 5*t + 1)/(-t^3 + 3*t^2 - 3*t + 1) TESTS:: @@ -882,8 +886,9 @@ def fraction(self): """ v = self.h_vector() d = len(v) - t = polygen(self.parent().base_ring(), 't') - numer = sum(v[i] * t**(d - 1 - i) for i in range(d)) + ring_t = PolynomialRing(self.parent().base_ring(), 't') + t = ring_t.gen() + numer = ring_t({d - 1 - i: v[i] for i in range(d)}) return numer / (1 - t)**d S = Shifted @@ -963,7 +968,7 @@ class Binomial(CombinatorialFreeModule, BindableClass): sage: F(4/3) 4/3*B[0] """ - def __init__(self, A): + def __init__(self, A) -> None: r""" Initialize ``self``. @@ -1068,8 +1073,11 @@ def _element_constructor_(self, x): def _coerce_map_from_(self, R): r""" - Return ``True`` if there is a coercion from ``R`` into ``self`` - and ``False`` otherwise. + Return whether there is a coercion from ``R`` into ``self``. + + INPUT: + + - ``R`` -- a commutative ring The things that coerce into ``self`` are @@ -1164,6 +1172,10 @@ def _poly(self, i): """ Convert the basis element `B[i]` to a polynomial. + INPUT: + + - ``i`` -- an integer + EXAMPLES:: sage: F = IntegerValuedPolynomialRing(ZZ).B() From 62c2a2714a2f4aea8bd3c2fe401e1a00ff9d094c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Wed, 6 Mar 2024 12:21:12 +0100 Subject: [PATCH 161/518] convert Puiseux ring to Parent framework --- src/sage/rings/puiseux_series_ring.py | 60 +++++++++++++++++---------- 1 file changed, 39 insertions(+), 21 deletions(-) diff --git a/src/sage/rings/puiseux_series_ring.py b/src/sage/rings/puiseux_series_ring.py index 2f761c51925..be515fe7b57 100644 --- a/src/sage/rings/puiseux_series_ring.py +++ b/src/sage/rings/puiseux_series_ring.py @@ -23,23 +23,23 @@ # 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.categories.fields import Fields from sage.misc.cachefunc import cached_method from sage.rings.infinity import infinity -from sage.rings.puiseux_series_ring_element import PuiseuxSeries -from sage.structure.unique_representation import UniqueRepresentation -from sage.rings.ring import CommutativeRing -from sage.structure.element import parent from sage.rings.laurent_series_ring import LaurentSeriesRing from sage.rings.laurent_series_ring_element import LaurentSeries from sage.rings.power_series_ring import is_PowerSeriesRing from sage.rings.power_series_ring_element import PowerSeries +from sage.rings.puiseux_series_ring_element import PuiseuxSeries +from sage.structure.element import parent +from sage.structure.parent import Parent +from sage.structure.unique_representation import UniqueRepresentation -class PuiseuxSeriesRing(UniqueRepresentation, CommutativeRing): +class PuiseuxSeriesRing(UniqueRepresentation, Parent): """ Rings of Puiseux series. @@ -101,11 +101,14 @@ def __init__(self, laurent_series): # ring will be R(( x )) self._laurent_series_ring = laurent_series - CommutativeRing.__init__(self, base_ring, - names=laurent_series.variable_names(), - category=laurent_series.category()) + cat = laurent_series.category() + if base_ring in Fields(): + cat &= Fields() + Parent.__init__(self, base_ring, + names=laurent_series.variable_names(), + category=cat) - def _repr_(self): + def _repr_(self) -> str: """ String representation. @@ -114,8 +117,7 @@ def _repr_(self): sage: PuiseuxSeriesRing(AA, 'y') # needs sage.rings.number_field Puiseux Series Ring in y over Algebraic Real Field """ - s = "Puiseux Series Ring in {} over {}".format(self.variable_name(), - self.base_ring()) + s = f"Puiseux Series Ring in {self.variable_name()} over {self.base_ring()}" if self.is_sparse(): s = 'Sparse ' + s return s @@ -152,7 +154,7 @@ def change_ring(self, R): """ return PuiseuxSeriesRing(self._laurent_series_ring.change_ring(R)) - def is_sparse(self): + def is_sparse(self) -> bool: """ Return whether ``self`` is sparse. @@ -164,7 +166,7 @@ def is_sparse(self): """ return self.laurent_series_ring().is_sparse() - def is_dense(self): + def is_dense(self) -> bool: """ Return whether ``self`` is dense. @@ -176,7 +178,7 @@ def is_dense(self): """ return self.laurent_series_ring().is_dense() - def is_field(self, proof=True): + def is_field(self, proof=True) -> bool: r""" Return whether ``self`` is a field. @@ -188,7 +190,11 @@ def is_field(self, proof=True): sage: A = PuiseuxSeriesRing(ZZ, 'y') sage: A.is_field() False - sage: A.change_ring(QQ).is_field() + sage: A in Fields() + False + sage: B = A.change_ring(QQ); B.is_field() + True + sage: B in Fields() True """ return self.base_ring().is_field(proof=proof) @@ -242,7 +248,7 @@ def residue_field(self): ... TypeError: the base ring is not a field """ - if not self.base_ring().is_field(): + if self.base_ring() not in Fields(): raise TypeError("the base ring is not a field") return self.base_ring() @@ -264,7 +270,7 @@ def uniformizer(self): ... TypeError: the base ring is not a field """ - if not self.base_ring().is_field(): + if self.base_ring() not in Fields(): raise TypeError("the base ring is not a field") return self.gen() @@ -310,7 +316,7 @@ def _element_constructor_(self, x, e=1, prec=infinity): # 1. x is a Puiseux series belonging to this ring. # This is short-circuited by the coercion framework. - #if isinstance(x, self.element_class) and P is self: + # if isinstance(x, self.element_class) and P is self: # return x # 2. x is a Puiseux series but not an element of this ring. the laurent # part should be coercible to the laurent series ring of self @@ -400,9 +406,21 @@ def gen(self, n=0): z """ if n != 0: - raise IndexError("generator {} not defined".format(n)) + raise IndexError(f"generator {n} not defined") return self.element_class(self, [0, 1], e=1) + def gens(self) -> tuple: + """ + Return the tuple of generators. + + EXAMPLES:: + + sage: A = PuiseuxSeriesRing(AA, 'z') # needs sage.rings.number_field + sage: A.gens() # needs sage.rings.number_field + (z,) + """ + return (self.gen(),) + def ngens(self): r""" Return the number of generators of ``self``, namely 1. From ea053cc65f03220ba18761efee76a5a7da3d0282 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Wed, 6 Mar 2024 13:46:52 +0100 Subject: [PATCH 162/518] some details in Puiseux --- src/sage/rings/puiseux_series_ring.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/sage/rings/puiseux_series_ring.py b/src/sage/rings/puiseux_series_ring.py index be515fe7b57..34e91fb6d9b 100644 --- a/src/sage/rings/puiseux_series_ring.py +++ b/src/sage/rings/puiseux_series_ring.py @@ -205,7 +205,7 @@ def fraction_field(self): If the base ring is a field, then Puiseux series are already a field. If the base ring is a domain, then the Puiseux series over its fraction - field is returned. Otherwise, raise a ``ValueError``. + field is returned. Otherwise, raise a :class:`ValueError`. EXAMPLES:: @@ -224,10 +224,9 @@ def fraction_field(self): from sage.categories.fields import Fields if self in Fields(): return self - elif self in IntegralDomains(): + if self in IntegralDomains(): return PuiseuxSeriesRing(self._laurent_series_ring.fraction_field()) - else: - raise ValueError('must be an integral domain') + raise ValueError('must be an integral domain') def residue_field(self): r""" @@ -415,13 +414,13 @@ def gens(self) -> tuple: EXAMPLES:: - sage: A = PuiseuxSeriesRing(AA, 'z') # needs sage.rings.number_field - sage: A.gens() # needs sage.rings.number_field - (z,) + sage: A = PuiseuxSeriesRing(QQ, 'z') + sage: A.gens() + (z,) """ return (self.gen(),) - def ngens(self): + def ngens(self) -> int: r""" Return the number of generators of ``self``, namely 1. From 5f8763e1222385eb4956afc562fda72e5282e288 Mon Sep 17 00:00:00 2001 From: RuchitJagodara Date: Wed, 6 Mar 2024 19:14:56 +0530 Subject: [PATCH 163/518] Fix documentation of __hash__ function --- src/sage/modules/free_module_element.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/modules/free_module_element.pyx b/src/sage/modules/free_module_element.pyx index 3492dbf2eb1..d08fdd138c5 100644 --- a/src/sage/modules/free_module_element.pyx +++ b/src/sage/modules/free_module_element.pyx @@ -1143,7 +1143,7 @@ cdef class FreeModuleElement(Vector): # abstract base class def __hash__(self): """ - Return hash of this vector. Only mutable vectors are hashable. + Return hash of this vector. Only immutable vectors are hashable. EXAMPLES:: From 2bb5e9171cc9848e4cede1c7518c157383c2677d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Wed, 6 Mar 2024 14:45:55 +0100 Subject: [PATCH 164/518] fix suggested details --- src/sage/rings/polynomial/integer_valued_polynomials.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/sage/rings/polynomial/integer_valued_polynomials.py b/src/sage/rings/polynomial/integer_valued_polynomials.py index 0fea588bc9d..2197ac7f3d3 100644 --- a/src/sage/rings/polynomial/integer_valued_polynomials.py +++ b/src/sage/rings/polynomial/integer_valued_polynomials.py @@ -16,6 +16,7 @@ from sage.categories.realizations import Category_realization_of_parent from sage.categories.rings import Rings from sage.combinat.free_module import CombinatorialFreeModule +from sage.data_structures.blas_dict import linear_combination from sage.matrix.constructor import matrix from sage.misc.bindable_class import BindableClass from sage.misc.cachefunc import cached_method @@ -786,10 +787,9 @@ def on_basis(n): return {A._indices(j): binomial(k + n - 1 - j, n - j) for j in range(n + 1)} - from sage.data_structures.blas_dict import linear_combination mc = self._monomial_coefficients ret = linear_combination((on_basis(index), coeff) - for (index, coeff) in mc.items()) + for index, coeff in mc.items()) return A.element_class(A, ret) def derivative_at_minus_one(self): @@ -1221,10 +1221,9 @@ def on_basis(n): return {A._indices(j): binomial(k, n - j) for j in range(n + 1)} - from sage.data_structures.blas_dict import linear_combination mc = self._monomial_coefficients ret = linear_combination((on_basis(index), coeff) - for (index, coeff) in mc.items()) + for index, coeff in mc.items()) return A.element_class(A, ret) B = Binomial From be7c5f113df9080ecc4843da0073a9fc8e71f1d2 Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Wed, 6 Mar 2024 14:46:12 +0100 Subject: [PATCH 165/518] Implemented the (local) Hilbert symbol for global function fields Amends: Changed some documentation, adapted characteristic check to check ==2 instead of <= 2 --- .../rings/function_field/function_field.py | 127 ++++++++++++++++++ 1 file changed, 127 insertions(+) diff --git a/src/sage/rings/function_field/function_field.py b/src/sage/rings/function_field/function_field.py index af5b6c4d924..83ba61572c4 100644 --- a/src/sage/rings/function_field/function_field.py +++ b/src/sage/rings/function_field/function_field.py @@ -1224,6 +1224,133 @@ def completion(self, place, name=None, prec=None, gen_name=None): from .maps import FunctionFieldCompletion return FunctionFieldCompletion(self, place, name=name, prec=prec, gen_name=gen_name) + def hilbert_symbol(self, a, b, P): + """ + Return the Hilbert symbol `(a,b)_{F_P}` (where `F_P` is the + completion of ``self`` at the place ``P``), i.e. the value 1 if + the quaternion algebra defined by `(a,b)` over `F_P` is split, + and -1 if it is division. + + We use the completion at the place `P` to compute the valuations `v(a)` + and `v(b)` as well as elements `a0` and `b0` such that, for a uniformizer + `pi` of the unique maximal ideal of the completion, the elememts + `a*pi^{-v(a))}` and `a0` respectively the elements `b*pi^{-v(b)}` and `b0` + are congruent modulo `pi`. Motivated by formula 12.4.10 in [Voi2021]_. + + Currently only tested for function fields separable over their base + since places are not fully supported for other function fields. Only + implemented for global function fields of odd characteristic. + + INPUT: + + - ``a`` and ``b``: Units in the function field ``self`` + + - ``P``: A place of the function field ``self`` + + EXAMPLES:: + + sage: K. = FunctionField(GF(17)) + sage: P = K.places()[0]; P + Place (1/x) + sage: a = (5*x + 6)/(x + 15) + sage: b = 7/x + sage: K.hilbert_symbol(a, b, P) + -1 + + sage: Q = K.places()[7]; Q + Place (x + 6) + sage: c = 15*x + 12 + sage: d = 16/(x + 13) + sage: K.hilbert_symbol(c, d, Q) + 1 + + sage: K. = FunctionField(GF(5)); R. = PolynomialRing(K) + sage: f = (x^2 + 2*x + 2)*T^5 + (4*x^2 + 2*x + 3)*T^4 + 3*T^3 + 4*T^2 + + (2/(x^2 + 4*x + 1))*T + 3*x^2 + 2*x + 4 + sage: L. = K.extension(f) + sage: P = L.places_above(K.places()[0])[1] + sage: a = ((3*x^3 + 2*x^2 + x + 1)/(x + 2))*y^4 + (4/(x + 4))*y^3 + + ((3*x + 4)/(x + 4))*y^2 + + ((x^4 + 2*x^3 + 3*x^2 + x + 2)/(x^4 + x^3 + x^2 + 2))*y + + (x^4 + x^2 + 2*x + 4)/(x^2 + 2*x + 2) + sage: b = ((x + 1)/(x + 4))*y^4 + + ((2*x^4 + 3*x^3 + 3*x^2 + 3)/(x^2 + 2*x + 2))*y^3 + (2/(x + 4))*y^2 + + ((4*x^4 + 4*x^3 + 4*x^2 + 2)/(x^4 + x^3 + x^2 + 2))*y + + (x^3 + x)/(x^3 + x^2 + x + 2) + sage: L.hilbert_symbol(a, b, P) + 1 + sage: Q = L.places_above(K.places()[1])[0] + sage: c = ((3*x^3 + 3*x^2 + 3*x + 3)/(x^3 + 3*x^2 + x))*y^4 + + ((3*x^4 + 2*x^3 + 2*x^2 + 4*x + 3)/(x^3 + 3*x^2 + x))*y^3 + + ((3*x + 4)/(x^3 + 3*x^2 + x))*y^2 + + ((4*x + 2)/(x^5 + 2*x^4 + 4*x^3 + 2*x^2 + x))*y + + (x^3 + 2*x^2 + 4)/(x^3 + 3*x^2 + x) + sage: d = ((2*x^4 + x^2 + 2*x + 3)/(x^2 + 2*x + 2))*y^4 + + ((4*x^2 + 2)/(x^2 + 2*x + 2))*y^3 + ((3*x^2 + 3*x + 3)/(x^2 + 1))*y^2 + + ((x^6 + 4*x^5 + 4*x^4 + 3*x^3 + 3*x^2 + x + 3)/(x^4 + x^3 + x^2 + 2))*y + + (4*x^3 + 4*x^2 + 2*x + 2)/(x + 4) + sage: L.hilbert_symbol(c, d, Q) + -1 + + sage: K. = FunctionField(GF(3)); R. = PolynomialRing(K) + sage: g = ((2*x + 1)/x)*T^5 + ((2*x + 1)/(x + 1))*T^4 + ((x^2 + 1)/x)*T^3 + + (2*x/(x^2 + 2*x + 2))*T^2 + 2*T + (2*x + 2)/(x + 2) + sage: L = K.extension(g) + sage: P = L.places_above(K.places()[1])[1] + sage: a = ((x + 2)/(x + 1))*y^4 + ((x^4 + 2*x^2 + 1)/(x^2 + 2*x))*y^3 + + ((x^5 + x^2 + 2*x + 1)/(x^4 + 2*x^3 + x^2 + x + 1))*y^2 + + ((2*x + 2)/(x + 2))*y + (2*x^4 + x^3 + x^2 + 2*x + 2)/(x^2 + x + 1) + sage: b = ((x^2 + x + 1)/(x^2 + 2*x + 1))*y^4 + ((2*x^2 + x + 1)/(x + 1))*y^3 + + ((x^5 + x^4 + 2*x^3 + x^2 + 2*x + 2)/(x^3 + x + 2))*y^2 + + (2*x/(x^2 + 2))*y + 2*x^2 + 2*x + 1 + sage: L.hilbert_symbol(a, b, P) + -1 + """ + if not self.is_global(): + raise NotImplementedError('only supported for global function fields') + + if self.characteristic() == 2: + raise NotImplementedError('only supported in odd characteristic') + + if not (a in self and b in self): + raise ValueError('a and b must be elements of the function field') + + if a.is_zero() or b.is_zero(): + raise ValueError('the invariants a and b must be nonzero') + + # Compute the completion map to precision 1 for computation of the + # valuations v(a), v(b) as well as the elements a_0, b_0 + try: + sigma = self.completion(P, prec=1, gen_name='i') + except AttributeError: + raise ValueError('P must be a place of the function field F') + + # Apply the completion map to a to get v(a) and a_0 + ser_a = sigma(a) + v_a = ser_a.valuation() + a0 = ser_a.coefficients()[0] + + # Apply the completion map to b to get v(b) and b_0 + ser_b = sigma(b) + v_b = ser_b.valuation() + b0 = ser_b.coefficients()[0] + + # Get the residue field of the completion, together with the residue map + k, _, tau = self.residue_field(P) + e = (k.order()-1)/2 + + # Use Euler's criterion to compute the power of Legendre symbols + a_rd_pw = tau(a0)**(v_b * e) + b_rd_pw = tau(b0)**(v_a * e) + + # Finally, put the result together + res = k(-1)**(v_a * v_b * e) * a_rd_pw * b_rd_pw + + if res == k(1): + return ZZ(1) + else: + return ZZ(-1) + def extension_constant_field(self, k): """ Return the constant field extension with constant field `k`. From 6196143dd27f93c0c4d3158faae7977139494e27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Wed, 6 Mar 2024 15:54:42 +0100 Subject: [PATCH 166/518] use Parent in Symbolic Ring --- src/sage/symbolic/ring.pyx | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/sage/symbolic/ring.pyx b/src/sage/symbolic/ring.pyx index 539c3bdf21e..1e6bfadab8a 100644 --- a/src/sage/symbolic/ring.pyx +++ b/src/sage/symbolic/ring.pyx @@ -32,7 +32,6 @@ The symbolic ring # **************************************************************************** from sage.rings.integer cimport Integer -from sage.rings.ring cimport CommutativeRing import sage.rings.abc @@ -45,7 +44,9 @@ from sage.symbolic.expression cimport ( new_Expression_symbol, ) +from sage.categories.commutative_rings import CommutativeRings from sage.structure.element cimport Element, Expression +from sage.structure.parent cimport Parent from sage.categories.morphism cimport Morphism from sage.structure.coerce cimport is_numpy_type @@ -71,6 +72,8 @@ cdef class SymbolicRing(sage.rings.abc.SymbolicRing): """ Initialize the Symbolic Ring. + This is a commutative ring of symbolic expressions and functions. + EXAMPLES:: sage: SR @@ -85,7 +88,7 @@ cdef class SymbolicRing(sage.rings.abc.SymbolicRing): """ if base_ring is None: base_ring = self - CommutativeRing.__init__(self, base_ring) + Parent.__init__(self, base_ring, category=CommutativeRings()) self._populate_coercion_lists_(convert_method_name='_symbolic_') self.symbols = {} @@ -98,9 +101,9 @@ cdef class SymbolicRing(sage.rings.abc.SymbolicRing): """ return the_SymbolicRing, tuple() - def _repr_(self): + def _repr_(self) -> str: """ - Return a string representation of self. + Return a string representation of ``self``. EXAMPLES:: From a1edfa5c95ee044441c37ff5d712e6e79cc6a3b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Wed, 6 Mar 2024 16:59:14 +0100 Subject: [PATCH 167/518] refresh sha_tate.py --- src/sage/schemes/elliptic_curves/sha_tate.py | 149 +++++++++---------- 1 file changed, 69 insertions(+), 80 deletions(-) diff --git a/src/sage/schemes/elliptic_curves/sha_tate.py b/src/sage/schemes/elliptic_curves/sha_tate.py index b5a7b48bf0a..c6aab99de40 100644 --- a/src/sage/schemes/elliptic_curves/sha_tate.py +++ b/src/sage/schemes/elliptic_curves/sha_tate.py @@ -78,23 +78,22 @@ # the License, or (at your option) any later version. # https://www.gnu.org/licenses/ # **************************************************************************** +from math import sqrt -from sage.structure.sage_object import SageObject -from sage.rings.integer import Integer -from sage.rings.real_mpfr import RealField -from sage.rings.rational_field import RationalField -from sage.rings.real_mpfi import RIF -from sage.rings.integer_ring import ZZ from sage.functions.log import log -from math import sqrt from sage.misc.verbose import verbose -import sage.arith.all as arith -from sage.rings.padics.factory import Qp from sage.modules.free_module_element import vector +from sage.rings.integer import Integer +from sage.rings.integer_ring import ZZ +from sage.rings.padics.factory import Qp +from sage.rings.rational_field import Q +from sage.rings.real_mpfi import RIF +from sage.rings.real_mpfr import RealField +from sage.structure.sage_object import SageObject +import sage.arith.all as arith factor = arith.factor valuation = arith.valuation -Q = RationalField() class Sha(SageObject): @@ -296,7 +295,7 @@ def an_numerical(self, prec=None, def an(self, use_database=False, descent_second_limit=12): r""" - Returns the Birch and Swinnerton-Dyer conjectural order of `Sha` + Return the Birch and Swinnerton-Dyer conjectural order of `Sha` as a provably correct integer, unless the analytic rank is > 1, in which case this function returns a numerical value. @@ -432,7 +431,7 @@ def an(self, use_database=False, descent_second_limit=12): return Sha else: # rank > 0 (Not provably correct) - L1, error_bound = E.lseries().deriv_at1(10*sqrt(E.conductor()) + 10) + L1, error_bound = E.lseries().deriv_at1(10 * sqrt(E.conductor()) + 10) if abs(L1) < error_bound: s = self.an_numerical() E.__an = s @@ -455,7 +454,7 @@ def an(self, use_database=False, descent_second_limit=12): def an_padic(self, p, prec=0, use_twists=True): r""" - Returns the conjectural order of `Sha(E/\QQ)`, + Return the conjectural order of `Sha(E/\QQ)`, according to the `p`-adic analogue of the Birch and Swinnerton-Dyer conjecture as formulated in [MTT1986]_ and [BP1993]_. @@ -561,21 +560,21 @@ def an_padic(self, p, prec=0, use_twists=True): Et, D = E.minimal_quadratic_twist() # trac 6455 : we have to assure that the twist back is allowed D = ZZ(D) - if D % p == 0: - D = ZZ(D/p) + if not D % p: + D = D // p for ell in D.prime_divisors(): - if ell % 2 == 1: - if Et.conductor() % ell**2 == 0: - D = ZZ(D/ell) + if ell % 2: + if not Et.conductor() % ell**2: + D = D // ell ve = valuation(D, 2) - de = ZZ((D/2**ve).abs()) + de = (D >> ve).abs() if de % 4 == 3: de = -de Et = E.quadratic_twist(de) # now check individually if we can twist by -1 or 2 or -2 Nmin = Et.conductor() Dmax = de - for DD in [-4*de, 8*de, -8*de]: + for DD in [-4 * de, 8 * de, -8 * de]: Et = E.quadratic_twist(DD) if Et.conductor() < Nmin and valuation(Et.conductor(), 2) <= valuation(DD, 2): Nmin = Et.conductor() @@ -593,12 +592,12 @@ def an_padic(self, p, prec=0, use_twists=True): # term will be the L-value divided by the Neron # period. ms = E.modular_symbol(sign=+1, normalize='L_ratio') - lstar = ms(0)/E.real_components() - bsd = tam/tors + lstar = ms(0) / E.real_components() + bsd = tam / tors if prec == 0: # prec = valuation(lstar/bsd, p) prec = 20 - shan = Qp(p, prec=prec + 2)(lstar/bsd) + shan = Qp(p, prec=prec + 2)(lstar / bsd) elif E.is_ordinary(p): K = reg.parent() @@ -608,9 +607,9 @@ def an_padic(self, p, prec=0, use_twists=True): if not E.is_good(p): eps = 2 else: - eps = (1 - arith.kronecker_symbol(D, p)/lp.alpha())**2 + eps = (1 - arith.kronecker_symbol(D, p) / lp.alpha())**2 # according to the p-adic BSD this should be equal to the leading term of the p-adic L-series divided by sha: - bsdp = tam * reg * eps/tors/lg**r + bsdp = tam * reg * eps / tors / lg**r else: r += 1 # exceptional zero eq = E.tate_curve(p) @@ -618,7 +617,7 @@ def an_padic(self, p, prec=0, use_twists=True): # according to the p-adic BSD (Mazur-Tate-Teitelbaum) # this should be equal to the leading term of the p-adic L-series divided by sha: - bsdp = tam * reg * Li/tors/lg**r + bsdp = tam * reg * Li / tors / lg**r v = bsdp.valuation() if v > 0: @@ -647,7 +646,7 @@ def an_padic(self, p, prec=0, use_twists=True): n += 1 verbose("increased precision to %s" % n) - shan = lstar/bsdp + shan = lstar / bsdp elif E.is_supersingular(p): K = reg[0].parent() @@ -681,14 +680,14 @@ def an_padic(self, p, prec=0, use_twists=True): verbose("...putting things together") if bsdp[0] != 0: - shan0 = lstar[0]/bsdp[0] + shan0 = lstar[0] / bsdp[0] else: shan0 = 0 # this should actually never happen if bsdp[1] != 0: - shan1 = lstar[1]/bsdp[1] + shan1 = lstar[1] / bsdp[1] else: shan1 = 0 # this should conjecturally only happen when the rank is 0 - verbose("the two values for Sha : %s" % [shan0, shan1]) + verbose(f"the two values for Sha : {shan0}, {shan1}") # check consistency (the first two are only here to avoid a bug in the p-adic L-series # (namely the coefficients of zero-relative precision are treated as zero) @@ -747,14 +746,14 @@ def p_primary_order(self, p): E = self.E # does not work if p = 2 if p == 2: - raise ValueError("{} is not an odd prime".format(p)) - if (E.is_ordinary(p) and E.conductor() % p != 0 and + raise ValueError(f"{p} is not an odd prime") + if (E.is_ordinary(p) and E.conductor() % p and E.galois_representation().is_surjective(p)): N = E.conductor() fac = N.factor() # the auxiliary prime will be one dividing the conductor if all(E.tate_curve(ell).parameter().valuation() % p == 0 - for (ell, e) in fac if e == 1): + for ell, e in fac if e == 1): raise ValueError("The order is not provably known using Skinner-Urban.\n" + "Try running p_primary_bound to get a bound.") else: @@ -859,24 +858,21 @@ def p_primary_bound(self, p): if p == 2: raise ValueError("The prime p must be odd.") E = self.Emin - if E.is_ordinary(p) or E.is_good(p): - rho = E.galois_representation() - su = rho.is_surjective(p) - re = rho.is_reducible(p) - if not su and not re: - raise ValueError("The p-adic Galois representation is not surjective or reducible. Current knowledge about Euler systems does not provide an upper bound in this case. Try an_padic for a conjectural bound.") - shan = self.an_padic(p, prec=0, use_twists=True) - if shan == 0: - raise RuntimeError("There is a bug in an_padic.") - S = shan.valuation() - else: + if not (E.is_ordinary(p) or E.is_good(p)): raise ValueError("The curve has to have semi-stable reduction at p.") - - return S + rho = E.galois_representation() + su = rho.is_surjective(p) + re = rho.is_reducible(p) + if not su and not re: + raise ValueError("The p-adic Galois representation is not surjective or reducible. Current knowledge about Euler systems does not provide an upper bound in this case. Try an_padic for a conjectural bound.") + shan = self.an_padic(p, prec=0, use_twists=True) + if shan == 0: + raise RuntimeError("There is a bug in an_padic.") + return shan.valuation() def two_selmer_bound(self): r""" - This returns the 2-rank, i.e. the `\GF{2}`-dimension + Return the 2-rank, i.e. the `\GF{2}`-dimension of the 2-torsion part of `Sha`, provided we can determine the rank of `E`. @@ -905,9 +901,7 @@ def two_selmer_bound(self): r = E.rank() t = E.two_torsion_rank() b = S - r - t - if b < 0: - b = 0 - return b + return 0 if b < 0 else b def bound_kolyvagin(self, D=0, regulator=None, ignore_nonsurj_hypothesis=False): @@ -997,10 +991,10 @@ def bound_kolyvagin(self, D=0, regulator=None, L1_vanishes = E.lseries().L1_vanishes() if eps == 1 and L1_vanishes: return 0, 0 # rank even hence >= 2, so Kolyvagin gives nothing. - alpha = sqrt(abs(D)) / (2*E.period_lattice().complex_area()) + alpha = sqrt(abs(D)) / (2 * E.period_lattice().complex_area()) F = E.quadratic_twist(D) - k_E = 2*sqrt(E.conductor()) + 10 - k_F = 2*sqrt(F.conductor()) + 10 + k_E = 2 * sqrt(E.conductor()) + 10 + k_F = 2 * sqrt(F.conductor()) + 10 # k_E = 2 # k_F = 2 @@ -1020,17 +1014,17 @@ def bound_kolyvagin(self, D=0, regulator=None, err_F = max(err_F, MIN_ERR) err_E = max(err_E, MIN_ERR) if regulator is not None: - hZ = regulator/2 + hZ = regulator / 2 else: - hZ = F.regulator(use_database=True)/2 + hZ = F.regulator(use_database=True) / 2 I = RIF(alpha) * RIF(LE1-err_E, LE1+err_E) * RIF(LF1-err_F, LF1+err_F) / RIF(hZ) else: # E has odd rank if regulator is not None: - hZ = regulator/2 + hZ = regulator / 2 else: - hZ = E.regulator(use_database=True)/2 + hZ = E.regulator(use_database=True) / 2 LE1, err_E = E.lseries().deriv_at1(k_E) LF1, err_F = F.lseries().at1(k_F) err_F = max(err_F, MIN_ERR) @@ -1044,7 +1038,7 @@ def bound_kolyvagin(self, D=0, regulator=None, if t: break elif I.absolute_diameter() < 1: - raise RuntimeError("Problem in bound_kolyvagin; square of index is not an integer -- D=%s, I=%s." % (D, I)) + raise RuntimeError("Problem in bound_kolyvagin; square of index is not an integer -- D={}, I={}.".format(D, I)) verbose("Doubling bounds") k_E *= 2 k_F *= 2 @@ -1054,22 +1048,20 @@ def bound_kolyvagin(self, D=0, regulator=None, if n == 0: return 0, 0 # no bound B = [2] - for p, e in factor(n): + for p, e in n.factor(): if p > 2: if e % 2: - raise RuntimeError("Problem in bound_kolyvagin; square of index is not a perfect square! D=%s, I=%s, n=%s, e=%s." % (D, I, n, e)) + raise RuntimeError("Problem in bound_kolyvagin; square of index is not a perfect square! D={}, I={}, n={}, e={}.".format(D, I, n, e)) B.append(p) else: - n /= 2**e # replace n by its odd part + n >>= e # replace n by its odd part if not ignore_nonsurj_hypothesis: - for p in E.galois_representation().non_surjective(): - B.append(p) - B = sorted({int(x) for x in B}) - return B, n + B.extend(E.galois_representation().non_surjective()) + return sorted({int(x) for x in B}), n def bound_kato(self): r""" - Returns a list of primes `p` such that the theorems of Kato's [Kat2004]_ + Return a list of primes `p` such that the theorems of Kato's [Kat2004]_ and others (e.g., as explained in a thesis of Grigor Grigorov [Gri2005]_) imply that if `p` divides the order of `Sha(E/\QQ)` then `p` is in the list. @@ -1128,28 +1120,27 @@ def bound_kato(self): return False B = [2] rho = E.galois_representation() - for p in rho.non_surjective(): - if p > 2 and p not in rho.reducible_primes(): - B.append(p) - for p in E.conductor().prime_divisors(): - if E.has_additive_reduction(p) and p not in B: - B.append(p) + B.extend(p for p in rho.non_surjective() + if p > 2 and p not in rho.reducible_primes()) + B.extend(p for p in E.conductor().prime_divisors() + if E.has_additive_reduction(p)) # The only other p that might divide B are those that divide # the integer 2*#E(Q)_tor^2 * L(E,1)/omega. So we compute # that to sufficient precision to determine it. Note that # we have to assume the Manin constant is <=2 in order to provably # compute L(E,1)/omega. - for p, n in factor(self.an()): + for p, n in self.an().factor(): if n >= 2: # use parity of Sha B.append(int(p)) - B = sorted(set(B)) - return B + return sorted(set(B)) def bound(self): r""" Compute a provably correct bound on the order of the Tate-Shafarevich - group of this curve. The bound is either ``False`` (no bound) or a + group of this curve. + + The bound is either ``False`` (no bound) or a list ``B`` of primes such that any prime divisor of the order of `Sha` is in this list. @@ -1159,7 +1150,5 @@ def bound(self): ([2], 1) """ if self.Emin.lseries().L1_vanishes(): - B = self.bound_kolyvagin() - else: - B = self.bound_kato() - return B + return self.bound_kolyvagin() + return self.bound_kato() From 16535d33c1c24c558f132982c411a73c13492eea Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Wed, 6 Mar 2024 17:30:37 +0100 Subject: [PATCH 168/518] Modified quaternion algebra documentation - Gave more details on the reference [Voi2021] - Modified some docstrings --- src/doc/en/reference/references/index.rst | 3 +- .../algebras/quatalg/quaternion_algebra.py | 32 ++++++++----------- 2 files changed, 16 insertions(+), 19 deletions(-) diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index 09b78514e1c..b0194d1eef7 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -6360,7 +6360,8 @@ REFERENCES: .. [Voi2012] \J. Voight. Identifying the matrix ring: algorithms for quaternion algebras and quadratic forms, to appear. -.. [Voi2021] \J. Voight. Quaternion algebras, Springer Nature (2021). +.. [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 e85cf86846f..585312851d1 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -1809,13 +1809,12 @@ def discriminant(self): return (MatrixSpace(QQ, 4, 4)(L)).determinant().sqrt() - def is_maximal(self): + def is_maximal(self) -> bool: r""" Check whether the order of ``self`` is maximal in the ambient quaternion algebra. - Only works in quaternion algebras over number fields - - OUTPUT: Boolean + Only implemented for quaternion algebras over number fields; for reference, + see Theorem 15.5.5 in [Voi2021]_. EXAMPLES:: @@ -3277,14 +3276,12 @@ def cyclic_right_subideals(self, p, alpha=None): ans.append(J) return ans - def is_integral(self): + def is_integral(self) -> bool: 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]_. - - OUTPUT: a boolean. + Check 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]_. EXAMPLES:: @@ -3313,7 +3310,8 @@ def primitive_decomposition(self): Let `I` = ``self``. If `I` is an integral left `\mathcal{O}`-ideal return its decomposition as an equivalent primitive ideal and an integer such that their product is the initial ideal. - OUTPUTS: and quivalent primitive ideal to `I`, i.e. equivalent ideal not contained in `n\mathcal{O}` for any `n>0`, and the smallest integer such that `I \subset g\mathcal{O}`. + OUTPUTS: A primitive ideal equivalent to `I`, i.e. an equivalent ideal not contained + in `n\mathcal{O}` for any `n>0`, and the smallest integer `g` such that `I \subset g\mathcal{O}`. EXAMPLES:: @@ -3330,7 +3328,7 @@ def primitive_decomposition(self): TESTS: - Checks on random crafted ideals that they decompose as expected:: + Check that randomly generated ideals decompose as expected:: sage: for d in ( m for m in range(400, 750) if is_squarefree(m) ): ....: A = QuaternionAlgebra(d) @@ -3362,13 +3360,11 @@ def primitive_decomposition(self): return J, g - def is_primitive(self): + def is_primitive(self) -> bool: r""" Check if the quaternion fractional ideal is primitive. An integral left - $O$-ideal for some order $O$ is said primitive if for all integers $n > 1$ - $I$ is not contained in $nO$. - - OUTPUT: a boolean. + `\mathcal{O}`-ideal for some order `\mathcal{O}` is called primitive if + for all integers `n > 1` it is not contained in `n\mathcal{O}` EXAMPLES:: From b7a9614b3b77d3adbae8cb4b9fe9496b7959f25d Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Wed, 6 Mar 2024 17:17:29 +0100 Subject: [PATCH 169/518] Corrected code in examples --- .../rings/function_field/function_field.py | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/sage/rings/function_field/function_field.py b/src/sage/rings/function_field/function_field.py index 83ba61572c4..1486fedec98 100644 --- a/src/sage/rings/function_field/function_field.py +++ b/src/sage/rings/function_field/function_field.py @@ -1266,43 +1266,43 @@ def hilbert_symbol(self, a, b, P): sage: K. = FunctionField(GF(5)); R. = PolynomialRing(K) sage: f = (x^2 + 2*x + 2)*T^5 + (4*x^2 + 2*x + 3)*T^4 + 3*T^3 + 4*T^2 - + (2/(x^2 + 4*x + 1))*T + 3*x^2 + 2*x + 4 + + (2/(x^2 + 4*x + 1))*T + 3*x^2 + 2*x + 4 sage: L. = K.extension(f) sage: P = L.places_above(K.places()[0])[1] sage: a = ((3*x^3 + 2*x^2 + x + 1)/(x + 2))*y^4 + (4/(x + 4))*y^3 - + ((3*x + 4)/(x + 4))*y^2 - + ((x^4 + 2*x^3 + 3*x^2 + x + 2)/(x^4 + x^3 + x^2 + 2))*y - + (x^4 + x^2 + 2*x + 4)/(x^2 + 2*x + 2) + + ((3*x + 4)/(x + 4))*y^2 + + ((x^4 + 2*x^3 + 3*x^2 + x + 2)/(x^4 + x^3 + x^2 + 2))*y + + (x^4 + x^2 + 2*x + 4)/(x^2 + 2*x + 2) sage: b = ((x + 1)/(x + 4))*y^4 - + ((2*x^4 + 3*x^3 + 3*x^2 + 3)/(x^2 + 2*x + 2))*y^3 + (2/(x + 4))*y^2 - + ((4*x^4 + 4*x^3 + 4*x^2 + 2)/(x^4 + x^3 + x^2 + 2))*y - + (x^3 + x)/(x^3 + x^2 + x + 2) + + ((2*x^4 + 3*x^3 + 3*x^2 + 3)/(x^2 + 2*x + 2))*y^3 + (2/(x + 4))*y^2 + + ((4*x^4 + 4*x^3 + 4*x^2 + 2)/(x^4 + x^3 + x^2 + 2))*y + + (x^3 + x)/(x^3 + x^2 + x + 2) sage: L.hilbert_symbol(a, b, P) 1 sage: Q = L.places_above(K.places()[1])[0] sage: c = ((3*x^3 + 3*x^2 + 3*x + 3)/(x^3 + 3*x^2 + x))*y^4 - + ((3*x^4 + 2*x^3 + 2*x^2 + 4*x + 3)/(x^3 + 3*x^2 + x))*y^3 - + ((3*x + 4)/(x^3 + 3*x^2 + x))*y^2 - + ((4*x + 2)/(x^5 + 2*x^4 + 4*x^3 + 2*x^2 + x))*y - + (x^3 + 2*x^2 + 4)/(x^3 + 3*x^2 + x) + + ((3*x^4 + 2*x^3 + 2*x^2 + 4*x + 3)/(x^3 + 3*x^2 + x))*y^3 + + ((3*x + 4)/(x^3 + 3*x^2 + x))*y^2 + + ((4*x + 2)/(x^5 + 2*x^4 + 4*x^3 + 2*x^2 + x))*y + + (x^3 + 2*x^2 + 4)/(x^3 + 3*x^2 + x) sage: d = ((2*x^4 + x^2 + 2*x + 3)/(x^2 + 2*x + 2))*y^4 - + ((4*x^2 + 2)/(x^2 + 2*x + 2))*y^3 + ((3*x^2 + 3*x + 3)/(x^2 + 1))*y^2 - + ((x^6 + 4*x^5 + 4*x^4 + 3*x^3 + 3*x^2 + x + 3)/(x^4 + x^3 + x^2 + 2))*y - + (4*x^3 + 4*x^2 + 2*x + 2)/(x + 4) + + ((4*x^2 + 2)/(x^2 + 2*x + 2))*y^3 + ((3*x^2 + 3*x + 3)/(x^2 + 1))*y^2 + + ((x^6 + 4*x^5 + 4*x^4 + 3*x^3 + 3*x^2 + x + 3)/(x^4 + x^3 + x^2 + 2))*y + + (4*x^3 + 4*x^2 + 2*x + 2)/(x + 4) sage: L.hilbert_symbol(c, d, Q) -1 sage: K. = FunctionField(GF(3)); R. = PolynomialRing(K) sage: g = ((2*x + 1)/x)*T^5 + ((2*x + 1)/(x + 1))*T^4 + ((x^2 + 1)/x)*T^3 - + (2*x/(x^2 + 2*x + 2))*T^2 + 2*T + (2*x + 2)/(x + 2) + + (2*x/(x^2 + 2*x + 2))*T^2 + 2*T + (2*x + 2)/(x + 2) sage: L = K.extension(g) sage: P = L.places_above(K.places()[1])[1] sage: a = ((x + 2)/(x + 1))*y^4 + ((x^4 + 2*x^2 + 1)/(x^2 + 2*x))*y^3 - + ((x^5 + x^2 + 2*x + 1)/(x^4 + 2*x^3 + x^2 + x + 1))*y^2 - + ((2*x + 2)/(x + 2))*y + (2*x^4 + x^3 + x^2 + 2*x + 2)/(x^2 + x + 1) + + ((x^5 + x^2 + 2*x + 1)/(x^4 + 2*x^3 + x^2 + x + 1))*y^2 + + ((2*x + 2)/(x + 2))*y + (2*x^4 + x^3 + x^2 + 2*x + 2)/(x^2 + x + 1) sage: b = ((x^2 + x + 1)/(x^2 + 2*x + 1))*y^4 + ((2*x^2 + x + 1)/(x + 1))*y^3 - + ((x^5 + x^4 + 2*x^3 + x^2 + 2*x + 2)/(x^3 + x + 2))*y^2 - + (2*x/(x^2 + 2))*y + 2*x^2 + 2*x + 1 + + ((x^5 + x^4 + 2*x^3 + x^2 + 2*x + 2)/(x^3 + x + 2))*y^2 + + (2*x/(x^2 + 2))*y + 2*x^2 + 2*x + 1 sage: L.hilbert_symbol(a, b, P) -1 """ From 2da8affed70fec69dd3acaf367b65f770ed98cb1 Mon Sep 17 00:00:00 2001 From: "John H. Palmieri" Date: Tue, 5 Mar 2024 15:05:17 -0800 Subject: [PATCH 170/518] PR #37501: combine CODE_OF_CONDUCT.md and HANDLING_VIOLATIONS.md into a single file. --- CODE_OF_CONDUCT.md | 274 +++++++++++++++++++++++++++++++++++++++-- HANDLING_VIOLATIONS.md | 241 ------------------------------------ 2 files changed, 264 insertions(+), 251 deletions(-) delete mode 100644 HANDLING_VIOLATIONS.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 7bb46dea139..8c700d251b4 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,3 +1,13 @@ +Table of Contents +----------------- + +* [Code of Conduct](#code-of-conduct) +* [Guide for the Sage Code of Conduct Committee](#guide-for-the-sage-code-of-conduct-committee) + +This document has two parts: the Code of Conduct itself, followed by a +part describing the roles and procedures for the Sage Code of Conduct +Committee. + # Code of Conduct # This Code was approved by the Sage community by a vote which ended on @@ -156,7 +166,8 @@ goal will be to respond within 72 hours. Potential consequences for violating the Sage Code of Conduct include: -- Nothing (if the committee determines that no violation occurred) +- Nothing (for example if the matter has been resolved publicly while + the committee was considering responses) - Private feedback or reprimand to the individual(s) involved - Warning the person to cease their behavior and that any further reports will result in sanctions @@ -189,18 +200,261 @@ Google groups: - [Content policy](https://support.google.com/groups/answer/4561696) - [Reporting procedures](https://support.google.com/groups/answer/81275) -## Amending this document ## +## Amending the Code of Conduct ## + +The first part of this document may be amended by a vote of the Sage +community in the sage-devel Google group, with the exception of facts +like the membership of the Sage Code of Conduct Committee, changes to +URLs, or changes to email addresses: changes like that can be done via +a normal pull request. Any pull requests involving this document +should list the committee members as reviewers. + + + +# Guide for the Sage Code of Conduct Committee # + +## Introduction ## + +This is the manual followed by the Sage Code of Conduct Committee. It is used +when we respond to an issue to make sure we’re consistent and fair. + +Enforcing the Code of Conduct impacts our community today and for the +future. It’s an action that we do not take lightly. When reviewing +enforcement measures, the Sage Code of Conduct Committee will keep the +following values and guidelines in mind: + +- Act in a personal manner rather than impersonal. The committee can + engage the parties to understand the situation, while respecting the + privacy and any necessary confidentiality of reporters. However, + it is sometimes necessary to communicate with one or more + individuals directly: the committee’s goal is to improve the health + of our community rather than only produce a formal decision. + +- Emphasize empathy for individuals rather than judging behavior, + avoiding binary labels of “good” and “bad/evil”. Overt, clear-cut + aggression and harassment exists and we will address that + firmly. But many scenarios that can prove challenging to resolve are + those where normal disagreements devolve into unhelpful or harmful + behavior from multiple parties. Understanding the full context and + finding a path that re-engages all is hard, but ultimately the most + productive for our community. + +- We understand that email is a difficult medium and can be + isolating. Receiving criticism over email, without personal contact, + can be particularly painful. This makes it especially important to + keep an atmosphere of open-minded respect of the views of others. It + also means that we must be transparent in our actions, and that we + will do everything in our power to make sure that all our members + are treated fairly and with sympathy. + +- Discrimination can be subtle and it can be unconscious. It can show + itself as unfairness and hostility in otherwise ordinary + interactions. We know that this does occur, and we will take care to + look out for it. We would very much like to hear from you if you + feel you have been treated unfairly, and we will use these + procedures to make sure that your complaint is heard and addressed. + +- Help increase engagement in good discussion practice: try to + identify where discussion may have broken down and provide + actionable information, pointers and resources that can lead to + positive change on these points. + +- Be mindful of the needs of new members: provide them with explicit + support and consideration, with the aim of increasing participation + from underrepresented groups in particular. + +Individuals come from different cultural backgrounds and native +languages. Try to identify any honest misunderstandings caused by a +non-native speaker and help them understand the issue and what they +can change to avoid causing offense. Complex discussion in a foreign +language can be very intimidating, and we want to grow our diversity +also across nationalities and cultures. + +Mediation: voluntary, informal mediation is a tool at our disposal. In +some contexts, such as when two or more parties have escalated to the +point of inappropriate behavior (something sadly common in human +conflict), it may be useful to facilitate a mediation process. This is +only an example: the committee can consider mediation in any case, +mindful that the process is meant to be strictly voluntary and no +party can be pressured to participate. If the committee suggests +mediation, it should: + +- Find a candidate who can serve as a mediator. + +- Obtain the agreement of the reporter(s). The reporter(s) have + complete freedom to decline the mediation idea, or to propose an + alternate mediator. + +- Obtain the agreement of the reported person(s). + +- Settle on the mediator: while parties can propose a different + mediator than the suggested candidate, only if common agreement is + reached on all terms can the process move forward. + +- Establish a timeline for mediation to complete, ideally within two weeks. + +The mediator will engage with all the parties and seek a resolution +that is satisfactory to all. Upon completion, the mediator will +provide a report (vetted by all parties to the process) to the +committee, with recommendations on further steps. The committee will +then evaluate these results (whether satisfactory resolution was +achieved or not) and decide on any additional action deemed necessary. + +## How the committee will respond to reports ## + +When the committee (or a committee member) receives a report, they +will first determine whether the report is about a clear and severe +breach (as defined below). If so, immediate action needs to be taken +in addition to the regular report-handling process. + +### Clear and severe breach actions ### + +We know that it is painfully common for internet communication to +start at or devolve into obvious and flagrant abuse. We will deal +quickly with clear and severe breaches like personal threats, violent, +sexist, or racist language. + +When a member of the Sage Code of Conduct Committee becomes aware of a +clear and severe breach, they will do the following: + +- Immediately disconnect the originator from all Sage communication channels. + +- Reply to the reporter that their report has been received and that + the originator has been disconnected. + +- In every case, the moderator should make a reasonable effort to + contact the originator, and tell them specifically how their + language or actions qualify as a “clear and severe breach”. The + moderator should also say that, if the originator believes this is + unfair or they want to be reconnected to Sage, they have the right + to ask for a review, as below, by the Sage Code of Conduct Committee. The + moderator should copy this explanation to the Sage Code of Conduct + Committee. + +The Sage Code of Conduct Committee will formally review and sign off on all +cases where this mechanism has been applied to make sure it is not +being used to control ordinary heated disagreement. + +### Report handling ### + +When a report is sent to the committee, they will immediately reply to +the reporter to confirm receipt. This reply must be sent within 72 +hours, and the group should strive to respond much quicker than that. + +If a report doesn’t contain enough information, the committee will +obtain all relevant data before acting. The committee is empowered to +contact any individuals involved to get a more complete account of +events. + +The committee will then review the incident and determine, to the best of their ability: + +- What happened. + +- Whether this event constitutes a Code of Conduct violation. + +- Who are the responsible party/parties. + +- Whether this is an ongoing situation, and whether there is a threat to anyone’s physical safety. + +This information will be collected in writing, and whenever possible +the group’s deliberations will be recorded and retained (i.e., chat +transcripts, email discussions, recorded conference calls, summaries +of voice conversations, etc.). + +It is important to retain an archive of all activities of this +committee to ensure consistency in behavior and provide institutional +memory for the project. To assist in this, the default channel of +discussion for this committee will be a private mailing list +accessible to current and future members of the committee. If the +committee finds the need to use off-list communications (e.g., phone +calls for early/rapid response), it should, in all cases, summarize +these back to the list so there’s a good record of the process. + +The Sage Code of Conduct Committee should aim to have a resolution agreed upon +within two weeks. In the event that a resolution can’t be determined +in that time, the committee will respond to the reporter(s) with an +update and projected timeline for the resolution. + +## Resolutions ## + +The committee must agree on a resolution by consensus. If the group +cannot reach consensus and deadlocks for over a week, the committee is +empowered to consult as needed to try to reach consensus. + +Possible responses may include: + +- Taking no further action: + + - if we determine no violations have occurred. + + - if the matter has been resolved publicly while the committee was considering responses. + +- Coordinating voluntary mediation: if all involved parties agree, the + committee may facilitate a mediation process as detailed above. + +- Remind publicly, and point out that some behavior/actions/language + have been judged inappropriate and why in the current context, or + can but hurtful to some people, requesting the community to + self-adjust. + +- A private reprimand from the committee to the individual(s) + involved. In this case, a representative of the committee will + deliver that reprimand to the individual(s) over email, cc’ing the + group. + +- A public reprimand. In this case, a committee representative will deliver + that reprimand in the same venue that the violation occurred, within + the limits of practicality. E.g., the original mailing list for an + email violation, but for a chat room discussion where the + person/context may be gone, they can be reached by other means. The + group may choose to publish this message elsewhere for documentation + purposes. + +- A request for a public or private apology, assuming the reporter + agrees to this idea: they may, at their discretion, refuse further + contact with the violator. A committee representative will deliver + this request. The committee may, if it chooses, attach “strings” to + this request: for example, the group may ask a violator to + apologize, in order to retain one’s membership on a mailing list. + +- A “mutually agreed upon hiatus” where the committee asks the + individual to temporarily refrain from community participation. If + the individual chooses not to take a temporary break voluntarily, + the committee may issue a “mandatory cooling off period”. + +- A permanent or temporary ban from some or all Sage spaces (mailing + lists, GitHub, etc.). The group will maintain records of all such + bans so that they may be reviewed in the future or otherwise + maintained. + +Once a resolution is agreed upon, the committee will contact the +original reporter and any other affected parties and explain that the +committee has taken action. Depending on the situation, the committee +may or may not choose to provide further details about what actions +were taken and how they might affect the reporter. + +The committee will never publicly discuss the issue; all public +statements will be made by a representative of the Sage Code of Conduct +Committee. + +## Conflicts of interest ## + +In the event of any conflict of interest, a committee member must +immediately notify the other members, and recuse themselves if +necessary. + +## Amending the Code of Conduct Committee manual ## -This document may be amended by a vote of the Sage community in the -sage-devel Google group, with the exception of facts like the -membership of the Sage Code of Conduct Committee, changes to URLs, or -changes to email addresses: changes like that can be done via a normal -pull request. Any pull requests involving this document should list -the committee members as reviewers. +This part of the document may be amended by a vote of the Sage Code of +Conduct Committee. ## Credits ## -Portions of this are adapted from the [SciPy code of +Portions of the first part (the code itself) are adapted from the +[SciPy code of conduct](https://docs.scipy.org/doc/scipy/dev/conduct/code_of_conduct.html) and the [NumFOCUS code of -conduct](https://numfocus.org/code-of-conduct). +conduct](https://numfocus.org/code-of-conduct). The second part (the +guide for the Sage Code of Conduct Committee) is largely adapted from +the [SciPy report handling +manual](https://docs.scipy.org/doc/scipy/dev/conduct/report_handling_manual.html). diff --git a/HANDLING_VIOLATIONS.md b/HANDLING_VIOLATIONS.md deleted file mode 100644 index 6f0e72cf161..00000000000 --- a/HANDLING_VIOLATIONS.md +++ /dev/null @@ -1,241 +0,0 @@ -# Guide for handling violations to Sage's Code of Conduct # - -## Introduction ## - -This is the manual followed by the Sage Code of Conduct Committee. It is used -when we respond to an issue to make sure we’re consistent and fair. - -Enforcing the Code of Conduct impacts our community today and for the -future. It’s an action that we do not take lightly. When reviewing -enforcement measures, the Sage Code of Conduct Committee will keep the -following values and guidelines in mind: - -- Act in a personal manner rather than impersonal. The committee can - engage the parties to understand the situation, while respecting the - privacy and any necessary confidentiality of reporters. However, - it is sometimes necessary to communicate with one or more - individuals directly: the committee’s goal is to improve the health - of our community rather than only produce a formal decision. - -- Emphasize empathy for individuals rather than judging behavior, - avoiding binary labels of “good” and “bad/evil”. Overt, clear-cut - aggression and harassment exists and we will address that - firmly. But many scenarios that can prove challenging to resolve are - those where normal disagreements devolve into unhelpful or harmful - behavior from multiple parties. Understanding the full context and - finding a path that re-engages all is hard, but ultimately the most - productive for our community. - -- We understand that email is a difficult medium and can be - isolating. Receiving criticism over email, without personal contact, - can be particularly painful. This makes it especially important to - keep an atmosphere of open-minded respect of the views of others. It - also means that we must be transparent in our actions, and that we - will do everything in our power to make sure that all our members - are treated fairly and with sympathy. - -- Discrimination can be subtle and it can be unconscious. It can show - itself as unfairness and hostility in otherwise ordinary - interactions. We know that this does occur, and we will take care to - look out for it. We would very much like to hear from you if you - feel you have been treated unfairly, and we will use these - procedures to make sure that your complaint is heard and addressed. - -- Help increase engagement in good discussion practice: try to - identify where discussion may have broken down and provide - actionable information, pointers and resources that can lead to - positive change on these points. - -- Be mindful of the needs of new members: provide them with explicit - support and consideration, with the aim of increasing participation - from underrepresented groups in particular. - -Individuals come from different cultural backgrounds and native -languages. Try to identify any honest misunderstandings caused by a -non-native speaker and help them understand the issue and what they -can change to avoid causing offense. Complex discussion in a foreign -language can be very intimidating, and we want to grow our diversity -also across nationalities and cultures. - -Mediation: voluntary, informal mediation is a tool at our disposal. In -some contexts, such as when two or more parties have escalated to the -point of inappropriate behavior (something sadly common in human -conflict), it may be useful to facilitate a mediation process. This is -only an example: the committee can consider mediation in any case, -mindful that the process is meant to be strictly voluntary and no -party can be pressured to participate. If the committee suggests -mediation, it should: - -- Find a candidate who can serve as a mediator. - -- Obtain the agreement of the reporter(s). The reporter(s) have - complete freedom to decline the mediation idea, or to propose an - alternate mediator. - -- Obtain the agreement of the reported person(s). - -- Settle on the mediator: while parties can propose a different - mediator than the suggested candidate, only if common agreement is - reached on all terms can the process move forward. - -- Establish a timeline for mediation to complete, ideally within two weeks. - -The mediator will engage with all the parties and seek a resolution -that is satisfactory to all. Upon completion, the mediator will -provide a report (vetted by all parties to the process) to the -committee, with recommendations on further steps. The committee will -then evaluate these results (whether satisfactory resolution was -achieved or not) and decide on any additional action deemed necessary. - -## How the committee will respond to reports ## - -When the committee (or a committee member) receives a report, they -will first determine whether the report is about a clear and severe -breach (as defined below). If so, immediate action needs to be taken -in addition to the regular report-handling process. - -### Clear and severe breach actions ### - -We know that it is painfully common for internet communication to -start at or devolve into obvious and flagrant abuse. We will deal -quickly with clear and severe breaches like personal threats, violent, -sexist, or racist language. - -When a member of the Sage Code of Conduct Committee becomes aware of a -clear and severe breach, they will do the following: - -- Immediately disconnect the originator from all Sage communication channels. - -- Reply to the reporter that their report has been received and that - the originator has been disconnected. - -- In every case, the moderator should make a reasonable effort to - contact the originator, and tell them specifically how their - language or actions qualify as a “clear and severe breach”. The - moderator should also say that, if the originator believes this is - unfair or they want to be reconnected to Sage, they have the right - to ask for a review, as below, by the Sage Code of Conduct Committee. The - moderator should copy this explanation to the Sage Code of Conduct - Committee. - -The Sage Code of Conduct Committee will formally review and sign off on all -cases where this mechanism has been applied to make sure it is not -being used to control ordinary heated disagreement. - -### Report handling ### - -When a report is sent to the committee, they will immediately reply to -the reporter to confirm receipt. This reply must be sent within 72 -hours, and the group should strive to respond much quicker than that. - -If a report doesn’t contain enough information, the committee will -obtain all relevant data before acting. The committee is empowered to -contact any individuals involved to get a more complete account of -events. - -The committee will then review the incident and determine, to the best of their ability: - -- What happened. - -- Whether this event constitutes a Code of Conduct violation. - -- Who are the responsible party/parties. - -- Whether this is an ongoing situation, and whether there is a threat to anyone’s physical safety. - -This information will be collected in writing, and whenever possible -the group’s deliberations will be recorded and retained (i.e., chat -transcripts, email discussions, recorded conference calls, summaries -of voice conversations, etc.). - -It is important to retain an archive of all activities of this -committee to ensure consistency in behavior and provide institutional -memory for the project. To assist in this, the default channel of -discussion for this committee will be a private mailing list -accessible to current and future members of the committee. If the -committee finds the need to use off-list communications (e.g., phone -calls for early/rapid response), it should, in all cases, summarize -these back to the list so there’s a good record of the process. - -The Sage Code of Conduct Committee should aim to have a resolution agreed upon -within two weeks. In the event that a resolution can’t be determined -in that time, the committee will respond to the reporter(s) with an -update and projected timeline for the resolution. - -## Resolutions ## - -The committee must agree on a resolution by consensus. If the group -cannot reach consensus and deadlocks for over a week, the committee is -empowered to consult as needed to try to reach consensus. - -Possible responses may include: - -- Taking no further action: - - - if we determine no violations have occurred. - - - if the matter has been resolved publicly while the committee was considering responses. - -- Coordinating voluntary mediation: if all involved parties agree, the - committee may facilitate a mediation process as detailed above. - -- Remind publicly, and point out that some behavior/actions/language - have been judged inappropriate and why in the current context, or - can but hurtful to some people, requesting the community to - self-adjust. - -- A private reprimand from the committee to the individual(s) - involved. In this case, a representative of the committee will - deliver that reprimand to the individual(s) over email, cc’ing the - group. - -- A public reprimand. In this case, a committee representative will deliver - that reprimand in the same venue that the violation occurred, within - the limits of practicality. E.g., the original mailing list for an - email violation, but for a chat room discussion where the - person/context may be gone, they can be reached by other means. The - group may choose to publish this message elsewhere for documentation - purposes. - -- A request for a public or private apology, assuming the reporter - agrees to this idea: they may, at their discretion, refuse further - contact with the violator. A committee representative will deliver - this request. The committee may, if it chooses, attach “strings” to - this request: for example, the group may ask a violator to - apologize, in order to retain one’s membership on a mailing list. - -- A “mutually agreed upon hiatus” where the committee asks the - individual to temporarily refrain from community participation. If - the individual chooses not to take a temporary break voluntarily, - the committee may issue a “mandatory cooling off period”. - -- A permanent or temporary ban from some or all Sage spaces (mailing - lists, GitHub, etc.). The group will maintain records of all such - bans so that they may be reviewed in the future or otherwise - maintained. - -Once a resolution is agreed upon, the committee will contact the -original reporter and any other affected parties and explain that the -committee has taken action. Depending on the situation, the committee -may or may not choose to provide further details about what actions -were taken and how they might affect the reporter. - -The committee will never publicly discuss the issue; all public -statements will be made by a representative of the Sage Code of Conduct -Committee. - -## Conflicts of interest ## - -In the event of any conflict of interest, a committee member must -immediately notify the other members, and recuse themselves if -necessary. - -## Amending this document ## - -This document may be amended by a vote of the Sage Code of Conduct -Committee. - -## Credits ## - -This is largely adapted from the [SciPy report handling -manual](https://docs.scipy.org/doc/scipy/dev/conduct/report_handling_manual.html). From 02c46a94009c598cebb1d1b4ced677b3b8ee2397 Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Wed, 6 Mar 2024 23:55:45 +0100 Subject: [PATCH 171/518] Small optimization and docstring modifications --- .../rings/function_field/function_field.py | 67 +++++++++---------- 1 file changed, 32 insertions(+), 35 deletions(-) diff --git a/src/sage/rings/function_field/function_field.py b/src/sage/rings/function_field/function_field.py index 1486fedec98..1ea0fb4820f 100644 --- a/src/sage/rings/function_field/function_field.py +++ b/src/sage/rings/function_field/function_field.py @@ -1265,44 +1265,44 @@ def hilbert_symbol(self, a, b, P): 1 sage: K. = FunctionField(GF(5)); R. = PolynomialRing(K) - sage: f = (x^2 + 2*x + 2)*T^5 + (4*x^2 + 2*x + 3)*T^4 + 3*T^3 + 4*T^2 - + (2/(x^2 + 4*x + 1))*T + 3*x^2 + 2*x + 4 + sage: f = ((x^2 + 2*x + 2)*T^5 + (4*x^2 + 2*x + 3)*T^4 + 3*T^3 + 4*T^2 + + (2/(x^2 + 4*x + 1))*T + 3*x^2 + 2*x + 4) sage: L. = K.extension(f) sage: P = L.places_above(K.places()[0])[1] - sage: a = ((3*x^3 + 2*x^2 + x + 1)/(x + 2))*y^4 + (4/(x + 4))*y^3 - + ((3*x + 4)/(x + 4))*y^2 - + ((x^4 + 2*x^3 + 3*x^2 + x + 2)/(x^4 + x^3 + x^2 + 2))*y - + (x^4 + x^2 + 2*x + 4)/(x^2 + 2*x + 2) - sage: b = ((x + 1)/(x + 4))*y^4 - + ((2*x^4 + 3*x^3 + 3*x^2 + 3)/(x^2 + 2*x + 2))*y^3 + (2/(x + 4))*y^2 - + ((4*x^4 + 4*x^3 + 4*x^2 + 2)/(x^4 + x^3 + x^2 + 2))*y - + (x^3 + x)/(x^3 + x^2 + x + 2) + sage: a = (((3*x^3 + 2*x^2 + x + 1)/(x + 2))*y^4 + (4/(x + 4))*y^3 + + ((3*x + 4)/(x + 4))*y^2 + + ((x^4 + 2*x^3 + 3*x^2 + x + 2)/(x^4 + x^3 + x^2 + 2))*y + + (x^4 + x^2 + 2*x + 4)/(x^2 + 2*x + 2)) + sage: b = (((x + 1)/(x + 4))*y^4 + + ((2*x^4 + 3*x^3 + 3*x^2 + 3)/(x^2 + 2*x + 2))*y^3 + (2/(x + 4))*y^2 + + ((4*x^4 + 4*x^3 + 4*x^2 + 2)/(x^4 + x^3 + x^2 + 2))*y + + (x^3 + x)/(x^3 + x^2 + x + 2)) sage: L.hilbert_symbol(a, b, P) 1 sage: Q = L.places_above(K.places()[1])[0] - sage: c = ((3*x^3 + 3*x^2 + 3*x + 3)/(x^3 + 3*x^2 + x))*y^4 - + ((3*x^4 + 2*x^3 + 2*x^2 + 4*x + 3)/(x^3 + 3*x^2 + x))*y^3 - + ((3*x + 4)/(x^3 + 3*x^2 + x))*y^2 - + ((4*x + 2)/(x^5 + 2*x^4 + 4*x^3 + 2*x^2 + x))*y - + (x^3 + 2*x^2 + 4)/(x^3 + 3*x^2 + x) - sage: d = ((2*x^4 + x^2 + 2*x + 3)/(x^2 + 2*x + 2))*y^4 - + ((4*x^2 + 2)/(x^2 + 2*x + 2))*y^3 + ((3*x^2 + 3*x + 3)/(x^2 + 1))*y^2 - + ((x^6 + 4*x^5 + 4*x^4 + 3*x^3 + 3*x^2 + x + 3)/(x^4 + x^3 + x^2 + 2))*y - + (4*x^3 + 4*x^2 + 2*x + 2)/(x + 4) + sage: c = (((3*x^3 + 3*x^2 + 3*x + 3)/(x^3 + 3*x^2 + x))*y^4 + + ((3*x^4 + 2*x^3 + 2*x^2 + 4*x + 3)/(x^3 + 3*x^2 + x))*y^3 + + ((3*x + 4)/(x^3 + 3*x^2 + x))*y^2 + + ((4*x + 2)/(x^5 + 2*x^4 + 4*x^3 + 2*x^2 + x))*y + + (x^3 + 2*x^2 + 4)/(x^3 + 3*x^2 + x)) + sage: d = (((2*x^4 + x^2 + 2*x + 3)/(x^2 + 2*x + 2))*y^4 + + ((4*x^2 + 2)/(x^2 + 2*x + 2))*y^3 + ((3*x^2 + 3*x + 3)/(x^2 + 1))*y^2 + + ((x^6 + 4*x^5 + 4*x^4 + 3*x^3 + 3*x^2 + x + 3)/(x^4 + x^3 + x^2 + 2))*y + + (4*x^3 + 4*x^2 + 2*x + 2)/(x + 4)) sage: L.hilbert_symbol(c, d, Q) -1 sage: K. = FunctionField(GF(3)); R. = PolynomialRing(K) - sage: g = ((2*x + 1)/x)*T^5 + ((2*x + 1)/(x + 1))*T^4 + ((x^2 + 1)/x)*T^3 - + (2*x/(x^2 + 2*x + 2))*T^2 + 2*T + (2*x + 2)/(x + 2) + sage: g = (((2*x + 1)/x)*T^5 + ((2*x + 1)/(x + 1))*T^4 + ((x^2 + 1)/x)*T^3 + + (2*x/(x^2 + 2*x + 2))*T^2 + 2*T + (2*x + 2)/(x + 2)) sage: L = K.extension(g) sage: P = L.places_above(K.places()[1])[1] - sage: a = ((x + 2)/(x + 1))*y^4 + ((x^4 + 2*x^2 + 1)/(x^2 + 2*x))*y^3 - + ((x^5 + x^2 + 2*x + 1)/(x^4 + 2*x^3 + x^2 + x + 1))*y^2 - + ((2*x + 2)/(x + 2))*y + (2*x^4 + x^3 + x^2 + 2*x + 2)/(x^2 + x + 1) - sage: b = ((x^2 + x + 1)/(x^2 + 2*x + 1))*y^4 + ((2*x^2 + x + 1)/(x + 1))*y^3 - + ((x^5 + x^4 + 2*x^3 + x^2 + 2*x + 2)/(x^3 + x + 2))*y^2 - + (2*x/(x^2 + 2))*y + 2*x^2 + 2*x + 1 + sage: a = (((x + 2)/(x + 1))*y^4 + ((x^4 + 2*x^2 + 1)/(x^2 + 2*x))*y^3 + + ((x^5 + x^2 + 2*x + 1)/(x^4 + 2*x^3 + x^2 + x + 1))*y^2 + + ((2*x + 2)/(x + 2))*y + (2*x^4 + x^3 + x^2 + 2*x + 2)/(x^2 + x + 1)) + sage: b = (((x^2 + x + 1)/(x^2 + 2*x + 1))*y^4 + ((2*x^2 + x + 1)/(x + 1))*y^3 + + ((x^5 + x^4 + 2*x^3 + x^2 + 2*x + 2)/(x^3 + x + 2))*y^2 + + (2*x/(x^2 + 2))*y + 2*x^2 + 2*x + 1) sage: L.hilbert_symbol(a, b, P) -1 """ @@ -1319,18 +1319,18 @@ def hilbert_symbol(self, a, b, P): raise ValueError('the invariants a and b must be nonzero') # Compute the completion map to precision 1 for computation of the - # valuations v(a), v(b) as well as the elements a_0, b_0 + # valuations v(a), v(b) as well as the elements a0, b0 try: sigma = self.completion(P, prec=1, gen_name='i') except AttributeError: raise ValueError('P must be a place of the function field F') - # Apply the completion map to a to get v(a) and a_0 + # Apply the completion map to a to get v(a) and a0 ser_a = sigma(a) v_a = ser_a.valuation() a0 = ser_a.coefficients()[0] - # Apply the completion map to b to get v(b) and b_0 + # Apply the completion map to b to get v(b) and b0 ser_b = sigma(b) v_b = ser_b.valuation() b0 = ser_b.coefficients()[0] @@ -1339,17 +1339,14 @@ def hilbert_symbol(self, a, b, P): k, _, tau = self.residue_field(P) e = (k.order()-1)/2 - # Use Euler's criterion to compute the power of Legendre symbols + # Use Euler's criterion to compute the powers of Legendre symbols a_rd_pw = tau(a0)**(v_b * e) b_rd_pw = tau(b0)**(v_a * e) # Finally, put the result together res = k(-1)**(v_a * v_b * e) * a_rd_pw * b_rd_pw - if res == k(1): - return ZZ(1) - else: - return ZZ(-1) + return ZZ(1) if res == k(1) else ZZ(-1) def extension_constant_field(self, k): """ From 36827d866909714e3c7a4a1cb52c99c810adf8c9 Mon Sep 17 00:00:00 2001 From: Henry Ehrhard Date: Wed, 6 Mar 2024 15:00:21 -0800 Subject: [PATCH 172/518] Implement suggested code edits --- src/sage/graphs/graph.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/sage/graphs/graph.py b/src/sage/graphs/graph.py index a2303323b3c..a078a5ea859 100644 --- a/src/sage/graphs/graph.py +++ b/src/sage/graphs/graph.py @@ -3977,6 +3977,7 @@ def chromatic_symmetric_function(self, R=None): if R is None: R = ZZ p = SymmetricFunctions(R).p() + Par = p.basis().keys() # Dict to store parent of each vertex in disjoint-set forest # representing components of current induced subgraph. @@ -3993,8 +3994,8 @@ def summand(queue, dsf, sizes): # Compute powersum terms obtained by adding each subset of # edges in queue to current subgraph. if not queue: - return p(sorted([sizes[v] for v in sizes if dsf[v] is None], - reverse=True)) + root_sizes = [s for v, s in sizes.items() if dsf[v] is None] + return p.monomial(Par(sorted(root_sizes, reverse=True))) else: ret = p.zero() e = queue.pop() @@ -4011,7 +4012,7 @@ def summand(queue, dsf, sizes): queue.append(e) return ret - return summand(list(self.edges()), dsf, sizes) + return summand(list(self.edges(labels=False)), dsf, sizes) @doc_index("Coloring") def chromatic_quasisymmetric_function(self, t=None, R=None): From d8f3133fd25c6f0a07cc7b60a004fea2fed207ac Mon Sep 17 00:00:00 2001 From: Henry Ehrhard Date: Wed, 6 Mar 2024 15:44:30 -0800 Subject: [PATCH 173/518] Fix variable name --- src/sage/graphs/graph.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/sage/graphs/graph.py b/src/sage/graphs/graph.py index a078a5ea859..fb494381a17 100644 --- a/src/sage/graphs/graph.py +++ b/src/sage/graphs/graph.py @@ -3987,29 +3987,29 @@ def chromatic_symmetric_function(self, R=None): sizes = {v: 1 for v in self.vertices()} def find(dsf, v): - # Find root of tree in disjoint-set forest + # Find root of tree in disjoint-set forest. return v if dsf[v] is None else find(dsf, dsf[v]) - def summand(queue, dsf, sizes): + def summand(stack, dsf, sizes): # Compute powersum terms obtained by adding each subset of - # edges in queue to current subgraph. - if not queue: + # edges in stack to current subgraph. + if not stack: root_sizes = [s for v, s in sizes.items() if dsf[v] is None] return p.monomial(Par(sorted(root_sizes, reverse=True))) else: ret = p.zero() - e = queue.pop() + e = stack.pop() u = find(dsf, e[0]) v = find(dsf, e[1]) # Terms cancel if edge creates a cycle. if u is not v: - ret = summand(queue, dsf, sizes) + ret = summand(stack, dsf, sizes) dsf[v] = u sizes[u] += sizes[v] - ret -= summand(queue, dsf, sizes) + ret -= summand(stack, dsf, sizes) dsf[v] = None sizes[u] -= sizes[v] - queue.append(e) + stack.append(e) return ret return summand(list(self.edges(labels=False)), dsf, sizes) From a818d2e533925784bed53144d340ca55fa47e774 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Thu, 7 Mar 2024 08:14:52 +0100 Subject: [PATCH 174/518] fix suggested details --- src/sage/schemes/elliptic_curves/sha_tate.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/sage/schemes/elliptic_curves/sha_tate.py b/src/sage/schemes/elliptic_curves/sha_tate.py index c6aab99de40..06f00074ff5 100644 --- a/src/sage/schemes/elliptic_curves/sha_tate.py +++ b/src/sage/schemes/elliptic_curves/sha_tate.py @@ -727,7 +727,7 @@ def p_primary_order(self, p): - `e` -- a non-negative integer such that `p^e` is the order of the `p`-primary order if the conditions are satisfied - and raises a ``ValueError`` otherwise. + and raises a :class:`ValueError` otherwise. EXAMPLES:: @@ -1140,9 +1140,8 @@ def bound(self): Compute a provably correct bound on the order of the Tate-Shafarevich group of this curve. - The bound is either ``False`` (no bound) or a - list ``B`` of primes such that any prime divisor of the order of `Sha` - is in this list. + The bound is either ``False`` (no bound) or a list ``B`` of primes + such that any prime divisor of the order of `Sha` is in this list. EXAMPLES:: From 767b2d74bfb76d84d112f513e500a2210171c269 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Thu, 7 Mar 2024 10:57:50 +0100 Subject: [PATCH 175/518] enrich the test_karatsuba failure message with explicit elements --- src/sage/rings/tests.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/sage/rings/tests.py b/src/sage/rings/tests.py index 3eed7e1aa31..7d0d2f1f90b 100644 --- a/src/sage/rings/tests.py +++ b/src/sage/rings/tests.py @@ -416,8 +416,9 @@ def test_random_arith(level=MAX_LEVEL, trials=1): @random_testing def test_karatsuba_multiplication(base_ring, maxdeg1, maxdeg2, - ref_mul=lambda f, g: f._mul_generic(g), base_ring_random_elt_args=[], - numtests=10, verbose=False): + ref_mul=lambda f, g: f._mul_generic(g), + base_ring_random_elt_args=[], + numtests=10, verbose=False): """ Test univariate Karatsuba multiplication against other multiplication algorithms. @@ -469,16 +470,20 @@ def test_karatsuba_multiplication(base_ring, maxdeg1, maxdeg2, """ from sage.misc.prandom import randint + from sage.misc.sage_input import sage_input from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing threshold = randint(0, min(maxdeg1, maxdeg2)) R = PolynomialRing(base_ring, 'x') if verbose: - print("test_karatsuba_multiplication: ring={}, threshold={}".format(R, threshold)) + print(f"test_karatsuba_multiplication: ring={R}, threshold={threshold}") for i in range(numtests): f = R.random_element(randint(0, maxdeg1), *base_ring_random_elt_args) g = R.random_element(randint(0, maxdeg2), *base_ring_random_elt_args) if verbose: print(" ({})*({})".format(f, g)) if ref_mul(f, g) - f._mul_karatsuba(g, threshold) != 0: - raise ValueError("Multiplication failed") + msg = "Multiplication failed for elements defined by:\n" + msg += f"f = {sage_input(f)}\n" + msg += f"g = {sage_input(g)}" + raise ValueError(msg) return From 2525402500b2432bccd8819058c28ed68cc80ecb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Thu, 7 Mar 2024 11:00:31 +0100 Subject: [PATCH 176/518] tweak message --- src/sage/rings/tests.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/sage/rings/tests.py b/src/sage/rings/tests.py index 7d0d2f1f90b..ad57abb5d16 100644 --- a/src/sage/rings/tests.py +++ b/src/sage/rings/tests.py @@ -482,8 +482,9 @@ def test_karatsuba_multiplication(base_ring, maxdeg1, maxdeg2, if verbose: print(" ({})*({})".format(f, g)) if ref_mul(f, g) - f._mul_karatsuba(g, threshold) != 0: - msg = "Multiplication failed for elements defined by:\n" - msg += f"f = {sage_input(f)}\n" - msg += f"g = {sage_input(g)}" + msg = "Multiplication failed for elements defined by\n" + msg += f"{sage_input(f)}\n" + msg += "and\n" + msg += f"{sage_input(g)}" raise ValueError(msg) return From 6f87c77bb49b0ef6d685bba0f1f1e824870339eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Thu, 7 Mar 2024 15:59:09 +0100 Subject: [PATCH 177/518] use Parent in infinity --- src/sage/rings/infinity.py | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/src/sage/rings/infinity.py b/src/sage/rings/infinity.py index 883be4aa253..2f3987a7e5f 100644 --- a/src/sage/rings/infinity.py +++ b/src/sage/rings/infinity.py @@ -219,7 +219,9 @@ import sage.rings.abc +from sage.structure.parent import Parent from sage.categories.rings import Rings +from sage.categories.semirings import Semirings from sage.misc.fast_methods import Singleton from sage.misc.lazy_import import lazy_import from sage.rings.ring import CommutativeRing @@ -232,7 +234,7 @@ _obj = {} -class _uniq(): +class _uniq: def __new__(cls, *args): """ This ensures uniqueness of these objects. @@ -248,7 +250,7 @@ def __new__(cls, *args): return O -class AnInfinity(): +class AnInfinity: """ TESTS:: @@ -560,7 +562,7 @@ def _sage_input_(self, sib, coerced): return -sib.name('oo') -class UnsignedInfinityRing_class(Singleton, CommutativeRing): +class UnsignedInfinityRing_class(Singleton, Parent): def __init__(self): """ @@ -584,7 +586,9 @@ def __init__(self): sage: UnsignedInfinityRing(3) == UnsignedInfinityRing(-19.5) True """ - CommutativeRing.__init__(self, self, names=('oo',), normalize=False) + cat = Semirings().Commutative() + Parent.__init__(self, self, names=('oo',), normalize=False, + category=cat) def ngens(self) -> int: """ @@ -599,19 +603,6 @@ def ngens(self) -> int: """ return 1 - def fraction_field(self): - """ - The unsigned infinity ring isn't an integral domain. - - EXAMPLES:: - - sage: UnsignedInfinityRing.fraction_field() - Traceback (most recent call last): - ... - TypeError: infinity 'ring' has no fraction field - """ - raise TypeError("infinity 'ring' has no fraction field") - def gen(self, n=0): """ The "generator" of ``self`` is the infinity object. @@ -1869,7 +1860,7 @@ def test_signed_infinity(pos_inf): sage: test_signed_infinity(RIF(oo)) # needs sage.rings.real_interval_field sage: test_signed_infinity(SR(oo)) # needs sage.symbolic """ - msg = 'testing {} ({})'.format(pos_inf, type(pos_inf)) + msg = f'testing {pos_inf} ({type(pos_inf)})' assert InfinityRing(pos_inf) is infinity, msg assert InfinityRing(-pos_inf) is minus_infinity, msg assert infinity == pos_inf, msg From 98abf2b4031fb02cc026cad24b8d2e399d057380 Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Thu, 7 Mar 2024 22:34:07 +0100 Subject: [PATCH 178/518] Implemented reviewer feedback Amend: Missed a period. --- .../algebras/quatalg/quaternion_algebra.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index 585312851d1..c4ef097601a 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -3278,10 +3278,12 @@ def cyclic_right_subideals(self, p, alpha=None): def is_integral(self) -> bool: r""" - Check 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 whether the quaternion fractional ideal ``self`` 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]_. EXAMPLES:: @@ -3362,9 +3364,11 @@ def primitive_decomposition(self): def is_primitive(self) -> bool: r""" - Check if the quaternion fractional ideal is primitive. An integral left - `\mathcal{O}`-ideal for some order `\mathcal{O}` is called primitive if - for all integers `n > 1` it is not contained in `n\mathcal{O}` + Check whether the quaternion fractional ideal ``self`` is primitive. + + An integral left `\mathcal{O}`-ideal for some order `\mathcal{O}` + is called primitive if for all integers `n > 1` it is not + contained in `n\mathcal{O}`. EXAMPLES:: From 3b31b1a009a2b89d653fa446d96b5126298eee94 Mon Sep 17 00:00:00 2001 From: Kwankyu Lee Date: Fri, 8 Mar 2024 10:22:09 +0900 Subject: [PATCH 179/518] Fixes for reviewer comments --- .../function_field/function_field_polymod.py | 7 ++---- src/sage/schemes/curves/affine_curve.py | 1 + src/sage/schemes/curves/constructor.py | 23 ++++++++----------- src/sage/schemes/curves/curve.py | 18 +++++++-------- 4 files changed, 22 insertions(+), 27 deletions(-) diff --git a/src/sage/rings/function_field/function_field_polymod.py b/src/sage/rings/function_field/function_field_polymod.py index d90890cec0c..ce7aa40ad29 100644 --- a/src/sage/rings/function_field/function_field_polymod.py +++ b/src/sage/rings/function_field/function_field_polymod.py @@ -41,9 +41,6 @@ from .function_field import FunctionField from .function_field_rational import RationalFunctionField -_FunctionFields = FunctionFields() -_NumberFields = NumberFields() - class FunctionField_polymod(FunctionField): """ @@ -164,7 +161,7 @@ def __init__(self, polynomial, names, category=None): self._polynomial = polynomial FunctionField.__init__(self, base_field, names=names, - category=_FunctionFields.or_subcategory(category)) + category=FunctionFields().or_subcategory(category)) from .place_polymod import FunctionFieldPlace_polymod self._place_class = FunctionFieldPlace_polymod @@ -1861,7 +1858,7 @@ def genus(self): The genus is computed by the Hurwitz genus formula. """ k, _ = self.exact_constant_field() - if k in _NumberFields: + if k in NumberFields(): k_degree = k.relative_degree() else: k_degree = k.degree() diff --git a/src/sage/schemes/curves/affine_curve.py b/src/sage/schemes/curves/affine_curve.py index b8e62b61971..992cd528803 100644 --- a/src/sage/schemes/curves/affine_curve.py +++ b/src/sage/schemes/curves/affine_curve.py @@ -2068,6 +2068,7 @@ def _genus(self): TESTS:: + sage: # needs sage.rings.number_field sage: R. = QQ[] sage: N. = NumberField(T^2 + 1) sage: A2. = AffineSpace(N, 2) diff --git a/src/sage/schemes/curves/constructor.py b/src/sage/schemes/curves/constructor.py index 3b85fd96241..3564cfd73cc 100644 --- a/src/sage/schemes/curves/constructor.py +++ b/src/sage/schemes/curves/constructor.py @@ -72,9 +72,6 @@ IntegralAffinePlaneCurve, IntegralAffinePlaneCurve_finite_field) -_Fields = Fields() -_NumberFields = NumberFields() - def _is_irreducible_and_reduced(F) -> bool: """ @@ -312,7 +309,7 @@ def Curve(F, A=None): if A.coordinate_ring().ideal(F).is_zero(): if isinstance(k, FiniteField): return IntegralAffineCurve_finite_field(A, F) - if k in _Fields: + if k in Fields(): return IntegralAffineCurve(A, F) return AffineCurve(A, F) raise TypeError(f"{F} does not define a curve in one-dimensional affine space") @@ -320,8 +317,8 @@ def Curve(F, A=None): if isinstance(k, FiniteField): if A.coordinate_ring().ideal(F).is_prime(): return IntegralAffineCurve_finite_field(A, F) - if k in _Fields: - if (k == QQ or k in _NumberFields) and A.coordinate_ring().ideal(F).is_prime(): + if k in Fields(): + if (k == QQ or k in NumberFields()) and A.coordinate_ring().ideal(F).is_prime(): return IntegralAffineCurve(A, F) return AffineCurve_field(A, F) return AffineCurve(A, F) @@ -334,8 +331,8 @@ def Curve(F, A=None): if _is_irreducible_and_reduced(F): return IntegralAffinePlaneCurve_finite_field(A, F) return AffinePlaneCurve_finite_field(A, F) - if k in _Fields: - if (k == QQ or k in _NumberFields) and _is_irreducible_and_reduced(F): + if k in Fields(): + if (k == QQ or k in NumberFields()) and _is_irreducible_and_reduced(F): return IntegralAffinePlaneCurve(A, F) return AffinePlaneCurve_field(A, F) return AffinePlaneCurve(A, F) @@ -345,7 +342,7 @@ def Curve(F, A=None): if A.coordinate_ring().ideal(F).is_zero(): if isinstance(k, FiniteField): return IntegralProjectiveCurve_finite_field(A, F) - if k in _Fields: + if k in Fields(): return IntegralProjectiveCurve(A, F) return ProjectiveCurve(A, F) raise TypeError(f"{F} does not define a curve in one-dimensional projective space") @@ -355,8 +352,8 @@ def Curve(F, A=None): if isinstance(k, FiniteField): if A.coordinate_ring().ideal(F).is_prime(): return IntegralProjectiveCurve_finite_field(A, F) - if k in _Fields: - if (k == QQ or k in _NumberFields) and A.coordinate_ring().ideal(F).is_prime(): + if k in Fields(): + if (k == QQ or k in NumberFields()) and A.coordinate_ring().ideal(F).is_prime(): return IntegralProjectiveCurve(A, F) return ProjectiveCurve_field(A, F) return ProjectiveCurve(A, F) @@ -374,8 +371,8 @@ def Curve(F, A=None): if _is_irreducible_and_reduced(F): return IntegralProjectivePlaneCurve_finite_field(A, F) return ProjectivePlaneCurve_finite_field(A, F) - if k in _Fields: - if (k == QQ or k in _NumberFields) and _is_irreducible_and_reduced(F): + if k in Fields(): + if (k == QQ or k in NumberFields()) and _is_irreducible_and_reduced(F): return IntegralProjectivePlaneCurve(A, F) return ProjectivePlaneCurve_field(A, F) return ProjectivePlaneCurve(A, F) diff --git a/src/sage/schemes/curves/curve.py b/src/sage/schemes/curves/curve.py index 1d2d2221330..cacc05f884b 100644 --- a/src/sage/schemes/curves/curve.py +++ b/src/sage/schemes/curves/curve.py @@ -238,17 +238,17 @@ def geometric_genus(self): sage: C.geometric_genus() 3 - Note that the geometric genus is only defined for `geometrically - irreducible curve `_. This - method does not check the condition. Be warned that you may get a - nonsensical result if the curve is not geometrically irreducible. + .. WARNING:: - A curve that is not geometrically irreducible:: + Geometric genus is only defined for `geometrically irreducible curve + `_. This method does not + check the condition. You may get a nonsensical result if the curve is + not geometrically irreducible:: - sage: P2. = ProjectiveSpace(QQ, 2) - sage: C = Curve(x^2 + y^2, P2) - sage: C.geometric_genus() # nonsense! - -1 + sage: P2. = ProjectiveSpace(QQ, 2) + sage: C = Curve(x^2 + y^2, P2) + sage: C.geometric_genus() # nonsense! + -1 """ try: return self._genus From c12a9de520ec9461e9e0a0faa14c94dd8c2ee6e0 Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Thu, 7 Mar 2024 23:40:11 +0100 Subject: [PATCH 180/518] Docstring corrections Amend: Added missing multiline continuation markers --- .../rings/function_field/function_field.py | 62 ++++++++++--------- 1 file changed, 33 insertions(+), 29 deletions(-) diff --git a/src/sage/rings/function_field/function_field.py b/src/sage/rings/function_field/function_field.py index 1ea0fb4820f..d542dfd353c 100644 --- a/src/sage/rings/function_field/function_field.py +++ b/src/sage/rings/function_field/function_field.py @@ -220,6 +220,9 @@ - Brent Baccala (2019-12-20): added function fields over number fields and QQbar +- Sebastian A. Spindler (2024-03-06): implemented Hilbert symbols for global + function fields + """ # ***************************************************************************** @@ -247,6 +250,7 @@ from sage.misc.cachefunc import cached_method from sage.misc.lazy_import import LazyImport +from sage.rings.integer_ring import ZZ from sage.rings.ring import Field from sage.categories.homset import Hom from sage.categories.function_fields import FunctionFields @@ -1227,9 +1231,9 @@ def completion(self, place, name=None, prec=None, gen_name=None): def hilbert_symbol(self, a, b, P): """ Return the Hilbert symbol `(a,b)_{F_P}` (where `F_P` is the - completion of ``self`` at the place ``P``), i.e. the value 1 if + completion of ``self`` at the place ``P``), i.e. the value `1` if the quaternion algebra defined by `(a,b)` over `F_P` is split, - and -1 if it is division. + and `-1` if it is division. We use the completion at the place `P` to compute the valuations `v(a)` and `v(b)` as well as elements `a0` and `b0` such that, for a uniformizer @@ -1265,44 +1269,44 @@ def hilbert_symbol(self, a, b, P): 1 sage: K. = FunctionField(GF(5)); R. = PolynomialRing(K) - sage: f = ((x^2 + 2*x + 2)*T^5 + (4*x^2 + 2*x + 3)*T^4 + 3*T^3 + 4*T^2 - + (2/(x^2 + 4*x + 1))*T + 3*x^2 + 2*x + 4) + sage: f = (x^2 + 2*x + 2)*T^5 + (4*x^2 + 2*x + 3)*T^4 + 3*T^3 + 4*T^2 + ....: + (2/(x^2 + 4*x + 1))*T + 3*x^2 + 2*x + 4 sage: L. = K.extension(f) sage: P = L.places_above(K.places()[0])[1] - sage: a = (((3*x^3 + 2*x^2 + x + 1)/(x + 2))*y^4 + (4/(x + 4))*y^3 - + ((3*x + 4)/(x + 4))*y^2 - + ((x^4 + 2*x^3 + 3*x^2 + x + 2)/(x^4 + x^3 + x^2 + 2))*y - + (x^4 + x^2 + 2*x + 4)/(x^2 + 2*x + 2)) - sage: b = (((x + 1)/(x + 4))*y^4 - + ((2*x^4 + 3*x^3 + 3*x^2 + 3)/(x^2 + 2*x + 2))*y^3 + (2/(x + 4))*y^2 - + ((4*x^4 + 4*x^3 + 4*x^2 + 2)/(x^4 + x^3 + x^2 + 2))*y - + (x^3 + x)/(x^3 + x^2 + x + 2)) + sage: a = ((3*x^3 + 2*x^2 + x + 1)/(x + 2))*y^4 + (4/(x + 4))*y^3 + ....: + ((3*x + 4)/(x + 4))*y^2 + ....: + ((x^4 + 2*x^3 + 3*x^2 + x + 2)/(x^4 + x^3 + x^2 + 2))*y + ....: + (x^4 + x^2 + 2*x + 4)/(x^2 + 2*x + 2) + sage: b = ((x + 1)/(x + 4))*y^4 + ....: + ((2*x^4 + 3*x^3 + 3*x^2 + 3)/(x^2 + 2*x + 2))*y^3 + (2/(x + 4))*y^2 + ....: + ((4*x^4 + 4*x^3 + 4*x^2 + 2)/(x^4 + x^3 + x^2 + 2))*y + ....: + (x^3 + x)/(x^3 + x^2 + x + 2) sage: L.hilbert_symbol(a, b, P) 1 sage: Q = L.places_above(K.places()[1])[0] - sage: c = (((3*x^3 + 3*x^2 + 3*x + 3)/(x^3 + 3*x^2 + x))*y^4 - + ((3*x^4 + 2*x^3 + 2*x^2 + 4*x + 3)/(x^3 + 3*x^2 + x))*y^3 - + ((3*x + 4)/(x^3 + 3*x^2 + x))*y^2 - + ((4*x + 2)/(x^5 + 2*x^4 + 4*x^3 + 2*x^2 + x))*y - + (x^3 + 2*x^2 + 4)/(x^3 + 3*x^2 + x)) - sage: d = (((2*x^4 + x^2 + 2*x + 3)/(x^2 + 2*x + 2))*y^4 - + ((4*x^2 + 2)/(x^2 + 2*x + 2))*y^3 + ((3*x^2 + 3*x + 3)/(x^2 + 1))*y^2 - + ((x^6 + 4*x^5 + 4*x^4 + 3*x^3 + 3*x^2 + x + 3)/(x^4 + x^3 + x^2 + 2))*y - + (4*x^3 + 4*x^2 + 2*x + 2)/(x + 4)) + sage: c = ((3*x^3 + 3*x^2 + 3*x + 3)/(x^3 + 3*x^2 + x))*y^4 + ....: + ((3*x^4 + 2*x^3 + 2*x^2 + 4*x + 3)/(x^3 + 3*x^2 + x))*y^3 + ....: + ((3*x + 4)/(x^3 + 3*x^2 + x))*y^2 + ....: + ((4*x + 2)/(x^5 + 2*x^4 + 4*x^3 + 2*x^2 + x))*y + ....: + (x^3 + 2*x^2 + 4)/(x^3 + 3*x^2 + x) + sage: d = ((2*x^4 + x^2 + 2*x + 3)/(x^2 + 2*x + 2))*y^4 + ....: + ((4*x^2 + 2)/(x^2 + 2*x + 2))*y^3 + ((3*x^2 + 3*x + 3)/(x^2 + 1))*y^2 + ....: + ((x^6 + 4*x^5 + 4*x^4 + 3*x^3 + 3*x^2 + x + 3)/(x^4 + x^3 + x^2 + 2))*y + ....: + (4*x^3 + 4*x^2 + 2*x + 2)/(x + 4) sage: L.hilbert_symbol(c, d, Q) -1 sage: K. = FunctionField(GF(3)); R. = PolynomialRing(K) - sage: g = (((2*x + 1)/x)*T^5 + ((2*x + 1)/(x + 1))*T^4 + ((x^2 + 1)/x)*T^3 - + (2*x/(x^2 + 2*x + 2))*T^2 + 2*T + (2*x + 2)/(x + 2)) + sage: g = ((2*x + 1)/x)*T^5 + ((2*x + 1)/(x + 1))*T^4 + ((x^2 + 1)/x)*T^3 + ....: + (2*x/(x^2 + 2*x + 2))*T^2 + 2*T + (2*x + 2)/(x + 2) sage: L = K.extension(g) sage: P = L.places_above(K.places()[1])[1] - sage: a = (((x + 2)/(x + 1))*y^4 + ((x^4 + 2*x^2 + 1)/(x^2 + 2*x))*y^3 - + ((x^5 + x^2 + 2*x + 1)/(x^4 + 2*x^3 + x^2 + x + 1))*y^2 - + ((2*x + 2)/(x + 2))*y + (2*x^4 + x^3 + x^2 + 2*x + 2)/(x^2 + x + 1)) - sage: b = (((x^2 + x + 1)/(x^2 + 2*x + 1))*y^4 + ((2*x^2 + x + 1)/(x + 1))*y^3 - + ((x^5 + x^4 + 2*x^3 + x^2 + 2*x + 2)/(x^3 + x + 2))*y^2 - + (2*x/(x^2 + 2))*y + 2*x^2 + 2*x + 1) + sage: a = ((x + 2)/(x + 1))*y^4 + ((x^4 + 2*x^2 + 1)/(x^2 + 2*x))*y^3 + ....: + ((x^5 + x^2 + 2*x + 1)/(x^4 + 2*x^3 + x^2 + x + 1))*y^2 + ....: + ((2*x + 2)/(x + 2))*y + (2*x^4 + x^3 + x^2 + 2*x + 2)/(x^2 + x + 1) + sage: b = ((x^2 + x + 1)/(x^2 + 2*x + 1))*y^4 + ((2*x^2 + x + 1)/(x + 1))*y^3 + ....: + ((x^5 + x^4 + 2*x^3 + x^2 + 2*x + 2)/(x^3 + x + 2))*y^2 + ....: + (2*x/(x^2 + 2))*y + 2*x^2 + 2*x + 1 sage: L.hilbert_symbol(a, b, P) -1 """ From 0db2eb1437503742d95c66ed7b8bb62b022665f3 Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Fri, 8 Mar 2024 08:38:41 +0100 Subject: [PATCH 181/518] Fixed docstrings and small modifications - Simplified return command, removed `ZZ` from import again - Return `0` instead of an error for zero inputs --- .../rings/function_field/function_field.py | 50 +++++++++---------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/src/sage/rings/function_field/function_field.py b/src/sage/rings/function_field/function_field.py index d542dfd353c..d6e323d3810 100644 --- a/src/sage/rings/function_field/function_field.py +++ b/src/sage/rings/function_field/function_field.py @@ -250,7 +250,6 @@ from sage.misc.cachefunc import cached_method from sage.misc.lazy_import import LazyImport -from sage.rings.integer_ring import ZZ from sage.rings.ring import Field from sage.categories.homset import Hom from sage.categories.function_fields import FunctionFields @@ -1231,9 +1230,11 @@ def completion(self, place, name=None, prec=None, gen_name=None): def hilbert_symbol(self, a, b, P): """ Return the Hilbert symbol `(a,b)_{F_P}` (where `F_P` is the - completion of ``self`` at the place ``P``), i.e. the value `1` if - the quaternion algebra defined by `(a,b)` over `F_P` is split, - and `-1` if it is division. + completion of ``self`` at the place ``P``). + + The Hilbert symbol `(a,b)_{F_P}` is `0` if one of ``a`` or ``b`` is + zero. Otherwise it takes the value `1` if the quaternion algebra + defined by `(a,b)` over `F_P` is split, and `-1` if it is division. We use the completion at the place `P` to compute the valuations `v(a)` and `v(b)` as well as elements `a0` and `b0` such that, for a uniformizer @@ -1269,44 +1270,44 @@ def hilbert_symbol(self, a, b, P): 1 sage: K. = FunctionField(GF(5)); R. = PolynomialRing(K) - sage: f = (x^2 + 2*x + 2)*T^5 + (4*x^2 + 2*x + 3)*T^4 + 3*T^3 + 4*T^2 - ....: + (2/(x^2 + 4*x + 1))*T + 3*x^2 + 2*x + 4 + sage: (f = (x^2 + 2*x + 2)*T^5 + (4*x^2 + 2*x + 3)*T^4 + 3*T^3 + 4*T^2 + ....: + (2/(x^2 + 4*x + 1))*T + 3*x^2 + 2*x + 4) sage: L. = K.extension(f) sage: P = L.places_above(K.places()[0])[1] - sage: a = ((3*x^3 + 2*x^2 + x + 1)/(x + 2))*y^4 + (4/(x + 4))*y^3 + sage: (a = ((3*x^3 + 2*x^2 + x + 1)/(x + 2))*y^4 + (4/(x + 4))*y^3 ....: + ((3*x + 4)/(x + 4))*y^2 ....: + ((x^4 + 2*x^3 + 3*x^2 + x + 2)/(x^4 + x^3 + x^2 + 2))*y - ....: + (x^4 + x^2 + 2*x + 4)/(x^2 + 2*x + 2) - sage: b = ((x + 1)/(x + 4))*y^4 + ....: + (x^4 + x^2 + 2*x + 4)/(x^2 + 2*x + 2)) + sage: (b = ((x + 1)/(x + 4))*y^4 ....: + ((2*x^4 + 3*x^3 + 3*x^2 + 3)/(x^2 + 2*x + 2))*y^3 + (2/(x + 4))*y^2 ....: + ((4*x^4 + 4*x^3 + 4*x^2 + 2)/(x^4 + x^3 + x^2 + 2))*y - ....: + (x^3 + x)/(x^3 + x^2 + x + 2) + ....: + (x^3 + x)/(x^3 + x^2 + x + 2)) sage: L.hilbert_symbol(a, b, P) 1 sage: Q = L.places_above(K.places()[1])[0] - sage: c = ((3*x^3 + 3*x^2 + 3*x + 3)/(x^3 + 3*x^2 + x))*y^4 + sage: (c = ((3*x^3 + 3*x^2 + 3*x + 3)/(x^3 + 3*x^2 + x))*y^4 ....: + ((3*x^4 + 2*x^3 + 2*x^2 + 4*x + 3)/(x^3 + 3*x^2 + x))*y^3 ....: + ((3*x + 4)/(x^3 + 3*x^2 + x))*y^2 ....: + ((4*x + 2)/(x^5 + 2*x^4 + 4*x^3 + 2*x^2 + x))*y - ....: + (x^3 + 2*x^2 + 4)/(x^3 + 3*x^2 + x) - sage: d = ((2*x^4 + x^2 + 2*x + 3)/(x^2 + 2*x + 2))*y^4 + ....: + (x^3 + 2*x^2 + 4)/(x^3 + 3*x^2 + x)) + sage: (d = ((2*x^4 + x^2 + 2*x + 3)/(x^2 + 2*x + 2))*y^4 ....: + ((4*x^2 + 2)/(x^2 + 2*x + 2))*y^3 + ((3*x^2 + 3*x + 3)/(x^2 + 1))*y^2 ....: + ((x^6 + 4*x^5 + 4*x^4 + 3*x^3 + 3*x^2 + x + 3)/(x^4 + x^3 + x^2 + 2))*y - ....: + (4*x^3 + 4*x^2 + 2*x + 2)/(x + 4) + ....: + (4*x^3 + 4*x^2 + 2*x + 2)/(x + 4)) sage: L.hilbert_symbol(c, d, Q) -1 sage: K. = FunctionField(GF(3)); R. = PolynomialRing(K) - sage: g = ((2*x + 1)/x)*T^5 + ((2*x + 1)/(x + 1))*T^4 + ((x^2 + 1)/x)*T^3 - ....: + (2*x/(x^2 + 2*x + 2))*T^2 + 2*T + (2*x + 2)/(x + 2) + sage: (g = ((2*x + 1)/x)*T^5 + ((2*x + 1)/(x + 1))*T^4 + ((x^2 + 1)/x)*T^3 + ....: + (2*x/(x^2 + 2*x + 2))*T^2 + 2*T + (2*x + 2)/(x + 2)) sage: L = K.extension(g) sage: P = L.places_above(K.places()[1])[1] - sage: a = ((x + 2)/(x + 1))*y^4 + ((x^4 + 2*x^2 + 1)/(x^2 + 2*x))*y^3 + sage: (a = ((x + 2)/(x + 1))*y^4 + ((x^4 + 2*x^2 + 1)/(x^2 + 2*x))*y^3 ....: + ((x^5 + x^2 + 2*x + 1)/(x^4 + 2*x^3 + x^2 + x + 1))*y^2 - ....: + ((2*x + 2)/(x + 2))*y + (2*x^4 + x^3 + x^2 + 2*x + 2)/(x^2 + x + 1) - sage: b = ((x^2 + x + 1)/(x^2 + 2*x + 1))*y^4 + ((2*x^2 + x + 1)/(x + 1))*y^3 + ....: + ((2*x + 2)/(x + 2))*y + (2*x^4 + x^3 + x^2 + 2*x + 2)/(x^2 + x + 1)) + sage: (b = ((x^2 + x + 1)/(x^2 + 2*x + 1))*y^4 + ((2*x^2 + x + 1)/(x + 1))*y^3 ....: + ((x^5 + x^4 + 2*x^3 + x^2 + 2*x + 2)/(x^3 + x + 2))*y^2 - ....: + (2*x/(x^2 + 2))*y + 2*x^2 + 2*x + 1 + ....: + (2*x/(x^2 + 2))*y + 2*x^2 + 2*x + 1) sage: L.hilbert_symbol(a, b, P) -1 """ @@ -1320,7 +1321,7 @@ def hilbert_symbol(self, a, b, P): raise ValueError('a and b must be elements of the function field') if a.is_zero() or b.is_zero(): - raise ValueError('the invariants a and b must be nonzero') + return 0 # Compute the completion map to precision 1 for computation of the # valuations v(a), v(b) as well as the elements a0, b0 @@ -1341,16 +1342,15 @@ def hilbert_symbol(self, a, b, P): # Get the residue field of the completion, together with the residue map k, _, tau = self.residue_field(P) - e = (k.order()-1)/2 + e = (k.order() - 1) >> 1 # Use Euler's criterion to compute the powers of Legendre symbols a_rd_pw = tau(a0)**(v_b * e) b_rd_pw = tau(b0)**(v_a * e) - # Finally, put the result together + # Finally, put the result together and transform it into the correct output res = k(-1)**(v_a * v_b * e) * a_rd_pw * b_rd_pw - - return ZZ(1) if res == k(1) else ZZ(-1) + return (res.is_one() << 1) - 1 def extension_constant_field(self, k): """ From b8f80569a9fa90f610686f8ed38c1e04bc01bfee Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Fri, 8 Mar 2024 18:43:20 +0100 Subject: [PATCH 182/518] Fixed LINT and docstring issues --- .../rings/function_field/function_field.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/sage/rings/function_field/function_field.py b/src/sage/rings/function_field/function_field.py index d6e323d3810..20610b1be35 100644 --- a/src/sage/rings/function_field/function_field.py +++ b/src/sage/rings/function_field/function_field.py @@ -1270,27 +1270,27 @@ def hilbert_symbol(self, a, b, P): 1 sage: K. = FunctionField(GF(5)); R. = PolynomialRing(K) - sage: (f = (x^2 + 2*x + 2)*T^5 + (4*x^2 + 2*x + 3)*T^4 + 3*T^3 + 4*T^2 + sage: f = ((x^2 + 2*x + 2)*T^5 + (4*x^2 + 2*x + 3)*T^4 + 3*T^3 + 4*T^2 ....: + (2/(x^2 + 4*x + 1))*T + 3*x^2 + 2*x + 4) sage: L. = K.extension(f) sage: P = L.places_above(K.places()[0])[1] - sage: (a = ((3*x^3 + 2*x^2 + x + 1)/(x + 2))*y^4 + (4/(x + 4))*y^3 + sage: a = (((3*x^3 + 2*x^2 + x + 1)/(x + 2))*y^4 + (4/(x + 4))*y^3 ....: + ((3*x + 4)/(x + 4))*y^2 ....: + ((x^4 + 2*x^3 + 3*x^2 + x + 2)/(x^4 + x^3 + x^2 + 2))*y ....: + (x^4 + x^2 + 2*x + 4)/(x^2 + 2*x + 2)) - sage: (b = ((x + 1)/(x + 4))*y^4 + sage: b = (((x + 1)/(x + 4))*y^4 ....: + ((2*x^4 + 3*x^3 + 3*x^2 + 3)/(x^2 + 2*x + 2))*y^3 + (2/(x + 4))*y^2 ....: + ((4*x^4 + 4*x^3 + 4*x^2 + 2)/(x^4 + x^3 + x^2 + 2))*y ....: + (x^3 + x)/(x^3 + x^2 + x + 2)) sage: L.hilbert_symbol(a, b, P) 1 sage: Q = L.places_above(K.places()[1])[0] - sage: (c = ((3*x^3 + 3*x^2 + 3*x + 3)/(x^3 + 3*x^2 + x))*y^4 + sage: c = (((3*x^3 + 3*x^2 + 3*x + 3)/(x^3 + 3*x^2 + x))*y^4 ....: + ((3*x^4 + 2*x^3 + 2*x^2 + 4*x + 3)/(x^3 + 3*x^2 + x))*y^3 ....: + ((3*x + 4)/(x^3 + 3*x^2 + x))*y^2 ....: + ((4*x + 2)/(x^5 + 2*x^4 + 4*x^3 + 2*x^2 + x))*y ....: + (x^3 + 2*x^2 + 4)/(x^3 + 3*x^2 + x)) - sage: (d = ((2*x^4 + x^2 + 2*x + 3)/(x^2 + 2*x + 2))*y^4 + sage: d = (((2*x^4 + x^2 + 2*x + 3)/(x^2 + 2*x + 2))*y^4 ....: + ((4*x^2 + 2)/(x^2 + 2*x + 2))*y^3 + ((3*x^2 + 3*x + 3)/(x^2 + 1))*y^2 ....: + ((x^6 + 4*x^5 + 4*x^4 + 3*x^3 + 3*x^2 + x + 3)/(x^4 + x^3 + x^2 + 2))*y ....: + (4*x^3 + 4*x^2 + 2*x + 2)/(x + 4)) @@ -1298,14 +1298,14 @@ def hilbert_symbol(self, a, b, P): -1 sage: K. = FunctionField(GF(3)); R. = PolynomialRing(K) - sage: (g = ((2*x + 1)/x)*T^5 + ((2*x + 1)/(x + 1))*T^4 + ((x^2 + 1)/x)*T^3 + sage: g = (((2*x + 1)/x)*T^5 + ((2*x + 1)/(x + 1))*T^4 + ((x^2 + 1)/x)*T^3 ....: + (2*x/(x^2 + 2*x + 2))*T^2 + 2*T + (2*x + 2)/(x + 2)) sage: L = K.extension(g) sage: P = L.places_above(K.places()[1])[1] - sage: (a = ((x + 2)/(x + 1))*y^4 + ((x^4 + 2*x^2 + 1)/(x^2 + 2*x))*y^3 + sage: a = (((x + 2)/(x + 1))*y^4 + ((x^4 + 2*x^2 + 1)/(x^2 + 2*x))*y^3 ....: + ((x^5 + x^2 + 2*x + 1)/(x^4 + 2*x^3 + x^2 + x + 1))*y^2 ....: + ((2*x + 2)/(x + 2))*y + (2*x^4 + x^3 + x^2 + 2*x + 2)/(x^2 + x + 1)) - sage: (b = ((x^2 + x + 1)/(x^2 + 2*x + 1))*y^4 + ((2*x^2 + x + 1)/(x + 1))*y^3 + sage: b = (((x^2 + x + 1)/(x^2 + 2*x + 1))*y^4 + ((2*x^2 + x + 1)/(x + 1))*y^3 ....: + ((x^5 + x^4 + 2*x^3 + x^2 + 2*x + 2)/(x^3 + x + 2))*y^2 ....: + (2*x/(x^2 + 2))*y + 2*x^2 + 2*x + 1) sage: L.hilbert_symbol(a, b, P) @@ -1348,7 +1348,7 @@ def hilbert_symbol(self, a, b, P): a_rd_pw = tau(a0)**(v_b * e) b_rd_pw = tau(b0)**(v_a * e) - # Finally, put the result together and transform it into the correct output + # Finally, put the result together and transform it into the correct output res = k(-1)**(v_a * v_b * e) * a_rd_pw * b_rd_pw return (res.is_one() << 1) - 1 From 902b67fb0e45ec1c78659e9d3355bfda06fb0461 Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Fri, 8 Mar 2024 17:49:44 +0000 Subject: [PATCH 183/518] improve documentation --- .../multi_polynomial_libsingular.pyx | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/sage/rings/polynomial/multi_polynomial_libsingular.pyx b/src/sage/rings/polynomial/multi_polynomial_libsingular.pyx index 70386eb0b50..60aa5bd97b8 100644 --- a/src/sage/rings/polynomial/multi_polynomial_libsingular.pyx +++ b/src/sage/rings/polynomial/multi_polynomial_libsingular.pyx @@ -2543,12 +2543,11 @@ cdef class MPolynomial_libsingular(MPolynomial_libsingular_base): OUTPUT: - If ``x`` is not given, return the maximum degree of the monomials of - the polynomial. Note that the degree of a monomial is affected by the - gradings given to the generators of the parent ring. If ``x`` is given, - it is (or coercible to) a generator of the parent ring and the output - is the maximum degree in ``x``. This is not affected by the gradings of - the generators. + If ``x`` is ``None``, return the total degree of ``self``. Note that + this result is affected by the weighting given to the generators of the + parent ring. Otherwise, if ``x`` is (or is coercible to) a generator of + the parent ring, the output is the maximum degree of ``x`` in ``self``. + This is not affected by the weighting of the generators. EXAMPLES:: @@ -2563,6 +2562,19 @@ cdef class MPolynomial_libsingular(MPolynomial_libsingular_base): sage: (y^10*x - 7*x^2*y^5 + 5*x^3).degree(y) 10 + When the generators have a grading (weighting) then the total degree + respects this, but the degree for a given generator is unaffected:: + + sage: T = TermOrder("wdegrevlex", (2, 3)) + sage: R. = PolynomialRing(QQ, order=T) + sage: f = x^2 * y + y^4 + sage: f.degree() + 12 + sage: f.degree(x) + 2 + sage: f.degree(y) + 4 + The term ordering of the parent ring determines the grading of the generators. :: From 95a50e1a5c2673cb2f2b8e74a5b7b53c000c4cb7 Mon Sep 17 00:00:00 2001 From: "John H. Palmieri" Date: Fri, 8 Mar 2024 10:57:16 -0800 Subject: [PATCH 184/518] PR #37501: use two files again --- CODE_OF_CONDUCT.md | 266 +---------------------------------- CODE_OF_CONDUCT_COMMITTEE.md | 242 +++++++++++++++++++++++++++++++ 2 files changed, 249 insertions(+), 259 deletions(-) create mode 100644 CODE_OF_CONDUCT_COMMITTEE.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 2a5d43a2828..c66c623169b 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,19 +1,5 @@ -Table of Contents ------------------ - -* [Code of Conduct](#code-of-conduct) -* [Guide for the Sage Code of Conduct Committee](#guide-for-the-sage-code-of-conduct-committee) - -This document has two parts: the Code of Conduct itself, followed by a -part describing the roles and procedures for the Sage Code of Conduct -Committee. - # Code of Conduct # -This document has two parts: the current one --- the Code of Conduct -itself --- and a second part describing the role of and procedures for -the Sage Code of Conduct Committee. - This Code was approved by the Sage community by a vote which ended on November 24, 2014. @@ -27,6 +13,10 @@ with varied backgrounds. This diversity is one of our strengths, but it can also lead to communication problems and unhappiness. People who love working on Sage can more effectively collaborate with others if they follow this Code. +This document is complemented by a second, [the Code of Conduct +Committee Manual](/CODE_OF_CONDUCT_COMMITTEE.md), which describes the +roles and procedures for the Sage Code of Conduct Committee. + ## Guidelines ## 1. Be friendly and patient. @@ -206,259 +196,17 @@ Google groups: ## Amending the Code of Conduct ## -The first part of this document may be amended by a vote of the Sage +This document may be amended by a vote of the Sage community in the sage-devel Google group, with the exception of facts like the membership of the Sage Code of Conduct Committee, changes to URLs, or changes to email addresses: changes like that can be done via a normal pull request. Any pull requests involving this document should list the committee members as reviewers. - - -# Guide for the Sage Code of Conduct Committee # - -## Introduction ## - -This is the manual followed by the Sage Code of Conduct Committee. It is used -when we respond to an issue to make sure we’re consistent and fair. - -Enforcing the Code of Conduct impacts our community today and for the -future. It’s an action that we do not take lightly. When reviewing -enforcement measures, the Sage Code of Conduct Committee will keep the -following values and guidelines in mind: - -- Act in a personal manner rather than impersonal. The committee can - engage the parties to understand the situation, while respecting the - privacy and any necessary confidentiality of reporters. However, - it is sometimes necessary to communicate with one or more - individuals directly: the committee’s goal is to improve the health - of our community rather than only produce a formal decision. - -- Emphasize empathy for individuals rather than judging behavior, - avoiding binary labels of “good” and “bad/evil”. Overt, clear-cut - aggression and harassment exists and we will address that - firmly. But many scenarios that can prove challenging to resolve are - those where normal disagreements devolve into unhelpful or harmful - behavior from multiple parties. Understanding the full context and - finding a path that re-engages all is hard, but ultimately the most - productive for our community. - -- We understand that email is a difficult medium and can be - isolating. Receiving criticism over email, without personal contact, - can be particularly painful. This makes it especially important to - keep an atmosphere of open-minded respect of the views of others. It - also means that we must be transparent in our actions, and that we - will do everything in our power to make sure that all our members - are treated fairly and with sympathy. - -- Discrimination can be subtle and it can be unconscious. It can show - itself as unfairness and hostility in otherwise ordinary - interactions. We know that this does occur, and we will take care to - look out for it. We would very much like to hear from you if you - feel you have been treated unfairly, and we will use these - procedures to make sure that your complaint is heard and addressed. - -- Help increase engagement in good discussion practice: try to - identify where discussion may have broken down and provide - actionable information, pointers and resources that can lead to - positive change on these points. - -- Be mindful of the needs of new members: provide them with explicit - support and consideration, with the aim of increasing participation - from underrepresented groups in particular. - -Individuals come from different cultural backgrounds and native -languages. Try to identify any honest misunderstandings caused by a -non-native speaker and help them understand the issue and what they -can change to avoid causing offense. Complex discussion in a foreign -language can be very intimidating, and we want to grow our diversity -also across nationalities and cultures. - -Mediation: voluntary, informal mediation is a tool at our disposal. In -some contexts, such as when two or more parties have escalated to the -point of inappropriate behavior (something sadly common in human -conflict), it may be useful to facilitate a mediation process. This is -only an example: the committee can consider mediation in any case, -mindful that the process is meant to be strictly voluntary and no -party can be pressured to participate. If the committee suggests -mediation, it should: - -- Find a candidate who can serve as a mediator. - -- Obtain the agreement of the reporter(s). The reporter(s) have - complete freedom to decline the mediation idea, or to propose an - alternate mediator. - -- Obtain the agreement of the reported person(s). - -- Settle on the mediator: while parties can propose a different - mediator than the suggested candidate, only if common agreement is - reached on all terms can the process move forward. - -- Establish a timeline for mediation to complete, ideally within two weeks. - -The mediator will engage with all the parties and seek a resolution -that is satisfactory to all. Upon completion, the mediator will -provide a report (vetted by all parties to the process) to the -committee, with recommendations on further steps. The committee will -then evaluate these results (whether satisfactory resolution was -achieved or not) and decide on any additional action deemed necessary. - -## How the committee will respond to reports ## - -When the committee (or a committee member) receives a report, they -will first determine whether the report is about a clear and severe -breach (as defined below). If so, immediate action needs to be taken -in addition to the regular report-handling process. - -### Clear and severe breach actions ### - -We know that it is painfully common for internet communication to -start at or devolve into obvious and flagrant abuse. We will deal -quickly with clear and severe breaches like personal threats, violent, -sexist, or racist language. - -When a member of the Sage Code of Conduct Committee becomes aware of a -clear and severe breach, they will do the following: - -- Immediately disconnect the originator from all Sage communication channels. - -- Reply to the reporter that their report has been received and that - the originator has been disconnected. - -- In every case, the moderator should make a reasonable effort to - contact the originator, and tell them specifically how their - language or actions qualify as a “clear and severe breach”. The - moderator should also say that, if the originator believes this is - unfair or they want to be reconnected to Sage, they have the right - to ask for a review, as below, by the Sage Code of Conduct Committee. The - moderator should copy this explanation to the Sage Code of Conduct - Committee. - -The Sage Code of Conduct Committee will formally review and sign off on all -cases where this mechanism has been applied to make sure it is not -being used to control ordinary heated disagreement. - -### Report handling ### - -When a report is sent to the committee, they will immediately reply to -the reporter to confirm receipt. This reply must be sent within 72 -hours, and the group should strive to respond much quicker than that. - -If a report doesn’t contain enough information, the committee will -obtain all relevant data before acting. The committee is empowered to -contact any individuals involved to get a more complete account of -events. - -The committee will then review the incident and determine, to the best of their ability: - -- What happened. - -- Whether this event constitutes a Code of Conduct violation. - -- Who are the responsible party/parties. - -- Whether this is an ongoing situation, and whether there is a threat to anyone’s physical safety. - -This information will be collected in writing, and whenever possible -the group’s deliberations will be recorded and retained (i.e., chat -transcripts, email discussions, recorded conference calls, summaries -of voice conversations, etc.). - -It is important to retain an archive of all activities of this -committee to ensure consistency in behavior and provide institutional -memory for the project. To assist in this, the default channel of -discussion for this committee will be a private mailing list -accessible to current and future members of the committee. If the -committee finds the need to use off-list communications (e.g., phone -calls for early/rapid response), it should, in all cases, summarize -these back to the list so there’s a good record of the process. - -The Sage Code of Conduct Committee should aim to have a resolution agreed upon -within two weeks. In the event that a resolution can’t be determined -in that time, the committee will respond to the reporter(s) with an -update and projected timeline for the resolution. - -## Resolutions ## - -The committee must agree on a resolution by consensus. If the group -cannot reach consensus and deadlocks for over a week, the committee is -empowered to consult as needed to try to reach consensus. - -Possible responses may include: - -- Taking no further action: - - - if we determine no violations have occurred. - - - if the matter has been resolved publicly while the committee was considering responses. - -- Coordinating voluntary mediation: if all involved parties agree, the - committee may facilitate a mediation process as detailed above. - -- Remind publicly, and point out that some behavior/actions/language - have been judged inappropriate and why in the current context, or - can but hurtful to some people, requesting the community to - self-adjust. - -- A private reprimand from the committee to the individual(s) - involved. In this case, a representative of the committee will - deliver that reprimand to the individual(s) over email, cc’ing the - group. - -- A public reprimand. In this case, a committee representative will deliver - that reprimand in the same venue that the violation occurred, within - the limits of practicality. E.g., the original mailing list for an - email violation, but for a chat room discussion where the - person/context may be gone, they can be reached by other means. The - group may choose to publish this message elsewhere for documentation - purposes. - -- A request for a public or private apology, assuming the reporter - agrees to this idea: they may, at their discretion, refuse further - contact with the violator. A committee representative will deliver - this request. The committee may, if it chooses, attach “strings” to - this request: for example, the group may ask a violator to - apologize, in order to retain one’s membership on a mailing list. - -- A “mutually agreed upon hiatus” where the committee asks the - individual to temporarily refrain from community participation. If - the individual chooses not to take a temporary break voluntarily, - the committee may issue a “mandatory cooling off period”. - -- A permanent or temporary ban from some or all Sage spaces (mailing - lists, GitHub, etc.). The group will maintain records of all such - bans so that they may be reviewed in the future or otherwise - maintained. - -Once a resolution is agreed upon, the committee will contact the -original reporter and any other affected parties and explain that the -committee has taken action. Depending on the situation, the committee -may or may not choose to provide further details about what actions -were taken and how they might affect the reporter. - -The committee will never publicly discuss the issue; all public -statements will be made by a representative of the Sage Code of Conduct -Committee. - -## Conflicts of interest ## - -In the event of any conflict of interest, a committee member must -immediately notify the other members, and recuse themselves if -necessary. - -## Amending the Code of Conduct Committee manual ## - -This part of the document may be amended by a vote of the Sage Code of -Conduct Committee. - ## Credits ## -Portions of the first part (the code itself) are adapted from the +Portions of this are adapted from the [SciPy code of conduct](https://docs.scipy.org/doc/scipy/dev/conduct/code_of_conduct.html) and the [NumFOCUS code of -conduct](https://numfocus.org/code-of-conduct). The second part (the -guide for the Sage Code of Conduct Committee) is largely adapted from -the [SciPy report handling -manual](https://docs.scipy.org/doc/scipy/dev/conduct/report_handling_manual.html). +conduct](https://numfocus.org/code-of-conduct). diff --git a/CODE_OF_CONDUCT_COMMITTEE.md b/CODE_OF_CONDUCT_COMMITTEE.md new file mode 100644 index 00000000000..abff7b21f3c --- /dev/null +++ b/CODE_OF_CONDUCT_COMMITTEE.md @@ -0,0 +1,242 @@ +# Guide for the Sage Code of Conduct Committee # + +## Introduction ## + +This is the manual followed by the Sage Code of Conduct Committee. It +complements the [Code of Conduct](/CODE_OF_CONDUCT.md). It is used when +we respond to an issue to make sure we’re consistent and fair. + +Enforcing the Code of Conduct impacts our community today and for the +future. It’s an action that we do not take lightly. When reviewing +enforcement measures, the Sage Code of Conduct Committee will keep the +following values and guidelines in mind: + +- Act in a personal manner rather than impersonal. The committee can + engage the parties to understand the situation, while respecting the + privacy and any necessary confidentiality of reporters. However, + it is sometimes necessary to communicate with one or more + individuals directly: the committee’s goal is to improve the health + of our community rather than only produce a formal decision. + +- Emphasize empathy for individuals rather than judging behavior, + avoiding binary labels of “good” and “bad/evil”. Overt, clear-cut + aggression and harassment exists and we will address that + firmly. But many scenarios that can prove challenging to resolve are + those where normal disagreements devolve into unhelpful or harmful + behavior from multiple parties. Understanding the full context and + finding a path that re-engages all is hard, but ultimately the most + productive for our community. + +- We understand that email is a difficult medium and can be + isolating. Receiving criticism over email, without personal contact, + can be particularly painful. This makes it especially important to + keep an atmosphere of open-minded respect of the views of others. It + also means that we must be transparent in our actions, and that we + will do everything in our power to make sure that all our members + are treated fairly and with sympathy. + +- Discrimination can be subtle and it can be unconscious. It can show + itself as unfairness and hostility in otherwise ordinary + interactions. We know that this does occur, and we will take care to + look out for it. We would very much like to hear from you if you + feel you have been treated unfairly, and we will use these + procedures to make sure that your complaint is heard and addressed. + +- Help increase engagement in good discussion practice: try to + identify where discussion may have broken down and provide + actionable information, pointers and resources that can lead to + positive change on these points. + +- Be mindful of the needs of new members: provide them with explicit + support and consideration, with the aim of increasing participation + from underrepresented groups in particular. + +Individuals come from different cultural backgrounds and native +languages. Try to identify any honest misunderstandings caused by a +non-native speaker and help them understand the issue and what they +can change to avoid causing offense. Complex discussion in a foreign +language can be very intimidating, and we want to grow our diversity +also across nationalities and cultures. + +Mediation: voluntary, informal mediation is a tool at our disposal. In +some contexts, such as when two or more parties have escalated to the +point of inappropriate behavior (something sadly common in human +conflict), it may be useful to facilitate a mediation process. This is +only an example: the committee can consider mediation in any case, +mindful that the process is meant to be strictly voluntary and no +party can be pressured to participate. If the committee suggests +mediation, it should: + +- Find a candidate who can serve as a mediator. + +- Obtain the agreement of the reporter(s). The reporter(s) have + complete freedom to decline the mediation idea, or to propose an + alternate mediator. + +- Obtain the agreement of the reported person(s). + +- Settle on the mediator: while parties can propose a different + mediator than the suggested candidate, only if common agreement is + reached on all terms can the process move forward. + +- Establish a timeline for mediation to complete, ideally within two weeks. + +The mediator will engage with all the parties and seek a resolution +that is satisfactory to all. Upon completion, the mediator will +provide a report (vetted by all parties to the process) to the +committee, with recommendations on further steps. The committee will +then evaluate these results (whether satisfactory resolution was +achieved or not) and decide on any additional action deemed necessary. + +## How the committee will respond to reports ## + +When the committee (or a committee member) receives a report, they +will first determine whether the report is about a clear and severe +breach (as defined below). If so, immediate action needs to be taken +in addition to the regular report-handling process. + +### Clear and severe breach actions ### + +We know that it is painfully common for internet communication to +start at or devolve into obvious and flagrant abuse. We will deal +quickly with clear and severe breaches like personal threats, violent, +sexist, or racist language. + +When a member of the Sage Code of Conduct Committee becomes aware of a +clear and severe breach, they will do the following: + +- Immediately disconnect the originator from all Sage communication channels. + +- Reply to the reporter that their report has been received and that + the originator has been disconnected. + +- In every case, the moderator should make a reasonable effort to + contact the originator, and tell them specifically how their + language or actions qualify as a “clear and severe breach”. The + moderator should also say that, if the originator believes this is + unfair or they want to be reconnected to Sage, they have the right + to ask for a review, as below, by the Sage Code of Conduct Committee. The + moderator should copy this explanation to the Sage Code of Conduct + Committee. + +The Sage Code of Conduct Committee will formally review and sign off on all +cases where this mechanism has been applied to make sure it is not +being used to control ordinary heated disagreement. + +### Report handling ### + +When a report is sent to the committee, they will immediately reply to +the reporter to confirm receipt. This reply must be sent within 72 +hours, and the group should strive to respond much quicker than that. + +If a report doesn’t contain enough information, the committee will +obtain all relevant data before acting. The committee is empowered to +contact any individuals involved to get a more complete account of +events. + +The committee will then review the incident and determine, to the best of their ability: + +- What happened. + +- Whether this event constitutes a Code of Conduct violation. + +- Who are the responsible party/parties. + +- Whether this is an ongoing situation, and whether there is a threat to anyone’s physical safety. + +This information will be collected in writing, and whenever possible +the group’s deliberations will be recorded and retained (i.e., chat +transcripts, email discussions, recorded conference calls, summaries +of voice conversations, etc.). + +It is important to retain an archive of all activities of this +committee to ensure consistency in behavior and provide institutional +memory for the project. To assist in this, the default channel of +discussion for this committee will be a private mailing list +accessible to current and future members of the committee. If the +committee finds the need to use off-list communications (e.g., phone +calls for early/rapid response), it should, in all cases, summarize +these back to the list so there’s a good record of the process. + +The Sage Code of Conduct Committee should aim to have a resolution agreed upon +within two weeks. In the event that a resolution can’t be determined +in that time, the committee will respond to the reporter(s) with an +update and projected timeline for the resolution. + +## Resolutions ## + +The committee must agree on a resolution by consensus. If the group +cannot reach consensus and deadlocks for over a week, the committee is +empowered to consult as needed to try to reach consensus. + +Possible responses may include: + +- Taking no further action: + + - if we determine no violations have occurred. + + - if the matter has been resolved publicly while the committee was considering responses. + +- Coordinating voluntary mediation: if all involved parties agree, the + committee may facilitate a mediation process as detailed above. + +- Remind publicly, and point out that some behavior/actions/language + have been judged inappropriate and why in the current context, or + can but hurtful to some people, requesting the community to + self-adjust. + +- A private reprimand from the committee to the individual(s) + involved. In this case, a representative of the committee will + deliver that reprimand to the individual(s) over email, cc’ing the + group. + +- A public reprimand. In this case, a committee representative will deliver + that reprimand in the same venue that the violation occurred, within + the limits of practicality. E.g., the original mailing list for an + email violation, but for a chat room discussion where the + person/context may be gone, they can be reached by other means. The + group may choose to publish this message elsewhere for documentation + purposes. + +- A request for a public or private apology, assuming the reporter + agrees to this idea: they may, at their discretion, refuse further + contact with the violator. A committee representative will deliver + this request. The committee may, if it chooses, attach “strings” to + this request: for example, the group may ask a violator to + apologize, in order to retain one’s membership on a mailing list. + +- A “mutually agreed upon hiatus” where the committee asks the + individual to temporarily refrain from community participation. If + the individual chooses not to take a temporary break voluntarily, + the committee may issue a “mandatory cooling off period”. + +- A permanent or temporary ban from some or all Sage spaces (mailing + lists, GitHub, etc.). The group will maintain records of all such + bans so that they may be reviewed in the future or otherwise + maintained. + +Once a resolution is agreed upon, the committee will contact the +original reporter and any other affected parties and explain that the +committee has taken action. Depending on the situation, the committee +may or may not choose to provide further details about what actions +were taken and how they might affect the reporter. + +The committee will never publicly discuss the issue; all public +statements will be made by a representative of the Sage Code of Conduct +Committee. + +## Conflicts of interest ## + +In the event of any conflict of interest, a committee member must +immediately notify the other members, and recuse themselves if +necessary. + +## Amending the Code of Conduct Committee manual ## + +This document may be amended by a vote of the Sage Code of Conduct +Committee. + +## Credits ## + +This document is largely adapted from the [SciPy report handling +manual](https://docs.scipy.org/doc/scipy/dev/conduct/report_handling_manual.html). From 24beec30cd7d385dc1e0f9f0c988ff0d07dae5a0 Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Fri, 8 Mar 2024 20:03:32 +0100 Subject: [PATCH 185/518] Small input adaption, finally fixed docstrings --- src/sage/rings/function_field/function_field.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sage/rings/function_field/function_field.py b/src/sage/rings/function_field/function_field.py index 20610b1be35..ea63ce337ae 100644 --- a/src/sage/rings/function_field/function_field.py +++ b/src/sage/rings/function_field/function_field.py @@ -1248,9 +1248,9 @@ def hilbert_symbol(self, a, b, P): INPUT: - - ``a`` and ``b``: Units in the function field ``self`` + - ``a`` and ``b``: Elements of ``self`` - - ``P``: A place of the function field ``self`` + - ``P``: A place of ``self`` EXAMPLES:: @@ -1300,7 +1300,7 @@ def hilbert_symbol(self, a, b, P): sage: K. = FunctionField(GF(3)); R. = PolynomialRing(K) sage: g = (((2*x + 1)/x)*T^5 + ((2*x + 1)/(x + 1))*T^4 + ((x^2 + 1)/x)*T^3 ....: + (2*x/(x^2 + 2*x + 2))*T^2 + 2*T + (2*x + 2)/(x + 2)) - sage: L = K.extension(g) + sage: L. = K.extension(g) sage: P = L.places_above(K.places()[1])[1] sage: a = (((x + 2)/(x + 1))*y^4 + ((x^4 + 2*x^2 + 1)/(x^2 + 2*x))*y^3 ....: + ((x^5 + x^2 + 2*x + 1)/(x^4 + 2*x^3 + x^2 + x + 1))*y^2 From e5bea05ce6ca55811bd06c5d97abcbf72fe83a63 Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Fri, 8 Mar 2024 20:17:27 +0100 Subject: [PATCH 186/518] Cleaned up `.random_element()`-doctest --- src/sage/algebras/quatalg/quaternion_algebra.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index c4ef097601a..5e42d9b1c11 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -2551,8 +2551,7 @@ def random_element(self, *args, **kwds): sage: B. = QuaternionAlgebra(211) sage: I = B.ideal([1, 1/4*j, 20*(i+k), 2/3*i]) - sage: x = I.random_element() # random - sage: x in I + sage: I.random_element() in I True """ return sum(ZZ.random_element(*args, **kwds) * g for g in self.gens()) From 7628632d84dde7dae86beffa07aef4b73c99442f Mon Sep 17 00:00:00 2001 From: Giorgos Mousa Date: Fri, 8 Mar 2024 23:29:30 +0200 Subject: [PATCH 187/518] More tests --- src/sage/matroids/matroid.pyx | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/sage/matroids/matroid.pyx b/src/sage/matroids/matroid.pyx index c5a2280b6f9..22de6f0974b 100644 --- a/src/sage/matroids/matroid.pyx +++ b/src/sage/matroids/matroid.pyx @@ -7764,6 +7764,19 @@ cdef class Matroid(SageObject): .. SEEALSO:: :meth:`~sage.matroids.matroid.Matroid.whitney_numbers` + + + TESTS:: + + sage: M = Matroid(groundset=[0,1,2], circuits=[[0]]) + sage: M.characteristic_polynomial() + 0 + sage: l = -1 + sage: for M in matroids.AllMatroids(6): # optional - matroid_database + ....: r = M.rank() + ....: assert M.characteristic_polynomial(l) == (-1)**r * M.tutte_polynomial(1-l, 0) + ....: if not M.loops(): + ....: assert (-1)**r * M.characteristic_polynomial(l) == sum(M.broken_circuit_complex().f_vector()) """ R = ZZ['l'] cdef list w = self.whitney_numbers() @@ -8110,6 +8123,16 @@ cdef class Matroid(SageObject): sage: M.broken_circuit_complex([5,4,3,2,1]) # needs sage.graphs Simplicial complex with vertex set (1, 2, 3, 4, 5) and facets {(1, 3, 5), (1, 4, 5), (2, 3, 5), (2, 4, 5)} + + TESTS:: + + sage: for M in matroids.AllMatroids(5): # optional - matroid_database + ....: r = M.rank() + ....: if r > 0 and not M.dual().loops(): + ....: C = SimplicialComplex(M.bases(), maximality_check=False) + ....: betti = C.betti() + ....: betti[0] -= 1 # reduced homology + ....: assert betti[r-1] == len(M.dual().broken_circuit_complex().facets()) """ from sage.topology.simplicial_complex import SimplicialComplex cdef int r = self.rank() From 4895293d3a5460d01b411923fb0be41db9cae530 Mon Sep 17 00:00:00 2001 From: dcoudert Date: Sat, 9 Mar 2024 12:15:46 +0100 Subject: [PATCH 188/518] improve _ford_fulkerson_chronicles and a few details --- src/sage/combinat/posets/posets.py | 113 +++++++++++++++-------------- 1 file changed, 59 insertions(+), 54 deletions(-) diff --git a/src/sage/combinat/posets/posets.py b/src/sage/combinat/posets/posets.py index 36f4d1eee10..9a6bd21616b 100644 --- a/src/sage/combinat/posets/posets.py +++ b/src/sage/combinat/posets/posets.py @@ -718,8 +718,8 @@ def Poset(data=None, element_labels=None, cover_relations=False, linear_extensio if len(data) == 2: # types 1 or 2 if callable(data[1]): # type 2 elements, function = data - relations = [[x, y] for x in elements for y in elements - if function(x, y)] + relations = ((x, y) for x in elements for y in elements + if function(x, y)) else: # type 1 elements, relations = data # check that relations are relations @@ -1079,10 +1079,10 @@ def __init__(self, hasse_diagram, elements, category, facade, key) -> None: for i in elements) # Relabel using the linear_extension. # So range(len(D)) becomes a linear extension of the poset. - rdict = {self._elements[i]: i for i in range(len(self._elements))} - self._hasse_diagram = HasseDiagram(hasse_diagram.relabel(rdict, inplace=False), data_structure="static_sparse") - self._element_to_vertex_dict = {self._elements[i]: i - for i in range(len(self._elements))} + rdict = {element: i for i, element in enumerate(self._elements)} + self._hasse_diagram = HasseDiagram(hasse_diagram.relabel(rdict, inplace=False), + data_structure="static_sparse") + self._element_to_vertex_dict = rdict self._is_facade = facade @lazy_attribute @@ -1110,8 +1110,8 @@ def _list(self): if self._is_facade: return self._elements else: - return tuple(self.element_class(self, self._elements[vertex], vertex) - for vertex in range(len(self._elements))) + return tuple(self.element_class(self, element, vertex) + for vertex, element in enumerate(self._elements)) # This defines the type (class) of elements of poset. Element = PosetElement @@ -2366,8 +2366,7 @@ def meet(self, x, y): mt = self._hasse_diagram._meet if mt[i, j] == -1: return None - else: - return self._vertex_to_element(mt[i, j]) + return self._vertex_to_element(mt[i, j]) def join(self, x, y): r""" @@ -2400,8 +2399,7 @@ def join(self, x, y): jn = self._hasse_diagram._join if jn[i, j] == -1: return None - else: - return self._vertex_to_element(jn[i, j]) + return self._vertex_to_element(jn[i, j]) def is_d_complete(self) -> bool: r""" @@ -2992,12 +2990,11 @@ def compare_elements(self, x, y): i, j = map(self._element_to_vertex, (x, y)) if i == j: return 0 - elif self._hasse_diagram.is_less_than(i, j): + if self._hasse_diagram.is_less_than(i, j): return -1 - elif self._hasse_diagram.is_less_than(j, i): + if self._hasse_diagram.is_less_than(j, i): return 1 - else: - return None + return None def minimal_elements(self): """ @@ -3049,8 +3046,7 @@ def bottom(self): hasse_bot = self._hasse_diagram.bottom() if hasse_bot is None: return None - else: - return self._vertex_to_element(hasse_bot) + return self._vertex_to_element(hasse_bot) def has_bottom(self): """ @@ -3105,8 +3101,7 @@ def top(self): hasse_top = self._hasse_diagram.top() if hasse_top is None: return None - else: - return self._vertex_to_element(hasse_top) + return self._vertex_to_element(hasse_top) def has_top(self): """ @@ -3185,7 +3180,7 @@ def height(self, certificate=False): max_chain.reverse() return (height, max_chain) - def has_isomorphic_subposet(self, other): + def has_isomorphic_subposet(self, other) -> bool: """ Return ``True`` if the poset contains a subposet isomorphic to ``other``. @@ -3210,13 +3205,10 @@ def has_isomorphic_subposet(self, other): sage: len([P for P in Posets(5) if P.has_isomorphic_subposet(D)]) 11 - """ if not hasattr(other, 'hasse_diagram'): raise TypeError("'other' is not a finite poset") - if self._hasse_diagram.transitive_closure().subgraph_search(other._hasse_diagram.transitive_closure(), induced=True) is None: - return False - return True + return self._hasse_diagram.transitive_closure().subgraph_search(other._hasse_diagram.transitive_closure(), induced=True) is not None def is_bounded(self) -> bool: """ @@ -3331,12 +3323,12 @@ def is_chain_of_poset(self, elms, ordered=False) -> bool: if ordered: sorted_o = elms return all(self.lt(a, b) for a, b in zip(sorted_o, sorted_o[1:])) - else: - # _element_to_vertex can be assumed to be a linear extension - # of the poset according to the documentation of class - # HasseDiagram. - sorted_o = sorted(elms, key=self._element_to_vertex) - return all(self.le(a, b) for a, b in zip(sorted_o, sorted_o[1:])) + + # _element_to_vertex can be assumed to be a linear extension + # of the poset according to the documentation of class + # HasseDiagram. + sorted_o = sorted(elms, key=self._element_to_vertex) + return all(self.le(a, b) for a, b in zip(sorted_o, sorted_o[1:])) def is_antichain_of_poset(self, elms): """ @@ -3494,14 +3486,14 @@ def is_EL_labelling(self, f, return_raising_chains=False): max_chains = sorted([[label_dict[(chain[i], chain[i + 1])] for i in range(len(chain) - 1)] for chain in P.maximal_chains_iterator()]) - if max_chains[0] != sorted(max_chains[0]) or any(max_chains[i] == sorted(max_chains[i]) for i in range(1, len(max_chains))): + if (max_chains[0] != sorted(max_chains[0]) or + any(max_chains[i] == sorted(max_chains[i]) for i in range(1, len(max_chains)))): return False - elif return_raising_chains: + if return_raising_chains: raising_chains[(a, b)] = max_chains[0] if return_raising_chains: return raising_chains - else: - return True + return True def dimension(self, certificate=False, *, solver=None, integrality_tolerance=1e-3): r""" @@ -3650,7 +3642,7 @@ def init_LP(k, cycles, inc_P): """ p = MixedIntegerLinearProgram(constraint_generation=True, solver=solver) b = p.new_variable(binary=True) - for (u, v) in inc_P: # Each point has a color + for u, v in inc_P: # Each point has a color p.add_constraint(p.sum(b[(u, v), i] for i in range(k)) == 1) p.add_constraint(p.sum(b[(v, u), i] for i in range(k)) == 1) for cycle in cycles: # No monochromatic set @@ -4632,8 +4624,7 @@ def is_isomorphic(self, other, **kwds): if hasattr(other, 'hasse_diagram'): return self.hasse_diagram().is_isomorphic(other.hasse_diagram(), **kwds) - else: - raise TypeError("'other' is not a finite poset") + raise TypeError("'other' is not a finite poset") def isomorphic_subposets_iterator(self, other): """ @@ -9114,33 +9105,47 @@ def _ford_fulkerson_chronicle(G, s, t, a): sage: next(ffc) (11, 2) """ - # pi: potential function as a dictionary. - pi = {v: 0 for v in G} + n = G.order() + m = G.size() + + # Make a copy of the graph with vertices relabeled 0..n-1 + index_to_vertex = list(G) + vertex_to_index = {u: i for i, u in enumerate(index_to_vertex)} + G = G.relabel(perm=vertex_to_index, inplace=False) + s = vertex_to_index[s] + t = vertex_to_index[t] + # Associate each edge to an integer + index_to_edge = list(G.edge_iterator(labels=False)) + edge_to_index = {e: i for i, e in enumerate(index_to_edge)} + # Change the cost function to a vector indexed by edge labels + a = [a[index_to_vertex[u], index_to_vertex[v]] for u, v in index_to_edge] + + # pi: potential function as a vector indexed by vertices. + pi = [0 for _ in range(n)] # p: value of the potential pi. p = 0 - # f: flow function as a dictionary. - f = {edge: 0 for edge in G.edge_iterator(labels=False)} + # f: flow function as a vector indexed by edge labels + f = [0 for _ in range(m)] # val: value of the flow f. (Cannot call it v due to Python's asinine # handling of for loops.) val = 0 - # capacity: capacity function as a dictionary. Here, just the - # indicator function of the set of arcs of G. - capacity = {edge: 1 for edge in G.edge_iterator(labels=False)} + # capacity: capacity function as a vector indexed by edge labels. Here, just + # the indicator function of the set of arcs of G. + capacity = [1 for _ in range(m)] while True: # Step MC1 in Britz-Fomin, Algorithm 7.2. # Gprime: directed graph G' from Britz-Fomin, Section 7. - Gprime = DiGraph() - Gprime.add_vertices(G) - for u, v in G.edge_iterator(labels=False): - if pi[v] - pi[u] == a[(u, v)]: - if f[(u, v)] < capacity[(u, v)]: + Gprime = DiGraph(n) + for e_index, (u, v) in enumerate(index_to_edge): + if pi[v] - pi[u] == a[e_index]: + if f[e_index] < capacity[e_index]: Gprime.add_edge(u, v) - elif f[(u, v)] > 0: + elif f[e_index] > 0: Gprime.add_edge(v, u) # X: list of vertices of G' reachable from s @@ -9150,10 +9155,10 @@ def _ford_fulkerson_chronicle(G, s, t, a): shortest_path = Gprime.shortest_path(s, t, by_weight=False) shortest_path_in_edges = zip(shortest_path[:-1], shortest_path[1:]) for u, v in shortest_path_in_edges: - if v in G.neighbor_out_iterator(u): - f[(u, v)] += 1 + if G.has_edge(u, v): + f[edge_to_index[u, v]] += 1 else: - f[(v, u)] -= 1 + f[edge_to_index[v, u]] -= 1 val += 1 else: # Step MC2b in Britz-Fomin, Algorithm 7.2. From 58b85065d32583c3d97ed1db192ce0b02e6d5ff0 Mon Sep 17 00:00:00 2001 From: dcoudert Date: Sat, 9 Mar 2024 12:38:55 +0100 Subject: [PATCH 189/518] further details --- src/sage/combinat/posets/posets.py | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/src/sage/combinat/posets/posets.py b/src/sage/combinat/posets/posets.py index 9a6bd21616b..68ac9a62e2a 100644 --- a/src/sage/combinat/posets/posets.py +++ b/src/sage/combinat/posets/posets.py @@ -8301,7 +8301,15 @@ def frank_network(self): sage: ps = [[16,12,14,-13],[[12,14],[14,-13],[12,16],[16,-13]]] sage: G, e = Poset(ps).frank_network() sage: G.edges(sort=True) - [((-1, 0), (0, -13), None), ((-1, 0), (0, 12), None), ((-1, 0), (0, 14), None), ((-1, 0), (0, 16), None), ((0, -13), (1, -13), None), ((0, -13), (1, 12), None), ((0, -13), (1, 14), None), ((0, -13), (1, 16), None), ((0, 12), (1, 12), None), ((0, 14), (1, 12), None), ((0, 14), (1, 14), None), ((0, 16), (1, 12), None), ((0, 16), (1, 16), None), ((1, -13), (2, 0), None), ((1, 12), (2, 0), None), ((1, 14), (2, 0), None), ((1, 16), (2, 0), None)] + [((-1, 0), (0, -13), None), ((-1, 0), (0, 12), None), + ((-1, 0), (0, 14), None), ((-1, 0), (0, 16), None), + ((0, -13), (1, -13), None), ((0, -13), (1, 12), None), + ((0, -13), (1, 14), None), ((0, -13), (1, 16), None), + ((0, 12), (1, 12), None), ((0, 14), (1, 12), None), + ((0, 14), (1, 14), None), ((0, 16), (1, 12), None), + ((0, 16), (1, 16), None), ((1, -13), (2, 0), None), + ((1, 12), (2, 0), None), ((1, 14), (2, 0), None), + ((1, 16), (2, 0), None)] sage: e {((-1, 0), (0, -13)): 0, ((-1, 0), (0, 12)): 0, @@ -9069,14 +9077,16 @@ def _ford_fulkerson_chronicle(G, s, t, a): EXAMPLES:: sage: from sage.combinat.posets.posets import _ford_fulkerson_chronicle - sage: G = DiGraph({1: [3,6,7], 2: [4], 3: [7], 4: [], 6: [7,8], 7: [9], 8: [9,12], 9: [], 10: [], 12: []}) + sage: G = DiGraph({1: [3, 6, 7], 2: [4], 3: [7], 4: [], 6: [7, 8], + ....: 7: [9], 8: [9, 12], 9: [], 10: [], 12: []}) sage: s = 1 sage: t = 9 sage: (1, 6, None) in G.edges(sort=False) True sage: (1, 6) in G.edges(sort=False) False - sage: a = {(1, 6): 4, (2, 4): 0, (1, 3): 4, (1, 7): 1, (3, 7): 6, (7, 9): 1, (6, 7): 3, (6, 8): 1, (8, 9): 0, (8, 12): 2} + sage: a = {(1, 6): 4, (2, 4): 0, (1, 3): 4, (1, 7): 1, (3, 7): 6, + ....: (7, 9): 1, (6, 7): 3, (6, 8): 1, (8, 9): 0, (8, 12): 2} sage: ffc = _ford_fulkerson_chronicle(G, s, t, a) sage: next(ffc) (1, 0) @@ -9114,10 +9124,10 @@ def _ford_fulkerson_chronicle(G, s, t, a): G = G.relabel(perm=vertex_to_index, inplace=False) s = vertex_to_index[s] t = vertex_to_index[t] - # Associate each edge to an integer + # Associate each edge to an integer, its index index_to_edge = list(G.edge_iterator(labels=False)) edge_to_index = {e: i for i, e in enumerate(index_to_edge)} - # Change the cost function to a vector indexed by edge labels + # Change the cost function to a vector indexed by edge indices a = [a[index_to_vertex[u], index_to_vertex[v]] for u, v in index_to_edge] # pi: potential function as a vector indexed by vertices. @@ -9125,14 +9135,14 @@ def _ford_fulkerson_chronicle(G, s, t, a): # p: value of the potential pi. p = 0 - # f: flow function as a vector indexed by edge labels + # f: flow function as a vector indexed by edge indices f = [0 for _ in range(m)] # val: value of the flow f. (Cannot call it v due to Python's asinine # handling of for loops.) val = 0 - # capacity: capacity function as a vector indexed by edge labels. Here, just - # the indicator function of the set of arcs of G. + # capacity: capacity function as a vector indexed by edge indices. Here, + # just the indicator function of the set of arcs of G. capacity = [1 for _ in range(m)] while True: @@ -9155,14 +9165,14 @@ def _ford_fulkerson_chronicle(G, s, t, a): shortest_path = Gprime.shortest_path(s, t, by_weight=False) shortest_path_in_edges = zip(shortest_path[:-1], shortest_path[1:]) for u, v in shortest_path_in_edges: - if G.has_edge(u, v): + if (u, v) in edge_to_index: f[edge_to_index[u, v]] += 1 else: f[edge_to_index[v, u]] -= 1 val += 1 else: # Step MC2b in Britz-Fomin, Algorithm 7.2. - for v in G: + for v in range(n): if v not in X: pi[v] += 1 p += 1 From 1009a24da233cf507f02ddee602cab5208ba53a0 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 9 Mar 2024 11:04:22 -0800 Subject: [PATCH 190/518] src/sage/doctest/forker.py (showwarning_with_traceback): Elide doctester internals from traceback --- src/sage/doctest/forker.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/sage/doctest/forker.py b/src/sage/doctest/forker.py index 5541e1aef8c..90570e1cbae 100644 --- a/src/sage/doctest/forker.py +++ b/src/sage/doctest/forker.py @@ -271,6 +271,10 @@ def showwarning_with_traceback(message, category, filename, lineno, file=None, l # Get traceback to display in warning tb = traceback.extract_stack() tb = tb[:-1] # Drop this stack frame for showwarning_with_traceback() + for i, frame_summary in enumerate(tb): + if frame_summary.filename.endswith('sage/doctest/forker.py') and frame_summary.name == 'compile_and_execute': + tb = tb[i + 1:] + break # Format warning lines = ["doctest:warning\n"] # Match historical warning messages in doctests From 2e703b74b66ec131756c51e2195bd7434ef18b4c Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 9 Mar 2024 11:24:43 -0800 Subject: [PATCH 191/518] src/sage/doctest/forker.py: Update doctest --- src/sage/doctest/forker.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/sage/doctest/forker.py b/src/sage/doctest/forker.py index 90570e1cbae..55b4adadda9 100644 --- a/src/sage/doctest/forker.py +++ b/src/sage/doctest/forker.py @@ -258,8 +258,7 @@ def showwarning_with_traceback(message, category, filename, lineno, file=None, l sage: from sage.doctest.forker import showwarning_with_traceback sage: showwarning_with_traceback("bad stuff", UserWarning, "myfile.py", 0) - doctest:warning - ... + doctest:warning... File "", line 1, in showwarning_with_traceback("bad stuff", UserWarning, "myfile.py", Integer(0)) : From d45cdef3fb383f343c3df5ad65c589fd6cdc5c86 Mon Sep 17 00:00:00 2001 From: Henry Ehrhard Date: Sat, 9 Mar 2024 16:15:37 -0800 Subject: [PATCH 192/518] Add description of algorithm --- src/sage/graphs/graph.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/sage/graphs/graph.py b/src/sage/graphs/graph.py index fb494381a17..f063f3e4b0b 100644 --- a/src/sage/graphs/graph.py +++ b/src/sage/graphs/graph.py @@ -3928,6 +3928,17 @@ def chromatic_symmetric_function(self, R=None): - ``R`` -- (optional) the base ring for the symmetric functions; this uses `\ZZ` by default + ALGORITHM: + + We traverse a binary tree whose leaves correspond to + subsets of edges, and whose internal vertices at depth `d` + correspond to a choice of whether to include the `d`-th + edge in a given subset. The components of the induced + subgraph are incrementally updated with a disjoint-set + forest. If the next edge would introduce a cycle to the + subset, we prune the branch as the terms produced by the + two subtrees cancel in this case. + EXAMPLES:: sage: s = SymmetricFunctions(ZZ).s() # needs sage.combinat sage.modules From 5f36e7e040e2a70528c8e79335e70ac696bb689d Mon Sep 17 00:00:00 2001 From: Henry Ehrhard Date: Sat, 9 Mar 2024 16:32:50 -0800 Subject: [PATCH 193/518] Implement suggested changes --- src/sage/graphs/graph.py | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/sage/graphs/graph.py b/src/sage/graphs/graph.py index f063f3e4b0b..095d615cc90 100644 --- a/src/sage/graphs/graph.py +++ b/src/sage/graphs/graph.py @@ -3985,10 +3985,10 @@ def chromatic_symmetric_function(self, R=None): 120*e[5] """ from sage.combinat.sf.sf import SymmetricFunctions + from sage.combinat.partition import _Partitions if R is None: R = ZZ p = SymmetricFunctions(R).p() - Par = p.basis().keys() # Dict to store parent of each vertex in disjoint-set forest # representing components of current induced subgraph. @@ -4005,23 +4005,23 @@ def summand(stack, dsf, sizes): # Compute powersum terms obtained by adding each subset of # edges in stack to current subgraph. if not stack: - root_sizes = [s for v, s in sizes.items() if dsf[v] is None] - return p.monomial(Par(sorted(root_sizes, reverse=True))) - else: - ret = p.zero() - e = stack.pop() - u = find(dsf, e[0]) - v = find(dsf, e[1]) - # Terms cancel if edge creates a cycle. - if u is not v: - ret = summand(stack, dsf, sizes) - dsf[v] = u - sizes[u] += sizes[v] - ret -= summand(stack, dsf, sizes) - dsf[v] = None - sizes[u] -= sizes[v] - stack.append(e) - return ret + return p.monomial(_Partitions(sorted( + [s for v, s in sizes.items() if dsf[v] is None], + reverse=True))) + ret = p.zero() + e = stack.pop() + u = find(dsf, e[0]) + v = find(dsf, e[1]) + # Terms cancel if edge creates a cycle. + if u is not v: + ret = summand(stack, dsf, sizes) + dsf[v] = u + sizes[u] += sizes[v] + ret -= summand(stack, dsf, sizes) + dsf[v] = None + sizes[u] -= sizes[v] + stack.append(e) + return ret return summand(list(self.edges(labels=False)), dsf, sizes) From e689b4151dbf0dde9ae8d95602969381b00b0f7d Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Sun, 10 Mar 2024 12:33:46 +0000 Subject: [PATCH 194/518] Update package dependencies for Fedora and Arch distros --- build/pkgs/cliquer/distros/arch.txt | 1 + build/pkgs/fplll/distros/arch.txt | 1 + build/pkgs/libbraiding/distros/fedora.txt | 2 +- build/pkgs/linbox/distros/fedora.txt | 2 +- build/pkgs/maxima/distros/fedora.txt | 4 ++++ build/pkgs/primecount/distros/debian.txt | 1 + grayskull | 1 + meson | 1 + 8 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 build/pkgs/cliquer/distros/arch.txt create mode 100644 build/pkgs/fplll/distros/arch.txt create mode 100644 build/pkgs/maxima/distros/fedora.txt create mode 100644 build/pkgs/primecount/distros/debian.txt create mode 160000 grayskull create mode 160000 meson diff --git a/build/pkgs/cliquer/distros/arch.txt b/build/pkgs/cliquer/distros/arch.txt new file mode 100644 index 00000000000..524938db8e3 --- /dev/null +++ b/build/pkgs/cliquer/distros/arch.txt @@ -0,0 +1 @@ +cliquer diff --git a/build/pkgs/fplll/distros/arch.txt b/build/pkgs/fplll/distros/arch.txt new file mode 100644 index 00000000000..58e97dac521 --- /dev/null +++ b/build/pkgs/fplll/distros/arch.txt @@ -0,0 +1 @@ +fplll diff --git a/build/pkgs/libbraiding/distros/fedora.txt b/build/pkgs/libbraiding/distros/fedora.txt index 3767599b368..b7873e1b3c6 100644 --- a/build/pkgs/libbraiding/distros/fedora.txt +++ b/build/pkgs/libbraiding/distros/fedora.txt @@ -1 +1 @@ -libbraiding +libbraiding-devel diff --git a/build/pkgs/linbox/distros/fedora.txt b/build/pkgs/linbox/distros/fedora.txt index 891a35cb224..e0fb5eb3ccc 100644 --- a/build/pkgs/linbox/distros/fedora.txt +++ b/build/pkgs/linbox/distros/fedora.txt @@ -1 +1 @@ -linbox +linbox-devel diff --git a/build/pkgs/maxima/distros/fedora.txt b/build/pkgs/maxima/distros/fedora.txt new file mode 100644 index 00000000000..ac87cd96f40 --- /dev/null +++ b/build/pkgs/maxima/distros/fedora.txt @@ -0,0 +1,4 @@ +# For ecl integration +maxima-runtime-ecl +# For binary +maxima diff --git a/build/pkgs/primecount/distros/debian.txt b/build/pkgs/primecount/distros/debian.txt new file mode 100644 index 00000000000..a783045f297 --- /dev/null +++ b/build/pkgs/primecount/distros/debian.txt @@ -0,0 +1 @@ +libprimecount-dev diff --git a/grayskull b/grayskull new file mode 160000 index 00000000000..5e74f1daa33 --- /dev/null +++ b/grayskull @@ -0,0 +1 @@ +Subproject commit 5e74f1daa33168642351563f5d73110e09dbd689 diff --git a/meson b/meson new file mode 160000 index 00000000000..5fdf7d7d8ca --- /dev/null +++ b/meson @@ -0,0 +1 @@ +Subproject commit 5fdf7d7d8caebb66f96f515c954182015caad492 From a5b8acd6d9eb4d1af8c5efc9753447aa78aecc18 Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Sun, 10 Mar 2024 12:35:23 +0000 Subject: [PATCH 195/518] Remove subprojects grayskull and meson --- grayskull | 1 - meson | 1 - 2 files changed, 2 deletions(-) delete mode 160000 grayskull delete mode 160000 meson diff --git a/grayskull b/grayskull deleted file mode 160000 index 5e74f1daa33..00000000000 --- a/grayskull +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 5e74f1daa33168642351563f5d73110e09dbd689 diff --git a/meson b/meson deleted file mode 160000 index 5fdf7d7d8ca..00000000000 --- a/meson +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 5fdf7d7d8caebb66f96f515c954182015caad492 From 25efb19a9c5a490b035ffa95542e6b096c8eb077 Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Sun, 22 Oct 2023 13:34:21 +0000 Subject: [PATCH 196/518] Add config for ruff --- .devcontainer/devcontainer.json | 5 ++--- .vscode/extensions.json | 3 ++- .vscode/settings.json | 5 ----- pyproject.toml | 13 +++++++++++++ 4 files changed, 17 insertions(+), 9 deletions(-) create mode 100644 pyproject.toml diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index c9b3aeb71a6..40b6f52c00a 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -22,13 +22,12 @@ "vscode": { "extensions": [ "guyskk.language-cython", - "ms-python.isort", "ms-toolsai.jupyter", "ms-python.vscode-pylance", - "ms-python.pylint", "ms-python.python", "lextudio.restructuredtext", - "trond-snekvik.simple-rst" + "trond-snekvik.simple-rst", + "charliermarsh.ruff" ] } } diff --git a/.vscode/extensions.json b/.vscode/extensions.json index d0efd087bc9..a304fa28467 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -2,6 +2,7 @@ // List of extensions which should be recommended for developers when a workspace is opened for the first time. // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. "recommendations": [ - "ms-python.python" + "ms-python.python", + "charliermarsh.ruff" ], } diff --git a/.vscode/settings.json b/.vscode/settings.json index 86885e6ea4a..86eac03ffe9 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -24,11 +24,6 @@ "--doctest-modules" ], "python.testing.unittestEnabled": false, - "python.linting.pycodestyleEnabled": true, - "python.linting.enabled": true, - // The following pycodestyle arguments are the same as the pycodestyle-minimal - // tox environnment, see the file SAGE_ROOT/src/tox.ini - "python.linting.pycodestyleArgs": ["--select= E111,E21,E222,E225,E227,E228,E25,E271,E303,E305,E306,E401,E502,E701,E702,E703,E71,E72,W291,W293,W391,W605"], "cSpell.words": [ "furo", "Conda", diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000000..d5ce3e63cda --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,13 @@ +[tool.ruff] +# Assume Python 3.9 +target-version = "py39" + +select = [ + "E", # pycodestyle errors - https://docs.astral.sh/ruff/rules/#error-e + "F", # pyflakes - https://docs.astral.sh/ruff/rules/#pyflakes-f + "I", # isort - https://docs.astral.sh/ruff/rules/#isort-i + "PL", # pylint - https://docs.astral.sh/ruff/rules/#pylint-pl +] +ignore = [ + "E501", # Line too long - hard to avoid in doctests, and better handled by black. +] From f5b080e4dfa375de1c7c6a9f59859653b572612c Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 24 Feb 2024 10:44:39 -0800 Subject: [PATCH 197/518] src/ruff.toml: Move ruff config here --- pyproject.toml => src/ruff.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) rename pyproject.toml => src/ruff.toml (86%) diff --git a/pyproject.toml b/src/ruff.toml similarity index 86% rename from pyproject.toml rename to src/ruff.toml index d5ce3e63cda..d6dd6fed1bd 100644 --- a/pyproject.toml +++ b/src/ruff.toml @@ -1,4 +1,5 @@ -[tool.ruff] +# https://docs.astral.sh/ruff/configuration/#config-file-discovery + # Assume Python 3.9 target-version = "py39" From 9914a6c21221fbcdc1d67b0c88fec836aea8758f Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 27 Feb 2024 09:32:15 -0800 Subject: [PATCH 198/518] ruff.toml: Move here from src/ --- src/ruff.toml => ruff.toml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/ruff.toml => ruff.toml (100%) diff --git a/src/ruff.toml b/ruff.toml similarity index 100% rename from src/ruff.toml rename to ruff.toml From 00e8b6f8cf0b09ad0d066fa48234051ddf80e6d8 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 9 Mar 2024 14:43:16 -0800 Subject: [PATCH 199/518] 29231: add intersphinx mapping for scipy --- src/doc/en/reference/matrices/index.rst | 2 +- src/sage/calculus/desolvers.py | 11 ++++---- .../differentiable/integrated_curve.py | 14 +++++----- src/sage/matrix/matrix_double_dense.pyx | 26 ++++++++++++------- src/sage/matroids/matroids_plot_helpers.py | 5 ++-- src/sage/modules/vector_double_dense.pyx | 4 +-- src/sage/numerical/optimize.py | 16 +++++------- src/sage_docbuild/conf.py | 2 ++ 8 files changed, 45 insertions(+), 35 deletions(-) diff --git a/src/doc/en/reference/matrices/index.rst b/src/doc/en/reference/matrices/index.rst index 89453635472..889a33bd717 100644 --- a/src/doc/en/reference/matrices/index.rst +++ b/src/doc/en/reference/matrices/index.rst @@ -29,7 +29,7 @@ following additional ways to compute with matrices: - The GSL C-library is included with Sage, and can be used via Cython. -- The ``scipy`` module provides support for +- The :mod:`scipy:scipy` module provides support for *sparse* numerical linear algebra, among many other things. - The ``numpy`` module, which you load by typing diff --git a/src/sage/calculus/desolvers.py b/src/sage/calculus/desolvers.py index 1a4692f3499..afeee69ce89 100644 --- a/src/sage/calculus/desolvers.py +++ b/src/sage/calculus/desolvers.py @@ -27,8 +27,8 @@ order equations, return list of points. - :func:`desolve_odeint` - Solve numerically a system of first-order ordinary - differential equations using ``odeint`` from `scipy.integrate module. - `_ + differential equations using :func:`~scipy:scipy.integrate.odeint` from + the module :mod:`scipy:scipy.integrate`. - :func:`desolve_system` - Solve a system of 1st order ODEs of any size using Maxima. Initial conditions are optional. @@ -1499,7 +1499,7 @@ def desolve_odeint(des, ics, times, dvars, ivar=None, compute_jac=False, args=() mxstep=0, mxhnil=0, mxordn=12, mxords=5, printmessg=0): r""" Solve numerically a system of first-order ordinary differential equations - using ``odeint`` from scipy.integrate module. + using :func:`scipy:scipy.integrate.odeint`. INPUT: @@ -1517,8 +1517,9 @@ def desolve_odeint(des, ics, times, dvars, ivar=None, compute_jac=False, args=() - ``compute_jac`` -- boolean. If True, the Jacobian of ``des`` is computed and used during the integration of stiff systems. Default value is False. - Other Parameters (taken from the documentation of odeint function from `scipy.integrate module. - `_) + Other Parameters (taken from the documentation of the + :func:`~scipy:scipy.integrate.odeint` function from + :mod:`scipy:scipy.integrate`): - ``rtol``, ``atol`` : float The input parameters ``rtol`` and ``atol`` determine the error diff --git a/src/sage/manifolds/differentiable/integrated_curve.py b/src/sage/manifolds/differentiable/integrated_curve.py index b0dd12bc744..063611becce 100644 --- a/src/sage/manifolds/differentiable/integrated_curve.py +++ b/src/sage/manifolds/differentiable/integrated_curve.py @@ -949,7 +949,7 @@ def solve(self, step=None, method='odeint', solution_key=None, use for the integration of the curve; available algorithms are: * ``'odeint'`` - makes use of - `scipy.integrate.odeint `_ + :func:`scipy:scipy.integrate.odeint` via Sage solver :func:`~sage.calculus.desolvers.desolve_odeint`; ``odeint`` invokes the LSODA algorithm of the @@ -961,9 +961,9 @@ def solve(self, step=None, method='odeint', solution_key=None, makes use of Maxima's dynamics package via Sage solver :func:`~sage.calculus.desolvers.desolve_system_rk4` (quite slow) * ``'dopri5'`` - Dormand-Prince Runge-Kutta of order (4)5 provided by - `scipy.integrate.ode `_ + :obj:`scipy:scipy.integrate.ode` * ``'dop853'`` - Dormand-Prince Runge-Kutta of order 8(5,3) provided by - `scipy.integrate.ode `_ + :obj:`scipy:scipy.integrate.ode` and those provided by ``GSL`` via Sage class :class:`~sage.calculus.ode.ode_solver`: @@ -1425,8 +1425,8 @@ def solve_across_charts(self, charts=None, step=None, solution_key=None, Integrate the curve numerically over the domain of integration, with the ability to switch chart mid-integration. - The only supported solver is - `scipy.integrate.ode `_, because it supports basic event handling, needed to detect when the + The only supported solver is :obj:`scipy:scipy.integrate.ode`, + because it supports basic event handling, needed to detect when the curve is reaching the frontier of the chart. This is an adaptive step solver. So the ``step`` is not the step of integration but instead the step used to peak at the current chart, and switch if needed. @@ -1525,8 +1525,8 @@ def solve_across_charts(self, charts=None, step=None, solution_key=None, The integration is done as usual, but using the method :meth:`solve_across_charts` instead of :meth:`solve`. This forces the - use of ``scipy.integrate.ode`` as the solver, because of event handling - support. + use of :obj:`scipy:scipy.integrate.ode` as the solver, because of event + handling support. The argument ``verbose=True`` will cause the solver to write a small message each time it is switching chart:: diff --git a/src/sage/matrix/matrix_double_dense.pyx b/src/sage/matrix/matrix_double_dense.pyx index ede06f07f7f..6877a924de2 100644 --- a/src/sage/matrix/matrix_double_dense.pyx +++ b/src/sage/matrix/matrix_double_dense.pyx @@ -594,8 +594,8 @@ cdef class Matrix_double_dense(Matrix_numpy_dense): ALGORITHM: - Computation is performed by the ``norm()`` function of - the SciPy/NumPy library. + Computation is performed by the :func:`~scipy:scipy.linalg.norm` + function of the SciPy/NumPy library. EXAMPLES: @@ -739,7 +739,7 @@ cdef class Matrix_double_dense(Matrix_numpy_dense): ALGORITHM: The singular values come from the SVD decomposition - computed by SciPy/NumPy. + computed by SciPy/NumPy using :func:`scipy:scipy.linalg.svd`. EXAMPLES: @@ -1073,7 +1073,7 @@ cdef class Matrix_double_dense(Matrix_numpy_dense): - ``'default'`` - applicable to any matrix with double-precision floating point entries. - Uses the :meth:`~scipy.linalg.eigvals` method from SciPy. + Uses the :func:`~scipy:scipy.linalg.eigvals` function from SciPy. - ``'symmetric'`` - converts the matrix into a real matrix (i.e. with entries from :class:`~sage.rings.real_double.RDF`), @@ -1081,9 +1081,9 @@ cdef class Matrix_double_dense(Matrix_numpy_dense): algorithm can be significantly faster than the ``'default'`` algorithm. - - ``'hermitian'`` - uses the :meth:`~scipy.linalg.eigh` method - from SciPy, which applies only to real symmetric or complex - Hermitian matrices. Since Hermitian is defined as a matrix + - ``'hermitian'`` - uses the :func:`~scipy:scipy.linalg.eigh` + function from SciPy, which applies only to real symmetric or + complex Hermitian matrices. Since Hermitian is defined as a matrix equaling its conjugate-transpose, for a matrix with real entries this property is equivalent to being symmetric. This algorithm can be significantly faster than the @@ -1707,6 +1707,10 @@ cdef class Matrix_double_dense(Matrix_numpy_dense): Find a solution `X` to the equation `A X = B` if ``self`` is a square matrix `A`. + ALGORITHM: + + Uses the function :func:`scipy:scipy.linalg.solve` from SciPy. + TESTS:: sage: # needs sage.symbolic @@ -1730,6 +1734,10 @@ cdef class Matrix_double_dense(Matrix_numpy_dense): Compute a least-squares solution `X` to the equation `A X = B` where ``self`` is the matrix `A`. + ALGORITHM: + + Uses the function :func:`scipy:scipy.linalg.lstsq` from SciPy. + TESTS:: sage: A = matrix(RDF, 3, 2, [1, 3, 4, 2, 0, -3]) @@ -1754,7 +1762,7 @@ cdef class Matrix_double_dense(Matrix_numpy_dense): ALGORITHM: - Use numpy + Uses :func:`scipy:scipy.linalg.det`. EXAMPLES:: @@ -2037,7 +2045,7 @@ cdef class Matrix_double_dense(Matrix_numpy_dense): ALGORITHM: - Calls "linalg.qr" from SciPy, which is in turn an + Calls :func:`scipy:scipy.linalg.qr` from SciPy, which is in turn an interface to LAPACK routines. EXAMPLES: diff --git a/src/sage/matroids/matroids_plot_helpers.py b/src/sage/matroids/matroids_plot_helpers.py index 3c084931cce..b7a3d7d6d03 100644 --- a/src/sage/matroids/matroids_plot_helpers.py +++ b/src/sage/matroids/matroids_plot_helpers.py @@ -20,8 +20,9 @@ via an optimization that gives aesthetically pleasing point placement (in some sense. This is not yet implemented). One can then use ``createline`` function to produce sequence of ``100`` points on a smooth - curve containing the points in the specified line which inturn uses - ``scipy.interpolate.splprep`` and ``scipy.interpolate.splev``. Then one + curve containing the points in the specified line which in turn uses + :func:`scipy:scipy.interpolate.splprep` and + :func:`scipy:scipy.interpolate.splev`. Then one can use sage's graphics primitives ``line``, ``point``, ``text`` and ``points`` to produce graphics object containing points (ground set elements) and lines (for a rank 3 matroid, these are flats of rank 2 of diff --git a/src/sage/modules/vector_double_dense.pyx b/src/sage/modules/vector_double_dense.pyx index 74c73270ffc..1d5f6e8c4ab 100644 --- a/src/sage/modules/vector_double_dense.pyx +++ b/src/sage/modules/vector_double_dense.pyx @@ -395,8 +395,8 @@ cdef class Vector_double_dense(Vector_numpy_dense): ALGORITHM: - Computation is performed by the ``norm()`` function of - the SciPy/NumPy library. + Computation is performed by the :func:`~scipy:scipy.linalg.norm` + function of the SciPy/NumPy library. EXAMPLES: diff --git a/src/sage/numerical/optimize.py b/src/sage/numerical/optimize.py index 35c316e905d..54262183b1b 100644 --- a/src/sage/numerical/optimize.py +++ b/src/sage/numerical/optimize.py @@ -33,8 +33,8 @@ def find_root(f, a, b, xtol=10e-13, rtol=2.0**-50, maxiter=100, full_output=Fals to lie within ``xtol`` of the value return. Should be `\geq 0`. The routine modifies this to take into account the relative precision of doubles. By default, rtol is ``4*numpy.finfo(float).eps``, the - minimum allowed value for ``scipy.optimize.brentq``, which is what - this method uses underneath. This value is equal to ``2.0**-50`` for + minimum allowed value for :func:`scipy:scipy.optimize.brentq`, which is + what this method uses underneath. This value is equal to ``2.0**-50`` for IEEE-754 double precision floats as used by Python. - ``maxiter`` -- integer; if convergence is not achieved in @@ -274,9 +274,7 @@ def find_local_minimum(f, a, b, tol=1.48e-08, maxfun=500): ALGORITHM: - Uses `scipy.optimize.fminbound - `_ - which uses Brent's method. + Uses :func:`scipy:scipy.optimize.fminbound` which uses Brent's method. AUTHOR: @@ -338,8 +336,8 @@ def minimize(func, x0, gradient=None, hessian=None, algorithm="default", .. NOTE:: For additional information on the algorithms implemented in this function, - consult SciPy's `documentation on optimization and root - finding `_ + consult SciPy's :mod:`documentation on optimization and root + finding `. EXAMPLES: @@ -650,8 +648,8 @@ def find_fit(data, model, initial_guess=None, parameters=None, variables=None, s ALGORITHM: - Uses ``scipy.optimize.leastsq`` which in turn uses MINPACK's lmdif and - lmder algorithms. + Uses :func:`scipy:scipy.optimize.leastsq` which in turn uses MINPACK's + ``lmdif`` and ``lmder`` algorithms. """ import numpy diff --git a/src/sage_docbuild/conf.py b/src/sage_docbuild/conf.py index c5329e79cfd..db6ef6a051b 100644 --- a/src/sage_docbuild/conf.py +++ b/src/sage_docbuild/conf.py @@ -277,6 +277,8 @@ def set_intersphinx_mappings(app, config): 'python': ('https://docs.python.org/', os.path.join(SAGE_DOC_SRC, "common", "python{}.inv".format(python_version))), + 'scipy': ('https://docs.scipy.org/doc/scipy/reference/', + None), } if PPLPY_DOCS and os.path.exists(os.path.join(PPLPY_DOCS, 'objects.inv')): app.config.intersphinx_mapping['pplpy'] = (PPLPY_DOCS, None) From cf0d8185a6e283b875637ace37c7682a89f51c63 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 9 Mar 2024 14:46:32 -0800 Subject: [PATCH 200/518] 29231: fallback for scipy inventory file when not connected to internet --- src/sage_docbuild/conf.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/sage_docbuild/conf.py b/src/sage_docbuild/conf.py index db6ef6a051b..ab0e1677b18 100644 --- a/src/sage_docbuild/conf.py +++ b/src/sage_docbuild/conf.py @@ -273,12 +273,18 @@ def set_intersphinx_mappings(app, config): app.config.intersphinx_mapping = {} return + python_inventory_file = os.path.join(SAGE_DOC_SRC, "common", + "python{}.inv".format(python_version)) + # If connected to the internet, the inventory file will be downloaded for + # projects that have `None` as first argument to the second inventory tuple + # item. To avoid docbuild failures when building Sage without internet + # connection, we use the local python inventory file as a fallback for other + # projects. Cross-references will not be resolved in that case, but the + # docbuild will still succeed. + dummy_inventory_file = python_inventory_file app.config.intersphinx_mapping = { - 'python': ('https://docs.python.org/', - os.path.join(SAGE_DOC_SRC, "common", - "python{}.inv".format(python_version))), - 'scipy': ('https://docs.scipy.org/doc/scipy/reference/', - None), + 'python': ('https://docs.python.org/', python_inventory_file), + 'scipy': ('https://docs.scipy.org/doc/scipy/reference/', (None, dummy_inventory_file)), } if PPLPY_DOCS and os.path.exists(os.path.join(PPLPY_DOCS, 'objects.inv')): app.config.intersphinx_mapping['pplpy'] = (PPLPY_DOCS, None) From 48f95b96f096e89d31ceef3e5b92f8965f889089 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 9 Mar 2024 14:49:09 -0800 Subject: [PATCH 201/518] 29231: use online pplpy docs for intersphinx with local fallback --- src/sage_docbuild/conf.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sage_docbuild/conf.py b/src/sage_docbuild/conf.py index ab0e1677b18..f72553ae4cd 100644 --- a/src/sage_docbuild/conf.py +++ b/src/sage_docbuild/conf.py @@ -289,7 +289,8 @@ def set_intersphinx_mappings(app, config): if PPLPY_DOCS and os.path.exists(os.path.join(PPLPY_DOCS, 'objects.inv')): app.config.intersphinx_mapping['pplpy'] = (PPLPY_DOCS, None) else: - app.config.intersphinx_mapping['pplpy'] = ('https://www.labri.fr/perso/vdelecro/pplpy/latest/', None) + app.config.intersphinx_mapping['pplpy'] = ('https://www.labri.fr/perso/vdelecro/pplpy/latest/', + (None, dummy_inventory_file)) # Add master intersphinx mapping dst = os.path.join(invpath, 'objects.inv') From 4ff50cd24c9d87ca4f3f7f8b98f84ec7e8ef2c11 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 9 Mar 2024 14:57:31 -0800 Subject: [PATCH 202/518] src/doc/common/python3.inv: Update --- src/doc/common/python3.inv | Bin 131309 -> 136166 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/src/doc/common/python3.inv b/src/doc/common/python3.inv index 50c7f1fa81417d7a23e9727f84fb97ff02948ee7..bb8148a2b389e554289a69c7dd51b1d825b6464b 100644 GIT binary patch literal 136166 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�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 literal 131309 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%0DtjHHJc+KG{h#ZC?g*v*rWUKB5&gE}M72_*g0 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�Njcjwo6m2|t`qv8seyAL`SKMsGJBnh#hw{XAwn;KAtGRA zEhh~x1m$n~aB+d>!7KEKnWl*1$*SbeF@{RO3O9c?AEm3ibT(_JMRD@OC^Z zIdZ{wZ9uegP?2MoMA$5sf$c!jjKYNBEXfG#&3)6_?u9_wS9y6a-=tD7a3F?W4M6%t zMc&Oo$yC+NRDcf4l($`%*|f~7d|smh*y`uWoFq=1J|qrQ)w{%}d{1P{SJJyAYu*_J zEnCE;RxR--_~bnJGFM6ixjnX#11edpDaKv=8>Vq3R`_Be4I#BdT#5C%VoKiZmT05_ z{f)>Wms*V(WGWTojz(bB)zt)dhN0P1pJ!!nrXoROmg>}1+<>{j413+lRcwGO%H=1s zmjDPAaU2Hom3cJK%2S&~JdjMkUtWNfkCR;}Hzh$4KW>&_FS&CjZu3AjcmM};ulBr1 zu@zfFQX5E^nbmp~7<9cIB3e@=&##EqfV;*YEVyDCPnQ{Nnw>dxi1ZbaHZtj8S_*u? z2?d{Zp^hO#4w|bIJP+X9X6bU~oA4FdjoZ^*GRX;uf5R3(pmfJS5yN<}_h(w>t3N`L z=e9Bt-fZshKn4TwADeU|6D~z@ltlj_a4-L%vd#a=*>47P5=GJus!c&GJ(VdKw>y`y zjihoIGp}|V2;t_v2{QCCKb2+cipmq_N@Z?B>O|(U z&sFyes71Ogc7s`%Fxh&Z?*@aM1`-?scSBm4Fh=Fe!9Z4f3>W{wK!pJ`efh^)y!=D0 zUf#9&%R9ArQFmhcI9@o~nWzmUAnac*5Uz6rYe4-+R$xvdxo=4fbqbG{x}<9mqR!QB za7tX5@0bdytJ5@^)oavM#2p)e;zR_8om~+ny9udka_xYJye62;6lxcs-vPM|WQtJ^ z3OgVpuK_mQ`SX}4JAY*B`!09>5GKm*T|+2jpX%R=iO#hf(~~~^vISvqnv)cEnxPXN z3IJ=2e4PTw1V$7bWC}q%0n7iy Date: Sat, 9 Mar 2024 15:23:57 -0800 Subject: [PATCH 203/518] src/doc/common/update-inventories.sh: Rename from update-python-inv.sh --- src/doc/common/{update-python-inv.sh => update-inventories.sh} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/doc/common/{update-python-inv.sh => update-inventories.sh} (100%) diff --git a/src/doc/common/update-python-inv.sh b/src/doc/common/update-inventories.sh similarity index 100% rename from src/doc/common/update-python-inv.sh rename to src/doc/common/update-inventories.sh From 645377be69df8149ad15eaf18500ea63b57d15f0 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 9 Mar 2024 15:27:00 -0800 Subject: [PATCH 204/518] src/sage_docbuild/conf.py: Update pplpy doc url --- src/sage_docbuild/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage_docbuild/conf.py b/src/sage_docbuild/conf.py index f72553ae4cd..60d29cad7ad 100644 --- a/src/sage_docbuild/conf.py +++ b/src/sage_docbuild/conf.py @@ -289,7 +289,7 @@ def set_intersphinx_mappings(app, config): if PPLPY_DOCS and os.path.exists(os.path.join(PPLPY_DOCS, 'objects.inv')): app.config.intersphinx_mapping['pplpy'] = (PPLPY_DOCS, None) else: - app.config.intersphinx_mapping['pplpy'] = ('https://www.labri.fr/perso/vdelecro/pplpy/latest/', + app.config.intersphinx_mapping['pplpy'] = ('https://www.sagemath.org/pplpy/', (None, dummy_inventory_file)) # Add master intersphinx mapping From bf2879d284242a4d492eaa12d0e71daebb743dd8 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 9 Mar 2024 15:46:34 -0800 Subject: [PATCH 205/518] src/doc/common/_vendor: Move python.inv here --- src/doc/common/{python3.inv => _vendor/python.inv} | Bin src/doc/common/update-inventories.sh | 13 ++++++++----- src/sage_docbuild/conf.py | 4 ++-- 3 files changed, 10 insertions(+), 7 deletions(-) rename src/doc/common/{python3.inv => _vendor/python.inv} (100%) diff --git a/src/doc/common/python3.inv b/src/doc/common/_vendor/python.inv similarity index 100% rename from src/doc/common/python3.inv rename to src/doc/common/_vendor/python.inv diff --git a/src/doc/common/update-inventories.sh b/src/doc/common/update-inventories.sh index e6dcc843ced..5d9c211f9ed 100755 --- a/src/doc/common/update-inventories.sh +++ b/src/doc/common/update-inventories.sh @@ -6,16 +6,19 @@ # # To be able to compile Sage without accessing the net, we use a local copy of # this database. Here is how to update it by downloading the file -# for the latest stable Python version: +# for the latest stable Python version. + +set -x if command -v wget > /dev/null 2>&1 ; then - rm -f python.inv python2.inv python3.inv - wget https://docs.python.org/3/objects.inv -O - > python3.inv + DOWNLOAD="wget -O -" elif command -v curl > /dev/null 2>&1 ; then # On OS X, curl is installed by default, but not wget. - rm -f python.inv python2.inv python3.inv - curl https://docs.python.org/3/objects.inv > python3.inv + DOWNLOAD=curl else echo "Error: neither wget nor curl is installed." return 1 fi + +rm -f python.inv python2.inv python3.inv +$DOWNLOAD https://docs.python.org/3/objects.inv > _vendor/python.inv diff --git a/src/sage_docbuild/conf.py b/src/sage_docbuild/conf.py index 60d29cad7ad..695b24b1bc0 100644 --- a/src/sage_docbuild/conf.py +++ b/src/sage_docbuild/conf.py @@ -273,8 +273,8 @@ def set_intersphinx_mappings(app, config): app.config.intersphinx_mapping = {} return - python_inventory_file = os.path.join(SAGE_DOC_SRC, "common", - "python{}.inv".format(python_version)) + inventories_dir = os.path.join(SAGE_DOC_SRC, "common", "_vendor") + python_inventory_file = os.path.join(inventories_dir, "python.inv") # If connected to the internet, the inventory file will be downloaded for # projects that have `None` as first argument to the second inventory tuple # item. To avoid docbuild failures when building Sage without internet From 8de1104b26026abc2ba8bca21e0b696b27e13e20 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 9 Mar 2024 16:21:58 -0800 Subject: [PATCH 206/518] src/sage_docbuild/conf.py: Unless SAGE_DOC_REMOTE_INVENTORIES=yes, do not contact remotes; use vendored inventories for scipy, flint --- src/doc/common/_vendor/flint.inv | Bin 0 -> 246266 bytes src/doc/common/_vendor/scipy.inv | Bin 0 -> 112691 bytes ...ries.sh => update-vendored-inventories.sh} | 4 ++ src/sage_docbuild/conf.py | 58 +++++++++++------- 4 files changed, 41 insertions(+), 21 deletions(-) create mode 100644 src/doc/common/_vendor/flint.inv create mode 100644 src/doc/common/_vendor/scipy.inv rename src/doc/common/{update-inventories.sh => update-vendored-inventories.sh} (79%) diff --git a/src/doc/common/_vendor/flint.inv b/src/doc/common/_vendor/flint.inv new file mode 100644 index 0000000000000000000000000000000000000000..099c465fa90ea54ae7426a78a02a5cd30c4501a1 GIT binary patch literal 246266 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

$Szf5x4Y9hd1C9TSv-znFJ29 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

3RV}2qqu=ZXZc+w^ZGNr9E)8!v<#Cg( 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

  • |v*24!PhJ^J&dt9~%haKIebfe(iv8Nq9 zxc!etciCy&I5U00pJpx;hdEL{1s!(WFt!@n_Da%UMD*G(z3}Q66|yw zj^3OP?|0)XL}q^vh1U_$WN1RYy~N?El9jnS9)@~!kr1|m)fhTYYNWAgSef@lEv09$ z7}_1YB)YBBjW016WFs0p>Q7Y7A5aed`gX~-s-!@I4HLjX!Xx&#j zDLFE=w?GB)9zqzQw2U96Vu2K`;RF7gb?VHl=M2cecOtU?>>J(*o;bGlp%=07q5!$` zpj8j)_Jfp$_rXIrucRuo57eq!MO%pOm#pFK8D7ETW-U32-$YT~&PtS#AMV&&TMeGU zU~CX9*|h{cc#Iv92yY^fR%YpXF+u0 zPF1bqnsvmPuBlU|CQ2KKcnUr~d%#kas??(Hww;=+mK9lho+t3)Z(0K#6t#^ytcEw1 zp`%!VPMRr$Rv$5-1DXmZ(@mc10nCLpBE=uCgScTfV2cY zg1PqQ(>p~PcSb$tR3#I{I62)yg8Nb}gp%y5yuVn#gW>Sa+ zrf&2#S8~9`T8jkg!*Y3Op6;a>fDAy(0YS5pw@eaN?8U8-A*;gRyKqu1gnOD%u zMQ=WjyFurw*K3)Xk9}mOK^*YXvG!Pk@;m6Y@<0Ah4-uD=_uJVyuft@fWABJ^N+%-e z7gl<{H_aPeWorse1TH6kT!$jcLIQ*CLjE%C2?E&!6B)Q4UyRRH6469rNk>?%4UEQk zIX`GJ85kBBQPmdfTf6b`A>&8zzk|_U74ZJqcq+g|U&_LNUS3#LY-QD99OPiYNtS3da%6QypE-K2auspuw%!Q-M$VK?WB3 z#eoD6Wqd;hw(bBKg}dZYP=0o5@b}eTX$pJ_x70&LKv6kvAzNjMQgMhTPI#OZ@4q`8 zNeZKEjJfpPlSx=VvVo48UdML(g@Jx&AdTD=3I?Ua#*_YiD4G3hqH8yg9w~pG&4R_8 zV+qQ-t$q2MS}SZ{fDx&K$^^tp@`$FV5eq!7`=dLet_c3)p-n(CU5Ws{Z?2tf zgvvu&7BxsdM7}Ps6gS%~lOQow$kq*w6ASS&pXq=U3h8BUv4cAUP|H;w8vL@C1#Tn2 zDu)uP5H3fSHa{(#r`L4_jba5t<>6Our29@q-g(5E^3pGl;mgx!!-ycix+wnMyJ~x$ zc8iZY##J4LJCaCpz6}y8Myn43D1;aM;vCoY?trU1bXt-HQe3F1x=hdu8*OD&It`@` z%Ubt1|8o1g=b;d*TqIaqp_t!;1#}u!xGx1LZ`>5G0M5!HNx0OG{R@O;W(gmIa`^8Y{yc(4eG^^orw?wXn8(ckUn z&@&Lt1~V_u2k3!xe?_Dvn^~ME=8?zWgnMI^eBqXCug5$o4lpCgE%&n~&gW52Tt}=! z=ZN;2mkiM|WRQV)M?o}eo@zL-X{QK;A=8b3f~65cA7MsPRJV7G#7lfKN~B#qbqUYq zhV^co99eT@FfZ(2F5__-`QL2nbUwS|x45U<;s5hY1X z;{OvPO`Yrl6ViDUkntb3yDxP?Ikmg92_pZEJBTC30BO!VKFddRK#QgU>ye?m%=ns`bo6pV z^#gj(h6KZ!qvo+C#%wff*J_cpTo8M2iE`%9z z41#5dSlE>(*sYhSDN(v)dpy5>vHb52QGTyr#&=j*qc$7?iDJOuox%;eY@qbIRlO-` z%v@AsJ$DO7y;5NiCLutbLjn4LaP)7|3HCwH1wZ8tz_bkfdZ1N^hClGv6K8kBn9}5T zYa0gih!=y~g7YJ{ft!xs%W37d*pZpJ+zA;La9@utl+qP}nwr$(CZQHizjIDFV zwr#%o?!E6v|LD}}UY(?BcdC-wHGH!WnUm#JxDuhTVXZJ*#J2+zBu&+eQL@;kX!+5> zA~u>kV9jF?hemIXLdUMqrbzL@fg;BzTVRc%5RF!9ic-qDx-ZJ8^z29xqk|>5Vvukb zeZCy^sLvig+32(5vuqFiWN{A@VU1y=@vrCSU5~gH@=7|7VyKA%b3MXBPh$Q8Yo24c zjSv2zH3R-7#;bv56rl(2L*1jm^^2i;c0c}|@r zuqTAKp2FrA>!zWk^P%u3DEzN3ty_IT@!0zL;?eZ(cE3h#kuZx$n zjCDy@JD4+Pg&_ZFL*VO0fLDhV*AZ3(BfX)GyjTA>lk2@H_nlvg>SL&;F!|ws8b#bD+RNL?O^U?4cK5hL`DveNd7G9kS@6Uxua#99naMH!A!C9Y0?nk4%diVZtIrC!e&(%~G#A1`yx=~a4F5d^RooKrKbCyb!lNy< zzhy51lHxW!WkXw9Qi$44h#NXeh#Q3!9CGj?#uTf8zPZtU-Q?s|9f<~1z6Bk~SM z7Vw?>Q5mlRDb6hJrn;KOArxJ;6^qXUlj1P`6-`BtqARsSQs&{cQc(PsARI2VNh95C z25yS@BA`tY_LO}9LTX#7k;-0l*UD)pCbbQt(vo_&(fa#uhc;5z=~`{LuES<0pIR0= zqS{erBe5v6Fe(ixQ+CR|mzJ0O+4<-fzP z(N@o*;5TfVD5y7xXe*0DzC|zM5pN>U){|zXl#vrB;^Bd2Yfg=&iDwkU62;9R%xsoaoGCJCobF^Qy>04;XrJJ z05q0OJ+sgq;dEFG?m?ND?lF) zeZXT7hvBp{06uJG3hp3x29>rxs4Y1cYNrm;TrD%od^tDD{22s3&$G2~=ls4a^hu-= z+h&FBwXqGb{f7`OCK`1!bYsaE&GXLiJ%FN+I8Mrp+-JROV54F28>JvPa1^hkeS)4YTmpQ z#o>vmrCFfqgMALp#mIiGT|B#0ESYJ80-}KlL$`4JLAgO&pym;b@s*h0y6DnKn3vW*QKtdIk`O zLm7;ff+zLSUXogFEW;T!{wk3vl)p??_RXGMleo$4)YI5Oofuc)lfK!r1TlOr1Z}S z(d&MwpqVs15(7;%YT`=_c-Odas-nW%6D2TO_M;r0<*?I;_H73CPdkZNy(kF6ME-asl&;@ni{1qT{$AK}IofK82AFCxQZZ*s= zj2g;B^hOtLa*Q)IjP-ZQ4F{K(?AH0!(#+&s_6bsIxtUD8S4|)^vqq*r1(M=?7#W1f zS1slB!t1utJjd4yO&-`p-?Sny{}htRHM{!{!)a&}bZDsVo>_;+`=aqG5->(!w|bZ>L(RhR#^Ca*{JwFxzJ*$- z1u=>?IEO|!v>B{i7+3+XfvlgwSz?UQz<_tl*s(5ISPPPOX zn2H8=TMP~Horb@uN(?@iiv>reTPz~xLCnyCBh1pmBh1l9gTchQSxK6Vwy&b#&efuV z!RSR8xf}2h7Iyga3#=}bymoMDC2DjL`3Sc#h6OrPL_5Td@IV~z3!?ykstiCd`>M~a z0xpQF*;d}5tB644`osY3&CEb?`rQ`#3U<{pH1su9iDpjQ!?77NPBA(Fp}=t}4q=q6 ziUVryL~@n#w)ajZiXd!4EzAM&4)zO%_(36X?Jfnx7CR~3--6HFisn_g4K1=G&D z2std5&4-1hS8P@Er=pR12z{(Q*0wMGthk>Dgv)A;zyrPAh;0v}1&^s@{4~Gjv~8qn ze#n#)rG_p%E-VYD>)ziEo?WBUDxYDM&tjLweAer-3C1SI#xfM`F43SZLZ#+mtw4f2 zh&w^YrRQM$UHi@Qo2s35TQUHYcLPIU{x2Fj#8hxJh$}GkDQDZ#*O59uWM6Gn&8oRV z-V6lKLz;qwnS-m)bb+&d1?IM{(B>{(Xa;5!Pq8*}6Jc>DgJ^pxre# z#oZVzf}AgEgRv&`L2=>VZx4x9VZWbbhCyk#BKP{JSTCac<`s!?1rpV3PbUL)%hU{9mlGrV z%ZPT00u6#2lS45|;uxHkX?t*KiTvF`_Sr&~qfQBrm!(5D)sd=X##^o6@D7#Q4Rnu! zKanjhSIX?2fU2dxG!?qw4TjtP5mV-N=?Hsj2}{w819y3Fzo3f&APtMra@jR~Z1*2P zSjuv=>xq4es{t-a=5G|YV#>P6AOZvF0cdj?XvJoL$~Lfu;lyBss;;a#uHouis`dPODr;MA zyPKZL93|`PC3eMwj=DaLbbJM2U4oiy18em=STu9L&>g31UL{PpaxFxe0;m^*foTQD ziQw?0COAY9-ZohgagZ_!X_7KAVVp8OB~3)0xJ2q=@D=SCNEIWK1@C}r&I71(DCd=A9;%E!()_96`dGASE_jVM7qwi`f0Gt0mo#*IxBjM?{v*Yn% zth5HMD$}RVygf~zzHFe%*`b&4@avzybrtJRzHA#g-~LaLJL$rYg{|*Tpm4uPSW0Gs zE@7L|VXq_@ib*UG@gsHd7c2C8xO^HfZjS_hi-QIHYBiuMgYqDHfR*h8fgH=aG?VMR zln6wJ2KZEMbaak$RT49GlD6Ztlt!Sa4i5K&nbFjQ*VMFEwzgwW&BlLi>=#&weE80@ z!(q(`H<#AHduI&^4X%GkQ0rOGp>&v#dX0z)7OFYW@1bY@q8Fd|U^2$&U<=md6(=2| z7Zu#e$wszIvAGrQ<%x*bqcOjBqT}Z{R*rO!GwG=~(AVfN^gLu)u5zOHk1otUGP zha>`|0_i#IreoRK2^7S#3*L@Sr8xUtt_1J_86z}<*bEw(hM?Xl7%1_uTvk|ytG=DZ zlzGlO-1B5*I*Q6*kgw3;5pQ78(RWdAWqp+l6@@>0N<)AkS1j9PKTyj=ssH*d43_uS zF9)>x3)Me0nqWO~hH2m~i%RFd$Bt7xkk;;xhi%*pg}6Ft!PQ5ZfY}HDX-1mgnTzNNr|e+O{|-SA_7{08xp0o~#(4QTT_#Kzvf`NbNiA)+Kn40$*=nlb+(gje6T+GM-l6^IOQqtWd zc*?rje?zZYzuszL3pXuaG*LlGdzF5iXN0RM&hytY1V z*u z%!hP-W=GM|vYPv_ciz26Jn24LUcFVYV-)y}2>m#xLC~}5hT^euQTFtcJ}e-lFEqp- zT;|gqa|g(h=^$0Vq7vQyKM_j|A-uNs@EQnGeyMJm6p_yw$qR{q^xa6(LM(+L98XF zPwUp~$c<=2gU(k$AZmIL(o`vdxHF{*ZiN-qon|vqv@NUOlvzr~QR|sGY2kxL%5&>o z?|KOZ_Q6qqsqe$HgfKcXG_*xmIwmvoL9?872m)Yu`riIKj-H9wh);HzG?!b#V8sOla=zIFk33atm0rW zD;Vn>=UknoB8j_`cpj*NF*!Q)0kog!F9L7`4%1=esW8UC&HrG~zI_$n4RpEOrm z_GeUPx#ZTULA6&;#prrjRp+d^7xJ6|@LUj*bxwr|=le79N>dP0lV%(FrQqT-0x#z) zu^ilRMsjdP;Rvm@Lb|bAziJ2?%!Z;iqY$dC-sXCl+0;}UqYeW5_(Zd`=lxpF0z}SB&%Q+YVJF%O!blDg(N$2 zB|Na%4Mk)c9z5-qVTD+&z6;*H5uN;{sS$2Lfz_m}Y{;6k$hXGx%xlilNX|~z&XS@w zNwqyW*ivYB?ZoT_m8Q6L1l92F)|8-@!=RSV5~4O?MMF9A9pDK&Xr>jg7L3uq!K?Ld^5o}-`+Hxf znJi~di7XfQi4xPe`-`h2-R7dRa`cKto^BR@v%`%NOK*#xTv-&b#eCcU(8Nc`^^6?p z@6DDhKf{9*deEARt*wfI9-6>Nl~}2m9ls}84Anup5#Fm}#YKZ)_#^+i9|Nb`snM?-D%TC;; zob!j7(vp^qvNH}o?K(Owik<*fKPo8?N_E23XP0a7Z;&Oo71Qw8WAiWnVsYBd`z|%I zp7j^}MR;sQXN|L@=ycJ4kfb0hZRsIx=RsXq39GJxQx0Cy*@o^v)EyfEd*fIXwd|le z;^?)DwXbQqo|Fgl7QuTlG0|{3w22RRPVbb~*FfWY=0zb5_8;6RUT~l>X5ay3de?}RzLO*c9 z*#NVl5Jy8SGmj8gn+4`*`2~H{I;?uX?pfKL*1?xoKmoVLNYAw6jq%`C5@1$QR2oud z<&Sn`z87n^8ilq2k>XO)0kS3DCcn5rOZ}*7@u8^0*>X~KcpT>-3eTdp;64_2ey39$g>=)yi}YY*w0o#XApi!GW4!Gz(9OYWZiIyf zm;^pJD$>N~A1EonCR5Tgj%`Hr3*9I%7Midu8wFrlRMmxE2d!$(QR-r(80l-x!xwsX za|UEIMeV172B^J;fB@1Zb)f(-d=Jzg$;)1WlJm>|@~TI==7<0`s$78rK`z+odE;^~1y_bn%ydLg%=ARRp{B0plGR~W!v!blM=q>RnyL>C znW_&DeML#tJ|KIZ@yL$VPFPqOGF|MMFj?=J_=%GEO*G$Dik_NoV!@=D5j6wB5i$S3 z?W+^axl|UKt$RiiYC)8{otTD719y{vl75#Ev=dO8=ng6=Zq%JOU_f3-DW@mtEnAe) zVbD^If6#W-)A^!qk)x6>F;c#pEel-aLA4|%CyoMU+Z?eNqTGMZpDa6kl$!@6%4vGa zx|vq_PZMX+l5+&VH?eYr&{!pXb&KU@)shXlfb*hdQLPa3r45Q;4cr@&v z@*Wf?UXyq2Jvx}Yb5>`6X1S^3$g1gDH#O6EbI$`Sz|@XY@!eAcDfv6>jxRF!z^9&Y z2JnBax}V&8S^tan2tOej%mJ+iEsOK!^4Zcp8Z%D5TrZP8QGB;CZ1;R2|0o*`HxCj5 zytsc6_#%_>pQ!KVf!C_Z2UDb(q|)2-z~6DW-B-I|k@021aJ`x~O5_HCHd+y++`s zs}CYsSd{6EJ6w=MS#mwSewhl{R0BD$LBfT$g9gBhYn)&#HX z#fDhZ^{|FJ5N#57m0JMNw)(NmYyuw@T4GGe9pxp$ zzcC&R?tF(zepSI?X=U)*i^ObQcsy(9 zRNXq0-@~pFZt-HeOxu4cJiV1JP@>5tv~5QZ&GZ?>ewloIGezAsgs#RO${q&}x0?a3 zVczJfqndhk?hh0ENyc2{Ik~b$Myn-9wk(bXl9={`jzbMW^_7G3u=~D9US$){qoijYk!w{!stU| zjlOOO$qBGj_A<~KIx9z(-Np^-Wpvw^54tdnK8%CHD+q8~;L}J|KrH)b#Rtlb6~$uJ_&BFk$jPZvJF^St9?uN`%kU_7$O7U+uQQoW>3s(V&!1%b8{8??-iSnSBZyGYF7 ztp{rQq}SP0KSkirAfQjB5%;zf4^4XeI8bWcbm{_bWM{kTxt_PIe~n%oRx`lszhb} zNp$%q(RxO^i>UNYYqp6=b;)7^fp3+G(F}Wipxz4LG76g|CPc7rfBZgY=QO8Vy=A36v7#%Z7Hw|>AUInn- zbJ|JaTmP#BXORdSk!^EN4Wdrqpf~JNBsc6%G&k&$ZsDTC z#7jM-oJ?oCO}`4nxQAuC8N>Aa1|r4e)NHoL`n1#TvN1o-Ym>OCI0%e%_&s5{#Wpsgx^w%=_ronrw!K!hQUEFPEA_|-t-J(~gG z=mK=CYNn*M61K9xvn|ErIG$~Ml`;zib&3c605LF+4A8b^A!6I8`D-uQ>u?)DA6D5@ zf4JqJ4JHDz3?JS|djOr_#d$O$h>CRq_}hzH-GU|;xs@eoqFPu{$|(sSyD|t&_xfos z+6qP2hQuandw9I47jaM>jVF!4AngQj{vehdzY`!L=*tX~U)Av1N_ft)3!_pps+!&9 z(vn_V-ychGmu`s+3fQ^@UISwMq5K_<_47BBhm=I^N~mAUQCN6W5wgo9^tPbYP2h$X z^!hEJI!(=YRJ*u8@1MFVozLcTQTO5gA6jqx6tT1lz`m8qs|v7? z=GDMJO$EoXPFFPFMqM)bn98IpmxvZp{^UJYLNWE>p-E6ES{!kUQL;ZIpdau{ZP)>< zfTEjOWZ1Z(ffgax|hlTP4%Imx^kW1Fk()ztR4PX699F{%Ye77Q0*R(W6ph=XO9gpT zDa~LI+ch-QEgt$yL=^CQzU25!TBEV!ix%AbZ>dYOgk<(^`V?YR;f`Az_Tq4EeO|s$ z_q3Q#5bV6cED|8_q&@q=hU)@ z;U{JoSiV3Rvi_c3hQGOX|M3d9DMx=}ohE}Y&M3Qv^2=!Gqo-#z;+oM*Coou#g_#=T zXOvL&18XhwPw49}am~Fc^dmUi{>FtAY-?d^r)KD{AE=2EMyWcAjH!lrSVC;zj*^#T zNqfo!5?TVc*S$I&Gg%OxbPiAxv=KshXANXN{Z@e&|Kho+PLQQA;c4<}esKN~jJT3PtR2o=%q-xqbKNj4RmQMt(8JfZ6z1eqh(k5DcLwT{J{RZ?V5F zvu^P_x_r;lqqE{VzS{9)*PdirGZH){_bfkf&>S!a^(S;jvlq z9l}3)Ev?jp=3bL_unK3yq2DSQu}@rkt{|a_3xqhN%lh|KaAE6m z6Il#dj2jwhwkp~TUk|`xeF@#$>Qnm(p8tb0Pcrq(>Vb-zM11HGRs#BP`uN(};-W?g zO*Ho033_~xmIIt!%4LxAw$nlxr2_I4{S)5ei-T}*7S6VP;3ge*UEhACw_PJN`Y3ib_uc8Rz`kdF zQ^?BsA!MjFHhrolHXU0D8$8G3Q=c)TVg7()pd&|CI_=!}lw;Wzk@ zL{?q*#9awRMyLQ)T%Wwv`s7RN)`#_WqP06^MX29o#QGd{_>2YWD-X9;IoZTzdB4<2 zcnuu|#SwHG=Vqmz`8xm2ei$tc0;3WF*QBjjN^36$LK_HxKZAL7lN*a)k zU;H>p-b{tagNfK+gQwEo!Vny>zNt7rb)vEs98*CTKT}yBK3U%olt*{yRA0X>MIAxG z-jEcTxWamZjOapJWZ4=a0jWP(C_jivf21YSsN6mnVs}p_d)Q0bvySnKHFxA6d zxxZHENPP(Ocx6Z~RGu_CuDhQ!4!3^{XvLEtb0-?&(7rnHt#<+v{JJ=HvHda^Gu3Iu zywHBRWXV2A)8o)B!@|HvbXd3U?g`F?U03ghVF05$A^wFH=_YRl*5io5>J}zejSc69 zYX-O!9uzUq({cRTG4Zk6k_e%f`^iv#_wM@dfbYRy48|Tl!$3wiA?`D6kfgT*ki0`V zeJTNF8lScSm|=HR>U$u!4Crk`x>&@a3kEcd;-OW^9ioI7>n|^-51k0~nVTl6dc^kl zXk(Lg`A@V+4L_5AcSQYF5)z*@!Or~{Tm>;Hi5)Rp;eUM1$!>BTb&zCqcN5aO#&2f| zD-PY!4s@eK%r=RDT()V`HwJFW%D7X%vQsR;%GoJd5scBD8~a2JEias2nHH~aV`8c; zPYLA2aBA!kHDmhBhX%QCEA~p(HZR0+=-2S}+fPOG_Gc7^q^I+yU+M{lJoEJgmlbnz zxGi^tEKFU;t9nlRIo-zPN~Xt=-nl`eQUTRX@hw1dOQLsS#OJjwIhMQ@^B_h?N+)iZ zDb%oL+r|UAA^xm`JWG@uLB98IE`H5*wi6U5k`X$H8a%o-oiT4}T+V3O+&jR;U0*XS zPqBO2EXRM3KR;HABmrG1HYaC-JrnZVBFBe4A;Z@Q9QSzy3?l*=ddvL_;WkYiuE@x3 zi4^`rZ3=i-MKVw&v;8I!sw67h8X>t1lRGbKW5Eyc)d$AnRdUEDB)&+Vr9!HSzL3Qp$f7y)1VVv5SE=K3{9F@Ma<<%pd7 zO3T*RyG2xz^u%R|d(UNP>mOXl)+hJchveQ2mhNX&=w#p1v?(;{^lQts(4pO$!BaHR z!O}fx^=tHq%W3RqR$rulUQUN)b%>b1fExY%tvrrCSNomxmXhkD0xqrn)X03fz;h+% z&~r6KK-+SlT0Ws#o&{D>9T+=*uV?`S?S?Uo8KnX&G<*1buHq!gof=8<9mU(*fe7Nc z0G@M+Az|Y9D6kvzb&M>jlKdxteKI=4D?vR1mPye7B>4TLAa&_43b1%7B+G0M7P+q) z2D>XU18>Q?F&4`zxoozz+r5NX?|LPLV zQ8+v10Chhe0&D)d!7V7ZY4XvC>X!-eCv0fO&F2(vga zQ)mK-{OZ&if7=P3=xkfggab2%{RmRDHOm@`pHE{u5 zVg@LP7;GNC1mpVHa7r77-Ys*~c+%yUG>7`{_c0PZ<0szRGa6np8e~OQ)n(MCQ+!8c z*3@fKa3|f|CosC_C=Ra#<&~isP57OXZtfy~&~V>S^D!95_bNp-S<3ITRO1g9pDJpM z9`T6BI=tH06`UnV`uG3&5$bXeynI{r`+GO;tyF(4TN~u+Hu)yQpnJ#QN~bi_Jstw+ zJR0eGjrR$;RX!}S#$L~tl+(QB=bXn%ZOe{bS_k5(8=zS;1XJA@Vw)TP=3X$Kk(X-1!ed zaZ{I8+29y^cm4beU$fQ2)lc9{jN)h~Uly%w4-u5?7gSe2Z@c@b?c^({Gh|cz0L357 z3G-B{n-JtB21-rE={BHdZp^`Sq2MrYLKZBm79uq8e757?Q)g(TwhC1;=*mpFu3r)+ z=+=%ylZWi5{J{vAx}_tO#!tb5n9I;&&d{A2!I&C^K?o5S|Hg4I^kuRC}`B3B24t9SU4GOe#2$C_~mB@;x_AVHQZMdOQv12 zf#Ko29!k6%R0U(P+Q81F`5uDa@bi2e>(~7Q*5U`t0x9x61-=a}u1a`t2gI>=tk_P& zSxBdOVA`piJ#XWXW9TK~Be+ghM zc3s%le){D3kwp7>EqFB|-sz&g=A_-Lx94{jnJjmXS8A;|2q?j-1rd|A&94e;{97$k z_iE9GYbrz@GIg#OakqiZP_f31%A1p}c7E=D?*CwvTxy6J13zV`uCy42u|(}S*(u07 zb9ZV4=G;sYZQVHAWs~~~I5dS)RzvyHL}>)SaA3!l=q!JPkpvnTGH z{j+%&z5Ai@cL%EjjrTt2RdBuRdeN-BsN2AwTvLb5=)%Znh$7uEMA%yas>todLz({J zgfE!}X-rtPVPe8?cI24!;{+b>AOYs9Fd_Djvr!qpulVctOkgKtx0(+r!LvT%0XVQMN}MAhH}GA zERMhYcR^@2Sw(cO>#)n?u$z;yC8HaZ7tsh$4Jy1IZ0x_@k;Et)6)`J`T_YSS=flT9 zPf|m*VQhdZFn4Xl#Qxz%fVHz(n>iuzUUX&VxMX*{<}kUFltECDdu0>UY`@D#S;Osg zw}q$-+Wq`Y9QMQ9_%qQkGF?ViWK4^9rl=25F$k5*r+fdfV>`Tws%#(JN~#33>jt^= zYojFHn=7~oQAGoV2zTzkpNh>VphOufH<9MA@oW0o{I#kNQ~o3>X?Rmd&-tNdq5_Po zKpMJsoapnkA;L{3N%-vF88T;TzTM16ASQ9&9=PXNL;^<}wBsG|aNt(I*ADjAi5?(e;tt-*- zDB)XH;b@LwKL=gm?nbn?&kDXN0;8@|T zmH~v;h4s-ZdXR?Dfi0BRUPXBYkTGqU`nn)+Q%Ep#wz8|80#|G^bjZM3%_2spcO}-j znZfi%a^%E-F&d-<>pM&Dup=MX?cen3sAhoro2DIFgj>%r*kE&kG8Ro(*Hl06urmW< zCM1`+EK!elbbj1X>!?7rUm&ipq1t1U6|$pfZWIaaelFT~ksVwDFlm$7HC;*SGr9aV zwZ^Am=yhy;X!#r!6Nk-v9nn;`XlY@v;OT*QUA=kbC3{$%HQu|RHUrCUYoJ|Zr@0x# z`d^hFRA)ud*3gG&gyf;c;1V2F=$tH5{hLVFSGhp^xP~X372elIbdn^IY1LtQ4 z&n-J?N`6Nxl4Bcac2x`p=hd>rF^SlaRic^}fh|W7QzFA9W^67^9Ft7Q#>gI%>Veb@ zkfgsgL2v5o&V(j$!0IOmP#$-7ZkTvz{HUz+ICl%jSqZC66cFzz*aEeHj%x$8kJ8@O zb)??@+t%}86zK@Q+dR6>fR85&L!IQHD% z!pm69F0@6c(s<4fvRH+HQd{CjvO$1dAMt49mX9%#fH;b93z6W8Zz)$}0O5{p$V z$;AkDz-|SAMZHvqNvu% zhtsZlXXHbiOh<>jR1lfiQ&AipxH@uW>7vnR>i?28%V)c&m}W3Qq8Me7Q%)x4SiHj% z{Dr4kg#Tr)udJeO>$v705wkyT;QYI|ARsJo#mey5Npal}PD!D$xm1G;3M5vi@cvcA zznyHH4>vTizicfv1CGkueMy1ov7Lk*s;tK(<#v%mX@3l?gpgnH&9r8fSer z#J<(0FtHv@^gER=ghmv1RbAYTXc9^9SA!z3Xj~@fz(8$V0+j)b=O@n=AJmqcu0fYZ z0?&*D7DzKGRNNs3QI{fh!tJz5!FaUM?u14px=~u*#tZ+(zd=PyQs-U}MLAKb1mSbV zU;$1Ehe!mlYhgC#FxI#MR5Z-wofXD7a5@;EAUgTL>1U6stsa|n)OG?KO*dqiqZeec zwsFc*Ln9K|&aEnPqIxEy{np)=Ssn{<6hTO3S(?Em2`u`djxT=0DfL5^(Nv zVNru|1$9VBdg^}r5(d~ZQ@6GwN4ReoUlQ; zv@{r}lJO4lR!Gba#n@hU3%&K@0E=l5@|78~1#=fZ?0)-~{eS(d&cZrRVdaA_oP~)L zJt15rG}wSe@xc?C$qiS~#=Bd&^n_p`dIT5<9zG%b3;2Lw^$~o}Wq=116bcsw!J!X6 zb;>73$Fr>K4AyNul;JRgeeG6U(83`(FF6|dC$4u>@n)ULO8a4~*HS|7DMvyLHFvlk zVxY|GMm;t#Ysvu79eoK33c$Pr(qv}1Dogqkt)M>&1d1Q7oK-jEU zi-r0`ua3Bj6>HN9fD4}nx970wS zpOBDcl+rhCq2DOd+^XTf4?{H|2s!qL8L~$vq_m@6q7HiWiLw6#Jv8zIoPSjxnn=Xk za6p5Bu;t=j-)m3Z6=s+FyJ%dCjzK7iAnd61&oFU$P;kAw<8A?*x9`L`Cg3$~3~QAC z1(+i2UrhjlxsXJPG=^XHMirZ^7OUi2;$6HnG!X@{G=Y4vu-;8|%|w#4=Q+TMw4c=M z+L4ni^hTlz^zxL)2lmn8KYXacIiG+~A-uSdLB!}W;T5+(Q737-U{HD^q3j~i3CJT? z^^rn43@jXtL^NVrrMnP_Dz=ELVQ~m}9{6Aj7l{8Zg8#n5uj9Ts^DUOFF|rJEJ87ok zH)Soy`AIuzgVhxMqVfx%mY1Fg>)ik^PEI7;KA{JW(;rp*l8lJl>$kkg5<+%kwI}$J z7-8k1VHnoBU;2X|yKVyn5hT5Z-yGDPp~rU~FZtjP%`=PaeC8$8Nj9h>Z7*070jk**P8E%xgjdJl8b9|BTyuV4s-{;lT>_Jf6aZzqSbN8z& zX8bT}S5wd~`$%K8W)K-d#KjO~Do5rH$}skMuSy&}V1LF39=Re$tEzoDE`Y5@z?Xkg z8vp3^!!Et-GvMZfs4VOJpnJs|{Lnp4Wz*_1^jjz?5a0yr> zrsJ*(bLZ7NJn3_;_9gV@JejKo4CHbu6>(BlywWs zUtsl^qL^C1fe2&Y{pLk)>bxy%@%|(AH32t!CgbFfb_U=J@2|f`$TN*rO}*Nvnk_8H zDuS84HnJf8=H*Jvl}&<^kG%`{JtFYC2Qp?(Hq$w|2<&3T1Ps*F0kY@5);E0Q z?UTM29&ZCr=9RQ}mJ4e;tn0H+N?j!U(ipir&_Z>jE`$rSJl_2@4w!oP=Y?^?^#`z# zi+TV`mZ^uUxiGdPR_+roSR+7~5!4)^OkqswaRf0pZw-=q+&NW9u^)Cp9v8M-4#l*f z-@<~p>@ITmS~lp~7Y_T2iiFmXIuvNW0+1=C6uza<*iL_klWf(4)MYnK78MjS?r$`lE5x!&;SKlh z4Gn??;jqa7i#5@mp3pXTgxsY4Q2FRFNk6B%oKGYss>@JVk*y63fB80 z`Om=AsPLgGmqE=9ct7xP6dodtA zftn&QXe8&pK>{DzD(m=)mZzcp@Z(t`#!1fB26#uLe z&fz}aJ!OObulZ^{=>InZrn+PpIFp*g9%yOg(sVSqA!@!js41t8P)cRM%RF` zW>5$BPN%1@xq}t^9uGCMVw}HD!2BxbUw`T}7BLC0f z%+YUPlGA<2C2@)0SRG1GA2+x4E@RHxw1lue?bB{mL=6|5HHORk^aae!GmXg;*5C@{ zwg^b@!;IPgqwAc4ByG9?-L`Gpwr$(CZDXcwbK16h+O}=m=JtI5-t5I*RYXSBTa}fW zCl8+kTOnW8(2RyvA;0RVDx!uv@h4|NMxW@;E*8z`3nlI*@=sSX>j{rlt@4DCsflf{ zX0E3r1^=$6&p$c6}5(BD{rN~4yxh)pv$c*Xpk zIjAgG)Iw3zu+TS}$S}-#&wCF!vBBXR zB5UmUrr1%VLyZE>*y9+QSa4^K(K|sM0>%s@BVBt7?rL^1>T34vAJob2f#819vaa5b z=j}V4RM!1ey+CpnAPVPH@P&uN_POH$opJPzngRmW$f zPxrTI*Xur^2i6bS+tx{yh1&PvXSsBhE;YU;0 zfoU$4Sd+zA^K3a6kfu1_hoVHVI%5MwY_W^H8x!?h1H^9pO{vXhDm!HdXogHnt)Ry% zET^rrC`Y)Wa;&Tga)Z$Y>3Ditvz$qivQt=o%Nzta7JRbBGJ^_2*$C0(3_xQ){ls&5JGaT;UuBQipA=Z3QAHr>F{^03q~E(opC z&(wl@YrR5HFI!qEOi;w=%Z(phVw@5tdV(@0_ybV=FjzSX{@;qeB8K)+rB0pNkw!c$ z_;Yr3^lq4YSfK(i)3L(jN`JpkM*4L99XVcx;yZ%xf4AcDama2M|COtkYPnw8_;av3 z>8uZ3%~SqNd1&G{U2O1I;T~LC9pL|j_#PeWrZv5st2O0IQwDa_W#K}18hz8}V^cXn zRnm7*y)u{uULMJC8dYm9q5t}I&QRx_;k8h8?3rGPRMtP4ku9+ior{YsT_b9CB57Gj zs%za_&u{fbE7`r~47pvokzU0nw|3VIn5M2vyH(laiNNu_txM#O65#YtbOn|fy*piZ z>~Z+NOK16il2$LSRWm;Qw(thVu~(2kX!G?>U$05!RHf$76K8+*NM*(DV#! zj21I`u*8P;=eI~;WG?v2%Y6S-=Q%-hIuQFXS1g-Sh%VPT5P2QW_w}Xne&;{iy+yz7@SYPxIf8JMF%e;yA+vXH~n!mjxIB2FyRzg2n zW5(E}&|8Xt)N^1ouxm$V9)vV_U+YDAiKJtJ?@U&z4+L_$A17x1=mjeA1C^{LS@!8C zE6#G3Lk{Zz3c8Xb6fL+Q4Nu=VebYXsjN!l~I*lXUE9{_-t80uxe{qDBJtN6?j>ghH z>21Og(O(C$TGnSclb@&$`37dt&{1bHkFKJYZw9zenIXh*5Pvzyon1rKt+Kwh;VuZW za{w9m4kOe&%f?k_d4%gdE5&>Kr$;-15ZfT>3Cn*KgQ6ZUsOK`j6OG?RSBzYIqGi9Z zGS@TMvOekTd5-035b)6vWo%`JJU|1-JR(er-nu}~#@NBDPAF+fQRB!a4v+jT?7D7iz&F@hVMv`Ts^X4Wl|Y*EJS6)B>$ z(m6(}sraFJ3JPM9mrII5|EU`P;?~~S&iXOrYZ;26n&TA%Mwdj_H=C(2+tuQF%op2g z#LVw0!M<1KPkI>H)+l#gOY;ju8`V7@wxDDUSZrtNejRmEj8L9M9vN$C>!@knRXrvt zeG;MQKE_p7#Z{0`I`%YB;f}JVI}gAQtG8*5oX16aveTpSSV6;q$^0|K9otI}ttq0O zle_8sVqykE#|jW~-;YcsGg!!-JDj)?TnyJ|^9;#&8G!iwjp6IB{Z>MNtG;K#cif>n zjxSy9!V0Lr@pMVg7iB#vgnQYtRHGosT$OLb)=d~|q@jLFnxHPBG@_g=r@X zT$tIGzvvO8^e(}XqvyS0nr5KpH2ok_&@drEx=E}LZMfeQ$bG%W9Qc7JT<<HT(6$! z>O%O^oKH0cx0jklT%k9JMPmK&{ElWgo*=rA04pd%4_%?V=$8X}M?=`>d_G$T*L93V z+fPTaGP*%>|KHM4XBj>kpodE{H~=k5RMB+-_YC$Jb`gBaX&n%a>5YbsM*Y@9Ln2cD zs7KL5GLeCg(ebJFot-|+@hhH;b8eo>;=zoGmGx&P|2eaEshJeVB-$)&j-9kliT34R z#Skk3*BYA0BbO<#qaAZ>eGMk3g!7+4wGsT(jUnH4pkU^Xc;AT8>-{Liw3A}<1yOHfmN>tW_P)A01w()z!& zqs|TP^bJvdR{;T8nsHSuo@V-+moK2$I4Le2j>yX?E&J-z*nG5INFn=6KI29M50;`?pjd zWM&DsNcxtkU(EK{>BBOe!7t2s@OYb;qMd~m#!T`*5;!v52Kv$*1?@Zeyq3NC(jGm$ z%U+gb_@MdG)8}BNd#{I6&Kf{W=#jwZA3})%>Q@ItF`yw`K5Mjsl7C1(Xc0$9O5zTp zj!X-#Rk2LGPAsRHwS=q3sL3?CGO|}U`PPV^ zJb|b4N=6GOtH>1b9Fs3gDIAO>)#e_FB1>HTbRnJs0j~$V_9bTOvYR8*4%KTO4BRC2 za+HY0n9#k*|y8zjf)s-0&`KfXuwzB-Tn!l6-756f#(N+bE`Wj5{pNh`PLmXu9 zM@pMRySgU0RV}tvBR+J#Z)ASrSf@57e8ggIiAET^fLg>AClDVMfuQfr%N^RFj!%x)2;QIN8#eF*GzG(+Ph_5;9(NQdd z(^o8Vt3g&8)=&g}GQis-i1m>L|Kc0<)$Vkw;!zB*83()-K0n?+o;|G$ay&PI3viEW ztn)x!vM^;OgTu|!4~asdEoLJt@nk!zf$F-yB8f8lRN`-7aqtt_b5Qz|SgDU)U-V}) zf7IGNYQy!v&3eZR6`JS^9PWsNB~lr6=KVghCT4PJT(7~oWEmrGw3@KtF99WA_jFns z*bqIiTPRFS_}=b7sBJ3F5L%BRU;MLOn2~^I?on4n6MJKeL@2ql3+l4p^{E|SN$cao zBkwxPZi`w~IGNLp0POf$Ze7BCWc{#HcGx(2tH?WVK)iA9Mm`dr1*5%`+j_)rVIp zyqmW^tVbhHY`kdBKtoR){dnS9iDIM|9JQjPwXeQ7t0Z3yU%!HR7$wsNW+lW^b#uyFDHmEF*f zhn6{UuL{DPRhd^;9Wp&QUZjQqrQu$O|Dz%ejwTVnOJrbx^qMp@L$desMgAa>CU|1! z;&AvUd2c4Q9Ngq^Enejkawfji?nfevTN(-FZ{v?&B-{vbW=zygMH4@4B9U7NfQa(g zj0ivq<}0G6YM7wP=*$+o6_&n>zU}pVL`pk^8e>lgX%+hi`Z>O=Lma=*A%&|S^)Vz1 ztEZqz^kQSz2J^rMI6roqkGn_MjHU;(h@Vvc+=@5Hu1KS6719xhgse{aSvKIc;#O)01;JZU+u&vj1g5*uU%y%CI)+kg1A5-?C4*iSK;wd3* z51+XpMD@D@6!p6n2}`NP*HiP9fVp;=doC(JJf!)Ek({S}PoJal>Fq=Ivj+Ph1ALrg zw#s8A3VEm7!Bw3nDGdBM^$Hs>mH^Fj#3R(3!=H(x%GN<|ouCw&iw0vJ+l2CY!7z;@ zF8~-%ZgwZLY3ITSbC$J6g4G!;!oUJB#~3(LqlcMU)!eUoDn|ih^^Pc#V?@}kbEIPL zJ@Ij&Gx~c{njvX)E@UE7dwrlkdfaJv?{LU|`0Z}NcFbO_%ZK^#b^Rx>!4RR>*UIJV zHCjpaee&l`Kse3hsfo;%aUzV(;QO}28Vx*i`5o@3gS6`lE-XsONph44l`ww~!On5! z-_VGaFk(Lt;sm01n=v9vj#wNRFEOJ=1Ru0@bd4T&ho9~zx3o0+63)vrpQbF{40|oz zCM&UNwZ0@6`%b3uj(5iGFd6~tD?Rvifube`{{g6ikhx*K{1{XIJ>-$ruhL#oq-Esl z6pwEJhDdBtjG5R58~KSCj)s3{I90EIWWvBy$aX*9PehpXU0Q4TmG7^^TtjL4Z={RO zm&^7S{P%mB?v5^pSTDNpXR;JRiOGkGHPfCjU=tD{xV^?b~w_kFbrsCkVl$D(az@iO2%{&cDPWHbD%X#$nSSaFx9wADJxCYz?-FxH7_kHq`t6J84 zGu%kT5Y%`K9@Z!`gEou$FqL4T46HSnD?f;6IV}OpXFko-61R_%2QUFg7b6Tef1{Gx z7hz3iX8~FG1xg$k;{0Y|cF><>qr3FisSCwPpz#2Y*DYYtiu%q7v<9A|)S0X&M(Cm1 zSPh|)caBPoX7$Oa7Ga4AX$ra6Tl7chEW5eLaOyUamhz23i7$W%l|V*{?o2nwwl34k zKMJ2`VmX&xLHmsM7x<2OPr;x_UMc8(j_)qtn$yE~k8oC8VX?L#WI`W%VIS{TeAG0p zcsp;^9JHN;Kfko~70>U4a?b9C`7g^MBXz=?PBUSPD-#8W5h-1_;g03TwL3LG@Z5Zv zu(u(8Kq$`2x6J_+y^vd%AU@7ILP+42uB9&BAs;~(`L66?52IW@2MXWy{cPdx2>qv3k)Q-C1Q|cN61m%Wq}M&kW%lW@6jaZSX>?qR7E&a z(l%`^ok(|@aj2O=NMroaHO&>{oWygi+p1BXs6^qnRo?+NaVXt!CkIVGs;K4zJ`>iK zcOTv*2giwshPs4`bQ7SWEpJO?Gsq;n-Ig@8vcpkk6KDi~MtVa)-Y+y{Z!?!1!2!v&Rh1s*8hTEH?!mx( z`;jOlF}($K>YRV)+P+TU8v@*P^5s>*C83kT=N&mpn59YRmbfl)eKWNW_JU=C!HXR@ z+S!mm`t>$Vt{Lhb4>k+^B|=bsa{Wc$V=!-YE-EB-$#Iv18Z6z@6S$Zn+&te(o&^kI zD>^C*7UwI>nVd=Mw3G-gEENX$_dQVwg~{>IgIFF2omyaDjoDo5gx5;z&2t7>{C;9@ z3`Zk8+9Zcw#{`d=JoE|t(gWBd7=Cg(GjY=oF6$-ZoJ43kx*;7iWuyR?u&4YT{oNE5 z8Q>Z_jG&V&tKG?GhVq&xb(mTwL060SNpn`k(<50_BPv3=SMbQu3uv2P41TwRU+)U8 z8`iW)_}_G17qZfz!s1+7%#u?k!xra8X#m*2TFxzZH#gR7s2$@U$dd`gR}z$ zk)i#TfxjTQGX7YY0_z%i-NOKj-Q{7P1So|k??n-Hz&?7=Is?GVqm;nz^MZYx(Xo0^ z)HUT7GgtSUp{&^QWDF%+6oxCWqq`0;vGpY)zg-}Pca@7eI)5Qt_xwh_#Yot5rG9IyW;scf* zNN}cv@x;E7d<&CIQ+pEmRGD3TehFvq3=q#S7@A_hk10CD@rQ>&3x{z5ni^h`r}C9%;^apy|^dcvdeF_83(jLQ|r zq=|k+%Hk@@WBvY|V0iDgt?EcvgK91j(b87@Xtuwrr@*dGcYCiZ4D@{h#Yv4RhYf6p zjqPSf(=H&e({z!QtwAa>cl0N#*nf!R`5J8VF1Egdgx13xA9GjFdl4}k=hraFxnFr9 z`+|Cd5q=LtX1ip?5vpywD<9{5R+%wTk~h>Z1GI{Z0Mn>DV7J@$z)6?%o~ge{aS-$yH69@OtqqM zy2qjb&FmcJ|6M^pb^vFydWFoKV09Sp;J9?TKxslJw!=n$=B1*0)&Jf(DoX?_EN5>P zXt5UP77u^pYd)1HxDC#{I+@6=TA*z{*Y?pLCvr*BHw-CUXML$&v(Y!c_r=W=YBS9% z@m9EtJuRpMYpMadfFt8}JlCbD*q3Xy70Gh)2WqTP2P5%hSjxLRRN9{7wP(*<1T>rk zT-ohn&BbuII&_?AsypplsWU2Oj`I_hHltTpug`K=Z{^sQ%E6dvnkeR0>_?Ki$T)?j zq@D4WG5ODu_l(XO*b~R%m|wZhn+r#nfNVP3=Sy2M@GeX z!)Szi&S-S$^Q1QH8TBje2A}v%H;63ocR&E8ff9~f?7A0THT~jy1A`xEj_+V2h(UOG z)26ujwj}q?@y7~%mbZypwdU7QRz3;HP5}5p^1!kAQh3eXJSRy@Fdt2uF$A5Dh1Vxs zAY?N}QY!pb)oz}N{wd7Q;bG&ui+z!J@(lnU%VlaG38pg2xuEE2vF`iUK*m#}$M4dg zx6xnYw-=<@3W1!y$3f`5;Qzk2|NfNf=GU_v%2krNeIN9`t(>oWp4U%CY>AH8=Oe){ z0<>l3_3{&HnawwxZXgwsNW7E}GL5{{Ld%EGL=}^u(}^`#&AgOiTdE;xOPsm#_%X&W zLYlb$x2uS*m`!ttxeZ5lruHMV^lci&srEMkZ!uicvAu%q!rG1j* zIAfKOh8;gBia`{67SJ~DIq_pB?pDeT_$d8w1E zw~hPQ>I||#G8M7K64*Z`Y${u2d2pDy$yUA%3XlD7=I{CcH^R#`;a*@SKj-v3Ad`GJ zB`IvbB-o&20h)V1k11Ahnw~9^W@uX7~Sh*X8}oV9YPN1VKl8}q}VX@ zfa1-q8s;Thk;{^4s2!#EeCR7=Xi>_b&{DohI6%faA<v3tNq%zOdM zFhO4Uv&5zBb36$>BIJxp*&e}BiFQHqNlEWMS~rocWC9_LEoZ{WiLl=)0IML`1PCQF zaQp;&B)K!PTon|cd76V%L3?KYEgM<)U!)Enqp@#a?(%{3Whx`EI0=|~gM&1S=nnSN zifIl|8(zR9tB0Wu^B>@pF7Y*b)^45WiVPM zg5`T8O%i+XV}W-v`zDS>=>?F|b^rXcZ6dZFyIv5S4gt&&t0MMjQTruHr7v zkoQ?SEM~<4%bTEmwcvFt!;cmx;mxl;f;jTjQvS+^acES;EMO_KX)YLg8}{&sH0*Z&LOMcUX~Q+VdjRY{ z(?j-L=r|$#Ko_NDT5+KscF|G|<`?-3s~fU(7fAtPdmUYY{a8owKBTm0QowVT8_Vvv z)x=aU{g~GgvdylC@RtX3)rR(Q2lfs0_gB?{-zDwdzhfJ(O zw+aNug*&dh^J zri(I$khz5_zb2nx=%5!Hxt;ay!Ekq51cSyKLWJv~>0nRL_0VE>X{j^r zGJ@^nPVWMQXb3%MfD+`NAS5{!Nj?}%MFuGX{ps1XpP_3CW#|1#>%S_De$Dc>ZYj1$ zZ~J`Eh@`MI0Ie67m?|UNeGf%M zEdUV+|Lv;-8$&4eyyGQ)%I0=YGpN^h1)Amazz22gMEP9mUgN2p%X~<|ZxynZ{i`~^ zfT>k;7CmCfNV%jnnq3Wc!*x`g5TV~c%GJC$Jore=P&R&k#La%tyj zgq(2F45TzZ77|W23Nv;w-OR4Lhr#$vRr&jth~0XB9WM{9;lTQ%_3;Y)7$4s{z+)#0 zbatC64oh6H%u!pDA{G02H|~+6xr)r-`Q7H!K3vhsgy@Ld>yrZ813>g*)T)@8a$3MZ=pW{xMLel3dI6 zO-=}985#kmf801UJL&1=%>3%+@)S`vp7EkI2m4BD`=VrNb)vtvHl&NyPW~}fK`q>` z_5Z^%;;@Gkk&xsnF_1LuQ%}lSXJ(I=k5Ktxiuo#l=KQQ_%(-?Jk#;NpuGuKv?YwQ6 zgG{cfHs3PXb`G|u`-7(ry+CgI_M*56un9Ksoi@w}zmdQfCSaeB1dTN3VjWM5>OXLH zcNOT@u-1dQFDkv!} z?$r+c$ZChM=vaKhhND9-vykaBi`CY^_MPW>fd5ecq@XCV745nkMow~V_=n+r zvHV(Dqn^=5(tZg-Z4f&}Lw9HlZTC~GX1Nt)p2%bjk4Cdzn%=T*8g9XD5~wsVG8z#T z_5lJ!b+t<}QrVjj?+6t(1$lM?EGo)7WH%;kP?RPelf8)ifPL1g`xYw*t0r3^l&C9C%)dAKYYx$1089uqL;wnQS#y#>%QUeI3fKp* z>QhMjy$G<@z||KO=ww-nh`nVS1Ij%w62w-kxW6h_0Fu-_i;EIC&O+9U(*WT{u+zZX z4jS~GP{%F)IxtMy&&fZY?&Tey%_MPh{kz!P289wOkK&792z-MkO1hg?$jhR>+6eNz zkcLUCAWZgjH`+w73Z8r|J1kzL=ZrIq=Wm0RTU3=Hbju`w%40i2 zKSY9p*b*)eQat%_H1*CIwWFyAN<>n0Qi0XVW!}k5P1mxfr2iC|6ZEcQpyE5p%92-p zEqjf>>wk4jN+i)(2wGu6HEY4{SEgmNta1IrzF+S`i0kJI+7P!teMA?u8-N`;Zho=< z82Ruc9twFw1<^61`vzYyLmFbAIF=Y-H!~~}{T|R}*b#LxzgDnsa54b2^P&_W2t4p> z3v~Kv3C3FyXegQI|5R0i&{j0B)lRs)_HG=ck9^|J)HaaxU+2+KX?6CNw?XIBgnv2n zHhvD^O;sE9zYB6b1PLMi@lN=;ufg#ytua3w&22G2j7(&(pfmhl0dqEr%as;S=8vhG8f7TZml%Rz= zc!?@gKcK;{(GNWdWU~Llkj0)v(b&{AQNzUQD>WLpX;gMd*<~_^=&!}Z3t;hD`o3B- z`m9*TYr3RW8DPV?<;4Na1yq%&`+-v=bqfZXIW)#ItU@+S%GFxB%^|EifMhwtDkbL{ zOnHNDeDsP9q=ipkHh*GpSy06kJoERXt7bt{T^d1kPnvC-p1U_pc-uh#a#qK2QnvZq z3)4Z`Y&4mBe8b}K^#!`ZvAd2AhOt4xKGm#733IVbHR!gc2xM-~!W3{baZ-95q!Rwo z&t^Wbg(n)xCe!5h&ujcl{bv>kap!Z+a6?K~%dE)E8r`b{&#Rj#Lz5C82meud5U^=3 zJfG#oP4>l4)k9zq<^_g(@e%qsKXeoewhmT&BM9*V@QlkPpxrGhZOIUL0pe}?apusL z*9{eW3M;P~7_8KKJ!&cMr#z>Z`N$x8i3fddm~40y0i#AR)J`9SIo52x|waTfx-DU z_Ip$o@XZB+n{pD@-#OSwXYQV3wyEe0AdaJXBub;(c?jK2&?#ErZ7(?QVk#!V4#W@W zNx;f1s^Sp3-r4e7Nw6UdLx;0p&>}BP(6h-y9b*b5QHfz4V}v?I2yucd9}TOeFozSD z{aJ8)wEr!_gvO796;*3L{-YKh+ zb3PQ*dyekDW7?$-X5!D#?Bri9WLh#6T}9+%TQkrB#ngo}y^4K^ewf?MrK$-gJ`H-_~fRz2ctD zmgyZJt->&2r4=Wd8*g=E(CmRNU~07nz(?hY@tPl5AuEGfqe|35({2Vc!N9t}sor8x z3qQF->%@P;7FyVEj&?2!`Hv$GwWM0szGNr6eA4mJRKSAxF&G=aCkO|BGRTX#mZ(YY zQO%A+Ohd%lt;Hx(y47{@{wLrbdc_=PcAqr^j4!o`)vg&WdtGE(aJ2qz-0ULOMNNQ< zUug0DI669iwx0`XvTY{*^~hf99}Hvmc(Rfo>I=gK`w~5fH}FcD#l}*aVfE-4#6^GW z8XuuxV%+>7s5bUvE6{x-P{XgGhly8rRN<_=Y07ZRgf6+ESV*~AQ=zJk?9UetYw_~O z*}E(EeU}bkykX^$i6;~5f_f&~;59tcHf#=_X(KZ&uWHth1Xp?thhejJ?S{_t9f+Ri zgn&UEp9EHC4Pt7wIWnlx&LHNYy3UB5k@9O+fXIdy7TY770tLBVln;#|-Z)EfMxT=$ zPLrBwrROXfC{X?_Oh#3hn8R#nb-wo&I*z6!y}uq`VE76~vE-lMgTCVl`y45Ud|iE$ zt5qUX*3&~_AL()s0yktBO8wH8; zq8bYNOg88z#M3Ua4YRgHh4y43>Wf`UaC~IKqNFB9R=V3me#roj{~8CWKxqfT7XTZce(P zV=IUY<9*A8EklwH*JOG+#P-);X27DLb#0&z4|*cfosMw{Kp#g)n04?3YzK-QlcsTD ztbXwHoK%AdjoJz$^o#N`)G{i+Ubw+-f7K@=$ETk3XJ?0g*! z(J>_*)Q)=1&{T+ukz`p0R)5$l?#aYw2=Xo9C`3_uk+#|hMA2(J?T0I5PYlh6qq5h% zjfHEK1$L4dpm||NGs#qyhB1h*a;EZz@`-UVSLFaZ#zm|McdUAMtQshjYmk!^MOj^- z>;~TUVIBZUJ*>enjY#z!l17#LLLW=D9Nd`33k3-;zs8{8&cTXWrN8XLPLxhuEq3b(C=1m&L~-f0r{D~ zKP}`!h3SEXbwSMN(t_zFM7ClrXiW?3*jeh3&zq}L;uf`((;Bom6#^P)WfmGmz3qAM&B`EQnErrh&@bo9E`80(l zMgPFTxrahN)62GMP0>iCF+EWFLx!B0?Q_;d_k8^?_uM2<0dOFtBKTjiVX>p3v&dNYbnsm?2_l7FuD)Ji3`Fml?!y1Hw|JjS$}!E zaKIU?t6E+ZZk|myAP(|)jsqV{XqAU9IUZt=jP2`A{vc;F;k)uilla2DTB2PFla59# z?zsLU!4aDT{}~c9+V7mL7x>m9L(EApkibK%DxY&ZM!pZGb%TM&-iIgF-8p44SFS7n z94+~VQY?Xv&M+V5EeeJ)9m&jwD(z<4FbbSG_iMv0FwBEiqx!hVR zqcgO2`hY;&x(q1J#Sc_cd`gslH>uDk_#rIJ=MA$WosMnd?Mc-lQr$4RG=$*m+^CWh zxvGc)KEzEieDBi&0`49R-r`ru(aQuR^S#Tk-09PZM?GbxQTRg4y#9%=S_{h>>q-`iz81Ud4@^kplsk-H;h5x!ZEEtz_|jid)g$EOJc*JrEsU6 z<~89EQLYdL61^(+{T1%soc-Iw(UWS?D=X1~eMo{UOthVDiv(WH00^7#LM>IMMnRqH z4|_p1+BWk+&t^HpjFs|uih)%3+kvUTZV17A3WFT=L_1WT$(tn!7EBwc-h;8oOyLAU zGb^hZ5*|l@5>IPkWG$kxw-9g#z?=Nk?=4O(v@mC?&ZnDY%f_%& zWU}J!f;0JSl}2W0I@*c1uH3Ts?`oiQXaIeu0gCV9 zvh7fe;YJ>E3?scv+w>~@!c)6t{hKc(7~YE>rl;}Cp@jywJ#PZZC~}g9upMENF(zoegCv9H)jA2{g!O7DG$Sqko#b$H7Tya9hBYwtA4%_GeGjaOKQP{NtO3Hr>C zBN4-Dp{Eu{iE@}MHUI^-IHMj_$Q`o`Pli;C?))Bsx4S{E1xA*(iy@&obeMc)XMP4`v_2joCsT~HIPo{MlF#wRhshK0z^0rkPZ@>JYch5Q1HAY+)$gM z;qY%4hg3-=T!^J<5HI&+@W;$8=_^z=yUAwB9jI_lj%*N~9fiNzwFCv`=bXI0LJ~Iy zcL8bU(9v^>;N1YKK7r;w+=|>lyXEN|azI{YX2N1k&h-YApDq_SCt&prmj5R6Cy zAD)yMO<+h5G9Q38agB~IhxE2{^ndHv>o*%X1-J*O9})}u3}FF((j~L~2;Jj|RC_?4 zCP_rtNA^#ejQvQHFfz;dqZX`{Z6*W`WpP12rYKNn9UnlZsd6%O*1uugW&s4e%n?vC z`4j1;`|&O_F)}Y#*0D5y;rDR@@evUCUbcwycrpDzn6O~1Vx#v-@*2+h4u0=cd*_C@ zEq|JB?8KxSPkQnLSm8>a@831Vh~t z6{LzV4ljlu`NHnv` zg2YDE8NWhbygBEXz9|7C=1jG*N{h|J)TzBhUbNZjmA*;~E#!o?utkeqPu==6C}=DA zcj6)i6!1S;%VOZbZPi15zpls&G`X3JzT&0=1RqPZM_J3ON{P5cZG;S?wh(;C!tg+h zvjKe`pXVRf^GT#rQ*^C(!7e%G>2(hldC4Az!F)Nd2gBvdScElE>@FE`B$6LsxgOCv z0X7vSm~ zneX!uS3qdm$~gDZo+`mWI^2s!lnKSeqR+{#&!Y`Iuaj2-($VlQzxXfrQ=EN2&T^uh zv!2)+9OU1QZKSKdI43$#2#8MPJt}b&F_x|%&0ofICAi$n!=9d4ZNiT6{>qe3>3_SK z3u|P4E2%54gQQY0^eJWbAY&jhG)Cu(VXT3RM@^&d8yGUtDrNm~m#ojY)rgU1>2+oS zCQ5}xQb4QN0M#fnwNmS3&|?H>k^;9}^p-*%neCC9GR>{$@&+s|Aq6`vO*}^#WaA#6 zm)~HWj8lLGJdJEO5;A$GV7gYl_9K3?Ug4D3@wjgHQDj-abID1bBMW{hA!aeax2)xb zb<6AJz-qRh;_Pu|!klo9mL6K-!tA z_lizV%wq8FIGscUYgz-mvSf$}{f(B|o3X@5If6-C2h2a*%LyCv8nx3+xW$J5{RlC) zK#(n{8*S(u{j%OMw(sOF%>uw575S6RBp>XEWRtow@2E`XTK0*(=u_|Ig%9a~=a51% zdPqpd0j&kI79*#U7H58vZ!BVOS&AuuTA=mS)%UcQ0?-$(^-N-#|Sbau~dmxe$HdrU>p zKv~uZ(#Gk@72R3S>?)+>7oPJ7y&f$hT(;kku)@8(70b*oGIr7T08=w+#J>nHRy?EF`UIR z-Xo}5jlRBJuG}#Rcke-Ip@FfM5ws6+aQ>hi=^#ZJ`Y$w}f?#PzCz{t7$JTmakfH|) z3IVrB(qOr-MoGl6pISfM7c@XIlx;RvIn@drHx{kD~gY)jQR ztCR56sA1)4c;_T zm;kz|I21M#@Xt9v>YPht-l(2|mpMIzZv#58M^UdG35HKXvwRfOL>3V)MRgrW3r7(5 z&>r{NUWc?}G-<0S0sy(?dLf5(Q`sDxUo_zM$PSpDad2Z@9jT#>OD{W8nkl$iXQ5~R z!lzv7fr)-HQtb-3&)V@R;0a*Oj--=vgJ@arYgm4lvae_C!=bMlbv-@cf{MP@!smiq z-+T=N`5DB(MiS-dG*}CyfLrvRJDW?W7val=AgFV9Ys(pI%r&)W=k#r!bwct+R>v=m zv;d%#5z4jm^O-HQyyPaqDDvT4!!Bn7c{DdEeDxy``c9%ei<-!Uh?J0n4!CeVmz&X? zJ$M_-7;>>{GlNshP^dl`Dm8??Wk(k$8c)D11i1cG3iH2Y0G5nJaD-%CFhn7u$r2%1 zX?MR7?%Od2 z`-(L=tG6#%b9|6RT1)wxkI}`&1xLn9sz!3hs*6HzzyV|8_oyntA!~v|ZfXxHKr)h@khD!u4M{0Zo51n=`v3&5 zw^#Tcmb618VO+;$ISf~(l~9fx#ArbtfMy*DCY`?7Y4xoM)eZ3e(GOf#ntnh!24F!o@|gcWk-y>)i@)CyWO&F9rZoNMhgg}1p{cYKdEDRJmNBs zutV=)9y|muX2tTXyAzL<IhDlyb_(B(N84~7wdTX9bbG}R}4JJ8nY9l_!6V`a;2n0 z3_&N`zF+3OuBmYjc$?IB7-RIIl6hF#Jg#^-Z>xJ($w4FTsTp&ICAwG()eI|9B;w1C z+5|aV)g6N2b|FA6029Y!;lcVCaE(&xYG=a{Y$9Y~s89$TXBtj?Av{MG>YZQEyt52Z ztLt;*qI&9xq>ac6Yg3KSNY6i&4@}O%fQ+<;1LOlF1m0Ex3!bFJZX3LKHidhIrx!q^ z$AF{Pz%JDkL3uqNbr+X2}3ZY)ai>?0p%8;U5rG0 z&~$(n=*@my$m1zWE@$K}Z~hHM2v0tqK+Jz^3K#{nJV#6dN92MPhA`@|mqsde*|bd_ zZ6R!Dkj@2x^=BUf8c8pOtaAw2%Z?7JU0hcWBz1LLuQ2COcYBATjE|888q6tt2T9t2 z6sgrc?Oj$CJy@7ueRFW8Cod_aH2h4+{ppERycNMbA8xuTj>~ zP-Tz6Kk+t*xWS0FJ&U%j8(~e;OB)2BI95kGPbJ%WcM@c8;sD%IC%Cq$zJm>^ zgR%cv8%Tt^!S$nY*MMj^8{zm+^~GW3?-TTpwkcBP@hZDY^QtKSRrliS8!M0iM<BxfTrli>Y;{pBktYF{qr+{Jcl32z>ou}ptBEgg%Noz5qV7qxMT!B$QlIj zLTQy~R9VG@9aX;&sz3Ul#I3KZJKV8x*;U6{ah4S85ph)nQ<3u59C}gdDMB$2ZyrE< z!2;YOY~0vg*x>xR#P!DEU3|G4YQsJc9|)Y^2IOywq5`*f0qD&_D0^55WfT`JCy68z z{?PnD(Aj?Nj0@_;Az!Y0N;TFTQ$lvZC+;x^oPE~%1Z|U#+~DOB6uhhz2D|1kA|`3g z5c3$#0SKxms7RHqaF-(Z+FA(k&(EqOq3{CBWPCkNS0<485vttWA(8>RwZvt{UHs)c zsS+m)qQ=!Dgtx6@Nb3RB*7=3h#g8@i%jt$>H+!dydsF#Yl9eJS!*IZRjI8nMIOir- z<~Ks=FWH-9psHp|I&(Dx?&`3G8HdXFt)i6yWt)A<9=cqjwgk$ZmWmxZ^*THDUfrra z@P1y?e(StIRxvSKfEp5w`e3V+3x#%4ZfKt(A=Mcw4e8{!(V&SDYtmL43U6MLnb9FA zTsiI4pSA={S!^N>!xTQ^)IRQ%)rcTBbgyw(CH1Rt6;WDAuY`NDuMr9tfdAgWUp+LV`9GEUf|tB0rnycFHoEt zs-8=_uC0-G9|j@rvM-=3m+c1aQs{R7CI_opDQ2mbIYW&IVRy6M$blNI)n>hoKa|r!vWkI4AdP;PU{v z%4IijwUh-#-HB{cf+A9fCrjy+`yw*71KPDIbm(W$Fii$cUE#2#g`2 zZ*9?z`d_I;s49MZj_3cUs;`WSqiMno?(V^z;O-8KySux)6A1237AN>(!67)o-JRf0 zu*D_7-S@jc?zuna^i+3O*G!-DOwUwJKczu!{m2RbnM0;}cFi&@g;5V%s#JTbgy`^C z>8$ned=S|#-hQCKhq>-IU>5815#YtSfL|&%E45`56>hw?>o>e%0yCijp5mD}Gl|wX zrK0C_Q|-x)gl#+t$wl~HTwzM6^E3Tu?~Rg|&0mc>;y$i#@JFX&KMb;pp`EfMU=lDF z#Nasg6!Mhy$NrA6b*a5_zDlLuHu8^#bTn0;tJNEO2ogAqjaN|&RsLylf55@KTJ4h% z3=@_hY#f`o09padmPzzF zHfiotmrijHPJNTn(C4uZC~r*d(JR&)E|0M5@nE3o191w6%{qA5O#~Viz(I(mB%Gh`f>= zyKkwqc$W`;_)xfqSr%QhDVY)tnO~G0S-_|HP?H3y@G3|g<4?kmoNi>NK18Igc;b|w zNVh4@dfkM&cJHCmK60uRL!)Z#mqn5-4ps86c&syaQ`Z;f7w=s!C1t!gDnwsRiGLIVVBS}of z+IzN(WBJ7eS29OoGu=FSAGM8&OVnBbMkxa)VhaNo$HR81Ppf3cX)28~d;{gYlfZn* zQwB)B#03|PeLCM2HjH8VgpP!iu{vRPjlYvyg#g-2ExKfNG5!wcyK*()m9RaN?&x8`p{&Ezf!e#ZGy>=!1ILF`lSF zE@|YAt)zsV?~gFWV8GZzV2eoqW|jcuvbgy{?p}X}euH7bT?a#1_c7MqPdaJ=#S7`X zg*g&P{v_iVmyuTf55!OF0va(u#4CI@Z5@h4%umf&|B0>}Tdy0%adIYXvh^AQ+n|yG!v}t8d z*3*RkbB)^)VlV0>O0M3ceqT52)LWiWkC!YsJf0g_p|@sKPCb-m`fOeFsK z8Kh9ac6rZa{)~=U6vO0|_H^@k@O~xDrs`9ga;7AGEL%LzhHXRWq_C+SU6rRgbi{EP z5*s*T`rhCCYLxh5N7SCJh3mg5XNa%g)2HEs+k6K<4gHM$Z1c#m&6Phn^~@C)ADcOBAZk-4r(CCn%?h z8ff_Ji;quc>J&rtlgJv5yd{3>8=qU3pm;1XO1mY|{*uP*v)yI;W&aekbB@82G3ZK~Amx1C7m?Tcae=P#RNO<}ZqEyBxcE81mB^NB68IAPbrHwd96 zS>O7|>Zjvz_XWD-cnXM-cvE4g&95!5UWZtpusip>0t<_)q?ZqcDJ(3QGvhplxck zWLB46-aq{}N$yUVbmz-R1iN@^`Oq0oQQOpHe!z|edR9Q$cN`k%C z^zwfgGjqUxFsj4E%7wCKVM}4wNN3YL!pX&WO2U7)OXOm*O4|a`luh&K+f|iaI;ATI znI;g5v?=DSE*-1OYjy~JBR&-?@q#k02!^ja5e*m674lO!X9UMJDs;{3#Oj`C@8MZz z`jxTV{A8#eWQA(n`599^$mHqo3oxSfvA>3#?=gbMjey2jy0PKh8*(`pr+FlV^EfPs zSFtKtfF}L0^71S-9A%Jmjxw{dJ|y?$&0^WP6Fo;Yv}0;=xY-ZgOcO*-kQo4pQO}+O zib+B38z2;&QEx5`S!Y<|Jq}+BlVWW+ZO70_LGAar-nc2G0h(q*E-g$071=_7LFtiT zFm0!7ZFm7wK}v!dJu)Aq8G5&DO=f-+XR*m*OsJWevt*(>2T&ljvlyBLP1NthG}`$=6J}h2e21QORjCONZxgd1a?S zMpJ;Oq5NOdi|nzPcyjf{QSwTdc=1?RjSEMTQSyj`*}tpl8ZO^UGh`N=&*!YT5Wu`(zP;K}tYQPwoS!;uTeRQL=9e^12wM!m zFdQ?;=LsqOm|AMZ0cjxiO6{PmMMlE0<~WS>uoRsBQpoLTFog&&Fmft6eEW$@n)Ucv}+Du$m3kEL%1tkx#64%(pkYhyr>jCF% z{F8n85SfR9EhP*Op6wT&bxI9G8u<20ICt*xrljx;3OER70*6C~&0^#XU?yign6yM6 zlWKV%#;L>BT;wA`RP6}sc->mf6M(Up(}>_u>uheI`5>+j`-CR#xCdX{(r#_5{6`$O z6oMoQ--wzoLXyktQAs@3vN3?rEv&}!3q*b5SN7#*Qc9@*mVice{RN+hn#5p2$MvL? zPh-4kr9W`P!}QRR7vA6+X#DJ3!Q2t^YMAj-=Eu;r;t)BNyOfmD@g*sxr76}@c^hkw zcHnYKFa5!$=DwTA@ntK)dvPnF#np9K&<1jNxDK!P@>wQs$k8*Xn6L*sQ|-g6NJz^jcdil-F{1u_-F9mIonqo)`l;zxb&C2g}4tx9pTa zuKxybbcX0R^I0ly<~FqYu+>xsa?!=a4aFd?*?Uqx+SEX{6t1LVjXQ=gfCV`v{ULxU z>N25{^cTSr|F0y100vS)A?Z(wPHDo1S%muk0fuE9m5CS9MpV@Gfbj1pHFd^rvpQ>@ z8EZ+G-f%|)DmFo#49SH;A~A7MS2=*8E)nsNM^w^ZIZn;w@9vPJMnt33x5Rwo1q<61 znRBO)=5jd`%as5bd)}GONWTJty=NQBf+b1eZPvlt<4C`r_`~1B_;vOTLypwUe#-GI z59yfnaTXTwD_iV4-bPMf)Gyx9j4s_Cq=--^Bg6QrSS9r7@?2vtgDm#w`t*InDYd^j ztQ-}Dmvab{TX?B|ED!qCP$)MbqS2B$k`g7iJjn9bQtvnPH4jNPNBGt7@Q^t=N2=FW zg7u)NEQFT@c_G~pa}7p@EtN3?(RGY0J?AFAwQbfZu+dnw&~UmrU0{$H;_S68H$TR2l)|0*lweCnB>o`9gmF&B z3Q7l!IOn*M+EHygJYBZaVukSfYl;L-omr|rKcyWdf{eQh4K6o(d@6?&^Yuj-_j+xX zzH_PTePt#th;3EyYXd#dinUk~&9si>Ju|!(qV9pMWw-amMA04I{|;0nkaq1fzPZApe<0IOCPl)kLG9EH|&kBDwAM zTPD4(!(%wBmtUDx9R2ViS9RnjrhdiZz5!SRSVV@``=ofy`TLQo>1?#COtCb;YV8|d zRghNtKQX)0(m(^FSFrQgxN&;LAF26wGlunEhs=wVMs{Vt9it;YpmEPq<%aDL(+Bgo zCY`6(JjPx(g9&;|K&_`Q8?L`8r(umcNqW(8lJcOQ?I{oMsbh`I#)__%v-0G6)dG>X z_j67TA&)kW)8Yca(Y|;^L%G;Q76>YmHnfe((w2O6DO_@L)%oDq>iy>s>d>m z#ihMtXq1Ujyv3@U5G<`oU$UvvO-kS`8Yz8<7G3bZv>X6f`thxG0?|VXSE+ZmI}0xZ zPB~$-Gyi1R{yU1}Df)h=_7@AkpmgcN>*s{Qdp6iGf-{+VsigRYU06&@DG1$wRBW^` zB`3DHo`Af(*hH>7b*9d8WA@G-+9Y4CJD*lSnz5!Ks1A#f$9uOie{w=<9S>Hj_&Jw) zF3ezP?BBg|0Bd!}w#=LyQnDO#y+iR5-<#N+bq^FoK|%1J}s!=D*?v+ zZTrT|ah6HMyy+{Wrzw45ZhnyHW}=(X-YJ)7^~~h47=*cK->w67iOe=Z$lb+9W6r|!S;e-*9xF)3ZS+cv^wyHS&tZe8?F-zL-$bUrwb*mT@>wFT8f#JJ1FPy||t z8rlQ6HoY`yHeU5qsT+FZ=NOb#(9hRgRAzKDG)jU#@Qc4fwKOr@ZdNkSOBwV9{gQ90 zJKf!UR#sQS@!*j81DH3S!C`%JlToFP>ZCo39- zK0M>NZ+&E~^KrYj2x&VU8~RqK3)IU!1O*cJl;cmomk(k^l|2({-ukEL=|^bdpJM4F zxGEN-n{4Tjdz!`7lR2SiU!Z#^A7oq2MpSR?&=_=lveol<$))hUg**vVCqsA4Ej>_l z;E0=p^*I)kN7WO-At*XnymeSIqg?8kYjfH8N-8o$frVmG**6I1qvhQaTo zhLI>w21dyOdT^>5qRU6M2wH4#j0aQ1{f84ZpN9Xul)W042b&wp@_|(}l|{Zxb(Ccn zVO$Ut-U$e2G*X5@GWI023*998qm1fz3as4lQ0eBI^b}Mm9^1_y&ofldP%EGxZqx{5 z2c2oFu8J@WOPRR9&Lv$mQ8{+@3x3mal0nvXv5_A{xOr%Y_EZKJLpH*-#}d3t`U z$eYvI=$J&mc9)Ajqd6(3HFA~{{R8S!h3h+`0yBHx%V_6{kQE;Tm|sk}%B%(z=9AlP zF=0c`D(81IgDQ^UB;UURLTuXxOQ_U%&ZFG4ph3_RYm_sLI#$J;rAs10`Gml~k@ZCj zm%jvb>W@Y{$y{n9qgQ^njby|ZWH{Bqn6s-Oq9!o2B_h)n3K=h&*1o=Ees#VVhj--T zL{v~7;!3n%1RbZ1o2`v65X_lfWAi#Va6L67jx;u_i4heUgfyGxSiYOPsI^)e&k-Gm zZxQMZ_$HOo9*mxvyr&(J$-k*a)qu-%?ppjizs=L0Yz&jpIhOm%`rS*z>=s_xDwXQg1BF-|vEV*}FMlHyJ{Odn( z8-Enx~OSMul*Xax?(xPqq%+ZtCo+7lw|sY zt?t9M)J7L5x<m;hdU;1ne#A-XCy^kqm3b`k@lxN#1karqD<^#+mg5#d!k>M8`&AM(+rcR*nf$T~wXn2jWovQ$~!+nmb^sfg}l{?tZ4ZJ4O zoNr*1f%`neXX%#1GzexY2J@k1KiH3!WoQtkT)47je(IbfF*RVJH(IBq`Mam!(b27g zAK6p-C=O_1%J!`ton7NvDC;Kc1syR1iQ7Ona|4UzIWJ!Mjpd5+cn24N&5L)OC*u5r zWp?!s$(fKs5z>XuISxQjZu9ylW*(SLsn2pg*R`Oi0O7uT4JG7 z>UY-B1??6geD_V{3Pa5ANBE7p7aFMKPIWS5x2c;*ol=;%`)z?mKc%+?t`$~BP*z=} zLFcPUCBBq4amsz(6Tajx=n~?Q@znXV-q!@u6Tn560jn=}o$jfu1G8|~C_Ecupi4>Y zQA+yoy$P zUX{?vtVk z{fa)MsU7t5a<)uUn!Nj@?vJXFOgUe8O;?eX;tZ)r=k~s$VCM=2l3l&!t5(pIu`=_N zlstjj3~k#e*Zv>_0ipU8sb1$pK(74 zM%(A-;P_lxPDgbhU`cgLKSHrrF_KB(@Jxz=XJL*O1k-Yj0NGT{Keif?9FXArXkqN3 z+OQPQ^lKIO0k&|NsKR$BovImr#Wy4|t*ERO`%}5NQcFZN{E*Lgn{tcv&876ZnLUfQ zu`f6beoS4oQ=gBSg~o|wSVfBhP7NI*N(5Gz=Iv%px>qAAmL(&PdRv1c{u~83KdTpU zHKIW1k5)pqV%!K1pYMq2EAffZUO0zFG0sNV&m9VZ!1t4O^u=HV5)0Y^Ovh3pc)ipC zCg)!Vsb(!%6KJ(jyJ@PREelq`n$IKDq75?cfm4*3T+GP!mcZ$cxZOo|>6QIj%$=67I6E#JuduqnkA~cd6V1N$dSr zO&(i$p6H*Ca|fVWYr$$dS6d-%VKg<{-5(DkG`lNxl^N(@ka`-1@o$m4L#<~S(&3R@ z=gsTOF8$PKq7X9yA&z!ku-UyZ#y|vg&WXEj!TjXbmEVs1;}*p`K|2RK)92RLbS$rJ zPX?qmF3rIbcslpTuQX)a&87CVj~fY*dih>CN6&PdU;Rf9;A1sSvDUG-6z8Wt&kMXV zMv6mXoiJ$ltcDI_7pN0(WxNnl2YaEaDc<%KUm7n2p)~2j{?COQwFbu-id$n}3#mt# zhSS2<(JtjqlBpXby{>o!26Cqh@NF-CX&^khWP`S;2JWSjA{cC66jB_xbl=i9=GPVI z1MmnL%hjQ-hs5UMKjf+i-gOyYVm0q%!79IHaRWP7vnQb^pRSKZz|7 zru};%WY_eq45fQ49eyH!ynrq~rq?F_`|QqOXAm-*ic>t3!# zh5oekEo<_XlWGZ@!+oZUwO`<9ElG9yKfZ2}Xf+IQIN9ZUTfx5yQI@ckOs|(urDXI{Ek7YWOuKM-?lyn1rs=hw!j*234NR%S8tG%`;L!Ml_+ zad9*){NUrl$pbyJ4+s)F5$m-4FDsA^{m+bEfLTI;;MzZr?QnbM-5dkh&>Ql9&Hb~ zOg^fPEs3gnm{w{>EHC6M{e2%bjo%zW=sllKEA>tc*Q|`aUbwgMsD*_?hc zuaUS0N*J#`of}ZG+_dG@Bk~~TWiu`kS7YceScym5H?jvS%y;g!6{tgWU>KZl9^}J& z{qGG(E*5zyp>{BR&`z~bJ~Lt)=sHEhS5vagT413u?!RWR|cMp=>;?E5mjq*EGnZ5Q9mq>Zb0>X zql2CFz&iyRs9-{0eP>MAP>H*2OfT-uDP`dg7M3;Nw9!BXX2U|o8ZB}FK)T&|m%o1} z#Y+fA)A?IHOErXOeGzxk*B)SOBPA4+iFBYWI>Of#xvP9yAoi^Q_DrD+?6sCWv{pg) zq;5=HEl2hXe1Ru2L(DislVXvK9%jZ>=C3pCqf8PDZIELBW^Ni`ghfOPt2JTz?;J+B z1~^T?rh;|s=<2G^)Mq#+a`#~S2lX_u+W1nifwa5RYYJKB48Gs%qsc*g&pYBUy39d~ z^AM&N#iWzh|FN~x`_$VpZL@(}DtN2~G-+yPj4pql)=<}h{?_S|7+=P@?&#+z3GV?z z10r1&*vy_FI0G=1yf%qs?W@zC2aZuYkqDuQQ`(}R@dD3UwzM4BgjwQ%i*FPyG%p15 zA^Tz;gR;&9a+-!=KUH|zxXVt){yKFnnEkleerVpmbrbQ@#PHVrS%6_bT;>^XbXzA; z_fHX*LN%NXg`*GN>wYGISj6V}&sxR111a76IjkgIo(7_c4dL4D9yQ|yxkDC;xy?<}M`ki@d$b7B{D}R;WVz-)25ImD^6b}V4n%UQyIijwgFHANsvbDW1 zI3#ljdH47yDT`J|#0Ca1Jiv`rts}lyAE*rwro_*WabGuC}R0BSs2>*QC-z4H)WVrCf;{dQHwMzJf8(JLx>APfRH;%#j9GvA*ih#9^?v{cDbNuB literal 0 HcmV?d00001 diff --git a/src/doc/common/_vendor/scipy.inv b/src/doc/common/_vendor/scipy.inv new file mode 100644 index 0000000000000000000000000000000000000000..36244b070581a450e8cc97be5414e81ab2e4af6f GIT binary patch literal 112691 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

    Jmd8AkG0%yB)rw`vN;B~4&Jh!F$kmoKjUwlMF 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

    eS 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 gu>E6E-p46Bu7y$mWEa0es4#Z _vendor/python.inv +$DOWNLOAD https://docs.scipy.org/doc/scipy/reference/objects.inv > _vendor/scipy.inv +$DOWNLOAD https://flintlib.org/doc/objects.inv > _vendor/flint.inv diff --git a/src/sage_docbuild/conf.py b/src/sage_docbuild/conf.py index 695b24b1bc0..0bc0e7ea257 100644 --- a/src/sage_docbuild/conf.py +++ b/src/sage_docbuild/conf.py @@ -258,39 +258,55 @@ def sphinx_plot(graphics, **kwds): # include the todos todo_include_todos = True -# Cross-links to other project's online documentation. -python_version = sys.version_info.major +# +# intersphinx: Cross-links to other projects' online or installed documentation. +# +SAGE_DOC_REMOTE_INVENTORIES = os.environ.get('SAGE_DOC_REMOTE_INVENTORIES', 'no') == 'yes' +_vendored_inventories_dir = os.path.join(SAGE_DOC_SRC, "common", "_vendor") + +_intersphinx_targets = { + 'python': ['https://docs.python.org/'], + 'pplpy': [PPLPY_DOCS, 'https://www.sagemath.org/pplpy/'], + 'scipy': ['https://docs.scipy.org/doc/scipy/reference/'], + 'flint': ['https://flintlib.org/doc/'], +} def set_intersphinx_mappings(app, config): """ Add precompiled inventory (the objects.inv) """ + app.config.intersphinx_mapping = {} + refpath = os.path.join(SAGE_DOC, "html", "en", "reference") invpath = os.path.join(SAGE_DOC, "inventory", "en", "reference") if app.config.multidoc_first_pass == 1 or \ not (os.path.exists(refpath) and os.path.exists(invpath)): - app.config.intersphinx_mapping = {} return - inventories_dir = os.path.join(SAGE_DOC_SRC, "common", "_vendor") - python_inventory_file = os.path.join(inventories_dir, "python.inv") - # If connected to the internet, the inventory file will be downloaded for - # projects that have `None` as first argument to the second inventory tuple - # item. To avoid docbuild failures when building Sage without internet - # connection, we use the local python inventory file as a fallback for other - # projects. Cross-references will not be resolved in that case, but the - # docbuild will still succeed. - dummy_inventory_file = python_inventory_file - app.config.intersphinx_mapping = { - 'python': ('https://docs.python.org/', python_inventory_file), - 'scipy': ('https://docs.scipy.org/doc/scipy/reference/', (None, dummy_inventory_file)), - } - if PPLPY_DOCS and os.path.exists(os.path.join(PPLPY_DOCS, 'objects.inv')): - app.config.intersphinx_mapping['pplpy'] = (PPLPY_DOCS, None) - else: - app.config.intersphinx_mapping['pplpy'] = ('https://www.sagemath.org/pplpy/', - (None, dummy_inventory_file)) + python_inventory_file = os.path.join(_vendored_inventories_dir, "python.inv") + for key, targets in _intersphinx_targets.items(): + inventories = [] + link_target = None + for target in targets: + if target and not target.startswith('http') and os.path.exists(target): + if not link_target: + link_target = target + if os.path.exists(os.path.join(target, 'objects.inv')): + inventories.append(target) + break + else: + if SAGE_DOC_REMOTE_INVENTORIES: + inventories.append(None) # Try downloading from link_target + vendored_inventory = os.path.join(_vendored_inventories_dir, key + '.inv') + if os.path.exists(vendored_inventory): + inventories.append(vendored_inventory) + else: + # To avoid docbuild failures when building Sage without internet + # connection, we use the local python inventory file as a fallback for other + # projects. Cross-references will not be resolved in that case, but the + # docbuild will still succeed. + inventories.append(python_inventory_file) # Add master intersphinx mapping dst = os.path.join(invpath, 'objects.inv') From 3156b958eaa75b918c38c5d1490f8265337d1470 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 9 Mar 2024 17:21:51 -0800 Subject: [PATCH 207/518] src/sage/stats/basic_stats.py: Update intersphinx reference to scipy --- src/sage/stats/basic_stats.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/stats/basic_stats.py b/src/sage/stats/basic_stats.py index 96684023b71..83dc50b4e89 100644 --- a/src/sage/stats/basic_stats.py +++ b/src/sage/stats/basic_stats.py @@ -108,7 +108,7 @@ def mode(v): in `v`, then the mode is the list of elements of `v` that occur `n` times. The list is sorted if possible. - This function is deprecated. Use :func:`scipy.stats.mode` or + This function is deprecated. Use :func:`scipy:scipy.stats.mode` or :func:`statistics.mode` instead. .. NOTE:: From fb7fe0d1b4be509d1067c6ea2d64b01bf9b8ee0f Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 9 Mar 2024 17:35:31 -0800 Subject: [PATCH 208/518] src/sage_docbuild/conf.py: Actually set the intersphinx_mapping --- src/sage_docbuild/conf.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/sage_docbuild/conf.py b/src/sage_docbuild/conf.py index 0bc0e7ea257..49db3df741f 100644 --- a/src/sage_docbuild/conf.py +++ b/src/sage_docbuild/conf.py @@ -307,6 +307,8 @@ def set_intersphinx_mappings(app, config): # projects. Cross-references will not be resolved in that case, but the # docbuild will still succeed. inventories.append(python_inventory_file) + assert link_target + app.config.intersphinx_mapping[key] = (link_target, tuple(inventories)) # Add master intersphinx mapping dst = os.path.join(invpath, 'objects.inv') From b3b47e250ffa1a9da92538c6726a508026a5f1b1 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 9 Mar 2024 17:36:40 -0800 Subject: [PATCH 209/518] src/doc/common/update-vendored-inventories.sh: Add fpylll.inv --- src/doc/common/_vendor/fpylll.inv | Bin 0 -> 1603 bytes src/doc/common/update-vendored-inventories.sh | 1 + 2 files changed, 1 insertion(+) create mode 100644 src/doc/common/_vendor/fpylll.inv diff --git a/src/doc/common/_vendor/fpylll.inv b/src/doc/common/_vendor/fpylll.inv new file mode 100644 index 0000000000000000000000000000000000000000..aca64880a236305330b3341dac5e284e4be39713 GIT binary patch literal 1603 zcmV-J2E6$rAX9K?X>NERX>N99Zgg*Qc_4OWa&u{KZXhxWBOp+6Z)#;@bUGkraCvNO zYziYFR%LQ?X>V>iATTa9E;3|gb_ydPRA^-&a%F8{X>Md?av*PJAarPHb0B7EY-J#6 zb0A}HZE$jBb8}^6Aa!$TZf78RY-wUH3V7PJn#*$PI1q;Sc?ws}Y@trJXP1kUqKd>6 zkU29em9QJ5IkM!@g-f1(BpZSagjlk&AQfN!Pc5mt)v~=Tca+lMvb2AJ=>h~+gtMsJ z{VaH^)f^7$4wXn*2iX-;=~$ZLD@4=vp(p z_ib(X?5%wBR}!|Kd#e#5j3|U0QeQ*8zzaMazpBZ-XO0^Kc6q1ye;E%}#F8tM$=O_c^4=8jWErM<7 zuNOU}JzM5n^&2-yNd~Co%D4dWbtA&+`A5L;f`Wf^Sx~Q_FzF101Z0+LCYN0FH>ohu z?w@EFG=r)vqQ%rd`jeAe?1|rv7^4-Mse{SP(DK0&>FqO)&&uSueeDsML%q zXL--Qc&-|9gUcR^j^cgSi*RA3%ghGGp$>MoEIw*zhca8w_@e9*e}=NiTID&^&_N3YI!X3ZfAijc^j1AoSop-sePNq5I3 zDHJxPfJG=+RZT7lg`hw)j>6`NpwLFV1VHA%kST#o^`my-n5L-2TgTe&Dl)W*ewEgB zq(o4Z;Y>8rU?@!$u|Y^-sX{?n%Bc)Ee(bQa2jZE0lks5LrlyhiV6iMfhNN-j_B^)( zt=`jwbeSeskbD&y{0+7SHB;K>4g07G#S6X0$SQe_a|yr zlJqwPbhf=(YL<3-p=N&Wn>+g(kH_7ebT9CG*K;C9kMVeZ*AEf<0^+;r_#ZTlAN}N0 z$aH#m+w3l%jpFBc?5F)Jp57<3*iZaRm~A2m_X*z%U0*z%%HMj*-Crb~A`nXLOBI)d zcaCt*nWqS8sva%@^v_(|2TKKo#!U z7wAn`5{4=IZCt7^sd|PFSeVVLaDCo-24l9;lRY8Tni|4ehqiX`u*QU$!8Ak7Wi7V9 zw)vgan`4f4Tg~@E_auKJbWQT7^0%IH_auLc@JJKq2J{a#_S9|N~(G-%< zaos(UI}h6AV64N-ql-c}SFR8r$zkSL~0I7 z!50*YOJ-fVuQu-4UWp#6E5^j?%GdZaLqBbxWiG8oVvrGDt}s*PXaN6dTz@Hi!R%}I z*XGX(6Ux!?b7RFL9M!U@A&U;O?xvh|@k?W>uI6B7DuZpCaX4EWJ+lAa?QK1Sbt?` zqqQ&0nC#3F!P?eHGqUNbEv(P&r@6j9!8zO<+HRWOvL~AFkJ`6>YW&CT{{y>aT&1eX B8|MH3 literal 0 HcmV?d00001 diff --git a/src/doc/common/update-vendored-inventories.sh b/src/doc/common/update-vendored-inventories.sh index 7aa7d671530..76cb3fcc6e0 100755 --- a/src/doc/common/update-vendored-inventories.sh +++ b/src/doc/common/update-vendored-inventories.sh @@ -26,3 +26,4 @@ rm -f python.inv python2.inv python3.inv $DOWNLOAD https://docs.python.org/3/objects.inv > _vendor/python.inv $DOWNLOAD https://docs.scipy.org/doc/scipy/reference/objects.inv > _vendor/scipy.inv $DOWNLOAD https://flintlib.org/doc/objects.inv > _vendor/flint.inv +$DOWNLOAD https://fpylll.readthedocs.io/en/latest/objects.inv > _vendor/fpylll.inv From 1de4c583d0e40642c31cd00cb7892d179be0b151 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 9 Mar 2024 18:58:37 -0800 Subject: [PATCH 210/518] src/sage_docbuild/conf.py: Fix, refactor --- src/sage_docbuild/conf.py | 66 ++++++++++++++++++++++++--------------- 1 file changed, 40 insertions(+), 26 deletions(-) diff --git a/src/sage_docbuild/conf.py b/src/sage_docbuild/conf.py index 49db3df741f..18f90960b24 100644 --- a/src/sage_docbuild/conf.py +++ b/src/sage_docbuild/conf.py @@ -265,13 +265,50 @@ def sphinx_plot(graphics, **kwds): _vendored_inventories_dir = os.path.join(SAGE_DOC_SRC, "common", "_vendor") + _intersphinx_targets = { 'python': ['https://docs.python.org/'], 'pplpy': [PPLPY_DOCS, 'https://www.sagemath.org/pplpy/'], - 'scipy': ['https://docs.scipy.org/doc/scipy/reference/'], + 'scipy': ['https://docs.scipy.org/doc/scipy/'], 'flint': ['https://flintlib.org/doc/'], } + +def _intersphinx_mapping(key): + inventories = [] + link_target = None + for target in _intersphinx_targets[key]: + if not target: + pass + elif target.startswith('http'): + if not link_target: + link_target = target + if SAGE_DOC_REMOTE_INVENTORIES: + inventories.append(None) # Try downloading inventory from link_target + elif os.path.exists(target): + if not link_target: + link_target = target + inventory = os.path.join(target, 'objects.inv') + if os.path.exists(inventory): + inventories.append(inventory) + break + else: + vendored_inventory = os.path.join(_vendored_inventories_dir, key + '.inv') + if os.path.exists(vendored_inventory): + inventories.append(vendored_inventory) + else: + # To avoid docbuild failures when building Sage without internet + # connection, we use the local python inventory file as a fallback for other + # projects. Cross-references will not be resolved in that case, but the + # docbuild will still succeed. + python_inventory_file = os.path.join(_vendored_inventories_dir, "python.inv") + inventories.append(python_inventory_file) + assert link_target + if len(inventories) == 1: + return link_target, inventories[0] + return link_target, tuple(inventories) + + def set_intersphinx_mappings(app, config): """ Add precompiled inventory (the objects.inv) @@ -284,31 +321,8 @@ def set_intersphinx_mappings(app, config): not (os.path.exists(refpath) and os.path.exists(invpath)): return - python_inventory_file = os.path.join(_vendored_inventories_dir, "python.inv") - for key, targets in _intersphinx_targets.items(): - inventories = [] - link_target = None - for target in targets: - if target and not target.startswith('http') and os.path.exists(target): - if not link_target: - link_target = target - if os.path.exists(os.path.join(target, 'objects.inv')): - inventories.append(target) - break - else: - if SAGE_DOC_REMOTE_INVENTORIES: - inventories.append(None) # Try downloading from link_target - vendored_inventory = os.path.join(_vendored_inventories_dir, key + '.inv') - if os.path.exists(vendored_inventory): - inventories.append(vendored_inventory) - else: - # To avoid docbuild failures when building Sage without internet - # connection, we use the local python inventory file as a fallback for other - # projects. Cross-references will not be resolved in that case, but the - # docbuild will still succeed. - inventories.append(python_inventory_file) - assert link_target - app.config.intersphinx_mapping[key] = (link_target, tuple(inventories)) + app.config.intersphinx_mapping = {key: _intersphinx_mapping(key) + for key in _intersphinx_targets} # Add master intersphinx mapping dst = os.path.join(invpath, 'objects.inv') From 1abd2f546855cfb3d09eb338a0a8c45ffb6a429b Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 9 Mar 2024 19:49:11 -0800 Subject: [PATCH 211/518] src/sage/libs/flint/arith_sage.pyx: Add intersphinx reference to FLINT function --- src/sage/libs/flint/arith_sage.pyx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/sage/libs/flint/arith_sage.pyx b/src/sage/libs/flint/arith_sage.pyx index dcd22bbdf9a..83972c11934 100644 --- a/src/sage/libs/flint/arith_sage.pyx +++ b/src/sage/libs/flint/arith_sage.pyx @@ -31,6 +31,10 @@ def bell_number(unsigned long n): See :wikipedia:`Bell_number`. + ALGORITHM: + + Uses :c:function:`arith_bell_number`. + EXAMPLES:: sage: from sage.libs.flint.arith_sage import bell_number From 1bee07c74c7c73605135dd7451949d1f4d4222a8 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 9 Mar 2024 20:17:48 -0800 Subject: [PATCH 212/518] sage_docbuild.vendor: New, replaces src/doc/common/update-vendored-inventories.sh --- src/doc/common/update-vendored-inventories.sh | 29 ------------------ src/sage_docbuild/vendor.py | 30 +++++++++++++++++++ 2 files changed, 30 insertions(+), 29 deletions(-) delete mode 100755 src/doc/common/update-vendored-inventories.sh create mode 100644 src/sage_docbuild/vendor.py diff --git a/src/doc/common/update-vendored-inventories.sh b/src/doc/common/update-vendored-inventories.sh deleted file mode 100755 index 76cb3fcc6e0..00000000000 --- a/src/doc/common/update-vendored-inventories.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env bash -# The file python3.inv contains the database of Sphinx hyperlink targets used by -# the intersphinx extension. See -# -# http://sphinx-doc.org/ext/intersphinx.html -# -# To be able to compile Sage without accessing the net, we use a local copy of -# this database. Here is how to update it by downloading the file -# for the latest stable Python version. -# -# Likewise for other intersphinx targets. - -set -x - -if command -v wget > /dev/null 2>&1 ; then - DOWNLOAD="wget -O -" -elif command -v curl > /dev/null 2>&1 ; then - # On OS X, curl is installed by default, but not wget. - DOWNLOAD=curl -else - echo "Error: neither wget nor curl is installed." - return 1 -fi - -rm -f python.inv python2.inv python3.inv -$DOWNLOAD https://docs.python.org/3/objects.inv > _vendor/python.inv -$DOWNLOAD https://docs.scipy.org/doc/scipy/reference/objects.inv > _vendor/scipy.inv -$DOWNLOAD https://flintlib.org/doc/objects.inv > _vendor/flint.inv -$DOWNLOAD https://fpylll.readthedocs.io/en/latest/objects.inv > _vendor/fpylll.inv diff --git a/src/sage_docbuild/vendor.py b/src/sage_docbuild/vendor.py new file mode 100644 index 00000000000..cc2825062f5 --- /dev/null +++ b/src/sage_docbuild/vendor.py @@ -0,0 +1,30 @@ +import os +import sys + +import requests + +from .conf import _vendored_inventories_dir, _intersphinx_targets + + +if __name__ == '__main__': + if not _vendored_inventories_dir: + print('Error: sage_docbuild.vendor needs to be able to write to SAGE_SRC', file=sys.stderr) + sys.exit(1) + errors = 0 + for key, targets in _intersphinx_targets.items(): + for target in targets: + if target.startswith('http'): + inv_url = target + 'objects.inv' + fname = os.path.join(_vendored_inventories_dir, key + '.inv') + print(f'Requesting {inv_url}', flush=True) + try: + r = requests.get(inv_url) + with open(fname, 'wb') as fd: + fd.write(r.content) + except Exception as e: + print(f'Error: {e}') + errors += 1 + else: + print(f'Updated {fname}') + break + sys.exit(errors) From 700e40f486dda7dc04e1842b3277ea14215e8078 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 9 Mar 2024 20:23:47 -0800 Subject: [PATCH 213/518] src/sage_docbuild/conf.py: Add more intersphinx items --- src/doc/common/_vendor/cypari2.inv | Bin 0 -> 10728 bytes src/doc/common/_vendor/cysignals.inv | Bin 0 -> 775 bytes src/doc/common/_vendor/matplotlib.inv | Bin 0 -> 105887 bytes src/doc/common/_vendor/mpmath.inv | Bin 0 -> 3115 bytes src/doc/common/_vendor/numpy.inv | Bin 0 -> 78006 bytes src/doc/common/_vendor/pplpy.inv | Bin 0 -> 1449 bytes src/doc/common/_vendor/sympy.inv | Bin 0 -> 55596 bytes src/sage_docbuild/conf.py | 15 +++++++++++---- 8 files changed, 11 insertions(+), 4 deletions(-) create mode 100644 src/doc/common/_vendor/cypari2.inv create mode 100644 src/doc/common/_vendor/cysignals.inv create mode 100644 src/doc/common/_vendor/matplotlib.inv create mode 100644 src/doc/common/_vendor/mpmath.inv create mode 100644 src/doc/common/_vendor/numpy.inv create mode 100644 src/doc/common/_vendor/pplpy.inv create mode 100644 src/doc/common/_vendor/sympy.inv diff --git a/src/doc/common/_vendor/cypari2.inv b/src/doc/common/_vendor/cypari2.inv new file mode 100644 index 0000000000000000000000000000000000000000..8f4ad4acb9878497812ef1fe140818b5f7d9aa8f GIT binary patch literal 10728 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 FsIax5^bG(2 literal 0 HcmV?d00001 diff --git a/src/doc/common/_vendor/numpy.inv b/src/doc/common/_vendor/numpy.inv new file mode 100644 index 0000000000000000000000000000000000000000..e5909cfe1909fff72939de5123090ec1e338477f GIT binary patch literal 78006 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 D+2_Ry literal 0 HcmV?d00001 diff --git a/src/doc/common/_vendor/sympy.inv b/src/doc/common/_vendor/sympy.inv new file mode 100644 index 0000000000000000000000000000000000000000..e77acdcb0957bd1f4090f85b706b9d38706370e7 GIT binary patch literal 55596 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 literal 0 HcmV?d00001 diff --git a/src/sage_docbuild/conf.py b/src/sage_docbuild/conf.py index 18f90960b24..a68dfe2cb7f 100644 --- a/src/sage_docbuild/conf.py +++ b/src/sage_docbuild/conf.py @@ -267,10 +267,17 @@ def sphinx_plot(graphics, **kwds): _intersphinx_targets = { - 'python': ['https://docs.python.org/'], - 'pplpy': [PPLPY_DOCS, 'https://www.sagemath.org/pplpy/'], - 'scipy': ['https://docs.scipy.org/doc/scipy/'], - 'flint': ['https://flintlib.org/doc/'], + 'cypari2': ['https://cypari2.readthedocs.io/en/latest/'], + 'cysignals': ['https://cysignals.readthedocs.io/en/latest/'], + 'flint': ['https://flintlib.org/doc/'], + 'fpylll': ['https://fpylll.readthedocs.io/en/latest/'], + 'matplotlib': ['https://matplotlib.org/stable/'], + 'mpmath': ['https://mpmath.org/doc/current/'], + 'numpy': ['https://numpy.org/doc/stable/'], + 'pplpy': [PPLPY_DOCS, 'https://www.sagemath.org/pplpy/'], + 'python': ['https://docs.python.org/'], + 'scipy': ['https://docs.scipy.org/doc/scipy/'], + 'sympy': ['https://docs.sympy.org/latest/'], } From b880a483a1c1ff840def4de443fa91fe93d0c028 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 9 Mar 2024 21:28:23 -0800 Subject: [PATCH 214/518] src/sage_docbuild/conf.py: Add some more intersphinx items --- src/doc/common/_vendor/cvxopt.inv | Bin 0 -> 1942 bytes src/doc/common/_vendor/cvxpy.inv | Bin 0 -> 13251 bytes src/doc/common/_vendor/gmpy2.inv | Bin 0 -> 2891 bytes src/doc/common/_vendor/ipywidgets.inv | Bin 0 -> 10934 bytes src/doc/common/_vendor/networkx.inv | Bin 0 -> 51830 bytes src/doc/common/_vendor/rpy2.inv | Bin 0 -> 3289 bytes src/sage_docbuild/conf.py | 7 +++++++ src/sage_docbuild/vendor.py | 2 ++ 8 files changed, 9 insertions(+) create mode 100644 src/doc/common/_vendor/cvxopt.inv create mode 100644 src/doc/common/_vendor/cvxpy.inv create mode 100644 src/doc/common/_vendor/gmpy2.inv create mode 100644 src/doc/common/_vendor/ipywidgets.inv create mode 100644 src/doc/common/_vendor/networkx.inv create mode 100644 src/doc/common/_vendor/rpy2.inv diff --git a/src/doc/common/_vendor/cvxopt.inv b/src/doc/common/_vendor/cvxopt.inv new file mode 100644 index 0000000000000000000000000000000000000000..18438ed538186aa4ca4cb5645347f060af7c9462 GIT binary patch literal 1942 zcmV;H2Wj{tAX9K?X>NERX>N99Zgg*Qc_4OWa&u{KZXhxWBOp+6Z)#;@bUGkIR#;C^ zR3KGzWpXETAV+m+WMv8?AXa5^b7^mGIv_DFGcGa;BOp|0Wgv28ZDDC{WMy(7Z)PBL zXlZjGW@&6?AZc?TV{dJ6a%FRKWn>_Ab7^j8AbMA-Onjl6>p`A z$2;#P@~7m?c$CPQ$}R;F5QP{7AOO@zUga9OUQXf$e6oE-m$fOq5a(-XpFHDkWS%-T#dEcNNfNj?2IDO_rdYoQZmLJpfY z*X5#0+A!$_9%}2*6~&0cc2#n*SSR}QmN@vnDI5c0?te>}t+?KjjO4(qrwS8Xv-u_+Pp;Af)a z!6!t#0gPDl$zZUzDDYPdIv(sWL2(<402_p#0gO?$80EM%r#mH}*~}>?S#-B(NYJ55 z2%0F1oI&eC^IkTa)h}LXjllCx`9=whg=+f9Q$r~~#6sCo$3nMT_dKU*zx9^G*RV1k zymc5XLfASCHeQ%xe8Ex9;23Xklsh=aA3W9}8FQ8~8kQhD{NN^^`-~?XXtt7(1W| z6wNSb#u3TLux3~+0UL}RIth2^Bvd97dn{vbkc=EXM~<9BW1E0RHvx@q0$O1grO=B~ z*c|Z;q#4+{qtSIoqw9`6A?uDOP&C7!8Al`|!GZeXsgKZB#bO+T&S$#NO{BKE4U;k|4&^u1dl`4)Ejq&v1o zqw0ugL_|HoAIm~yW4oKSjx*cdMDqEZ6pj^eg;H; zQyY@Hcen4jO$`GFeet>AOPmExbvg}M;fd9oa(}nFTl^Xr(_}?R?dQrnCZWd>Nt1Qe z?jCA@SE$3(Q|QI{q5cj8ZJ?-J5Sr1JP<6f!{>0{7#jI<6NbEI$&q~5Pj2Q3Q13M;< z+VPx!BYx+34Lz*VZzQ#$_m$Rrs99dX-UMN7@~RuufsiVFGCbS4kW&vfqz2=?d=I__ zkKeSZP?Oq37)*nVNiHdv99Y(jR=lPEcyor;!X3oo>Cea2m*fx2%zyv^DEKu1cha_&?Ii7R$_5ErlY(>|N!B1$8*VpI&uI$71HRO! zS@T1SFQ95yUJVkvmJiZ;AgMcw6H~2DQ1WFsqn$zVbUWw;5|FQ;OnvqHt^di_-z1xGX;Os!0U&s6FYOIEH94)@7R`0?glogoz?%WMr-eV zTPNE6dZy0c^aEKsuJHb-h2mw**Co6#tX9}hO#WLzs1HLrG=F?1dPyG z>GaD(3h0#GGIAI8TdSSb|I{ZUU->``mF)8$fzfn-YG5}O6w+O9!g8$YnCt5I70Dns cJBPlyHFsAwacm3^jSWAr?dpN?JNceURhOEby8r+H literal 0 HcmV?d00001 diff --git a/src/doc/common/_vendor/cvxpy.inv b/src/doc/common/_vendor/cvxpy.inv new file mode 100644 index 0000000000000000000000000000000000000000..33eabf78f40c3f600c86b36dd8896fc0a9c727e3 GIT binary patch literal 13251 zcmV;!Gd#>AAX9K?X>NERX>N99Zgg*Qc_4OWa&u{KZXhxWBOp+6Z)#;@bUGkIR#;G3 z3L_v^WpZ_Ab7^j8AbM}c>JOal6_xh>=9*>jgTyW>OlxOwY8gmL&g zMd@AS7X^s=S?J|-Pjd=#FZg{~p58<>F8bs*Vc`Y43}Iqg-loA4oWlZ~Fj@9ILVoe{ z=r{|W=bB%i`#3^;(ljaW!-94&*bV-EA0~9; z2Z2W)vV8M02wuVev?$QHvEmRAL-03Y;^&VZmKt2oi_K+td{NnMl7_}`XVL}JL^N;I;9(kv_ z9%hst*uPU0g2X;aVQAoM7D_;O`D$QcvJ4D{@YsJ4|Jk+1w7i8LW&krt%OcH7`Tt6& zy4?)McGKV{lPvDqV(+Jb|8w?d83{ueV0p;!-$D)m=%=`?ZfMT`E~DWP{Y2j)F9?s~ zzhzVcqn`_!CH`B+f&haQXBq#ynC2`SCi0Rrk9Wad|Gbz~0S!_gd}#UV!(vj7GRPPf zWw=ux7Bg~`e#RIV*I}+B{$Iu?LHg;k^d6cU`@4*aL-Z5nH1b#{B|j{qQz-q657=8h zEbguz4E@z3_2Mw8a6CIUzpXWTG#J3($GLG*S{uou1P7XWG4&JmVI{>oxEntM)AYfk zd7iR&?w4Tg-&8QKT$$*8P4w6=<0_K>uT(ggWM^TGUUR#k62GD?(^8dA73ZS9B z*pq6XS3-xjm4^@BKKFO}=k*E&E9!bR6szW{butbgXu!-K?c;jz4ERKl=j&pg@xLp* zD8zzsbH6g20hdeyHtJ~LN~#_E@UGge^ayDp&50~6PJy`z!u?)?uTw9Y_&rZB{{=Jw zo75T2i;Qmt>z`LDxNPsy@IVu`8QA=`76WUi*Rt)ehM29}n=mV7T?-{EGyJz6;9cbh z-X0%beOehj#f|R>#+JsnmEKzsf(6SAHuY&OWZbIXOfp@Eme@I5QLb1QIONgKSA^8e z$Cll=Mv>~);%Ur8h6)u8W|aEpH3_l|kv3cS@EShYZ<~x_mucxo9`k)bMK|SfJ-y4E z&9wP{>%kt=#7k%u`$@uAKiU_T4P_lP$$kU&^ul)8bQhQqAR@z1Mx%I~7Xal*IKIeo!BU9p|C(4cP)OX?N0+`MKVw{)$p*95(Rq2Ny0 z$s=9~i+EOj9YzY}qjbW@D8vDX6H=_+g(Yc%bMkj-ay`DBFb8uJRLs(|^Ol{(ux|Bf zVy|3pTHvczm?rkB9Zw5-?MldZF0a#j>^6VAT&WtiP*<)dZS+;ky90jG5`TA+5`TBn z5`TBf5`TA!5-)($A=4fk|us~h6WF_d@C0bDU7ILm(+V$~wnx~uphCeG+M1{BVORRBMEnp3HrIKwOLz%H| z)jDJ{7s0xfT0r5!s-;jt$Pc$HSQ5ldzJ(CYHP4v`&#YY#hB3rBilRP`%$MH$@wG`^ zdJ^#FqlN80A3X`e_@o53Q=0`0cJrJ01h&)4$qYL`ityOI#}5B)F2vVs{I+_98X`MQ zB^zWsRmB@51u{IbJvw5Xgw^xGGJqEv9+D?k;ACxZnDs>Ma**_NZB_OL-5#G`6&QHLksc?yy&y+1uk z{v-`Df07oNKUFo$pQ@haPs5TwRk=MGPj&MIEM;ENC#l#^@;FcQG`lK4`el`$+EB8O zQL%`{GIqrx4vdaq7s+8Mjcrhe6?oK)_0&W*fh3mF*z8g)<~Te7NA?69cTZ4Q6Coh$iV%h1m9KWCZ zIaXf#iKxHd{3gO|udv>u@YY=8sX^DRjy3Me>!BUs6RdsK94ogU4S3c1T;r}>hh>D< z%_s3;rMld~U9(Pi!Po4}4d|=3oG$*#EvT2^1TCq{wQ`Fx5Ukp=y7()!uv?fo?Y=E^ zVQIxCo9Tbjj5Rx53l`mF!g>1vRoWrY=ECI5Vv;rjq|MZ!75{8LN;j<8e5_WeviU%* z2xK#X>X1eY8bK#K*<58X_eK@3QH{GXw5LCon5ivQC9T9x{(=(D{2UL5c`AOO)YsZ@8)9a8WdqU6gk7&{7L70OPxw)+T$RhWlhQyqt z7m>|)#s#F?2xN<=c|guJy#m=#9v?hx`RCj;+FTqTUgFmaWht%NPzTvg*FZ;TPt!1m zNl(-ugI&gI5CcwwDx&TWW57~|!*VGyh|X%NC!*RGWrel%@Es`eBm%t*<0S<{@=dy^I!jX`F;7f-8tx2)-M4 zk?y`5xAc#BYVx=2+EyEAp0cxVvTrV~-fcgAc=_ht$L;qoi#ayaNz|BXL!Cm2={D6V zv|zu{z-uI2elmsFnKRVtI!~Xpr8J0nec*>)y}$FU2j8a%bWQ9sR-aClu<&>)gfJ=T zf#%;)l_HD6(({AAr(sflcRh+Us;r`;%v9uwW<~15C48_6KD+R76wj$g0R!PF`};24 z&QCCz5L@T*WW1Q~Kwai^_3r$Tox^SF|4aD)a+j^W^wL9fvNe}pzLPAr^sz0fH;b50 zw7x@+e#8f}V)F_t z-rX?iN#5Wf<%!aG|--VWMh3X?~?WWMKx}XRsD4ydCB7D+8WeH-oB#}(-%6|)?oT_ z=h_;~SoB;;1!kIH&V-3;Ij5jsvZ8ZpeI~E#oJ64|t2-xAX!aV<3ALHC(zB8}@3Yz4 z2q)6u>8u(HRi4JU8CPVw0e>n@7A@pkDy2SdKFCk2_&XYKH=c({Q!Jvm%w|~Cv(iYg zo`ySRkbO1!3s2Sv&2zPMrXeH8QEYM82#142sMFV35kLTyYOMkHCT0MX~ zg$qL=JOA0aunrAxEr^fj*fGnqSgKnSS|k!*6SBl)+GW9lVZd0P=3yDqVr`at8Xj&- zn6rqcvI2HH<@GAHy@Lvq6AYDaF*zB*m*>CdHgM2~<{peIOF)ivx3Ht?&!jywWA|~5 z;2;mna?>dDL{p)xpnHOD;)Nd^eLx9bK&M3?YN4}@3oV^s{ERw4Xi)|~NB#|suxq3b zKAv|9Jo(_(<GSE7PxU50IC@uXP?-gqDAo{Pj9NC^uGBK zyd=&N5H5Xo>AuAe&bdkVyaFs*E`J6hkb2#0#v z!ggd=+9Vn2XC!mqnq*LRD!IvgUHuUqt(I+V~r@eut-!g)xU62?P(qIh0TR?YDf z*T-%@@!2Z>4quhaJbfBU!*#yi0N9O+m=H~zFmb}fU-kh5Wl597cl|u~AKe1FwcS89 zYLpU)dQ8lV!^FFxetaJW<*n3(;SzQOsc~PL`5dSSE_A^a8<$I~&W`hBA!*9Jynf?e z>Q@|tFhj?#e*oJKX;atg0leB2mKArT;@bggjil8sYz8hnj`m3B_Mt&{@XbF^$7)(& z$K9SsxL0^#;2E{7rDF{31gGvreZd)tcsPdL4vy&7Bd{5}VHFPmH^QE{*V6nQ9=FFp zTg#KK+Q%6KRtCWYhK3E{{sP~~D*&+PRA(scIUC;Z?!Z~~ff{Ud)SRG0%e&Kk%f3&1 z_J;STJ$Gjh*d6gJ9Vmx-*<(zB)&hz9QGIu&8FJ72jJBev;>-Xt$&AHVOD}6A2YVY^ zBSoqQUzi+@s$xl@&w^CHH%4wIOKRDd9PG8c;23Nti;3-X*;UF7iehwrg(C812TO`z zJ#Td;^GaHx5e?1M0oU(gq-|34#4gw>i#wwJko9K=idrD>kHIT63z`w z+UHHzh;x%u{~MZ(yIVgwSiCYRK%~GgEd=MLI_$#-tGEA)xD{l=C;*URp2dtv0>F{U zN12n{c(E8l3petW0$$>V@FKvD5;#I3qZ(lL@(m+*2K(U(3_ZGhtr~vT`|^ zKUWJ7Sm@0o{5i^H8+B0YxuQoLY@H|D; zSkX~e<%$kgpM-#bSac>#0jN?!0dh+iF+7h^_1AAc^r`-5sWUuwnig0VzAGFgCNyv% zi_0in_M(mEKqc=iz4L3WCd=;=^^AWpRNJm7zandF7lq%e8^0ZlmKg4d?v=R<7O=}V zO6S>%O*mZNAskLKA1}r!+iKy;ls1`PLp59!UWPc!a?Y9>xcQk-N>Vr6>o6 z_7U)*?qN{f0X2lukL`69OU<6!7fu==@?%8p>{Gm-@cS>6D4;~}AebhBS{Zuf(F>Cd z7fH}FSpWkB5fB#)?+yFnh%k^Y%PA4K?FAIePJx3E4$?H(dhs1S0_CJ3uX_qpk6+1f zp+{olD%;uYc@nnZ-g*il$G1xvYy*j5v)6{b9k->_F$FfP;~pz~;0`k`{Jq17lgxi( z?mnR6#6Fjsdh!NtZU85K;GCqvc`;vK2h-3+Rbjc*yrW5x!b@Tfe)W5i5Ds6^7Cw8n z%>h@(rO1U2fN+1GJA1=rMbNe?Y_?^Z4=E+lQ%NbwFEw{xE{q(GN?AE;h9!iZdR=rJ zr@e+Mao8oNy%=j@UOjKD!s-Cn*BTZ<4CWrjwZ1S(I-)LkQ07p4*8L{CPNdLp{C@?O zr+yIo*)%XI^f@eUqp&DzvAC-#(;$kj|0xP$@cz^YM@ibH-Xt5;y4!SJmcYOvCla->$(qL8En$tWugo17pHq$ z!?HAk^g*yWC*6I+8a3%5D>0XpvKK~3WI8g}so8yFPF?1+HPP`v=$ca^r7tunXII7O zDQT&d**UF>(N%HogP9$|daVJd1j^T|~AqxxdnOrGtI#Wi3*HYTAGcWe_|XjWI?FP&||N!7m@fQxHa z|4ii1E&YZzDF-zC!Xn;%5hqK()R8nq7>)Ugi)b1X19DHios$edpel~jKa!%dxTRlp zvBCPKvBNq^?e0n5PBjv@uol-}m5h76V=8~qBf@;aKtMA}zBfF}qzFWlZnk_Q_;sBI zGy56Go6VSjcvf}_?m!co54r@>JUlXgu;K-bzra!93>zkfjeR@>BO2Ic6WoESZ)dVR zcP~TSQo_}M04+>(6U2xNxgW=9$0Cy1JG>7PzSb_`mlv8pc*99#-flJMD?%?ibx{%f z-DQxkOIU-Lw)3>bY-=EfTRVQ@M~_9wXS7gkK}d0ytV8QA02c4v;ALO`N*Z97JuE<9 z`eDQtDsy^QvF!k?pkOFsolN&?QK)ftc>aqKgt%%e2}w5zYc=WW~!$4>7D@|jTx~|zD0GMILrw= zy;fJzy4TWAvDA-XTZsCTPV1Yr;yM|2D8mBb!vBr;8%F^GqBLc zG}7rR>2SSTUF)2Eis`2$EK2FF|6S=PA^KF(&p)&^iu^F9`5fnqOC=3J$HnK1F2kdC z90(9s229HW7Z8@L#RXY5$wsoP7I*!I>fMl~+WS@!aIm*-S10%`N2vD~s^h-1Fndi% z^$_yDVfof9)=%5iX7)JJ)7l&ieB;)MXeV?E6*u05;AI1c!QyfLMJ7JSaOkdifN*pz z4h0&A5MNzfZ5u}rXFCAAGH&-TcFr)nV)T$DHmkb>Cm|64{U~Fe<=c7>6!T0AS)n!m zLK;A?4CoHV)8^r{7n&?}Fm8rf@iScL+BI}0JN9?C&Il z3Xyq`al}^!8pUl_aC^UsN)M(+wBdvMN`W{+&Q|^6sKUj);x{RTGdoj9+zxlVl7b52 zd>X{Lf;d+YWy-fV=D8yp6M?lg=KFlMfwQbl-*v zj}TJ>a?eM8T9NrP`}s{+=6?R@o|8*ygg-hKekWC2>YCH z7rD}o_hsXnb$UMc7o9NiS%qA!lc=zu>?*R~FN41qpbxTT=j?!&l>z3rQK zA6S2S{(}{Bue3Oencm~c)B;~JTNexQrGAE23SdR6d)_`4C5?MDSv0Y( z4A<%%yUtI2OHWlFeOHGIi#1A2Pl6BS9sCL&%TGLU*nmCvpU%g%vVsvq0l!rgBQ9|N z0)KcPEKkxy!LdU9RRE2Qa%c<&V1cC=m~T{9{qCkp{1=z~;#kDs8RzPT>~TM^OYUDd zLLC$aUAK?)Hr%0UlEdt!zbOg-&Rxo;NMP)Gw6Lf;MlmrcaHr1tCl`K#GGVU*HB)b* zo|Reoo?2_wa8_O9aBAVsJg!yO``P`NmU31qYABI6^l;w8Tr$TVGs3`?9;3gvD<*S?ew3))+!}xxTqX@DARQOdiQ3?}S zy2pUpv==Jf4PwF15)4eWfTVcse(uZE?=53-?aH~o# zOYon0M+^5fX|laEKKRLt{$lI7Sy)jt&(nmwh7pn`Axsa1(Nf>nqGgr^+rCVrem?y! z-CPrwnwYtteDlyRDT(*Jkorwx1cxgG+e~6@bFrmZJPF|+skevGok!2|R9HeI`-qU3 z?WiAmW>`ZJq564;LP$*v3e^Obc6CIPLwW0s4^g91+LXSE3BSW-cetJLtK)cWSmrS- z@^lu>&>uO~*8Gf+qf)ZxDH@C#`KXD^>Ll5^MbSPSh zkWPtY%a+t1GCPkZ!=ap44;gc0nzGktI3uL(^NpY7>$rdEF_7pZn@U#&SZ2rBn}vWv z7z#lY;_Usq!Po}R-PbSQJL;nT_M3N#`x*;IvsdBSMN;Fm7oxy;V{`RnR)^br zUMG61RdwLPc(4!g;NzB-0&yPT^9D{?_J>Bb^f`Pkmpv6+WBGIDi9vxy$d;b++vRwEUugI;dX6Tt)l*HuLDV^i1z%RM=T$Bs)gJQWJz zbw8K0H`^pbAuGr1oM-h9%_EY1_Rp-X)92lIOf`-lrMMuGxL_?$LahV8FbIszz#v|m zp*6~Vp2jx*KNb#dwGs&0;|=uMg4y9d!4X^w6Lblnmgnq*9MR-{?LaB?f~}moVGPtq zHcHwY2>nSx8sr6bSvLyXPR4kmTB6A&!MmAXO&pS$O-lPYEjyVStU)Qkk*pr}6pOsd z$GtcB5$;S$O#Nhm7Y{&q2;s;Nv$2#i1hDE(6Avo~ejXT3B@OzN2n^O0OALNO>DNAV z=K#|<=+zcEHA#Q%Szg)C-Cu<`;ykkCD$9a4C>R;I;a7z#f;Sv0eEuwg`5gE29rPa< z*=}cGM1e^cBllM`2`8T&d5obJ&aI!v>;T%rx-4;cg>HPCjoQ?WIJY!HpY5LE$vW~s zhtd3d{>*Xff$KIr+(ww3!zLKqpViIIZF=tpDTZCcl}3v|Vjw_*6r-)7waL#Z{+WZk z;UFS3SdAOybleFbeTXiH0#?oR3s6@b*ngzW=ZYYr~ zL}$^SK~tBXot&nw_TV{X7L(dWfj-DDq23kJ=>kMEx(nHuh|c}ErSX5KmG z+&FZLVI5NVhuEz!AekjSViAT#1Qwo8;@wV2V#2U$rOed4b9wWxfBaLD3uU2vxmx#W z{>?@sc%z&f1q3Zd4`2ig#JT19d1fBBxUFvJGS5*oN9MAlESVC&w+VpafWemdScgN) zDw79ftw!XQSJg8P#ydv@BMR(IAC1#ln$pbk&otV>+)D*eWFY_yM*V~$Lt(a~j+(%e z^dFBfPGY4ze?}qy#sCUTd+HYA z;tZT1rMcyKD<(v0p*Kws2Z!~L`=n2uN%lu^tU;R-$~R^*Py!lo)S9b^i%sM14Zo-) zxO-!r!$k)45sThqfZ`G#O0?WC9EM1kH#(b^F-F7y5=-n(gP_?O+RWO6KuxiaH1!!R zk+6_vD7!ehUUO%3pfB^{xp8sn}kLV_rXw=IYc z=Fcv3lXF{+V>V@CaPbr z*#T{vtumy3?(Y_Kfbw(`pg7v+*@(JUsN=E-vf>Pyh3ifui61L$E-4{;9Igj^I}G02 z&uTEX*Uf+CG2Ibt#JQVE+Xuuoc6C!WNINygbnMt>?kUbKKjQ%SygGpSG&6UgT9W9j z?SfS!>dDT`IscYTz6i==1#n@2o4Gp=2sm3I4B@Q?=fv_v^z=$CBx?xEL)9v>(c|JI zw33>r>mmmZ&q~WzDkU{;>=8x1;3&9oN?+D=2He(%{N+X+;-58~tO%cSYKh7T* zYn7lXqIW7gii(kyaFek0vT}<4p6JD!vRrhHcO`_0Cj7Pwh$Mz>EcDF~KL~$+9*&iJ zsfpOE=vUZv13XEPR>?0ZL4_oZac6rUvW(so%qDhLDNXELy#}11B{l(UbVxyLQe(dz zq;_C#eeSZ1 z3GC<``UNz;%|>nNMw~PQ$%vi$@mPumG=XOHc4-FFUob=fhp_Y(4CgBQKqCy@gJ%*k z5P~!2h|_Z_=SsQWq#4H90Z3$-sqAy3V%-=p^V|;v65+oPj${lmqc~M4oEsGi$8Uwg zkui0qu6U!G!APb zbt%nu7Nr;8;98%H7%>8b(jptr~|1mki1K*N=|)e$kKZ640B6MZeEYn%xw4wZC) z;^@WaVQ8mWY1IS(;Q&MjObY1Cmx2lq0H!&c0{iHulkj6@Vts9~Jua|`hD{pS*CXVp z9mWmW_s0zxSUv9zNAj!bah`Pp<1pGaKVC|lWD(9ghSQthy~ku@#()s?Tfq|VWw69uBUqw1 zvTJuzeK<)G;&_1L)O$U15$mTUP6K-xqIyz}YQKruvu)Yk zsO*NMylVtZoM~x_Yeaywf-vT`QS$}{(M~MBXA+f+uJqeS?aQe@+y4n=7aUAPac9Z_ zjGk-|ALPjj)nw-+BvP&`PM@=kaq6vkyl5FqfpU=!I+(X4(UP8ww}#P@FDZcOS>#Y( zDGWI1Tgi{Q`09|IO{5%65{y(nsjf$Bo+ya3h!UutN_1o5IIf`b!NV+iF z#jy!U7~qF4iIEsBFR@|h7GRC<%EfM>26dSICum4b^TAdjM=pUcnET1qct?Jijir^xuGIs7kn;Vx2|%XG(IsV3ggS|$Lep(qu!`poh1(OS zbuw2$Y9z_`uYdegBXAnA$EIbD*96&&7H1!Y>FNYrU7jd;3ofgA5M&0Hx&|0hU0@b5 zjID+*YQmOScaEk3)%#&v1Gswr4E5a$RGY0zsagwk5bN5hmEpdo|XGaj5B=O%)M zFmxiow;d%oyC4@z%Cnf$#{kAIPh!duz<}vAgVM7d)v(AS|8a`j+V`u&04xV9`q0B0iVKCySA7EiWar7rrE5>LQ= zl5fDU=c|`~bV%8r{o*}yt~d9FR`{gG-wyLT!V+M;)85}vn|SU&%5ZVDl%rPC{c>_0 z%?TIVY8V0#YG^Uh?lxOE?CUhBi`z6U8VmaZrTdIl<1Z85s)u9SaEGiX0HejehFsc% zU+dtDTY-#e$yB^WMHdzr&>!Z#K{t)S)YrzSYrt;uf?Anj-der8T{aevlAW_PX#&>` zrSSfuTMWBfKb$)ZqWlHQJH=Rb&O;|V=hD%H{Y2`YNX^Iw8RY|BGtS8&THNL3vLy?4 zWO*4xxy(7HFy;MQb|SR?iD;1K1cOC&!%ws>Jr@g=PC+56IUMdhSQYcOsN%Vc?(2k_ z(__r76kM(mRgPhrj_*&06sPgm2msfN{I*KSw^pn-P2**d0s|4V%+4#7biL__)5f(4%5+|M)9 zvM@xV5Q&aBlxe!m7bVgwbCE%Bfj=V?<_%7qv&Z=q)^>^)b{MK)+BvT78*VYMIi1eX zINSAcf;!HGBk|8``6z)k_dwBrEm}1mJw?M-CRcD8cj*Py6*mcI@Z?a}=Jcjan4mCmssc>pMGMw{ zMFhZsig3V+Fy1@hy@cPZ)gQiwqpj}Z)0X}>ipwym>+g1Hm6UABeT6gjd__rP!yq`w zhOv}VBG}lvf8zxHU*~D*z`(kwR;vt0a}Jb#62cA`V{?aJ5cSH`TU7A?1B+R&f80EL zA$_9fcu*+olsjN6h)-r{L8(UUp*`x(@me7(O#?3b$pGkd9D*k7r5N_f+Ri^}H|0|g zkAnx1^_47V>I~mCJuJp*lw}CKFnLw#g)4R6|t)l}$) zmiLqndR}qKYRH90F%sr zqYpdER^oqlcTm03(xte^g~`g8BU)a;RF@wjW-(Cf@#Eht9*X7q!asZXANYu(Bb<#$ zHtr3FY^pWtHJ6a4ER|I=44zWenn7~w1j+SjkH zQhUxb`$QtukKgk+K4xBfg|}*y11j>jyONd~n1!Nz)5NT6SQryw_76@>rBEbcYg^S4 zGRU(3n$lefOk9nTh?m#b0#7z2Em@pcGqNZ=A*?5kldD{xJK((6kl?@$WsGf2yhhAS z$`_O@a)&ZuY&znlp)g;`k|ig>-?YG0S=Q`q)r_ZN^lxA|G=-(ISBIhTbdU_q7?VoZ z&x#%te&of7E~{v)7OBg!e`AlH##smkMEodslE&11^tS`j0)(rl_Pvt-qLnLl@+oQDZ%5sX_~zteV_G)pg@{{}(le*wXlU~ BnYaJ| literal 0 HcmV?d00001 diff --git a/src/doc/common/_vendor/gmpy2.inv b/src/doc/common/_vendor/gmpy2.inv new file mode 100644 index 0000000000000000000000000000000000000000..c91bc5ccdf59d96dbcd279bc6a613aa75a7b7a92 GIT binary patch literal 2891 zcmV-R3$*kjAX9K?X>NERX>N99Zgg*Qc_4OWa&u{KZXhxWBOp+6Z)#;@bUGksZE$%q z3L_v^WpZ_Ab7^j8AbMP}Lr z%7(%cvgwP28j!o)tGq^DFHaJXkXo{l_#bE0ZTa^HiH`%EbL3+um6MJAT(`x(y?$FL zZtecl)82pne0H_^{i$xRhyLqNyW#&IQ) zA%B>}KQ-cw7w(I&opNODg)3S9(h9=^K$QP-5vEf5^`W}I@a80lntwFq|4}0^v<-CC zHARG{PFYfC04vKI=p-6pm$%W-n{x* z=V!f2Sdqjn^t|YlFfgt3=>Ws6#84;R+{d!~w2znU3i>&=TP@t#4>Ev{YYa(nrYVM% z2j_oCAPDTh?GnMkh>zWfa7b-vPGbp0CjiE^1{dtjUGKFktJuhlbHteDv>@rC+ z85^3aqCJhNXycOFlb(P2hN?JF1@#k2QSriQBROpi@JDTJ=n6)iF%@w}RyAEd+@DPh0_mqNOTm5AHWH4bdL@JW!si z{HQO2E;|aPU+FTZU%8>RU84=HJ9Uh3P|bOJ=9bnHts;(_R#S~?OKjz^-u7`R^3xmr zOsdK7Qu4+UHNn%zRoHAkbeBeW@gsYRUii?S!dJ%Z3(<4qMO1+*WmOS1HJ2f}MpeCZ zwDs0dPoF`+$lj}7ML)juvZ-rnGG{vCKGA0b-U$#3^`LG)Mv(h`+=+8jG*5J~=P9I{p+=c}==s797*;<4Ty=QCy0^meV=6*^ zOaub!0j3I_Brb`F|^L55J zLasb-Gg-llv%uiD7UFcxjI!Zt)l|Zt^BhSbkGkPSA!(v94wf4*%cxnNCO>h5q*i-QmnGExt$q~- zBaN&DNa`a$5(Kk2UcSk!P?eG8Nl=>YBjg8EFu5qQHy+0=b*er(+@p@Jc&N>~MjL-1 zMw@N)Xyk_G3uI9PhMCG|WH)Bi-HG31)vUlD&Z8U|l96t`mTPD-A7~v;rU9+vw`n}<e9FRNeJ~qV)x2@iTF!2>G`2)mhBq`= z@E5rF;CbEJL|DO-Q(=WoPlh#kWjd_mD-&W%aGwyj#ote%bvBg(x5X!N-?s3?99@Ao zX6cF|HBZ;juaxEvq0`19iTZs@c$*!5#Pq05pzq5rd`}ST zOF)v&ba+y;crhA+$5*2fczih;VLx7vM(F9aXB2*dVF5qh`0{g(U7J-&cym^z6{%U3 zBnJ=s)d`4B&yqBHwUa`J>B?C9Ep*rhiNkR+01R$y^4OkL&8Xin*Yhjul;3GzNINd2M3O3JK&g}g=^>*vNq;eMSHafFh?9BKarZKaQJH)FqPkzpoEE4%Ji2|oQ=WKbi z?B+b1n#=DAF6KVx3dE9Z7J1GHizYfObD9x&O3KT@8jASdWi4mAbxc*x7CYt|BjA)_ zoPkr*I7yu$jG8K7;b49s!Ms2i1EQ9*H0LaM#weCdNle^d%6QzDmAE0WS-#@r}K>B{)7$)o=Ae5lX?cfJFRc<=@a{=;N}NeHrS05{+8?{3dDjkheay3#cM7+ca5uSVkiUGuMk#aWH;D`)nMqQ5bMAbrr^ z(4-FgY5a<0TwIZiiz~8uNG6t~Ft?q0Fg(oOy7Jpn7vYCfXOFl4X>ga?{U85aUU*Z8 za_@923;Up3|s-us{Zf5Kyp(4Cz9E6 zx=8Ulr^ago`b+$@n>}%+DSjOW=AuQcUuXQ0XCWM}4VvGE?|%(r*>y4Re|A&jwSoS7 p{Iq-Zr-@@HRXJy;i*XG3Thp(0|6R`okNEyK-I+q{{s*I-4b{tLqO1S_ literal 0 HcmV?d00001 diff --git a/src/doc/common/_vendor/ipywidgets.inv b/src/doc/common/_vendor/ipywidgets.inv new file mode 100644 index 0000000000000000000000000000000000000000..745e1f94c4980b75e4f90b118f9b3a5f7f671223 GIT binary patch literal 10934 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 Ym@JJ^YzF)wlX`oi-Joy&7e=6Dy6v{}761SM literal 0 HcmV?d00001 diff --git a/src/doc/common/_vendor/networkx.inv b/src/doc/common/_vendor/networkx.inv new file mode 100644 index 0000000000000000000000000000000000000000..4d38bd31eddf7dfeb33fea09937aaa535c79c203 GIT binary patch literal 51830 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 LcJ==U%D~KLlSU`D literal 0 HcmV?d00001 diff --git a/src/doc/common/_vendor/rpy2.inv b/src/doc/common/_vendor/rpy2.inv new file mode 100644 index 0000000000000000000000000000000000000000..8220930b97371dab71343736098b68381969f7a1 GIT binary patch literal 3289 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 XwTw#k)r8Q}4FWRnfNlN{3C@Kd0039h literal 0 HcmV?d00001 diff --git a/src/sage_docbuild/conf.py b/src/sage_docbuild/conf.py index a68dfe2cb7f..9bd98aee7d0 100644 --- a/src/sage_docbuild/conf.py +++ b/src/sage_docbuild/conf.py @@ -266,16 +266,23 @@ def sphinx_plot(graphics, **kwds): _vendored_inventories_dir = os.path.join(SAGE_DOC_SRC, "common", "_vendor") +# Run "sage -python -m sage_docbuild.vendor" to update src/doc/common/_vendor/*.inv _intersphinx_targets = { + 'cvxopt': ['https://cvxopt.org/userguide/'], + 'cvxpy': ['https://www.cvxpy.org/'], 'cypari2': ['https://cypari2.readthedocs.io/en/latest/'], 'cysignals': ['https://cysignals.readthedocs.io/en/latest/'], 'flint': ['https://flintlib.org/doc/'], 'fpylll': ['https://fpylll.readthedocs.io/en/latest/'], + 'gmpy2': ['https://gmpy2.readthedocs.io/en/latest/'], + 'ipywidgets': ['https://ipywidgets.readthedocs.io/en/stable/'], 'matplotlib': ['https://matplotlib.org/stable/'], 'mpmath': ['https://mpmath.org/doc/current/'], + 'networkx': ['https://networkx.org/documentation/stable/'], 'numpy': ['https://numpy.org/doc/stable/'], 'pplpy': [PPLPY_DOCS, 'https://www.sagemath.org/pplpy/'], 'python': ['https://docs.python.org/'], + 'rpy2': ['https://rpy2.github.io/doc/latest/html/'], 'scipy': ['https://docs.scipy.org/doc/scipy/'], 'sympy': ['https://docs.sympy.org/latest/'], } diff --git a/src/sage_docbuild/vendor.py b/src/sage_docbuild/vendor.py index cc2825062f5..5009f6e35de 100644 --- a/src/sage_docbuild/vendor.py +++ b/src/sage_docbuild/vendor.py @@ -1,3 +1,5 @@ +# "sage -python -m sage_docbuild.vendor" updates src/doc/common/_vendor/*.inv +# import os import sys From 4c0c20c1c1a3ac909b6e00a910ede4403eab9c1b Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 9 Mar 2024 22:09:01 -0800 Subject: [PATCH 215/518] src/sage/libs/flint/arith_sage.pyx: Fix markup --- src/sage/libs/flint/arith_sage.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/libs/flint/arith_sage.pyx b/src/sage/libs/flint/arith_sage.pyx index 83972c11934..302a3760a98 100644 --- a/src/sage/libs/flint/arith_sage.pyx +++ b/src/sage/libs/flint/arith_sage.pyx @@ -33,7 +33,7 @@ def bell_number(unsigned long n): ALGORITHM: - Uses :c:function:`arith_bell_number`. + Uses :c:func:`arith_bell_number`. EXAMPLES:: From b4fe4632a9b490ee6cf3f7efd4cb04f8cd8113ab Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 10 Mar 2024 17:01:35 -0700 Subject: [PATCH 216/518] src/sage/graphs: Replace broken networkx.lanl.gov links by intersphinx references --- src/sage/graphs/generators/families.py | 4 ++-- src/sage/graphs/generic_graph.py | 26 +++++++++++++------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/sage/graphs/generators/families.py b/src/sage/graphs/generators/families.py index f090296938e..f1ce793f80c 100644 --- a/src/sage/graphs/generators/families.py +++ b/src/sage/graphs/generators/families.py @@ -513,12 +513,12 @@ def BalancedTree(r, h): OUTPUT: The perfectly balanced tree of height `h \geq 1` and whose root has - degree `r \geq 2`. A ``NetworkXError`` is returned if `r < 2` or + degree `r \geq 2`. A :class:`~networkx.NetworkXError`` is raised if `r < 2` or `h < 1`. ALGORITHM: - Uses `NetworkX `_. + Uses the :ref:`NetworkX ` function :func:`~networkx.balanced_tree`. EXAMPLES: diff --git a/src/sage/graphs/generic_graph.py b/src/sage/graphs/generic_graph.py index 7c942b4f5df..844672fbf05 100644 --- a/src/sage/graphs/generic_graph.py +++ b/src/sage/graphs/generic_graph.py @@ -1336,9 +1336,9 @@ def export_to_file(self, filename, format=None, **kwds): - ``format`` -- string (default: ``None``); select the output format explicitly. If set to ``None`` (default), the format is set to be the - file extension of ``filename``. Admissible formats are: ``adjlist``, - ``dot``, ``edgelist``, ``gexf``, ``gml``, ``graphml``, - ``multiline_adjlist``, ``pajek``, ``yaml``. + file extension of ``filename``. Admissible formats are: ``'adjlist'``, + ``'dot'``, ``'edgelist'``, ``'gexf'``, ``'gml'``, ``'graphml'``, + ``'multiline_adjlist'``, ``'pajek'``, ``'yaml'``. - All other arguments are forwarded to the subfunction. For more information, see their respective documentation: @@ -1348,15 +1348,15 @@ def export_to_file(self, filename, format=None, **kwds): :widths: 30, 70 :delim: | - ``adjlist`` | http://networkx.lanl.gov/reference/generated/networkx.readwrite.adjlist.write_adjlist.html - ``dot`` | https://networkx.github.io/documentation/latest/reference/generated/networkx.drawing.nx_pydot.write_dot.html - ``edgelist`` | http://networkx.lanl.gov/reference/generated/networkx.readwrite.edgelist.write_edgelist.html - ``gexf`` | http://networkx.lanl.gov/reference/generated/networkx.readwrite.gexf.write_gexf.html - ``gml`` | http://networkx.lanl.gov/reference/generated/networkx.readwrite.gml.write_gml.html - ``graphml`` | http://networkx.lanl.gov/reference/generated/networkx.readwrite.graphml.write_graphml.html - ``multiline_adjlist`` | http://networkx.lanl.gov/reference/generated/networkx.readwrite.multiline_adjlist.write_multiline_adjlist.html - ``pajek`` | http://networkx.lanl.gov/reference/generated/networkx.readwrite.pajek.write_pajek.html - ``yaml`` | http://networkx.lanl.gov/reference/generated/networkx.readwrite.nx_yaml.write_yaml.html + ``'adjlist'`` | :func:`networkx.readwrite.adjlist.write_adjlist` + ``'dot'`` | :func:`networkx.drawing.nx_pydot.write_dot` + ``'edgelist'`` | :func:`networkx.readwrite.edgelist.write_edgelist` + ``'gexf'`` | :func:`networkx.readwrite.gexf.write_gexf` + ``'gml'`` | :func:`networkx.readwrite.gml.write_gml` + ``'graphml'`` | :func:`networkx.readwrite.graphml.write_graphml` + ``'multiline_adjlist'`` | :func:`networkx.readwrite.multiline_adjlist.write_multiline_adjlist` + ``'pajek'`` | :func:`networkx.readwrite.pajek.write_pajek` + ``'yaml'`` | :func:`networkx.readwrite.nx_yaml.write_yaml` .. SEEALSO:: @@ -1366,7 +1366,7 @@ def export_to_file(self, filename, format=None, **kwds): .. NOTE:: This functions uses the ``write_*`` functions defined in NetworkX - (see http://networkx.lanl.gov/reference/readwrite.html). + (see :mod:`networkx.readwrite`). EXAMPLES:: From e33a8ed4ffe4a02f3dcd50f52e0d0082ffb64ff0 Mon Sep 17 00:00:00 2001 From: Henry Ehrhard Date: Sun, 10 Mar 2024 17:01:37 -0700 Subject: [PATCH 217/518] Fix indentation --- src/sage/graphs/graph.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/sage/graphs/graph.py b/src/sage/graphs/graph.py index 095d615cc90..47cff46c32e 100644 --- a/src/sage/graphs/graph.py +++ b/src/sage/graphs/graph.py @@ -3930,14 +3930,13 @@ def chromatic_symmetric_function(self, R=None): ALGORITHM: - We traverse a binary tree whose leaves correspond to - subsets of edges, and whose internal vertices at depth `d` - correspond to a choice of whether to include the `d`-th - edge in a given subset. The components of the induced - subgraph are incrementally updated with a disjoint-set - forest. If the next edge would introduce a cycle to the - subset, we prune the branch as the terms produced by the - two subtrees cancel in this case. + We traverse a binary tree whose leaves correspond to subsets of + edges, and whose internal vertices at depth `d` correspond to a + choice of whether to include the `d`-th edge in a given subset. + The components of the induced subgraph are incrementally + updated using a disjoint-set forest. If the next edge would + introduce a cycle to the subset, we prune the branch as the + terms produced by the two subtrees cancel in this case. EXAMPLES:: From 363797b8873b53a935f458f2121406dd7db95147 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 10 Mar 2024 18:20:04 -0700 Subject: [PATCH 218/518] src/doc/en/developer/sage_manuals.rst: Document intersphinx --- src/doc/en/developer/sage_manuals.rst | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/doc/en/developer/sage_manuals.rst b/src/doc/en/developer/sage_manuals.rst index 578419384c7..70ee02adf5a 100644 --- a/src/doc/en/developer/sage_manuals.rst +++ b/src/doc/en/developer/sage_manuals.rst @@ -94,6 +94,19 @@ used as the link name, instead of its full path. absolute. If you are documenting ``method_one``, you can write ``:meth:`method_two```. +**Intersphinx references:** you can refer to the modules, classes, methods, functions +of the Python standard library and of the Python packages :mod:`cvxopt`, :mod:`cvxpy`, +:mod:`cypari2`, :mod:`cysignals`, :mod:`fpylll`, :mod:`ipywidgets`, :mod:`gmpy2`, +:mod:`matplotlib`, :mod:`mpmath`, :mod:`networkx`, :mod:`numpy`, :mod:`pplpy`, +:mod:`rpy2`, :mod:`scipy`, :mod:`sympy` in the same way. See the `Intersphinx +documentation `_ +for details. + +Likewise, you can refer to the C functions of the :ref:`FLINT ` library, +for example using ``:c:func:`arith_bell_number```; see `Sphinx' documentation on +the C domain `_ +for more information. + **Global namespace:** if an object (e.g. ``integral``) is automatically imported by Sage, you can link toward it without specifying its full path: From 99f4d0aa09e8fac9e2123bf1d4156ad3445169e0 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 10 Mar 2024 21:49:59 -0700 Subject: [PATCH 219/518] src/doc/en/developer/sage_manuals.rst: Expand intersphinx examples --- src/doc/en/developer/sage_manuals.rst | 86 ++++++++++++++++++++++----- 1 file changed, 71 insertions(+), 15 deletions(-) diff --git a/src/doc/en/developer/sage_manuals.rst b/src/doc/en/developer/sage_manuals.rst index 70ee02adf5a..e57a4e13996 100644 --- a/src/doc/en/developer/sage_manuals.rst +++ b/src/doc/en/developer/sage_manuals.rst @@ -38,7 +38,7 @@ Sage's manuals are written in `ReST `_ ``SAGE_ROOT/src/doc/en``. - Some documents have been **translated** into other languages. In order to - access them, change en/ into fr/,es/, de/... See :ref:`section-manuals-names`. + access them, change ``en/`` into ``fr/``, ``es/``, ``de/``... See :ref:`section-manuals-names`. .. _section-manuals-edit: @@ -82,9 +82,12 @@ The documentation can contain links toward modules, classes, or methods, e.g.:: :mod:`link to a module ` :mod:`sage.module_name` (here the link's text is the module's name) -For links toward classes, methods, or function, replace **:mod:** by -**:class:**, **:meth:** or **:func:** respectively. See `Sphinx' documentation -`_. +For links toward classes, methods, or functions, replace ``:mod:`` by +``:class:``, ``:meth:``, or ``:func:``, respectively. See Sphinx' documentation +on `cross-referencing Python objects +`_ +and for the general syntax of +`roles `_. **Short links:** the link ``:func:`~sage.mod1.mod2.mod3.func1``` is equivalent to ``:func:`func1 ```: the function's name will be @@ -94,19 +97,72 @@ used as the link name, instead of its full path. absolute. If you are documenting ``method_one``, you can write ``:meth:`method_two```. -**Intersphinx references:** you can refer to the modules, classes, methods, functions -of the Python standard library and of the Python packages :mod:`cvxopt`, :mod:`cvxpy`, -:mod:`cypari2`, :mod:`cysignals`, :mod:`fpylll`, :mod:`ipywidgets`, :mod:`gmpy2`, -:mod:`matplotlib`, :mod:`mpmath`, :mod:`networkx`, :mod:`numpy`, :mod:`pplpy`, -:mod:`rpy2`, :mod:`scipy`, :mod:`sympy` in the same way. See the `Intersphinx -documentation `_ -for details. - -Likewise, you can refer to the C functions of the :ref:`FLINT ` library, -for example using ``:c:func:`arith_bell_number```; see `Sphinx' documentation on -the C domain `_ +**Intersphinx references:** in the same way, you can refer to the modules, classes, +methods, functions of the Python standard library and of several Python packages +used by SageMath; see the `Intersphinx documentation +`_ +for details. Likewise, you can refer to the C functions of the +:ref:`FLINT ` library; see `Sphinx' documentation on +cross-referencing C constructs +`_ for more information. +.. LIST-TABLE:: + :widths: 4 7 5 + :header-rows: 0 + + * - Python + - ``:exc:`ValueError``` + - :exc:`ValueError` + * - :ref:`CVXOPT ` + - ``:func:`cvxopt.solvers.socp``` + - :func:`cvxopt.solvers.socp` + * - :ref:`CVXpy ` + - ``:class:`~cvxpy.atoms.log_det.log_det``` + - :class:`~cvxpy.atoms.log_det.log_det` + * - :ref:`cypari2 ` + - ``:class:`cypari2.gen.Gen``` + - :class:`cypari2.gen.Gen` + * - :ref:`cysignals ` + - ``:envvar:`CYSIGNALS_CRASH_DAYS``` + - :envvar:`CYSIGNALS_CRASH_DAYS` + * - :ref:`FLINT ` + - ``:c:func:`arith_bell_number``` + - :c:func:`arith_bell_number` + * - :ref:`gmpy2 ` + - ``:func:`gmpy2.gamma_inc``` + - :func:`gmpy2.gamma_inc` + * - :ref:`ipywidgets ` + - ``:mod:`~ipywidgets.widgets.widget_date``` + - :mod:`~ipywidgets.widgets.widget_date` + * - :ref:`Matplotlib ` + - ``:mod:`matplotlib.bezier``` + - :mod:`matplotlib.bezier` + * - :ref:`mpmath ` + - ``:attr:`mpmath.mp.khinchin``` + - :attr:`mpmath.mp.khinchin` + * - :ref:`NetworkX ` + - ``:attr:`~networkx.DiGraph.out_degree``` + - :attr:`~networkx.DiGraph.out_degree` + * - :ref:`NumPy ` + - ``:data:`numpy.NAN``` + - :data:`numpy.NAN` + * - :ref:`pplpy ` + - ``:mod:`ppl.polyhedron``` + - :mod:`ppl.polyhedron` + * - :ref:`rpy2 ` + - ``:class:`~rpy2.robjects.vectors.DataFrame``` + - :class:`~rpy2.robjects.vectors.DataFrame` + * - :ref:`SciPy ` + - ``:data:`scipy.special.huber``` + - :data:`scipy.special.huber` + * - :ref:`SymPy ` + - ``:class:`~sympy.diffgeom.WedgeProduct``` + - :class:`~sympy.diffgeom.WedgeProduct` + +To see the available cross references in any of these libraries, you can use the command +``./sage -python -m sphinx.ext.intersphinx src/doc/common/_vendor/numpy.inv``. + **Global namespace:** if an object (e.g. ``integral``) is automatically imported by Sage, you can link toward it without specifying its full path: From 2d2646600f11fb3f9acdec60012ce81090cc6df9 Mon Sep 17 00:00:00 2001 From: Kwankyu Lee Date: Tue, 5 Mar 2024 17:51:03 +0900 Subject: [PATCH 220/518] Edit docstrings attached to free resolutions --- src/sage/homology/free_resolution.py | 24 +++++++------- src/sage/homology/graded_resolution.py | 43 ++++++++++---------------- 2 files changed, 29 insertions(+), 38 deletions(-) diff --git a/src/sage/homology/free_resolution.py b/src/sage/homology/free_resolution.py index 8cc950df90c..29c33e4e2a6 100644 --- a/src/sage/homology/free_resolution.py +++ b/src/sage/homology/free_resolution.py @@ -7,8 +7,8 @@ .. MATH:: - R^{n_0} \xleftarrow{d_1} R^{n_1} \xleftarrow{d_2} - \cdots \xleftarrow{d_k} R^{n_k} \xleftarrow{d_{k+1}} 0 + 0 \rightarrow R^{n_k} \xrightarrow{d_k} + \cdots \xrightarrow{d_2} R^{n_1} \xrightarrow{d_1} R^{n_0} terminating with a zero module at the end that is exact (all homology groups are zero) such that the image of `d_1` is `M`. @@ -87,8 +87,8 @@ class FreeResolution(SageObject, metaclass=ClasscallMetaclass): .. MATH:: - R^{n_1} \xleftarrow{d_1} R^{n_1} \xleftarrow{d_2} - \cdots \xleftarrow{d_k} R^{n_k} \xleftarrow{d_{k+1}} \cdots + \cdots \rightarrow R^{n_k} \xrightarrow{d_k} + \cdots \xrightarrow{d_2} R^{n_1} \xrightarrow{d_1} R^{n_0} that is exact (all homology groups are zero) such that the image of `d_1` is `M`. @@ -792,14 +792,14 @@ class FiniteFreeResolution_singular(FiniteFreeResolution): The available algorithms and the corresponding Singular commands are shown below: - ============= ============================ - algorithm Singular commands - ============= ============================ - ``minimal`` ``mres(ideal)`` - ``shreyer`` ``minres(sres(std(ideal)))`` - ``standard`` ``minres(nres(std(ideal)))`` - ``heuristic`` ``minres(res(std(ideal)))`` - ============= ============================ + ============= ============================ + algorithm Singular commands + ============= ============================ + ``minimal`` ``mres(ideal)`` + ``shreyer`` ``minres(sres(std(ideal)))`` + ``standard`` ``minres(nres(std(ideal)))`` + ``heuristic`` ``minres(res(std(ideal)))`` + ============= ============================ EXAMPLES:: diff --git a/src/sage/homology/graded_resolution.py b/src/sage/homology/graded_resolution.py index 0c01ad6f423..ceadd15c2de 100644 --- a/src/sage/homology/graded_resolution.py +++ b/src/sage/homology/graded_resolution.py @@ -97,16 +97,16 @@ class GradedFiniteFreeResolution(FiniteFreeResolution): - ``module`` -- a homogeneous submodule of a free module `M` of rank `n` over `S` or a homogeneous ideal of a multivariate polynomial ring `S` + - ``degrees`` -- (default: a list with all entries `1`) a list of integers or integer vectors giving degrees of variables of `S` + - ``shifts`` -- a list of integers or integer vectors giving shifts of degrees of `n` summands of the free module `M`; this is a list of zero degrees of length `n` by default - - ``name`` -- a string; name of the base ring - .. WARNING:: + - ``name`` -- a string; name of the base ring - This does not check that the module is homogeneous. """ def __init__(self, module, degrees=None, shifts=None, name='S', **kwds): r""" @@ -317,10 +317,6 @@ class GradedFiniteFreeResolution_free_module(GradedFiniteFreeResolution, FiniteF r""" Graded free resolution of free modules. - .. WARNING:: - - This does not check that the module is homogeneous. - EXAMPLES:: sage: from sage.homology.free_resolution import FreeResolution @@ -434,8 +430,10 @@ class GradedFiniteFreeResolution_singular(GradedFiniteFreeResolution, FiniteFree - ``algorithm`` -- Singular algorithm to compute a resolution of ``ideal`` - If ``module`` is an ideal of `S`, it is considered as a submodule of a - free module of rank `1` over `S`. + OUTPUT: a graded minimal free resolution of ``ideal`` + + If ``module`` is an ideal of `S`, it is considered as a submodule of a free + module of rank `1` over `S`. The degrees given to the variables of `S` are integers or integer vectors of the same length. In the latter case, `S` is said to be multigraded, and the @@ -446,25 +444,18 @@ class GradedFiniteFreeResolution_singular(GradedFiniteFreeResolution, FiniteFree rank one over `S`, denoted `S(-d)` with shift `d`. The computation of the resolution is done by using ``libSingular``. - Different Singular algorithms can be chosen for best performance. - - OUTPUT: a graded minimal free resolution of ``ideal`` - - The available algorithms and the corresponding Singular commands are shown + Different Singular algorithms can be chosen for best performance. The + available algorithms and the corresponding Singular commands are shown below: - ============= ============================ - algorithm Singular commands - ============= ============================ - ``minimal`` ``mres(ideal)`` - ``shreyer`` ``minres(sres(std(ideal)))`` - ``standard`` ``minres(nres(std(ideal)))`` - ``heuristic`` ``minres(res(std(ideal)))`` - ============= ============================ - - .. WARNING:: - - This does not check that the module is homogeneous. + ============= ============================ + algorithm Singular commands + ============= ============================ + ``minimal`` ``mres(ideal)`` + ``shreyer`` ``minres(sres(std(ideal)))`` + ``standard`` ``minres(nres(std(ideal)))`` + ``heuristic`` ``minres(res(std(ideal)))`` + ============= ============================ EXAMPLES:: From 3b7c39679c9328a325e27ec0286ebfd5a307f738 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Mon, 11 Mar 2024 14:49:34 +0100 Subject: [PATCH 221/518] ruff PERF fixes in algebras/ --- src/sage/algebras/free_algebra.py | 7 +- src/sage/algebras/free_zinbiel_algebra.py | 10 +- .../lie_algebras/classical_lie_algebra.py | 17 ++- .../algebras/lie_algebras/free_lie_algebra.py | 27 ++-- .../algebras/quatalg/quaternion_algebra.py | 16 +-- src/sage/algebras/schur_algebra.py | 44 +++--- .../algebras/steenrod/steenrod_algebra.py | 53 ++++--- .../steenrod/steenrod_algebra_bases.py | 135 +++++++++--------- 8 files changed, 149 insertions(+), 160 deletions(-) diff --git a/src/sage/algebras/free_algebra.py b/src/sage/algebras/free_algebra.py index 66d0a425c31..e84a6b03f24 100644 --- a/src/sage/algebras/free_algebra.py +++ b/src/sage/algebras/free_algebra.py @@ -648,11 +648,8 @@ def _element_constructor_(self, x): M = self._indices def exp_to_monomial(T): - out = [] - for i in range(len(T)): - if T[i]: - out.append((i % ngens, T[i])) - return M(out) + return M([(i % ngens, Ti) for i, Ti in enumerate(T) if Ti]) + return self.element_class(self, {exp_to_monomial(T): c for T, c in x.letterplace_polynomial().dict().items()}) # ok, not a free algebra element (or should not be viewed as one). diff --git a/src/sage/algebras/free_zinbiel_algebra.py b/src/sage/algebras/free_zinbiel_algebra.py index d421b2d7aee..720ee803d9b 100644 --- a/src/sage/algebras/free_zinbiel_algebra.py +++ b/src/sage/algebras/free_zinbiel_algebra.py @@ -864,14 +864,12 @@ def check(x): return None ret = list(self.vars) cur_vars = set(ret) - for v in other.vars: - if v not in cur_vars: - ret.append(v) + ret.extend(v for v in other.vars if v not in cur_vars) return ZinbielFunctor(ret, self._side) - else: - return None - def _repr_(self): + return None + + def _repr_(self) -> str: """ TESTS:: diff --git a/src/sage/algebras/lie_algebras/classical_lie_algebra.py b/src/sage/algebras/lie_algebras/classical_lie_algebra.py index 4466cf6e46e..023dbef8b0c 100644 --- a/src/sage/algebras/lie_algebras/classical_lie_algebra.py +++ b/src/sage/algebras/lie_algebras/classical_lie_algebra.py @@ -861,6 +861,7 @@ def simple_root(self, i, h): return 2*h[i,i] return h[i,i] - h[i+1,i+1] + class ExceptionalMatrixLieAlgebra(ClassicalMatrixLieAlgebra): """ A matrix Lie algebra of exceptional type. @@ -890,6 +891,7 @@ def _repr_(self): """ return "Simple matrix Lie algebra of type {} over {}".format(self.cartan_type(), self.base_ring()) + class e6(ExceptionalMatrixLieAlgebra): r""" The matrix Lie algebra `\mathfrak{e}_6`. @@ -918,6 +920,7 @@ def __init__(self, R): f = [MS({(c[1],c[0]): one for c in coord}) for coord in coords] ExceptionalMatrixLieAlgebra.__init__(self, R, CartanType(['E', 6]), e, f) + class e7(ExceptionalMatrixLieAlgebra): r""" The matrix Lie algebra `\mathfrak{e}_7`. @@ -952,6 +955,7 @@ def __init__(self, R): f = [MS({(c[1], c[0]): one for c in coord}) for coord in coords] ExceptionalMatrixLieAlgebra.__init__(self, R, CartanType(['E', 7]), e, f) + class e8(ExceptionalMatrixLieAlgebra): r""" The matrix Lie algebra `\mathfrak{e}_8`. @@ -993,6 +997,7 @@ def basis(self): g = LieAlgebraChevalleyBasis(self.base_ring(), self.cartan_type()) return Family([ge.adjoint_matrix(sparse=True) for ge in g.basis()]) + class f4(ExceptionalMatrixLieAlgebra): r""" The matrix Lie algebra `\mathfrak{f}_4`. @@ -1040,6 +1045,7 @@ def __init__(self, R): f.reverse() ExceptionalMatrixLieAlgebra.__init__(self, R, CartanType(['F', 4]), e, f) + class g2(ExceptionalMatrixLieAlgebra): r""" The matrix Lie algebra `\mathfrak{g}_2`. @@ -1066,6 +1072,7 @@ def __init__(self, R): MS({(1,1): one, (2,2): -one, (4,4): one, (5,5): -one})] ExceptionalMatrixLieAlgebra.__init__(self, R, CartanType(['G', 2]), e, f, h) + ####################################### ## Compact real form @@ -2117,13 +2124,9 @@ def gens(self): alphacheck = self._Q.simple_coroots() B = self.basis() - ret = [] - for i in index_set: - ret.append(B[alpha[i]]) - for i in index_set: - ret.append(B[-alpha[i]]) - for i in index_set: - ret.append(B[alphacheck[i]]) + ret = [B[alpha[i]] for i in index_set] + ret.extend(B[-alpha[i]] for i in index_set) + ret.extend(B[alphacheck[i]] for i in index_set) return tuple(ret) def highest_root_basis_elt(self, pos=True): diff --git a/src/sage/algebras/lie_algebras/free_lie_algebra.py b/src/sage/algebras/lie_algebras/free_lie_algebra.py index 3d36e870b98..e641a9140e7 100644 --- a/src/sage/algebras/lie_algebras/free_lie_algebra.py +++ b/src/sage/algebras/lie_algebras/free_lie_algebra.py @@ -43,6 +43,7 @@ from sage.rings.integer_ring import ZZ + class FreeLieBasis_abstract(FinitelyGeneratedLieAlgebra, IndexedGenerators, BindableClass): """ Abstract base class for all bases of a free Lie algebra. @@ -266,7 +267,7 @@ def graded_dimension(self, k): return 0 from sage.arith.misc import moebius s = len(self.lie_algebra_generators()) - k = ZZ(k) # Make sure we have something that is in ZZ + k = ZZ(k) # Make sure we have something that is in ZZ return sum(moebius(d) * s**(k // d) for d in k.divisors()) // k @abstract_method @@ -306,6 +307,7 @@ def _rewrite_bracket(self, l, r): Element = FreeLieAlgebraElement + class FreeLieAlgebra(Parent, UniqueRepresentation): r""" The free Lie algebra of a set `X`. @@ -387,7 +389,7 @@ def _repr_(self): sage: LieAlgebra(QQ, 3, 'x') Free Lie algebra generated by (x0, x1, x2) over Rational Field """ - n = tuple(map(LieGenerator, self._names, range(len(self._names)))) # To remove those stupid quote marks + n = tuple(map(LieGenerator, self._names, range(len(self._names)))) # To remove those stupid quote marks return "Free Lie algebra generated by {} over {}".format(n, self.base_ring()) def _construct_UEA(self): @@ -519,10 +521,8 @@ def _generate_hall_set(self, k): range(len(self.variable_names())))) if k == 2: basis = self._generate_hall_set(1) - ret = [] - for i,a in enumerate(basis): - for b in basis[i+1:]: - ret.append( GradedLieBracket(a, b, 2) ) + ret = [GradedLieBracket(a, b, 2) for i, a in enumerate(basis) + for b in basis[i+1:]] return tuple(ret) # We don't want to do the middle when we're even, so we add 1 and @@ -537,13 +537,13 @@ def _generate_hall_set(self, k): # [a, b] < [x, y], we will always be in the Hall set. if k == 4: basis = self._generate_hall_set(2) - for i,a in enumerate(basis): + for i, a in enumerate(basis): for b in basis[i+1:]: ret.append(GradedLieBracket(a, b, k)) # Do the middle case when we are even and k > 4 elif k % 2 == 0: - basis = self._generate_hall_set(k // 2) # grade >= 2 - for i,a in enumerate(basis): + basis = self._generate_hall_set(k // 2) # grade >= 2 + for i, a in enumerate(basis): for b in basis[i+1:]: if b._left <= a: ret.append(GradedLieBracket(a, b, k)) @@ -859,7 +859,7 @@ def graded_basis(self, k): one = self.base_ring().one() if k == 1: return tuple(self.element_class(self, {LieGenerator(n, k): one}) - for k,n in enumerate(names)) + for k, n in enumerate(names)) from sage.combinat.combinat_cython import lyndon_word_iterator n = len(self._indices) @@ -884,8 +884,9 @@ def pbw_basis(self, **kwds): poincare_birkhoff_witt_basis = pbw_basis + ####################################### -## Category for the realizations +# Category for the realizations class FreeLieAlgebraBases(Category_realization_of_parent): r""" @@ -937,11 +938,13 @@ def super_categories(self): """ return [LieAlgebras(self.base().base_ring()).WithBasis(), Realizations(self.base())] -def is_lyndon(w): + +def is_lyndon(w) -> bool: """ Modified form of ``Word(w).is_lyndon()`` which uses the default order (this will either be the natural integer order or lex order) and assumes the input ``w`` behaves like a nonempty list. + This function here is designed for speed. EXAMPLES:: diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index 849a5bdd727..464d1df8097 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -1800,16 +1800,10 @@ def discriminant(self): sage: type(S.discriminant()) <... 'sage.rings.rational.Rational'> """ - L = [] - for d in self.basis(): - MM = [] - for e in self.basis(): - MM.append(d.pair(e)) - L.append(MM) - + L = [[d.pair(e) for e in self.basis()] for d in self.basis()] return (MatrixSpace(QQ, 4, 4)(L)).determinant().sqrt() - def is_maximal(self): + def is_maximal(self) -> bool: r""" Check whether the order of ``self`` is maximal in the ambient quaternion algebra. @@ -2761,8 +2755,8 @@ def minimal_element(self): if not qf.is_positive_definite(): raise ValueError('quaternion algebra must be definite') pariqf = qf.__pari__() - _,v = pariqf.qfminim(None, None, 1) - return sum(ZZ(c)*g for c,g in zip(v, self.basis())) + _, v = pariqf.qfminim(None, None, 1) + return sum(ZZ(c) * g for c, g in zip(v, self.basis())) def theta_series(self, B, var='q'): r""" @@ -3380,7 +3374,7 @@ def is_primitive(self): False """ - _,g = self.primitive_decomposition() + _, g = self.primitive_decomposition() return g.is_one() ####################################################################### diff --git a/src/sage/algebras/schur_algebra.py b/src/sage/algebras/schur_algebra.py index fc4d787a266..ef809e2ab01 100644 --- a/src/sage/algebras/schur_algebra.py +++ b/src/sage/algebras/schur_algebra.py @@ -121,11 +121,8 @@ def schur_representative_indices(n, r): I1 = _schur_I_nr_representatives(n, k) else: I2 = _schur_I_nr_representatives(n, k - j) - I = [] - for m1 in range(len(I1)): - for m2 in range(len(I2)): - I.append(I1[m1] + I2[m2]) - I1 = I + I1 = [I1[m1] + I2[m2] for m1 in range(len(I1)) + for m2 in range(len(I2))] j = k elif k == l - 1: I2 = [] @@ -134,16 +131,13 @@ def schur_representative_indices(n, r): I1 = _schur_I_nr_representatives(n, k) else: I2 = _schur_I_nr_representatives(n, k - j) - I = [] - for m1 in range(len(I1)): - for m2 in range(len(I2)): - I.append(I1[m1] + I2[m2]) - I1 = I + I1 = [I1[m1] + I2[m2] for m1 in range(len(I1)) + for m2 in range(len(I2))] else: k += 1 - for v in I1: - basis.append((tuple(e), tuple(v))) + te = tuple(e) + basis.extend((te, tuple(v)) for v in I1) return basis @@ -332,10 +326,8 @@ def product_on_basis(self, e_ij, e_kl): l = sorted(l) # Find basis elements (p,q) such that p ~ i and q ~ l - e_pq = [] - for v in self.basis().keys(): - if v[0] == i and sorted(v[1]) == l: - e_pq.append(v) + e_pq = [v for v in self.basis().keys() + if v[0] == i and sorted(v[1]) == l] b = self.basis() product = self.zero() @@ -481,10 +473,9 @@ def _monomial_product(self, xi, v): sage: T._monomial_product(xi, (1, 1, 1)) B[1] # B[1] # B[2] + B[1] # B[2] # B[1] + B[2] # B[1] # B[1] """ - ret = [] - for i in itertools.product(list(range(1, self._n + 1)), repeat=self._r): - if schur_representative_from_index(i, v) == xi: - ret.append(tuple(i)) + L = range(1, self._n + 1) + ret = [tuple(i) for i in itertools.product(L, repeat=self._r) + if schur_representative_from_index(i, v) == xi] return self.sum_of_monomials(ret) class Element(CombinatorialFreeModule_Tensor.Element): @@ -675,13 +666,10 @@ def GL_irreducible_character(n, mu, KK): length = len(carter_lusztig) phi = mbasis.zero() - for aa in range(len(contents)): - mat = [] - for kk in range(len(JJ[aa])): - temp = [] - for j in range(length): - temp.append(graded_basis[aa][kk].inner_product(carter_lusztig[j])) - mat.append(temp) + for aa, c_aa in enumerate(contents): + mat = [[graded_basis[aa][kk].inner_product(carter_lusztig[j]) + for j in range(length)] + for kk in range(len(JJ[aa]))] angle = Matrix(mat) - phi += (len(JJ[aa]) - angle.nullity()) * mbasis(contents[aa]) + phi += (len(JJ[aa]) - angle.nullity()) * mbasis(c_aa) return phi diff --git a/src/sage/algebras/steenrod/steenrod_algebra.py b/src/sage/algebras/steenrod/steenrod_algebra.py index ae37091ed57..3f9153cf962 100644 --- a/src/sage/algebras/steenrod/steenrod_algebra.py +++ b/src/sage/algebras/steenrod/steenrod_algebra.py @@ -1215,12 +1215,12 @@ def product_on_basis(self, t1, t2): if t1[-1] + t2[0] == 2: return self.zero() mono = t1[:-1] + (t1[-1] + t2[0],) + t2[1:] - d = make_mono_admissible(mono, p,generic=self._generic) + d = make_mono_admissible(mono, p, generic=self._generic) else: # p=2 mono = t1 + t2 while len(mono) > 1 and mono[-1] == 0: mono = mono[:-1] - d = make_mono_admissible(mono,generic=self._generic) + d = make_mono_admissible(mono, generic=self._generic) return self._from_dict(d, coerce=True) else: x = self({t1: 1}) @@ -1382,9 +1382,10 @@ def coprod_list(t): A = SteenrodAlgebra(p=p, basis=algorithm, generic=self._generic) x = A(self._change_basis_on_basis(t, algorithm)).coproduct(algorithm=algorithm) result = [] - for (a,b), coeff in x: + for (a, b), coeff in x: result.append((tensor((A._change_basis_on_basis(a, basis), - A._change_basis_on_basis(b, basis))),coeff)) + A._change_basis_on_basis(b, basis))), + coeff)) return self.tensor_square().linear_combination(result) def coproduct(self, x, algorithm='milnor'): @@ -1516,7 +1517,7 @@ def antipode_on_basis(self, t): if n != 0: antipode = -self.Q(0) * antipode * (-1)**antipode.degree() else: - B = SteenrodAlgebra(p=p,generic=self._generic).basis(n * 2 * (p-1)) + B = SteenrodAlgebra(p=p, generic=self._generic).basis(n * 2 * (p-1)) s = self(0) for b in B: if len(b.leading_support()[0]) == 0: @@ -1620,7 +1621,7 @@ def _milnor_on_basis(self, t): """ basis = self.basis_name() p = self.prime() - A = SteenrodAlgebra(p=p,generic=self._generic) + A = SteenrodAlgebra(p=p, generic=self._generic) # milnor if basis == 'milnor': return A({t: 1}) @@ -1645,14 +1646,14 @@ def _milnor_on_basis(self, t): elif basis == 'woody' or basis == 'woodz': # each entry in t is a pair (m,k), corresponding to w(m,k), defined by # `w(m,k) = \text{Sq}^{2^m (2^{k+1}-1)}`. - for (m,k) in t: + for (m, k) in t: ans = ans * A.Sq(2**m * (2**(k+1) - 1)) # wall[_long] elif basis.find('wall') >= 0: # each entry in t is a pair (m,k), corresponding to Q^m_k, defined by # `Q^m_k = Sq(2^k) Sq(2^{k+1}) ... Sq(2^m)`. - for (m,k) in t: + for (m, k) in t: exponent = 2**k ans = ans * A.Sq(exponent) for i in range(m-k): @@ -1796,7 +1797,7 @@ def _change_basis_on_basis(self, t, basis='milnor'): """ from sage.matrix.constructor import matrix from sage.rings.finite_rings.finite_field_constructor import GF - from .steenrod_algebra_bases import steenrod_algebra_basis,\ + from .steenrod_algebra_bases import steenrod_algebra_basis, \ convert_from_milnor_matrix from .steenrod_algebra_misc import get_basis_name basis = get_basis_name(basis, self.prime(), generic=self._generic) @@ -1813,9 +1814,7 @@ def _change_basis_on_basis(self, t, basis='milnor'): return A(a.leading_coefficient()) Bnew = steenrod_algebra_basis(deg, basis, p, generic=self._generic) Bmil = steenrod_algebra_basis(deg, 'milnor', p, generic=self._generic) - v = [] - for a in Bmil: - v.append(d.get(a, 0)) + v = [d.get(a, 0) for a in Bmil] out = (matrix(GF(p), 1, len(v), v) * convert_from_milnor_matrix(deg, basis, p, generic=self._generic)) new_d = dict(zip(Bnew, out[0])) @@ -1952,7 +1951,7 @@ def q_degree(m, prime=3): if basis == 'woody' or basis == 'woodz': # each entry in t is a pair (m,k), corresponding to w(m,k), defined by # `w(m,k) = \text{Sq}^{2^m (2^{k+1}-1)}`. - return sum(2**m * (2**(k+1)-1) for (m,k) in t) + return sum(2**m * (2**(k+1)-1) for (m, k) in t) # wall, arnon_a if basis.find('wall') >= 0 or basis.find('arnona') >= 0: @@ -1963,7 +1962,7 @@ def q_degree(m, prime=3): # Arnon A: each entry in t is a pair (m,k), corresponding # to X^m_k, defined by `X^m_k = Sq(2^m) ... Sq(2^{k+1}) # Sq(2^k)` - return sum(2**k * (2**(m-k+1)-1) for (m,k) in t) + return sum(2**k * (2**(m-k+1)-1) for (m, k) in t) # pst, comm if basis.find('pst') >= 0 or basis.find('comm') >= 0: @@ -1974,7 +1973,7 @@ def q_degree(m, prime=3): # to c_{i,j}, the iterated commutator defined by # c_{i,1} = Sq(2^i) and c_{i,j} = [c_{i,j-1}, # Sq(2^{i+j-1})]. - return sum(2**m * (2**k - 1) for (m,k) in t) + return sum(2**m * (2**k - 1) for (m, k) in t) # p odd: # # Pst: have pair (Q, P) where Q is a tuple of Q's, as in @@ -1987,7 +1986,7 @@ def q_degree(m, prime=3): # iterated commutator defined by c_{s,1} = P(p^s) and # c_{s,t} = [P(p^{s+t-1}), c_{s,t-1}]. q_deg = q_degree(t[0], prime=p) - p_deg = sum(2 * n * p**s * (p**t - 1) for ((s,t), n) in t[1]) + p_deg = sum(2 * n * p**s * (p**t - 1) for ((s, t), n) in t[1]) return q_deg + p_deg # coercion methods: @@ -2305,8 +2304,8 @@ def _check_profile_on_basis(self, t): # p odd: if any(self.profile(i, 1) != 2 for i in t[0]): return False - return all(self.profile(i + 1,0) == Infinity - or t[1][i] < p**self.profile(i + 1,0) + return all(self.profile(i + 1, 0) == Infinity + or t[1][i] < p**self.profile(i + 1, 0) for i in range(len(t[1]))) def P(self, *nums): @@ -2789,7 +2788,7 @@ def gen(self, i=0): for t in range(1, min(j, last_t) + 1): s = j - t if self.profile(t) > s: - guess = self.pst(s,t) + guess = self.pst(s, t) idx += 1 if idx == i: elt = guess @@ -2800,7 +2799,7 @@ def gen(self, i=0): if self.profile(1) == Infinity: if not self._generic: return self.Sq(p**i) - elif self.profile(0,1) == 2: + elif self.profile(0, 1) == 2: if i == 0: return self.Q(0) else: @@ -2810,7 +2809,7 @@ def gen(self, i=0): idx = -1 tot = 1 found = False - A = SteenrodAlgebra(p=p,generic=self._generic) + A = SteenrodAlgebra(p=p, generic=self._generic) while not found: if self._generic: test = A.Q(tot-1) @@ -2820,7 +2819,7 @@ def gen(self, i=0): break for t in range(1, tot+1): s = tot - t - test = A.pst(s,t) + test = A.pst(s, t) if test in self: idx += 1 if idx == i: @@ -2878,8 +2877,8 @@ def is_commutative(self): n = max(self._profile) return all(self.profile(i) == 0 for i in range(1, n)) n = max(self._profile[0]) - return (all(self.profile(i,0) == 0 for i in range(1, n)) - and all(self.profile(i,1) == 1 for i in range(n))) + return (all(self.profile(i, 0) == 0 for i in range(1, n)) + and all(self.profile(i, 1) == 1 for i in range(n))) def is_finite(self): r""" @@ -2979,7 +2978,7 @@ def top_class(self): rp, ep = self._profile e = [kk for kk in range(len(ep)) if ep[kk] == 2] r = [p**kk-1 for kk in rp] - ans = AM.monomial((tuple(e),tuple(r))) + ans = AM.monomial((tuple(e), tuple(r))) return self(ans.change_basis(self.basis_name())) def order(self): @@ -3331,7 +3330,7 @@ def change_basis(self, basis='milnor'): A = self.parent() return A._change_basis(self, basis) - def _basis_dictionary(self,basis): + def _basis_dictionary(self, basis): r""" Convert self to ``basis``, returning a dictionary of terms of the form (mono: coeff), where mono is a monomial in the given @@ -3733,7 +3732,7 @@ def wall_height(self): if x > 0: for j in range(1+Integer(x).exact_log(2)): if (2**j & x) != 0: - for k in range(j,i+j): + for k in range(j, i+j): h[k] += 1 i += 1 h.reverse() diff --git a/src/sage/algebras/steenrod/steenrod_algebra_bases.py b/src/sage/algebras/steenrod/steenrod_algebra_bases.py index 9c8cc0935c8..302815af15f 100644 --- a/src/sage/algebras/steenrod/steenrod_algebra_bases.py +++ b/src/sage/algebras/steenrod/steenrod_algebra_bases.py @@ -106,13 +106,14 @@ :file:`steenrod_algebra.py`. """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2008-2010 John H. Palmieri # Distributed under the terms of the GNU General Public License (GPL) -#***************************************************************************** +# **************************************************************************** from sage.misc.cachefunc import cached_function + @cached_function def convert_to_milnor_matrix(n, basis, p=2, generic='auto'): r""" @@ -171,7 +172,7 @@ def convert_to_milnor_matrix(n, basis, p=2, generic='auto'): generic = p != 2 if n == 0: return matrix(GF(p), 1, 1, [[1]]) - milnor_base = steenrod_algebra_basis(n,'milnor',p, generic=generic) + milnor_base = steenrod_algebra_basis(n, 'milnor', p, generic=generic) rows = [] A = SteenrodAlgebra(basis=basis, p=p, generic=generic) for poly in A.basis(n): @@ -180,7 +181,8 @@ def convert_to_milnor_matrix(n, basis, p=2, generic='auto'): entry = d.get(v, 0) rows = rows + [entry] d = len(milnor_base) - return matrix(GF(p),d,d,rows) + return matrix(GF(p), d, d, rows) + def convert_from_milnor_matrix(n, basis, p=2, generic='auto'): r""" @@ -248,11 +250,11 @@ def convert_from_milnor_matrix(n, basis, p=2, generic='auto'): [1 2 0 0] [0 1 0 0] """ - mat = convert_to_milnor_matrix(n,basis,p,generic) - if mat.nrows() != 0: - return convert_to_milnor_matrix(n,basis,p,generic).inverse() - else: - return mat + mat = convert_to_milnor_matrix(n, basis, p, generic) + if mat.nrows(): + return convert_to_milnor_matrix(n, basis, p, generic).inverse() + return mat + @cached_function def steenrod_algebra_basis(n, basis='milnor', p=2, **kwds): @@ -348,36 +350,37 @@ def steenrod_algebra_basis(n, basis='milnor', p=2, **kwds): profile = kwds.get("profile", None) if (profile is not None and profile != () and profile != ((), ()) - and basis != 'milnor' and basis.find('pst') == -1): + and basis != 'milnor' and basis.find('pst') == -1): raise ValueError("Profile functions may only be used with the Milnor or pst bases") - ## Milnor basis + # Milnor basis if basis_name == 'milnor': - return milnor_basis(n,p,**kwds) - ## Serre-Cartan basis + return milnor_basis(n, p, **kwds) + # Serre-Cartan basis elif basis_name == 'serre-cartan': - return serre_cartan_basis(n,p,**kwds) - ## Atomic bases, p odd: + return serre_cartan_basis(n, p, **kwds) + # Atomic bases, p odd: elif generic and (basis_name.find('pst') >= 0 or basis_name.find('comm') >= 0): return atomic_basis_odd(n, basis_name, p, **kwds) - ## Atomic bases, p=2 + # Atomic bases, p=2 elif not generic and (basis_name == 'woody' or basis_name == 'woodz' or basis_name == 'wall' or basis_name == 'arnona' or basis_name.find('pst') >= 0 or basis_name.find('comm') >= 0): return atomic_basis(n, basis_name, **kwds) - ## Arnon 'C' basis + # Arnon 'C' basis elif not generic and basis == 'arnonc': return arnonC_basis(n) else: raise ValueError("Unknown basis: %s at the prime %s" % (basis, p)) + # helper functions for producing bases def restricted_partitions(n, l, no_repeats=False): """ - List of 'restricted' partitions of `n`: partitions with parts taken + Iterator over 'restricted' partitions of `n`: partitions with parts taken from list `l`. INPUT: @@ -387,25 +390,25 @@ def restricted_partitions(n, l, no_repeats=False): - ``no_repeats`` -- boolean (optional, default: ``False``), if ``True``, only return partitions with no repeated parts - OUTPUT: list of lists + OUTPUT: iterator of lists One could also use ``Partitions(n, parts_in=l)``, but this function may be faster. Also, while ``Partitions(n, parts_in=l, max_slope=-1)`` should in theory return the partitions of `n` with parts in ``l`` with no repetitions, the ``max_slope=-1`` argument - is ignored, so it doesn't work. (At the moment, the + is ignored, so it does not work. (At the moment, the ``no_repeats=True`` case is the only one used in the code.) EXAMPLES:: sage: from sage.algebras.steenrod.steenrod_algebra_bases import restricted_partitions - sage: restricted_partitions(10, [7,5,1]) + sage: list(restricted_partitions(10, [7,5,1])) [[7, 1, 1, 1], [5, 5], [5, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]] - sage: restricted_partitions(10, [6,5,4,3,2,1], no_repeats=True) + sage: list(restricted_partitions(10, [6,5,4,3,2,1], no_repeats=True)) [[6, 4], [6, 3, 1], [5, 4, 1], [5, 3, 2], [4, 3, 2, 1]] - sage: restricted_partitions(10, [6,4,2]) + sage: list(restricted_partitions(10, [6,4,2])) [[6, 4], [6, 2, 2], [4, 4, 2], [4, 2, 2, 2], [2, 2, 2, 2, 2]] - sage: restricted_partitions(10, [6,4,2], no_repeats=True) + sage: list(restricted_partitions(10, [6,4,2], no_repeats=True)) [[6, 4]] ``l`` may have repeated elements. If ``no_repeats`` is ``False``, this @@ -413,9 +416,9 @@ def restricted_partitions(n, l, no_repeats=False): elements appear consecutively in ``l``, then each element may be used only as many times as it appears in ``l``:: - sage: restricted_partitions(10, [6,4,2,2], no_repeats=True) + sage: list(restricted_partitions(10, [6,4,2,2], no_repeats=True)) [[6, 4], [6, 2, 2]] - sage: restricted_partitions(10, [6,4,2,2,2], no_repeats=True) + sage: list(restricted_partitions(10, [6,4,2,2,2], no_repeats=True)) [[6, 4], [6, 2, 2], [4, 2, 2, 2]] (If the repeated elements don't appear consecutively, the results @@ -424,31 +427,29 @@ def restricted_partitions(n, l, no_repeats=False): In the following examples, ``no_repeats`` is ``False``:: - sage: restricted_partitions(10, [6,4,2]) + sage: list(restricted_partitions(10, [6,4,2])) [[6, 4], [6, 2, 2], [4, 4, 2], [4, 2, 2, 2], [2, 2, 2, 2, 2]] - sage: restricted_partitions(10, [6,4,2,2,2]) + sage: list(restricted_partitions(10, [6,4,2,2,2])) [[6, 4], [6, 2, 2], [4, 4, 2], [4, 2, 2, 2], [2, 2, 2, 2, 2]] - sage: restricted_partitions(10, [6,4,4,4,2,2,2,2,2,2]) + sage: list(restricted_partitions(10, [6,4,4,4,2,2,2,2,2,2])) [[6, 4], [6, 2, 2], [4, 4, 2], [4, 2, 2, 2], [2, 2, 2, 2, 2]] """ if n < 0: - return [] - elif n == 0: - return [[]] - else: - results = [] - if no_repeats: - index = 1 - else: - index = 0 - old_i = 0 - for i in l: - if old_i != i: - for sigma in restricted_partitions(n-i, l[index:], no_repeats): - results.append([i] + sigma) - index += 1 - old_i = i - return results + return + + if n == 0: + yield [] + return + + index = 1 if no_repeats else 0 + old_i = 0 + for i in l: + if old_i != i: + for sigma in restricted_partitions(n - i, l[index:], no_repeats): + yield [i] + sigma + index += 1 + old_i = i + def xi_degrees(n, p=2, reverse=True): r""" @@ -491,6 +492,7 @@ def xi_degrees(n, p=2, reverse=True): l.reverse() return l + ######################################################## # Functions for defining bases. @@ -665,6 +667,7 @@ def milnor_basis(n, p=2, **kwds): result.append((tuple(q_mono), tuple(p_mono))) return tuple(result) + def serre_cartan_basis(n, p=2, bound=1, **kwds): r""" Serre-Cartan basis in dimension `n`. @@ -746,6 +749,7 @@ def serre_cartan_basis(n, p=2, bound=1, **kwds): result.append(new) return tuple(result) + def atomic_basis(n, basis, **kwds): r""" Basis for dimension `n` made of elements in 'atomic' degrees: @@ -927,8 +931,9 @@ def sorting_pair(s,t,basis): # pair used for sorting the basis result.append(tuple(big_list)) return tuple(result) + @cached_function -def arnonC_basis(n,bound=1): +def arnonC_basis(n, bound=1): r""" Arnon's C basis in dimension `n`. @@ -959,18 +964,20 @@ def arnonC_basis(n,bound=1): """ if n == 0: return ((),) - else: - # Build basis recursively. first = first term. - # first is >= bound, and we will prepend (first,) to the - # elements from arnonC_basis (n - first, first / 2). - # first also must be divisible by 2**(len(old-basis-elt)) - # This means that 3 first <= 2 n. - result = [(n,)] - for first in range(bound, 1+2*n//3): - for vec in arnonC_basis(n - first, max(first//2,1)): - if first % 2**len(vec) == 0: - result.append((first,) + vec) - return tuple(result) + + # Build basis recursively. first = first term. + # first is >= bound, and we will prepend (first,) to the + # elements from arnonC_basis (n - first, first / 2). + # first also must be divisible by 2**(len(old-basis-elt)) + # This means that 3 first <= 2 n. + result = [(n,)] + for first in range(bound, 1 + 2 * n // 3): + tup = (first,) + result.extend(tup + vec + for vec in arnonC_basis(n - first, max(first // 2, 1)) + if not first % 2**len(vec)) + return tuple(result) + def atomic_basis_odd(n, basis, p, **kwds): r""" @@ -1018,15 +1025,15 @@ def atomic_basis_odd(n, basis, p, **kwds): sage: atomic_basis_odd(18, 'pst_rlex', 3, profile=((), (2,2,2))) (((0, 2), ()),) """ - def sorting_pair(s,t,basis): # pair used for sorting the basis + def sorting_pair(s, t, basis): # pair used for sorting the basis if basis.find('rlex') >= 0: - return (t,s) + return (t, s) elif basis.find('llex') >= 0: - return (s,t) + return (s, t) elif basis.find('deg') >= 0: - return (s+t,t) + return (s + t, t) elif basis.find('revz') >= 0: - return (s+t,s) + return (s + t, s) generic = kwds.get('generic', p != 2) if n == 0: From fed224d8686417c1f5312d9010f30855ad3d2c62 Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Mon, 11 Mar 2024 22:43:20 +0900 Subject: [PATCH 222/518] Do not canonicalize input when only given a gap group. --- src/sage/groups/perm_gps/permgroup.py | 74 ++++++++++++--------- src/sage/groups/perm_gps/permgroup_named.py | 20 ++++-- 2 files changed, 58 insertions(+), 36 deletions(-) diff --git a/src/sage/groups/perm_gps/permgroup.py b/src/sage/groups/perm_gps/permgroup.py index 208758282b2..4f0cadd4768 100644 --- a/src/sage/groups/perm_gps/permgroup.py +++ b/src/sage/groups/perm_gps/permgroup.py @@ -478,6 +478,18 @@ def __init__(self, gens=None, gap_group=None, canonicalize=True, sage: TestSuite(PermutationGroup([[]])).run() sage: TestSuite(PermutationGroup([])).run() sage: TestSuite(PermutationGroup([(0,1)])).run() + + Check that :issue:`37590` is fixed:: + + sage: lgg = libgap.eval("Group((1,2,3,4,5),(4,5,6))") + sage: P = PermutationGroup(gap_group=lgg) + sage: h = P.hom(codomain=P, im_gens=P.gens()) + sage: h + Group endomorphism of Permutation Group with generators [(1,2,3,4,5), (4,5,6)] + sage: P.gens() + ((1,2,3,4,5), (4,5,6)) + sage: P.gap().GeneratorsOfGroup() + [ (1,2,3,4,5), (4,5,6) ] """ if (gens is None and gap_group is None): raise ValueError("you must specify gens or gap_group") @@ -496,6 +508,7 @@ def __init__(self, gens=None, gap_group=None, canonicalize=True, if isinstance(gap_group, LibGapElement): self._libgap = gap_group + canonicalize = False # Handle the case where only the GAP group is specified. if gens is None: @@ -1828,7 +1841,7 @@ def stabilizer(self, point, action="OnPoints"): sage: G = PermutationGroup([[(1,2),(3,4)], [(1,2,3,4,10)]]) sage: G.stabilizer(10) - Subgroup generated by [(2,3,4), (1,2)(3,4)] of (Permutation Group with generators [(1,2)(3,4), (1,2,3,4,10)]) + Subgroup generated by [(1,2)(3,4), (2,3,4)] of (Permutation Group with generators [(1,2)(3,4), (1,2,3,4,10)]) sage: G.stabilizer(1) == G.subgroup(['(2,3)(4,10)', '(2,10,3)']) True sage: G = PermutationGroup([[(2,3,4)],[(6,7)]]) @@ -2430,7 +2443,6 @@ def fitting_subgroup(self): sage: G.fitting_subgroup() Subgroup generated by [(1,2)(3,4), (1,3)(2,4)] of (Permutation Group with generators [(1,2), (1,2,3,4)]) - """ return self.subgroup(gap_group=self._libgap_().FittingSubgroup()) @@ -2445,7 +2457,7 @@ def solvable_radical(self): sage: G = SymmetricGroup(4) sage: G.solvable_radical() - Subgroup generated by [(1,2), (1,2,3,4)] of + Subgroup generated by [(1,2,3,4), (1,2)] of (Symmetric group of order 4! as a permutation group) sage: G = SymmetricGroup(5) sage: G.solvable_radical() @@ -2581,7 +2593,7 @@ def conjugate(self, g): sage: G = DihedralGroup(6) sage: a = PermutationGroupElement("(1,2,3,4)") sage: G.conjugate(a) - Permutation Group with generators [(1,4)(2,6)(3,5), (1,5,6,2,3,4)] + Permutation Group with generators [(1,5,6,2,3,4), (1,4)(2,6)(3,5)] The element performing the conjugation can be specified in several ways. :: @@ -2589,15 +2601,15 @@ def conjugate(self, g): sage: G = DihedralGroup(6) sage: strng = "(1,2,3,4)" sage: G.conjugate(strng) - Permutation Group with generators [(1,4)(2,6)(3,5), (1,5,6,2,3,4)] + Permutation Group with generators [(1,5,6,2,3,4), (1,4)(2,6)(3,5)] sage: G = DihedralGroup(6) sage: lst = [2,3,4,1] sage: G.conjugate(lst) - Permutation Group with generators [(1,4)(2,6)(3,5), (1,5,6,2,3,4)] + Permutation Group with generators [(1,5,6,2,3,4), (1,4)(2,6)(3,5)] sage: G = DihedralGroup(6) sage: cycles = [(1,2,3,4)] sage: G.conjugate(cycles) - Permutation Group with generators [(1,4)(2,6)(3,5), (1,5,6,2,3,4)] + Permutation Group with generators [(1,5,6,2,3,4), (1,4)(2,6)(3,5)] Conjugation is a group automorphism, so conjugate groups will be isomorphic. :: @@ -2671,24 +2683,24 @@ def direct_product(self, other, maps=True): sage: G = CyclicPermutationGroup(4) sage: D = G.direct_product(G, False); D - Permutation Group with generators [(5,6,7,8), (1,2,3,4)] + Permutation Group with generators [(1,2,3,4), (5,6,7,8)] sage: D,iota1,iota2,pr1,pr2 = G.direct_product(G) sage: D; iota1; iota2; pr1; pr2 - Permutation Group with generators [(5,6,7,8), (1,2,3,4)] + Permutation Group with generators [(1,2,3,4), (5,6,7,8)] Permutation group morphism: From: Cyclic group of order 4 as a permutation group - To: Permutation Group with generators [(5,6,7,8), (1,2,3,4)] + To: Permutation Group with generators [(1,2,3,4), (5,6,7,8)] Defn: Embedding( Group( [ (1,2,3,4), (5,6,7,8) ] ), 1 ) Permutation group morphism: From: Cyclic group of order 4 as a permutation group - To: Permutation Group with generators [(5,6,7,8), (1,2,3,4)] + To: Permutation Group with generators [(1,2,3,4), (5,6,7,8)] Defn: Embedding( Group( [ (1,2,3,4), (5,6,7,8) ] ), 2 ) Permutation group morphism: - From: Permutation Group with generators [(5,6,7,8), (1,2,3,4)] + From: Permutation Group with generators [(1,2,3,4), (5,6,7,8)] To: Cyclic group of order 4 as a permutation group Defn: Projection( Group( [ (1,2,3,4), (5,6,7,8) ] ), 1 ) Permutation group morphism: - From: Permutation Group with generators [(5,6,7,8), (1,2,3,4)] + From: Permutation Group with generators [(1,2,3,4), (5,6,7,8)] To: Cyclic group of order 4 as a permutation group Defn: Projection( Group( [ (1,2,3,4), (5,6,7,8) ] ), 2 ) sage: g = D([(1,3),(2,4)]); g @@ -2934,7 +2946,7 @@ def holomorph(self): sage: D4 = DihedralGroup(4) sage: H = D4.holomorph() sage: H.gens() - ((3,8)(4,7), (2,3,5,8), (2,5)(3,8), (1,4,6,7)(2,3,5,8), (1,8)(2,7)(3,6)(4,5)) + ((2,3,5,8), (2,5)(3,8), (3,8)(4,7), (1,4,6,7)(2,3,5,8), (1,8)(2,7)(3,6)(4,5)) sage: G = H.subgroup([H.gens()[0],H.gens()[1],H.gens()[2]]) sage: N = H.subgroup([H.gens()[3],H.gens()[4]]) sage: N.is_normal(H) @@ -2954,7 +2966,6 @@ def holomorph(self): - Kevin Halasz (2012-08-14) """ - libgap.eval('G := Group({})'.format(list(self.gens()))) libgap.eval('aut := AutomorphismGroup(G)') libgap.eval('alpha := InverseGeneralMapping(NiceMonomorphism(aut))') @@ -3137,7 +3148,7 @@ def quotient(self, N, **kwds): sage: G.quotient(N) Permutation Group with generators [(1,2)] sage: G.quotient(G) - Permutation Group with generators [()] + Permutation Group with generators [(), ()] """ Q = self._libgap_() / N._libgap_() # Return Q as a permutation group @@ -3599,13 +3610,13 @@ def conjugacy_classes_subgroups(self): (Permutation Group with generators [(1,2)(3,4), (1,2,3,4)]), Subgroup generated by [(2,4)] of (Permutation Group with generators [(1,2)(3,4), (1,2,3,4)]), - Subgroup generated by [(1,2)(3,4), (1,4)(2,3)] of + Subgroup generated by [(1,4)(2,3), (1,2)(3,4)] of (Permutation Group with generators [(1,2)(3,4), (1,2,3,4)]), Subgroup generated by [(2,4), (1,3)(2,4)] of (Permutation Group with generators [(1,2)(3,4), (1,2,3,4)]), Subgroup generated by [(1,2,3,4), (1,3)(2,4)] of (Permutation Group with generators [(1,2)(3,4), (1,2,3,4)]), - Subgroup generated by [(2,4), (1,2)(3,4), (1,4)(2,3)] of + Subgroup generated by [(1,4)(2,3), (1,2)(3,4), (2,4)] of (Permutation Group with generators [(1,2)(3,4), (1,2,3,4)])] :: @@ -3618,7 +3629,7 @@ def conjugacy_classes_subgroups(self): (Symmetric group of order 3! as a permutation group), Subgroup generated by [(1,2,3)] of (Symmetric group of order 3! as a permutation group), - Subgroup generated by [(2,3), (1,2,3)] of + Subgroup generated by [(1,2,3), (2,3)] of (Symmetric group of order 3! as a permutation group)] AUTHORS: @@ -3640,7 +3651,7 @@ def subgroups(self): with one element on through up to the whole group. Conjugacy classes of subgroups are contiguous in the list. - .. warning:: + .. WARNING:: For even relatively small groups this method can take a very long time to execute, or create vast @@ -3671,7 +3682,7 @@ def subgroups(self): (Symmetric group of order 3! as a permutation group), Subgroup generated by [(1,2,3)] of (Symmetric group of order 3! as a permutation group), - Subgroup generated by [(2,3), (1,2,3)] of + Subgroup generated by [(1,2,3), (2,3)] of (Symmetric group of order 3! as a permutation group)] sage: G = CyclicPermutationGroup(14) @@ -3682,8 +3693,8 @@ def subgroups(self): (Cyclic group of order 14 as a permutation group), Subgroup generated by [(1,3,5,7,9,11,13)(2,4,6,8,10,12,14)] of (Cyclic group of order 14 as a permutation group), - Subgroup generated by [(1,2,3,4,5,6,7,8,9,10,11,12,13,14), - (1,3,5,7,9,11,13)(2,4,6,8,10,12,14)] of + Subgroup generated by [(1,3,5,7,9,11,13)(2,4,6,8,10,12,14), + (1,2,3,4,5,6,7,8,9,10,11,12,13,14)] of (Cyclic group of order 14 as a permutation group)] AUTHOR: @@ -4049,15 +4060,15 @@ def normalizer(self, g): sage: G = PermutationGroup([[(1,2),(3,4)], [(1,2,3,4)]]) sage: g = G([(1,3)]) sage: G.normalizer(g) - Subgroup generated by [(2,4), (1,3)] of + Subgroup generated by [(1,3), (2,4)] of (Permutation Group with generators [(1,2)(3,4), (1,2,3,4)]) sage: g = G([(1,2,3,4)]) sage: G.normalizer(g) - Subgroup generated by [(2,4), (1,2,3,4), (1,3)(2,4)] of + Subgroup generated by [(1,2,3,4), (1,3)(2,4), (2,4)] of (Permutation Group with generators [(1,2)(3,4), (1,2,3,4)]) sage: H = G.subgroup([G([(1,2,3,4)])]) sage: G.normalizer(H) - Subgroup generated by [(2,4), (1,2,3,4), (1,3)(2,4)] of + Subgroup generated by [(1,2,3,4), (1,3)(2,4), (2,4)] of (Permutation Group with generators [(1,2)(3,4), (1,2,3,4)]) """ return self.subgroup(gap_group=self._libgap_().Normalizer(g)) @@ -4071,7 +4082,7 @@ def centralizer(self, g): sage: G = PermutationGroup([[(1,2),(3,4)], [(1,2,3,4)]]) sage: g = G([(1,3)]) sage: G.centralizer(g) - Subgroup generated by [(2,4), (1,3)] of + Subgroup generated by [(1,3), (2,4)] of (Permutation Group with generators [(1,2)(3,4), (1,2,3,4)]) sage: g = G([(1,2,3,4)]) sage: G.centralizer(g) @@ -4715,7 +4726,7 @@ def composition_series(self): sage: G.composition_series() [Subgroup generated by [(3,4), (1,2,3)(4,5)] of (Permutation Group with generators [(3,4), (1,2,3)(4,5)]), - Subgroup generated by [(1,3,5), (1,5)(3,4), (1,5)(2,4)] of + Subgroup generated by [(1,5)(3,4), (1,5)(2,4), (1,3,5)] of (Permutation Group with generators [(3,4), (1,2,3)(4,5)]), Subgroup generated by [()] of (Permutation Group with generators [(3,4), (1,2,3)(4,5)])] @@ -4746,7 +4757,7 @@ def derived_series(self): sage: G.derived_series() [Subgroup generated by [(3,4), (1,2,3)(4,5)] of (Permutation Group with generators [(3,4), (1,2,3)(4,5)]), - Subgroup generated by [(1,3,5), (1,5)(3,4), (1,5)(2,4)] of + Subgroup generated by [(1,5)(3,4), (1,5)(2,4), (1,3,5)] of (Permutation Group with generators [(3,4), (1,2,3)(4,5)])] """ libgap.set_seed() @@ -4770,7 +4781,7 @@ def lower_central_series(self): sage: G.lower_central_series() [Subgroup generated by [(3,4), (1,2,3)(4,5)] of (Permutation Group with generators [(3,4), (1,2,3)(4,5)]), - Subgroup generated by [(1,3,5), (1,5)(3,4), (1,5)(2,4)] of + Subgroup generated by [(1,5)(3,4), (1,5)(2,4), (1,3,5)] of (Permutation Group with generators [(3,4), (1,2,3)(4,5)])] """ libgap.set_seed() @@ -5123,7 +5134,8 @@ def __richcmp__(self, other, op): sage: G Symmetric group of order 6! as a permutation group sage: G3 - Subgroup generated by [(1,2), (1,2,3,4,5,6)] of (Symmetric group of order 6! as a permutation group) + Subgroup generated by [(1,2,3,4,5,6), (1,2)] of + (Symmetric group of order 6! as a permutation group) sage: G is G3 False sage: G == G3 # as permutation groups diff --git a/src/sage/groups/perm_gps/permgroup_named.py b/src/sage/groups/perm_gps/permgroup_named.py index 222ee36f6da..6b42e654234 100644 --- a/src/sage/groups/perm_gps/permgroup_named.py +++ b/src/sage/groups/perm_gps/permgroup_named.py @@ -449,7 +449,7 @@ def young_subgroup(self, comp): sage: S = SymmetricGroup(8) sage: c = Composition([2,2,2,2]) sage: S.young_subgroup(c) - Subgroup generated by [(7,8), (5,6), (3,4), (1,2)] of + Subgroup generated by [(1,2), (3,4), (5,6), (7,8)] of (Symmetric group of order 8! as a permutation group) sage: S = SymmetricGroup(['a','b','c']) @@ -706,6 +706,16 @@ def __init__(self, domain=None): sage: groups.permutation.Alternating(6) Alternating group of order 6!/2 as a permutation group + + Check that alternating groups have their generators in the correct + order (:issue:`37590`):: + + sage: A = AlternatingGroup(6) + sage: A.gens() + ((1,2,3,4,5), (4,5,6)) + sage: A.gap().GeneratorsOfGroup() + [ (1,2,3,4,5), (4,5,6) ] + sage: h = A.hom(codomain=A, im_gens=A.gens()) """ PermutationGroup_symalt.__init__(self, gap_group='AlternatingGroup(%s)' % len(domain), domain=domain) @@ -3084,8 +3094,8 @@ def __init__(self, q, name='a'): EXAMPLES:: sage: SuzukiGroup(8) # needs sage.rings.finite_rings - Permutation Group with generators [(1,2)(3,10)(4,42)(5,18)(6,50)(7,26)(8,58)(9,34)(12,28)(13,45)(14,44)(15,23)(16,31)(17,21)(19,39)(20,38)(22,25)(24,61)(27,60)(29,65)(30,55)(32,33)(35,52)(36,49)(37,59)(40,54)(41,62)(43,53)(46,48)(47,56)(51,63)(57,64), - (1,28,10,44)(3,50,11,42)(4,43,53,64)(5,9,39,52)(6,36,63,13)(7,51,60,57)(8,33,37,16)(12,24,55,29)(14,30,48,47)(15,19,61,54)(17,59,22,62)(18,23,34,31)(20,38,49,25)(21,26,45,58)(27,32,41,65)(35,46,40,56)] + Permutation Group with generators [(1,28,10,44)(3,50,11,42)(4,43,53,64)(5,9,39,52)(6,36,63,13)(7,51,60,57)(8,33,37,16)(12,24,55,29)(14,30,48,47)(15,19,61,54)(17,59,22,62)(18,23,34,31)(20,38,49,25)(21,26,45,58)(27,32,41,65)(35,46,40,56), + (1,2)(3,10)(4,42)(5,18)(6,50)(7,26)(8,58)(9,34)(12,28)(13,45)(14,44)(15,23)(16,31)(17,21)(19,39)(20,38)(22,25)(24,61)(27,60)(29,65)(30,55)(32,33)(35,52)(36,49)(37,59)(40,54)(41,62)(43,53)(46,48)(47,56)(51,63)(57,64)] sage: print(SuzukiGroup(8)) # needs sage.rings.finite_rings The Suzuki group over Finite Field in a of size 2^3 @@ -3101,8 +3111,8 @@ def __init__(self, q, name='a'): TESTS:: sage: groups.permutation.Suzuki(8) # needs sage.rings.finite_rings - Permutation Group with generators [(1,2)(3,10)(4,42)(5,18)(6,50)(7,26)(8,58)(9,34)(12,28)(13,45)(14,44)(15,23)(16,31)(17,21)(19,39)(20,38)(22,25)(24,61)(27,60)(29,65)(30,55)(32,33)(35,52)(36,49)(37,59)(40,54)(41,62)(43,53)(46,48)(47,56)(51,63)(57,64), - (1,28,10,44)(3,50,11,42)(4,43,53,64)(5,9,39,52)(6,36,63,13)(7,51,60,57)(8,33,37,16)(12,24,55,29)(14,30,48,47)(15,19,61,54)(17,59,22,62)(18,23,34,31)(20,38,49,25)(21,26,45,58)(27,32,41,65)(35,46,40,56)] + Permutation Group with generators [(1,28,10,44)(3,50,11,42)(4,43,53,64)(5,9,39,52)(6,36,63,13)(7,51,60,57)(8,33,37,16)(12,24,55,29)(14,30,48,47)(15,19,61,54)(17,59,22,62)(18,23,34,31)(20,38,49,25)(21,26,45,58)(27,32,41,65)(35,46,40,56), + (1,2)(3,10)(4,42)(5,18)(6,50)(7,26)(8,58)(9,34)(12,28)(13,45)(14,44)(15,23)(16,31)(17,21)(19,39)(20,38)(22,25)(24,61)(27,60)(29,65)(30,55)(32,33)(35,52)(36,49)(37,59)(40,54)(41,62)(43,53)(46,48)(47,56)(51,63)(57,64)] REFERENCES: From c7580bea6e371ae9d35581042dd1a1f7d6761efd Mon Sep 17 00:00:00 2001 From: "John H. Palmieri" Date: Mon, 11 Mar 2024 10:43:18 -0700 Subject: [PATCH 223/518] pr #37501: a few capitalizations --- CODE_OF_CONDUCT.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index c66c623169b..5e29250d6b2 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -58,20 +58,20 @@ roles and procedures for the Sage Code of Conduct Committee. 8. Repeated harassment of others. In general, if someone asks you to stop, then stop. 9. Advocating for, or encouraging, any of the above behavior. -This code of conduct applies to all spaces managed by the Sage +This Code of Conduct applies to all spaces managed by the Sage project, including all public and private mailing lists, issue trackers, wikis, and any other communication channel used by our community. It also applies to Sage Days and any other in-person or virtual events. -This code of conduct should be honored by everyone who participates in +This Code of Conduct should be honored by everyone who participates in the Sage community formally or informally, or claims any affiliation with the project, in any project-related activities, and, especially, when representing the project, in any role. -This code is neither exhaustive nor complete. It serves to distill our +This Code is neither exhaustive nor complete. It serves to distill our common understanding of a collaborative, shared environment and -goals. Please try to follow this code in spirit as much as in letter, +goals. Please try to follow this Code in spirit as much as in letter, to create a friendly and productive environment that enriches the surrounding community. From aaf6208695168d9f5cfae848f0396ad2d2156a3a Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Mon, 11 Mar 2024 18:13:17 +0000 Subject: [PATCH 224/518] Simplify computation of rational points for curves over finite fields --- .../elliptic_curves/ell_finite_field.py | 83 ++++++++++--------- 1 file changed, 43 insertions(+), 40 deletions(-) diff --git a/src/sage/schemes/elliptic_curves/ell_finite_field.py b/src/sage/schemes/elliptic_curves/ell_finite_field.py index b751543e0f1..3f5d0979f41 100644 --- a/src/sage/schemes/elliptic_curves/ell_finite_field.py +++ b/src/sage/schemes/elliptic_curves/ell_finite_field.py @@ -40,7 +40,6 @@ from sage.rings.polynomial.polynomial_ring import polygen from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.schemes.curves.projective_curve import Hasse_bounds -from sage.schemes.hyperelliptic_curves.hyperelliptic_finite_field import HyperellipticCurve_finite_field from sage.structure.element import Element from . import ell_point @@ -48,7 +47,7 @@ from .ell_field import EllipticCurve_field -class EllipticCurve_finite_field(EllipticCurve_field, HyperellipticCurve_finite_field): +class EllipticCurve_finite_field(EllipticCurve_field): r""" Elliptic curve over a finite field. @@ -144,29 +143,45 @@ def _points_via_group_structure(self): [(0 : 1 : 0), (0 : a : 1), (0 : a + 1 : 1), (1 : 0 : 1), (1 : 1 : 1), (a : 0 : 1), (a : 1 : 1), (a + 1 : 0 : 1), (a + 1 : 1 : 1)] """ # TODO, eliminate when polynomial calling is fast - G = self.abelian_group() - pts = [x.element() for x in G.gens()] - - ni = G.generator_orders() - ngens = G.ngens() + # 11-03-2024 - G. Pope : it is not clear to me what the above TODO references - H0 = [self(0)] - if ngens == 0: # trivial group - return H0 - for m in range(1,ni[0]): - H0.append(H0[-1]+pts[0]) - if ngens == 1: # cyclic group - return H0 - - # else noncyclic group - H1 = [self(0)] - for m in range(1,ni[1]): - H1.append(H1[-1]+pts[1]) - return [P+Q for P in H0 for Q in H1] + # Compute the generators of the abelian group of the curve + G = self.abelian_group() + gens = [x.element() for x in G.gens()] + + # Zero element of the group + zero = self(0) + + # Trivial group, return the identity element only + if len(gens) == 0: + return [zero] + + def __multiples(G): + """ + Compute the list of points [i]G for i in [0, G.order()) + """ + H = [zero, G] + P = G + for _ in range(2, G.order()): + P += G + H.append(P) + return H + + # Collect all multiples of the generator + H1 = __multiples(gens[0]) + + # Cyclic case, we now have all points + if len(gens) == 1: + return H1 + + # Non-cyclic case we generate the second set of points and compute + # the entire set of points from the Cartesian product + H2 = __multiples(gens[1]) + return [P + Q for P in H1 for Q in H2] def points(self): r""" - All the points on this elliptic curve. The list of points is cached + Return all rational points on this elliptic curve. The list of points is cached so subsequent calls are free. EXAMPLES:: @@ -174,48 +189,36 @@ def points(self): sage: p = 5 sage: F = GF(p) sage: E = EllipticCurve(F, [1, 3]) - sage: a_sub_p = E.change_ring(QQ).ap(p); a_sub_p - 2 - - :: - sage: len(E.points()) 4 - sage: p + 1 - a_sub_p + sage: E.order() 4 sage: E.points() [(0 : 1 : 0), (1 : 0 : 1), (4 : 1 : 1), (4 : 4 : 1)] :: - sage: # needs sage.rings.finite_rings - sage: K = GF((p, 2),'a') + sage: K = GF((p, 2), 'a') sage: E = E.change_ring(K) sage: len(E.points()) 32 - sage: (p + 1)**2 - a_sub_p**2 + sage: E.order() 32 sage: w = E.points(); w [(0 : 1 : 0), (0 : 2*a + 4 : 1), (0 : 3*a + 1 : 1), (1 : 0 : 1), (2 : 2*a + 4 : 1), (2 : 3*a + 1 : 1), (3 : 2*a + 4 : 1), (3 : 3*a + 1 : 1), (4 : 1 : 1), (4 : 4 : 1), (a : 1 : 1), (a : 4 : 1), (a + 2 : a + 1 : 1), (a + 2 : 4*a + 4 : 1), (a + 3 : a : 1), (a + 3 : 4*a : 1), (a + 4 : 0 : 1), (2*a : 2*a : 1), (2*a : 3*a : 1), (2*a + 4 : a + 1 : 1), (2*a + 4 : 4*a + 4 : 1), (3*a + 1 : a + 3 : 1), (3*a + 1 : 4*a + 2 : 1), (3*a + 2 : 2*a + 3 : 1), (3*a + 2 : 3*a + 2 : 1), (4*a : 0 : 1), (4*a + 1 : 1 : 1), (4*a + 1 : 4 : 1), (4*a + 3 : a + 3 : 1), (4*a + 3 : 4*a + 2 : 1), (4*a + 4 : a + 4 : 1), (4*a + 4 : 4*a + 1 : 1)] Note that the returned list is an immutable sorted Sequence:: - sage: w[0] = 9 # needs sage.rings.finite_rings + sage: w[0] = 9 Traceback (most recent call last): ... ValueError: object is immutable; please change a copy instead. """ - try: + if hasattr(self, "__points"): return self.__points - except AttributeError: - pass from sage.structure.sequence import Sequence - k = self.base_ring() - if k.is_prime_field() and k.order() > 50: - v = self._points_via_group_structure() - else: - v = self._points_fast_sqrt() + v = self._points_via_group_structure() v.sort() self.__points = Sequence(v, immutable=True) return self.__points @@ -1641,7 +1644,7 @@ def endomorphism_discriminant_from_class_number(self, h): raise ValueError("Incorrect class number {}".format(h)) if len(cs) == 1: return (v//cs[0])**2 * D0 - from sage.sets.set import Set + L = sorted(set(sum([c.prime_factors() for c in cs], []))) for ell in L: e = self.height_above_floor(ell,v.valuation(ell)) From a679dcc62f736812d0f6b2a1b1cc9db498a66a56 Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Mon, 11 Mar 2024 18:48:56 +0000 Subject: [PATCH 225/518] Update docstring --- .../schemes/elliptic_curves/ell_finite_field.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/sage/schemes/elliptic_curves/ell_finite_field.py b/src/sage/schemes/elliptic_curves/ell_finite_field.py index 3f5d0979f41..fcc356fe4ad 100644 --- a/src/sage/schemes/elliptic_curves/ell_finite_field.py +++ b/src/sage/schemes/elliptic_curves/ell_finite_field.py @@ -112,10 +112,19 @@ def plot(self, *args, **kwds): return points([P[0:2] for P in self.points() if not P.is_zero()], *args, **kwds) def _points_via_group_structure(self): - """ - Return a list of all the points on the curve, for prime fields only. - - See points() for the general case. + r""" + Return a list of all the points on the curve using the group structure. + + For cyclic groups with generator `G` of order `n`, the set of points + is computed from `[x]G` for `x \in [0, n - 1]` requiring `n - 2` + additions on the curve. + + For non-cyclic groups, first two cyclic subgroups `H_i` are computed as + above from `[x]G_i` for `x \in [0, n_i]` requiring `n_1 + n_2 - 4` + additions. The set of all points is returned as the cartesian product + of these two cyclic groups. + + When the group is trivial, only the point at infinity is returned. EXAMPLES:: From 2161774d2b1d1f524610ccad8388cd36eb731e96 Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Mon, 11 Mar 2024 19:08:11 +0000 Subject: [PATCH 226/518] linter --- src/sage/schemes/elliptic_curves/ell_finite_field.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/schemes/elliptic_curves/ell_finite_field.py b/src/sage/schemes/elliptic_curves/ell_finite_field.py index fcc356fe4ad..6d97f3094b4 100644 --- a/src/sage/schemes/elliptic_curves/ell_finite_field.py +++ b/src/sage/schemes/elliptic_curves/ell_finite_field.py @@ -114,7 +114,7 @@ def plot(self, *args, **kwds): def _points_via_group_structure(self): r""" Return a list of all the points on the curve using the group structure. - + For cyclic groups with generator `G` of order `n`, the set of points is computed from `[x]G` for `x \in [0, n - 1]` requiring `n - 2` additions on the curve. From 8b811475bb0a4bcb9cd03ef9344ba0ef9a4cae3b Mon Sep 17 00:00:00 2001 From: Lorenz Panny Date: Mon, 11 Mar 2024 20:12:08 +0100 Subject: [PATCH 227/518] =?UTF-8?q?some=20R(1)=20=E2=86=92=20R.one(),=20et?= =?UTF-8?q?c.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/sage/schemes/elliptic_curves/ell_point.py | 4 ++-- src/sage/schemes/projective/projective_point.py | 15 ++++++++------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/sage/schemes/elliptic_curves/ell_point.py b/src/sage/schemes/elliptic_curves/ell_point.py index 35e6644db3a..dc6706dddaa 100644 --- a/src/sage/schemes/elliptic_curves/ell_point.py +++ b/src/sage/schemes/elliptic_curves/ell_point.py @@ -794,7 +794,7 @@ def _add_(self, right): x3 = -x1 - x2 - a2 + m*(m+a1) y3 = -y1 - a3 - a1*x3 + m*(x1-x3) # See trac #4820 for why we need to coerce 1 into the base ring here: - return E.point([x3, y3, E.base_ring()(1)], check=False) + return E.point([x3, y3, E.base_ring().one()], check=False) def _sub_(self, right): """ @@ -839,7 +839,7 @@ def __neg__(self): return self E, x, y = self.curve(), self[0], self[1] # See trac #4820 for why we need to coerce 1 into the base ring here: - return E.point([x, -y - E.a1()*x - E.a3(), E.base_ring()(1)], check=False) + return E.point([x, -y - E.a1()*x - E.a3(), E.base_ring().one()], check=False) def xy(self): """ diff --git a/src/sage/schemes/projective/projective_point.py b/src/sage/schemes/projective/projective_point.py index 7fb61b37e1a..f04a5bc0e3a 100644 --- a/src/sage/schemes/projective/projective_point.py +++ b/src/sage/schemes/projective/projective_point.py @@ -35,6 +35,7 @@ from sage.categories.integral_domains import IntegralDomains from sage.categories.number_fields import NumberFields _NumberFields = NumberFields() +from sage.rings.integer_ring import ZZ from sage.rings.fraction_field import FractionField from sage.rings.number_field.order import is_NumberFieldOrder, Order as NumberFieldOrder from sage.rings.qqbar import number_field_elements_from_algebraics @@ -183,7 +184,7 @@ def __init__(self, X, v, check=True): R = X.value_ring() v = Sequence(v, R) if len(v) == d-1: # very common special case - v.append(R(1)) + v.append(R.one()) if R in IntegralDomains(): # Over integral domains, any tuple with at least one @@ -524,7 +525,7 @@ def scale_by(self, t): sage: Q.scale_by(1/2);Q (1 : 1 : 1) """ - if t == 0: #what if R(t) == 0 ? + if t.is_zero(): #what if R(t) == 0 ? raise ValueError("Cannot scale by 0") R = self.codomain().base_ring() if isinstance(R, QuotientRing_generic): @@ -620,14 +621,14 @@ def normalize_coordinates(self): GCD = R(gcd(self._coords[0], self._coords[1])) index = 2 neg = self._coords[0] <= 0 and self._coords[1] <= 0 - while GCD != 1 and index < len(self._coords): + while not GCD.is_one() and index < len(self._coords): neg = self._coords[index] <= 0 GCD = R(gcd(GCD, self._coords[index])) index += 1 - if GCD != 1: + if not GCD.is_one(): self.scale_by(~GCD) if neg: - self.scale_by(-1) + self.scale_by(-ZZ.one()) self._normalized = True def dehomogenize(self,n): @@ -676,7 +677,7 @@ def dehomogenize(self,n): ... ValueError: can...t dehomogenize at 0 coordinate """ - if self[n] == 0: + if self[n].is_zero(): raise ValueError("can't dehomogenize at 0 coordinate") PS = self.codomain() A = PS.affine_patch(n) @@ -1159,7 +1160,7 @@ def __init__(self, X, v, check=True): R = X.value_ring() v = Sequence(v, R) if len(v) == d-1: # very common special case - v.append(R(1)) + v.append(R.one()) n = len(v) all_zero = True From fdcdb7ff8490de17ac51a01e3b2ac873b2df674d Mon Sep 17 00:00:00 2001 From: Lorenz Panny Date: Mon, 11 Mar 2024 20:12:46 +0100 Subject: [PATCH 228/518] streamline projective normalization code fixes #37593 --- .../schemes/projective/projective_point.py | 43 +++++++++---------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/src/sage/schemes/projective/projective_point.py b/src/sage/schemes/projective/projective_point.py index f04a5bc0e3a..b2948fecb3e 100644 --- a/src/sage/schemes/projective/projective_point.py +++ b/src/sage/schemes/projective/projective_point.py @@ -608,7 +608,7 @@ def normalize_coordinates(self): return R = self.codomain().base_ring() if isinstance(R, QuotientRing_generic): - index = self.codomain().ambient_space().dimension_relative() + index = len(self._coords) - 1 while not self._coords[index]: index -= 1 last = self._coords[index].lift() @@ -1162,23 +1162,19 @@ def __init__(self, X, v, check=True): if len(v) == d-1: # very common special case v.append(R.one()) - n = len(v) - all_zero = True - for i in range(n): - last = n-1-i - if v[last]: - all_zero = False - c = v[last] - if c == R.one(): - break + for last in reversed(range(len(v))): + c = v[last] + if c.is_one(): + break + if c: for j in range(last): v[j] /= c v[last] = R.one() - self._normalized = True break - if all_zero: + else: raise ValueError(f"{v} does not define a valid projective " "point since all entries are zero") + self._normalized = True X.extended_codomain()._check_satisfies_equations(v) @@ -1225,16 +1221,19 @@ def normalize_coordinates(self): """ if self._normalized: return - index = self.codomain().ambient_space().dimension_relative() - while not self._coords[index]: - index -= 1 - inv = self._coords[index].inverse() - new_coords = [] - for i in range(index): - new_coords.append(self._coords[i] * inv) - new_coords.append(self.base_ring().one()) - new_coords.extend(self._coords[index+1:]) - self._coords = tuple(new_coords) + for index in reversed(range(len(self._coords))): + c = self._coords[index] + if c.is_one(): + break + if c: + inv = c.inverse() + new_coords = [d * inv for d in self._coords[:index]] + new_coords.append(self.base_ring().one()) + new_coords.extend(self._coords[index+1:]) + self._coords = tuple(new_coords) + break + else: + assert False, 'bug: invalid projective point' self._normalized = True def _number_field_from_algebraics(self): From 2740ad512b20ac9ccb5a1ed3c405cfe9767925a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Tue, 12 Mar 2024 09:38:58 +0100 Subject: [PATCH 229/518] extend the definition of q-binomial coefficients to (k > 0, n < 0) --- src/sage/combinat/q_analogues.py | 39 +++++++++++++++++++------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/src/sage/combinat/q_analogues.py b/src/sage/combinat/q_analogues.py index f31cbe41589..9f384fbc285 100644 --- a/src/sage/combinat/q_analogues.py +++ b/src/sage/combinat/q_analogues.py @@ -9,16 +9,14 @@ # 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.combinat.dyck_word import DyckWords +from sage.combinat.partition import _Partitions from sage.misc.cachefunc import cached_function from sage.misc.misc_c import prod -from sage.structure.element import parent from sage.rings.integer_ring import ZZ -from sage.combinat.dyck_word import DyckWords -from sage.combinat.partition import _Partitions +from sage.structure.element import parent def q_int(n, q=None): @@ -69,10 +67,9 @@ def q_int(n, q=None): """ if n not in ZZ: raise ValueError(f'{n} must be an integer') - if q is None: q = ZZ['q'].gen() - if n == 0: # Special case + if n == 0: return parent(q)(0) if n > 0: return sum(q**i for i in range(n)) @@ -186,13 +183,20 @@ def q_binomial(n, k, q=None, algorithm='auto'): sage: g.parent() Univariate Polynomial Ring in q over Integer Ring - The `q`-binomial coefficient vanishes unless `0 \leq k \leq n`:: + For `n \geq 0`, the `q`-binomial coefficient vanishes unless `0 \leq k \leq n`:: sage: q_binomial(4,5) 0 sage: q_binomial(5,-1) 0 + For `k \geq 0`, the `q`-binomial coefficient is extended as a polynomial in `n`:: + + sage: q_binomial(-4,1) + (-q^3 - q^2 - q - 1)/q^4 + sage: q_binomial(-2,3) + (-q^3 - q^2 - q - 1)/q^9 + Other variables can be used, given as third parameter:: sage: p = ZZ['p'].gen() @@ -237,12 +241,12 @@ def q_binomial(n, k, q=None, algorithm='auto'): ... TypeError: no conversion of this rational to integer - One checks that `n` is nonnegative:: + One checks that either `k` or `n` is nonnegative:: - sage: q_binomial(-4,1) + sage: q_binomial(-4,-1) Traceback (most recent call last): ... - ValueError: n must be nonnegative + ValueError: either k or n must be nonnegative This also works for variables in the symbolic ring:: @@ -314,10 +318,8 @@ def q_binomial(n, k, q=None, algorithm='auto'): # sanity checks n = ZZ(n) k = ZZ(k) - if n < 0: - raise ValueError('n must be nonnegative') - - k = min(n - k, k) # Pick the smallest k + if k < 0 and n < 0: + raise ValueError('either k or n must be nonnegative') # polynomiality test if q is None: @@ -328,6 +330,11 @@ def q_binomial(n, k, q=None, algorithm='auto'): from sage.rings.polynomial.polynomial_element import Polynomial is_polynomial = isinstance(q, Polynomial) + if n >= 0: + k = min(n - k, k) # Pick the smallest k + else: + return (-1)**k * q**(k * n - (k * k - k) // 2) * q_binomial(-n + k - 1, k, q=q) + # We support non-Sage Elements too, where parent(q) is really # type(q). The calls R(0) and R(1) should work in all cases to # generate the correct 0 and 1 elements. From d6cef9e44cf31d073599a2b84beef2df8ae6b9e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Tue, 12 Mar 2024 14:27:24 +0100 Subject: [PATCH 230/518] some details in lrcalc --- src/sage/libs/lrcalc/lrcalc.py | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/sage/libs/lrcalc/lrcalc.py b/src/sage/libs/lrcalc/lrcalc.py index cf50d52a927..cd9248f59d9 100644 --- a/src/sage/libs/lrcalc/lrcalc.py +++ b/src/sage/libs/lrcalc/lrcalc.py @@ -193,7 +193,7 @@ import lrcalc -def _lrcalc_dict_to_sage(result): +def _lrcalc_dict_to_sage(result) -> dict: r""" Translate from lrcalc output format to Sage expected format. @@ -203,7 +203,9 @@ def _lrcalc_dict_to_sage(result): sage: mult([2,1],[3,2,1],3) # indirect doctest {[3, 3, 3]: 1, [4, 3, 2]: 2, [4, 4, 1]: 1, [5, 2, 2]: 1, [5, 3, 1]: 1} """ - return {_Partitions(la): Integer(k) for la, k in result.items()} + return {_Partitions.element_class(_Partitions, [Integer(p) for p in la]): + Integer(k) for la, k in result.items()} + def lrcoef_unsafe(outer, inner1, inner2): r""" @@ -268,7 +270,7 @@ def lrcoef(outer, inner1, inner2): return lrcoef_unsafe(_Partitions(outer), _Partitions(inner1), _Partitions(inner2)) -def mult(part1, part2, maxrows=None, level=None, quantum=None): +def mult(part1, part2, maxrows=None, level=None, quantum=None) -> dict: r""" Compute a product of two Schur functions. @@ -345,13 +347,13 @@ def mult(part1, part2, maxrows=None, level=None, quantum=None): result = lrcalc.mult_quantum(part1, part2, maxrows, level, degrees=True) P = quantum.parent() output = {} - for i,k in result.items(): + for i, k in result.items(): la = _Partitions(i[0]) output[la] = output.get(la, P.zero()) + k * quantum**(i[1]) return output -def skew(outer, inner, maxrows=-1): +def skew(outer, inner, maxrows=-1) -> dict: """ Compute the Schur expansion of a skew Schur function. @@ -377,7 +379,7 @@ def skew(outer, inner, maxrows=-1): return _lrcalc_dict_to_sage(lrcalc.skew(outer, inner, maxrows)) -def coprod(part, all=0): +def coprod(part, all=0) -> dict: """ Compute the coproduct of a Schur function. @@ -404,11 +406,13 @@ def coprod(part, all=0): [(([1, 1], [1]), 1), (([2], [1]), 1), (([2, 1], []), 1)] """ result = lrcalc.coprod(part, all) - return {tuple([_Partitions(mu) for mu in la]): Integer(k) + return {tuple([_Partitions.element_class(_Partitions, + [Integer(p) for p in mu]) + for mu in la]): Integer(k) for la, k in result.items()} -def mult_schubert(w1, w2, rank=0): +def mult_schubert(w1, w2, rank=0) -> dict: r""" Compute a product of two Schubert polynomials. @@ -436,7 +440,7 @@ def mult_schubert(w1, w2, rank=0): ([7, 3, 4, 1, 2, 5, 6], 1), ([7, 4, 2, 1, 3, 5, 6], 1)] """ result = lrcalc.schubmult(w1, w2, rank) - return {Permutation(list(la)):Integer(k) for la,k in result.items()} + return {Permutation(list(la)): Integer(k) for la, k in result.items()} def lrskew(outer, inner, weight=None, maxrows=-1): @@ -503,7 +507,7 @@ def lrskew(outer, inner, weight=None, maxrows=-1): if weight is None: ST = SemistandardSkewTableaux(shape) for data in iterator: - yield ST.from_shape_and_word(shape, [i+1 for i in data]) + yield ST.from_shape_and_word(shape, [i + 1 for i in data]) else: wt = _Partitions(weight) ST = SemistandardSkewTableaux(shape, wt) @@ -517,4 +521,4 @@ def lrskew(outer, inner, weight=None, maxrows=-1): break w[j] += 1 if w == wt: - yield ST.from_shape_and_word(shape, [i+1 for i in data]) + yield ST.from_shape_and_word(shape, [i + 1 for i in data]) From c81fd1dc68e9739df227d6bd7b33b7b59535002b Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Wed, 13 Mar 2024 00:55:36 +0900 Subject: [PATCH 231/518] Fixing failing doctests. Adding one for a GAP group (as a string). --- .../en/prep/Quickstarts/Abstract-Algebra.rst | 14 +++---- .../en/thematic_tutorials/group_theory.rst | 22 +++++------ src/sage/categories/finite_groups.py | 8 ++-- src/sage/categories/groups.py | 4 +- src/sage/categories/monoids.py | 2 +- src/sage/groups/abelian_gps/abelian_group.py | 2 +- .../abelian_gps/abelian_group_element.py | 2 +- .../additive_abelian_group.py | 2 +- src/sage/groups/class_function.py | 6 +-- src/sage/groups/perm_gps/permgroup.py | 8 ++++ .../polynomial/polynomial_rational_flint.pyx | 2 +- .../judson-abstract-algebra/cosets-sage.py | 2 +- .../judson-abstract-algebra/galois-sage.py | 8 ++-- .../judson-abstract-algebra/homomorph-sage.py | 14 +++---- .../judson-abstract-algebra/sylow-sage.py | 39 ++++++++++--------- 15 files changed, 70 insertions(+), 65 deletions(-) diff --git a/src/doc/en/prep/Quickstarts/Abstract-Algebra.rst b/src/doc/en/prep/Quickstarts/Abstract-Algebra.rst index 101cbcdb4e7..fe465251bfc 100644 --- a/src/doc/en/prep/Quickstarts/Abstract-Algebra.rst +++ b/src/doc/en/prep/Quickstarts/Abstract-Algebra.rst @@ -65,13 +65,13 @@ We can access a lot of information about groups, such as: Subgroup generated by [(1,5)(2,6)(3,7)(4,8)] of (Dihedral group of order 16 as a permutation group) Subgroup generated by [(2,8)(3,7)(4,6)] of (Dihedral group of order 16 as a permutation group) Subgroup generated by [(1,2)(3,8)(4,7)(5,6)] of (Dihedral group of order 16 as a permutation group) - Subgroup generated by [(1,3,5,7)(2,4,6,8), (1,5)(2,6)(3,7)(4,8)] of (Dihedral group of order 16 as a permutation group) - Subgroup generated by [(2,8)(3,7)(4,6), (1,5)(2,6)(3,7)(4,8)] of (Dihedral group of order 16 as a permutation group) - Subgroup generated by [(1,2)(3,8)(4,7)(5,6), (1,5)(2,6)(3,7)(4,8)] of (Dihedral group of order 16 as a permutation group) - Subgroup generated by [(2,8)(3,7)(4,6), (1,3,5,7)(2,4,6,8), (1,5)(2,6)(3,7)(4,8)] of (Dihedral group of order 16 as a permutation group) - Subgroup generated by [(1,2,3,4,5,6,7,8), (1,3,5,7)(2,4,6,8), (1,5)(2,6)(3,7)(4,8)] of (Dihedral group of order 16 as a permutation group) - Subgroup generated by [(1,2)(3,8)(4,7)(5,6), (1,3,5,7)(2,4,6,8), (1,5)(2,6)(3,7)(4,8)] of (Dihedral group of order 16 as a permutation group) - Subgroup generated by [(2,8)(3,7)(4,6), (1,2,3,4,5,6,7,8), (1,3,5,7)(2,4,6,8), (1,5)(2,6)(3,7)(4,8)] of (Dihedral group of order 16 as a permutation group) + Subgroup generated by [(1,5)(2,6)(3,7)(4,8), (1,3,5,7)(2,4,6,8)] of (Dihedral group of order 16 as a permutation group) + Subgroup generated by [(1,5)(2,6)(3,7)(4,8), (2,8)(3,7)(4,6)] of (Dihedral group of order 16 as a permutation group) + Subgroup generated by [(1,5)(2,6)(3,7)(4,8), (1,2)(3,8)(4,7)(5,6)] of (Dihedral group of order 16 as a permutation group) + Subgroup generated by [(1,5)(2,6)(3,7)(4,8), (1,3,5,7)(2,4,6,8), (2,8)(3,7)(4,6)] of (Dihedral group of order 16 as a permutation group) + Subgroup generated by [(1,5)(2,6)(3,7)(4,8), (1,3,5,7)(2,4,6,8), (1,2,3,4,5,6,7,8)] of (Dihedral group of order 16 as a permutation group) + Subgroup generated by [(1,5)(2,6)(3,7)(4,8), (1,3,5,7)(2,4,6,8), (1,2)(3,8)(4,7)(5,6)] of (Dihedral group of order 16 as a permutation group) + Subgroup generated by [(1,5)(2,6)(3,7)(4,8), (1,3,5,7)(2,4,6,8), (2,8)(3,7)(4,6), (1,2,3,4,5,6,7,8)] of (Dihedral group of order 16 as a permutation group) In the previous cell we once again did a for loop over a set of objects rather than just a list of numbers. This can be very powerful. diff --git a/src/doc/en/thematic_tutorials/group_theory.rst b/src/doc/en/thematic_tutorials/group_theory.rst index e9e6b23953f..dd0afcbef0c 100644 --- a/src/doc/en/thematic_tutorials/group_theory.rst +++ b/src/doc/en/thematic_tutorials/group_theory.rst @@ -591,8 +591,8 @@ subgroups. :: Subgroup generated by [(1,11)(2,12)(3,13)(4,14)(5,15)(6,16)(7,17)(8,18)(9,19)(10,20)] of (Cyclic group of order 20 as a permutation group), Subgroup generated by [(1,6,11,16)(2,7,12,17)(3,8,13,18)(4,9,14,19)(5,10,15,20), (1,11)(2,12)(3,13)(4,14)(5,15)(6,16)(7,17)(8,18)(9,19)(10,20)] of (Cyclic group of order 20 as a permutation group), Subgroup generated by [(1,5,9,13,17)(2,6,10,14,18)(3,7,11,15,19)(4,8,12,16,20)] of (Cyclic group of order 20 as a permutation group), - Subgroup generated by [(1,3,5,7,9,11,13,15,17,19)(2,4,6,8,10,12,14,16,18,20), (1,5,9,13,17)(2,6,10,14,18)(3,7,11,15,19)(4,8,12,16,20)] of (Cyclic group of order 20 as a permutation group), - Subgroup generated by [(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20), (1,3,5,7,9,11,13,15,17,19)(2,4,6,8,10,12,14,16,18,20), (1,5,9,13,17)(2,6,10,14,18)(3,7,11,15,19)(4,8,12,16,20)] of (Cyclic group of order 20 as a permutation group)] + Subgroup generated by [(1,5,9,13,17)(2,6,10,14,18)(3,7,11,15,19)(4,8,12,16,20), (1,3,5,7,9,11,13,15,17,19)(2,4,6,8,10,12,14,16,18,20)] of (Cyclic group of order 20 as a permutation group), + Subgroup generated by [(1,5,9,13,17)(2,6,10,14,18)(3,7,11,15,19)(4,8,12,16,20), (1,3,5,7,9,11,13,15,17,19)(2,4,6,8,10,12,14,16,18,20), (1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20)] of (Cyclic group of order 20 as a permutation group)] Be careful, this command uses some more advanced ideas and will not usually list *all* of the subgroups of a group. Here we are relying on @@ -651,16 +651,16 @@ suitable `g`. As an illustration, the code below: Subgroup generated by [(1,2)(3,12)(4,11)(5,10)(6,9)(7,8)] of (Dihedral group of order 24 as a permutation group), Subgroup generated by [(1,5,9)(2,6,10)(3,7,11)(4,8,12)] of (Dihedral group of order 24 as a permutation group), Subgroup generated by [(2,12)(3,11)(4,10)(5,9)(6,8), (1,7)(2,8)(3,9)(4,10)(5,11)(6,12)] of (Dihedral group of order 24 as a permutation group), - Subgroup generated by [(1,7)(2,8)(3,9)(4,10)(5,11)(6,12), (1,10,7,4)(2,11,8,5)(3,12,9,6)] of (Dihedral group of order 24 as a permutation group), + Subgroup generated by [(1,10,7,4)(2,11,8,5)(3,12,9,6), (1,7)(2,8)(3,9)(4,10)(5,11)(6,12)] of (Dihedral group of order 24 as a permutation group), Subgroup generated by [(1,2)(3,12)(4,11)(5,10)(6,9)(7,8), (1,7)(2,8)(3,9)(4,10)(5,11)(6,12)] of (Dihedral group of order 24 as a permutation group), - Subgroup generated by [(1,3,5,7,9,11)(2,4,6,8,10,12), (1,5,9)(2,6,10)(3,7,11)(4,8,12)] of (Dihedral group of order 24 as a permutation group), - Subgroup generated by [(2,12)(3,11)(4,10)(5,9)(6,8), (1,5,9)(2,6,10)(3,7,11)(4,8,12)] of (Dihedral group of order 24 as a permutation group), - Subgroup generated by [(1,2)(3,12)(4,11)(5,10)(6,9)(7,8), (1,5,9)(2,6,10)(3,7,11)(4,8,12)] of (Dihedral group of order 24 as a permutation group), - Subgroup generated by [(2,12)(3,11)(4,10)(5,9)(6,8), (1,7)(2,8)(3,9)(4,10)(5,11)(6,12), (1,10,7,4)(2,11,8,5)(3,12,9,6)] of (Dihedral group of order 24 as a permutation group), - Subgroup generated by [(2,12)(3,11)(4,10)(5,9)(6,8), (1,3,5,7,9,11)(2,4,6,8,10,12), (1,5,9)(2,6,10)(3,7,11)(4,8,12)] of (Dihedral group of order 24 as a permutation group), - Subgroup generated by [(1,2,3,4,5,6,7,8,9,10,11,12), (1,3,5,7,9,11)(2,4,6,8,10,12), (1,5,9)(2,6,10)(3,7,11)(4,8,12)] of (Dihedral group of order 24 as a permutation group), - Subgroup generated by [(1,2)(3,12)(4,11)(5,10)(6,9)(7,8), (1,3,5,7,9,11)(2,4,6,8,10,12), (1,5,9)(2,6,10)(3,7,11)(4,8,12)] of (Dihedral group of order 24 as a permutation group), - Subgroup generated by [(2,12)(3,11)(4,10)(5,9)(6,8), (1,2,3,4,5,6,7,8,9,10,11,12), (1,3,5,7,9,11)(2,4,6,8,10,12), (1,5,9)(2,6,10)(3,7,11)(4,8,12)] of (Dihedral group of order 24 as a permutation group)] + Subgroup generated by [(1,5,9)(2,6,10)(3,7,11)(4,8,12), (1,3,5,7,9,11)(2,4,6,8,10,12)] of (Dihedral group of order 24 as a permutation group), + Subgroup generated by [(1,5,9)(2,6,10)(3,7,11)(4,8,12), (2,12)(3,11)(4,10)(5,9)(6,8)] of (Dihedral group of order 24 as a permutation group), + Subgroup generated by [(1,5,9)(2,6,10)(3,7,11)(4,8,12), (1,2)(3,12)(4,11)(5,10)(6,9)(7,8)] of (Dihedral group of order 24 as a permutation group), + Subgroup generated by [(2,12)(3,11)(4,10)(5,9)(6,8), (1,10,7,4)(2,11,8,5)(3,12,9,6), (1,7)(2,8)(3,9)(4,10)(5,11)(6,12)] of (Dihedral group of order 24 as a permutation group), + Subgroup generated by [(1,5,9)(2,6,10)(3,7,11)(4,8,12), (1,3,5,7,9,11)(2,4,6,8,10,12), (2,12)(3,11)(4,10)(5,9)(6,8)] of (Dihedral group of order 24 as a permutation group), + Subgroup generated by [(1,5,9)(2,6,10)(3,7,11)(4,8,12), (1,3,5,7,9,11)(2,4,6,8,10,12), (1,2,3,4,5,6,7,8,9,10,11,12)] of (Dihedral group of order 24 as a permutation group), + Subgroup generated by [(1,5,9)(2,6,10)(3,7,11)(4,8,12), (1,3,5,7,9,11)(2,4,6,8,10,12), (1,2)(3,12)(4,11)(5,10)(6,9)(7,8)] of (Dihedral group of order 24 as a permutation group), + Subgroup generated by [(1,5,9)(2,6,10)(3,7,11)(4,8,12), (1,3,5,7,9,11)(2,4,6,8,10,12), (2,12)(3,11)(4,10)(5,9)(6,8), (1,2,3,4,5,6,7,8,9,10,11,12)] of (Dihedral group of order 24 as a permutation group)] sage: print("An order two subgroup:\n{}".format(sg[1].list())) An order two subgroup: diff --git a/src/sage/categories/finite_groups.py b/src/sage/categories/finite_groups.py index f6866485e93..70cc029e389 100644 --- a/src/sage/categories/finite_groups.py +++ b/src/sage/categories/finite_groups.py @@ -48,7 +48,7 @@ class ParentMethods: def semigroup_generators(self): """ - Returns semigroup generators for self. + Return semigroup generators for ``self``. For finite groups, the group generators are also semigroup generators. Hence, this default implementation calls @@ -58,7 +58,7 @@ def semigroup_generators(self): sage: A = AlternatingGroup(4) sage: A.semigroup_generators() - Family ((2,3,4), (1,2,3)) + Family ((1,2,3), (2,3,4)) """ return self.group_generators() @@ -74,7 +74,7 @@ def monoid_generators(self): sage: A = AlternatingGroup(4) sage: A.monoid_generators() - Family ((2,3,4), (1,2,3)) + Family ((1,2,3), (2,3,4)) """ return self.group_generators() @@ -113,7 +113,7 @@ def some_elements(self): sage: A = AlternatingGroup(4) sage: A.some_elements() - Family ((2,3,4), (1,2,3)) + Family ((1,2,3), (2,3,4)) """ return self.group_generators() diff --git a/src/sage/categories/groups.py b/src/sage/categories/groups.py index 445ba111c09..22f02ae4b30 100644 --- a/src/sage/categories/groups.py +++ b/src/sage/categories/groups.py @@ -102,7 +102,7 @@ def group_generators(self): sage: A = AlternatingGroup(4) # needs sage.groups sage: A.group_generators() # needs sage.groups - Family ((2,3,4), (1,2,3)) + Family ((1,2,3), (2,3,4)) """ from sage.sets.family import Family try: @@ -124,7 +124,7 @@ def monoid_generators(self): sage: # needs sage.groups sage: A = AlternatingGroup(4) sage: A.monoid_generators() - Family ((2,3,4), (1,2,3)) + Family ((1,2,3), (2,3,4)) sage: F. = FreeGroup() sage: F.monoid_generators() Family (x, y, x^-1, y^-1) diff --git a/src/sage/categories/monoids.py b/src/sage/categories/monoids.py index 28474bd17b6..a4340621bbe 100644 --- a/src/sage/categories/monoids.py +++ b/src/sage/categories/monoids.py @@ -565,7 +565,7 @@ def algebra_generators(self): sage: A10 = AlternatingGroup(10) # needs sage.groups sage: GroupAlgebras(QQ).example(A10).algebra_generators() # needs sage.groups sage.modules - Family ((8,9,10), (1,2,3,4,5,6,7,8,9)) + Family ((1,2,3,4,5,6,7,8,9), (8,9,10)) sage: A = DihedralGroup(3).algebra(QQ); A # needs sage.groups sage.modules Algebra of Dihedral group of order 6 as a permutation group diff --git a/src/sage/groups/abelian_gps/abelian_group.py b/src/sage/groups/abelian_gps/abelian_group.py index bbe793b3625..f98bfe0cde5 100644 --- a/src/sage/groups/abelian_gps/abelian_group.py +++ b/src/sage/groups/abelian_gps/abelian_group.py @@ -1132,7 +1132,7 @@ def permutation_group(self): sage: G = AbelianGroup(2,[2,3]); G Multiplicative Abelian group isomorphic to C2 x C3 sage: G.permutation_group() # needs sage.groups - Permutation Group with generators [(3,4,5), (1,2)] + Permutation Group with generators [(1,2), (3,4,5)] TESTS: diff --git a/src/sage/groups/abelian_gps/abelian_group_element.py b/src/sage/groups/abelian_gps/abelian_group_element.py index 6ae81844f98..b081f1e1c4e 100644 --- a/src/sage/groups/abelian_gps/abelian_group_element.py +++ b/src/sage/groups/abelian_gps/abelian_group_element.py @@ -101,7 +101,7 @@ def as_permutation(self): Multiplicative Abelian group isomorphic to C2 x C3 x C4 sage: a,b,c = G.gens() sage: Gp = G.permutation_group(); Gp # needs sage.groups - Permutation Group with generators [(6,7,8,9), (3,4,5), (1,2)] + Permutation Group with generators [(1,2), (3,4,5), (6,7,8,9)] sage: a.as_permutation() # needs sage.libs.gap (1,2) sage: ap = a.as_permutation(); ap # needs sage.libs.gap diff --git a/src/sage/groups/additive_abelian/additive_abelian_group.py b/src/sage/groups/additive_abelian/additive_abelian_group.py index c0a90f0c110..5f389fcfb81 100644 --- a/src/sage/groups/additive_abelian/additive_abelian_group.py +++ b/src/sage/groups/additive_abelian/additive_abelian_group.py @@ -451,7 +451,7 @@ def permutation_group(self): sage: G = AdditiveAbelianGroup([2, 3]) sage: G.permutation_group() # needs sage.groups - Permutation Group with generators [(3,4,5), (1,2)] + Permutation Group with generators [(1,2), (3,4,5)] TESTS: diff --git a/src/sage/groups/class_function.py b/src/sage/groups/class_function.py index e390cbbf288..7dd5eb30cda 100644 --- a/src/sage/groups/class_function.py +++ b/src/sage/groups/class_function.py @@ -744,7 +744,7 @@ def induct(self, G): sage: G = SymmetricGroup(5) sage: H = G.subgroup([(1,2,3), (1,2), (4,5)]) sage: xi = H.trivial_character(); xi - Character of Subgroup generated by [(4,5), (1,2), (1,2,3)] of + Character of Subgroup generated by [(1,2,3), (1,2), (4,5)] of (Symmetric group of order 5! as a permutation group) sage: xi.induct(G) Character of Symmetric group of order 5! as a permutation group @@ -1425,7 +1425,7 @@ def restrict(self, H): Character of Symmetric group of order 5! as a permutation group sage: H = G.subgroup([(1,2,3), (1,2), (4,5)]) sage: chi.restrict(H) - Character of Subgroup generated by [(4,5), (1,2), (1,2,3)] of + Character of Subgroup generated by [(1,2,3), (1,2), (4,5)] of (Symmetric group of order 5! as a permutation group) sage: chi.restrict(H).values() [3, -3, -3, -1, 0, 0] @@ -1457,7 +1457,7 @@ def induct(self, G): sage: G = SymmetricGroup(5) sage: H = G.subgroup([(1,2,3), (1,2), (4,5)]) sage: xi = H.trivial_character(); xi - Character of Subgroup generated by [(4,5), (1,2), (1,2,3)] of + Character of Subgroup generated by [(1,2,3), (1,2), (4,5)] of (Symmetric group of order 5! as a permutation group) sage: xi.induct(G) Character of Symmetric group of order 5! as a permutation group diff --git a/src/sage/groups/perm_gps/permgroup.py b/src/sage/groups/perm_gps/permgroup.py index 4f0cadd4768..4ee53ba4f06 100644 --- a/src/sage/groups/perm_gps/permgroup.py +++ b/src/sage/groups/perm_gps/permgroup.py @@ -490,6 +490,14 @@ def __init__(self, gens=None, gap_group=None, canonicalize=True, ((1,2,3,4,5), (4,5,6)) sage: P.gap().GeneratorsOfGroup() [ (1,2,3,4,5), (4,5,6) ] + + sage: gg = gap.eval("Group((1,2,3,4,5),(4,5,6))") + sage: P = PermutationGroup(gap_group=gg) + sage: P.gens() + ((1,2,3,4,5), (4,5,6)) + sage: h = P.hom(codomain=P, im_gens=P.gens()) + sage: h + Group endomorphism of Permutation Group with generators [(1,2,3,4,5), (4,5,6)] """ if (gens is None and gap_group is None): raise ValueError("you must specify gens or gap_group") diff --git a/src/sage/rings/polynomial/polynomial_rational_flint.pyx b/src/sage/rings/polynomial/polynomial_rational_flint.pyx index 1ef3cbaf681..805bb9fe8ae 100644 --- a/src/sage/rings/polynomial/polynomial_rational_flint.pyx +++ b/src/sage/rings/polynomial/polynomial_rational_flint.pyx @@ -2116,7 +2116,7 @@ cdef class Polynomial_rational_flint(Polynomial): sage: G = f.galois_group(); G Transitive group number 5 of degree 4 sage: G.gens() - ((1,2), (1,2,3,4)) + ((1,2,3,4), (1,2)) sage: G.order() 24 diff --git a/src/sage/tests/books/judson-abstract-algebra/cosets-sage.py b/src/sage/tests/books/judson-abstract-algebra/cosets-sage.py index 25efca28dce..028dd8cb33c 100644 --- a/src/sage/tests/books/judson-abstract-algebra/cosets-sage.py +++ b/src/sage/tests/books/judson-abstract-algebra/cosets-sage.py @@ -95,7 +95,7 @@ Subgroup generated by [(1,2)] of (Symmetric group of order 3! as a permutation group), Subgroup generated by [(1,3)] of (Symmetric group of order 3! as a permutation group), Subgroup generated by [(1,2,3)] of (Symmetric group of order 3! as a permutation group), - Subgroup generated by [(2,3), (1,2,3)] of (Symmetric group of order 3! as a permutation group)] + Subgroup generated by [(1,2,3), (2,3)] of (Symmetric group of order 3! as a permutation group)] ~~~~~~~~~~~~~~~~~~~~~~ :: diff --git a/src/sage/tests/books/judson-abstract-algebra/galois-sage.py b/src/sage/tests/books/judson-abstract-algebra/galois-sage.py index 036b0a390c0..b1ecf0d8d48 100644 --- a/src/sage/tests/books/judson-abstract-algebra/galois-sage.py +++ b/src/sage/tests/books/judson-abstract-algebra/galois-sage.py @@ -271,10 +271,10 @@ ((1,4),), ((1,2)(3,4),), ((1,3)(2,4),), - ((2,3), (1,4)(2,3)), - ((1,2,4,3), (1,4)(2,3)), - ((1,2)(3,4), (1,4)(2,3)), - ((2,3), (1,2,4,3), (1,4)(2,3))] + ((1,4)(2,3), (2,3)), + ((1,4)(2,3), (1,2,4,3)), + ((1,4)(2,3), (1,2)(3,4)), + ((1,4)(2,3), (2,3), (1,2,4,3))] ~~~~~~~~~~~~~~~~~~~~~~ :: diff --git a/src/sage/tests/books/judson-abstract-algebra/homomorph-sage.py b/src/sage/tests/books/judson-abstract-algebra/homomorph-sage.py index abff8d3189c..dbc7bca8d96 100644 --- a/src/sage/tests/books/judson-abstract-algebra/homomorph-sage.py +++ b/src/sage/tests/books/judson-abstract-algebra/homomorph-sage.py @@ -164,15 +164,14 @@ sage: H = DihedralGroup(4) sage: results = G.direct_product(H) sage: results[0] - Permutation Group with generators [(4,5,6,7), (4,7)(5,6), (1,2,3)] + Permutation Group with generators [(1,2,3), (4,5,6,7), (4,7)(5,6)] ~~~~~~~~~~~~~~~~~~~~~~ :: sage: results[1] Permutation group morphism: From: Cyclic group of order 3 as a permutation group - To: Permutation Group with generators - [(4,5,6,7), (4,7)(5,6), (1,2,3)] + To: Permutation Group with generators [(1,2,3), (4,5,6,7), (4,7)(5,6)] Defn: Embedding( Group( [ (1,2,3), (4,5,6,7), (4,7)(5,6) ] ), 1 ) ~~~~~~~~~~~~~~~~~~~~~~ :: @@ -180,16 +179,14 @@ sage: results[2] Permutation group morphism: From: Dihedral group of order 8 as a permutation group - To: Permutation Group with generators - [(4,5,6,7), (4,7)(5,6), (1,2,3)] + To: Permutation Group with generators [(1,2,3), (4,5,6,7), (4,7)(5,6)] Defn: Embedding( Group( [ (1,2,3), (4,5,6,7), (4,7)(5,6) ] ), 2 ) ~~~~~~~~~~~~~~~~~~~~~~ :: sage: results[3] Permutation group morphism: - From: Permutation Group with generators - [(4,5,6,7), (4,7)(5,6), (1,2,3)] + From: Permutation Group with generators [(1,2,3), (4,5,6,7), (4,7)(5,6)] To: Cyclic group of order 3 as a permutation group Defn: Projection( Group( [ (1,2,3), (4,5,6,7), (4,7)(5,6) ] ), 1 ) @@ -197,8 +194,7 @@ sage: results[4] Permutation group morphism: - From: Permutation Group with generators - [(4,5,6,7), (4,7)(5,6), (1,2,3)] + From: Permutation Group with generators [(1,2,3), (4,5,6,7), (4,7)(5,6)] To: Dihedral group of order 8 as a permutation group Defn: Projection( Group( [ (1,2,3), (4,5,6,7), (4,7)(5,6) ] ), 2 ) diff --git a/src/sage/tests/books/judson-abstract-algebra/sylow-sage.py b/src/sage/tests/books/judson-abstract-algebra/sylow-sage.py index 0051f206528..cd78b147ca3 100644 --- a/src/sage/tests/books/judson-abstract-algebra/sylow-sage.py +++ b/src/sage/tests/books/judson-abstract-algebra/sylow-sage.py @@ -65,12 +65,12 @@ sage: uniqS2 [Permutation Group with generators [(2,18)(3,17)(4,16)(5,15)(6,14)(7,13)(8,12)(9,11), (1,10)(2,11)(3,12)(4,13)(5,14)(6,15)(7,16)(8,17)(9,18)], Permutation Group with generators [(1,7)(2,6)(3,5)(8,18)(9,17)(10,16)(11,15)(12,14), (1,10)(2,11)(3,12)(4,13)(5,14)(6,15)(7,16)(8,17)(9,18)], - Permutation Group with generators [(1,10)(2,11)(3,12)(4,13)(5,14)(6,15)(7,16)(8,17)(9,18), (1,13)(2,12)(3,11)(4,10)(5,9)(6,8)(14,18)(15,17)], - Permutation Group with generators [(1,10)(2,11)(3,12)(4,13)(5,14)(6,15)(7,16)(8,17)(9,18), (1,15)(2,14)(3,13)(4,12)(5,11)(6,10)(7,9)(16,18)], + Permutation Group with generators [(1,13)(2,12)(3,11)(4,10)(5,9)(6,8)(14,18)(15,17), (1,10)(2,11)(3,12)(4,13)(5,14)(6,15)(7,16)(8,17)(9,18)], + Permutation Group with generators [(1,15)(2,14)(3,13)(4,12)(5,11)(6,10)(7,9)(16,18), (1,10)(2,11)(3,12)(4,13)(5,14)(6,15)(7,16)(8,17)(9,18)], Permutation Group with generators [(1,3)(4,18)(5,17)(6,16)(7,15)(8,14)(9,13)(10,12), (1,10)(2,11)(3,12)(4,13)(5,14)(6,15)(7,16)(8,17)(9,18)], Permutation Group with generators [(1,9)(2,8)(3,7)(4,6)(10,18)(11,17)(12,16)(13,15), (1,10)(2,11)(3,12)(4,13)(5,14)(6,15)(7,16)(8,17)(9,18)], - Permutation Group with generators [(1,10)(2,11)(3,12)(4,13)(5,14)(6,15)(7,16)(8,17)(9,18), (1,11)(2,10)(3,9)(4,8)(5,7)(12,18)(13,17)(14,16)], - Permutation Group with generators [(1,10)(2,11)(3,12)(4,13)(5,14)(6,15)(7,16)(8,17)(9,18), (1,17)(2,16)(3,15)(4,14)(5,13)(6,12)(7,11)(8,10)], + Permutation Group with generators [(1,11)(2,10)(3,9)(4,8)(5,7)(12,18)(13,17)(14,16), (1,10)(2,11)(3,12)(4,13)(5,14)(6,15)(7,16)(8,17)(9,18)], + Permutation Group with generators [(1,17)(2,16)(3,15)(4,14)(5,13)(6,12)(7,11)(8,10), (1,10)(2,11)(3,12)(4,13)(5,14)(6,15)(7,16)(8,17)(9,18)], Permutation Group with generators [(1,5)(2,4)(6,18)(7,17)(8,16)(9,15)(10,14)(11,13), (1,10)(2,11)(3,12)(4,13)(5,14)(6,15)(7,16)(8,17)(9,18)]] ~~~~~~~~~~~~~~~~~~~~~~ :: @@ -83,8 +83,8 @@ sage: G = DihedralGroup(18) sage: S3 = G.sylow_subgroup(3); S3 Subgroup generated by - [(1,7,13)(2,8,14)(3,9,15)(4,10,16)(5,11,17)(6,12,18), - (1,15,11,7,3,17,13,9,5)(2,16,12,8,4,18,14,10,6)] + [(1,15,11,7,3,17,13,9,5)(2,16,12,8,4,18,14,10,6), + (1,7,13)(2,8,14)(3,9,15)(4,10,16)(5,11,17)(6,12,18)] of (Dihedral group of order 36 as a permutation group) ~~~~~~~~~~~~~~~~~~~~~~ :: @@ -92,8 +92,8 @@ sage: uniqS3 = all_sylow(G, 3) sage: uniqS3 [Permutation Group with generators - [(1,7,13)(2,8,14)(3,9,15)(4,10,16)(5,11,17)(6,12,18), - (1,15,11,7,3,17,13,9,5)(2,16,12,8,4,18,14,10,6)]] + [(1,15,11,7,3,17,13,9,5)(2,16,12,8,4,18,14,10,6), + (1,7,13)(2,8,14)(3,9,15)(4,10,16)(5,11,17)(6,12,18)]] ~~~~~~~~~~~~~~~~~~~~~~ :: @@ -117,8 +117,8 @@ sage: S3 = G.sylow_subgroup(3) sage: N2 = G.normalizer(S2); N2 Subgroup generated by - [(2,18)(3,17)(4,16)(5,15)(6,14)(7,13)(8,12)(9,11), - (1,10)(2,11)(3,12)(4,13)(5,14)(6,15)(7,16)(8,17)(9,18)] + [(1,10)(2,11)(3,12)(4,13)(5,14)(6,15)(7,16)(8,17)(9,18), + (2,18)(3,17)(4,16)(5,15)(6,14)(7,13)(8,12)(9,11)] of (Dihedral group of order 36 as a permutation group) ~~~~~~~~~~~~~~~~~~~~~~ :: @@ -129,11 +129,12 @@ ~~~~~~~~~~~~~~~~~~~~~~ :: sage: N3 = G.normalizer(S3); N3 - Subgroup generated by [(2,18)(3,17)(4,16)(5,15)(6,14)(7,13)(8,12)(9,11), - (1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18), - (1,7,13)(2,8,14)(3,9,15)(4,10,16)(5,11,17)(6,12,18), - (1,15,11,7,3,17,13,9,5)(2,16,12,8,4,18,14,10,6)] of (Dihedral group of - order 36 as a permutation group) + Subgroup generated by + [(1,15,11,7,3,17,13,9,5)(2,16,12,8,4,18,14,10,6), + (1,7,13)(2,8,14)(3,9,15)(4,10,16)(5,11,17)(6,12,18), + (2,18)(3,17)(4,16)(5,15)(6,14)(7,13)(8,12)(9,11), + (1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18)] + of (Dihedral group of order 36 as a permutation group) ~~~~~~~~~~~~~~~~~~~~~~ :: @@ -154,10 +155,10 @@ sage: N = G.normalizer(H) sage: N Subgroup generated by - [(1,2)(3,18)(4,17)(5,16)(6,15)(7,14)(8,13)(9,12)(10,11), - (1,5)(2,4)(6,18)(7,17)(8,16)(9,15)(10,14)(11,13), - (1,7,13)(2,8,14)(3,9,15)(4,10,16)(5,11,17)(6,12,18)] of (Dihedral group of - order 36 as a permutation group) + [(1,5)(2,4)(6,18)(7,17)(8,16)(9,15)(10,14)(11,13), + (1,7,13)(2,8,14)(3,9,15)(4,10,16)(5,11,17)(6,12,18), + (1,2)(3,18)(4,17)(5,16)(6,15)(7,14)(8,13)(9,12)(10,11)] + of (Dihedral group of order 36 as a permutation group) ~~~~~~~~~~~~~~~~~~~~~~ :: From 1b7d79406555e94a1e0de31646a84a982b63d4d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Tue, 12 Mar 2024 20:03:28 +0100 Subject: [PATCH 232/518] swap lines, return first Co-authored-by: Travis Scrimshaw --- src/sage/combinat/q_analogues.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sage/combinat/q_analogues.py b/src/sage/combinat/q_analogues.py index 9f384fbc285..a3447a1e8e9 100644 --- a/src/sage/combinat/q_analogues.py +++ b/src/sage/combinat/q_analogues.py @@ -330,11 +330,11 @@ def q_binomial(n, k, q=None, algorithm='auto'): from sage.rings.polynomial.polynomial_element import Polynomial is_polynomial = isinstance(q, Polynomial) - if n >= 0: - k = min(n - k, k) # Pick the smallest k - else: + if n < 0: return (-1)**k * q**(k * n - (k * k - k) // 2) * q_binomial(-n + k - 1, k, q=q) + k = min(n - k, k) # Pick the smallest k + # We support non-Sage Elements too, where parent(q) is really # type(q). The calls R(0) and R(1) should work in all cases to # generate the correct 0 and 1 elements. From 1e646f17c7a4e015617b85e5611c2be1e4cd3490 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Tue, 12 Mar 2024 20:29:18 +0100 Subject: [PATCH 233/518] use Laurent polys for negative n --- src/sage/combinat/q_analogues.py | 33 +++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/src/sage/combinat/q_analogues.py b/src/sage/combinat/q_analogues.py index a3447a1e8e9..0944c52edf8 100644 --- a/src/sage/combinat/q_analogues.py +++ b/src/sage/combinat/q_analogues.py @@ -16,6 +16,8 @@ from sage.misc.cachefunc import cached_function from sage.misc.misc_c import prod from sage.rings.integer_ring import ZZ +from sage.rings.polynomial.laurent_polynomial_ring import LaurentPolynomialRing +from sage.rings.polynomial.polynomial_ring import polygen from sage.structure.element import parent @@ -44,7 +46,7 @@ def q_int(n, q=None): sage: q_int(3) q^2 + q + 1 sage: q_int(-3) - (-q^2 - q - 1)/q^3 + -q^-3 - q^-2 - q^-1 sage: p = ZZ['p'].0 sage: q_int(3,p) p^2 + p + 1 @@ -68,7 +70,10 @@ def q_int(n, q=None): if n not in ZZ: raise ValueError(f'{n} must be an integer') if q is None: - q = ZZ['q'].gen() + if n >= 0: + q = polygen(ZZ, 'q') + else: + q = LaurentPolynomialRing(ZZ, 'q').gen() if n == 0: return parent(q)(0) if n > 0: @@ -193,9 +198,9 @@ def q_binomial(n, k, q=None, algorithm='auto'): For `k \geq 0`, the `q`-binomial coefficient is extended as a polynomial in `n`:: sage: q_binomial(-4,1) - (-q^3 - q^2 - q - 1)/q^4 + -q^-4 - q^-3 - q^-2 - q^-1 sage: q_binomial(-2,3) - (-q^3 - q^2 - q - 1)/q^9 + -q^-9 - q^-8 - q^-7 - q^-6 Other variables can be used, given as third parameter:: @@ -323,9 +328,11 @@ def q_binomial(n, k, q=None, algorithm='auto'): # polynomiality test if q is None: - from sage.rings.polynomial.polynomial_ring import polygen - q = polygen(ZZ, name='q') is_polynomial = True + if n >= 0: + q = polygen(ZZ, 'q') + else: + q = LaurentPolynomialRing(ZZ, 'q').gen() else: from sage.rings.polynomial.polynomial_element import Polynomial is_polynomial = isinstance(q, Polynomial) @@ -333,7 +340,7 @@ def q_binomial(n, k, q=None, algorithm='auto'): if n < 0: return (-1)**k * q**(k * n - (k * k - k) // 2) * q_binomial(-n + k - 1, k, q=q) - k = min(n - k, k) # Pick the smallest k + k = min(n - k, k) # Pick the smallest k # We support non-Sage Elements too, where parent(q) is really # type(q). The calls R(0) and R(1) should work in all cases to @@ -608,7 +615,7 @@ def q_pochhammer(n, a, q=None): - :wikipedia:`Q-Pochhammer_symbol` """ if q is None: - q = ZZ['q'].gen() + q = polygen(ZZ, 'q') if n not in ZZ: raise ValueError("{} must be an integer".format(n)) R = parent(q) @@ -677,13 +684,13 @@ def q_jordan(t, q=None): - Xavier Caruso (2012-06-29) """ if q is None: - q = ZZ['q'].gen() + q = polygen(ZZ, 'q') if all(part == 0 for part in t): return parent(q)(1) tj = 0 res = parent(q)(0) - for i in range(len(t)-1, -1, -1): + for i in range(len(t) - 1, -1, -1): ti = t[i] if ti > tj: tp = list(t) @@ -827,7 +834,7 @@ def q_subgroups_of_abelian_group(la, mu, q=None, algorithm='birkhoff'): - Tomer Bauer (2013, 2018): Implemented the Birkhoff algorithm and refactoring """ if q is None: - q = ZZ['q'].gen() + q = polygen(ZZ, 'q') la_c = _Partitions(la).conjugate() mu_c = _Partitions(mu).conjugate() k = mu_c.length() @@ -910,7 +917,7 @@ def q_stirling_number1(n, k, q=None): - [Ca1954]_ """ if q is None: - q = ZZ['q'].gen() + q = polygen(ZZ, 'q') if n < 0: raise ValueError('q-Stirling numbers are not defined for n < 0') if n == 0 == k: @@ -972,7 +979,7 @@ def q_stirling_number2(n, k, q=None): - [Mil1978]_ """ if q is None: - q = ZZ['q'].gen() + q = polygen(ZZ, 'q') if n < 0: raise ValueError('q-Stirling numbers are not defined for n < 0') if n == 0 == k: From 2a51c3e87f2d3bd47affa687771b7979f8df8030 Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Wed, 13 Mar 2024 06:31:06 +0900 Subject: [PATCH 234/518] Fixing one last doctest. --- src/sage/groups/class_function.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/groups/class_function.py b/src/sage/groups/class_function.py index 7dd5eb30cda..901b0083b49 100644 --- a/src/sage/groups/class_function.py +++ b/src/sage/groups/class_function.py @@ -717,7 +717,7 @@ def restrict(self, H): Character of Symmetric group of order 5! as a permutation group sage: H = G.subgroup([(1,2,3), (1,2), (4,5)]) sage: chi.restrict(H) - Character of Subgroup generated by [(4,5), (1,2), (1,2,3)] of + Character of Subgroup generated by [(1,2,3), (1,2), (4,5)] of (Symmetric group of order 5! as a permutation group) sage: chi.restrict(H).values() [3, -3, -3, -1, 0, 0] From 47bb6188ccb0b2f887024f8366a825a2d72c021b Mon Sep 17 00:00:00 2001 From: dcoudert Date: Wed, 13 Mar 2024 11:41:27 +0100 Subject: [PATCH 235/518] graphs.triangulations(3) cannot return a triangle --- src/sage/graphs/graph_generators.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/sage/graphs/graph_generators.py b/src/sage/graphs/graph_generators.py index b5edc179ef9..5a09c617ba2 100644 --- a/src/sage/graphs/graph_generators.py +++ b/src/sage/graphs/graph_generators.py @@ -2237,6 +2237,12 @@ def triangulations(self, order, minimum_degree=None, minimum_connectivity=None, sage: g.is_isomorphic(graphs.OctahedralGraph()) # optional - plantri True + The minimum degree of a triangulation is 3, so the method can not output + a triangle:: + + sage: list(graphs.triangulations(3)) # optional - plantri + [] + An overview of the number of 5-connected triangulations on up to 22 vertices. This agrees with :oeis:`A081621`:: From 46e53c10a2abc31f522d82b9f5c857d6eb9c8694 Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Wed, 13 Mar 2024 10:58:30 +0000 Subject: [PATCH 236/518] remove duplicated functions --- .../hyperelliptic_curves/hyperelliptic_g2.py | 118 +----------------- 1 file changed, 4 insertions(+), 114 deletions(-) diff --git a/src/sage/schemes/hyperelliptic_curves/hyperelliptic_g2.py b/src/sage/schemes/hyperelliptic_curves/hyperelliptic_g2.py index 7a8fe986448..3b009444c9e 100644 --- a/src/sage/schemes/hyperelliptic_curves/hyperelliptic_g2.py +++ b/src/sage/schemes/hyperelliptic_curves/hyperelliptic_g2.py @@ -30,10 +30,10 @@ def is_odd_degree(self): return df % 2 == 1 elif df < 6: return False - else: - a0 = f.leading_coefficient() - c0 = h.leading_coefficient() - return (c0**2 + 4*a0) == 0 + + a0 = f.leading_coefficient() + c0 = h.leading_coefficient() + return (c0**2 + 4*a0) == 0 def jacobian(self): """ @@ -180,113 +180,3 @@ def absolute_igusa_invariants_kohel(self): """ f, h = self.hyperelliptic_polynomials() return invariants.absolute_igusa_invariants_kohel(4*f + h**2) - - def clebsch_invariants(self): - r""" - Return the Clebsch invariants `(A, B, C, D)` of Mestre, p 317, [Mes1991]_. - - .. SEEALSO:: - - :meth:`sage.schemes.hyperelliptic_curves.invariants` - - EXAMPLES:: - - sage: R. = QQ[] - sage: f = x^5 - x^4 + 3 - sage: HyperellipticCurve(f).clebsch_invariants() - (0, -2048/375, -4096/25, -4881645568/84375) - sage: HyperellipticCurve(f(2*x)).clebsch_invariants() - (0, -8388608/375, -1073741824/25, -5241627016305836032/84375) - - sage: HyperellipticCurve(f, x).clebsch_invariants() - (-8/15, 17504/5625, -23162896/140625, -420832861216768/7119140625) - sage: HyperellipticCurve(f(2*x), 2*x).clebsch_invariants() - (-512/15, 71696384/5625, -6072014209024/140625, -451865844002031331704832/7119140625) - - TESTS:: - - sage: # optional - magma - sage: magma(HyperellipticCurve(f)).ClebschInvariants() - [ 0, -2048/375, -4096/25, -4881645568/84375 ] - sage: magma(HyperellipticCurve(f(2*x))).ClebschInvariants() - [ 0, -8388608/375, -1073741824/25, -5241627016305836032/84375 ] - sage: magma(HyperellipticCurve(f, x)).ClebschInvariants() - [ -8/15, 17504/5625, -23162896/140625, -420832861216768/7119140625 ] - sage: magma(HyperellipticCurve(f(2*x), 2*x)).ClebschInvariants() - [ -512/15, 71696384/5625, -6072014209024/140625, -451865844002031331704832/7119140625 ] - """ - f, h = self.hyperelliptic_polynomials() - return invariants.clebsch_invariants(4*f + h**2) - - def igusa_clebsch_invariants(self): - r""" - Return the Igusa-Clebsch invariants `I_2, I_4, I_6, I_{10}` of Igusa and Clebsch [IJ1960]_. - - .. SEEALSO:: - - :meth:`sage.schemes.hyperelliptic_curves.invariants` - - EXAMPLES:: - - sage: R. = QQ[] - sage: f = x^5 - x + 2 - sage: HyperellipticCurve(f).igusa_clebsch_invariants() - (-640, -20480, 1310720, 52160364544) - sage: HyperellipticCurve(f(2*x)).igusa_clebsch_invariants() - (-40960, -83886080, 343597383680, 56006764965979488256) - - sage: HyperellipticCurve(f, x).igusa_clebsch_invariants() - (-640, 17920, -1966656, 52409511936) - sage: HyperellipticCurve(f(2*x), 2*x).igusa_clebsch_invariants() - (-40960, 73400320, -515547070464, 56274284941110411264) - - TESTS:: - - sage: magma(HyperellipticCurve(f)).IgusaClebschInvariants() # optional - magma - [ -640, -20480, 1310720, 52160364544 ] - sage: magma(HyperellipticCurve(f(2*x))).IgusaClebschInvariants() # optional - magma - [ -40960, -83886080, 343597383680, 56006764965979488256 ] - - sage: magma(HyperellipticCurve(f, x)).IgusaClebschInvariants() # optional - magma - [ -640, 17920, -1966656, 52409511936 ] - sage: magma(HyperellipticCurve(f(2*x), 2*x)).IgusaClebschInvariants() # optional - magma - [ -40960, 73400320, -515547070464, 56274284941110411264 ] - """ - f, h = self.hyperelliptic_polynomials() - return invariants.igusa_clebsch_invariants(4*f + h**2) - - def absolute_igusa_invariants_wamelen(self): - r""" - Return the three absolute Igusa invariants used by van Wamelen [Wam1999]_. - - EXAMPLES:: - - sage: R. = QQ[] - sage: HyperellipticCurve(x^5 - 1).absolute_igusa_invariants_wamelen() - (0, 0, 0) - sage: HyperellipticCurve((x^5 - 1)(x - 2), (x^2)(x - 2)).absolute_igusa_invariants_wamelen() - (0, 0, 0) - """ - f, h = self.hyperelliptic_polynomials() - return invariants.absolute_igusa_invariants_wamelen(4*f + h**2) - - def absolute_igusa_invariants_kohel(self): - r""" - Return the three absolute Igusa invariants used by Kohel [KohECHIDNA]_. - - .. SEEALSO:: - - :meth:`sage.schemes.hyperelliptic_curves.invariants` - - EXAMPLES:: - - sage: R. = QQ[] - sage: HyperellipticCurve(x^5 - 1).absolute_igusa_invariants_kohel() - (0, 0, 0) - sage: HyperellipticCurve(x^5 - x + 1, x^2).absolute_igusa_invariants_kohel() - (-1030567/178769, 259686400/178769, 20806400/178769) - sage: HyperellipticCurve((x^5 - x + 1)(3*x + 1), (x^2)(3*x + 1)).absolute_igusa_invariants_kohel() - (-1030567/178769, 259686400/178769, 20806400/178769) - """ - f, h = self.hyperelliptic_polynomials() - return invariants.absolute_igusa_invariants_kohel(4*f + h**2) From d6863c20deaaa72b161ca0d8d460a2d70c0538dc Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Wed, 13 Mar 2024 11:09:56 +0000 Subject: [PATCH 237/518] ensure degree is an Integer --- .../multi_polynomial_libsingular.pyx | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/sage/rings/polynomial/multi_polynomial_libsingular.pyx b/src/sage/rings/polynomial/multi_polynomial_libsingular.pyx index ec4aca5733c..7f2f7d89385 100644 --- a/src/sage/rings/polynomial/multi_polynomial_libsingular.pyx +++ b/src/sage/rings/polynomial/multi_polynomial_libsingular.pyx @@ -2659,13 +2659,25 @@ cdef class MPolynomial_libsingular(MPolynomial_libsingular_base): 1 sage: poly.degree(S.1) 2 + + Ensure that :issue:`37603` is fixed:: + + sage: R. = QQ[] + sage: f = x + y + 1 + sage: type(f.degree()) + + sage: type(f.degree(x)) + + sage: type(f.degree(y)) + + """ cdef ring *r = self._parent_ring cdef poly *p = self._poly if not x: if std_grading: - return self.total_degree(std_grading=True) - return singular_polynomial_deg(p, NULL, r) + return Integer(self.total_degree(std_grading=True)) + return Integer(singular_polynomial_deg(p, NULL, r)) if not x.parent() is self.parent(): try: @@ -2675,7 +2687,7 @@ cdef class MPolynomial_libsingular(MPolynomial_libsingular_base): if not x.is_generator(): raise TypeError("argument is not a generator") - return singular_polynomial_deg(p, x._poly, r) + return Integer(singular_polynomial_deg(p, x._poly, r)) def total_degree(self, int std_grading=False): """ From c2de698cf2224f8fa479b8f52ffa03b864b4ef0e Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Wed, 13 Mar 2024 14:08:42 +0000 Subject: [PATCH 238/518] ensure degrees() and total_degree() methods lso return Integers --- .../multi_polynomial_libsingular.pyx | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/src/sage/rings/polynomial/multi_polynomial_libsingular.pyx b/src/sage/rings/polynomial/multi_polynomial_libsingular.pyx index 7f2f7d89385..b2557287b47 100644 --- a/src/sage/rings/polynomial/multi_polynomial_libsingular.pyx +++ b/src/sage/rings/polynomial/multi_polynomial_libsingular.pyx @@ -2739,6 +2739,14 @@ cdef class MPolynomial_libsingular(MPolynomial_libsingular_base): -1 sage: R(1).total_degree() 0 + + Ensure that :issue:`37603` is fixed:: + sage: R. = QQ[] + sage: f = x^4 + y + z + sage: f.total_degree() + 4 + sage: type(f.total_degree()) + """ cdef int i, result cdef poly *p = self._poly @@ -2749,8 +2757,8 @@ cdef class MPolynomial_libsingular(MPolynomial_libsingular_base): while p: result = max(result, sum([p_GetExp(p,i,r) for i in range(1,r.N+1)])) p = pNext(p) - return result - return singular_polynomial_deg(p, NULL, r) + return Integer(result) + return Integer(singular_polynomial_deg(p, NULL, r)) def degrees(self): """ @@ -2767,6 +2775,17 @@ cdef class MPolynomial_libsingular(MPolynomial_libsingular_base): (1, 2, 1) sage: (q + y0^5).degrees() (5, 2, 1) + + TESTS: + + Ensure that :issue:`37603` is fixed:: + + sage: R. = QQ[] + sage: f = x^4 + y + z + sage: f.degrees() + (4, 1, 1) + sage: type(f.degrees()[0]) + """ cdef poly *p = self._poly cdef ring *r = self._parent_ring @@ -2776,7 +2795,7 @@ cdef class MPolynomial_libsingular(MPolynomial_libsingular_base): for i from 0 <= i < r.N: d[i] = max(d[i],p_GetExp(p, i+1, r)) p = pNext(p) - return tuple(d) + return tuple(map(Integer, d)) def coefficient(self, degrees): """ From 91da2084e6dafdbbb550f91391e0370c0d439897 Mon Sep 17 00:00:00 2001 From: Lorenz Panny Date: Wed, 13 Mar 2024 15:36:13 +0100 Subject: [PATCH 239/518] fix doctest failures from Python ints --- src/sage/dynamics/arithmetic_dynamics/wehlerK3.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/sage/dynamics/arithmetic_dynamics/wehlerK3.py b/src/sage/dynamics/arithmetic_dynamics/wehlerK3.py index 4125ff5a377..7d775faf96a 100644 --- a/src/sage/dynamics/arithmetic_dynamics/wehlerK3.py +++ b/src/sage/dynamics/arithmetic_dynamics/wehlerK3.py @@ -1668,7 +1668,7 @@ def lambda_plus(self, P, v, N, m, n, prec=100): l = L.index(max(L)) newP = copy(PK) #normalize PK - newP.scale_by([1/PK[0][i],1]) + newP.scale_by([~PK[0][i], ZZ.one()]) #Find B and A, helper values for the local height if PK[1][j].abs() <= PK[1][l].abs(): @@ -1679,7 +1679,7 @@ def lambda_plus(self, P, v, N, m, n, prec=100): #Normalize Q newQ = copy(Q) - newQ.scale_by([1,1/Q[1][l]]) + newQ.scale_by([ZZ.one(), ~Q[1][l]]) if PK[0][i].abs() <= PK[0][k].abs(): A = Ry(W.Gpoly(0, k))(tuple(newQ[1]))*PK[0][i]/PK[0][k] @@ -1691,7 +1691,7 @@ def lambda_plus(self, P, v, N, m, n, prec=100): i = k j = l - newQ.scale_by([1/Q[0][k], 1]) + newQ.scale_by([~Q[0][k], ZZ.one()]) PK = newQ return local_height @@ -1763,7 +1763,7 @@ def lambda_minus(self, P, v, N, m, n, prec=100): k = L.index(max(L)) #Normalize the point newP = copy(PK) - newP.scale_by([1, 1/PK[1][i]]) + newP.scale_by([ZZ.one(), ~PK[1][i]]) #Find A and B, helper functions for computing local height if PK[0][j].abs() <= PK[0][l].abs(): B = Ry(W.Gpoly(0, l))(tuple(newP[1]))*PK[0][j]/PK[0][l] @@ -1773,7 +1773,7 @@ def lambda_minus(self, P, v, N, m, n, prec=100): #Normalize Q newQ = copy(Q) - newQ.scale_by([1/Q[0][l], 1]) + newQ.scale_by([~Q[0][l], ZZ.one()]) if PK[1][i].abs() <= PK[1][k].abs(): A = Rx(W.Gpoly(1, k))(tuple(newQ[0]))*PK[1][i]/PK[1][k] @@ -1785,7 +1785,7 @@ def lambda_minus(self, P, v, N, m, n, prec=100): local_height += beta**(-2*R(e)-1)*R(A.abs()).log() + beta**(-2*R(e))*R(B.abs()).log() i = k j = l - newQ.scale_by([1, 1/Q[1][k]]) + newQ.scale_by([ZZ.one(), ~Q[1][k]]) PK = newQ return local_height From 2dcc2c6423728a49f57433cb2c777836f641991f Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 6 Jan 2024 13:21:37 -0800 Subject: [PATCH 240/518] make SPKG-check for normal/wheel/script packages via sage-spkg --- build/bin/sage-spkg | 16 ++++++++++------ build/make/Makefile.in | 22 ++++++---------------- 2 files changed, 16 insertions(+), 22 deletions(-) diff --git a/build/bin/sage-spkg b/build/bin/sage-spkg index 6b3915bea52..0f053301f6f 100755 --- a/build/bin/sage-spkg +++ b/build/bin/sage-spkg @@ -788,8 +788,6 @@ run_test_suite() { ############################################### # SAGE_INST_LOCAL. It might make more sense to run the tests before, but the # spkg-check scripts were written before use of DESTDIR installs, and so # fail in many cases. This might be good to change later. - -if [ "$SAGE_CHECK" = "yes" -o "$SAGE_CHECK" = "warn" ]; then if [ -f spkg-check ]; then echo "Running the test suite for $PKG_NAME..." time ./spkg-check @@ -807,11 +805,13 @@ if [ "$SAGE_CHECK" = "yes" -o "$SAGE_CHECK" = "warn" ]; then TEST_SUITE_RESULT="passed" echo "Passed the test suite for $PKG_NAME." fi + elif [ -f spkg-check.in ]; then + echo "The test suite for $PKG_NAME requires the temporary build directory." + exit 1 else echo "Package $PKG_NAME has no test suite." TEST_SUITE_RESULT="not available" fi -fi } ################################################# run_test_suite write_installation_record() { #################################### @@ -889,17 +889,21 @@ if [ $INSTALL = 1 ]; then extract_the_package prepare_for_installation actually_build_and_install -else - cd "$SAGE_BUILD_DIR/$PKG_NAME" || exit $? fi if [ $POST_INSTALL = 1 ]; then + cd "$SAGE_BUILD_DIR/$PKG_NAME" || exit $? unload_destdir install_scripts post_install fi -run_test_suite +if [ "$SAGE_CHECK" = "yes" -o "$SAGE_CHECK" = "warn" ]; then + if ! cd "$SAGE_BUILD_DIR/$PKG_NAME" 2>/dev/null; then + cd "$PKG_SCRIPTS" || exit $? + fi + run_test_suite +fi if [ $POST_INSTALL = 1 ]; then write_installation_record diff --git a/build/make/Makefile.in b/build/make/Makefile.in index dc8cb8a6cd7..28003ba0aff 100644 --- a/build/make/Makefile.in +++ b/build/make/Makefile.in @@ -614,6 +614,11 @@ $(1)-$(4)-no-deps: $(1)-no-deps: $(1)-$(4)-no-deps +$(1)-$(4)-check: + $(PLUS)@sage-logger -p 'PATH=$$(SAGE_SRC)/bin:$$($(4))/bin:$$$$PATH $$(SAGE_SPKG) --check-only $(1)-$(2) $$($(4))' '$$(SAGE_LOGS)/$(1)-$(2).log' + +$(1)-check: $(1)-$(4)-check + $(1)-$(4)-uninstall: if [ -d '$$($(4))' ]; then \ sage-spkg-uninstall $(if $(filter $(1),$(TOOLCHAIN_DEPS)),--keep-files) \ @@ -760,22 +765,7 @@ $(1)-$(4)-no-deps: $(1)-no-deps: $(1)-$(4)-no-deps $(1)-$(4)-check: - $(PLUS)@if [ -x $$(SAGE_ROOT)/build/pkgs/$(1)/spkg-check ]; then \ - cd '$$(SAGE_ROOT)/build/pkgs/$(1)' && \ - . '$$(SAGE_ROOT)/src/bin/sage-src-env-config' && \ - . '$$(SAGE_ROOT)/src/bin/sage-env-config' && \ - . '$$(SAGE_ROOT)/src/bin/sage-env' && \ - . '$$(SAGE_ROOT)/build/bin/sage-build-env-config' && \ - . '$$(SAGE_ROOT)/build/bin/sage-build-env' && \ - PKG_BASE="$(1)" \ - PKG_VER="$(2)" \ - PKG_NAME="$(1)-$(2)" \ - SAGE_SPKG_WHEELS=$$($(4))/var/lib/sage/wheels \ - SAGE_SPKG_SCRIPTS=$$($(4))/var/lib/sage/scripts \ - SAGE_INST_LOCAL=$$($(4)) \ - SAGE_CHECK=$$(SAGE_CHECK_$(1)) \ - sage-logger -p '$$(SAGE_ROOT)/build/pkgs/$(1)/spkg-check' '$$(SAGE_LOGS)/$(1)-$(2).log'; \ - fi + $(PLUS)@sage-logger -p 'PATH=$$(SAGE_SRC)/bin:$$($(4))/bin:$$$$PATH $$(SAGE_SPKG) --check-only $(1)-$(2) $$($(4))' '$$(SAGE_LOGS)/$(1)-$(2).log' $(1)-check: $(1)-$(4)-check From 5f9089c91e08c7b58ae29de1a96261ebbb4e74d1 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 6 Jan 2024 15:43:55 -0800 Subject: [PATCH 241/518] build/bin/sage-spkg: Make spkg-check an installed script --- build/bin/sage-spkg | 69 +++++++++++++++++++++++++++++---------------- 1 file changed, 45 insertions(+), 24 deletions(-) diff --git a/build/bin/sage-spkg b/build/bin/sage-spkg index 0f053301f6f..74163f27d58 100755 --- a/build/bin/sage-spkg +++ b/build/bin/sage-spkg @@ -355,8 +355,9 @@ export SAGE_DESTDIR="${SAGE_BUILD_DIR}/${PKG_NAME}/inst" # end of spkg-install. export SAGE_DESTDIR_LOCAL="${SAGE_DESTDIR}${SAGE_INST_LOCAL}" -INSTALLED_SCRIPTS="prerm piprm postrm" -WRAPPED_SCRIPTS="build install check preinst postinst $INSTALLED_SCRIPTS" +INSTALLED_SCRIPTS_DEST="$SAGE_SPKG_SCRIPTS/$PKG_BASE" +INSTALLED_SCRIPTS="prerm piprm postrm check" +WRAPPED_SCRIPTS="build install preinst pipinst postinst $INSTALLED_SCRIPTS" warning_for_experimental_packages() { ############################ @@ -490,7 +491,7 @@ if [ -z "$PKG_NAME_UPSTREAM" ]; then if [ -d "$PKG_SCRIPTS"/src ]; then ln -s $(cd "$PKG_SCRIPTS"/src && pwd -P) "$PKG_NAME"/src fi - for a in build install check preinst postinst; do # replace by use of $WRAPPED_SCRIPTS later + for a in $WRAPPED_SCRIPTS; do if [ -r "$PKG_SCRIPTS"/spkg-$a.in ]; then cp "$PKG_SCRIPTS"/spkg-$a.in "$PKG_NAME"/ elif [ -x "$PKG_SCRIPTS"/spkg-$a ]; then @@ -544,6 +545,7 @@ prepare_for_installation() { ##################################### write_script_wrapper() { local script="$1" local script_dir="$2" + local fallback_script_dir="$3" trap "echo >&2 Error: Unexpected error writing wrapper script for $script; exit \$_" ERR @@ -584,14 +586,21 @@ if [ \$? -ne 0 ]; then exit 1 fi -cd "\$SAGE_PKG_DIR" +__EOF__ + if [ -n "$fallback_script_dir" ]; then + cat >> "$tmpscript" <<__EOF__ +cd "\$SAGE_PKG_DIR" 2>/dev/null || cd "$fallback_script_dir" + +__EOF__ + else + cat >> "$tmpscript" <<__EOF__ if [ \$? -ne 0 ]; then echo >&2 "Error: could not cd to the package build directory \$SAGE_PKG_DIR" exit 1 fi __EOF__ - + fi cat "$script.in" >> "$tmpscript" mv "$tmpscript" "$script" chmod +x "$script" @@ -607,20 +616,29 @@ touch spkg-piprm.in # or sdh_store_and_pip_install_wheel. touch spkg-pipinst.in -for script in $WRAPPED_SCRIPTS pipinst; do # pipinst can be added to WRAPPED_SCRIPTS later +for script in $WRAPPED_SCRIPTS; do # 'Installed' scripts are not run immediately out of the package build - # directory, and may be run later, so set their root directory to - # $SAGE_ROOT - if echo "$INSTALLED_SCRIPTS" | grep -w "$script" > /dev/null; then - script_dir="$SAGE_ROOT" - else - script_dir="$(pwd)" - fi + # directory, and may be run later. + # For the installed *rm scripts, set their root directory to $SAGE_ROOT. + # For the installed check scripts, some need the temporary build directory, + # others don't. So try to run out of the temporary build directory + # but fall back to $SAGE_ROOT. + case $script in + check) script_dir="$(pwd)" + fallback_script_dir="\$SAGE_ROOT" + ;; + *rm) script_dir="\$SAGE_ROOT" + fallback_script_dir= + ;; + *) script_dir="$(pwd)" + fallback_script_dir= + ;; + esac script="spkg-$script" if [ -f "$script.in" ]; then - write_script_wrapper "$(pwd)/$script" "$script_dir" + write_script_wrapper "$(pwd)/$script" "$script_dir" "$fallback_script_dir" fi done } ####################################### prepare_for_installation @@ -735,15 +753,14 @@ install_scripts() { ############################################## # Some spkg scripts, if they exist, should also be installed to # $SAGE_SPKG_SCRIPTS; they are not included in the package's manifest, but are # removed by sage-spkg-uninstall -INSTALLED_SCRIPTS_DEST="$SAGE_SPKG_SCRIPTS/$PKG_BASE" -if [ ! -f $INSTALLED_SCRIPTS_DEST/spkg-requirements.txt ]; then +if [ ! -f "$INSTALLED_SCRIPTS_DEST"/spkg-requirements.txt ]; then # No packages to uninstall with pip, so remove the prepared uninstall script # and the prepared deferred installation script rm -f spkg-piprm spkg-piprm.in spkg-pipinst spkg-pipinst.in fi -for script in $INSTALLED_SCRIPTS pipinst; do # pipinst can be added to WRAPPED_SCRIPTS later +for script in $INSTALLED_SCRIPTS; do script="spkg-$script" if [ -f "$script" ]; then @@ -788,9 +805,14 @@ run_test_suite() { ############################################### # SAGE_INST_LOCAL. It might make more sense to run the tests before, but the # spkg-check scripts were written before use of DESTDIR installs, and so # fail in many cases. This might be good to change later. - if [ -f spkg-check ]; then + if ! cd "$SAGE_BUILD_DIR/$PKG_NAME" 2>/dev/null; then + cd "$PKG_SCRIPTS" || exit $? + fi + + if [ -f "$INSTALLED_SCRIPTS_DEST"/spkg-check ]; then echo "Running the test suite for $PKG_NAME..." - time ./spkg-check + export PKG_BASE + time "$INSTALLED_SCRIPTS_DEST"/spkg-check if [ $? -ne 0 ]; then TEST_SUITE_RESULT="failed" if [ "$SAGE_CHECK" = "warn" ]; then @@ -805,8 +827,10 @@ run_test_suite() { ############################################### TEST_SUITE_RESULT="passed" echo "Passed the test suite for $PKG_NAME." fi - elif [ -f spkg-check.in ]; then - echo "The test suite for $PKG_NAME requires the temporary build directory." + elif [ -f "$PKG_SCRIPTS"/spkg-check.in -o -f "$PKG_SCRIPTS"/spkg-check ]; then + echo "The test suite for $PKG_NAME cannot be run because the script" + echo "$INSTALLED_SCRIPTS_DEST/spkg-check" + echo "is missing. Install/re-install package $PKG_NAME to run the test suite." exit 1 else echo "Package $PKG_NAME has no test suite." @@ -899,9 +923,6 @@ if [ $POST_INSTALL = 1 ]; then fi if [ "$SAGE_CHECK" = "yes" -o "$SAGE_CHECK" = "warn" ]; then - if ! cd "$SAGE_BUILD_DIR/$PKG_NAME" 2>/dev/null; then - cd "$PKG_SCRIPTS" || exit $? - fi run_test_suite fi From 48ed5bd332840c24b0aaf9040ba1dbf87ec838ca Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 9 Jan 2024 14:34:06 -0800 Subject: [PATCH 242/518] build/bin/sage-spkg: For spkg-check, fall back to PKG_SCRIPTS, not SAGE_ROOT --- build/bin/sage-spkg | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build/bin/sage-spkg b/build/bin/sage-spkg index 74163f27d58..e56c6949fd9 100755 --- a/build/bin/sage-spkg +++ b/build/bin/sage-spkg @@ -621,11 +621,11 @@ for script in $WRAPPED_SCRIPTS; do # directory, and may be run later. # For the installed *rm scripts, set their root directory to $SAGE_ROOT. # For the installed check scripts, some need the temporary build directory, - # others don't. So try to run out of the temporary build directory - # but fall back to $SAGE_ROOT. + # others are OK with $PKG_SCRIPTS. So try to run out of the temporary + # build directory but fall back to the latter. case $script in check) script_dir="$(pwd)" - fallback_script_dir="\$SAGE_ROOT" + fallback_script_dir="$PKG_SCRIPTS" ;; *rm) script_dir="\$SAGE_ROOT" fallback_script_dir= From 3b413e470e78d5035dfa66556ec272b434f873f6 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 7 Mar 2024 21:29:02 -0800 Subject: [PATCH 243/518] build/bin/sage-spkg: Add missing 'cd' --- build/bin/sage-spkg | 1 + 1 file changed, 1 insertion(+) diff --git a/build/bin/sage-spkg b/build/bin/sage-spkg index e56c6949fd9..f30e38fa5c6 100755 --- a/build/bin/sage-spkg +++ b/build/bin/sage-spkg @@ -594,6 +594,7 @@ cd "\$SAGE_PKG_DIR" 2>/dev/null || cd "$fallback_script_dir" __EOF__ else cat >> "$tmpscript" <<__EOF__ +cd "\$SAGE_PKG_DIR" if [ \$? -ne 0 ]; then echo >&2 "Error: could not cd to the package build directory \$SAGE_PKG_DIR" exit 1 From bbcc6e4464620b7ffd63d00554f2a3630178f545 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 13 Mar 2024 20:55:33 -0700 Subject: [PATCH 244/518] src/sage/matrix: Doctest cosmetics --- src/sage/matrix/action.pyx | 35 ++- src/sage/matrix/args.pyx | 102 +++++---- src/sage/matrix/compute_J_ideal.py | 4 +- src/sage/matrix/constructor.pyx | 68 +++--- src/sage/matrix/matrix0.pyx | 180 ++++++++-------- src/sage/matrix/matrix1.pyx | 54 ++--- src/sage/matrix/matrix2.pyx | 248 ++++++++++++---------- src/sage/matrix/matrix_generic_sparse.pyx | 3 +- src/sage/matrix/matrix_space.py | 16 +- src/sage/matrix/matrix_sparse.pyx | 2 +- src/sage/matrix/symplectic_basis.py | 2 +- 11 files changed, 392 insertions(+), 322 deletions(-) diff --git a/src/sage/matrix/action.pyx b/src/sage/matrix/action.pyx index 4bb1f4bbe2a..18c71299317 100644 --- a/src/sage/matrix/action.pyx +++ b/src/sage/matrix/action.pyx @@ -19,11 +19,15 @@ Actions used by the coercion model for matrix and vector multiplications sage: MSZ = MatrixSpace(ZZ['x'], 2) sage: A = MSQ.get_action(MSZ) sage: A - Left action by Full MatrixSpace of 2 by 2 dense matrices over Rational Field on Full MatrixSpace of 2 by 2 dense matrices over Univariate Polynomial Ring in x over Integer Ring + Left action by Full MatrixSpace of 2 by 2 dense matrices over Rational Field + on Full MatrixSpace of 2 by 2 dense matrices + over Univariate Polynomial Ring in x over Integer Ring sage: import gc sage: _ = gc.collect() sage: A - Left action by Full MatrixSpace of 2 by 2 dense matrices over Rational Field on Full MatrixSpace of 2 by 2 dense matrices over Univariate Polynomial Ring in x over Integer Ring + Left action by Full MatrixSpace of 2 by 2 dense matrices over Rational Field + on Full MatrixSpace of 2 by 2 dense matrices + over Univariate Polynomial Ring in x over Integer Ring .. NOTE:: @@ -83,13 +87,17 @@ cdef class MatrixMulAction(Action): sage: MSQ = MatrixSpace(QQ, 2) sage: MSZ = MatrixSpace(ZZ['x'], 2) sage: A = MSQ.get_action(MSZ); A - Left action by Full MatrixSpace of 2 by 2 dense matrices over Rational Field on Full MatrixSpace of 2 by 2 dense matrices over Univariate Polynomial Ring in x over Integer Ring + Left action by Full MatrixSpace of 2 by 2 dense matrices over Rational Field + on Full MatrixSpace of 2 by 2 dense matrices + over Univariate Polynomial Ring in x over Integer Ring sage: A.actor() Full MatrixSpace of 2 by 2 dense matrices over Rational Field sage: A.domain() - Full MatrixSpace of 2 by 2 dense matrices over Univariate Polynomial Ring in x over Integer Ring + Full MatrixSpace of 2 by 2 dense matrices + over Univariate Polynomial Ring in x over Integer Ring sage: A.codomain() - Full MatrixSpace of 2 by 2 dense matrices over Univariate Polynomial Ring in x over Rational Field + Full MatrixSpace of 2 by 2 dense matrices + over Univariate Polynomial Ring in x over Rational Field """ def __init__(self, G, S, is_left): if not is_MatrixSpace(G): @@ -130,9 +138,13 @@ cdef class MatrixMatrixAction(MatrixMulAction): sage: MSQ = MatrixSpace(QQ, 3, 2) sage: from sage.matrix.action import MatrixMatrixAction sage: A = MatrixMatrixAction(MSR, MSQ); A - Left action by Full MatrixSpace of 3 by 3 dense matrices over Univariate Polynomial Ring in x over Integer Ring on Full MatrixSpace of 3 by 2 dense matrices over Rational Field + Left action + by Full MatrixSpace of 3 by 3 dense matrices + over Univariate Polynomial Ring in x over Integer Ring + on Full MatrixSpace of 3 by 2 dense matrices over Rational Field sage: A.codomain() - Full MatrixSpace of 3 by 2 dense matrices over Univariate Polynomial Ring in x over Rational Field + Full MatrixSpace of 3 by 2 dense matrices + over Univariate Polynomial Ring in x over Rational Field sage: A(matrix(R, 3, 3, x), matrix(QQ, 3, 2, range(6))) [ 0 x] [2*x 3*x] @@ -183,7 +195,8 @@ cdef class MatrixMatrixAction(MatrixMulAction): sage: MSR = MatrixSpace(R, 3, 3) sage: MSQ = MatrixSpace(QQ, 3, 2) sage: A = MatrixMatrixAction(MSR, MSQ); A - Left action by Full MatrixSpace of 3 by 3 dense matrices over Univariate Polynomial Ring in x over Integer Ring on Full MatrixSpace of 3 by 2 dense matrices over Rational Field + Left action by Full MatrixSpace of 3 by 3 dense matrices over Univariate Polynomial Ring in x over Integer Ring + on Full MatrixSpace of 3 by 2 dense matrices over Rational Field sage: A.codomain() Full MatrixSpace of 3 by 2 dense matrices over Univariate Polynomial Ring in x over Rational Field @@ -303,7 +316,8 @@ cdef class MatrixVectorAction(MatrixMulAction): sage: M = MatrixSpace(QQ, 5, 3) sage: V = VectorSpace(CDF, 3) # strong reference prevents garbage collection sage: A = MatrixVectorAction(M, V); A - Left action by Full MatrixSpace of 5 by 3 dense matrices over Rational Field on Vector space of dimension 3 over Complex Double Field + Left action by Full MatrixSpace of 5 by 3 dense matrices over Rational Field + on Vector space of dimension 3 over Complex Double Field sage: A.codomain() Vector space of dimension 5 over Complex Double Field """ @@ -354,7 +368,8 @@ cdef class VectorMatrixAction(MatrixMulAction): sage: V = VectorSpace(CDF, 3) sage: A = VectorMatrixAction(M, V) sage: A - Right action by Full MatrixSpace of 3 by 5 dense matrices over Rational Field on Vector space of dimension 3 over Complex Double Field + Right action by Full MatrixSpace of 3 by 5 dense matrices over Rational Field + on Vector space of dimension 3 over Complex Double Field sage: A.codomain() Vector space of dimension 5 over Complex Double Field """ diff --git a/src/sage/matrix/args.pyx b/src/sage/matrix/args.pyx index 6b247595059..91937c22206 100644 --- a/src/sage/matrix/args.pyx +++ b/src/sage/matrix/args.pyx @@ -156,24 +156,33 @@ cdef class MatrixArgs: sage: ma = MatrixArgs(2, 2, (x for x in range(4))); ma > sage: ma.finalized() - + Many types of input are possible:: - sage: ma = MatrixArgs(2, 2, entries=None); ma.finalized(); ma.matrix() - + sage: ma = MatrixArgs(2, 2, entries=None); ma.finalized() + + sage: ma.matrix() [0 0] [0 0] - sage: ma = MatrixArgs(2, 2, entries={}); ma.finalized(); ma.matrix() - + sage: ma = MatrixArgs(2, 2, entries={}); ma.finalized() + + sage: ma.matrix() [0 0] [0 0] - sage: ma = MatrixArgs(2, 2, entries=[1,2,3,4]); ma.finalized(); ma.matrix() - + sage: ma = MatrixArgs(2, 2, entries=[1,2,3,4]); ma.finalized() + + sage: ma.matrix() [1 2] [3 4] - sage: ma = MatrixArgs(2, 2, entries=math.pi); ma.finalized(); ma.matrix() - + sage: ma = MatrixArgs(2, 2, entries=math.pi); ma.finalized() + + sage: ma.matrix() [3.141592653589793 0.0] [ 0.0 3.141592653589793] sage: ma = MatrixArgs(2, 2, entries=pi); ma.finalized() # needs sage.symbolic @@ -182,16 +191,22 @@ cdef class MatrixArgs: sage: ma.matrix() # needs sage.symbolic [pi 0] [ 0 pi] - sage: ma = MatrixArgs(ZZ, 2, 2, entries={(0,0):7}); ma.finalized(); ma.matrix() - + sage: ma = MatrixArgs(ZZ, 2, 2, entries={(0,0): 7}); ma.finalized() + + sage: ma.matrix() [7 0] [0 0] - sage: ma = MatrixArgs(ZZ, 2, 2, entries=((1,2),(3,4))); ma.finalized(); ma.matrix() - + sage: ma = MatrixArgs(ZZ, 2, 2, entries=((1,2), (3,4))); ma.finalized() + + sage: ma.matrix() [1 2] [3 4] - sage: ma = MatrixArgs(ZZ, 2, 2, entries=(1,2,3,4)); ma.finalized(); ma.matrix() - + sage: ma = MatrixArgs(ZZ, 2, 2, entries=(1,2,3,4)); ma.finalized() + + sage: ma.matrix() [1 2] [3 4] @@ -218,14 +233,19 @@ cdef class MatrixArgs: sage: ma = MatrixArgs(entries=matrix(2,2)); ma.finalized(); ma.matrix() + sage: ma.matrix() [0 0] [0 0] - sage: ma = MatrixArgs(2, 2, entries=lambda i,j: 1+2*i+j); ma.finalized(); ma.matrix() - + sage: ma = MatrixArgs(2, 2, entries=lambda i,j: 1+2*i+j); ma.finalized() + + sage: ma.matrix() [1 2] [3 4] - sage: ma = MatrixArgs(ZZ, 2, 2, entries=lambda i,j: 1+2*i+j); ma.finalized(); ma.matrix() - > + sage: ma = MatrixArgs(ZZ, 2, 2, entries=lambda i,j: 1+2*i+j); ma.finalized() + > + sage: ma.matrix() [1 2] [3 4] @@ -263,9 +283,12 @@ cdef class MatrixArgs: [1 0 1] [1 1 0] - sage: ma = MatrixArgs([vector([0,1], sparse=True), vector([0,0], sparse=True)], sparse=True) - sage: ma.finalized(); ma.matrix() - + sage: ma = MatrixArgs([vector([0,1], sparse=True), + ....: vector([0,0], sparse=True)], sparse=True) + sage: ma.finalized() + + sage: ma.matrix() [0 1] [0 0] @@ -633,7 +656,8 @@ cdef class MatrixArgs: :: sage: ma = MatrixArgs(M); ma.finalized() - sage: ma.matrix() [0 1 2] @@ -642,8 +666,9 @@ cdef class MatrixArgs: :: sage: ma = MatrixArgs(M, sparse=False); ma.finalized() - + sage: ma.matrix() [0 1 2] [3 4 5] @@ -651,8 +676,9 @@ cdef class MatrixArgs: :: sage: ma = MatrixArgs(RDF, M); ma.finalized() - + sage: ma.matrix(convert=False) [0 1 2] [3 4 5] @@ -810,7 +836,8 @@ cdef class MatrixArgs: sage: S = MatrixSpace(QQ, 3, 2, sparse=True) sage: _ = ma.set_space(S) sage: ma.finalized() - + sage: M = ma.matrix(); M [0 0] [0 0] @@ -848,7 +875,8 @@ cdef class MatrixArgs: ... TypeError: the dimensions of the matrix must be specified sage: MatrixArgs(2, 3, 0.0).finalized() - + sage: MatrixArgs(RR, 2, 3, 1.0).finalized() Traceback (most recent call last): ... @@ -1020,19 +1048,21 @@ cdef class MatrixArgs: EXAMPLES:: sage: from sage.matrix.args import MatrixArgs - sage: ma = MatrixArgs({(2,5):1/2, (4,-3):1/3}) - sage: ma = MatrixArgs(2, 2, {(-1,0):2, (0,-1):1}, sparse=True) + sage: ma = MatrixArgs({(2,5): 1/2, (4,-3): 1/3}) + sage: ma = MatrixArgs(2, 2, {(-1,0): 2, (0,-1): 1}, sparse=True) sage: ma.finalized() - - sage: ma = MatrixArgs(2, 2, {(-1,0):2, (0,-1):1}, sparse=False) + + sage: ma = MatrixArgs(2, 2, {(-1,0): 2, (0,-1): 1}, sparse=False) sage: ma.finalized() - - sage: ma = MatrixArgs(2, 1, {(1,0):88, (0,1):89}) + + sage: ma = MatrixArgs(2, 1, {(1,0): 88, (0,1): 89}) sage: ma.finalized() Traceback (most recent call last): ... IndexError: invalid column index 1 - sage: ma = MatrixArgs(1, 2, {(1,0):88, (0,1):89}) + sage: ma = MatrixArgs(1, 2, {(1,0): 88, (0,1): 89}) sage: ma.finalized() Traceback (most recent call last): ... diff --git a/src/sage/matrix/compute_J_ideal.py b/src/sage/matrix/compute_J_ideal.py index 406e036871b..f7005c04d31 100644 --- a/src/sage/matrix/compute_J_ideal.py +++ b/src/sage/matrix/compute_J_ideal.py @@ -541,8 +541,8 @@ def mccoy_column(self, p, t, nu): sage: x = polygen(ZZ, 'x') sage: nu_4 = x^2 + 3*x + 2 sage: g = C.mccoy_column(2, 2, nu_4) - sage: b = matrix(9, 1, (x-B).adjugate().list()) - sage: M = matrix.block([[b , -B.charpoly(x)*matrix.identity(9)]]) + sage: b = matrix(9, 1, (x - B).adjugate().list()) + sage: M = matrix.block([[b, -B.charpoly(x)*matrix.identity(9)]]) sage: (M*g % 4).is_zero() True diff --git a/src/sage/matrix/constructor.pyx b/src/sage/matrix/constructor.pyx index 665ae4fbde9..e08fdd7d7f9 100644 --- a/src/sage/matrix/constructor.pyx +++ b/src/sage/matrix/constructor.pyx @@ -98,32 +98,32 @@ def matrix(*args, **kwds): :: - sage: m = matrix(2,3); m; m.parent() + sage: m = matrix(2, 3); m; m.parent() [0 0 0] [0 0 0] Full MatrixSpace of 2 by 3 dense matrices over Integer Ring :: - sage: m = matrix(QQ,[[1,2,3],[4,5,6]]); m; m.parent() + sage: m = matrix(QQ, [[1,2,3], [4,5,6]]); m; m.parent() [1 2 3] [4 5 6] Full MatrixSpace of 2 by 3 dense matrices over Rational Field :: - sage: m = matrix(QQ, 3, 3, lambda i, j: i+j); m + sage: m = matrix(QQ, 3, 3, lambda i, j: i + j); m [0 1 2] [1 2 3] [2 3 4] - sage: m = matrix(3, lambda i,j: i-j); m + sage: m = matrix(3, lambda i, j: i - j); m [ 0 -1 -2] [ 1 0 -1] [ 2 1 0] :: - sage: matrix(QQ, 2, 3, lambda x, y: x+y) + sage: matrix(QQ, 2, 3, lambda x, y: x + y) [0 1 2] [1 2 3] sage: matrix(QQ, 5, 5, lambda x, y: (x+1) / (y+1)) @@ -279,11 +279,11 @@ def matrix(*args, **kwds): [0 0] [0 0] Full MatrixSpace of 2 by 2 dense matrices over Integer Ring - sage: m = matrix(QQ,2); m; m.parent() + sage: m = matrix(QQ, 2); m; m.parent() [0 0] [0 0] Full MatrixSpace of 2 by 2 dense matrices over Rational Field - sage: m = matrix(QQ,2,3); m; m.parent() + sage: m = matrix(QQ, 2, 3); m; m.parent() [0 0 0] [0 0 0] Full MatrixSpace of 2 by 3 dense matrices over Rational Field @@ -324,19 +324,19 @@ def matrix(*args, **kwds): [1 2 3] [4 5 6] Full MatrixSpace of 2 by 3 dense matrices over Integer Ring - sage: m = matrix(QQ,2,[[1,2,3],[4,5,6]]); m; m.parent() + sage: m = matrix(QQ, 2, [[1,2,3],[4,5,6]]); m; m.parent() [1 2 3] [4 5 6] Full MatrixSpace of 2 by 3 dense matrices over Rational Field - sage: m = matrix(QQ,3,[[1,2,3],[4,5,6]]); m; m.parent() + sage: m = matrix(QQ, 3, [[1,2,3],[4,5,6]]); m; m.parent() Traceback (most recent call last): ... ValueError: inconsistent number of rows: should be 3 but got 2 - sage: m = matrix(QQ,2,3,[[1,2,3],[4,5,6]]); m; m.parent() + sage: m = matrix(QQ, 2, 3, [[1,2,3],[4,5,6]]); m; m.parent() [1 2 3] [4 5 6] Full MatrixSpace of 2 by 3 dense matrices over Rational Field - sage: m = matrix(QQ,2,4,[[1,2,3],[4,5,6]]); m; m.parent() + sage: m = matrix(QQ, 2, 4, [[1,2,3],[4,5,6]]); m; m.parent() Traceback (most recent call last): ... ValueError: sequence too short (expected length 4, got 3) @@ -350,19 +350,19 @@ def matrix(*args, **kwds): sage: m = matrix((1,2,3,4,5,6)); m; m.parent() [1 2 3 4 5 6] Full MatrixSpace of 1 by 6 dense matrices over Integer Ring - sage: m = matrix(QQ,[1,2,3,4,5,6]); m; m.parent() + sage: m = matrix(QQ, [1,2,3,4,5,6]); m; m.parent() [1 2 3 4 5 6] Full MatrixSpace of 1 by 6 dense matrices over Rational Field - sage: m = matrix(QQ,3,2,[1,2,3,4,5,6]); m; m.parent() + sage: m = matrix(QQ, 3, 2, [1,2,3,4,5,6]); m; m.parent() [1 2] [3 4] [5 6] Full MatrixSpace of 3 by 2 dense matrices over Rational Field - sage: m = matrix(QQ,2,4,[1,2,3,4,5,6]); m; m.parent() + sage: m = matrix(QQ, 2, 4, [1,2,3,4,5,6]); m; m.parent() Traceback (most recent call last): ... ValueError: sequence too short (expected length 8, got 6) - sage: m = matrix(QQ,5,[1,2,3,4,5,6]); m; m.parent() + sage: m = matrix(QQ, 5, [1,2,3,4,5,6]); m; m.parent() Traceback (most recent call last): ... ValueError: sequence too long (expected length 5, got more) @@ -377,47 +377,47 @@ def matrix(*args, **kwds): [0 0] [0 2] Full MatrixSpace of 2 by 2 dense matrices over Integer Ring - sage: m = matrix(QQ,{(1,1): 2}); m; m.parent() + sage: m = matrix(QQ, {(1,1): 2}); m; m.parent() [0 0] [0 2] Full MatrixSpace of 2 by 2 sparse matrices over Rational Field - sage: m = matrix(QQ,3,{(1,1): 2}); m; m.parent() + sage: m = matrix(QQ, 3, {(1,1): 2}); m; m.parent() [0 0 0] [0 2 0] [0 0 0] Full MatrixSpace of 3 by 3 sparse matrices over Rational Field - sage: m = matrix(QQ,3,4,{(1,1): 2}); m; m.parent() + sage: m = matrix(QQ, 3, 4, {(1,1): 2}); m; m.parent() [0 0 0 0] [0 2 0 0] [0 0 0 0] Full MatrixSpace of 3 by 4 sparse matrices over Rational Field - sage: m = matrix(QQ,2,{(1,1): 2}); m; m.parent() + sage: m = matrix(QQ, 2, {(1,1): 2}); m; m.parent() [0 0] [0 2] Full MatrixSpace of 2 by 2 sparse matrices over Rational Field - sage: m = matrix(QQ,1,{(1,1): 2}); m; m.parent() + sage: m = matrix(QQ, 1, {(1,1): 2}); m; m.parent() Traceback (most recent call last): ... IndexError: invalid row index 1 sage: m = matrix({}); m; m.parent() [] Full MatrixSpace of 0 by 0 sparse matrices over Integer Ring - sage: m = matrix(QQ,{}); m; m.parent() + sage: m = matrix(QQ, {}); m; m.parent() [] Full MatrixSpace of 0 by 0 sparse matrices over Rational Field - sage: m = matrix(QQ,2,{}); m; m.parent() + sage: m = matrix(QQ, 2, {}); m; m.parent() [0 0] [0 0] Full MatrixSpace of 2 by 2 sparse matrices over Rational Field - sage: m = matrix(QQ,2,3,{}); m; m.parent() + sage: m = matrix(QQ, 2, 3, {}); m; m.parent() [0 0 0] [0 0 0] Full MatrixSpace of 2 by 3 sparse matrices over Rational Field - sage: m = matrix(2,{}); m; m.parent() + sage: m = matrix(2, {}); m; m.parent() [0 0] [0 0] Full MatrixSpace of 2 by 2 sparse matrices over Integer Ring - sage: m = matrix(2,3,{}); m; m.parent() + sage: m = matrix(2, 3, {}); m; m.parent() [0 0 0] [0 0 0] Full MatrixSpace of 2 by 3 sparse matrices over Integer Ring @@ -430,27 +430,27 @@ def matrix(*args, **kwds): sage: m = matrix(2,0); m; m.parent() [] Full MatrixSpace of 2 by 0 dense matrices over Integer Ring - sage: m = matrix(0,[1]); m; m.parent() + sage: m = matrix(0, [1]); m; m.parent() Traceback (most recent call last): ... ValueError: sequence too long (expected length 0, got more) - sage: m = matrix(1,0,[]); m; m.parent() + sage: m = matrix(1, 0, []); m; m.parent() [] Full MatrixSpace of 1 by 0 dense matrices over Integer Ring - sage: m = matrix(0,1,[]); m; m.parent() + sage: m = matrix(0, 1, []); m; m.parent() [] Full MatrixSpace of 0 by 1 dense matrices over Integer Ring - sage: m = matrix(0,[]); m; m.parent() + sage: m = matrix(0, []); m; m.parent() [] Full MatrixSpace of 0 by 0 dense matrices over Integer Ring - sage: m = matrix(0,{}); m; m.parent() + sage: m = matrix(0, {}); m; m.parent() [] Full MatrixSpace of 0 by 0 sparse matrices over Integer Ring - sage: m = matrix(0,{(1,1):2}); m; m.parent() + sage: m = matrix(0, {(1,1): 2}); m; m.parent() Traceback (most recent call last): ... IndexError: invalid row index 1 - sage: m = matrix(2,0,{(1,1):2}); m; m.parent() + sage: m = matrix(2, 0, {(1,1): 2}); m; m.parent() Traceback (most recent call last): ... IndexError: invalid column index 1 @@ -557,7 +557,7 @@ def matrix(*args, **kwds): [ 1.0 2.0 3.0] [ 2.0 1.0 + 2.0*I 3.0] Full MatrixSpace of 2 by 3 dense matrices over Complex Double Field - sage: m = matrix(3,3,1/2); m; m.parent() + sage: m = matrix(3, 3, 1/2); m; m.parent() [1/2 0 0] [ 0 1/2 0] [ 0 0 1/2] @@ -570,7 +570,7 @@ def matrix(*args, **kwds): Traceback (most recent call last): ... TypeError: 'sage.rings.integer.Integer' object is not iterable - sage: matrix(vector(RR,[1,2,3])).parent() + sage: matrix(vector(RR, [1,2,3])).parent() Full MatrixSpace of 1 by 3 dense matrices over Real Field with 53 bits of precision Check :issue:`10158`:: diff --git a/src/sage/matrix/matrix0.pyx b/src/sage/matrix/matrix0.pyx index 9eb3f9f4213..b7a6fde1e67 100644 --- a/src/sage/matrix/matrix0.pyx +++ b/src/sage/matrix/matrix0.pyx @@ -8,7 +8,7 @@ Base class for matrices, part 0 EXAMPLES:: - sage: matrix(2,[1,2,3,4]) + sage: matrix(2, [1,2,3,4]) [1 2] [3 4] """ @@ -467,7 +467,8 @@ cdef class Matrix(sage.structure.element.Matrix): sage: A[0,0] = 10 Traceback (most recent call last): ... - ValueError: matrix is immutable; please change a copy instead (i.e., use copy(M) to change a copy of M). + ValueError: matrix is immutable; please change a copy instead + (i.e., use copy(M) to change a copy of M). sage: hash(A) #random 12 sage: v = {A:1}; v @@ -674,7 +675,7 @@ cdef class Matrix(sage.structure.element.Matrix): ... TypeError: index must be an integer - sage: m=[(1, -2, -1, -1,9), (1, 8, 6, 2,2), (1, 1, -1, 1,4), (-1, 2, -2, -1,4)];M= matrix(m) + sage: m = [(1, -2, -1, -1,9), (1, 8, 6, 2,2), (1, 1, -1, 1,4), (-1, 2, -2, -1,4)]; M = matrix(m) sage: M [ 1 -2 -1 -1 9] [ 1 8 6 2 2] @@ -1619,7 +1620,7 @@ cdef class Matrix(sage.structure.element.Matrix): EXAMPLES:: - sage: m=matrix(QQ,2,[1,2,3,4]) + sage: m = matrix(QQ, 2, [1,2,3,4]) sage: m.base_ring() Rational Field """ @@ -2548,7 +2549,7 @@ cdef class Matrix(sage.structure.element.Matrix): EXAMPLES: We create a matrix:: - sage: M = matrix(ZZ,[[1,0,0,0,0],[0,2,0,0,0],[0,0,3,0,0],[0,0,0,4,0],[0,0,0,0,5]]) + sage: M = matrix(ZZ, [[1,0,0,0,0],[0,2,0,0,0],[0,0,3,0,0],[0,0,0,4,0],[0,0,0,0,5]]) sage: M [1 0 0 0 0] [0 2 0 0 0] @@ -2600,7 +2601,7 @@ cdef class Matrix(sage.structure.element.Matrix): EXAMPLES: We create some matrix:: - sage: M = matrix(ZZ,[[1,0,0,0,0],[0,2,0,0,0],[0,0,3,0,0],[0,0,0,4,0],[0,0,0,0,5]]) + sage: M = matrix(ZZ, [[1,0,0,0,0],[0,2,0,0,0],[0,0,3,0,0],[0,0,0,4,0],[0,0,0,0,5]]) sage: M [1 0 0 0 0] [0 2 0 0 0] @@ -2646,8 +2647,8 @@ cdef class Matrix(sage.structure.element.Matrix): EXAMPLES: We create a rational matrix:: - sage: M = MatrixSpace(QQ,3,3) - sage: A = M([1,9,-7,4/5,4,3,6,4,3]) + sage: M = MatrixSpace(QQ, 3, 3) + sage: A = M([1,9,-7, 4/5,4,3, 6,4,3]) sage: A [ 1 9 -7] [4/5 4 3] @@ -2656,7 +2657,7 @@ cdef class Matrix(sage.structure.element.Matrix): Since the first row is numbered zero, this swaps the first and third rows:: - sage: A.swap_rows(0,2); A + sage: A.swap_rows(0, 2); A [ 6 4 3] [4/5 4 3] [ 1 9 -7] @@ -2733,7 +2734,7 @@ cdef class Matrix(sage.structure.element.Matrix): EXAMPLES: We create a matrix:: - sage: M = matrix(ZZ,[[1,0,0,0,0],[0,2,0,0,0],[0,0,3,0,0],[0,0,0,4,0],[0,0,0,0,5]]) + sage: M = matrix(ZZ, [[1,0,0,0,0],[0,2,0,0,0],[0,0,3,0,0],[0,0,0,4,0],[0,0,0,0,5]]) sage: M [1 0 0 0 0] [0 2 0 0 0] @@ -2783,7 +2784,7 @@ cdef class Matrix(sage.structure.element.Matrix): EXAMPLES: We create a matrix:: - sage: M = matrix(ZZ,[[1,0,0,0,0],[0,2,0,0,0],[0,0,3,0,0],[0,0,0,4,0],[0,0,0,0,5]]) + sage: M = matrix(ZZ, [[1,0,0,0,0],[0,2,0,0,0],[0,0,3,0,0],[0,0,0,4,0],[0,0,0,0,5]]) sage: M [1 0 0 0 0] [0 2 0 0 0] @@ -2842,7 +2843,7 @@ cdef class Matrix(sage.structure.element.Matrix): EXAMPLES: We create a matrix:: - sage: M = matrix(ZZ,[[1,0,0,0,0],[0,2,0,0,0],[0,0,3,0,0],[0,0,0,4,0],[0,0,0,0,5]]) + sage: M = matrix(ZZ, [[1,0,0,0,0],[0,2,0,0,0],[0,0,3,0,0],[0,0,0,4,0],[0,0,0,0,5]]) sage: M [1 0 0 0 0] [0 2 0 0 0] @@ -2888,7 +2889,7 @@ cdef class Matrix(sage.structure.element.Matrix): EXAMPLES: We create a matrix:: - sage: M = matrix(ZZ,[[1,0,0,0,0],[0,2,0,0,0],[0,0,3,0,0],[0,0,0,4,0],[0,0,0,0,5]]) + sage: M = matrix(ZZ, [[1,0,0,0,0],[0,2,0,0,0],[0,0,3,0,0],[0,0,0,4,0],[0,0,0,0,5]]) sage: M [1 0 0 0 0] [0 2 0 0 0] @@ -3047,10 +3048,10 @@ cdef class Matrix(sage.structure.element.Matrix): EXAMPLES: We add -1 times the third column to the second column of an integer matrix, remembering to start numbering cols at zero:: - sage: a = matrix(ZZ,2,3,range(6)); a + sage: a = matrix(ZZ, 2, 3, range(6)); a [0 1 2] [3 4 5] - sage: b = a.with_added_multiple_of_column(1,2,-1); b + sage: b = a.with_added_multiple_of_column(1, 2, -1); b [ 0 -1 2] [ 3 -1 5] @@ -3063,7 +3064,7 @@ cdef class Matrix(sage.structure.element.Matrix): Adding a rational multiple is okay, and reassigning a variable is okay:: - sage: a = a.with_added_multiple_of_column(0,1,1/3); a + sage: a = a.with_added_multiple_of_column(0, 1, 1/3); a [ 1/3 1 2] [13/3 4 5] """ @@ -3100,11 +3101,11 @@ cdef class Matrix(sage.structure.element.Matrix): EXAMPLES: We rescale the second row of a matrix over the rational numbers:: - sage: a = matrix(QQ,3,range(6)); a + sage: a = matrix(QQ, 3, range(6)); a [0 1] [2 3] [4 5] - sage: a.rescale_row(1,1/2); a + sage: a.rescale_row(1, 1/2); a [ 0 1] [ 1 3/2] [ 4 5] @@ -3112,11 +3113,11 @@ cdef class Matrix(sage.structure.element.Matrix): We rescale the second row of a matrix over a polynomial ring:: sage: R. = QQ[] - sage: a = matrix(R,3,[1,x,x^2,x^3,x^4,x^5]);a + sage: a = matrix(R, 3, [1,x,x^2,x^3,x^4,x^5]); a [ 1 x] [x^2 x^3] [x^4 x^5] - sage: a.rescale_row(1,1/2); a + sage: a.rescale_row(1, 1/2); a [ 1 x] [1/2*x^2 1/2*x^3] [ x^4 x^5] @@ -3124,10 +3125,10 @@ cdef class Matrix(sage.structure.element.Matrix): We try and fail to rescale a matrix over the integers by a non-integer:: - sage: a = matrix(ZZ,2,3,[0,1,2, 3,4,4]); a + sage: a = matrix(ZZ, 2, 3, [0,1,2, 3,4,4]); a [0 1 2] [3 4 4] - sage: a.rescale_row(1,1/2) + sage: a.rescale_row(1, 1/2) Traceback (most recent call last): ... TypeError: Rescaling row by Rational Field element cannot be done @@ -3139,7 +3140,7 @@ cdef class Matrix(sage.structure.element.Matrix): sage: a = a.change_ring(QQ); a [0 1 2] [3 4 4] - sage: a.rescale_col(1,1/2); a + sage: a.rescale_col(1, 1/2); a [ 0 1/2 2] [ 3 2 4] """ @@ -3162,11 +3163,11 @@ cdef class Matrix(sage.structure.element.Matrix): EXAMPLES: We rescale the second row of a matrix over the integers:: - sage: a = matrix(ZZ,3,2,range(6)); a + sage: a = matrix(ZZ, 3, 2, range(6)); a [0 1] [2 3] [4 5] - sage: b = a.with_rescaled_row(1,-2); b + sage: b = a.with_rescaled_row(1, -2); b [ 0 1] [-4 -6] [ 4 5] @@ -3181,7 +3182,7 @@ cdef class Matrix(sage.structure.element.Matrix): Adding a rational multiple is okay, and reassigning a variable is okay:: - sage: a = a.with_rescaled_row(2,1/3); a + sage: a = a.with_rescaled_row(2, 1/3); a [ 0 1] [ 2 3] [4/3 5/3] @@ -3219,30 +3220,30 @@ cdef class Matrix(sage.structure.element.Matrix): EXAMPLES: We rescale the last column of a matrix over the rational numbers:: - sage: a = matrix(QQ,2,3,range(6)); a + sage: a = matrix(QQ, 2, 3, range(6)); a [0 1 2] [3 4 5] - sage: a.rescale_col(2,1/2); a + sage: a.rescale_col(2, 1/2); a [ 0 1 1] [ 3 4 5/2] sage: R. = QQ[] We rescale the last column of a matrix over a polynomial ring:: - sage: a = matrix(R,2,3,[1,x,x^2,x^3,x^4,x^5]); a + sage: a = matrix(R, 2, 3, [1,x,x^2,x^3,x^4,x^5]); a [ 1 x x^2] [x^3 x^4 x^5] - sage: a.rescale_col(2,1/2); a + sage: a.rescale_col(2, 1/2); a [ 1 x 1/2*x^2] [ x^3 x^4 1/2*x^5] We try and fail to rescale a matrix over the integers by a non-integer:: - sage: a = matrix(ZZ,2,3,[0,1,2, 3,4,4]); a + sage: a = matrix(ZZ, 2, 3, [0,1,2, 3,4,4]); a [0 1 2] [3 4 4] - sage: a.rescale_col(2,1/2) + sage: a.rescale_col(2, 1/2) Traceback (most recent call last): ... TypeError: Rescaling column by Rational Field element cannot be done @@ -3278,10 +3279,10 @@ cdef class Matrix(sage.structure.element.Matrix): EXAMPLES: We rescale the last column of a matrix over the integers:: - sage: a = matrix(ZZ,2,3,range(6)); a + sage: a = matrix(ZZ, 2, 3, range(6)); a [0 1 2] [3 4 5] - sage: b = a.with_rescaled_col(2,-2); b + sage: b = a.with_rescaled_col(2, -2); b [ 0 1 -4] [ 3 4 -10] @@ -3294,7 +3295,7 @@ cdef class Matrix(sage.structure.element.Matrix): Adding a rational multiple is okay, and reassigning a variable is okay:: - sage: a = a.with_rescaled_col(1,1/3); a + sage: a = a.with_rescaled_col(1, 1/3); a [ 0 1/3 2] [ 3 4/3 5] """ @@ -3319,10 +3320,10 @@ cdef class Matrix(sage.structure.element.Matrix): EXAMPLES: We change the second row to -3 times the first row:: - sage: a = matrix(ZZ,2,3,range(6)); a + sage: a = matrix(ZZ, 2, 3, range(6)); a [0 1 2] [3 4 5] - sage: a.set_row_to_multiple_of_row(1,0,-3) + sage: a.set_row_to_multiple_of_row(1, 0, -3) sage: a [ 0 1 2] [ 0 -3 -6] @@ -3330,10 +3331,11 @@ cdef class Matrix(sage.structure.element.Matrix): If we try to multiply a row by a rational number, we get an error message:: - sage: a.set_row_to_multiple_of_row(1,0,1/2) + sage: a.set_row_to_multiple_of_row(1, 0, 1/2) Traceback (most recent call last): ... - TypeError: Multiplying row by Rational Field element cannot be done over Integer Ring, use change_ring or with_row_set_to_multiple_of_row instead. + TypeError: Multiplying row by Rational Field element cannot be done over + Integer Ring, use change_ring or with_row_set_to_multiple_of_row instead. """ self.check_row_bounds_and_mutability(i, j) cdef Py_ssize_t n @@ -3350,10 +3352,10 @@ cdef class Matrix(sage.structure.element.Matrix): EXAMPLES: We change the second row to -3 times the first row:: - sage: a = matrix(ZZ,2,3,range(6)); a + sage: a = matrix(ZZ, 2, 3, range(6)); a [0 1 2] [3 4 5] - sage: b = a.with_row_set_to_multiple_of_row(1,0,-3); b + sage: b = a.with_row_set_to_multiple_of_row(1, 0, -3); b [ 0 1 2] [ 0 -3 -6] @@ -3366,7 +3368,7 @@ cdef class Matrix(sage.structure.element.Matrix): Adding a rational multiple is okay, and reassigning a variable is okay:: - sage: a = a.with_row_set_to_multiple_of_row(1,0,1/2); a + sage: a = a.with_row_set_to_multiple_of_row(1, 0, 1/2); a [ 0 1 2] [ 0 1/2 1] """ @@ -3397,10 +3399,10 @@ cdef class Matrix(sage.structure.element.Matrix): :: - sage: a = matrix(ZZ,2,3,range(6)); a + sage: a = matrix(ZZ, 2, 3, range(6)); a [0 1 2] [3 4 5] - sage: a.set_col_to_multiple_of_col(1,0,-3) + sage: a.set_col_to_multiple_of_col(1, 0, -3) sage: a [ 0 0 2] [ 3 -9 5] @@ -3408,7 +3410,7 @@ cdef class Matrix(sage.structure.element.Matrix): If we try to multiply a column by a rational number, we get an error message:: - sage: a.set_col_to_multiple_of_col(1,0,1/2) + sage: a.set_col_to_multiple_of_col(1, 0, 1/2) Traceback (most recent call last): ... TypeError: Multiplying column by Rational Field element cannot be done over Integer Ring, use change_ring or with_col_set_to_multiple_of_col instead. @@ -3433,10 +3435,10 @@ cdef class Matrix(sage.structure.element.Matrix): :: - sage: a = matrix(ZZ,2,3,range(6)); a + sage: a = matrix(ZZ, 2, 3, range(6)); a [0 1 2] [3 4 5] - sage: b = a.with_col_set_to_multiple_of_col(1,0,-3); b + sage: b = a.with_col_set_to_multiple_of_col(1, 0, -3); b [ 0 0 2] [ 3 -9 5] @@ -3449,7 +3451,7 @@ cdef class Matrix(sage.structure.element.Matrix): Adding a rational multiple is okay, and reassigning a variable is okay:: - sage: a = a.with_col_set_to_multiple_of_col(1,0,1/2); a + sage: a = a.with_col_set_to_multiple_of_col(1, 0, 1/2); a [ 0 0 2] [ 3 3/2 5] """ @@ -3600,7 +3602,7 @@ cdef class Matrix(sage.structure.element.Matrix): Mutation of the B-matrix of the quiver of type `A_3`:: - sage: M = matrix(ZZ,3,[0,1,0,-1,0,-1,0,1,0]); M + sage: M = matrix(ZZ, 3, [0,1,0,-1,0,-1,0,1,0]); M [ 0 1 0] [-1 0 -1] [ 0 1 0] @@ -3615,7 +3617,7 @@ cdef class Matrix(sage.structure.element.Matrix): [-1 0 1] [ 1 -1 0] - sage: M = matrix(ZZ,6,[0,1,0,-1,0,-1,0,1,0,1,0,0,0,1,0,0,0,1]); M + sage: M = matrix(ZZ, 6, [0,1,0,-1,0,-1,0,1,0,1,0,0,0,1,0,0,0,1]); M [ 0 1 0] [-1 0 -1] [ 0 1 0] @@ -3683,12 +3685,12 @@ cdef class Matrix(sage.structure.element.Matrix): EXAMPLES:: - sage: M = matrix(ZZ,3,[0,1,0,-1,0,-1,0,1,0]); M + sage: M = matrix(ZZ, 3, [0,1,0,-1,0,-1,0,1,0]); M [ 0 1 0] [-1 0 -1] [ 0 1 0] - sage: M._travel_column({0:1},0,-1,True) + sage: M._travel_column({0: 1}, 0, -1, True) [1] """ cdef list L = [] @@ -3797,7 +3799,7 @@ cdef class Matrix(sage.structure.element.Matrix): EXAMPLES:: - sage: a = matrix(ZZ,2,3,range(6)); a + sage: a = matrix(ZZ, 2, 3, range(6)); a [0 1 2] [3 4 5] sage: a.linear_combination_of_rows([1,2]) @@ -3818,7 +3820,7 @@ cdef class Matrix(sage.structure.element.Matrix): We check that a matrix with no rows behaves properly. :: - sage: matrix(QQ,0,2).linear_combination_of_rows([]) + sage: matrix(QQ, 0, 2).linear_combination_of_rows([]) (0, 0) The object returned is a vector, or a free module element. :: @@ -3838,7 +3840,7 @@ cdef class Matrix(sage.structure.element.Matrix): The length of v can be less than the number of rows, but not greater. :: - sage: A = matrix(QQ,3,4,range(12)) + sage: A = matrix(QQ, 3, 4, range(12)) sage: A.linear_combination_of_rows([2,3]) (12, 17, 22, 27) sage: A.linear_combination_of_rows([1,2,3,4]) @@ -3874,7 +3876,7 @@ cdef class Matrix(sage.structure.element.Matrix): EXAMPLES:: - sage: a = matrix(ZZ,2,3,range(6)); a + sage: a = matrix(ZZ, 2, 3, range(6)); a [0 1 2] [3 4 5] sage: a.linear_combination_of_columns([1,1,1]) @@ -3895,7 +3897,7 @@ cdef class Matrix(sage.structure.element.Matrix): We check that a matrix with no columns behaves properly. :: - sage: matrix(QQ,2,0).linear_combination_of_columns([]) + sage: matrix(QQ, 2, 0).linear_combination_of_columns([]) (0, 0) The object returned is a vector, or a free module element. :: @@ -3915,7 +3917,7 @@ cdef class Matrix(sage.structure.element.Matrix): The length of v can be less than the number of columns, but not greater. :: - sage: A = matrix(QQ,3,5, range(15)) + sage: A = matrix(QQ, 3, 5, range(15)) sage: A.linear_combination_of_columns([1,-2,3,-4]) (-8, -18, -28) sage: A.linear_combination_of_columns([1,2,3,4,5,6]) @@ -4415,7 +4417,7 @@ cdef class Matrix(sage.structure.element.Matrix): sage: matrix([[0,6],[3,0]]).is_skew_symmetrizable(positive=True) False - sage: M = matrix(4,[0,1,0,0,-1,0,-1,0,0,2,0,1,0,0,-1,0]); M + sage: M = matrix(4, [0,1,0,0, -1,0,-1,0, 0,2,0,1, 0,0,-1,0]); M [ 0 1 0 0] [-1 0 -1 0] [ 0 2 0 1] @@ -4424,7 +4426,7 @@ cdef class Matrix(sage.structure.element.Matrix): sage: M.is_skew_symmetrizable(return_diag=True) [1, 1, 1/2, 1/2] - sage: M2 = diagonal_matrix([1,1,1/2,1/2])*M; M2 + sage: M2 = diagonal_matrix([1,1,1/2,1/2]) * M; M2 [ 0 1 0 0] [ -1 0 -1 0] [ 0 1 0 1/2] @@ -4450,9 +4452,9 @@ cdef class Matrix(sage.structure.element.Matrix): EXAMPLES:: - sage: matrix(QQ,2,2,range(4)).is_dense() + sage: matrix(QQ, 2, 2, range(4)).is_dense() True - sage: matrix(QQ,2,2,range(4),sparse=True).is_dense() + sage: matrix(QQ, 2, 2, range(4), sparse=True).is_dense() False """ return self.is_dense_c() @@ -4466,9 +4468,9 @@ cdef class Matrix(sage.structure.element.Matrix): EXAMPLES:: - sage: matrix(QQ,2,2,range(4)).is_sparse() + sage: matrix(QQ, 2, 2, range(4)).is_sparse() False - sage: matrix(QQ,2,2,range(4),sparse=True).is_sparse() + sage: matrix(QQ, 2, 2, range(4), sparse=True).is_sparse() True """ return self.is_sparse_c() @@ -4480,9 +4482,9 @@ cdef class Matrix(sage.structure.element.Matrix): EXAMPLES:: - sage: matrix(QQ,2,2,range(4)).is_square() + sage: matrix(QQ, 2, 2, range(4)).is_square() True - sage: matrix(QQ,2,3,range(6)).is_square() + sage: matrix(QQ, 2, 3, range(6)).is_square() False """ return self._nrows == self._ncols @@ -4515,7 +4517,7 @@ cdef class Matrix(sage.structure.element.Matrix): :: - sage: A = MatrixSpace(IntegerRing(),2)([1,10,0,-1]) + sage: A = MatrixSpace(IntegerRing(), 2)([1,10,0,-1]) sage: A.is_invertible() True sage: ~A # compute the inverse @@ -4528,7 +4530,7 @@ cdef class Matrix(sage.structure.element.Matrix): :: sage: R. = PolynomialRing(IntegerRing()) - sage: A = MatrixSpace(R,2)([1,x,0,-1]) + sage: A = MatrixSpace(R, 2)([1,x,0,-1]) sage: A.is_invertible() True sage: ~A @@ -4565,7 +4567,7 @@ cdef class Matrix(sage.structure.element.Matrix): A singular matrix over the field ``QQ``. :: - sage: A = matrix(QQ, 4, [-1,2,-3,6,0,-1,-1,0,-1,1,-5,7,-1,6,5,2]) + sage: A = matrix(QQ, 4, [-1,2,-3,6, 0,-1,-1,0, -1,1,-5,7, -1,6,5,2]) sage: A.is_singular() True sage: A.right_kernel().dimension() @@ -4573,7 +4575,7 @@ cdef class Matrix(sage.structure.element.Matrix): A matrix that is not singular, i.e. nonsingular, over a field. :: - sage: B = matrix(QQ, 4, [1,-3,-1,-5,2,-5,-2,-7,-2,5,3,4,-1,4,2,6]) + sage: B = matrix(QQ, 4, [1,-3,-1,-5, 2,-5,-2,-7, -2,5,3,4, -1,4,2,6]) sage: B.is_singular() False sage: B.left_kernel().dimension() @@ -4593,7 +4595,7 @@ cdef class Matrix(sage.structure.element.Matrix): When the base ring is not a field, then a matrix may be both not invertible and not singular. :: - sage: D = matrix(ZZ, 4, [2,0,-4,8,2,1,-2,7,2,5,7,0,0,1,4,-6]) + sage: D = matrix(ZZ, 4, [2,0,-4,8, 2,1,-2,7, 2,5,7,0, 0,1,4,-6]) sage: D.is_invertible() False sage: D.is_singular() @@ -4686,7 +4688,7 @@ cdef class Matrix(sage.structure.element.Matrix): EXAMPLES:: - sage: a = matrix(QQ,3,3,range(9)); a + sage: a = matrix(QQ, 3, 3, range(9)); a [0 1 2] [3 4 5] [6 7 8] @@ -5050,7 +5052,7 @@ cdef class Matrix(sage.structure.element.Matrix): EXAMPLES:: - sage: B = matrix(QQ,2, [1,2,3,4]) + sage: B = matrix(QQ, 2, [1,2,3,4]) sage: V = VectorSpace(QQ, 2) sage: v = V([-1,5]) sage: v*B @@ -5155,13 +5157,13 @@ cdef class Matrix(sage.structure.element.Matrix): EXAMPLES:: - sage: A = matrix(ZZ,2, [1,1,3,5]); A + sage: A = matrix(ZZ, 2, [1,1,3,5]); A [1 1] [3 5] sage: v = vector([1,0]) - sage: A.iterates(v,0) + sage: A.iterates(v, 0) [] - sage: A.iterates(v,5) + sage: A.iterates(v, 5) [ 1 0] [ 1 1] [ 4 6] @@ -5170,17 +5172,17 @@ cdef class Matrix(sage.structure.element.Matrix): Another example:: - sage: a = matrix(ZZ,3,range(9)); a + sage: a = matrix(ZZ, 3, range(9)); a [0 1 2] [3 4 5] [6 7 8] sage: v = vector([1,0,0]) - sage: a.iterates(v,4) + sage: a.iterates(v, 4) [ 1 0 0] [ 0 1 2] [ 15 18 21] [180 234 288] - sage: a.iterates(v,4,rows=False) + sage: a.iterates(v, 4, rows=False) [ 1 0 15 180] [ 0 3 42 558] [ 0 6 69 936] @@ -5300,13 +5302,13 @@ cdef class Matrix(sage.structure.element.Matrix): """ EXAMPLES:: - sage: a = matrix(QQ['x'],2,range(6)) + sage: a = matrix(QQ['x'], 2, range(6)) sage: (3/4) * a [ 0 3/4 3/2] [ 9/4 3 15/4] sage: R. = QQ[] - sage: a = matrix(R,2,3,[1,x,y,-x*y,x+y,x-y]); a + sage: a = matrix(R, 2, 3, [1,x,y, -x*y,x+y,x-y]); a [ 1 x y] [ -x*y x + y x - y] sage: (x*y) * a @@ -5388,7 +5390,7 @@ cdef class Matrix(sage.structure.element.Matrix): :: sage: R. = QQ[] - sage: a = matrix(R,2,3,[1,x,y,-x*y,x+y,x-y]); a + sage: a = matrix(R, 2, 3, [1,x,y, -x*y,x+y,x-y]); a [ 1 x y] [ -x*y x + y x - y] sage: b = a.transpose(); b @@ -5629,7 +5631,7 @@ cdef class Matrix(sage.structure.element.Matrix): :: - sage: I = MatrixSpace(ZZ,2)(1) # identity matrix + sage: I = MatrixSpace(ZZ, 2)(1) # identity matrix sage: ~I [1 0] [0 1] @@ -5644,7 +5646,7 @@ cdef class Matrix(sage.structure.element.Matrix): A matrix with 0 rows and 0 columns is invertible (see :issue:`3734`):: - sage: M = MatrixSpace(RR,0,0)(0); M + sage: M = MatrixSpace(RR, 0, 0)(0); M [] sage: M.determinant() 1.00000000000000 @@ -5655,13 +5657,13 @@ cdef class Matrix(sage.structure.element.Matrix): Matrices over the integers modulo a composite modulus:: - sage: m = matrix(Zmod(49),2,[2,1,3,3]) + sage: m = matrix(Zmod(49), 2, [2,1,3,3]) sage: type(m) sage: ~m [ 1 16] [48 17] - sage: m = matrix(Zmod(2^100),2,[2,1,3,3]) + sage: m = matrix(Zmod(2^100), 2, [2,1,3,3]) sage: type(m) sage: (~m)*m # needs sage.libs.pari @@ -5687,7 +5689,7 @@ cdef class Matrix(sage.structure.element.Matrix): This matrix is not invertible:: - sage: m = matrix(Zmod(9),2,[2,1,3,3]) + sage: m = matrix(Zmod(9), 2, [2,1,3,3]) sage: ~m Traceback (most recent call last): ... @@ -6138,8 +6140,8 @@ def unpickle(cls, parent, immutability, cache, data, version): OVER `\ZZ`:: - sage: A = matrix(ZZ,2,range(4)) - sage: loads(dumps(A)) # indirect doctest + sage: A = matrix(ZZ, 2, range(4)) + sage: loads(dumps(A)) # indirect doctest [0 1] [2 3] diff --git a/src/sage/matrix/matrix1.pyx b/src/sage/matrix/matrix1.pyx index ef8aa35ce03..b15eb21da77 100644 --- a/src/sage/matrix/matrix1.pyx +++ b/src/sage/matrix/matrix1.pyx @@ -1093,7 +1093,7 @@ cdef class Matrix(Matrix0): An example over the integers:: - sage: a = matrix(3,3,range(9)); a + sage: a = matrix(3, 3, range(9)); a [0 1 2] [3 4 5] [6 7 8] @@ -1102,7 +1102,7 @@ cdef class Matrix(Matrix0): We do an example over a polynomial ring:: - sage: R. = QQ[ ] + sage: R. = QQ[] sage: a = matrix(R, 2, [x,x^2, 2/3*x,1+x^5]); a [ x x^2] [ 2/3*x x^5 + 1] @@ -1119,7 +1119,7 @@ cdef class Matrix(Matrix0): Check that the returned rows are immutable as per :issue:`14874`:: - sage: m = Mat(ZZ,3,3)(range(9)) + sage: m = Mat(ZZ, 3, 3)(range(9)) sage: v = m.dense_columns() sage: [x.is_mutable() for x in v] [False, False, False] @@ -1173,7 +1173,7 @@ cdef class Matrix(Matrix0): Check that the returned rows are immutable as per :issue:`14874`:: - sage: m = Mat(ZZ,3,3)(range(9)) + sage: m = Mat(ZZ, 3, 3)(range(9)) sage: v = m.dense_rows() sage: [x.is_mutable() for x in v] [False, False, False] @@ -1209,7 +1209,7 @@ cdef class Matrix(Matrix0): EXAMPLES:: - sage: a = matrix(2,3,range(6)); a + sage: a = matrix(2, 3, range(6)); a [0 1 2] [3 4 5] sage: v = a.sparse_columns(); v @@ -1229,7 +1229,7 @@ cdef class Matrix(Matrix0): Check that the returned columns are immutable as per :issue:`14874`:: - sage: m = Mat(ZZ,3,3,sparse=True)(range(9)) + sage: m = Mat(ZZ, 3, 3, sparse=True)(range(9)) sage: v = m.sparse_columns() sage: [x.is_mutable() for x in v] [False, False, False] @@ -1285,7 +1285,7 @@ cdef class Matrix(Matrix0): EXAMPLES:: - sage: m = Mat(ZZ,3,3,sparse=True)(range(9)); m + sage: m = Mat(ZZ, 3, 3, sparse=True)(range(9)); m [0 1 2] [3 4 5] [6 7 8] @@ -1311,7 +1311,7 @@ cdef class Matrix(Matrix0): Check that the returned rows are immutable as per :issue:`14874`:: - sage: m = Mat(ZZ,3,3,sparse=True)(range(9)) + sage: m = Mat(ZZ, 3, 3, sparse=True)(range(9)) sage: v = m.sparse_rows() sage: [x.is_mutable() for x in v] [False, False, False] @@ -1376,7 +1376,7 @@ cdef class Matrix(Matrix0): EXAMPLES:: - sage: a = matrix(2,3,range(6)); a + sage: a = matrix(2, 3, range(6)); a [0 1 2] [3 4 5] sage: a.column(1) @@ -1390,7 +1390,7 @@ cdef class Matrix(Matrix0): TESTS:: - sage: a = matrix(2,3,range(6)); a + sage: a = matrix(2, 3, range(6)); a [0 1 2] [3 4 5] sage: a.column(3) @@ -1435,7 +1435,7 @@ cdef class Matrix(Matrix0): EXAMPLES:: - sage: a = matrix(2,3,range(6)); a + sage: a = matrix(2, 3, range(6)); a [0 1 2] [3 4 5] sage: a.row(0) @@ -1447,7 +1447,7 @@ cdef class Matrix(Matrix0): TESTS:: - sage: a = matrix(2,3,range(6)); a + sage: a = matrix(2, 3, range(6)); a [0 1 2] [3 4 5] sage: a.row(2) @@ -1982,7 +1982,7 @@ cdef class Matrix(Matrix0): EXAMPLES:: - sage: M = MatrixSpace(Integers(8),3,3) + sage: M = MatrixSpace(Integers(8), 3, 3) sage: A = M(range(9)); A [0 1 2] [3 4 5] @@ -2021,7 +2021,7 @@ cdef class Matrix(Matrix0): EXAMPLES:: - sage: A = Matrix(3,4,range(12)); A + sage: A = Matrix(3, 4, range(12)); A [ 0 1 2 3] [ 4 5 6 7] [ 8 9 10 11] @@ -2080,7 +2080,7 @@ cdef class Matrix(Matrix0): EXAMPLES:: - sage: M = MatrixSpace(Integers(8),3,3) + sage: M = MatrixSpace(Integers(8), 3, 3) sage: A = M(range(9)); A [0 1 2] [3 4 5] @@ -2118,7 +2118,7 @@ cdef class Matrix(Matrix0): EXAMPLES:: - sage: A = Matrix(4,3,range(12)); A + sage: A = Matrix(4, 3, range(12)); A [ 0 1 2] [ 3 4 5] [ 6 7 8] @@ -2176,7 +2176,7 @@ cdef class Matrix(Matrix0): EXAMPLES:: - sage: M = MatrixSpace(Integers(8),3,3) + sage: M = MatrixSpace(Integers(8), 3, 3) sage: A = M(range(9)); A [0 1 2] [3 4 5] @@ -2196,7 +2196,7 @@ cdef class Matrix(Matrix0): For example here we take from row 1 columns 2 then 0 twice, and do this 3 times:: - sage: A.matrix_from_rows_and_columns([1,1,1],[2,0,0]) + sage: A.matrix_from_rows_and_columns([1,1,1], [2,0,0]) [5 3 3] [5 3 3] [5 3 3] @@ -2416,7 +2416,8 @@ cdef class Matrix(Matrix0): sage: A.set_column(0, [1/4, 1]); A Traceback (most recent call last): ... - TypeError: Cannot set column with Rational Field elements over Integer Ring, use change_ring first. + TypeError: Cannot set column with Rational Field elements + over Integer Ring, use change_ring first. """ if len(v) != self._nrows: msg = "list of new entries must be of length {0} (not {1})" @@ -2683,9 +2684,11 @@ cdef class Matrix(Matrix0): sage: M = MatrixSpace(QQ, 3, implementation='generic') sage: m = M.an_element() sage: m.matrix_space() - Full MatrixSpace of 3 by 3 dense matrices over Rational Field (using Matrix_generic_dense) + Full MatrixSpace of 3 by 3 dense matrices over Rational Field + (using Matrix_generic_dense) sage: m.matrix_space(nrows=2, ncols=12) - Full MatrixSpace of 2 by 12 dense matrices over Rational Field (using Matrix_generic_dense) + Full MatrixSpace of 2 by 12 dense matrices over Rational Field + (using Matrix_generic_dense) sage: m.matrix_space(nrows=2, sparse=True) Full MatrixSpace of 2 by 3 sparse matrices over Rational Field """ @@ -2757,16 +2760,19 @@ cdef class Matrix(Matrix0): [0.000000000000000 0.000000000000000 0.000000000000000] [0.000000000000000 0.000000000000000 0.000000000000000] sage: A.new_matrix().parent() - Full MatrixSpace of 2 by 3 dense matrices over Real Field with 53 bits of precision + Full MatrixSpace of 2 by 3 dense matrices + over Real Field with 53 bits of precision :: sage: M = MatrixSpace(ZZ, 2, 3, implementation='generic') sage: m = M.an_element() sage: m.new_matrix().parent() - Full MatrixSpace of 2 by 3 dense matrices over Integer Ring (using Matrix_generic_dense) + Full MatrixSpace of 2 by 3 dense matrices over Integer Ring + (using Matrix_generic_dense) sage: m.new_matrix(3,3).parent() - Full MatrixSpace of 3 by 3 dense matrices over Integer Ring (using Matrix_generic_dense) + Full MatrixSpace of 3 by 3 dense matrices over Integer Ring + (using Matrix_generic_dense) sage: m.new_matrix(3,3, sparse=True).parent() Full MatrixSpace of 3 by 3 sparse matrices over Integer Ring """ diff --git a/src/sage/matrix/matrix2.pyx b/src/sage/matrix/matrix2.pyx index 47a836a0952..8fa8467d0b1 100644 --- a/src/sage/matrix/matrix2.pyx +++ b/src/sage/matrix/matrix2.pyx @@ -208,7 +208,7 @@ cdef class Matrix(Matrix1): However, sparse matrices remain sparse:: - sage: m = matrix({(3,2): -x, (59,38): x^2+2}, nrows=1000, ncols=1000) + sage: m = matrix({(3,2): -x, (59,38): x^2 + 2}, nrows=1000, ncols=1000) sage: m1 = m.subs(x=1) sage: m1.is_sparse() True @@ -279,8 +279,8 @@ cdef class Matrix(Matrix1): EXAMPLES:: - sage: A = matrix(QQ,4,2, [0, -1, 1, 0, -2, 2, 1, 0]) - sage: B = matrix(QQ,2,2, [1, 0, 1, -1]) + sage: A = matrix(QQ, 4,2, [0, -1, 1, 0, -2, 2, 1, 0]) + sage: B = matrix(QQ, 2,2, [1, 0, 1, -1]) sage: X = A.solve_left(B) sage: X*A == B True @@ -355,8 +355,8 @@ cdef class Matrix(Matrix1): TESTS:: - sage: A = matrix(QQ,4,2, [0, -1, 1, 0, -2, 2, 1, 0]) - sage: B = vector(QQ,2, [2,1]) + sage: A = matrix(QQ, 4,2, [0, -1, 1, 0, -2, 2, 1, 0]) + sage: B = vector(QQ, 2, [2,1]) sage: X = A.solve_left(B) sage: X*A == B True @@ -514,7 +514,7 @@ cdef class Matrix(Matrix1): EXAMPLES:: sage: A = matrix(QQ, 3, [1,2,3,-1,2,5,2,3,1]) - sage: b = vector(QQ,[1,2,3]) + sage: b = vector(QQ, [1,2,3]) sage: x = A.solve_right(b); x (-13/12, 23/12, -7/12) sage: A * x @@ -522,7 +522,8 @@ cdef class Matrix(Matrix1): We solve with A nonsquare:: - sage: A = matrix(QQ,2,4, [0, -1, 1, 0, -2, 2, 1, 0]); B = matrix(QQ,2,2, [1, 0, 1, -1]) + sage: A = matrix(QQ, 2,4, [0, -1, 1, 0, -2, 2, 1, 0]) + sage: B = matrix(QQ, 2,2, [1, 0, 1, -1]) sage: X = A.solve_right(B); X [-3/2 1/2] [ -1 0] @@ -555,8 +556,8 @@ cdef class Matrix(Matrix1): A :class:`ValueError` is raised if the input is invalid:: - sage: A = matrix(QQ,4,2, [0, -1, 1, 0, -2, 2, 1, 0]) - sage: B = matrix(QQ,2,2, [1, 0, 1, -1]) + sage: A = matrix(QQ, 4,2, [0, -1, 1, 0, -2, 2, 1, 0]) + sage: B = matrix(QQ, 2,2, [1, 0, 1, -1]) sage: X = A.solve_right(B) Traceback (most recent call last): ... @@ -565,7 +566,7 @@ cdef class Matrix(Matrix1): We solve with A singular:: - sage: A = matrix(QQ,2,3, [1,2,3,2,4,6]); B = matrix(QQ,2,2, [6, -6, 12, -12]) + sage: A = matrix(QQ, 2,3, [1,2,3,2,4,6]); B = matrix(QQ, 2,2, [6, -6, 12, -12]) sage: X = A.solve_right(B); X [ 6 -6] [ 0 0] @@ -600,11 +601,12 @@ cdef class Matrix(Matrix1): Solving over a polynomial ring:: sage: x = polygen(QQ, 'x') - sage: A = matrix(2, [x,2*x,-5*x^2+1,3]) - sage: v = vector([3,4*x - 2]) + sage: A = matrix(2, [x, 2*x, -5*x^2 + 1, 3]) + sage: v = vector([3, 4*x - 2]) sage: X = A.solve_right(v) sage: X - ((-4/5*x^2 + 2/5*x + 9/10)/(x^3 + 1/10*x), (19/10*x^2 - 1/5*x - 3/10)/(x^3 + 1/10*x)) + ((-4/5*x^2 + 2/5*x + 9/10)/(x^3 + 1/10*x), + (19/10*x^2 - 1/5*x - 3/10)/(x^3 + 1/10*x)) sage: A * X == v True @@ -968,8 +970,8 @@ cdef class Matrix(Matrix1): EXAMPLES:: - sage: A = matrix(QQ,3,[1,2,4,5,3,1,1,2,-1]) - sage: B = matrix(QQ,3,2,[1,5,1,2,1,5]) + sage: A = matrix(QQ, 3, [1,2,4,5,3,1,1,2,-1]) + sage: B = matrix(QQ, 3,2, [1,5,1,2,1,5]) sage: A._solve_right_nonsingular_square(B) [ -1/7 -11/7] [ 4/7 23/7] @@ -1000,7 +1002,7 @@ cdef class Matrix(Matrix1): EXAMPLES:: - sage: A = matrix(QQ,3,3, [0,0,0,1,2,3,2,4,6]); A + sage: A = matrix(QQ, 3,3, [0,0,0,1,2,3,2,4,6]); A [0 0 0] [1 2 3] [2 4 6] @@ -1036,7 +1038,7 @@ cdef class Matrix(Matrix1): EXAMPLES:: - sage: A = matrix(QQ,2,3, [1,2,3,2,4,6]); B = matrix(QQ,2,2, [6, -6, 12, -12]) + sage: A = matrix(QQ, 2,3, [1,2,3,2,4,6]); B = matrix(QQ, 2,2, [6, -6, 12, -12]) sage: A._solve_right_general(B) [ 6 -6] [ 0 0] @@ -1151,7 +1153,8 @@ cdef class Matrix(Matrix1): [ x^2 + 1 4] [ 9 -4*x + 16] sage: F.parent() - Full MatrixSpace of 2 by 2 dense matrices over Univariate Polynomial Ring in x over Rational Field + Full MatrixSpace of 2 by 2 dense matrices + over Univariate Polynomial Ring in x over Rational Field :: @@ -1180,26 +1183,31 @@ cdef class Matrix(Matrix1): Input that is not a matrix will raise an error. :: - sage: A = random_matrix(ZZ,5,10,x=20) + sage: A = random_matrix(ZZ, 5, 10, x=20) sage: A.elementwise_product(vector(ZZ, [1,2,3,4])) Traceback (most recent call last): ... - TypeError: no common canonical parent for objects with parents: 'Full MatrixSpace of 5 by 10 dense matrices over Integer Ring' and 'Ambient free module of rank 4 over the principal ideal domain Integer Ring' + TypeError: no common canonical parent for objects with parents: + 'Full MatrixSpace of 5 by 10 dense matrices over Integer Ring' and + 'Ambient free module of rank 4 over the principal ideal domain Integer Ring' sage: A = matrix(2, 2, range(4)) sage: A.elementwise_product(polygen(parent(A))) Traceback (most recent call last): ... - TypeError: elementwise_product() argument should be a matrix or coercible to a matrix + TypeError: elementwise_product() argument should be a matrix + or coercible to a matrix Matrices of different sizes for operands will raise an error. :: - sage: A = random_matrix(ZZ,5,10,x=20) - sage: B = random_matrix(ZZ,10,5,x=40) + sage: A = random_matrix(ZZ, 5, 10, x=20) + sage: B = random_matrix(ZZ, 10, 5, x=40) sage: A.elementwise_product(B) Traceback (most recent call last): ... - TypeError: no common canonical parent for objects with parents: 'Full MatrixSpace of 5 by 10 dense matrices over Integer Ring' and 'Full MatrixSpace of 10 by 5 dense matrices over Integer Ring' + TypeError: no common canonical parent for objects with parents: + 'Full MatrixSpace of 5 by 10 dense matrices over Integer Ring' and + 'Full MatrixSpace of 10 by 5 dense matrices over Integer Ring' Some pairs of rings do not have a common parent where multiplication makes sense. This will raise an error. :: @@ -1316,7 +1324,7 @@ cdef class Matrix(Matrix1): sage: A.permanent() 24 - sage: A = matrix(3,6,[1,1,1,1,0,0,0,1,1,1,1,0,0,0,1,1,1,1]) + sage: A = matrix(3,6, [1,1,1,1,0,0,0,1,1,1,1,0,0,0,1,1,1,1]) sage: A.permanent() 36 sage: B = A.change_ring(RR) @@ -1335,7 +1343,7 @@ cdef class Matrix(Matrix1): :: - sage: A = matrix(4,5,[1,1,0,1,1,0,1,1,1,1,1,0,1,0,1,1,1,0,1,0]) + sage: A = matrix(4,5, [1,1,0,1,1,0,1,1,1,1,1,0,1,0,1,1,1,0,1,0]) sage: A.permanent() 32 @@ -1351,21 +1359,21 @@ cdef class Matrix(Matrix1): :: - sage: A = matrix(QQ,2,2,[1/5,2/7,3/2,4/5]) + sage: A = matrix(QQ, 2,2, [1/5,2/7,3/2,4/5]) sage: A.permanent() 103/175 :: sage: R. = PolynomialRing(ZZ) - sage: A = matrix(R,2,2,[a,1,a,a+1]) + sage: A = matrix(R, 2,2, [a,1,a,a+1]) sage: A.permanent() a^2 + 2*a :: - sage: R. = PolynomialRing(ZZ,2) - sage: A = matrix(R,2,2,[x, y, x^2, y^2]) + sage: R. = PolynomialRing(ZZ, 2) + sage: A = matrix(R, 2,2, [x, y, x^2, y^2]) sage: A.permanent() x^2*y + x*y^2 """ @@ -1444,13 +1452,13 @@ cdef class Matrix(Matrix1): EXAMPLES:: - sage: A = matrix(4,[1,0,1,0,1,0,1,0,1,0,10,10,1,0,1,1]) + sage: A = matrix(4, [1,0,1,0,1,0,1,0,1,0,10,10,1,0,1,1]) sage: A.permanental_minor(2) 114 :: - sage: A = matrix(3,6,[1,1,1,1,0,0,0,1,1,1,1,0,0,0,1,1,1,1]) + sage: A = matrix(3,6, [1,1,1,1,0,0,0,1,1,1,1,0,0,0,1,1,1,1]) sage: A.permanental_minor(0) 1 sage: A.permanental_minor(1) @@ -1471,7 +1479,8 @@ cdef class Matrix(Matrix1): sage: m, n = 3, 6 sage: C = matrix(m, n, lambda i,j: 1 - A[i,j]) - sage: sum((-1)^k * C.permanental_minor(k)*factorial(n-k)/factorial(n-m) for k in range(m+1)) + sage: sum((-1)^k * C.permanental_minor(k)*factorial(n-k)/factorial(n-m) + ....: for k in range(m+1)) 36 See Theorem 7.2.1 of Brualdi and Ryser: Combinatorial Matrix @@ -1931,7 +1940,7 @@ cdef class Matrix(Matrix1): EXAMPLES:: - sage: A = Matrix(ZZ,2,3,[1,2,3,4,5,6]); A + sage: A = Matrix(ZZ, 2,3, [1,2,3,4,5,6]); A [1 2 3] [4 5 6] sage: A.minors(2) @@ -1975,7 +1984,7 @@ cdef class Matrix(Matrix1): EXAMPLES:: - sage: A = MatrixSpace(Integers(8),3)([1,7,3, 1,1,1, 3,4,5]) + sage: A = MatrixSpace(Integers(8), 3)([1,7,3, 1,1,1, 3,4,5]) sage: A.det() 6 """ @@ -2008,7 +2017,7 @@ cdef class Matrix(Matrix1): EXAMPLES:: - sage: A = MatrixSpace(Integers(8),3)([1,7,3, 1,1,1, 3,4,5]) + sage: A = MatrixSpace(Integers(8), 3)([1,7,3, 1,1,1, 3,4,5]) sage: A.determinant() 6 sage: A.determinant() is A.determinant() @@ -2019,8 +2028,8 @@ cdef class Matrix(Matrix1): We compute the determinant of the arbitrary 3x3 matrix:: - sage: R = PolynomialRing(QQ,9,'x') - sage: A = matrix(R,3,R.gens()) + sage: R = PolynomialRing(QQ, 9, 'x') + sage: A = matrix(R, 3, R.gens()) sage: A [x0 x1 x2] [x3 x4 x5] @@ -2033,7 +2042,7 @@ cdef class Matrix(Matrix1): :: - sage: R. = PolynomialRing(IntegerRing(),2) + sage: R. = PolynomialRing(IntegerRing(), 2) sage: A = MatrixSpace(R,2)([x, y, x**2, y**2]) sage: A.determinant() -x^2*y + x*y^2 @@ -2059,9 +2068,9 @@ cdef class Matrix(Matrix1): We verify that :issue:`7704` is resolved:: - sage: matrix(ZZ, {(0,0):1,(1,1):2,(2,2):3,(3,3):4}).det() + sage: matrix(ZZ, {(0,0): 1, (1,1): 2, (2,2): 3, (3,3): 4}).det() 24 - sage: matrix(QQ, {(0,0):1,(1,1):2,(2,2):3,(3,3):4}).det() + sage: matrix(QQ, {(0,0): 1, (1,1): 2, (2,2): 3, (3,3): 4}).det() 24 We verify that :issue:`10063` is resolved:: @@ -2977,7 +2986,8 @@ cdef class Matrix(Matrix1): sage: f = M.charpoly('x'); f x^2 + (-2*a - 1)*x + a^2 sage: f.parent() - Univariate Polynomial Ring in x over Univariate Polynomial Ring in a over Integer Ring + Univariate Polynomial Ring in x + over Univariate Polynomial Ring in a over Integer Ring sage: M.trace() 2*a + 1 sage: M.determinant() @@ -2987,7 +2997,7 @@ cdef class Matrix(Matrix1): multi-variate polynomial ring `\ZZ[x,y]`:: sage: R. = PolynomialRing(ZZ,2) - sage: A = MatrixSpace(R,2)([x, y, x^2, y^2]) + sage: A = MatrixSpace(R, 2)([x, y, x^2, y^2]) sage: f = A.charpoly('x'); f x^2 + (-y^2 - x)*x - x^2*y + x*y^2 @@ -3015,7 +3025,7 @@ cdef class Matrix(Matrix1): Over integers modulo `n` with composite `n`:: - sage: A = Mat(Integers(6),3,3)(range(9)) + sage: A = Mat(Integers(6), 3, 3)(range(9)) sage: A.charpoly() x^3 @@ -3036,7 +3046,7 @@ cdef class Matrix(Matrix1): TESTS:: sage: P. = PolynomialRing(Rationals()) - sage: u = MatrixSpace(P,3)([[0,0,a],[1,0,b],[0,1,c]]) + sage: u = MatrixSpace(P, 3)([[0,0,a], [1,0,b], [0,1,c]]) sage: Q. = PolynomialRing(P) sage: u.charpoly('x') x^3 - c*x^2 - b*x - a @@ -3285,7 +3295,7 @@ cdef class Matrix(Matrix1): EXAMPLES:: - sage: A = MatrixSpace(QQ,2)([1/2, 1/3, 1/5, 1/7]) + sage: A = MatrixSpace(QQ, 2)([1/2, 1/3, 1/5, 1/7]) sage: A.denominator() 210 @@ -3352,7 +3362,7 @@ cdef class Matrix(Matrix1): EXAMPLES:: - sage: A = matrix([[2,5],[3,7]]); A + sage: A = matrix([[2,5], [3,7]]); A [2 5] [3 7] sage: A.diagonal() @@ -3399,13 +3409,13 @@ cdef class Matrix(Matrix1): EXAMPLES:: - sage: a = matrix(3,3,range(9)); a + sage: a = matrix(3, 3, range(9)); a [0 1 2] [3 4 5] [6 7 8] sage: a.trace() 12 - sage: a = matrix({(1,1):10, (2,1):-3, (2,2):4/3}); a + sage: a = matrix({(1,1): 10, (2,1): -3, (2,2): 4/3}); a [ 0 0 0] [ 0 10 0] [ 0 -3 4/3] @@ -3454,7 +3464,7 @@ cdef class Matrix(Matrix1): EXAMPLES:: - sage: A = matrix(ZZ,4,[2, 1, 1, -2, 2, 2, -1, -1, -1,1,2,3,4,5,6,7]) + sage: A = matrix(ZZ, 4, [2, 1, 1, -2, 2, 2, -1, -1, -1,1,2,3,4,5,6,7]) sage: h = A.hessenberg_form(); h [ 2 -7/2 -19/5 -2] [ 2 1/2 -17/5 -1] @@ -3498,7 +3508,7 @@ cdef class Matrix(Matrix1): EXAMPLES:: - sage: A = matrix(QQ,3, [2, 1, 1, -2, 2, 2, -1, -1, -1]) + sage: A = matrix(QQ, 3, [2, 1, 1, -2, 2, 2, -1, -1, -1]) sage: A.hessenbergize(); A [ 2 3/2 1] [ -2 3 2] @@ -3506,7 +3516,7 @@ cdef class Matrix(Matrix1): :: - sage: A = matrix(QQ,4, [2, 1, 1, -2, 2, 2, -1, -1, -1,1,2,3,4,5,6,7]) + sage: A = matrix(QQ, 4, [2, 1, 1, -2, 2, 2, -1, -1, -1,1,2,3,4,5,6,7]) sage: A.hessenbergize(); A [ 2 -7/2 -19/5 -2] [ 2 1/2 -17/5 -1] @@ -3596,15 +3606,15 @@ cdef class Matrix(Matrix1): EXAMPLES:: - sage: matrix(QQ,3,3,range(9))._charpoly_hessenberg('Z') + sage: matrix(QQ, 3,3, range(9))._charpoly_hessenberg('Z') Z^3 - 12*Z^2 - 18*Z - sage: matrix(ZZ,3,3,range(9))._charpoly_hessenberg('Z') + sage: matrix(ZZ, 3,3, range(9))._charpoly_hessenberg('Z') Z^3 - 12*Z^2 - 18*Z sage: matrix(GF(7), 3, 3, range(9))._charpoly_hessenberg('Z') Z^3 + 2*Z^2 + 3*Z - sage: matrix(QQ['x'],3,3,range(9))._charpoly_hessenberg('Z') + sage: matrix(QQ['x'], 3,3, range(9))._charpoly_hessenberg('Z') Z^3 - 12*Z^2 - 18*Z - sage: matrix(ZZ['ZZ'],3,3,range(9))._charpoly_hessenberg('Z') + sage: matrix(ZZ['ZZ'], 3,3, range(9))._charpoly_hessenberg('Z') Z^3 - 12*Z^2 - 18*Z """ if self._nrows != self._ncols: @@ -3671,7 +3681,7 @@ cdef class Matrix(Matrix1): EXAMPLES:: - sage: M = Matrix(QQ,[[1,0,0,1],[0,1,1,0],[1,1,1,0]]) + sage: M = Matrix(QQ, [[1,0,0,1], [0,1,1,0], [1,1,1,0]]) sage: M.nullity() 0 sage: M.left_nullity() @@ -3895,7 +3905,7 @@ cdef class Matrix(Matrix1): 'computed-smith-form' sage: P = result[1]; P [-1 -y 1] - sage: A*P.transpose() == zero_matrix(R, 2, 1) + sage: A * P.transpose() == zero_matrix(R, 2, 1) True TESTS: @@ -3909,7 +3919,8 @@ cdef class Matrix(Matrix1): [0 1] sage: A = matrix(R, 2, 0) sage: A._right_kernel_matrix_over_domain()[1].parent() - Full MatrixSpace of 0 by 0 dense matrices over Univariate Polynomial Ring in y over Rational Field + Full MatrixSpace of 0 by 0 dense matrices + over Univariate Polynomial Ring in y over Rational Field sage: A = zero_matrix(R, 4, 3) sage: A._right_kernel_matrix_over_domain()[1] [1 0 0] @@ -4041,19 +4052,19 @@ cdef class Matrix(Matrix1): sage: C = A.right_kernel_matrix(algorithm='default', basis='computed'); C [-1 2 -2 -1 0] [ 1 2 0 0 -1] - sage: A*C.transpose() == zero_matrix(QQ, 4, 2) + sage: A * C.transpose() == zero_matrix(QQ, 4, 2) True sage: P = A.right_kernel_matrix(algorithm='padic', basis='pivot'); P [ 1 -2 2 1 0] [-1 -2 0 0 1] - sage: A*P.transpose() == zero_matrix(QQ, 4, 2) + sage: A * P.transpose() == zero_matrix(QQ, 4, 2) True sage: C == -P True sage: E = A.right_kernel_matrix(algorithm='default', basis='echelon'); E [ 1 0 1 1/2 -1/2] [ 0 1 -1/2 -1/4 -1/4] - sage: A*E.transpose() == zero_matrix(QQ, 4, 2) + sage: A * E.transpose() == zero_matrix(QQ, 4, 2) True Since the rationals are a field, we can call the general code @@ -4066,7 +4077,7 @@ cdef class Matrix(Matrix1): sage: G = A.right_kernel_matrix(algorithm='generic', basis='echelon'); G [ 1 0 1 1/2 -1/2] [ 0 1 -1/2 -1/4 -1/4] - sage: A*G.transpose() == zero_matrix(QQ, 4, 2) + sage: A * G.transpose() == zero_matrix(QQ, 4, 2) True We verify that the rational matrix code is called for both @@ -4211,7 +4222,7 @@ cdef class Matrix(Matrix1): sage: A = matrix(GF(2), [[0, 1, 1, 0, 0, 0], ....: [1, 0, 0, 0, 1, 1,], ....: [1, 0, 0, 0, 1, 1]]) - sage: set_verbose(2) + sage: set_verbose(2) # needs sage.rings.finite_rings sage: A.right_kernel(algorithm='default') verbose ... verbose ... () computing right kernel matrix over integers mod 2 for 3x6 matrix @@ -4430,9 +4441,9 @@ cdef class Matrix(Matrix1): ....: [-0.8090169944, 0.0, 0.5], ....: [ 0.8090169944, 0.0, -0.5], ....: [-0.8090169944, 0.0, -0.5]]).transpose() - sage: (A*A.right_kernel_matrix().transpose()).norm() > 2 + sage: (A * A.right_kernel_matrix().transpose()).norm() > 2 True - sage: (A*A.right_kernel_matrix(basis='computed').transpose()).norm() < 1e-15 + sage: (A * A.right_kernel_matrix(basis='computed').transpose()).norm() < 1e-15 True Trivial Cases: @@ -4725,7 +4736,7 @@ cdef class Matrix(Matrix1): [ 0 1 0 0 5 5 5] [ 0 0 1 0 -1 -2 -3] [ 0 0 0 1 0 1 1] - sage: A*K.basis_matrix().transpose() == zero_matrix(QQ, 4, 4) + sage: A * K.basis_matrix().transpose() == zero_matrix(QQ, 4, 4) True The default is basis vectors that form a matrix in echelon form. @@ -4752,7 +4763,7 @@ cdef class Matrix(Matrix1): [-1 0 0 -1 1 0 0] [-1 0 1 2 0 1 0] [ 1 0 -1 -1 0 0 1] - sage: A*K.basis_matrix().transpose() == zero_matrix(QQ, 4, 4) + sage: A * K.basis_matrix().transpose() == zero_matrix(QQ, 4, 4) True Matrices may have any field as a base ring. Number fields are @@ -4879,10 +4890,11 @@ cdef class Matrix(Matrix1): sage: A = matrix(R, [[ 1, y, 1+y^2], ....: [y^3, y^2, 2*y^3]]) sage: K = A.right_kernel(algorithm='default', basis='echelon'); K - Free module of degree 3 and rank 1 over Univariate Polynomial Ring in y over Rational Field - Echelon basis matrix: - [-1 -y 1] - sage: A*K.basis_matrix().transpose() == zero_matrix(ZZ, 2, 1) + Free module of degree 3 and rank 1 + over Univariate Polynomial Ring in y over Rational Field + Echelon basis matrix: + [-1 -y 1] + sage: A * K.basis_matrix().transpose() == zero_matrix(ZZ, 2, 1) True It is possible to compute a kernel for a matrix over an integral @@ -4894,7 +4906,8 @@ cdef class Matrix(Matrix1): sage: A.right_kernel() Traceback (most recent call last): ... - ArithmeticError: Ideal Ideal (x^2 - x, x^2 - 8) of Univariate Polynomial Ring in x over Integer Ring not principal + ArithmeticError: Ideal Ideal (x^2 - x, x^2 - 8) of Univariate + Polynomial Ring in x over Integer Ring not principal Matrices over non-commutative rings are not a good idea either. These are the "usual" quaternions. :: @@ -4904,7 +4917,8 @@ cdef class Matrix(Matrix1): sage: A.right_kernel() Traceback (most recent call last): ... - NotImplementedError: Cannot compute a matrix kernel over Quaternion Algebra (-1, -1) with base ring Rational Field + NotImplementedError: Cannot compute a matrix kernel over + Quaternion Algebra (-1, -1) with base ring Rational Field Sparse matrices, over the rationals and the integers, use the same routines as the dense versions. :: @@ -5331,9 +5345,9 @@ cdef class Matrix(Matrix1): EXAMPLES:: - sage: MS1 = MatrixSpace(ZZ,4) - sage: MS2 = MatrixSpace(QQ,6) - sage: A = MS1.matrix([3,4,5,6,7,3,8,10,14,5,6,7,2,2,10,9]) + sage: MS1 = MatrixSpace(ZZ, 4) + sage: MS2 = MatrixSpace(QQ, 6) + sage: A = MS1.matrix([3,4,5,6, 7,3,8,10, 14,5,6,7, 2,2,10,9]) sage: B = MS2.random_element() :: @@ -5436,8 +5450,8 @@ cdef class Matrix(Matrix1): EXAMPLES:: - sage: M = MatrixSpace(QQ,3,3) - sage: A = M([1,9,-7,4/5,4,3,6,4,3]) + sage: M = MatrixSpace(QQ, 3, 3) + sage: A = M([1,9,-7, 4/5,4,3, 6,4,3]) sage: A.column_space() Vector space of degree 3 and dimension 3 over Rational Field Basis matrix: @@ -5451,10 +5465,11 @@ cdef class Matrix(Matrix1): [ 1.00000000000000 2.00000000000000 + 3.00000000000000*I] [4.00000000000000 + 5.00000000000000*I 9.00000000000000] sage: B.column_space() - Vector space of degree 2 and dimension 2 over Complex Field with 53 bits of precision - Basis matrix: - [ 1.00000000000000 0.000000000000000] - [0.000000000000000 1.00000000000000] + Vector space of degree 2 and dimension 2 + over Complex Field with 53 bits of precision + Basis matrix: + [ 1.00000000000000 0.000000000000000] + [0.000000000000000 1.00000000000000] """ return self.column_module() @@ -5514,7 +5529,7 @@ cdef class Matrix(Matrix1): EXAMPLES:: - sage: A = matrix(ZZ, 4, [3,4,5,6,7,3,8,10,14,5,6,7,2,2,10,9]) + sage: A = matrix(ZZ, 4, [3,4,5,6, 7,3,8,10, 14,5,6,7, 2,2,10,9]) sage: B = matrix(QQ, 6, 6, range(36)) sage: B*11 [ 0 11 22 33 44 55] @@ -5895,7 +5910,7 @@ cdef class Matrix(Matrix1): EXAMPLES:: sage: V = QQ^3 - sage: A = matrix(QQ,3,[1,2,0, 3,4,0, 0,0,0]) + sage: A = matrix(QQ, 3, [1,2,0, 3,4,0, 0,0,0]) sage: W = V.subspace([[1,0,0], [1,2,3]]) sage: A.restrict_domain(W) [1 2 0] @@ -5930,7 +5945,7 @@ cdef class Matrix(Matrix1): EXAMPLES:: - sage: A = matrix(QQ,3,[1..9]) + sage: A = matrix(QQ, 3, [1..9]) sage: V = (QQ^3).span([[1,2,3], [7,8,9]]); V Vector space of degree 3 and dimension 2 over Rational Field Basis matrix: @@ -6351,12 +6366,13 @@ cdef class Matrix(Matrix1): sage: A.change_ring(RR).eigenspaces_left() Traceback (most recent call last): ... - NotImplementedError: eigenspaces cannot be computed reliably for inexact rings such as Real Field with 53 bits of precision, + NotImplementedError: eigenspaces cannot be computed reliably + for inexact rings such as Real Field with 53 bits of precision, consult numerical or symbolic matrix classes for other options sage: # needs scipy sage: em = A.change_ring(RDF).eigenmatrix_left() - sage: eigenvalues = em[0]; eigenvalues.dense_matrix() # abs tol 1e-13 + sage: eigenvalues = em[0]; eigenvalues.dense_matrix() # abs tol 1e-13 [13.348469228349522 0.0 0.0] [ 0.0 -1.348469228349534 0.0] [ 0.0 0.0 0.0] @@ -7547,8 +7563,8 @@ cdef class Matrix(Matrix1): Since :meth:`echelon_form` is not implemented for every ring, sometimes behavior varies, as here:: - sage: R.=ZZ[] - sage: C = matrix(3,[2,x,x^2,x+1,3-x,-1,3,2,1]) + sage: R. = ZZ[] + sage: C = matrix(3, [2,x,x^2, x+1,3-x,-1, 3,2,1]) sage: C.rref() [1 0 0] [0 1 0] @@ -7570,7 +7586,7 @@ cdef class Matrix(Matrix1): [1 0 0] [0 1 0] [0 0 1] - sage: C = matrix(3,[2,x,x^2,x+1,3-x,-1/x,3,2,1/2]) + sage: C = matrix(3, [2,x,x^2, x+1,3-x,-1/x, 3,2,1/2]) sage: C.echelon_form() [1 0 0] [0 1 0] @@ -7720,7 +7736,7 @@ cdef class Matrix(Matrix1): EXAMPLES:: - sage: a = matrix(QQ,3,3,range(9)); a + sage: a = matrix(QQ, 3,3, range(9)); a [0 1 2] [3 4 5] [6 7 8] @@ -7733,7 +7749,7 @@ cdef class Matrix(Matrix1): An immutable matrix cannot be transformed into echelon form. Use ``self.echelon_form()`` instead:: - sage: a = matrix(QQ,3,3,range(9)); a.set_immutable() + sage: a = matrix(QQ, 3,3, range(9)); a.set_immutable() sage: a.echelonize() Traceback (most recent call last): ... @@ -7747,7 +7763,7 @@ cdef class Matrix(Matrix1): Echelon form over the integers is what is also classically often known as Hermite normal form:: - sage: a = matrix(ZZ,3,3,range(9)) + sage: a = matrix(ZZ, 3,3, range(9)) sage: a.echelonize(); a [ 3 0 -3] [ 0 1 2] @@ -7786,7 +7802,7 @@ cdef class Matrix(Matrix1): Echelon form is not defined over arbitrary rings:: - sage: a = matrix(Integers(9),3,3,range(9)) + sage: a = matrix(Integers(9), 3,3, range(9)) sage: a.echelon_form() Traceback (most recent call last): ... @@ -7981,7 +7997,7 @@ cdef class Matrix(Matrix1): [ 1 0 -1] [ 0 1 2] [ 0 0 0] - sage: a = matrix(QQ,2,[1..6]) + sage: a = matrix(QQ, 2, [1..6]) sage: a._echelon('classical') [ 1 0 -1] [ 0 1 2] @@ -8040,7 +8056,7 @@ cdef class Matrix(Matrix1): [ 1 0 -1] [ 0 1 2] [ 0 0 0] - sage: a = matrix(QQ,2,[1..6]) + sage: a = matrix(QQ, 2, [1..6]) sage: a._echelon_classical() [ 1 0 -1] [ 0 1 2] @@ -8215,7 +8231,7 @@ cdef class Matrix(Matrix1): [ 1 0 -1] [ 0 1 2] [ 0 0 0] - sage: a = matrix(QQ,2,[1..6]) + sage: a = matrix(QQ, 2, [1..6]) sage: P = a._echelon_in_place_classical(); a [ 1 0 -1] [ 0 1 2] @@ -8803,7 +8819,7 @@ cdef class Matrix(Matrix1): EXAMPLES:: - sage: a = matrix(ZZ,4,4,range(16)) + sage: a = matrix(ZZ, 4,4, range(16)) sage: a._multiply_strassen(a,2) [ 56 62 68 74] [152 174 196 218] @@ -9519,16 +9535,16 @@ cdef class Matrix(Matrix1): EXAMPLES:: - sage: m = matrix(QQ,2,2,range(4)) + sage: m = matrix(QQ, 2,2, range(4)) sage: m.is_one() False - sage: m = matrix(QQ,2,[5,0,0,5]) + sage: m = matrix(QQ, 2, [5,0,0,5]) sage: m.is_one() False - sage: m = matrix(QQ,2,[1,0,0,1]) + sage: m = matrix(QQ, 2, [1,0,0,1]) sage: m.is_one() True - sage: m = matrix(QQ,2,[1,1,1,1]) + sage: m = matrix(QQ, 2, [1,1,1,1]) sage: m.is_one() False """ @@ -9550,16 +9566,16 @@ cdef class Matrix(Matrix1): EXAMPLES:: - sage: m = matrix(QQ,2,2,range(4)) + sage: m = matrix(QQ, 2,2, range(4)) sage: m.is_scalar(5) False - sage: m = matrix(QQ,2,[5,0,0,5]) + sage: m = matrix(QQ, 2, [5,0,0,5]) sage: m.is_scalar(5) True - sage: m = matrix(QQ,2,[1,0,0,1]) + sage: m = matrix(QQ, 2, [1,0,0,1]) sage: m.is_scalar(1) True - sage: m = matrix(QQ,2,[1,1,1,1]) + sage: m = matrix(QQ, 2, [1,1,1,1]) sage: m.is_scalar(1) False """ @@ -9592,16 +9608,16 @@ cdef class Matrix(Matrix1): EXAMPLES:: - sage: m = matrix(QQ,2,2,range(4)) + sage: m = matrix(QQ, 2,2, range(4)) sage: m.is_diagonal() False - sage: m = matrix(QQ,2,[5,0,0,5]) + sage: m = matrix(QQ, 2, [5,0,0,5]) sage: m.is_diagonal() True - sage: m = matrix(QQ,2,[1,0,0,1]) + sage: m = matrix(QQ, 2, [1,0,0,1]) sage: m.is_diagonal() True - sage: m = matrix(QQ,2,[1,1,1,1]) + sage: m = matrix(QQ, 2, [1,1,1,1]) sage: m.is_diagonal() False """ @@ -10054,7 +10070,7 @@ cdef class Matrix(Matrix1): :: - sage: A = matrix(QQ,3,3,[0,1,2,3,0,0,6,7,8]) + sage: A = matrix(QQ, 3,3, [0,1,2,3,0,0,6,7,8]) sage: A.density() 2/3 @@ -14628,7 +14644,7 @@ cdef class Matrix(Matrix1): sage: n = ZZ.random_element(6) sage: A = matrix.diagonal(random_vector(QQ, n)) - sage: I = matrix.identity(QQ,n) + sage: I = matrix.identity(QQ, n) sage: P,L,D = A.block_ldlt() sage: P == I and L == I and A == D True @@ -14914,7 +14930,7 @@ cdef class Matrix(Matrix1): ....: [ 2, -7, 4, 7]]) sage: A.is_positive_definite() True - sage: [A[:i,:i].determinant() for i in range(1,A.nrows()+1)] + sage: [A[:i,:i].determinant() for i in range(1, A.nrows() + 1)] [4, 36, 144, 144] A real symmetric matrix that is not positive-definite and a @@ -15057,7 +15073,7 @@ cdef class Matrix(Matrix1): EXAMPLES:: - sage: A = Matrix([[1,-1/2,0],[-1/2,1,-1/2],[0,-1/2,1]]) + sage: A = Matrix([[1,-1/2,0], [-1/2,1,-1/2], [0,-1/2,1]]) sage: B = A.principal_square_root() sage: A == B^2 True @@ -15385,7 +15401,7 @@ cdef class Matrix(Matrix1): EXAMPLES:: - sage: A = matrix(ZZ, [[1,2,4,3],[-1,0,3,-10]]) + sage: A = matrix(ZZ, [[1,2,4,3], [-1,0,3,-10]]) sage: A.norm(1) 13.0 sage: A.norm(Infinity) diff --git a/src/sage/matrix/matrix_generic_sparse.pyx b/src/sage/matrix/matrix_generic_sparse.pyx index 59fffe388d2..bc251f3e5c0 100644 --- a/src/sage/matrix/matrix_generic_sparse.pyx +++ b/src/sage/matrix/matrix_generic_sparse.pyx @@ -47,7 +47,8 @@ EXAMPLES:: [ 5 x x^2] [x^3 x^4 x^5] sage: parent(d) - Full MatrixSpace of 2 by 3 dense matrices over Univariate Polynomial Ring in x over Rational Field + Full MatrixSpace of 2 by 3 dense matrices + over Univariate Polynomial Ring in x over Rational Field sage: c.sparse_matrix() is c True sage: c.is_sparse() diff --git a/src/sage/matrix/matrix_space.py b/src/sage/matrix/matrix_space.py index 7f93cceb6ad..aa9a6fe6111 100644 --- a/src/sage/matrix/matrix_space.py +++ b/src/sage/matrix/matrix_space.py @@ -7,11 +7,11 @@ EXAMPLES:: - sage: MS = MatrixSpace(QQ,6,6,sparse=True); MS + sage: MS = MatrixSpace(QQ, 6,6, sparse=True); MS Full MatrixSpace of 6 by 6 sparse matrices over Rational Field sage: MS.base_ring() Rational Field - sage: MS = MatrixSpace(ZZ,3,5,sparse=False); MS + sage: MS = MatrixSpace(ZZ, 3,5, sparse=False); MS Full MatrixSpace of 3 by 5 dense matrices over Integer Ring TESTS:: @@ -2091,8 +2091,8 @@ def matrix(self, x=None, **kwds): :issue:`10628` allowed to provide the data as lists of matrices, but :issue:`13012` prohibited it:: - sage: MS = MatrixSpace(ZZ,4,2) - sage: MS0 = MatrixSpace(ZZ,2) + sage: MS = MatrixSpace(ZZ, 4,2) + sage: MS0 = MatrixSpace(ZZ, 2) sage: MS.matrix([MS0([1,2,3,4]), MS0([5,6,7,8])]) Traceback (most recent call last): ... @@ -2164,7 +2164,7 @@ def ncols(self): EXAMPLES:: - sage: M = Mat(ZZ['x'],200000,500000,sparse=True) + sage: M = Mat(ZZ['x'], 200000, 500000, sparse=True) sage: M.ncols() 500000 """ @@ -2176,7 +2176,7 @@ def nrows(self): EXAMPLES:: - sage: M = Mat(ZZ,200000,500000) + sage: M = Mat(ZZ, 200000, 500000) sage: M.nrows() 200000 """ @@ -2381,12 +2381,12 @@ def _magma_init_(self, magma): :: - sage: magma(MatrixSpace(QQ,3)) # optional - magma + sage: magma(MatrixSpace(QQ, 3)) # optional - magma Full Matrix Algebra of degree 3 over Rational Field :: - sage: magma(MatrixSpace(Integers(8),2,3)) # optional - magma + sage: magma(MatrixSpace(Integers(8), 2, 3)) # optional - magma Full RMatrixSpace of 2 by 3 matrices over IntegerRing(8) """ K = magma(self.base_ring()) diff --git a/src/sage/matrix/matrix_sparse.pyx b/src/sage/matrix/matrix_sparse.pyx index 0648f488f98..b494e699568 100644 --- a/src/sage/matrix/matrix_sparse.pyx +++ b/src/sage/matrix/matrix_sparse.pyx @@ -696,7 +696,7 @@ cdef class Matrix_sparse(matrix.Matrix): If we did not specify the codomain, the resulting matrix in the above case ends up over `\ZZ` again:: - sage: n = m.apply_map(lambda x:x%3) + sage: n = m.apply_map(lambda x: x%3) sage: n.parent() Full MatrixSpace of 10000 by 10000 sparse matrices over Integer Ring sage: n[1, 2] diff --git a/src/sage/matrix/symplectic_basis.py b/src/sage/matrix/symplectic_basis.py index 66dc1883a12..db38f5f3611 100644 --- a/src/sage/matrix/symplectic_basis.py +++ b/src/sage/matrix/symplectic_basis.py @@ -252,7 +252,7 @@ def symplectic_basis_over_field(M): [ 1.14718543053828 -0.278305070958958 0.0840205427053993 0.356957405431611 0.836072521423577 0.000000000000000 0.214878541347751 -1.20221688928379] [ -1.03076138152950 0.0781320488361574 -1.28202592892333 0.699960114607661 -0.450137632758469 -0.214878541347751 0.000000000000000 0.785074452163036] [ 0.227739521708484 0.496003664217833 0.512563654267693 -0.0260496330859998 0.696145287292091 1.20221688928379 -0.785074452163036 0.000000000000000] - sage: F, C = symplectic_basis_over_field(E); F # random + sage: F, C = symplectic_basis_over_field(E); F # random [ 0.000000000000000 0.000000000000000 2.22044604925031e-16 -2.22044604925031e-16 1.00000000000000 0.000000000000000 0.000000000000000 -3.33066907387547e-16] [ 0.000000000000000 8.14814392305203e-17 -1.66533453693773e-16 -1.11022302462516e-16 0.000000000000000 1.00000000000000 -1.11022302462516e-16 0.000000000000000] [-5.27829526256056e-16 -2.40004077757759e-16 1.28373418199470e-16 -1.11022302462516e-16 0.000000000000000 -3.15483812822081e-16 1.00000000000000 -4.44089209850063e-16] From a456cdc727df24eb19715fd717f1433ffbb0e231 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 13 Mar 2024 21:42:13 -0700 Subject: [PATCH 245/518] src/sage/modules: Doctest cosmetics --- src/sage/modules/free_module.py | 159 ++++++++++-------- src/sage/modules/free_module_element.pyx | 32 ++-- src/sage/modules/free_module_homspace.py | 69 ++++---- src/sage/modules/free_module_integer.py | 3 +- src/sage/modules/free_module_morphism.py | 26 +-- src/sage/modules/free_quadratic_module.py | 41 ++--- ...free_quadratic_module_integer_symmetric.py | 2 +- src/sage/modules/matrix_morphism.py | 68 ++++---- src/sage/modules/quotient_module.py | 18 +- src/sage/modules/torsion_quadratic_module.py | 2 +- src/sage/modules/vector_integer_dense.pyx | 2 +- src/sage/modules/vector_modn_dense.pyx | 6 +- .../modules/with_basis/indexed_element.pyx | 6 +- 13 files changed, 225 insertions(+), 209 deletions(-) diff --git a/src/sage/modules/free_module.py b/src/sage/modules/free_module.py index fd3c2735838..cc74991f309 100644 --- a/src/sage/modules/free_module.py +++ b/src/sage/modules/free_module.py @@ -18,7 +18,7 @@ EXAMPLES:: - sage: V = VectorSpace(QQ,3) + sage: V = VectorSpace(QQ, 3) sage: W = V.subspace([[1,2,7], [1,1,0]]) sage: W Vector space of degree 3 and dimension 2 over Rational Field @@ -75,10 +75,10 @@ sage: print([v for _,v in zip(range(31), ZZ^3)]) [(0, 0, 0), - (1, 0, 0), (-1, 0, 0), (0, 1, 0), (0, -1, 0), (0, 0, 1), (0, 0, -1), - (1, 1, 0), (-1, 1, 0), (1, -1, 0), (-1, -1, 0), (1, 0, 1), (-1, 0, 1), (1, 0, -1), (-1, 0, -1), (0, 1, 1), (0, -1, 1), (0, 1, -1), (0, -1, -1), - (2, 0, 0), (-2, 0, 0), (0, 2, 0), (0, -2, 0), (0, 0, 2), (0, 0, -2), - (1, 1, 1), (-1, 1, 1), (1, -1, 1), (-1, -1, 1), (1, 1, -1), ...] + (1, 0, 0), (-1, 0, 0), (0, 1, 0), (0, -1, 0), (0, 0, 1), (0, 0, -1), + (1, 1, 0), (-1, 1, 0), (1, -1, 0), (-1, -1, 0), (1, 0, 1), (-1, 0, 1), (1, 0, -1), (-1, 0, -1), (0, 1, 1), (0, -1, 1), (0, 1, -1), (0, -1, -1), + (2, 0, 0), (-2, 0, 0), (0, 2, 0), (0, -2, 0), (0, 0, 2), (0, 0, -2), + (1, 1, 1), (-1, 1, 1), (1, -1, 1), (-1, -1, 1), (1, 1, -1), ...] For other infinite enumerated base rings (i.e., rings which are objects of the category :class:`InfiniteEnumeratedSets`), @@ -399,12 +399,14 @@ def FreeModule(base_ring, rank_or_basis_keys=None, sparse=False, inner_product_m Ambient free module of rank 10 over the principal ideal domain Integer Ring sage: FreeModule(FiniteField(5), 10) Vector space of dimension 10 over Finite Field of size 5 - sage: FreeModule(Integers(7),10) + sage: FreeModule(Integers(7), 10) Vector space of dimension 10 over Ring of integers modulo 7 - sage: FreeModule(PolynomialRing(QQ,'x'),5) - Ambient free module of rank 5 over the principal ideal domain Univariate Polynomial Ring in x over Rational Field - sage: FreeModule(PolynomialRing(ZZ,'x'),5) - Ambient free module of rank 5 over the integral domain Univariate Polynomial Ring in x over Integer Ring + sage: FreeModule(PolynomialRing(QQ,'x'), 5) + Ambient free module of rank 5 over the principal ideal domain + Univariate Polynomial Ring in x over Rational Field + sage: FreeModule(PolynomialRing(ZZ,'x'), 5) + Ambient free module of rank 5 over the integral domain + Univariate Polynomial Ring in x over Integer Ring Of course we can make rank 0 free modules:: @@ -450,8 +452,8 @@ def FreeModule(base_ring, rank_or_basis_keys=None, sparse=False, inner_product_m :: - sage: A = MatrixSpace(ZZ,2)([[1,0],[0,-1]]) - sage: M = FreeModule(ZZ,2,inner_product_matrix=A) + sage: A = MatrixSpace(ZZ, 2)([[1,0], [0,-1]]) + sage: M = FreeModule(ZZ, 2, inner_product_matrix=A) sage: v, w = M.gens() sage: v.inner_product(w) 0 @@ -468,13 +470,13 @@ def FreeModule(base_ring, rank_or_basis_keys=None, sparse=False, inner_product_m :: - sage: FreeModule(ZZ,2,inner_product_matrix=1).inner_product_matrix() + sage: FreeModule(ZZ, 2, inner_product_matrix=1).inner_product_matrix() [1 0] [0 1] - sage: FreeModule(ZZ,2,inner_product_matrix=[1,2,3,4]).inner_product_matrix() + sage: FreeModule(ZZ, 2, inner_product_matrix=[1,2,3,4]).inner_product_matrix() [1 2] [3 4] - sage: FreeModule(ZZ,2,inner_product_matrix=[[1,2],[3,4]]).inner_product_matrix() + sage: FreeModule(ZZ, 2, inner_product_matrix=[[1,2], [3,4]]).inner_product_matrix() [1 2] [3 4] @@ -1231,7 +1233,7 @@ def __richcmp__(self, other, op): We test that :issue:`5525` is fixed:: - sage: A = (QQ^1).span([[1/3]],ZZ); B = (QQ^1).span([[1]],ZZ) + sage: A = (QQ^1).span([[1/3]], ZZ); B = (QQ^1).span([[1]], ZZ) sage: A.intersection(B) Free module of degree 1 and rank 1 over Integer Ring Echelon basis matrix: @@ -1280,8 +1282,8 @@ def __richcmp__(self, other, op): sage: P. = QQ[] sage: M = P**2 - sage: S1 = M.submodule([(x,y),(y,x)]) - sage: S2 = M.submodule([(x,y),(y-x,x-y)]) + sage: S1 = M.submodule([(x, y), (y, x)]) + sage: S2 = M.submodule([(x, y), (y - x, x - y)]) sage: S1 == S2 False sage: S1 != S2 @@ -1597,14 +1599,15 @@ def span(self, gens, base_ring=None, check=True, already_echelonized=False): sage: S. = PolynomialRing(QQ) sage: M = S**2 sage: M.span([vector([x - y, z]), vector([y*z, x*z])]) - Submodule of Ambient free module of rank 2 over the integral domain Multivariate Polynomial Ring in x, y, z over Rational Field + Submodule of Ambient free module of rank 2 over the integral domain + Multivariate Polynomial Ring in x, y, z over Rational Field Generated by the rows of the matrix: [x - y z] [ y*z x*z] Over a PID:: - sage: V = FreeModule(ZZ,3) + sage: V = FreeModule(ZZ, 3) sage: W = V.submodule([V.gen(0)]) sage: W.span([V.gen(1)]) Free module of degree 3 and rank 1 over Integer Ring @@ -1614,7 +1617,7 @@ def span(self, gens, base_ring=None, check=True, already_echelonized=False): Traceback (most recent call last): ... ArithmeticError: argument gens (= [(0, 1, 0)]) does not generate a submodule of self - sage: V.span([[1,0,0],[1/5,4,0],[6,3/4,0]]) + sage: V.span([[1,0,0], [1/5,4,0], [6,3/4,0]]) Free module of degree 3 and rank 2 over Integer Ring Echelon basis matrix: [1/5 0 0] @@ -1622,19 +1625,21 @@ def span(self, gens, base_ring=None, check=True, already_echelonized=False): It also works with other things than integers:: - sage: R.=QQ[] - sage: L=R^1 - sage: a=L.span([(1/x,)]) + sage: R. = QQ[] + sage: L = R^1 + sage: a = L.span([(1/x,)]) sage: a - Free module of degree 1 and rank 1 over Univariate Polynomial Ring in x over Rational Field + Free module of degree 1 and rank 1 + over Univariate Polynomial Ring in x over Rational Field Echelon basis matrix: [1/x] sage: b=L.span([(1/x,)]) sage: a(b.gens()[0]) (1/x) sage: L2 = R^2 - sage: L2.span([[(x^2+x)/(x^2-3*x+2),1/5],[(x^2+2*x)/(x^2-4*x+3),x]]) - Free module of degree 2 and rank 2 over Univariate Polynomial Ring in x over Rational Field + sage: L2.span([[(x^2+x)/(x^2-3*x+2), 1/5], [(x^2+2*x)/(x^2-4*x+3), x]]) + Free module of degree 2 and rank 2 + over Univariate Polynomial Ring in x over Rational Field Echelon basis matrix: [x/(x^3 - 6*x^2 + 11*x - 6) 2/15*x^2 - 17/75*x - 1/75] [ 0 x^3 - 11/5*x^2 - 3*x + 4/5] @@ -1643,8 +1648,10 @@ def span(self, gens, base_ring=None, check=True, already_echelonized=False): repeat the previous example over the fraction field of R and get a simpler vector space. :: - sage: L2.span([[(x^2+x)/(x^2-3*x+2),1/5],[(x^2+2*x)/(x^2-4*x+3),x]],base_ring=R.fraction_field()) - Vector space of degree 2 and dimension 2 over Fraction Field of Univariate Polynomial Ring in x over Rational Field + sage: L2.span([[(x^2+x)/(x^2-3*x+2), 1/5], [(x^2+2*x)/(x^2-4*x+3), x]], + ....: base_ring=R.fraction_field()) + Vector space of degree 2 and dimension 2 over + Fraction Field of Univariate Polynomial Ring in x over Rational Field Basis matrix: [1 0] [0 1] @@ -1709,7 +1716,7 @@ def submodule(self, gens, check=True, already_echelonized=False): sage: M = FreeModule(ZZ, 3) sage: B = M.basis() - sage: W = M.submodule([B[0]+B[1], 2*B[1]-B[2]]) + sage: W = M.submodule([B[0] + B[1], 2*B[1] - B[2]]) sage: W Free module of degree 3 and rank 2 over Integer Ring Echelon basis matrix: @@ -1737,20 +1744,23 @@ def submodule(self, gens, check=True, already_echelonized=False): sage: R = PolynomialRing(QQ, 'x'); x = R.gen() sage: M = FreeModule(R, 3) sage: B = M.basis() - sage: W = M.submodule([x*B[0], 2*B[1]- x*B[2]]); W - Free module of degree 3 and rank 2 over Univariate Polynomial Ring in x over Rational Field + sage: W = M.submodule([x*B[0], 2*B[1] - x*B[2]]); W + Free module of degree 3 and rank 2 + over Univariate Polynomial Ring in x over Rational Field Echelon basis matrix: [ x 0 0] [ 0 2 -x] sage: W.ambient_module() - Ambient free module of rank 3 over the principal ideal domain Univariate Polynomial Ring in x over Rational Field + Ambient free module of rank 3 over the principal ideal domain + Univariate Polynomial Ring in x over Rational Field Over a generic ring:: sage: S. = PolynomialRing(QQ) sage: A = S**2 sage: A.submodule([vector([x - y,z]), vector([y*z, x*z])]) - Submodule of Ambient free module of rank 2 over the integral domain Multivariate Polynomial Ring in x, y, z over Rational Field + Submodule of Ambient free module of rank 2 over the integral domain + Multivariate Polynomial Ring in x, y, z over Rational Field Generated by the rows of the matrix: [x - y z] [ y*z x*z] @@ -1952,14 +1962,14 @@ def __init__(self, base_ring, rank, degree, sparse=False, TESTS:: - sage: M = FreeModule(ZZ,20,sparse=False) + sage: M = FreeModule(ZZ, 20, sparse=False) sage: x = M.random_element() sage: type(x) sage: M.element_class - sage: N = FreeModule(ZZ,20,sparse=True) + sage: N = FreeModule(ZZ, 20, sparse=True) sage: y = N.random_element() sage: type(y) @@ -1996,7 +2006,7 @@ def construction(self): EXAMPLES:: - sage: R = PolynomialRing(QQ,3,'x') + sage: R = PolynomialRing(QQ, 3, 'x') sage: V = R^5 sage: V.construction() (VectorFunctor, Multivariate Polynomial Ring in x0, x1, x2 over Rational Field) @@ -2021,8 +2031,8 @@ def dense_module(self): We first illustrate conversion with ambient spaces:: - sage: M = FreeModule(QQ,3) - sage: S = FreeModule(QQ,3, sparse=True) + sage: M = FreeModule(QQ, 3) + sage: S = FreeModule(QQ, 3, sparse=True) sage: M.sparse_module() Sparse vector space of dimension 3 over Rational Field sage: S.dense_module() @@ -2038,7 +2048,7 @@ def dense_module(self): Next we create a subspace:: - sage: M = FreeModule(QQ,3, sparse=True) + sage: M = FreeModule(QQ, 3, sparse=True) sage: V = M.span([ [1,2,3] ] ); V Sparse vector space of degree 3 and dimension 1 over Rational Field Basis matrix: @@ -2061,8 +2071,8 @@ def _dense_module(self): EXAMPLES:: - sage: M = FreeModule(Integers(8),3) - sage: S = FreeModule(Integers(8),3, sparse=True) + sage: M = FreeModule(Integers(8), 3) + sage: S = FreeModule(Integers(8), 3, sparse=True) sage: M is S._dense_module() True """ @@ -2078,8 +2088,8 @@ def sparse_module(self): We first illustrate conversion with ambient spaces:: - sage: M = FreeModule(Integers(8),3) - sage: S = FreeModule(Integers(8),3, sparse=True) + sage: M = FreeModule(Integers(8), 3) + sage: S = FreeModule(Integers(8), 3, sparse=True) sage: M.sparse_module() Ambient sparse free module of rank 3 over Ring of integers modulo 8 sage: S.dense_module() @@ -2118,8 +2128,8 @@ def _sparse_module(self): EXAMPLES:: - sage: M = FreeModule(Integers(8),3) - sage: S = FreeModule(Integers(8),3, sparse=True) + sage: M = FreeModule(Integers(8), 3) + sage: S = FreeModule(Integers(8), 3, sparse=True) sage: M._sparse_module() is S True """ @@ -2258,9 +2268,9 @@ def is_submodule(self, other): EXAMPLES:: - sage: M = FreeModule(ZZ,3) + sage: M = FreeModule(ZZ, 3) sage: V = M.ambient_vector_space() - sage: X = V.span([[1/2,1/2,0],[1/2,0,1/2]], ZZ) + sage: X = V.span([[1/2,1/2,0], [1/2,0,1/2]], ZZ) sage: Y = V.span([[1,1,1]], ZZ) sage: N = X + Y sage: M.is_submodule(X) @@ -2274,7 +2284,7 @@ def is_submodule(self, other): sage: M.is_submodule(N) True - sage: M = FreeModule(ZZ,2) + sage: M = FreeModule(ZZ, 2) sage: M.is_submodule(M) True sage: N = M.scale(2) @@ -2292,9 +2302,9 @@ def is_submodule(self, other): testing does not work for all PID's. However, trivial cases are already used (and useful) for coercion, e.g.:: - sage: QQ(1/2) * vector(ZZ['x']['y'],[1,2,3,4]) + sage: QQ(1/2) * vector(ZZ['x']['y'], [1,2,3,4]) (1/2, 1, 3/2, 2) - sage: vector(ZZ['x']['y'],[1,2,3,4]) * QQ(1/2) + sage: vector(ZZ['x']['y'], [1,2,3,4]) * QQ(1/2) (1/2, 1, 3/2, 2) TESTS:: @@ -2305,7 +2315,7 @@ def is_submodule(self, other): False sage: M1 = QQ^3 / [[1,2,3]] - sage: V1 = span(QQ,[(1,0,0)]) + M1.relations() + sage: V1 = span(QQ, [(1,0,0)]) + M1.relations() sage: M2 = V1 / M1.relations() sage: M2.is_submodule(M1) # Different ambient vector spaces False @@ -2555,7 +2565,7 @@ def basis_matrix(self, ring=None): EXAMPLES:: - sage: FreeModule(Integers(12),3).basis_matrix() + sage: FreeModule(Integers(12), 3).basis_matrix() [1 0 0] [0 1 0] [0 0 1] @@ -2580,7 +2590,7 @@ def basis_matrix(self, ring=None): :: - sage: M = FreeModule(QQ,2).span_of_basis([[1,-1],[1,0]]); M + sage: M = FreeModule(QQ, 2).span_of_basis([[1,-1], [1,0]]); M Vector space of degree 2 and dimension 2 over Rational Field User basis matrix: [ 1 -1] @@ -2644,7 +2654,7 @@ def echelonized_basis_matrix(self): sage: R = IntegerModRing(12) sage: S. = R[] - sage: M = FreeModule(S,3) + sage: M = FreeModule(S, 3) sage: M.echelonized_basis_matrix() [1 0 0] [0 1 0] @@ -2729,9 +2739,9 @@ def coordinates(self, v, check=True): EXAMPLES:: - sage: M = FreeModule(ZZ, 2); M0,M1=M.gens() + sage: M = FreeModule(ZZ, 2); M0, M1 = M.gens() sage: W = M.submodule([M0 + M1, M0 - 2*M1]) - sage: W.coordinates(2*M0-M1) + sage: W.coordinates(2*M0 - M1) [2, -1] """ return self.coordinate_vector(v, check=check).list() @@ -2752,7 +2762,7 @@ def coordinate_vector(self, v, check=True): EXAMPLES:: - sage: M = FreeModule(ZZ, 2); M0,M1=M.gens() + sage: M = FreeModule(ZZ, 2); M0, M1 = M.gens() sage: W = M.submodule([M0 + M1, M0 - 2*M1]) sage: W.coordinate_vector(2*M0 - M1) (2, -1) @@ -4253,7 +4263,7 @@ def vector_space_span(self, gens, check=True): :: - sage: M = FreeModule(ZZ,3) + sage: M = FreeModule(ZZ, 3) sage: W = M.submodule([M([1,2,3])]) sage: W.vector_space_span([M([2,3,4])]) Vector space of degree 3 and dimension 1 over Rational Field @@ -4282,7 +4292,7 @@ def vector_space_span_of_basis(self, basis, check=True): sage: V = VectorSpace(QQ, 3) sage: B = V.basis() - sage: W = V.vector_space_span_of_basis([B[0]+B[1], 2*B[1]-B[2]]) + sage: W = V.vector_space_span_of_basis([B[0] + B[1], 2*B[1] - B[2]]) sage: W Vector space of degree 3 and dimension 2 over Rational Field User basis matrix: @@ -4383,7 +4393,9 @@ def _Hom_(self, Y, category): sage: type(H) sage: H - Set of Morphisms (Linear Transformations) from Vector space of dimension 2 over Rational Field to Vector space of dimension 3 over Rational Field + Set of Morphisms (Linear Transformations) + from Vector space of dimension 2 over Rational Field + to Vector space of dimension 3 over Rational Field sage: V = QQ^2 sage: W = ZZ^3 @@ -5051,7 +5063,8 @@ def linear_dependence(self, vectors, zeros='left', check=True): sage: (RR^3).linear_dependence([v1,v2], check=True) Traceback (most recent call last): ... - ValueError: vector (1.00000000000000, 2.00000000000000, 3.00000000000000, 4.00000000000000) is not an element of Vector space of dimension 3 over Real Field with 53 bits of precision + ValueError: vector (1.00000000000000, 2.00000000000000, 3.00000000000000, 4.00000000000000) + is not an element of Vector space of dimension 3 over Real Field with 53 bits of precision The ``zeros`` keyword is checked. :: @@ -5282,7 +5295,7 @@ def quotient_abstract(self, sub, check=True, **kwds): Another example involving a quotient of one subspace by another:: - sage: A = matrix(QQ,4,4,[0,1,0,0, 0,0,1,0, 0,0,0,1, 0,0,0,0]) + sage: A = matrix(QQ, 4,4, [0,1,0,0, 0,0,1,0, 0,0,0,1, 0,0,0,0]) sage: V = (A^3).kernel() sage: W = A.kernel() sage: U, pi, lift = V.quotient_abstract(W) @@ -6030,7 +6043,7 @@ class FreeModule_ambient_domain(FreeModule_generic_domain, FreeModule_ambient): sage: FreeModule(PolynomialRing(GF(5), 'x'), 3) Ambient free module of rank 3 over the principal ideal domain - Univariate Polynomial Ring in x over Finite Field of size 5 + Univariate Polynomial Ring in x over Finite Field of size 5 """ def __init__(self, base_ring, rank, sparse=False, coordinate_ring=None, category=None): """ @@ -6051,7 +6064,7 @@ def _repr_(self): EXAMPLES:: sage: R = PolynomialRing(ZZ,'x') - sage: M = FreeModule(R,7) + sage: M = FreeModule(R, 7) sage: M Ambient free module of rank 7 over the integral domain Univariate Polynomial Ring in x over Integer Ring sage: print(M._repr_()) @@ -6071,7 +6084,7 @@ def _repr_(self): :: - sage: N = FreeModule(R,7,sparse=True) + sage: N = FreeModule(R, 7, sparse=True) sage: N Ambient sparse free module of rank 7 over the integral domain Univariate Polynomial Ring in x over Integer Ring @@ -6456,7 +6469,7 @@ class FreeModule_submodule_with_basis_pid(FreeModule_generic_pid): EXAMPLES:: sage: M = ZZ^3 - sage: W = M.span_of_basis([[1,2,3],[4,5,6]]); W + sage: W = M.span_of_basis([[1,2,3], [4,5,6]]); W Free module of degree 3 and rank 2 over Integer Ring User basis matrix: [1 2 3] @@ -6465,7 +6478,7 @@ class FreeModule_submodule_with_basis_pid(FreeModule_generic_pid): Now we create a submodule of the ambient vector space, rather than ``M`` itself:: - sage: W = M.span_of_basis([[1,2,3/2],[4,5,6]]); W + sage: W = M.span_of_basis([[1,2,3/2], [4,5,6]]); W Free module of degree 3 and rank 2 over Integer Ring User basis matrix: [ 1 2 3/2] @@ -6751,7 +6764,7 @@ def _denominator(self, B): EXAMPLES:: sage: V = QQ^3 - sage: L = V.span([[1,1/2,1/3], [-1/5,2/3,3]],ZZ) + sage: L = V.span([[1,1/2,1/3], [-1/5,2/3,3]], ZZ) sage: L Free module of degree 3 and rank 2 over Integer Ring Echelon basis matrix: @@ -7396,7 +7409,8 @@ def change_ring(self, R): [0 1] sage: N = M.change_ring(QQ['x']) sage: N - Free module of degree 2 and rank 2 over Univariate Polynomial Ring in x over Rational Field + Free module of degree 2 and rank 2 + over Univariate Polynomial Ring in x over Rational Field Echelon basis matrix: [1/2 0] [ 0 1/2] @@ -7408,7 +7422,8 @@ def change_ring(self, R): sage: M.change_ring(ZZ['x']) Traceback (most recent call last): ... - TypeError: the new ring Univariate Polynomial Ring in x over Integer Ring should be a principal ideal domain + TypeError: the new ring Univariate Polynomial Ring in x over Integer Ring + should be a principal ideal domain """ if self.base_ring() is R: return self @@ -8290,7 +8305,7 @@ def __init__(self, obj): EXAMPLES:: sage: R. = QQ[] - sage: V = span(R,[[x,1+x],[x^2,2+x]]) + sage: V = span(R, [[x, 1 + x], [x^2, 2 + x]]) sage: W = R^2 sage: from sage.modules.free_module import EchelonMatrixKey sage: V = EchelonMatrixKey(V) diff --git a/src/sage/modules/free_module_element.pyx b/src/sage/modules/free_module_element.pyx index ff43d239493..1483bfd721b 100644 --- a/src/sage/modules/free_module_element.pyx +++ b/src/sage/modules/free_module_element.pyx @@ -82,7 +82,7 @@ field. Matrix vector multiply:: - sage: MS = MatrixSpace(QQ,3) + sage: MS = MatrixSpace(QQ, 3) sage: A = MS([0,1,0,1,0,0,0,0,1]) sage: V = QQ^3 sage: v = V([1,2,3]) @@ -1033,36 +1033,36 @@ cdef class FreeModuleElement(Vector): # abstract base class sage: F = FreeModule(ZZ, 2, inner_product_matrix=matrix(ZZ, 2, 2, [1, 0, 0, -1])) sage: v = F([1, 2]) - sage: M = magma(v); M # optional - magma + sage: M = magma(v); M # optional - magma (1 2) - sage: M.Type() # optional - magma + sage: M.Type() # optional - magma ModTupRngElt - sage: M.Parent() # optional - magma + sage: M.Parent() # optional - magma Full RSpace of degree 2 over Integer Ring Inner Product Matrix: [ 1 0] [ 0 -1] - sage: M.sage() # optional - magma + sage: M.sage() # optional - magma (1, 2) - sage: M.sage() == v # optional - magma + sage: M.sage() == v # optional - magma True - sage: M.sage().parent() is v.parent() # optional - magma + sage: M.sage().parent() is v.parent() # optional - magma True :: sage: v = vector(QQ, [1, 2, 5/6]) - sage: M = magma(v); M # optional - magma + sage: M = magma(v); M # optional - magma ( 1 2 5/6) - sage: M.Type() # optional - magma + sage: M.Type() # optional - magma ModTupFldElt - sage: M.Parent() # optional - magma + sage: M.Parent() # optional - magma Full Vector space of degree 3 over Rational Field - sage: M.sage() # optional - magma + sage: M.sage() # optional - magma (1, 2, 5/6) - sage: M.sage() == v # optional - magma + sage: M.sage() == v # optional - magma True - sage: M.sage().parent() is v.parent() # optional - magma + sage: M.sage().parent() is v.parent() # optional - magma True """ # Get a reference to Magma version of parent. @@ -2332,7 +2332,7 @@ cdef class FreeModuleElement(Vector): # abstract base class Three-dimensional examples:: sage: v = vector(RDF, (1,2,1)) - sage: plot(v) # defaults to an arrow plot # needs sage.plot + sage: plot(v) # defaults to an arrow plot # needs sage.plot Graphics3d Object :: @@ -2348,7 +2348,7 @@ cdef class FreeModuleElement(Vector): # abstract base class :: - sage: plot(v, plot_type='step') # calls v.plot_step() # needs sage.plot + sage: plot(v, plot_type='step') # calls v.plot_step() # needs sage.plot Graphics object consisting of 1 graphics primitive :: @@ -2377,7 +2377,7 @@ cdef class FreeModuleElement(Vector): # abstract base class TESTS:: sage: u = vector([1,1]); v = vector([2,2,2]); z=(3,3,3) - sage: plot(u) #test when start=None # needs sage.plot + sage: plot(u) #test when start=None # needs sage.plot Graphics object consisting of 1 graphics primitive :: diff --git a/src/sage/modules/free_module_homspace.py b/src/sage/modules/free_module_homspace.py index f494e4aba36..9513a56ea56 100644 --- a/src/sage/modules/free_module_homspace.py +++ b/src/sage/modules/free_module_homspace.py @@ -5,7 +5,7 @@ We create `\mathrm{End}(\ZZ^2)` and compute a basis. :: - sage: M = FreeModule(IntegerRing(),2) + sage: M = FreeModule(IntegerRing(), 2) sage: E = End(M) sage: B = E.basis() sage: len(B) @@ -14,22 +14,21 @@ Free module morphism defined by the matrix [1 0] [0 0] - Domain: Ambient free module of rank 2 over the principal ideal domain ... + Domain: Ambient free module of rank 2 over the principal ideal domain ... Codomain: Ambient free module of rank 2 over the principal ideal domain ... We create `\mathrm{Hom}(\ZZ^3, \ZZ^2)` and compute a basis. :: - sage: V3 = FreeModule(IntegerRing(),3) - sage: V2 = FreeModule(IntegerRing(),2) - sage: H = Hom(V3,V2) + sage: V3 = FreeModule(IntegerRing(), 3) + sage: V2 = FreeModule(IntegerRing(), 2) + sage: H = Hom(V3, V2) sage: H - Set of Morphisms from Ambient free module of rank 3 over - the principal ideal domain Integer Ring - to Ambient free module of rank 2 - over the principal ideal domain Integer Ring - in Category of finite dimensional modules with basis over - (Dedekind domains and euclidean domains - and infinite enumerated sets and metric spaces) + Set of Morphisms + from Ambient free module of rank 3 over the principal ideal domain Integer Ring + to Ambient free module of rank 2 over the principal ideal domain Integer Ring + in Category of finite dimensional modules with basis over + (Dedekind domains and euclidean domains + and infinite enumerated sets and metric spaces) sage: B = H.basis() sage: len(B) 6 @@ -168,10 +167,10 @@ def __call__(self, A, **kwds): [0 0 0] [0 0 0] [0 0 0] - Domain: Free module of degree 3 and rank 3 over Integer Ring - Echelon ... + Domain: Free module of degree 3 and rank 3 over Integer Ring + Echelon ... Codomain: Free module of degree 3 and rank 3 over Integer Ring - Echelon ... + Echelon ... The following tests the bug fixed in :issue:`31818`. If there is no coercion between base rings, one can only define the zero morphism, @@ -189,7 +188,7 @@ def __call__(self, A, **kwds): Free module morphism defined by the matrix [0 0] [0 0] - Domain: Vector space of dimension 2 over Rational Field + Domain: Vector space of dimension 2 over Rational Field Codomain: Ambient free module of rank 2 over the principal ideal domain Integer Ring sage: [h(v) for v in V.gens()] [(0, 0), (0, 0)] @@ -237,7 +236,7 @@ def zero(self, side="left"): Free module morphism defined by the matrix [0 0 0] [0 0 0] - Domain: Ambient free module of rank 2 over the principal ideal domain Integer Ring + Domain: Ambient free module of rank 2 over the principal ideal domain Integer Ring Codomain: Ambient free module of rank 3 over the principal ideal domain Integer Ring sage: f(E.an_element()) (0, 0, 0) @@ -248,7 +247,7 @@ def zero(self, side="left"): [0 0] [0 0] [0 0] - Domain: Ambient free module of rank 2 over the principal ideal domain Integer Ring + Domain: Ambient free module of rank 2 over the principal ideal domain Integer Ring Codomain: Ambient free module of rank 3 over the principal ideal domain Integer Ring @@ -260,7 +259,7 @@ def zero(self, side="left"): Free module morphism defined by the matrix [0 0 0] [0 0 0] - Domain: Ambient free module of rank 2 over the principal ideal domain Integer Ring + Domain: Ambient free module of rank 2 over the principal ideal domain Integer Ring Codomain: Ambient free module of rank 3 over the principal ideal domain Integer Ring """ return self(lambda x: self.codomain().zero(), side=side) @@ -311,24 +310,24 @@ def basis(self, side="left"): sage: H = Hom(ZZ^2, ZZ^1) sage: H.basis() (Free module morphism defined by the matrix - [1] - [0] - Domain: Ambient free module of rank 2 over the principal ideal domain ... - Codomain: Ambient free module of rank 1 over the principal ideal domain ..., - Free module morphism defined by the matrix - [0] - [1] - Domain: Ambient free module of rank 2 over the principal ideal domain ... - Codomain: Ambient free module of rank 1 over the principal ideal domain ...) + [1] + [0] + Domain: Ambient free module of rank 2 over the principal ideal domain ... + Codomain: Ambient free module of rank 1 over the principal ideal domain ..., + Free module morphism defined by the matrix + [0] + [1] + Domain: Ambient free module of rank 2 over the principal ideal domain ... + Codomain: Ambient free module of rank 1 over the principal ideal domain ...) sage: H.basis("right") (Free module morphism defined as left-multiplication by the matrix - [1 0] - Domain: Ambient free module of rank 2 over the principal ideal domain ... - Codomain: Ambient free module of rank 1 over the principal ideal domain ..., + [1 0] + Domain: Ambient free module of rank 2 over the principal ideal domain ... + Codomain: Ambient free module of rank 1 over the principal ideal domain ..., Free module morphism defined as left-multiplication by the matrix - [0 1] - Domain: Ambient free module of rank 2 over the principal ideal domain ... - Codomain: Ambient free module of rank 1 over the principal ideal domain ...) + [0 1] + Domain: Ambient free module of rank 2 over the principal ideal domain ... + Codomain: Ambient free module of rank 1 over the principal ideal domain ...) """ M = self._matrix_space(side) B = M.basis() @@ -353,7 +352,7 @@ def identity(self, side="left"): [0 0 1 0 0] [0 0 0 1 0] [0 0 0 0 1] - Domain: Ambient free module of rank 5 over the principal ideal domain ... + Domain: Ambient free module of rank 5 over the principal ideal domain ... Codomain: Ambient free module of rank 5 over the principal ideal domain ... """ if self.is_endomorphism_set(): diff --git a/src/sage/modules/free_module_integer.py b/src/sage/modules/free_module_integer.py index 2d072679d66..c2023737e31 100644 --- a/src/sage/modules/free_module_integer.py +++ b/src/sage/modules/free_module_integer.py @@ -662,7 +662,8 @@ def voronoi_cell(self, radius=None): The volume of the Voronoi cell is the square root of the discriminant of the lattice:: - sage: L = IntegerLattice(Matrix(ZZ, 4, 4, [[0,0,1,-1],[1,-1,2,1],[-6,0,3,3,],[-6,-24,-6,-5]])); L + sage: L = IntegerLattice(Matrix(ZZ, 4, 4, [[0,0,1,-1], [1,-1,2,1], + ....: [-6,0,3,3,], [-6,-24,-6,-5]])); L Free module of degree 4 and rank 4 over Integer Ring User basis matrix: [ 0 0 1 -1] diff --git a/src/sage/modules/free_module_morphism.py b/src/sage/modules/free_module_morphism.py index 3c84527f8b7..b747ac6ed24 100644 --- a/src/sage/modules/free_module_morphism.py +++ b/src/sage/modules/free_module_morphism.py @@ -73,8 +73,8 @@ def __init__(self, parent, A, 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: V = ZZ^3; W = span([[1,2,3], [-1,2,8]], ZZ) + sage: phi = V.hom(matrix(ZZ, 3, [1..9])) sage: type(phi) """ @@ -91,8 +91,8 @@ def pushforward(self, x): EXAMPLES:: - sage: V = QQ^3; W = span([[1,2,3],[-1,2,5/3]], QQ) - sage: phi = V.hom(matrix(QQ,3,[1..9])) + sage: V = QQ^3; W = span([[1,2,3], [-1,2,5/3]], QQ) + sage: phi = V.hom(matrix(QQ, 3, [1..9])) sage: phi.rank() 2 sage: phi(V) #indirect doctest @@ -104,12 +104,12 @@ def pushforward(self, x): We compute the image of a submodule of a ZZ-module embedded in a rational vector space:: - sage: V = QQ^3; W = V.span_of_basis([[2,2,3],[-1,2,5/3]], ZZ) - sage: phi = W.hom([W.0, W.0-W.1]); phi + sage: V = QQ^3; W = V.span_of_basis([[2,2,3], [-1,2,5/3]], ZZ) + sage: phi = W.hom([W.0, W.0 - W.1]); phi Free module morphism defined by the matrix [ 1 0] [ 1 -1]... - sage: phi(span([2*W.1],ZZ)) + sage: phi(span([2*W.1], ZZ)) Free module of degree 3 and rank 1 over Integer Ring Echelon basis matrix: [ 6 0 8/3] @@ -127,8 +127,8 @@ def _repr_(self): EXAMPLES:: - sage: V = ZZ^3; W = span([[1,2,3],[-1,2,8]], ZZ) - sage: phi = V.hom(matrix(ZZ,3,[1..9])) + sage: V = ZZ^3; W = span([[1,2,3], [-1,2,8]], ZZ) + sage: phi = V.hom(matrix(ZZ, 3, [1..9])) sage: phi._repr_() 'Free module morphism defined by the matrix\n[1 2 3]\n[4 5 6]\n[7 8 9]\nDomain: Ambient free module of rank 3 over the principal ideal domain Integer Ring\nCodomain: Ambient free module of rank 3 over the principal ideal domain Integer Ring' @@ -242,8 +242,8 @@ def inverse_image(self, V): We test computing inverse images over a field:: - sage: V = QQ^3; W = span([[1,2,3],[-1,2,5/3]], QQ) - sage: phi = V.hom(matrix(QQ,3,[1..9])) + sage: V = QQ^3; W = span([[1,2,3], [-1,2,5/3]], QQ) + sage: phi = V.hom(matrix(QQ, 3, [1..9])) sage: phi.rank() 2 sage: I = phi.inverse_image(W); I @@ -297,7 +297,7 @@ def inverse_image(self, V): We test that :issue:`24590` is resolved:: - sage: A = FreeQuadraticModule(ZZ,1,matrix([2])) + sage: A = FreeQuadraticModule(ZZ, 1, matrix([2])) sage: f = A.Hom(A).an_element() sage: f.inverse_image(A) Free module of degree 1 and rank 1 over Integer Ring @@ -443,7 +443,7 @@ def lift(self, x): sage: V = QQ^2; m = matrix(2, [1, 1, 0, 1]) sage: V.hom(m, side="right").lift(V.0 + V.1) (0, 1) - sage: V.hom(m).lift(V.0+V.1) + sage: V.hom(m).lift(V.0 + V.1) (1, 0) """ from .free_module_element import vector diff --git a/src/sage/modules/free_quadratic_module.py b/src/sage/modules/free_quadratic_module.py index b1a1b2a2448..78b3b179b33 100644 --- a/src/sage/modules/free_quadratic_module.py +++ b/src/sage/modules/free_quadratic_module.py @@ -18,8 +18,8 @@ EXAMPLES:: - sage: M = Matrix(QQ,[[2,1,0],[1,2,1],[0,1,2]]) - sage: V = VectorSpace(QQ,3,inner_product_matrix=M) + sage: M = Matrix(QQ, [[2,1,0], [1,2,1], [0,1,2]]) + sage: V = VectorSpace(QQ, 3, inner_product_matrix=M) sage: type(V) sage: V.inner_product_matrix() @@ -44,11 +44,11 @@ TESTS:: - sage: M = Matrix(QQ,[[2,1,0],[1,2,1],[0,1,2]]) - sage: V = VectorSpace(QQ,3,inner_product_matrix = M) + sage: M = Matrix(QQ, [[2,1,0], [1,2,1], [0,1,2]]) + sage: V = VectorSpace(QQ, 3, inner_product_matrix=M) sage: V == loads(dumps(V)) True - sage: W = QuadraticSpace(QQ,3,M) + sage: W = QuadraticSpace(QQ, 3, M) sage: W == V True @@ -111,13 +111,13 @@ def FreeQuadraticModule(base_ring, rank, inner_product_matrix, EXAMPLES:: - sage: M2 = FreeQuadraticModule(ZZ,2,inner_product_matrix=[1,2,3,4]) - sage: M2 is FreeQuadraticModule(ZZ,2,inner_product_matrix=[1,2,3,4]) + sage: M2 = FreeQuadraticModule(ZZ, 2, inner_product_matrix=[1,2,3,4]) + sage: M2 is FreeQuadraticModule(ZZ, 2, inner_product_matrix=[1,2,3,4]) True sage: M2.inner_product_matrix() [1 2] [3 4] - sage: M3 = FreeModule(ZZ,2,inner_product_matrix=[[1,2],[3,4]]) + sage: M3 = FreeModule(ZZ, 2, inner_product_matrix=[[1,2],[3,4]]) sage: M3 is M2 True @@ -193,10 +193,11 @@ def QuadraticSpace(K, dimension, inner_product_matrix, sparse=False): The base can be complicated, as long as it is a field:: sage: F. = FractionField(PolynomialRing(ZZ,'x')) - sage: D = diagonal_matrix([x,x-1,x+1]) - sage: V = QuadraticSpace(F,3,D) + sage: D = diagonal_matrix([x, x - 1, x + 1]) + sage: V = QuadraticSpace(F, 3, D) sage: V - Ambient quadratic space of dimension 3 over Fraction Field of Univariate Polynomial Ring in x over Integer Ring + Ambient quadratic space of dimension 3 over + Fraction Field of Univariate Polynomial Ring in x over Integer Ring Inner product matrix: [ x 0 0] [ 0 x - 1 0] @@ -210,7 +211,7 @@ def QuadraticSpace(K, dimension, inner_product_matrix, sparse=False): The base must be a field or a :class:`TypeError` is raised:: - sage: QuadraticSpace(ZZ,5,identity_matrix(ZZ,2)) + sage: QuadraticSpace(ZZ, 5, identity_matrix(ZZ,2)) Traceback (most recent call last): ... TypeError: argument K (= Integer Ring) must be a field @@ -1362,7 +1363,7 @@ def change_ring(self, R): Basis matrix: [1 2 4] - sage: N = FreeModule(ZZ, 2, inner_product_matrix=[[1,-1],[2,5]]) + sage: N = FreeModule(ZZ, 2, inner_product_matrix=[[1,-1], [2,5]]) sage: N.inner_product_matrix() [ 1 -1] [ 2 5] @@ -1392,7 +1393,7 @@ class FreeQuadraticModule_submodule_pid(free_module.FreeModule_submodule_pid, EXAMPLES:: sage: M = ZZ^3 - sage: W = M.span_of_basis([[1,2,3],[4,5,19]]); W + sage: W = M.span_of_basis([[1,2,3], [4,5,19]]); W Free module of degree 3 and rank 2 over Integer Ring User basis matrix: [ 1 2 3] @@ -1510,14 +1511,14 @@ def __init__(self, ambient, basis, inner_product_matrix, EXAMPLES:: sage: V = QQ^3 - sage: W = V.span_of_basis([[1,2,3],[4,5,6]]) + sage: W = V.span_of_basis([[1,2,3], [4,5,6]]) sage: W Vector space of degree 3 and dimension 2 over Rational Field User basis matrix: [1 2 3] [4 5 6] sage: V = VectorSpace(QQ, 3, inner_product_matrix=1) - sage: V.span_of_basis([[1,2,3],[4,5,6]]) + sage: V.span_of_basis([[1,2,3], [4,5,6]]) Quadratic space of degree 3 and dimension 2 over Rational Field Basis matrix: [1 2 3] @@ -1604,7 +1605,7 @@ class FreeQuadraticModule_submodule_field(free_module.FreeModule_submodule_field coordinates:: sage: V = QQ^3 - sage: W = V.span([[1,2,3],[4,5,6]]) + sage: W = V.span([[1,2,3], [4,5,6]]) sage: W Vector space of degree 3 and dimension 2 over Rational Field Basis matrix: @@ -1630,7 +1631,7 @@ def __init__(self, ambient, gens, inner_product_matrix, check=True, already_eche EXAMPLES:: sage: V = QQ^3 - sage: W = V.span([[1,2,3],[4,5,6]]) + sage: W = V.span([[1,2,3], [4,5,6]]) sage: W Vector space of degree 3 and dimension 2 over Rational Field Basis matrix: @@ -1647,7 +1648,7 @@ def _repr_(self): EXAMPLES:: - sage: V = VectorSpace(QQ,5) + sage: V = VectorSpace(QQ, 5) sage: U = V.submodule([ V.gen(i) - V.gen(0) for i in range(1,5) ]) sage: U # indirect doctest Vector space of degree 5 and dimension 4 over Rational Field @@ -1680,7 +1681,7 @@ def _repr_(self): Sparse vector spaces print this fact:: - sage: V = VectorSpace(QQ,5,sparse=True) + sage: V = VectorSpace(QQ, 5, sparse=True) sage: U = V.submodule([ V.gen(i) - V.gen(0) for i in range(1,5) ]) sage: U # indirect doctest Sparse vector space of degree 5 and dimension 4 over Rational Field diff --git a/src/sage/modules/free_quadratic_module_integer_symmetric.py b/src/sage/modules/free_quadratic_module_integer_symmetric.py index cac77eda355..720fb6245c6 100644 --- a/src/sage/modules/free_quadratic_module_integer_symmetric.py +++ b/src/sage/modules/free_quadratic_module_integer_symmetric.py @@ -631,7 +631,7 @@ class FreeQuadraticModule_integer_symmetric(FreeQuadraticModule_submodule_with_b EXAMPLES:: - sage: IntegralLattice("U",basis=[vector([1,1])]) + sage: IntegralLattice("U", basis=[vector([1,1])]) Lattice of degree 2 and rank 1 over Integer Ring Basis matrix: [1 1] diff --git a/src/sage/modules/matrix_morphism.py b/src/sage/modules/matrix_morphism.py index 4624465f8dc..a195000ab31 100644 --- a/src/sage/modules/matrix_morphism.py +++ b/src/sage/modules/matrix_morphism.py @@ -882,7 +882,7 @@ def trace(self): EXAMPLES:: - sage: V = ZZ^2; phi = V.hom([V.0+V.1, 2*V.1]) + sage: V = ZZ^2; phi = V.hom([V.0 + V.1, 2*V.1]) sage: phi.trace() 3 """ @@ -894,7 +894,7 @@ def det(self): EXAMPLES:: - sage: V = ZZ^2; phi = V.hom([V.0+V.1, 2*V.1]) + sage: V = ZZ^2; phi = V.hom([V.0 + V.1, 2*V.1]) sage: phi.det() 2 """ @@ -922,14 +922,14 @@ def kernel(self): EXAMPLES:: - sage: V = VectorSpace(QQ,3) + sage: V = VectorSpace(QQ, 3) sage: id = V.Hom(V)(identity_matrix(QQ,3)) sage: null = V.Hom(V)(0*identity_matrix(QQ,3)) sage: id.kernel() Vector space of degree 3 and dimension 0 over Rational Field Basis matrix: [] - sage: phi = V.Hom(V)(matrix(QQ,3,range(9))) + sage: phi = V.Hom(V)(matrix(QQ, 3, range(9))) sage: phi.kernel() Vector space of degree 3 and dimension 1 over Rational Field Basis matrix: @@ -972,7 +972,7 @@ def image(self): EXAMPLES:: - sage: V = VectorSpace(QQ,3) + sage: V = VectorSpace(QQ, 3) sage: phi = V.Hom(V)(matrix(QQ, 3, range(9))) sage: phi.image() Vector space of degree 3 and dimension 2 over Rational Field @@ -1003,7 +1003,7 @@ def image(self): Compute the image of the identity map on a ZZ-submodule:: - sage: V = (ZZ^2).span([[1,2],[3,4]]) + sage: V = (ZZ^2).span([[1,2], [3,4]]) sage: phi = V.Hom(V)(identity_matrix(ZZ,2)) sage: phi(V.0) == V.0 True @@ -1524,8 +1524,8 @@ def restrict(self, sub): Codomain: Free module of degree 2 and rank 1 over Integer Ring Echelon ... - sage: V = (QQ^2).span_of_basis([[1,2],[3,4]]) - sage: phi = V.hom([V.0+V.1, 2*V.1]) + sage: V = (QQ^2).span_of_basis([[1,2], [3,4]]) + sage: phi = V.hom([V.0 + V.1, 2*V.1]) sage: phi(V.1) == 2*V.1 True sage: W = span([V.1]) @@ -1536,12 +1536,12 @@ def restrict(self, sub): sage: psi = phi.restrict(W); psi Vector space morphism represented by the matrix: [2] - Domain: Vector space of degree 2 and dimension 1 over Rational Field - Basis matrix: - [ 1 4/3] + Domain: Vector space of degree 2 and dimension 1 over Rational Field + Basis matrix: + [ 1 4/3] Codomain: Vector space of degree 2 and dimension 1 over Rational Field - Basis matrix: - [ 1 4/3] + Basis matrix: + [ 1 4/3] sage: psi.domain() == W True sage: psi(W.0) == 2*W.0 @@ -1557,26 +1557,26 @@ def restrict(self, sub): Free module morphism defined by the matrix [ 1 1] [-1 1] - Domain: Free module of degree 3 and rank 2 over Integer Ring - Echelon basis matrix: - [0 2 0] - [0 0 2] + Domain: Free module of degree 3 and rank 2 over Integer Ring + Echelon basis matrix: + [0 2 0] + [0 0 2] Codomain: Free module of degree 3 and rank 2 over Integer Ring - Echelon basis matrix: - [0 2 0] - [0 0 2] + Echelon basis matrix: + [0 2 0] + [0 0 2] sage: h2.restrict(SV) Free module morphism defined as left-multiplication by the matrix [ 1 -1] [ 1 1] - Domain: Free module of degree 3 and rank 2 over Integer Ring - Echelon basis matrix: - [0 2 0] - [0 0 2] + Domain: Free module of degree 3 and rank 2 over Integer Ring + Echelon basis matrix: + [0 2 0] + [0 0 2] Codomain: Free module of degree 3 and rank 2 over Integer Ring - Echelon basis matrix: - [0 2 0] - [0 0 2] + Echelon basis matrix: + [0 2 0] + [0 0 2] """ if not self.is_endomorphism(): raise ArithmeticError("matrix morphism must be an endomorphism") @@ -1621,7 +1621,7 @@ def __init__(self, parent, A, copy_matrix=True, side='left'): sage: from sage.modules.matrix_morphism import MatrixMorphism sage: T = End(ZZ^3) - sage: M = MatrixSpace(ZZ,3) + sage: M = MatrixSpace(ZZ, 3) sage: I = M.identity_matrix() sage: A = MatrixMorphism(T, I) sage: loads(A.dumps()) == A @@ -1710,10 +1710,10 @@ def is_injective(self): sage: V1 = QQ^2 sage: V2 = QQ^3 - sage: phi = V1.hom(Matrix([[1,2,3],[4,5,6]]),V2) + sage: phi = V1.hom(Matrix([[1,2,3], [4,5,6]]),V2) sage: phi.is_injective() True - sage: psi = V2.hom(Matrix([[1,2],[3,4],[5,6]]),V1) + sage: psi = V2.hom(Matrix([[1,2], [3,4], [5,6]]),V1) sage: psi.is_injective() False @@ -1735,10 +1735,10 @@ def is_surjective(self): sage: V1 = QQ^2 sage: V2 = QQ^3 - sage: phi = V1.hom(Matrix([[1,2,3],[4,5,6]]), V2) + sage: phi = V1.hom(Matrix([[1,2,3], [4,5,6]]), V2) sage: phi.is_surjective() False - sage: psi = V2.hom(Matrix([[1,2],[3,4],[5,6]]), V1) + sage: psi = V2.hom(Matrix([[1,2], [3,4], [5,6]]), V1) sage: psi.is_surjective() True @@ -1747,7 +1747,7 @@ def is_surjective(self): sage: R. = PolynomialRing(QQ) sage: A = R^2 sage: B = R^2 - sage: H = A.hom([B([x^2-1, 1]), B([x^2, 1])]) + sage: H = A.hom([B([x^2 - 1, 1]), B([x^2, 1])]) sage: H.image() Free module of degree 2 and rank 2 over Univariate Polynomial Ring in x over Rational Field Echelon basis matrix: @@ -1759,7 +1759,7 @@ def is_surjective(self): This tests if :issue:`11552` is fixed. :: sage: V = ZZ^2 - sage: m = matrix(ZZ, [[1,2],[0,2]]) + sage: m = matrix(ZZ, [[1,2], [0,2]]) sage: phi = V.hom(m, V) sage: phi.lift(vector(ZZ, [0, 1])) Traceback (most recent call last): diff --git a/src/sage/modules/quotient_module.py b/src/sage/modules/quotient_module.py index 3cb75a7de96..edb09c35e7e 100644 --- a/src/sage/modules/quotient_module.py +++ b/src/sage/modules/quotient_module.py @@ -48,10 +48,10 @@ class QuotientModule_free_ambient(Module_free_ambient): sage: N = M.submodule([vector([x - y, z]), vector([y*z, x*z])]) sage: M.quotient_module(N) Quotient module by Submodule of Ambient free module of rank 2 over - the integral domain Multivariate Polynomial Ring in x, y, z over Rational Field - Generated by the rows of the matrix: - [x - y z] - [ y*z x*z] + the integral domain Multivariate Polynomial Ring in x, y, z over Rational Field + Generated by the rows of the matrix: + [x - y z] + [ y*z x*z] """ def __init__(self, module, sub): """ @@ -97,10 +97,10 @@ def _repr_(self): sage: N = M.submodule([vector([x - y, z]), vector([y*z , x*z])]) sage: M.quotient_module(N) Quotient module by Submodule of Ambient free module of rank 2 over - the integral domain Multivariate Polynomial Ring in x, y, z over Rational Field - Generated by the rows of the matrix: - [x - y z] - [ y*z x*z] + the integral domain Multivariate Polynomial Ring in x, y, z over Rational Field + Generated by the rows of the matrix: + [x - y z] + [ y*z x*z] """ return "Quotient module by %s" % self._sub @@ -419,7 +419,7 @@ def __init__(self, domain, sub, quotient_matrix, lift_matrix, inner_product_matr sage: Q(V.0) (1) - sage: Q( V.0 - 2/3*V.1 ) + sage: Q(V.0 - 2/3*V.1) (0) sage: v = Q.lift(Q.0); v (1, 0, -1, 1, 1) diff --git a/src/sage/modules/torsion_quadratic_module.py b/src/sage/modules/torsion_quadratic_module.py index f7be88a6000..da24d2da474 100644 --- a/src/sage/modules/torsion_quadratic_module.py +++ b/src/sage/modules/torsion_quadratic_module.py @@ -164,7 +164,7 @@ def quadratic_product(self): sage: from sage.modules.torsion_quadratic_module import TorsionQuadraticModule sage: W = FreeQuadraticModule(ZZ, 2, 2*matrix.identity(2)) sage: V = (1/2) * W - sage: T = TorsionQuadraticModule(V,W) + sage: T = TorsionQuadraticModule(V, W) sage: x = T.gen(0) sage: x (1, 0) diff --git a/src/sage/modules/vector_integer_dense.pyx b/src/sage/modules/vector_integer_dense.pyx index 52e0bbd4064..cc2b55c3480 100644 --- a/src/sage/modules/vector_integer_dense.pyx +++ b/src/sage/modules/vector_integer_dense.pyx @@ -7,7 +7,7 @@ AUTHOR: EXAMPLES:: - sage: v = vector(ZZ,[1,2,3,4,5]) + sage: v = vector(ZZ, [1,2,3,4,5]) sage: v (1, 2, 3, 4, 5) sage: 3*v diff --git a/src/sage/modules/vector_modn_dense.pyx b/src/sage/modules/vector_modn_dense.pyx index c2155a68990..f0646e6b8e5 100644 --- a/src/sage/modules/vector_modn_dense.pyx +++ b/src/sage/modules/vector_modn_dense.pyx @@ -3,7 +3,7 @@ Vectors with integer mod `n` entries, with small `n` EXAMPLES:: - sage: v = vector(Integers(8),[1,2,3,4,5]) + sage: v = vector(Integers(8), [1,2,3,4,5]) sage: type(v) sage: v @@ -21,8 +21,8 @@ EXAMPLES:: sage: v * v 7 - sage: v = vector(Integers(8),[1,2,3,4,5]) - sage: u = vector(Integers(8),[1,2,3,4,4]) + sage: v = vector(Integers(8), [1,2,3,4,5]) + sage: u = vector(Integers(8), [1,2,3,4,4]) sage: v - u (0, 0, 0, 0, 1) sage: u - v diff --git a/src/sage/modules/with_basis/indexed_element.pyx b/src/sage/modules/with_basis/indexed_element.pyx index 5e875ef3929..a9845cc4126 100644 --- a/src/sage/modules/with_basis/indexed_element.pyx +++ b/src/sage/modules/with_basis/indexed_element.pyx @@ -508,7 +508,7 @@ cdef class IndexedFreeModuleElement(ModuleElement): sage: QS3 = SymmetricGroupAlgebra(QQ, 3) # needs sage.combinat sage: a = 2 + QS3([2,1,3]) # needs sage.combinat - sage: latex(a) #indirect doctest # needs sage.combinat + sage: latex(a) #indirect doctest # needs sage.combinat 2 [1, 2, 3] + [2, 1, 3] :: @@ -687,7 +687,7 @@ cdef class IndexedFreeModuleElement(ModuleElement): :: sage: s = SymmetricFunctions(QQ).schur() # needs sage.combinat - sage: -s([2,1]) # indirect doctest # needs sage.combinat + sage: -s([2,1]) # indirect doctest # needs sage.combinat -s[2, 1] """ return type(self)(self._parent, negate(self._monomial_coefficients)) @@ -704,7 +704,7 @@ cdef class IndexedFreeModuleElement(ModuleElement): :: sage: s = SymmetricFunctions(QQ).schur() # needs sage.combinat - sage: s([2,1]) - s([5,4]) # indirect doctest # needs sage.combinat + sage: s([2,1]) - s([5,4]) # indirect doctest # needs sage.combinat s[2, 1] - s[5, 4] """ return type(self)(self._parent, From 3aca56ddaa50221a74b78e3edaec568461214f91 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 13 Mar 2024 21:56:51 -0700 Subject: [PATCH 246/518] src/sage/schemes: Doctest cosmetics --- src/sage/schemes/elliptic_curves/Qcurves.py | 10 +-- .../elliptic_curves/ell_curve_isogeny.py | 3 +- .../schemes/elliptic_curves/ell_generic.py | 10 +-- .../schemes/elliptic_curves/ell_local_data.py | 16 ++-- src/sage/schemes/elliptic_curves/ell_point.py | 8 +- .../elliptic_curves/gal_reps_number_field.py | 35 +++++--- src/sage/schemes/elliptic_curves/kraus.py | 84 +++++++++++-------- .../schemes/elliptic_curves/period_lattice.py | 38 +++++---- .../elliptic_curves/period_lattice_region.pyx | 4 +- src/sage/schemes/generic/algebraic_scheme.py | 10 +-- src/sage/schemes/generic/ambient_space.py | 2 +- .../schemes/hyperelliptic_curves/mestre.py | 2 +- .../schemes/projective/proj_bdd_height.py | 2 +- .../schemes/projective/projective_morphism.py | 4 +- .../schemes/projective/projective_point.py | 2 +- .../riemann_surfaces/riemann_surface.py | 28 +++---- 16 files changed, 141 insertions(+), 117 deletions(-) diff --git a/src/sage/schemes/elliptic_curves/Qcurves.py b/src/sage/schemes/elliptic_curves/Qcurves.py index c7d3da73691..e0f55597f90 100644 --- a/src/sage/schemes/elliptic_curves/Qcurves.py +++ b/src/sage/schemes/elliptic_curves/Qcurves.py @@ -195,11 +195,11 @@ def is_Q_curve(E, maxp=100, certificate=False, verbose=False): True sage: cert {'CM': 0, - 'N': 2, - 'core_degs': [1, 2], - 'core_poly': x^2 - 840064*x + 1593413632, - 'r': 1, - 'rho': 1} + 'N': 2, + 'core_degs': [1, 2], + 'core_poly': x^2 - 840064*x + 1593413632, + 'r': 1, + 'rho': 1} TESTS:: diff --git a/src/sage/schemes/elliptic_curves/ell_curve_isogeny.py b/src/sage/schemes/elliptic_curves/ell_curve_isogeny.py index 4567a0f0923..3dc8ea0f11e 100644 --- a/src/sage/schemes/elliptic_curves/ell_curve_isogeny.py +++ b/src/sage/schemes/elliptic_curves/ell_curve_isogeny.py @@ -1229,7 +1229,8 @@ def _call_(self, P): Traceback (most recent call last): ... TypeError: (20 : 90 : 1) fails to convert into the map's domain - Elliptic Curve defined by y^2 = x^3 + 7*x over Number Field in th with defining polynomial x^2 + 3, + Elliptic Curve defined by y^2 = x^3 + 7*x over + Number Field in th with defining polynomial x^2 + 3, but a `pushforward` method is not properly implemented Check that copying the order over works:: diff --git a/src/sage/schemes/elliptic_curves/ell_generic.py b/src/sage/schemes/elliptic_curves/ell_generic.py index b887b7666b5..4719b914bde 100644 --- a/src/sage/schemes/elliptic_curves/ell_generic.py +++ b/src/sage/schemes/elliptic_curves/ell_generic.py @@ -546,11 +546,11 @@ def __call__(self, *args, **kwds): sage: T = E.torsion_subgroup() sage: [E(t) for t in T] [(0 : 1 : 0), - (9 : 23 : 1), - (2 : 2 : 1), - (1 : -1 : 1), - (2 : -5 : 1), - (9 : -33 : 1)] + (9 : 23 : 1), + (2 : 2 : 1), + (1 : -1 : 1), + (2 : -5 : 1), + (9 : -33 : 1)] :: diff --git a/src/sage/schemes/elliptic_curves/ell_local_data.py b/src/sage/schemes/elliptic_curves/ell_local_data.py index 5e897054c28..8192b8ce84c 100644 --- a/src/sage/schemes/elliptic_curves/ell_local_data.py +++ b/src/sage/schemes/elliptic_curves/ell_local_data.py @@ -742,7 +742,7 @@ def _tate(self, proof=None, globally=False): sage: # needs sage.rings.number_field sage: K. = NumberField(x^7 - 2*x + 177) sage: E = EllipticCurve([0,1,0,t,t]) - sage: P = K.ideal(2,t^3 + t + 1) + sage: P = K.ideal(2, t^3 + t + 1) sage: E.local_data(P).kodaira_symbol() II """ @@ -1142,17 +1142,17 @@ def check_prime(K, P): .. NOTE:: - If `P` is not a prime and does not generate a prime, a ``TypeError`` + If `P` is not a prime and does not generate a prime, a :class:`TypeError` is raised. EXAMPLES:: sage: from sage.schemes.elliptic_curves.ell_local_data import check_prime - sage: check_prime(QQ,3) + sage: check_prime(QQ, 3) 3 - sage: check_prime(QQ,QQ(3)) + sage: check_prime(QQ, QQ(3)) 3 - sage: check_prime(QQ,ZZ.ideal(31)) + sage: check_prime(QQ, ZZ.ideal(31)) 31 sage: # needs sage.rings.number_field @@ -1160,11 +1160,11 @@ def check_prime(K, P): sage: K. = NumberField(x^2 - 5) sage: check_prime(K, a) Fractional ideal (a) - sage: check_prime(K,a+1) + sage: check_prime(K, a + 1) Fractional ideal (a + 1) - sage: [check_prime(K,P) for P in K.primes_above(31)] + sage: [check_prime(K, P) for P in K.primes_above(31)] [Fractional ideal (5/2*a + 1/2), Fractional ideal (5/2*a - 1/2)] - sage: L. = NumberField(x^2+3) + sage: L. = NumberField(x^2 + 3) sage: check_prime(K, L.ideal(5)) Traceback (most recent call last): ... diff --git a/src/sage/schemes/elliptic_curves/ell_point.py b/src/sage/schemes/elliptic_curves/ell_point.py index 35e6644db3a..77b62f542aa 100644 --- a/src/sage/schemes/elliptic_curves/ell_point.py +++ b/src/sage/schemes/elliptic_curves/ell_point.py @@ -3265,7 +3265,7 @@ def archimedean_local_height(self, v=None, prec=None, weighted=False): def non_archimedean_local_height(self, v=None, prec=None, weighted=False, is_minimal=None): """ - Compute the local height of self at the non-archimedean place `v`. + Compute the local height of ``self`` at the non-archimedean place `v`. INPUT: @@ -3277,10 +3277,10 @@ def non_archimedean_local_height(self, v=None, prec=None, of self at `v`. If `v` is None, return the total non-archimedean contribution to the global height. - - ``prec`` -- integer, or None (default). The precision of the - computation. If None, the height is returned symbolically. + - ``prec`` -- integer, or ``None`` (default). The precision of the + computation. If ``None``, the height is returned symbolically. - - ``weighted`` -- boolean. If False (default), the height is + - ``weighted`` -- boolean. If ``False`` (default), the height is normalised to be invariant under extension of `K`. If True, return this normalised height multiplied by the local degree if `v` is a single place, or by the degree of `K` if `v` is diff --git a/src/sage/schemes/elliptic_curves/gal_reps_number_field.py b/src/sage/schemes/elliptic_curves/gal_reps_number_field.py index 397ff4dd4e6..ea9d3bdc2ed 100644 --- a/src/sage/schemes/elliptic_curves/gal_reps_number_field.py +++ b/src/sage/schemes/elliptic_curves/gal_reps_number_field.py @@ -80,7 +80,9 @@ class GaloisRepresentation(SageObject): sage: E = EllipticCurve('11a1').change_ring(K) sage: rho = E.galois_representation() sage: rho - Compatible family of Galois representations associated to the Elliptic Curve defined by y^2 + y = x^3 + (-1)*x^2 + (-10)*x + (-20) over Number Field in a with defining polynomial x^2 + 1 + Compatible family of Galois representations associated to the Elliptic Curve + defined by y^2 + y = x^3 + (-1)*x^2 + (-10)*x + (-20) + over Number Field in a with defining polynomial x^2 + 1 """ def __init__(self, E): @@ -94,7 +96,9 @@ def __init__(self, E): sage: E = EllipticCurve('11a1').change_ring(K) sage: rho = E.galois_representation() sage: rho - Compatible family of Galois representations associated to the Elliptic Curve defined by y^2 + y = x^3 + (-1)*x^2 + (-10)*x + (-20) over Number Field in a with defining polynomial x^2 + 1 + Compatible family of Galois representations associated to the Elliptic Curve + defined by y^2 + y = x^3 + (-1)*x^2 + (-10)*x + (-20) + over Number Field in a with defining polynomial x^2 + 1 sage: loads(rho.dumps()) == rho True """ @@ -111,12 +115,16 @@ def __repr__(self): sage: E = EllipticCurve('11a1').change_ring(K) sage: rho = E.galois_representation() sage: rho - Compatible family of Galois representations associated to the Elliptic Curve defined by y^2 + y = x^3 + (-1)*x^2 + (-10)*x + (-20) over Number Field in a with defining polynomial x^2 + 1 + Compatible family of Galois representations associated to the Elliptic Curve + defined by y^2 + y = x^3 + (-1)*x^2 + (-10)*x + (-20) + over Number Field in a with defining polynomial x^2 + 1 sage: K. = NumberField(x^2-x+1) sage: E = EllipticCurve([0,0,0,a,0]) sage: E.galois_representation() - Compatible family of Galois representations associated to the CM Elliptic Curve defined by y^2 = x^3 + a*x over Number Field in a with defining polynomial x^2 - x + 1 + Compatible family of Galois representations associated to the + CM Elliptic Curve defined by y^2 = x^3 + a*x + over Number Field in a with defining polynomial x^2 - x + 1 """ if self.E.has_cm(): return "Compatible family of Galois representations associated to the CM " + repr(self.E) @@ -225,10 +233,10 @@ def is_surjective(self, p, A=100): INPUT: - * ``p`` - int - a prime number. + * ``p`` -- a prime number. - * ``A`` - int - a bound on the number of traces of Frobenius to use - while trying to prove surjectivity. + * ``A`` -- (integer) a bound on the number of traces of Frobenius to use + while trying to prove surjectivity. EXAMPLES:: @@ -258,7 +266,7 @@ def is_surjective(self, p, A=100): For CM curves, the mod-p representation is never surjective:: - sage: K. = NumberField(x^2-x+1) + sage: K. = NumberField(x^2 - x + 1) sage: E = EllipticCurve([0,0,0,0,a]) sage: E.has_cm() True @@ -320,7 +328,7 @@ def isogeny_bound(self, A=100): `p` for which the mod-`p` representation is reducible, and [0] is returned:: - sage: K. = NumberField(x^2-x+1) + sage: K. = NumberField(x^2 - x + 1) sage: E = EllipticCurve([0,0,0,0,a]) sage: E.has_rational_cm() True @@ -403,7 +411,7 @@ def reducible_primes(self): `p` for which the mod-`p` representation is reducible, and [0] is returned:: - sage: K. = NumberField(x^2-x+1) + sage: K. = NumberField(x^2 - x + 1) sage: E = EllipticCurve([0,0,0,0,a]) sage: E.has_rational_cm() True @@ -426,7 +434,7 @@ def _non_surjective(E, patience=100): - ``E`` -- EllipticCurve (over a number field). - ``A`` -- int (a bound on the number of traces of Frobenius to use - while trying to prove surjectivity). + while trying to prove surjectivity). OUTPUT: @@ -848,7 +856,8 @@ def _semistable_reducible_primes(E, verbose=False): sage: x = polygen(ZZ, 'x') sage: K. = NumberField(x^5 - 6*x^3 + 8*x - 1) - sage: E = EllipticCurve(K, [a^3 - 2*a, a^4 - 2*a^3 - 4*a^2 + 6*a + 1, a + 1, -a^3 + a + 1, -a]) + sage: E = EllipticCurve(K, [a^3 - 2*a, a^4 - 2*a^3 - 4*a^2 + 6*a + 1, + ....: a + 1, -a^3 + a + 1, -a]) sage: from sage.schemes.elliptic_curves.gal_reps_number_field import _semistable_reducible_primes sage: _semistable_reducible_primes(E) [2, 5, 53, 1117] @@ -1406,7 +1415,7 @@ def Billerey_R_bound(E, max_l=200, num_l=8, small_prime_bound=None, debug=False) .. NOTE:: - The purpose of the small_prime_bound is that it is faster to + The purpose of the ``small_prime_bound`` is that it is faster to deal with these using the local test; by ignoring them here, we enable the algorithm to terminate sooner when there are no large reducible primes, which is always the case in practice. diff --git a/src/sage/schemes/elliptic_curves/kraus.py b/src/sage/schemes/elliptic_curves/kraus.py index 334d28f0461..19895200687 100644 --- a/src/sage/schemes/elliptic_curves/kraus.py +++ b/src/sage/schemes/elliptic_curves/kraus.py @@ -90,7 +90,7 @@ def c4c6_nonsingular(c4, c6): sage: K. = NumberField(x^2 - 10) sage: c4c6_nonsingular(-217728*a - 679104, 141460992*a + 409826304) True - sage: K. = NumberField(x^3-10) + sage: K. = NumberField(x^3 - 10) sage: c4c6_nonsingular(-217728*a - 679104, 141460992*a + 409826304) True """ @@ -108,15 +108,15 @@ def c4c6_model(c4, c6, assume_nonsingular=False): - ``c4``, ``c6`` -- elements of a number field - - ``assume_nonsingular`` (boolean, default False) -- if True, + - ``assume_nonsingular`` (boolean, default ``False``) -- if ``True``, check for integrality and nosingularity. OUTPUT: The elliptic curve with a-invariants [0,0,0,-c4/48,-c6/864], whose c-invariants are the given c4, c6. If the supplied invariants are - singular, returns None when ``assume_nonsingular`` is False and - raises an ArithmeticError otherwise. + singular, returns ``None`` when ``assume_nonsingular`` is ``False`` and + raises an :class:`ArithmeticError` otherwise. EXAMPLES:: @@ -174,12 +174,11 @@ def make_integral(a, P, e): sage: e = P.ramification_index(); e 2 sage: x = 1/5 - sage: b = make_integral(x,P,e) - sage: b + sage: b = make_integral(x, P, e); b 1 sage: (b-x).valuation(P) >= e True - sage: make_integral(1/a,P,e) + sage: make_integral(1/a, P, e) Traceback (most recent call last): ... ArithmeticError: Cannot lift 1/10*a to O_K mod (Fractional ideal (2, a))^2 @@ -213,11 +212,11 @@ def sqrt_mod_4(x, P): sage: x = polygen(ZZ, 'x') sage: K. = NumberField(x^2 - 10) sage: P = K.primes_above(2)[0] - sage: sqrt_mod_4(1+2*a,P) + sage: sqrt_mod_4(1 + 2*a, P) (False, 0) - sage: sqrt_mod_4(-1+2*a,P) + sage: sqrt_mod_4(-1 + 2*a, P) (True, a + 1) - sage: (1+a)^2 - (-1+2*a) + sage: (1+a)^2 - (-1 + 2*a) 12 sage: e = P.ramification_index() sage: ((1+a)^2 - (-1+2*a)).mod(P**e) @@ -332,7 +331,9 @@ def test_b2_global(c4, c6, b2, debug=False): sage: b2 = a+1 sage: from sage.schemes.elliptic_curves.kraus import test_b2_global sage: test_b2_global(c4,c6,b2) - Elliptic Curve defined by y^2 = x^3 + (1/4*a+1/4)*x^2 + (10091/8*a-128595/16)*x + (4097171/64*a-19392359/64) over Number Field in a with defining polynomial x^2 - 10 + Elliptic Curve defined by + y^2 = x^3 + (1/4*a+1/4)*x^2 + (10091/8*a-128595/16)*x + (4097171/64*a-19392359/64) + over Number Field in a with defining polynomial x^2 - 10 sage: test_b2_global(c4,c6,0,debug=True) test_b2_global: not integral at all primes dividing 3 False @@ -453,7 +454,9 @@ def test_a1a3_local(c4, c6, P, a1, a3, debug=False): sage: c6 = -55799680*a + 262126328 sage: P = K.primes_above(2)[0] sage: test_a1a3_local(c4,c6,P,a,0) - Elliptic Curve defined by y^2 + a*x*y = x^3 + (3784/3*a-24106/3)*x + (1772120/27*a-2790758/9) over Number Field in a with defining polynomial x^2 - 10 + Elliptic Curve defined by + y^2 + a*x*y = x^3 + (3784/3*a-24106/3)*x + (1772120/27*a-2790758/9) + over Number Field in a with defining polynomial x^2 - 10 sage: test_a1a3_local(c4,c6,P,a,a,debug=True) test_a1a3_local: not integral at Fractional ideal (2, a) False @@ -484,7 +487,7 @@ def test_a1a3_global(c4, c6, a1, a3, debug=False): The elliptic curve which is the (a1^2/12,a1/2,a3/2)-transform of [0,0,0,-c4/48,-c6/864] if this is integral at all primes P - dividing 2, else False. + dividing 2, else ``False``. EXAMPLES:: @@ -497,7 +500,9 @@ def test_a1a3_global(c4, c6, a1, a3, debug=False): sage: test_a1a3_global(c4,c6,a,a,debug=False) False sage: test_a1a3_global(c4,c6,a,0) - Elliptic Curve defined by y^2 + a*x*y = x^3 + (3784/3*a-24106/3)*x + (1772120/27*a-2790758/9) over Number Field in a with defining polynomial x^2 - 10 + Elliptic Curve defined by + y^2 + a*x*y = x^3 + (3784/3*a-24106/3)*x + (1772120/27*a-2790758/9) + over Number Field in a with defining polynomial x^2 - 10 """ E = c4c6_model(c4,c6).rst_transform(a1**2/12,a1/2,a3/2) if not (c4, c6) == E.c_invariants(): @@ -526,7 +531,7 @@ def test_rst_global(c4, c6, r, s, t, debug=False): The elliptic curve which is the (r,s,t)-transform of [0,0,0,-c4/48,-c6/864] if this is integral at all primes P, else - False. + ``False``. EXAMPLES:: @@ -537,7 +542,9 @@ def test_rst_global(c4, c6, r, s, t, debug=False): sage: c4 = -60544*a + 385796 sage: c6 = -55799680*a + 262126328 sage: test_rst_global(c4,c6,1/3*a - 133/6, 3/2*a, -89/2*a + 5) - Elliptic Curve defined by y^2 + 3*a*x*y + (-89*a+10)*y = x^3 + (a-89)*x^2 + (1202*a-5225)*x + (34881*a-151813) over Number Field in a with defining polynomial x^2 - 10 + Elliptic Curve defined by + y^2 + 3*a*x*y + (-89*a+10)*y = x^3 + (a-89)*x^2 + (1202*a-5225)*x + (34881*a-151813) + over Number Field in a with defining polynomial x^2 - 10 sage: test_rst_global(c4,c6,a, 3, -89*a, debug=False) False """ @@ -582,18 +589,18 @@ def check_Kraus_local_2(c4, c6, P, a1=None, assume_nonsingular=False): - ``P`` -- a prime ideal of the number field which divides 2 - - ``a1`` -- an integral elements of a number field, or None (default) + - ``a1`` -- an integral elements of a number field, or ``None`` (default) - - ``assume_nonsingular`` (boolean, default False) -- if True, - check for integrality and nosingularity. + - ``assume_nonsingular`` (boolean, default ``False``) -- if ``True``, + check for integrality and nonsingularity. OUTPUT: - Either (False, 0, 0) if Kraus's conditions fail, or (True, a1, - a3) if they pass, in which case the elliptic curve which is the + Either ``(False, 0, 0)`` if Kraus's conditions fail, or ``(True, a1, + a3)`` if they pass, in which case the elliptic curve which is the (a1**2/12,a1/2,a3/2)-transform of [0,0,0,-c4/48,-c6/864] is integral at P. If a1 is provided and valid then the output will - be (True, a1, a3) for suitable a3. + be ``(True, a1, a3)`` for suitable a3. EXAMPLES:: @@ -674,13 +681,13 @@ def check_Kraus_local(c4, c6, P, assume_nonsingular=False): - ``P`` -- a prime ideal of the number field - - ``assume_nonsingular`` (boolean, default False) -- if True, - check for integrality and nosingularity. + - ``assume_nonsingular`` (boolean, default ``False``) -- if ``True``, + check for integrality and nonsingularity. OUTPUT: - Tuple: either (True,E) if there is a Weierstrass model E integral - at P and with invariants c4, c6, or (False, None) if there is + Tuple: either ``(True, E)`` if there is a Weierstrass model E integral + at P and with invariants c4, c6, or ``(False, None)`` if there is none. EXAMPLES:: @@ -746,12 +753,12 @@ def check_Kraus_global(c4, c6, assume_nonsingular=False, debug=False): - ``c4``, ``c6`` -- elements of a number field - - ``assume_nonsingular`` (boolean, default False) -- if True, - check for integrality and nosingularity. + - ``assume_nonsingular`` (boolean, default ``False``) -- if ``True``, + check for integrality and nonsingularity. OUTPUT: - Either False if Kraus's conditions fail, or, if they pass, an + Either ``False`` if Kraus's conditions fail, or, if they pass, an elliptic curve E which is integral and has c-invariants c4,c6. EXAMPLES:: @@ -772,21 +779,24 @@ def check_Kraus_global(c4, c6, assume_nonsingular=False, debug=False): (a1, b2, a3) = (3*a, a + 1, 0) Using (r, s, t)=(1/3*a - 133/6, 3/2*a, -89/2*a + 5) should give a global integral model... ...and it does! - Elliptic Curve defined by y^2 + 3*a*x*y + (-89*a+10)*y = x^3 + (a-89)*x^2 + (1202*a-5225)*x + (34881*a-151813) over Number Field in a with defining polynomial x^2 - 10 + Elliptic Curve defined by + y^2 + 3*a*x*y + (-89*a+10)*y = x^3 + (a-89)*x^2 + (1202*a-5225)*x + (34881*a-151813) + over Number Field in a with defining polynomial x^2 - 10 sage: # needs sage.rings.number_field sage: K. = NumberField(x^2 - 15) sage: E = EllipticCurve([0, 0, 0, 4536*a + 14148, -163728*a - 474336]) sage: c4, c6 = E.c_invariants() sage: check_Kraus_global(c4,c6) - Elliptic Curve defined by y^2 = x^3 + (4536*a+14148)*x + (-163728*a-474336) over Number Field in a with defining polynomial x^2 - 15 + Elliptic Curve defined by y^2 = x^3 + (4536*a+14148)*x + (-163728*a-474336) + over Number Field in a with defining polynomial x^2 - 15 TESTS (see :issue:`17295`):: sage: # needs sage.rings.number_field sage: K. = NumberField(x^3 - 7*x - 5) sage: E = EllipticCurve([a, 0, 1, 2*a^2 + 5*a + 3, -a^2 - 3*a - 2]) - sage: assert E.conductor().norm() ==8 + sage: assert E.conductor().norm() == 8 sage: G = K.galois_group(names='b') sage: def conj_curve(E,sigma): return EllipticCurve([sigma(a) for a in E.ainvs()]) sage: EL = conj_curve(E,G[0]) @@ -921,14 +931,14 @@ def semi_global_minimal_model(E, debug=False): OUTPUT: - A tuple (Emin,I) where Emin is an elliptic curve which is either a + A tuple ``(Emin, I)`` where Emin is an elliptic curve which is either a global minimal model of E if one exists (i.e., an integral model - which is minimal at every prime), or a semin-global minimal model + which is minimal at every prime), or a semi-global minimal model (i.e., an integral model which is minimal at every prime except one). I is the unit ideal of Emin is a global minimal model, else is the unique prime at which Emin is not minimal. Thus in all cases, - Emin.minimal_discriminant_ideal() * I**12 == (E.discriminant()). + ``Emin.minimal_discriminant_ideal() * I**12 == (E.discriminant())``. .. NOTE:: @@ -948,7 +958,9 @@ def semi_global_minimal_model(E, debug=False): sage: from sage.schemes.elliptic_curves.kraus import semi_global_minimal_model sage: Emin, P = semi_global_minimal_model(E) sage: Emin - Elliptic Curve defined by y^2 + 3*x*y + (2*a-11)*y = x^3 + (a-10)*x^2 + (-152*a-415)*x + (1911*a+5920) over Number Field in a with defining polynomial x^2 - 10 + Elliptic Curve defined by + y^2 + 3*x*y + (2*a-11)*y = x^3 + (a-10)*x^2 + (-152*a-415)*x + (1911*a+5920) + over Number Field in a with defining polynomial x^2 - 10 sage: E.minimal_discriminant_ideal()*P**12 == K.ideal(Emin.discriminant()) True diff --git a/src/sage/schemes/elliptic_curves/period_lattice.py b/src/sage/schemes/elliptic_curves/period_lattice.py index 1d3cbb020c2..e5aa953898e 100644 --- a/src/sage/schemes/elliptic_curves/period_lattice.py +++ b/src/sage/schemes/elliptic_curves/period_lattice.py @@ -526,9 +526,9 @@ def normalised_basis(self, prec=None, algorithm='sage'): - ``prec`` (default: ``None``) -- precision in bits (default precision if ``None``). - - ``algorithm`` (string, default 'sage') -- choice of + - ``algorithm`` (string, default ``'sage'``) -- choice of implementation (for real embeddings only) between 'sage' - (native Sage implementation) or 'pari' (use the PARI + (native Sage implementation) or ``'pari'`` (use the PARI library: only available for real embeddings). OUTPUT: @@ -713,7 +713,7 @@ def _compute_periods_complex(self, prec=None, normalise=True): - `prec` (int or ``None`` (default)) -- floating point precision (in bits); if None, use the default precision. - - `normalise` (bool, default True) -- whether to normalise the + - `normalise` (bool, default ``True``) -- whether to normalise the basis after computation. OUTPUT: @@ -780,7 +780,7 @@ def _compute_periods_complex(self, prec=None, normalise=True): def is_real(self): r""" - Return True if this period lattice is real. + Return ``True`` if this period lattice is real. EXAMPLES:: @@ -857,9 +857,9 @@ def real_period(self, prec=None, algorithm='sage'): - ``prec`` (int or ``None`` (default)) -- real precision in bits (default real precision if ``None``) - - ``algorithm`` (string, default 'sage') -- choice of - implementation (for real embeddings only) between 'sage' - (native Sage implementation) or 'pari' (use the PARI + - ``algorithm`` (string, default ``'sage'``) -- choice of + implementation (for real embeddings only) between ``'sage'`` + (native Sage implementation) or ``'pari'`` (use the PARI library: only available for real embeddings). .. NOTE:: @@ -894,7 +894,7 @@ def omega(self, prec=None, bsd_normalise=False): INPUT: - - ``prec`` (int or ``None``(default)) -- real precision in + - ``prec`` (int or ``None`` (default)) -- real precision in bits (default real precision if ``None``) - ``bsd_normalise`` (bool, default ``False``) -- flag to use @@ -977,16 +977,16 @@ def basis_matrix(self, prec=None, normalised=False): INPUT: - - ``prec`` (int or ``None``(default)) -- real precision in + - ``prec`` (int or ``None`` (default)) -- real precision in bits (default real precision if ``None``). - - ``normalised`` (bool, default False) -- if True and the + - ``normalised`` (bool, default ``False``) -- if ``True`` and the embedding is real, use the normalised basis (see ``normalised_basis()``) instead of the default. OUTPUT: - A 2x2 real matrix whose rows are the lattice basis vectors, + A `2\times 2` real matrix whose rows are the lattice basis vectors, after identifying `\CC` with `\RR^2`. EXAMPLES:: @@ -1046,7 +1046,7 @@ def complex_area(self, prec=None): INPUT: - - ``prec`` (int or ``None``(default)) -- real precision in + - ``prec`` (int or ``None`` (default)) -- real precision in bits (default real precision if ``None``). EXAMPLES:: @@ -1079,7 +1079,7 @@ def sigma(self, z, prec=None, flag=0): - ``z`` -- a complex number - ``prec`` (default: ``None``) -- real precision in bits - (default real precision if None). + (default real precision if ``None``). - ``flag`` -- @@ -1329,6 +1329,8 @@ def e_log_RC(self, xP, yP, prec=None, reduce=True): r""" Return the elliptic logarithm of a real or complex point. + INPUT: + - ``xP, yP`` (real or complex) -- Coordinates of a point on the embedded elliptic curve associated with this period lattice. @@ -1537,7 +1539,7 @@ def elliptic_logarithm(self, P, prec=None, reduce=True): with this period lattice. - ``prec`` (default: ``None``) -- real precision in bits - (default real precision if None). + (default real precision if ``None``). - ``reduce`` (default: ``True``) -- if ``True``, the result is reduced with respect to the period lattice basis. @@ -1730,7 +1732,7 @@ def elliptic_exponential(self, z, to_curve=True): - ``z`` (complex) -- A complex number (viewed modulo this period lattice). - - ``to_curve`` (bool, default True): see below. + - ``to_curve`` (bool, default ``True``): see below. OUTPUT: @@ -1746,8 +1748,8 @@ def elliptic_exponential(self, z, to_curve=True): lattice this is. - If the lattice is real and `z` is also real then the output - is a pair of real numbers if ``to_curve`` is True, or a - point in `E(\RR)` if ``to_curve`` is False. + is a pair of real numbers if ``to_curve`` is ``True``, or a + point in `E(\RR)` if ``to_curve`` is ``False``. .. NOTE:: @@ -1862,7 +1864,7 @@ def elliptic_exponential(self, z, to_curve=True): (0.000000000000000 : 1.00000000000000 : 0.000000000000000) The elliptic exponential of `z` is returned as (0 : 1 : 0) if - the coordinates of z with respect to the period lattice are + the coordinates of `z` with respect to the period lattice are approximately integral:: sage: (100/log(2.0,10))/0.8 diff --git a/src/sage/schemes/elliptic_curves/period_lattice_region.pyx b/src/sage/schemes/elliptic_curves/period_lattice_region.pyx index 06e6525f5c3..ec1198b296e 100644 --- a/src/sage/schemes/elliptic_curves/period_lattice_region.pyx +++ b/src/sage/schemes/elliptic_curves/period_lattice_region.pyx @@ -607,7 +607,7 @@ cdef class PeriodicRegion: def innermost_point(self): """ - Returns a point well inside the region, specifically the center of + Return a point well inside the region, specifically the center of (one of) the last tile(s) to be removed on contraction. EXAMPLES:: @@ -635,7 +635,7 @@ cdef class PeriodicRegion: def plot(self, **kwds): """ - Plots this region in the fundamental lattice. If full is False plots + Plot this region in the fundamental lattice. If ``full`` is ``False``, plots only the lower half. Note that the true nature of this region is periodic. EXAMPLES:: diff --git a/src/sage/schemes/generic/algebraic_scheme.py b/src/sage/schemes/generic/algebraic_scheme.py index b7ee556f136..172256fa6a1 100644 --- a/src/sage/schemes/generic/algebraic_scheme.py +++ b/src/sage/schemes/generic/algebraic_scheme.py @@ -1638,7 +1638,7 @@ def __mul__(self, right): def intersection(self, other): """ - Return the scheme-theoretic intersection of self and other in their + Return the scheme-theoretic intersection of ``self`` and ``other`` in their common ambient space. EXAMPLES:: @@ -1660,12 +1660,12 @@ def intersection(self, other): def complement(self, other=None): """ - Return the scheme-theoretic complement other - self, where - self and other are both closed algebraic subschemes of the + Return the scheme-theoretic complement ``other - self``, where + ``self`` and ``other`` are both closed algebraic subschemes of the same ambient space. - If other is unspecified, it is taken to be the ambient space - of self. + If ``other`` is unspecified, it is taken to be the ambient space + of ``self``. EXAMPLES:: diff --git a/src/sage/schemes/generic/ambient_space.py b/src/sage/schemes/generic/ambient_space.py index 09d26860af0..52b3f1e0e39 100644 --- a/src/sage/schemes/generic/ambient_space.py +++ b/src/sage/schemes/generic/ambient_space.py @@ -158,7 +158,7 @@ def _validate(self, polynomials): def change_ring(self, R): r""" - Return an ambient space over ring `R` and otherwise the same as self. + Return an ambient space over ring `R` and otherwise the same as ``self``. INPUT: diff --git a/src/sage/schemes/hyperelliptic_curves/mestre.py b/src/sage/schemes/hyperelliptic_curves/mestre.py index f2de1a2eb36..b8cc8ca96be 100644 --- a/src/sage/schemes/hyperelliptic_curves/mestre.py +++ b/src/sage/schemes/hyperelliptic_curves/mestre.py @@ -229,7 +229,7 @@ def Mestre_conic(i, xyz=False, names='u,v,w'): - ``i`` - list or tuple of length 4 containing the four Igusa-Clebsch invariants: I2, I4, I6, I10 - - ``xyz`` - Boolean (default: False) if True, the algorithm also + - ``xyz`` - Boolean (default: ``False``) if ``True``, the algorithm also returns three invariants x,y,z used in Mestre's algorithm - ``names`` (default: 'u,v,w') - the variable names for the conic diff --git a/src/sage/schemes/projective/proj_bdd_height.py b/src/sage/schemes/projective/proj_bdd_height.py index 86100cf832a..0ea174ddc4d 100644 --- a/src/sage/schemes/projective/proj_bdd_height.py +++ b/src/sage/schemes/projective/proj_bdd_height.py @@ -242,7 +242,7 @@ def points_of_bounded_height(PS, K, dim, bound, prec=53): - ``K`` -- a number field - - ``dim`` -- a positive interger + - ``dim`` -- a positive integer - ``bound`` -- a real number diff --git a/src/sage/schemes/projective/projective_morphism.py b/src/sage/schemes/projective/projective_morphism.py index cefebbe5ff4..86693c6cc27 100644 --- a/src/sage/schemes/projective/projective_morphism.py +++ b/src/sage/schemes/projective/projective_morphism.py @@ -1580,9 +1580,9 @@ def rational_preimages(self, Q, k=1): INPUT: - - ``Q`` - a rational point or subscheme in the domain of this map. + - ``Q`` -- a rational point or subscheme in the domain of this map. - - ``k`` - positive integer. + - ``k`` -- positive integer. OUTPUT: diff --git a/src/sage/schemes/projective/projective_point.py b/src/sage/schemes/projective/projective_point.py index 7fb61b37e1a..4b07fce13a9 100644 --- a/src/sage/schemes/projective/projective_point.py +++ b/src/sage/schemes/projective/projective_point.py @@ -632,7 +632,7 @@ def normalize_coordinates(self): def dehomogenize(self,n): r""" - Dehomogenizes at the nth coordinate. + Dehomogenizes at the `n`-th coordinate. INPUT: diff --git a/src/sage/schemes/riemann_surfaces/riemann_surface.py b/src/sage/schemes/riemann_surfaces/riemann_surface.py index a7760fa7101..32d2d568693 100644 --- a/src/sage/schemes/riemann_surfaces/riemann_surface.py +++ b/src/sage/schemes/riemann_surfaces/riemann_surface.py @@ -2415,7 +2415,7 @@ def riemann_matrix(self): field:: sage: x = polygen(QQ) - sage: K. = NumberField(x^2-x+2) + sage: K. = NumberField(x^2 - x + 2) sage: all(len(m.algdep(6).roots(K)) > 0 for m in M.list()) True """ @@ -2950,10 +2950,10 @@ def _integrate_differentials_iteratively( sage: from sage.schemes.riemann_surfaces.riemann_surface import RiemannSurface sage: R. = QQ[] - sage: S = RiemannSurface(w^2+z^4-1, prec=100) + sage: S = RiemannSurface(w^2 + z^4 - 1, prec=100) sage: branch = 0 sage: eps = S._RR(2)**(-S._prec) - sage: z_start = 1-eps + sage: z_start = 1 - eps sage: z_end = 0 sage: w_start = S.w_values(z_start)[0] sage: s = sign(w_start) @@ -3577,7 +3577,7 @@ def reduce_over_period_lattice( sage: from sage.schemes.riemann_surfaces.riemann_surface import RiemannSurface sage: R. = QQ[] - sage: S = RiemannSurface(y^2-x^5+1) + sage: S = RiemannSurface(y^2 - x^5 + 1) sage: epsilon = S._RR(2)^(-S._prec+1) sage: for vector in S.period_matrix().columns(): ....: print(bool(S.reduce_over_period_lattice(vector).norm() Date: Mon, 11 Mar 2024 22:48:14 +0900 Subject: [PATCH 247/518] Add links to sources in github --- src/doc/common/static/custom-furo.css | 14 ++ src/doc/en/a_tour_of_sage/conf.py | 5 + src/doc/en/constructions/conf.py | 5 + src/doc/en/developer/conf.py | 5 + src/doc/en/faq/conf.py | 5 + src/doc/en/installation/conf.py | 5 + src/doc/en/prep/conf.py | 5 + src/doc/en/reference/conf.py | 5 + src/doc/en/reference/conf_sub.py | 5 + src/doc/en/thematic_tutorials/conf.py | 5 + src/doc/en/tutorial/conf.py | 5 + src/doc/es/a_tour_of_sage/conf.py | 5 + src/doc/es/tutorial/conf.py | 5 + src/doc/fr/a_tour_of_sage/conf.py | 5 + src/doc/fr/tutorial/conf.py | 5 + src/doc/hu/a_tour_of_sage/conf.py | 5 + src/doc/it/a_tour_of_sage/conf.py | 5 + src/doc/it/faq/conf.py | 5 + src/doc/it/tutorial/conf.py | 5 + src/doc/ja/a_tour_of_sage/conf.py | 5 + src/doc/ja/tutorial/conf.py | 5 + src/doc/pt/a_tour_of_sage/conf.py | 5 + src/doc/pt/tutorial/conf.py | 5 + src/doc/ru/tutorial/conf.py | 5 + src/doc/tr/a_tour_of_sage/conf.py | 5 + src/sage/misc/sageinspect.py | 254 +++++++++----------------- src/sage_docbuild/conf.py | 212 +++++++++++++-------- 27 files changed, 355 insertions(+), 245 deletions(-) diff --git a/src/doc/common/static/custom-furo.css b/src/doc/common/static/custom-furo.css index fc76a3f7c0f..ae75c2b6383 100644 --- a/src/doc/common/static/custom-furo.css +++ b/src/doc/common/static/custom-furo.css @@ -21,3 +21,17 @@ a.pdf:hover { text-decoration: none; } +/* Style for announcement banner */ + +.announcement { + background: orange; +} + +.announcement-content { + color: black; +} + +.announcement-content a { + color: white; + text-decoration: none; +} diff --git a/src/doc/en/a_tour_of_sage/conf.py b/src/doc/en/a_tour_of_sage/conf.py index 14ecb994215..38184747c9f 100644 --- a/src/doc/en/a_tour_of_sage/conf.py +++ b/src/doc/en/a_tour_of_sage/conf.py @@ -20,6 +20,11 @@ # contains common paths. html_static_path = [] + html_common_static_path +# Add a small edit button. +html_theme_options.update({ + 'source_edit_link': os.path.join(source_repository, f'blob/develop/src/doc/en/a_tour_of_sage', '{filename}'), +}) + # General information about the project. project = "A Tour of Sage" diff --git a/src/doc/en/constructions/conf.py b/src/doc/en/constructions/conf.py index 37215f418c6..13743e5b276 100644 --- a/src/doc/en/constructions/conf.py +++ b/src/doc/en/constructions/conf.py @@ -20,6 +20,11 @@ # contains common paths. html_static_path = [] + html_common_static_path +# Add a small edit button. +html_theme_options.update({ + 'source_edit_link': os.path.join(source_repository, 'blob/develop/src/doc/en/constructions', '{filename}'), +}) + # General information about the project. project = "Constructions" diff --git a/src/doc/en/developer/conf.py b/src/doc/en/developer/conf.py index 9dcfc261ed1..58e0ca995b7 100644 --- a/src/doc/en/developer/conf.py +++ b/src/doc/en/developer/conf.py @@ -20,6 +20,11 @@ # contains common paths. html_static_path = [] + html_common_static_path +# Add a small edit button. +html_theme_options.update({ + 'source_edit_link': os.path.join(source_repository, 'blob/develop/src/doc/en/developer', '{filename}'), +}) + # General information about the project. project = "Developer Guide" diff --git a/src/doc/en/faq/conf.py b/src/doc/en/faq/conf.py index 42c3378b129..fab823b6592 100644 --- a/src/doc/en/faq/conf.py +++ b/src/doc/en/faq/conf.py @@ -22,6 +22,11 @@ # contains common paths. html_static_path = [] + html_common_static_path +# Add a small edit button. +html_theme_options.update({ + 'source_edit_link': os.path.join(source_repository, 'blob/develop/src/doc/en/faq', '{filename}'), +}) + # General information about the project. project = "FAQ" diff --git a/src/doc/en/installation/conf.py b/src/doc/en/installation/conf.py index e50bf3c1c57..41f91e228d0 100644 --- a/src/doc/en/installation/conf.py +++ b/src/doc/en/installation/conf.py @@ -20,6 +20,11 @@ # contains common paths. html_static_path = [] + html_common_static_path +# Add a small edit button. +html_theme_options.update({ + 'source_edit_link': os.path.join(source_repository, 'blob/develop/src/doc/en/installation', '{filename}'), +}) + # General information about the project. project = "Installation Guide" diff --git a/src/doc/en/prep/conf.py b/src/doc/en/prep/conf.py index bbc6663b3df..3dc26674397 100644 --- a/src/doc/en/prep/conf.py +++ b/src/doc/en/prep/conf.py @@ -20,6 +20,11 @@ # contains common paths. html_static_path = [] + html_common_static_path +# Add a small edit button. +html_theme_options.update({ + 'source_edit_link': os.path.join(source_repository, 'blob/develop/src/doc/en/prep', '{filename}'), +}) + # General information about the project. project = "PREP Tutorials" copyright = "2012, Rob Beezer, Karl-Dieter Crisman, and Jason Grout" diff --git a/src/doc/en/reference/conf.py b/src/doc/en/reference/conf.py index 86aa0b05a81..d9bb5da5acb 100644 --- a/src/doc/en/reference/conf.py +++ b/src/doc/en/reference/conf.py @@ -25,6 +25,11 @@ ref_src = os.path.join(SAGE_DOC_SRC, 'en', 'reference') ref_out = os.path.join(SAGE_DOC, 'html', 'en', 'reference') +# Add a small edit button. +html_theme_options.update({ + 'source_edit_link': os.path.join(source_repository, 'blob/develop/src/doc/en/reference/index.rst'), +}) + # General information about the project. project = "Reference Manual" diff --git a/src/doc/en/reference/conf_sub.py b/src/doc/en/reference/conf_sub.py index b6f20311d68..8346b13c9f4 100644 --- a/src/doc/en/reference/conf_sub.py +++ b/src/doc/en/reference/conf_sub.py @@ -42,6 +42,11 @@ title = name.capitalize() title = title.replace('`', '$') +# We use the directory's name to add a small edit button. +html_theme_options.update({ + 'source_edit_link': os.path.join(source_repository, f'blob/develop/src/doc/en/reference/{name}', '{filename}'), +}) + # General information about the project. project = title diff --git a/src/doc/en/thematic_tutorials/conf.py b/src/doc/en/thematic_tutorials/conf.py index 3b638d40173..19f9a3da5af 100644 --- a/src/doc/en/thematic_tutorials/conf.py +++ b/src/doc/en/thematic_tutorials/conf.py @@ -22,6 +22,11 @@ # contains common paths. html_static_path = [] + html_common_static_path +# Add a small edit button. +html_theme_options.update({ + 'source_edit_link': os.path.join(source_repository, 'blob/develop/src/doc/en/thematic_tutorials', '{filename}'), +}) + # General information about the project. project = "Thematic Tutorials" diff --git a/src/doc/en/tutorial/conf.py b/src/doc/en/tutorial/conf.py index 8a4f626f440..7b475a3bb6e 100644 --- a/src/doc/en/tutorial/conf.py +++ b/src/doc/en/tutorial/conf.py @@ -20,6 +20,11 @@ # contains common paths. html_static_path = [] + html_common_static_path +# Add a small edit button. +html_theme_options.update({ + 'source_edit_link': os.path.join(source_repository, 'blob/develop/src/doc/en/tutorial', '{filename}'), +}) + # General information about the project. project = "Tutorial" diff --git a/src/doc/es/a_tour_of_sage/conf.py b/src/doc/es/a_tour_of_sage/conf.py index 1bf598c11f5..b6f43afd2da 100644 --- a/src/doc/es/a_tour_of_sage/conf.py +++ b/src/doc/es/a_tour_of_sage/conf.py @@ -22,6 +22,11 @@ # contains common paths. html_static_path = [] + html_common_static_path +# Add a small edit button. +html_theme_options.update({ + 'source_edit_link': os.path.join(source_repository, 'blob/develop/src/doc/es/a_tour_of_sage', '{filename}'), +}) + # General information about the project. project = 'Un Tour Por Sage' name = 'a_tour_of_sage' diff --git a/src/doc/es/tutorial/conf.py b/src/doc/es/tutorial/conf.py index bbebf61f532..913dfc06025 100644 --- a/src/doc/es/tutorial/conf.py +++ b/src/doc/es/tutorial/conf.py @@ -20,6 +20,11 @@ # contains common paths. html_static_path = [] + html_common_static_path +# Add a small edit button. +html_theme_options.update({ + 'source_edit_link': os.path.join(source_repository, 'blob/develop/src/doc/es/tutorial', '{filename}'), +}) + # General information about the project. project = "Sage Tutorial" name = 'tutorial-es' diff --git a/src/doc/fr/a_tour_of_sage/conf.py b/src/doc/fr/a_tour_of_sage/conf.py index f6438ea695a..19c047882bf 100644 --- a/src/doc/fr/a_tour_of_sage/conf.py +++ b/src/doc/fr/a_tour_of_sage/conf.py @@ -20,6 +20,11 @@ # contains common paths. html_static_path = [] + html_common_static_path +# Add a small edit button. +html_theme_options.update({ + 'source_edit_link': os.path.join(source_repository, 'blob/develop/src/doc/fr/a_tour_of_sage', '{filename}'), +}) + # General information about the project. project = 'Sage en quelques mots' name = 'a_tour_of_sage' diff --git a/src/doc/fr/tutorial/conf.py b/src/doc/fr/tutorial/conf.py index f44315072ff..87c13353111 100644 --- a/src/doc/fr/tutorial/conf.py +++ b/src/doc/fr/tutorial/conf.py @@ -20,6 +20,11 @@ # contains common paths. html_static_path = [] + html_common_static_path +# Add a small edit button. +html_theme_options.update({ + 'source_edit_link': os.path.join(source_repository, 'blob/develop/src/doc/fr/tutorial', '{filename}'), +}) + # General information about the project. project = "Tutoriel Sage" name = 'tutorial-fr' diff --git a/src/doc/hu/a_tour_of_sage/conf.py b/src/doc/hu/a_tour_of_sage/conf.py index 90517269f2e..590be9195cb 100644 --- a/src/doc/hu/a_tour_of_sage/conf.py +++ b/src/doc/hu/a_tour_of_sage/conf.py @@ -22,6 +22,11 @@ # contains common paths. html_static_path = [] + html_common_static_path +# Add a small edit button. +html_theme_options.update({ + 'source_edit_link': os.path.join(source_repository, 'blob/develop/src/doc/hu/a_tour_of_sage', '{filename}'), +}) + # General information about the project. project = 'A Sage bemutatása' name = 'a_tour_of_sage' diff --git a/src/doc/it/a_tour_of_sage/conf.py b/src/doc/it/a_tour_of_sage/conf.py index f897b790c2a..6d217dee7ec 100644 --- a/src/doc/it/a_tour_of_sage/conf.py +++ b/src/doc/it/a_tour_of_sage/conf.py @@ -20,6 +20,11 @@ # contains common paths. html_static_path = [] + html_common_static_path +# Add a small edit button. +html_theme_options.update({ + 'source_edit_link': os.path.join(source_repository, 'blob/develop/src/doc/it/a_tour_of_sage', '{filename}'), +}) + # General information about the project. project = 'Esplora Sage' name = 'a_tour_of_sage' diff --git a/src/doc/it/faq/conf.py b/src/doc/it/faq/conf.py index b5a536d383b..c2cd668d0f8 100644 --- a/src/doc/it/faq/conf.py +++ b/src/doc/it/faq/conf.py @@ -22,6 +22,11 @@ # contains common paths. html_static_path = [] + html_common_static_path +# Add a small edit button. +html_theme_options.update({ + 'source_edit_link': os.path.join(source_repository, 'blob/develop/src/doc/it/faq', '{filename}'), +}) + # General information about the project. project = "Sage FAQ" diff --git a/src/doc/it/tutorial/conf.py b/src/doc/it/tutorial/conf.py index 1f3bdec9367..13bfa100034 100644 --- a/src/doc/it/tutorial/conf.py +++ b/src/doc/it/tutorial/conf.py @@ -20,6 +20,11 @@ # contains common paths. html_static_path = [] + html_common_static_path +# Add a small edit button. +html_theme_options.update({ + 'source_edit_link': os.path.join(source_repository, 'blob/develop/src/doc/it/tutorial', '{filename}'), +}) + # General information about the project. project = "Tutorial Sage" name = 'tutorial-it' diff --git a/src/doc/ja/a_tour_of_sage/conf.py b/src/doc/ja/a_tour_of_sage/conf.py index 451d7cc5b58..105cae0a93d 100644 --- a/src/doc/ja/a_tour_of_sage/conf.py +++ b/src/doc/ja/a_tour_of_sage/conf.py @@ -20,6 +20,11 @@ # contains common paths. html_static_path = [] + html_common_static_path +# Add a small edit button. +html_theme_options.update({ + 'source_edit_link': os.path.join(source_repository, 'blob/develop/src/doc/ja/a_tour_of_sage', '{filename}'), +}) + # General information about the project. project = "Sage ガイドツアー" name = 'a_tour_of_sage' diff --git a/src/doc/ja/tutorial/conf.py b/src/doc/ja/tutorial/conf.py index e4b79bd7875..ef32faab6b8 100644 --- a/src/doc/ja/tutorial/conf.py +++ b/src/doc/ja/tutorial/conf.py @@ -20,6 +20,11 @@ # contains common paths. html_static_path = [] + html_common_static_path +# Add a small edit button. +html_theme_options.update({ + 'source_edit_link': os.path.join(source_repository, 'blob/develop/src/doc/ja/tutorial', '{filename}'), +}) + # General information about the project. project = "Sage チュートリアル" name = 'tutorial-jp' diff --git a/src/doc/pt/a_tour_of_sage/conf.py b/src/doc/pt/a_tour_of_sage/conf.py index 10d9367f942..075e2d7a4f5 100644 --- a/src/doc/pt/a_tour_of_sage/conf.py +++ b/src/doc/pt/a_tour_of_sage/conf.py @@ -20,6 +20,11 @@ # contains common paths. html_static_path = [] + html_common_static_path +# Add a small edit button. +html_theme_options.update({ + 'source_edit_link': os.path.join(source_repository, 'blob/develop/src/doc/pt/a_tour_of_sage', '{filename}'), +}) + # General information about the project. project = 'Uma Turnê pelo Sage' name = 'a_tour_of_sage' diff --git a/src/doc/pt/tutorial/conf.py b/src/doc/pt/tutorial/conf.py index f299582db9a..13582d8b896 100644 --- a/src/doc/pt/tutorial/conf.py +++ b/src/doc/pt/tutorial/conf.py @@ -20,6 +20,11 @@ # contains common paths. html_static_path = [] + html_common_static_path +# Add a small edit button. +html_theme_options.update({ + 'source_edit_link': os.path.join(source_repository, 'blob/develop/src/doc/pt/tutorial', '{filename}'), +}) + # General information about the project. project = "Tutorial Sage" name = 'tutorial-pt' diff --git a/src/doc/ru/tutorial/conf.py b/src/doc/ru/tutorial/conf.py index cda4d9762ed..5ff38a94d50 100644 --- a/src/doc/ru/tutorial/conf.py +++ b/src/doc/ru/tutorial/conf.py @@ -20,6 +20,11 @@ # contains common paths. html_static_path = [] + html_common_static_path +# Add a small edit button. +html_theme_options.update({ + 'source_edit_link': os.path.join(source_repository, 'blob/develop/src/doc/ru/tutorial', '{filename}'), +}) + # General information about the project. project = "Sage Tutorial in Russian" name = 'tutorial-ru' diff --git a/src/doc/tr/a_tour_of_sage/conf.py b/src/doc/tr/a_tour_of_sage/conf.py index f969a1b516a..c1854ee2f42 100644 --- a/src/doc/tr/a_tour_of_sage/conf.py +++ b/src/doc/tr/a_tour_of_sage/conf.py @@ -20,6 +20,11 @@ # contains common paths. html_static_path = [] + html_common_static_path +# Add a small edit button. +html_theme_options.update({ + 'source_edit_link': os.path.join(source_repository, f'blob/develop/src/doc/tr/a_tour_of_sage', '{filename}'), +}) + # General information about the project. project = 'Sage Turu' name = 'a_tour_of_sage' diff --git a/src/sage/misc/sageinspect.py b/src/sage/misc/sageinspect.py index aff47ef96bb..7a5c304f67b 100644 --- a/src/sage/misc/sageinspect.py +++ b/src/sage/misc/sageinspect.py @@ -3,14 +3,6 @@ This module extends parts of Python's inspect module to Cython objects. -AUTHORS: - -- originally taken from Fernando Perez's IPython -- William Stein (extensive modifications) -- Nick Alexander (extensions) -- Nick Alexander (testing) -- Simon King (some extension for Cython, generalisation of SageArgSpecVisitor) - EXAMPLES:: sage: from sage.misc.sageinspect import * @@ -21,10 +13,8 @@ sage: sage_getfile(sage.rings.rational) '.../rational.pyx' - sage: sage_getdoc(sage.rings.rational).lstrip() 'Rational Numbers...' - sage: sage_getsource(sage.rings.rational) '# distutils: ...Rational Numbers...' @@ -32,10 +22,8 @@ sage: sage_getfile(sage.misc.sageinspect) '.../sageinspect.py' - sage: print(sage_getdoc(sage.misc.sageinspect).lstrip()[:40]) Inspect Python, Sage, and Cython objects - sage: sage_getsource(sage.misc.sageinspect).lstrip()[5:-1] 'Inspect Python, Sage, and Cython objects...' @@ -45,10 +33,8 @@ sage: sage_getfile(sage.rings.rational.Rational) '.../rational.pyx' - sage: sage_getdoc(sage.rings.rational.Rational).lstrip() 'A rational number...' - sage: sage_getsource(sage.rings.rational.Rational) 'cdef class Rational...' @@ -56,10 +42,8 @@ sage: sage_getfile(BlockFinder) '.../sage/misc/sageinspect.py' - sage: sage_getdoc(BlockFinder).lstrip()[:50] 'Provide a tokeneater() method to detect the...' - sage: sage_getsource(BlockFinder) 'class BlockFinder:...' @@ -69,13 +53,10 @@ sage: sage_getdef(sage.rings.rational.make_rational, obj_name='mr') 'mr(s)' - sage: sage_getfile(sage.rings.rational.make_rational) '.../rational.pyx' - sage: sage_getdoc(sage.rings.rational.make_rational).lstrip() 'Make a rational number ...' - sage: sage_getsource(sage.rings.rational.make_rational) '@cython.binding(True)\ndef make_rational(s):...' @@ -83,13 +64,10 @@ sage: sage_getdef(sage.misc.sageinspect.sage_getfile, obj_name='sage_getfile') 'sage_getfile(obj)' - sage: sage_getfile(sage.misc.sageinspect.sage_getfile) '.../sageinspect.py' - sage: sage_getdoc(sage.misc.sageinspect.sage_getfile).lstrip() 'Get the full file name associated to "obj" as a string...' - sage: sage_getsource(sage.misc.sageinspect.sage_getfile)[4:] 'sage_getfile(obj):...' @@ -98,7 +76,6 @@ sage: sage_getdef(''.find, 'find') 'find(*args, **kwds)' - sage: sage_getdef(str.find, 'find') 'find(*args, **kwds)' @@ -112,6 +89,22 @@ def foo(unsigned int x=1, a=')"', b={not (2+1==3):'bar'}, *args, **kwds): return sage: sage_getargspec(foo) FullArgSpec(args=['x', 'a', 'b'], varargs='args', varkw='kwds', defaults=(1, ')"', {False: 'bar'}), kwonlyargs=[], kwonlydefaults=None, annotations={}) +AUTHORS: + +- Originally taken from Fernando Perez's IPython +- William Stein: extensive modifications +- William Stein: in :func:`_sage_getargspec_cython`, a modified version of + ``inspect.getargspec`` from the Python Standard Library, which was taken from + IPython for use in Sage +- Nick Alexander: extensions, testing +- Simon King: some extension for Cython, generalisation of SageArgSpecVisitor +- Simon King: in :func:`sage_getsourcelines`, if a class has no docstring then let the + class definition be found starting from the ``__init__`` method. +- Simon King: in :func:`sage_getsourcelines`, get source lines for dynamic classes +- Simon King: in :func:`_sage_getargspec_cython`, return an ``ArgSpec``, fix some bugs +- Simon King (2011-09): added :func:`_sage_getsourcelines_name_with_dot` +- Simon King (2013-02): in :func:`_sage_getargspec_cython()`, recognise varargs and + default values in cython code, and return an ``ArgSpec`` """ import ast @@ -200,7 +193,9 @@ def isclassinstance(obj): r""" Check if argument is instance of non built-in class - INPUT: ``obj`` -- object + INPUT: + + - ``obj`` -- object EXAMPLES:: @@ -282,11 +277,6 @@ def _extract_embedded_position(docstring): ....: print(f.read()) cpdef test_funct(x,y): return - AUTHORS: - - - William Stein - - Extensions by Nick Alexander - - Extension for interactive Cython code by Simon King """ try: res = __embedded_position_re.search(docstring) @@ -334,10 +324,6 @@ def _extract_embedded_signature(docstring, name): INPUT: ``docstring`` (string) - AUTHORS: - - - Simon King - EXAMPLES:: sage: from sage.misc.sageinspect import _extract_embedded_signature @@ -349,6 +335,7 @@ def _extract_embedded_signature(docstring, name): FullArgSpec(args=['self', 'x', 'r'], varargs='args', varkw='kwds', defaults=((1, 2, 3.4),), kwonlyargs=[], kwonlydefaults=None, annotations={}) sage: _extract_embedded_signature(range.__call__.__doc__, '__call__') ('Call self as a function.', None) + """ # If there is an embedded signature, it is in the first line L = docstring.split(os.linesep, 1) @@ -370,7 +357,7 @@ def _extract_embedded_signature(docstring, name): class BlockFinder: """ - Provide a tokeneater() method to detect the end of a code block. + Provide a :meth:`tokeneater` method to detect the end of a code block. This is the Python library's :class:`inspect.BlockFinder` modified to recognize Cython definitions. @@ -444,8 +431,8 @@ def _extract_source(lines, lineno): INPUT: - - ``lines`` - string or list of strings - - ``lineno`` - positive integer + - ``lines`` -- string or list of strings + - ``lineno`` -- positive integer EXAMPLES:: @@ -494,6 +481,7 @@ class SageArgSpecVisitor(ast.NodeVisitor): 'jc' sage: visitor.visit(v.value) ['veni', 'vidi', 'vici'] + """ def visit_Name(self, node): """ @@ -501,11 +489,9 @@ def visit_Name(self, node): INPUT: - - ``node`` - the node instance to visit - - OUTPUT: + - ``node`` -- the node instance to visit - - None, True, False, or the ``node``'s name as a string. + OUTPUT: ``None``, ``True``, ``False``, or the ``node``'s name as a string EXAMPLES:: @@ -516,6 +502,7 @@ def visit_Name(self, node): ['foo', 'bar'] sage: [type(vis(n)) for n in ['foo', 'bar']] [, ] + """ return node.id @@ -530,9 +517,7 @@ def visit_NameConstant(self, node): - ``node`` - the node instance to visit - OUTPUT: - - - None, True, False. + OUTPUT: ``None``, ``True``, ``False`` EXAMPLES:: @@ -543,6 +528,7 @@ def visit_NameConstant(self, node): [True, False, None] sage: [type(vis(n)) for n in ['True', 'False', 'None']] [, , ] + """ return node.value @@ -560,9 +546,7 @@ def visit_arg(self, node): - ``node`` -- the node instance to visit - OUTPUT: - - the argument name + OUTPUT: the argument name EXAMPLES:: @@ -572,6 +556,7 @@ def visit_arg(self, node): sage: args = ast.parse(s).body[0].args.args sage: [visitor.visit_arg(n) for n in args] ['a', 'b', 'c', 'd'] + """ return node.arg @@ -581,11 +566,9 @@ def visit_Num(self, node): INPUT: - - ``node`` - the node instance to visit - - OUTPUT: + - ``node`` -- the node instance to visit - - the number the ``node`` represents + OUTPUT: the number the ``node`` represents EXAMPLES:: @@ -608,11 +591,9 @@ def visit_Str(self, node): INPUT: - - ``node`` - the node instance to visit - - OUTPUT: + - ``node`` -- the node instance to visit - - the string the ``node`` represents + OUTPUT: the string the ``node`` represents EXAMPLES:: @@ -621,6 +602,7 @@ def visit_Str(self, node): sage: vis = lambda x: visitor.visit_Str(ast.parse(x).body[0].value) sage: [vis(s) for s in ['"abstract"', "'syntax'", r'''r"tr\ee"''']] ['abstract', 'syntax', 'tr\\ee'] + """ return node.value @@ -630,11 +612,9 @@ def visit_List(self, node): INPUT: - - ``node`` - the node instance to visit - - OUTPUT: + - ``node`` -- the node instance to visit - - the list the ``node`` represents + OUTPUT: the list the ``node`` represents EXAMPLES:: @@ -643,6 +623,7 @@ def visit_List(self, node): sage: vis = lambda x: visitor.visit_List(ast.parse(x).body[0].value) sage: [vis(l) for l in ['[]', "['s', 't', 'u']", '[[e], [], [pi]]']] [[], ['s', 't', 'u'], [['e'], [], ['pi']]] + """ t = [] for n in node.elts: @@ -655,11 +636,9 @@ def visit_Tuple(self, node): INPUT: - - ``node`` - the node instance to visit - - OUTPUT: + - ``node`` -- the node instance to visit - - the tuple the ``node`` represents + OUTPUT: the tuple the ``node`` represents EXAMPLES:: @@ -668,6 +647,7 @@ def visit_Tuple(self, node): sage: vis = lambda x: visitor.visit_Tuple(ast.parse(x).body[0].value) sage: [vis(t) for t in ['()', '(x,y)', '("Au", "Al", "Cu")']] [(), ('x', 'y'), ('Au', 'Al', 'Cu')] + """ t = [] for n in node.elts: @@ -680,11 +660,9 @@ def visit_Dict(self, node): INPUT: - - ``node`` - the node instance to visit - - OUTPUT: + - ``node`` -- the node instance to visit - - the dictionary the ``node`` represents + OUTPUT: the dictionary the ``node`` represents EXAMPLES:: @@ -694,6 +672,7 @@ def visit_Dict(self, node): sage: v = [vis(d) for d in ['{}', "{1:one, 'two':2, other:bother}"]] sage: [sorted(d.items(), key=lambda x: str(x[0])) for d in v] [[], [(1, 'one'), ('other', 'bother'), ('two', 2)]] + """ d = {} for k, v in zip(node.keys, node.values): @@ -706,15 +685,9 @@ def visit_BoolOp(self, node): INPUT: - - ``node`` - the node instance to visit - - OUTPUT: - - - The result that ``node`` represents - - AUTHOR: + - ``node`` -- the node instance to visit - - Simon King + OUTPUT: the result that ``node`` represents EXAMPLES:: @@ -745,15 +718,9 @@ def visit_Compare(self, node): INPUT: - - ``node`` - the node instance to visit - - OUTPUT: - - - The result that ``node`` represents - - AUTHOR: + - ``node`` -- the node instance to visit - - Simon King + OUTPUT: the result that ``node`` represents EXAMPLES:: @@ -797,15 +764,9 @@ def visit_BinOp(self, node): INPUT: - - ``node`` - the node instance to visit - - OUTPUT: - - - The result that ``node`` represents - - AUTHOR: + - ``node`` -- the node instance to visit - - Simon King + OUTPUT: the result that ``node`` represents EXAMPLES:: @@ -814,6 +775,7 @@ def visit_BinOp(self, node): sage: vis = lambda x: visitor.visit(ast.parse(x).body[0].value) sage: [vis(d) for d in ['(3+(2*4))', '7|8', '5^3', '7/3', '7//3', '3<<4']] #indirect doctest [11, 15, 6, 2.3333333333333335, 2, 48] + """ op = node.op.__class__.__name__ if op == 'Add': @@ -867,15 +829,9 @@ def visit_UnaryOp(self, node): INPUT: - - ``node`` - the node instance to visit - - OUTPUT: - - - The result that ``node`` represents - - AUTHOR: + - ``node`` -- the node instance to visit - - Simon King + OUTPUT: the result that ``node`` represents EXAMPLES:: @@ -901,7 +857,7 @@ def _grep_first_pair_of_parentheses(s): INPUT: - A string + - ``s`` -- a string OUTPUT: @@ -924,6 +880,7 @@ def _grep_first_pair_of_parentheses(s): Traceback (most recent call last): ... SyntaxError: The given string does not contain balanced parentheses + """ out = [] single_quote = False @@ -956,7 +913,7 @@ def _split_syntactical_unit(s): INPUT: - - ``s``, a string + - ``s`` -- a string OUTPUT: @@ -1101,12 +1058,10 @@ def _sage_getargspec_from_ast(source): INPUT: - - ``source`` - a string; the function's (or method's) source code + - ``source`` -- a string; the function's (or method's) source code definition. The function's body is ignored. - OUTPUT: - - - an instance of :obj:`inspect.ArgSpec`, i.e., a named tuple + OUTPUT: an instance of :obj:`inspect.ArgSpec`, i.e., a named tuple EXAMPLES:: @@ -1123,6 +1078,7 @@ def _sage_getargspec_from_ast(source): True sage: set(from_ast(sms.sage_getsource(x)) == inspect.getfullargspec(x) for x in [factor, identity_matrix, Graph.__init__]) # needs sage.graphs sage.modules {True} + """ ast_args = ast.parse(source.lstrip()).body[0].args @@ -1146,13 +1102,11 @@ def _sage_getargspec_cython(source): INPUT: - - ``source`` - a string; the function's (or method's) source code + - ``source`` -- a string; the function's (or method's) source code definition. The function's body is ignored. The definition may contain type definitions for the function arguments. - OUTPUT: - - - an instance of :class:`inspect.FullArgSpec`, i.e., a named tuple + OUTPUT: an instance of :class:`inspect.FullArgSpec`, i.e., a named tuple EXAMPLES:: @@ -1222,13 +1176,6 @@ def _sage_getargspec_cython(source): defaults=('a string', {(1, 2, 3): True}), kwonlyargs=[], kwonlydefaults=None, annotations={}) - - AUTHORS: - - - Nick Alexander: original version - - Simon King (02-2013): recognise varargs and default values in - cython code, and return an ``ArgSpec``. - """ defpos = source.find('def ') assert defpos > -1, "The given source does not contain 'def'" @@ -1352,7 +1299,9 @@ def sage_getfile(obj): r""" Get the full file name associated to ``obj`` as a string. - INPUT: ``obj``, a Sage object, module, etc. + INPUT: + + - ``obj`` -- a Sage object, module, etc. EXAMPLES:: @@ -1394,10 +1343,6 @@ def sage_getfile(obj): sage: sage_getfile(range) '' - AUTHORS: - - - Nick Alexander - - Simon King """ # We try to extract from docstrings, but not using Python's inspect # because _sage_getdoc_unformatted is more robust. @@ -1432,7 +1377,9 @@ def sage_getfile_relative(obj): if the source file is part of the ``sage.*`` namespace, it makes the file name relative so that it starts with ``sage/``. - INPUT: ``obj``, a Sage object, module, etc. + INPUT: + + - ``obj`` -- a Sage object, module, etc. EXAMPLES:: @@ -1445,6 +1392,7 @@ def sage_getfile_relative(obj): 'sage/symbolic/expression.pyx' sage: sage_getfile_relative(range) '' + """ filename = sage_getfile(obj) if not filename: @@ -1667,12 +1615,6 @@ def foo(x, a='\')"', b={not (2+1==3):'bar'}): return sage: shell.run_cell('f??') ...the source code string... - AUTHORS: - - - William Stein: a modified version of inspect.getargspec from the - Python Standard Library, which was taken from IPython for use in Sage. - - Extensions by Nick Alexander - - Simon King: Return an ``ArgSpec``, fix some bugs. """ from sage.misc.lazy_attribute import lazy_attribute from sage.misc.abstract_method import AbstractMethod @@ -1771,7 +1713,7 @@ def formatannotation(annotation, base_module=None): INPUT: - ``annotation`` -- annotation for a function - - ``base_module`` (optional, default ``None``) + - ``base_module``-- (optional, default ``None``) This is only relevant with Python 3, so the doctests are marked accordingly. @@ -1893,8 +1835,8 @@ def sage_getdef(obj, obj_name=''): INPUT: - - ``obj`` - function - - ``obj_name`` - string (optional, default '') + - ``obj`` -- function + - ``obj_name`` -- string (optional, default '') ``obj_name`` is prepended to the output. @@ -1913,11 +1855,6 @@ def sage_getdef(obj, obj_name=''): If an exception is generated, None is returned instead and the exception is suppressed. - - AUTHORS: - - - William Stein - - extensions by Nick Alexander """ try: spec = sage_getargspec(obj) @@ -1983,10 +1920,6 @@ def _sage_getdoc_unformatted(obj): sage: _sage_getdoc_unformatted(obj) '' - AUTHORS: - - - William Stein - - extensions by Nick Alexander """ if obj is None: return '' @@ -2124,10 +2057,6 @@ def sage_getdoc(obj, obj_name='', embedded=False): sage: sage_getdoc(f1) 'specialised documentation\n' - AUTHORS: - - - William Stein - - extensions by Nick Alexander """ import sage.misc.sagedoc if obj is None: @@ -2168,11 +2097,6 @@ def sage_getsource(obj): 'identity_matrix(ring, n=0, sparse=False):' sage: sage_getsource(identity_matrix)[19:60] # needs sage.modules 'identity_matrix(ring, n=0, sparse=False):' - - AUTHORS: - - - William Stein - - extensions by Nick Alexander """ # First we should check if the object has a _sage_src_ # method. If it does, we just return the output from @@ -2234,10 +2158,6 @@ class ParentMethods: has a docstring. Otherwise, the code of ``B`` could not be found (Cython inserts embedding information into the docstring) and thus the code of ``B.A`` couldn't be found either. - - AUTHOR: - - - Simon King (2011-09) """ # First, split the name: if '.' in obj.__name__: @@ -2344,26 +2264,29 @@ def sage_getsourcelines(obj): EXAMPLES:: + sage: # needs sage.modules sage: from sage.misc.sageinspect import sage_getsourcelines - sage: sage_getsourcelines(matrix)[1] # needs sage.modules + sage: sage_getsourcelines(matrix)[1] 21 - sage: sage_getsourcelines(matrix)[0][0] # needs sage.modules + sage: sage_getsourcelines(matrix)[0][0] 'def matrix(*args, **kwds):\n' Some classes customize this using a ``_sage_src_lines_`` method, which gives the source lines of a class instance, but not the class itself. We demonstrate this for :class:`CachedFunction`:: - sage: cachedfib = cached_function(fibonacci) # needs sage.combinat - sage: sage_getsourcelines(cachedfib)[0][0] # needs sage.combinat + sage: # needs sage.combinat + sage: cachedfib = cached_function(fibonacci) + sage: sage_getsourcelines(cachedfib)[0][0] 'def fibonacci(n, algorithm="pari") -> Integer:\n' - sage: sage_getsourcelines(type(cachedfib))[0][0] # needs sage.combinat + sage: sage_getsourcelines(type(cachedfib))[0][0] 'cdef class CachedFunction():\n' TESTS:: - sage: cython('''cpdef test_funct(x,y): return''') # needs sage.misc.cython - sage: sage_getsourcelines(test_funct) # needs sage.misc.cython + sage: # needs sage.misc.cython + sage: cython('''cpdef test_funct(x,y): return''') + sage: sage_getsourcelines(test_funct) (['cpdef test_funct(x,y): return\n'], 1) The following tests that an instance of ``functools.partial`` is correctly @@ -2440,15 +2363,6 @@ class Element(): ' Ideal_generic):\n', ' def __init__(self, ring, gens, coerce=True):\n', ...) - - AUTHORS: - - - William Stein - - Extensions by Nick Alexander - - Extension to interactive Cython code by Simon King - - Simon King: If a class has no docstring then let the class - definition be found starting from the ``__init__`` method. - - Simon King: Get source lines for dynamic classes. """ # First try the method _sage_src_lines_(), which is meant to give # the source lines of an object (not of its type!). @@ -2488,11 +2402,7 @@ class Element(): pos = _extract_embedded_position(d) if pos is None: try: - # BEWARE HERE - # inspect gives str (=bytes) in python2 - # and str (=unicode) in python3 return inspect.getsourcelines(obj) - except (OSError, TypeError) as err: try: objinit = obj.__init__ diff --git a/src/sage_docbuild/conf.py b/src/sage_docbuild/conf.py index c5329e79cfd..93a3fd3ad42 100644 --- a/src/sage_docbuild/conf.py +++ b/src/sage_docbuild/conf.py @@ -18,24 +18,23 @@ # Load configuration shared with sage.misc.sphinxify from sage.misc.sagedoc_conf import * -import importlib import sys import os +import importlib +import dateutil.parser import sphinx import sphinx.ext.intersphinx as intersphinx -import dateutil.parser -import sage.version - from sphinx import highlighting from sphinx.transforms import SphinxTransform from IPython.lib.lexers import IPythonConsoleLexer, IPyLexer - from sage.misc.sagedoc import extlinks from sage.env import SAGE_DOC_SRC, SAGE_DOC, PPLPY_DOCS, MATHJAX_DIR from sage.misc.latex_macros import sage_mathjax_macros from sage.features import PythonModule from sage.features.all import all_features +import sage.version +# --------------------- # General configuration # --------------------- @@ -48,6 +47,7 @@ 'sphinx.ext.todo', 'sphinx.ext.extlinks', 'sphinx.ext.mathjax', + 'sphinx.ext.linkcode', 'sphinx_copybutton', 'sphinx_inline_tabs', 'IPython.sphinxext.ipython_directive', @@ -195,6 +195,9 @@ def sphinx_plot(graphics, **kwds): version = sage.version.version release = sage.version.version +source_repository = 'https://github.com/sagemath/sage/' +source_branch = 'develop' + # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # language = None @@ -252,6 +255,7 @@ def sphinx_plot(graphics, **kwds): # displaying all parents. toc_object_entries_show_parents = 'hide' +# ----------------------- # Extension configuration # ----------------------- @@ -310,73 +314,122 @@ def set_intersphinx_mappings(app, config): copybutton_exclude = '.linenos, .c1' # exclude single comments (in particular, # optional!) copybutton_only_copy_prompt_lines = True + +# https://www.sphinx-doc.org/en/master/usage/extensions/linkcode.html +def linkcode_resolve(domain, info): + import inspect + from urllib.parse import quote + from sage.misc.sageinspect import sage_getsourcelines + if domain != 'py': + return None + if info['module']: + m = importlib.import_module(info['module']) + filename = quote(info['module'].replace('.', '/')) + if m.__file__.endswith('py'): + filename += '.py' + else: + filename += '.pyx' + if 'fullname' in info: + fullname = info['fullname'] + obj = m + try: + for attr in fullname.split('.'): + obj = getattr(obj, attr) + lineno = sage_getsourcelines(obj)[-1] + except: + return None + anchor = f'#L{lineno}' + else: + anchor = '' + return f"{source_repository}blob/{version}/src/{filename}{anchor}" + return None + + +class EDIT_LINK(str): + def format(self, filename): + link = os.path.join(source_repository, f'blob/{version}/src/doc/en/reference/{filename}.py') + return link + + +# ----------------------- # Options for HTML output # ----------------------- # Add any paths that contain custom themes here, relative to this directory. html_theme_path = [os.path.join(SAGE_DOC_SRC, "common", "themes")] -if PythonModule("furo").is_present(): - # Sphinx theme "furo" does not permit an extension. Do not attempt to make - # a "sage-furo" theme. - html_theme = "furo" - - # Theme options are theme-specific and customize the look and feel of - # a theme further. For a list of options available for each theme, - # see the documentation. - html_theme_options = { - "light_css_variables": { - "color-brand-primary": "#0f0fff", - "color-brand-content": "#0f0fff", - }, - "light_logo": "logo_sagemath_black.svg", - "dark_logo": "logo_sagemath_white.svg", - } - - # The name of the Pygments (syntax highlighting) style to use. This - # overrides a HTML theme's corresponding setting. - pygments_style = "sphinx" - pygments_dark_style = "monokai" - - # Add siderbar/home.html to the default sidebar. - html_sidebars = { - "**": [ - "sidebar/scroll-start.html", - "sidebar/brand.html", - "sidebar/search.html", - "sidebar/home.html", - "sidebar/navigation.html", - "sidebar/ethical-ads.html", - "sidebar/scroll-end.html", - "sidebar/variant-selector.html", - ] - } - - # These paths are either relative to html_static_path - # or fully qualified paths (eg. https://...) - html_css_files = [ - 'custom-furo.css', - 'custom-jupyter-sphinx.css', - 'custom-codemirror-monokai.css', - ] +# Deprecated Sage classic theme: +# +# html_theme = "sage-classic" +# html_theme_options = {} +# +# See the directory doc/common/themes/sage-classic/ for theme files. + +# Sphinx theme "furo" does not permit an extension. Do not attempt to make +# a "sage-furo" theme. +html_theme = "furo" + +# Theme options are theme-specific and customize the look and feel of +# a theme further. For a list of options available for each theme, +# see the documentation. +html_theme_options = { + "light_css_variables": { + "color-brand-primary": "#0f0fff", + "color-brand-content": "#0f0fff", + }, + "light_logo": "logo_sagemath_black.svg", + "dark_logo": "logo_sagemath_white.svg", + # Furo can add a small edit button to each document to allow visitors to + # easily propose changes to that document using the repository’s source + # control system. + # https://pradyunsg.me/furo/customisation/edit-button/#adding-an-edit-button + "source_repository": source_repository, + "source_branch": source_branch, + # "source_directory" is defined in conf.py customized for the doc +} - html_js_files = [ - 'jupyter-sphinx-furo.js', +if not version.split('.')[-1].isnumeric(): # develop version + html_theme_options.update({ + "announcement": f'This is documentation for Sage development version {version}. ' + 'The documentation for the latest stable version is available ' + 'here.' +}) + +# The name of the Pygments (syntax highlighting) style to use. This +# overrides a HTML theme's corresponding setting. +pygments_style = "sphinx" +pygments_dark_style = "monokai" + +# Add siderbar/home.html to the default sidebar. +html_sidebars = { + "**": [ + "sidebar/scroll-start.html", + "sidebar/brand.html", + "sidebar/search.html", + "sidebar/home.html", + "sidebar/navigation.html", + "sidebar/ethical-ads.html", + "sidebar/scroll-end.html", + "sidebar/variant-selector.html", ] +} - # A list of paths that contain extra templates (or templates that overwrite - # builtin/theme-specific templates). Relative paths are taken as relative - # to the configuration directory. - templates_path = [os.path.join(SAGE_DOC_SRC, 'common', 'templates-furo')] + templates_path +# These paths are either relative to html_static_path +# or fully qualified paths (eg. https://...) +html_css_files = [ + 'custom-furo.css', + 'custom-jupyter-sphinx.css', + 'custom-codemirror-monokai.css', +] -else: - # Sage default Sphinx theme. - # - # See the directory doc/common/themes/sage-classic/ for files comprising - # the custom theme. - html_theme = "sage-classic" +html_js_files = [ + 'jupyter-sphinx-furo.js', +] - html_theme_options = {} +# A list of paths that contain extra templates (or templates that overwrite +# builtin/theme-specific templates). Relative paths are taken as relative +# to the configuration directory. +templates_path = [os.path.join(SAGE_DOC_SRC, 'common', 'templates-furo')] + templates_path # HTML style sheet. This overrides a HTML theme's corresponding setting. # html_style = 'default.css' @@ -435,7 +488,7 @@ def set_intersphinx_mappings(app, config): # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' +# html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. @@ -457,27 +510,29 @@ def set_intersphinx_mappings(app, config): modindex_common_prefix = ['sage.'] # If false, no index is generated. -#html_use_index = True +# html_use_index = True # If true, the index is split into individual pages for each letter. html_split_index = True # If true, the reST sources are included in the HTML build as _sources/. -#html_copy_source = True +# html_copy_source = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. -#html_use_opensearch = '' +# html_use_opensearch = '' # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = '' +# html_file_suffix = '' # Output file base name for HTML help builder. -#htmlhelp_basename = '' +# htmlhelp_basename = '' +# ------------------------ # Options for LaTeX output # ------------------------ + # See http://sphinx-doc.org/config.html#confval-latex_elements latex_elements = {} @@ -520,13 +575,14 @@ def set_intersphinx_mappings(app, config): """ # Documents to append as an appendix to all manuals. -#latex_appendices = [] +# latex_appendices = [] # If false, no module index is generated. -#latex_use_modindex = True +# latex_use_modindex = True -##################################################### +# ------------------------- # add LaTeX macros for Sage +# ------------------------- from sage.misc.latex_macros import sage_latex_macros @@ -541,8 +597,9 @@ def set_intersphinx_mappings(app, config): # used when building html version pngmath_latex_preamble += macro + '\n' -##################################################### +# ------------------------------------------ # add custom context variables for templates +# ------------------------------------------ def add_page_context(app, pagename, templatename, context, doctree): # # The template function @@ -566,7 +623,17 @@ def add_page_context(app, pagename, templatename, context, doctree): context['reference_title'] = 'Sage {}'.format(release) + ' Reference Manual' context['reference_root'] = os.path.join(relpath, 'index.html') context['refsub'] = True - + if pagename.startswith('sage/'): + # This is for adding small edit button using Furo's feature: + # https://pradyunsg.me/furo/customisation/edit-button/#adding-an-edit-button + # This works well if the source file is '.rst' file. But the '.rst' + # files in the directory 'sage/' are generated by the Sphinx + # autodoc from the Python or Cython source files. Hence we teak + # here template context variables so that links to the correct + # source files are generated. + suffix = '.py' if importlib.import_module(pagename.replace('/','.')).__file__.endswith('.py') else '.pyx' + context['page_source_suffix'] = suffix + context['theme_source_edit_link'] = os.path.join(source_repository, f'blob/develop/src', '{filename}') dangling_debug = False @@ -728,7 +795,6 @@ def nitpick_patch_config(app): '__builtin__', ] - def check_nested_class_picklability(app, what, name, obj, skip, options): """ Print a warning if pickling is broken for nested classes. From 92726b7ed3b8c9510355ea3cfe6035118a625f34 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 13 Mar 2024 22:38:10 -0700 Subject: [PATCH 248/518] src/sage/rings: Doctest cosmetics --- src/sage/rings/complex_conversion.pyx | 2 +- src/sage/rings/complex_mpfr.pyx | 4 +- .../finite_rings/residue_field_givaro.pyx | 2 +- .../function_field/function_field_polymod.py | 52 +++++--- .../polynomial/polynomial_rational_flint.pyx | 2 +- .../rings/puiseux_series_ring_element.pyx | 4 +- src/sage/rings/qqbar.py | 39 ++++-- src/sage/rings/real_mpfi.pyx | 126 +++++++++--------- src/sage/rings/real_mpfr.pyx | 4 +- src/sage/rings/tate_algebra_element.pyx | 119 ++++++++++------- .../rings/valuation/augmented_valuation.py | 2 +- 11 files changed, 199 insertions(+), 157 deletions(-) diff --git a/src/sage/rings/complex_conversion.pyx b/src/sage/rings/complex_conversion.pyx index 39c8b63eb55..027c4426ea1 100644 --- a/src/sage/rings/complex_conversion.pyx +++ b/src/sage/rings/complex_conversion.pyx @@ -9,7 +9,7 @@ cdef class CCtoCDF(Map): """ EXAMPLES:: sage: from sage.rings.complex_conversion import CCtoCDF - sage: f = CCtoCDF(CC, CDF) # indirect doctest + sage: f = CCtoCDF(CC, CDF) # indirect doctest sage: f(CC.0) 1.0*I sage: f(exp(pi*CC.0/4)) # needs sage.symbolic diff --git a/src/sage/rings/complex_mpfr.pyx b/src/sage/rings/complex_mpfr.pyx index 872ca8c60a3..193c58db708 100644 --- a/src/sage/rings/complex_mpfr.pyx +++ b/src/sage/rings/complex_mpfr.pyx @@ -444,7 +444,9 @@ class ComplexField_class(sage.rings.abc.ComplexField): sage: CC.gen() + QQ.extension(x^2 + 1, 'I', embedding=None).gen() # needs sage.rings.number_field Traceback (most recent call last): ... - TypeError: unsupported operand parent(s) for +: 'Complex Field with 53 bits of precision' and 'Number Field in I with defining polynomial x^2 + 1' + TypeError: unsupported operand parent(s) for +: + 'Complex Field with 53 bits of precision' and + 'Number Field in I with defining polynomial x^2 + 1' In the absence of arguments we return zero:: diff --git a/src/sage/rings/finite_rings/residue_field_givaro.pyx b/src/sage/rings/finite_rings/residue_field_givaro.pyx index 07fd740c044..a00bc5138fd 100644 --- a/src/sage/rings/finite_rings/residue_field_givaro.pyx +++ b/src/sage/rings/finite_rings/residue_field_givaro.pyx @@ -123,7 +123,7 @@ class ResidueFiniteField_givaro(ResidueField_generic, FiniteField_givaro): sage: R. = GF(3)[]; P = R.ideal(t^4 - t^3 + t + 1); k. = P.residue_field() sage: V = k.vector_space(map=False); v = V([0,1,2,3]) - sage: k(v) # indirect doctest + sage: k(v) # indirect doctest 2*a^2 + a """ try: diff --git a/src/sage/rings/function_field/function_field_polymod.py b/src/sage/rings/function_field/function_field_polymod.py index 60a0773afb8..d2c9ad869ea 100644 --- a/src/sage/rings/function_field/function_field_polymod.py +++ b/src/sage/rings/function_field/function_field_polymod.py @@ -738,46 +738,50 @@ def free_module(self, base=None, basis=None, map=True): We convert an element of the vector space back to the function field:: - sage: from_V(V.1) # needs sage.modules + sage: from_V(V.1) # needs sage.modules y We define an interesting element of the function field:: - sage: a = 1/L.0; a # needs sage.modules + sage: a = 1/L.0; a # needs sage.modules (x/(x^4 + 1))*y^4 - 2*x^2/(x^4 + 1) We convert it to the vector space, and get a vector over the base field:: - sage: to_V(a) # needs sage.modules + sage: to_V(a) # needs sage.modules (-2*x^2/(x^4 + 1), 0, 0, 0, x/(x^4 + 1)) We convert to and back, and get the same element:: - sage: from_V(to_V(a)) == a # needs sage.modules + sage: from_V(to_V(a)) == a # needs sage.modules True In the other direction:: - sage: v = x*V.0 + (1/x)*V.1 # needs sage.modules - sage: to_V(from_V(v)) == v # needs sage.modules + sage: v = x*V.0 + (1/x)*V.1 # needs sage.modules + sage: to_V(from_V(v)) == v # needs sage.modules True And we show how it works over an extension of an extension field:: sage: R2. = L[]; M. = L.extension(z^2 - y) - sage: M.free_module() # needs sage.modules - (Vector space of dimension 2 over Function field in y defined by y^5 - 2*x*y + (-x^4 - 1)/x, Isomorphism: + sage: M.free_module() # needs sage.modules + (Vector space of dimension 2 over Function field in y defined by y^5 - 2*x*y + (-x^4 - 1)/x, + Isomorphism: From: Vector space of dimension 2 over Function field in y defined by y^5 - 2*x*y + (-x^4 - 1)/x - To: Function field in z defined by z^2 - y, Isomorphism: + To: Function field in z defined by z^2 - y, + Isomorphism: From: Function field in z defined by z^2 - y To: Vector space of dimension 2 over Function field in y defined by y^5 - 2*x*y + (-x^4 - 1)/x) We can also get the vector space of ``M`` over ``K``:: - sage: M.free_module(K) # needs sage.modules - (Vector space of dimension 10 over Rational function field in x over Rational Field, Isomorphism: + sage: M.free_module(K) # needs sage.modules + (Vector space of dimension 10 over Rational function field in x over Rational Field, + Isomorphism: From: Vector space of dimension 10 over Rational function field in x over Rational Field - To: Function field in z defined by z^2 - y, Isomorphism: + To: Function field in z defined by z^2 - y, + Isomorphism: From: Function field in z defined by z^2 - y To: Vector space of dimension 10 over Rational function field in x over Rational Field) @@ -1270,10 +1274,12 @@ def simple_model(self, name=None): sage: L. = K.extension(y^2 - x); R. = L[] sage: M. = L.extension(z^2 - y) sage: M.simple_model() - (Function field in z defined by z^4 + x, Function Field morphism: + (Function field in z defined by z^4 + x, + Function Field morphism: From: Function field in z defined by z^4 + x To: Function field in z defined by z^2 + y - Defn: z |--> z, Function Field morphism: + Defn: z |--> z, + Function Field morphism: From: Function field in z defined by z^2 + y To: Function field in z defined by z^4 + x Defn: z |--> z @@ -1443,7 +1449,8 @@ def separable_model(self, names=None): sage: L.separable_model() # needs sage.rings.finite_rings Traceback (most recent call last): ... - NotImplementedError: constructing a separable model is only implemented for function fields over a perfect constant base field + NotImplementedError: constructing a separable model is only implemented + for function fields over a perfect constant base field TESTS: @@ -1492,11 +1499,13 @@ def separable_model(self, names=None): sage: R. = L[] sage: M. = L.extension(z^3 - y) sage: M.separable_model() - (Function field in z_ defined by z_ + x_^6, Function Field morphism: + (Function field in z_ defined by z_ + x_^6, + Function Field morphism: From: Function field in z_ defined by z_ + x_^6 To: Function field in z defined by z^3 + y Defn: z_ |--> x - x_ |--> z, Function Field morphism: + x_ |--> z, + Function Field morphism: From: Function field in z defined by z^3 + y To: Function field in z_ defined by z_ + x_^6 Defn: z |--> x_ @@ -1605,12 +1614,14 @@ def change_variable_name(self, name): y |--> y x |--> x) sage: M.change_variable_name(('zz','yy')) - (Function field in zz defined by zz^2 - yy, Function Field morphism: + (Function field in zz defined by zz^2 - yy, + Function Field morphism: From: Function field in zz defined by zz^2 - yy To: Function field in z defined by z^2 - y Defn: zz |--> z yy |--> y - x |--> x, Function Field morphism: + x |--> x, + Function Field morphism: From: Function field in z defined by z^2 - y To: Function field in zz defined by zz^2 - yy Defn: z |--> zz @@ -1686,7 +1697,8 @@ def _inversion_isomorphism(self): From: Function field in T defined by T^3 + (x^4 + x^2 + 1)/x^6 To: Function field in y defined by y^3 + x^6 + x^4 + x^2 Defn: T |--> y - x |--> 1/x, Composite map: + x |--> 1/x, + Composite map: From: Function field in y defined by y^3 + x^6 + x^4 + x^2 To: Function field in s defined by s^3 + x^16 + x^14 + x^12 Defn: Function Field morphism: diff --git a/src/sage/rings/polynomial/polynomial_rational_flint.pyx b/src/sage/rings/polynomial/polynomial_rational_flint.pyx index eb534474eb7..55b08809a33 100644 --- a/src/sage/rings/polynomial/polynomial_rational_flint.pyx +++ b/src/sage/rings/polynomial/polynomial_rational_flint.pyx @@ -336,7 +336,7 @@ cdef class Polynomial_rational_flint(Polynomial): INPUT: - - ``singular`` - Singular interpreter (default: default interpreter) + - ``singular`` -- Singular interpreter (default: default interpreter) EXAMPLES:: diff --git a/src/sage/rings/puiseux_series_ring_element.pyx b/src/sage/rings/puiseux_series_ring_element.pyx index 3f50fb45e2e..33d03269302 100644 --- a/src/sage/rings/puiseux_series_ring_element.pyx +++ b/src/sage/rings/puiseux_series_ring_element.pyx @@ -49,7 +49,9 @@ Mind the base ring. However, the base ring can be changed:: sage: I*q # needs sage.rings.number_field Traceback (most recent call last): ... - TypeError: unsupported operand parent(s) for *: 'Number Field in I with defining polynomial x^2 + 1 with I = 1*I' and 'Puiseux Series Ring in x over Rational Field' + TypeError: unsupported operand parent(s) for *: + 'Number Field in I with defining polynomial x^2 + 1 with I = 1*I' and + 'Puiseux Series Ring in x over Rational Field' sage: qz = q.change_ring(ZZ); qz x^(1/3) + x^(1/2) sage: qz.parent() diff --git a/src/sage/rings/qqbar.py b/src/sage/rings/qqbar.py index 9e107208c39..36063eb23e0 100644 --- a/src/sage/rings/qqbar.py +++ b/src/sage/rings/qqbar.py @@ -269,7 +269,8 @@ Bailey, Yozo, Li and Thompson discusses this result. Evidently it is difficult to find, but we can easily verify it. :: - sage: alpha = QQbar.polynomial_root(x^10 + x^9 - x^7 - x^6 - x^5 - x^4 - x^3 + x + 1, RIF(1, 1.2)) + sage: alpha = QQbar.polynomial_root(x^10 + x^9 - x^7 - x^6 + ....: - x^5 - x^4 - x^3 + x + 1, RIF(1, 1.2)) sage: lhs = alpha^630 - 1 sage: rhs_num = (alpha^315 - 1) * (alpha^210 - 1) * (alpha^126 - 1)^2 * (alpha^90 - 1) * (alpha^3 - 1)^3 * (alpha^2 - 1)^5 * (alpha - 1)^3 sage: rhs_den = (alpha^35 - 1) * (alpha^15 - 1)^2 * (alpha^14 - 1)^2 * (alpha^5 - 1)^6 * alpha^68 @@ -468,8 +469,10 @@ 0.?e-15 sage: ax = polygen(AA) - sage: x2 = AA.polynomial_root(256*ax**8 - 128*ax**7 - 448*ax**6 + 192*ax**5 + 240*ax**4 - 80*ax**3 - 40*ax**2 + 8*ax + 1, RIF(0.9829, 0.983)) - sage: y2 = (1-x2**2).sqrt() + sage: x2 = AA.polynomial_root(256*ax**8 - 128*ax**7 - 448*ax**6 + 192*ax**5 + ....: + 240*ax**4 - 80*ax**3 - 40*ax**2 + 8*ax + 1, + ....: RIF(0.9829, 0.983)) + sage: y2 = (1 - x2**2).sqrt() sage: x - x2 0.?e-18 sage: y - y2 @@ -485,7 +488,8 @@ sage: x = AA['x'].gen() sage: P = 1/(1+x^4) sage: P.partial_fraction_decomposition() - (0, [(-0.3535533905932738?*x + 1/2)/(x^2 - 1.414213562373095?*x + 1), (0.3535533905932738?*x + 1/2)/(x^2 + 1.414213562373095?*x + 1)]) + (0, [(-0.3535533905932738?*x + 1/2)/(x^2 - 1.414213562373095?*x + 1), + (0.3535533905932738?*x + 1/2)/(x^2 + 1.414213562373095?*x + 1)]) Check that :issue:`22202` is fixed:: @@ -2638,7 +2642,8 @@ def number_field_elements_from_algebraics(numbers, minimal=False, Things work fine with rational numbers, too:: sage: number_field_elements_from_algebraics((QQbar(1/2), AA(17))) - (Rational Field, [1/2, 17], Ring morphism: + (Rational Field, [1/2, 17], + Ring morphism: From: Rational Field To: Algebraic Real Field Defn: 1 |--> 1) @@ -2690,8 +2695,8 @@ def number_field_elements_from_algebraics(numbers, minimal=False, It is also possible to have an embedded Number Field:: sage: x = polygen(ZZ) - sage: my_num = AA.polynomial_root(x^3-2, RIF(0,3)) - sage: res = number_field_elements_from_algebraics(my_num,embedded=True) + sage: my_num = AA.polynomial_root(x^3 - 2, RIF(0,3)) + sage: res = number_field_elements_from_algebraics(my_num, embedded=True) sage: res[0].gen_embedding() 1.259921049894873? sage: res[2] @@ -2783,12 +2788,14 @@ def number_field_elements_from_algebraics(numbers, minimal=False, Tests trivial cases:: sage: number_field_elements_from_algebraics([], embedded=True) - (Rational Field, [], Ring morphism: + (Rational Field, [], + Ring morphism: From: Rational Field To: Algebraic Real Field Defn: 1 |--> 1) sage: number_field_elements_from_algebraics([1], embedded=True) - (Rational Field, [1], Ring morphism: + (Rational Field, [1], + Ring morphism: From: Rational Field To: Algebraic Real Field Defn: 1 |--> 1) @@ -2798,7 +2805,7 @@ def number_field_elements_from_algebraics(numbers, minimal=False, sage: # needs sage.libs.gap sage.symbolic sage: UCF = UniversalCyclotomicField() sage: E = UCF.gen(5) - sage: L. = NumberField(x^2-189*x+16, embedding=200) + sage: L. = NumberField(x^2 - 189*x + 16, embedding=200) sage: x = polygen(ZZ) sage: my_nums = [-52*E - 136*E^2 - 136*E^3 - 52*E^4, ....: L.gen()._algebraic_(AA), @@ -4382,7 +4389,8 @@ def as_number_field_element(self, minimal=False, embedded=False, prec=53): sage: AA(elt) Traceback (most recent call last): ... - ValueError: need a real or complex embedding to convert a non rational element of a number field into an algebraic number + ValueError: need a real or complex embedding to convert a non rational + element of a number field into an algebraic number sage: hom(elt) == rt True @@ -4391,7 +4399,8 @@ def as_number_field_element(self, minimal=False, embedded=False, prec=53): sage: (nf, elt, hom) = rt.as_number_field_element(embedded=True) sage: nf.coerce_embedding() Generic morphism: - From: Number Field in a with defining polynomial y^3 - 2*y^2 - 31*y - 50 with a = 7.237653139801104? + From: Number Field in a with defining polynomial y^3 - 2*y^2 - 31*y - 50 + with a = 7.237653139801104? To: Algebraic Real Field Defn: a -> 7.237653139801104? sage: elt @@ -4518,9 +4527,11 @@ def _exact_field(self): sage: QQbar(2)._exact_field() Trivial generator sage: (sqrt(QQbar(2)) + sqrt(QQbar(19)))._exact_field() - Number Field in a with defining polynomial y^4 - 20*y^2 + 81 with a in -3.789313782671036? + Number Field in a with defining polynomial y^4 - 20*y^2 + 81 + with a in -3.789313782671036? sage: (QQbar(7)^(3/5))._exact_field() - Number Field in a with defining polynomial y^5 - 2*y^4 - 18*y^3 + 38*y^2 + 82*y - 181 with a in 2.554256611698490? + Number Field in a with defining polynomial + y^5 - 2*y^4 - 18*y^3 + 38*y^2 + 82*y - 181 with a in 2.554256611698490? """ sd = self._descr if isinstance(sd, (ANRational, ANExtensionElement)): diff --git a/src/sage/rings/real_mpfi.pyx b/src/sage/rings/real_mpfi.pyx index 69143c243f3..4cc03624ec8 100644 --- a/src/sage/rings/real_mpfi.pyx +++ b/src/sage/rings/real_mpfi.pyx @@ -3294,7 +3294,7 @@ cdef class RealIntervalFieldElement(RingElement): This method returns `+1` if all elements in this interval are positive, `-1` if all of them are negative and `0` if it contains only zero. - Otherwise it raises a ``ValueError``. + Otherwise it raises a :class:`ValueError`. EXAMPLES:: @@ -3329,7 +3329,7 @@ cdef class RealIntervalFieldElement(RingElement): def argument(self): r""" The argument of this interval, if it is well-defined, in the - complex sense. Otherwise raises a ``ValueError``. + complex sense. Otherwise raises a :class:`ValueError`. OUTPUT: @@ -3367,12 +3367,10 @@ cdef class RealIntervalFieldElement(RingElement): def unique_floor(self): """ - Returns the unique floor of this interval, if it is well defined, - otherwise raises a ``ValueError``. + Return the unique floor of this interval, if it is well defined, + otherwise raise a :class:`ValueError`. - OUTPUT: - - - an integer. + OUTPUT: an integer. .. SEEALSO:: @@ -3398,12 +3396,10 @@ cdef class RealIntervalFieldElement(RingElement): def unique_ceil(self): """ - Returns the unique ceiling of this interval, if it is well defined, - otherwise raises a ``ValueError``. + Return the unique ceiling of this interval, if it is well defined, + otherwise raise a :class:`ValueError`. - OUTPUT: - - - an integer. + OUTPUT: an integer. .. SEEALSO:: @@ -3429,12 +3425,10 @@ cdef class RealIntervalFieldElement(RingElement): def unique_round(self): """ - Returns the unique round (nearest integer) of this interval, - if it is well defined, otherwise raises a ``ValueError``. + Return the unique round (nearest integer) of this interval, + if it is well defined, otherwise raise a :class:`ValueError`. - OUTPUT: - - - an integer. + OUTPUT: an integer. .. SEEALSO:: @@ -3494,7 +3488,7 @@ cdef class RealIntervalFieldElement(RingElement): def unique_trunc(self): r""" Return the nearest integer toward zero if it is unique, otherwise raise - a ``ValueError``. + a :class:`ValueError`. .. SEEALSO:: @@ -3526,7 +3520,7 @@ cdef class RealIntervalFieldElement(RingElement): def unique_integer(self): """ Return the unique integer in this interval, if there is exactly one, - otherwise raises a ``ValueError``. + otherwise raise a :class:`ValueError`. EXAMPLES:: @@ -3563,7 +3557,7 @@ cdef class RealIntervalFieldElement(RingElement): Traceback (most recent call last): ... ValueError: unable to convert interval 0.50000000000000000? to an integer - sage: ZZ(RIF(1/2,3/2)) + sage: ZZ(RIF(1/2, 3/2)) Traceback (most recent call last): ... ValueError: unable to convert interval 1.? to an integer @@ -4331,51 +4325,51 @@ cdef class RealIntervalFieldElement(RingElement): ############################ def sqrt(self): + r""" + Return a square root of ``self``. Raises an error if ``self`` is + nonpositive. + + If you use :meth:`square_root()` then an interval will always be + returned (though it will be ``NaN`` if ``self`` is nonpositive). + + EXAMPLES:: + + sage: r = RIF(4.0) + sage: r.sqrt() + 2 + sage: r.sqrt()^2 == r + True + + :: + + sage: r = RIF(4344) + sage: r.sqrt() + 65.90902821313633? + sage: r.sqrt()^2 == r + False + sage: r in r.sqrt()^2 + True + sage: r.sqrt()^2 - r + 0.?e-11 + sage: (r.sqrt()^2 - r).str(style='brackets') + '[-9.0949470177292824e-13 .. 1.8189894035458565e-12]' + + :: + + sage: r = RIF(-2.0) + sage: r.sqrt() + Traceback (most recent call last): + ... + ValueError: self (=-2) is not >= 0 + + :: + + sage: r = RIF(-2, 2) + sage: r.sqrt() + Traceback (most recent call last): + ... + ValueError: self (=0.?e1) is not >= 0 """ - Return a square root of ``self``. Raises an error if ``self`` is - nonpositive. - - If you use :meth:`square_root()` then an interval will always be - returned (though it will be ``NaN`` if self is nonpositive). - - EXAMPLES:: - - sage: r = RIF(4.0) - sage: r.sqrt() - 2 - sage: r.sqrt()^2 == r - True - - :: - - sage: r = RIF(4344) - sage: r.sqrt() - 65.90902821313633? - sage: r.sqrt()^2 == r - False - sage: r in r.sqrt()^2 - True - sage: r.sqrt()^2 - r - 0.?e-11 - sage: (r.sqrt()^2 - r).str(style='brackets') - '[-9.0949470177292824e-13 .. 1.8189894035458565e-12]' - - :: - - sage: r = RIF(-2.0) - sage: r.sqrt() - Traceback (most recent call last): - ... - ValueError: self (=-2) is not >= 0 - - :: - - sage: r = RIF(-2, 2) - sage: r.sqrt() - Traceback (most recent call last): - ... - ValueError: self (=0.?e1) is not >= 0 - """ if self.lower() < 0: raise ValueError("self (=%s) is not >= 0" % self) return self.square_root() @@ -4384,7 +4378,7 @@ cdef class RealIntervalFieldElement(RingElement): def square_root(self): """ Return a square root of ``self``. An interval will always be returned - (though it will be ``NaN`` if self is nonpositive). + (though it will be ``NaN`` if ``self`` is nonpositive). EXAMPLES:: @@ -5135,7 +5129,7 @@ cdef class RealIntervalFieldElement(RingElement): def psi(self): """ - Return the digamma function evaluated on self. + Return the digamma function evaluated on ``self``. INPUT: diff --git a/src/sage/rings/real_mpfr.pyx b/src/sage/rings/real_mpfr.pyx index 20ba8dfa284..4241d5237e2 100644 --- a/src/sage/rings/real_mpfr.pyx +++ b/src/sage/rings/real_mpfr.pyx @@ -4295,7 +4295,9 @@ cdef class RealNumber(sage.structure.element.RingElement): sage: (1/2)^2.0 0.250000000000000 sage: [n^(1.5) for n in range(10)] - [0.000000000000000, 1.00000000000000, 2.82842712474619, 5.19615242270663, 8.00000000000000, 11.1803398874989, 14.6969384566991, 18.5202591774521, 22.6274169979695, 27.0000000000000] + [0.000000000000000, 1.00000000000000, 2.82842712474619, 5.19615242270663, + 8.00000000000000, 11.1803398874989, 14.6969384566991, 18.5202591774521, + 22.6274169979695, 27.0000000000000] sage: int(-2)^(0.333333) 0.629961522017056 + 1.09112272417509*I sage: int(0)^(1.0) diff --git a/src/sage/rings/tate_algebra_element.pyx b/src/sage/rings/tate_algebra_element.pyx index e812a0a7f5f..ddb58fcf2c8 100644 --- a/src/sage/rings/tate_algebra_element.pyx +++ b/src/sage/rings/tate_algebra_element.pyx @@ -59,7 +59,8 @@ def _pushout_family(elements, initial=ZZ): sage: S. = Zq(4) sage: _pushout_family([a, x, 3]) - Tate Algebra in x (val >= 0), y (val >= 0) over 2-adic Unramified Extension Field in a defined by x^2 + x + 1 + Tate Algebra in x (val >= 0), y (val >= 0) + over 2-adic Unramified Extension Field in a defined by x^2 + x + 1 """ from sage.structure.coerce_exceptions import CoercionException @@ -89,14 +90,15 @@ cdef class TateAlgebraTerm(MonoidElement): - ``coeff`` -- an element in the base field - - ``exponent`` - a tuple of length ``n`` + - ``exponent`` -- a tuple of length ``n`` EXAMPLES:: sage: R = Zp(2, print_mode='digits', prec=10) sage: A. = TateAlgebra(R) sage: T = A.monoid_of_terms(); T - Monoid of terms in x (val >= 0), y (val >= 0) over 2-adic Field with capped relative precision 10 + Monoid of terms in x (val >= 0), y (val >= 0) + over 2-adic Field with capped relative precision 10 sage: T(2*x*y) ...00000000010*x*y @@ -301,7 +303,8 @@ cdef class TateAlgebraTerm(MonoidElement): sage: R = Zp(2, print_mode='digits', prec=10) sage: A. = TateAlgebra(R) sage: T = A.monoid_of_terms(); T - Monoid of terms in x (val >= 0), y (val >= 0) over 2-adic Field with capped relative precision 10 + Monoid of terms in x (val >= 0), y (val >= 0) + over 2-adic Field with capped relative precision 10 sage: t = T(2,(1,1)) sage: t.exponent() (1, 1) @@ -315,7 +318,7 @@ cdef class TateAlgebraTerm(MonoidElement): INPUT: - - ``other`` - a Tate algebra term + - ``other`` -- a Tate algebra term EXAMPLES:: @@ -644,7 +647,7 @@ cdef class TateAlgebraTerm(MonoidElement): INPUT: - - ``other`` - a Tate term + - ``other`` -- a Tate term EXAMPLES:: @@ -708,7 +711,7 @@ cdef class TateAlgebraTerm(MonoidElement): INPUT: - - ``other`` - a Tate term + - ``other`` -- a Tate term EXAMPLES:: @@ -738,7 +741,7 @@ cdef class TateAlgebraTerm(MonoidElement): INPUT: - - ``other`` - a Tate term + - ``other`` -- a Tate term EXAMPLES:: @@ -776,7 +779,7 @@ cdef class TateAlgebraTerm(MonoidElement): INPUT: - - ``other`` - a Tate term + - ``other`` -- a Tate term EXAMPLES:: @@ -803,7 +806,7 @@ cdef class TateAlgebraTerm(MonoidElement): INPUT: - - ``other`` - a Tate term + - ``other`` -- a Tate term EXAMPLES:: @@ -839,9 +842,9 @@ cdef class TateAlgebraTerm(MonoidElement): INPUT: - - ``other`` - a Tate term + - ``other`` -- a Tate term - - ``integral`` - (default: ``False``); if ``True``, test + - ``integral`` -- (default: ``False``); if ``True``, test for divisibility in the ring of integers of the Tate algebra EXAMPLES:: @@ -897,9 +900,9 @@ cdef class TateAlgebraTerm(MonoidElement): INPUT: - - ``other`` - a Tate term + - ``other`` -- a Tate term - - ``integral`` - (default: ``False``); if ``True``, test for + - ``integral`` -- (default: ``False``); if ``True``, test for divisibility in the ring of integers of the Tate algebra EXAMPLES:: @@ -954,9 +957,9 @@ cdef class TateAlgebraTerm(MonoidElement): INPUT: - - ``other`` - a Tate term + - ``other`` -- a Tate term - - ``integral`` - (default: ``False``) if ``True``, test for + - ``integral`` -- (default: ``False``) if ``True``, test for divisibility in the ring of integers of the Tate algebra EXAMPLES:: @@ -986,7 +989,7 @@ cdef class TateAlgebraTerm(MonoidElement): INPUT: - - ``other`` - a Tate term + - ``other`` -- a Tate term EXAMPLES:: @@ -1019,7 +1022,7 @@ cdef class TateAlgebraTerm(MonoidElement): INPUT: - - ``other`` - a Tate term + - ``other`` -- a Tate term EXAMPLES:: @@ -1053,7 +1056,7 @@ cdef class TateAlgebraElement(CommutativeAlgebraElement): EXAMPLES:: - sage: R = Zp(2,prec=10,print_mode='digits') + sage: R = Zp(2, prec=10, print_mode='digits') sage: A. = TateAlgebra(R) sage: A(2*x+1) ...0000000001 + ...00000000010*x @@ -1069,7 +1072,7 @@ cdef class TateAlgebraElement(CommutativeAlgebraElement): TESTS:: - sage: R = Zp(2,prec=10,print_mode='digits') + sage: R = Zp(2, prec=10, print_mode='digits') sage: A. = TateAlgebra(R) sage: TestSuite(x).run() @@ -1126,7 +1129,7 @@ cdef class TateAlgebraElement(CommutativeAlgebraElement): EXAMPLES:: - sage: R = Zp(2,prec=10,print_mode='digits') + sage: R = Zp(2, prec=10, print_mode='digits') sage: A. = TateAlgebra(R) sage: x + y # indirect doctest ...0000000001*x + ...0000000001*y @@ -1142,7 +1145,7 @@ cdef class TateAlgebraElement(CommutativeAlgebraElement): """ Normalize this series. - sage: R = Zp(2,prec=10,print_mode='digits') + sage: R = Zp(2, prec=10, print_mode='digits') sage: A. = TateAlgebra(R) sage: A(78612, prec=3) # indirect doctest ...100 + O(2^3 * ) @@ -1446,8 +1449,9 @@ cdef class TateAlgebraElement(CommutativeAlgebraElement): sage: f = 2*x + 1; f ...0000000001 + ...00000000010*x sage: f.inverse_of_unit() - ...0000000001 + ...1111111110*x + ...0000000100*x^2 + ...1111111000*x^3 + ...0000010000*x^4 + - ...1111100000*x^5 + ...0001000000*x^6 + ...1110000000*x^7 + ...0100000000*x^8 + ...1000000000*x^9 + O(2^10 * ) + ...0000000001 + ...1111111110*x + ...0000000100*x^2 + ...1111111000*x^3 + + ...0000010000*x^4 + ...1111100000*x^5 + ...0001000000*x^6 + + ...1110000000*x^7 + ...0100000000*x^8 + ...1000000000*x^9 + O(2^10 * ) sage: f.inverse_of_unit(prec=4) ...0001 + ...1110*x + ...0100*x^2 + ...1000*x^3 + O(2^4 * ) @@ -1492,11 +1496,11 @@ cdef class TateAlgebraElement(CommutativeAlgebraElement): def is_unit(self): r""" - Return ``True`` if this series in invertible. + Return ``True`` if this series is invertible. EXAMPLES:: - sage: R = Zp(2, print_mode='digits',prec=10) + sage: R = Zp(2, print_mode='digits', prec=10) sage: A. = TateAlgebra(R) sage: f = 2*x + 1; f ...0000000001 + ...00000000010*x @@ -1551,12 +1555,14 @@ cdef class TateAlgebraElement(CommutativeAlgebraElement): sage: f = 1 + 6*x^2 + 9*y^2 sage: f^(-1) - ...0001 + ...2210*x^2 + ...1100*x^4 + ...2200*y^2 + ...1000*x^6 + ...1000*x^2*y^2 + O(3^4 * ) + ...0001 + ...2210*x^2 + ...1100*x^4 + ...2200*y^2 + ...1000*x^6 + + ...1000*x^2*y^2 + O(3^4 * ) or a square root (or more generally a nth root):: sage: g = f^(1/2); g - ...0001 + ...0010*x^2 + ...1100*x^4 + ...1200*y^2 + ...2000*x^6 + ...1000*x^2*y^2 + O(3^4 * ) + ...0001 + ...0010*x^2 + ...1100*x^4 + ...1200*y^2 + ...2000*x^6 + + ...1000*x^2*y^2 + O(3^4 * ) sage: g^2 == f True @@ -1636,10 +1642,12 @@ cdef class TateAlgebraElement(CommutativeAlgebraElement): sage: A. = TateAlgebra(R) sage: f = 1 + 6*x^2 + 9*y^2 sage: g = f.sqrt(); g - ...0000000001 + ...0000000010*x^2 + ...1111111100*x^4 + ...1111111200*y^2 + ...1111112000*x^6 + ...1111111000*x^2*y^2 + ... + O(3^10 * ) + ...0000000001 + ...0000000010*x^2 + ...1111111100*x^4 + ...1111111200*y^2 + + ...1111112000*x^6 + ...1111111000*x^2*y^2 + ... + O(3^10 * ) sage: f.square_root(prec=4) - ...0001 + ...0010*x^2 + ...1100*x^4 + ...1200*y^2 + ...2000*x^6 + ...1000*x^2*y^2 + O(3^4 * ) + ...0001 + ...0010*x^2 + ...1100*x^4 + ...1200*y^2 + ...2000*x^6 + + ...1000*x^2*y^2 + O(3^4 * ) sage: g^2 == f True @@ -1675,10 +1683,12 @@ cdef class TateAlgebraElement(CommutativeAlgebraElement): sage: A. = TateAlgebra(R) sage: f = 1 + 6*x^2 + 9*y^2 sage: g = f.sqrt(); g - ...0000000001 + ...0000000010*x^2 + ...1111111100*x^4 + ...1111111200*y^2 + ...1111112000*x^6 + ...1111111000*x^2*y^2 + ... + O(3^10 * ) + ...0000000001 + ...0000000010*x^2 + ...1111111100*x^4 + ...1111111200*y^2 + + ...1111112000*x^6 + ...1111111000*x^2*y^2 + ... + O(3^10 * ) sage: f.sqrt(prec=4) - ...0001 + ...0010*x^2 + ...1100*x^4 + ...1200*y^2 + ...2000*x^6 + ...1000*x^2*y^2 + O(3^4 * ) + ...0001 + ...0010*x^2 + ...1100*x^4 + ...1200*y^2 + ...2000*x^6 + + ...1000*x^2*y^2 + O(3^4 * ) sage: g^2 == f True @@ -1720,7 +1730,8 @@ cdef class TateAlgebraElement(CommutativeAlgebraElement): sage: A. = TateAlgebra(R) sage: f = 1 + 9*x^2 + 9*y^2 sage: g = f.nth_root(3, prec=3); g - ...001 + ...010*x^2 + ...010*y^2 + ...200*x^6 + ...200*y^6 + ...200*x^4 + ...100*x^2*y^2 + ...200*y^4 + O(3^3 * ) + ...001 + ...010*x^2 + ...010*y^2 + ...200*x^6 + ...200*y^6 + ...200*x^4 + + ...100*x^2*y^2 + ...200*y^4 + O(3^3 * ) sage: g^3 == f True @@ -2018,12 +2029,12 @@ cdef class TateAlgebraElement(CommutativeAlgebraElement): def __lshift__(self, n): r""" - Return the product of this series by the ``n``th power + Return the product of this series by the ``n``-th power of the uniformizer. INPUT: - - ``n`` - an integer + - ``n`` -- an integer EXAMPLES:: @@ -2055,7 +2066,7 @@ cdef class TateAlgebraElement(CommutativeAlgebraElement): INPUT: - - ``n`` - an integer + - ``n`` -- an integer EXAMPLES:: @@ -2102,12 +2113,12 @@ cdef class TateAlgebraElement(CommutativeAlgebraElement): INPUT: - - ``prec`` - an integer or ``None`` (default: ``None``), + - ``prec`` -- an integer or ``None`` (default: ``None``), the precision at which the series should be compared to zero EXAMPLES:: - sage: R = Zp(2, print_mode='digits',prec=10) + sage: R = Zp(2, print_mode='digits', prec=10) sage: A. = TateAlgebra(R) sage: f = x + 2*x^2 + x^3; f ...0000000001*x^3 + ...0000000001*x + ...00000000010*x^2 @@ -2151,7 +2162,8 @@ cdef class TateAlgebraElement(CommutativeAlgebraElement): sage: g = f.restriction(-1); g ...0000000001*y^2 + ...00000000010*x sage: g.parent() - Tate Algebra in x (val >= 1), y (val >= 1) over 2-adic Field with capped relative precision 10 + Tate Algebra in x (val >= 1), y (val >= 1) + over 2-adic Field with capped relative precision 10 Note that restricting may change the order of the terms:: @@ -2352,7 +2364,7 @@ cdef class TateAlgebraElement(CommutativeAlgebraElement): EXAMPLES:: - sage: R = Zp(2,prec=10,print_mode='digits') + sage: R = Zp(2, prec=10, print_mode='digits') sage: A. = TateAlgebra(R) sage: f = 32*x + 64*x^2; f ...000000000100000*x + ...0000000001000000*x^2 @@ -2430,7 +2442,7 @@ cdef class TateAlgebraElement(CommutativeAlgebraElement): EXAMPLES:: - sage: R = Zp(2,prec=10,print_mode='digits') + sage: R = Zp(2, prec=10, print_mode='digits') sage: A. = TateAlgebra(R) sage: f = x + 2*x^2; f ...0000000001*x + ...00000000010*x^2 @@ -2507,7 +2519,7 @@ cdef class TateAlgebraElement(CommutativeAlgebraElement): EXAMPLES:: - sage: R = Zp(2, print_mode='digits',prec=10) + sage: R = Zp(2, print_mode='digits', prec=10) sage: A. = TateAlgebra(R.fraction_field()) sage: f = x^4 + 4*x*y + 1; f ...0000000001*x^4 + ...0000000001 + ...000000000100*x*y @@ -2549,10 +2561,12 @@ cdef class TateAlgebraElement(CommutativeAlgebraElement): sage: A. = TateAlgebra(R) sage: f = 1 + 3*x + 9*y^2 sage: f.log() - ...0000000010*x + ...0000000100*x^3 + ...1111111100*x^2 + ...0000000100*y^2 + ...2222222000*x*y^2 + ... + O(3^10 * ) + ...0000000010*x + ...0000000100*x^3 + ...1111111100*x^2 + ...0000000100*y^2 + + ...2222222000*x*y^2 + ... + O(3^10 * ) sage: f.log(prec=4) - ...0010*x + ...0100*x^3 + ...1100*x^2 + ...0100*y^2 + ...2000*x*y^2 + O(3^4 * ) + ...0010*x + ...0100*x^3 + ...1100*x^2 + ...0100*y^2 + ...2000*x*y^2 + + O(3^4 * ) If the precision on the input is not enough to determine the result at precision ``prec``, a result with smaller precision @@ -2561,9 +2575,11 @@ cdef class TateAlgebraElement(CommutativeAlgebraElement): sage: g = f.add_bigoh(4); g ...0001 + ...0010*x + ...0100*y^2 + O(3^4 * ) sage: g.log() - ...0010*x + ...0100*x^3 + ...1100*x^2 + ...0100*y^2 + ...2000*x*y^2 + O(3^4 * ) + ...0010*x + ...0100*x^3 + ...1100*x^2 + ...0100*y^2 + ...2000*x*y^2 + + O(3^4 * ) sage: g.log(prec=10) - ...0010*x + ...0100*x^3 + ...1100*x^2 + ...0100*y^2 + ...2000*x*y^2 + O(3^4 * ) + ...0010*x + ...0100*x^3 + ...1100*x^2 + ...0100*y^2 + ...2000*x*y^2 + + O(3^4 * ) When the input value is outside the domain of convergence, an error is raised:: @@ -2577,7 +2593,8 @@ cdef class TateAlgebraElement(CommutativeAlgebraElement): However `\log(1+x)` converges on a smaller disk:: sage: f.restriction(-1).log() - ...0000000001*x + ...000000000.1*x^3 + ...111111111*x^2 + ... + O(3^10 * <3*x, 3*y>) + ...0000000001*x + ...000000000.1*x^3 + ...111111111*x^2 + ... + + O(3^10 * <3*x, 3*y>) TESTS:: @@ -2706,7 +2723,8 @@ cdef class TateAlgebraElement(CommutativeAlgebraElement): sage: A. = TateAlgebra(R) sage: f = 3*x^2 + 9*y sage: f.exp() - ...0000000001 + ...0000000010*x^2 + ...1111111200*x^6 + ...1111111200*x^4 + ...0000000100*y + ... + O(3^10 * ) + ...0000000001 + ...0000000010*x^2 + ...1111111200*x^6 + ...1111111200*x^4 + + ...0000000100*y + ... + O(3^10 * ) sage: f.exp(prec=3) ...001 + ...010*x^2 + ...200*x^6 + ...200*x^4 + ...100*y + O(3^3 * ) @@ -2734,7 +2752,8 @@ cdef class TateAlgebraElement(CommutativeAlgebraElement): However `\exp(x)` converges on a smaller disk:: sage: f.restriction(-1).exp() - ...0000000001 + ...0000000001*x + ...111111111.2*x^3 + ...111111112*x^2 + ... + O(3^10 * <3*x, 3*y>) + ...0000000001 + ...0000000001*x + ...111111111.2*x^3 + ...111111112*x^2 + + ... + O(3^10 * <3*x, 3*y>) TESTS:: @@ -2805,7 +2824,7 @@ cdef class TateAlgebraElement(CommutativeAlgebraElement): EXAMPLES:: - sage: R = Zp(2, print_mode='digits',prec=10) + sage: R = Zp(2, print_mode='digits', prec=10) sage: A. = TateAlgebra(R) sage: f = x^4 + x*y + 1; f ...0000000001*x^4 + ...0000000001*x*y + ...0000000001 diff --git a/src/sage/rings/valuation/augmented_valuation.py b/src/sage/rings/valuation/augmented_valuation.py index 77221e8d0df..3c98e082bc4 100644 --- a/src/sage/rings/valuation/augmented_valuation.py +++ b/src/sage/rings/valuation/augmented_valuation.py @@ -589,7 +589,7 @@ def restriction(self, ring): sage: R. = K[] sage: v = GaussValuation(R, K.valuation(2)) sage: w = v.augmentation(x^2 + x + 1, 1) - sage: w.restriction(QQ['x']) # needs sage.lins.singular + sage: w.restriction(QQ['x']) # needs sage.libs.singular [ Gauss valuation induced by 2-adic valuation, v(x^2 + x + 1) = 1 ] """ if ring.is_subring(self.domain()): From 8ab6051bcceb146f93ed93b7a4c211af5e859770 Mon Sep 17 00:00:00 2001 From: Kwankyu Lee Date: Thu, 14 Mar 2024 14:49:39 +0900 Subject: [PATCH 249/518] Remove spurious code and satisfy linter --- src/sage_docbuild/conf.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/sage_docbuild/conf.py b/src/sage_docbuild/conf.py index 93a3fd3ad42..cf9269c3cd4 100644 --- a/src/sage_docbuild/conf.py +++ b/src/sage_docbuild/conf.py @@ -336,7 +336,7 @@ def linkcode_resolve(domain, info): for attr in fullname.split('.'): obj = getattr(obj, attr) lineno = sage_getsourcelines(obj)[-1] - except: + except (AttributeError, TypeError, OSError): return None anchor = f'#L{lineno}' else: @@ -345,12 +345,6 @@ def linkcode_resolve(domain, info): return None -class EDIT_LINK(str): - def format(self, filename): - link = os.path.join(source_repository, f'blob/{version}/src/doc/en/reference/{filename}.py') - return link - - # ----------------------- # Options for HTML output # ----------------------- @@ -635,6 +629,7 @@ def add_page_context(app, pagename, templatename, context, doctree): context['page_source_suffix'] = suffix context['theme_source_edit_link'] = os.path.join(source_repository, f'blob/develop/src', '{filename}') + dangling_debug = False def debug_inf(app, message): From df4c0420ec810f5c3535928a3dec08ada4bf178d Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 13 Mar 2024 22:59:03 -0700 Subject: [PATCH 250/518] src/sage/combinat: Doctest cosmetics --- src/sage/combinat/binary_tree.py | 2 +- .../combinat/designs/difference_family.py | 15 ++- src/sage/combinat/free_dendriform_algebra.py | 6 +- src/sage/combinat/interval_posets.py | 71 +++++++---- src/sage/combinat/matrices/latin.py | 6 +- src/sage/combinat/necklace.py | 2 +- src/sage/combinat/partition.py | 11 +- src/sage/combinat/partition_tuple.py | 14 ++- .../combinat/path_tableaux/semistandard.py | 39 ++++-- src/sage/combinat/permutation.py | 29 +++-- src/sage/combinat/plane_partition.py | 11 +- src/sage/combinat/posets/linear_extensions.py | 55 +++++---- src/sage/combinat/posets/poset_examples.py | 13 +- src/sage/combinat/posets/posets.py | 31 +++-- src/sage/combinat/q_bernoulli.pyx | 3 +- src/sage/combinat/schubert_polynomial.py | 8 +- src/sage/combinat/subword_complex.py | 4 +- src/sage/combinat/tableau_tuple.py | 2 +- src/sage/combinat/tiling.py | 113 +++++++++++------- src/sage/combinat/yang_baxter_graph.py | 4 +- 20 files changed, 259 insertions(+), 180 deletions(-) diff --git a/src/sage/combinat/binary_tree.py b/src/sage/combinat/binary_tree.py index 33bcd26ea6a..26e459e13e3 100644 --- a/src/sage/combinat/binary_tree.py +++ b/src/sage/combinat/binary_tree.py @@ -1617,7 +1617,7 @@ def to_tilting(self): sage: t = from_hexacode('2020222002000', BinaryTrees()) sage: print(t.to_tilting()) [(0, 1), (2, 3), (4, 5), (6, 7), (4, 7), (8, 9), (10, 11), - (8, 11), (4, 11), (12, 13), (4, 13), (2, 13), (0, 13)] + (8, 11), (4, 11), (12, 13), (4, 13), (2, 13), (0, 13)] sage: w = DyckWord([1,1,1,1,0,1,1,0,0,0,1,1,0,1,0,1,1,0,1,1,0,0,0,0,0,0]) # needs sage.combinat sage: t2 = w.to_binary_tree() # needs sage.combinat diff --git a/src/sage/combinat/designs/difference_family.py b/src/sage/combinat/designs/difference_family.py index 2b0474f5ed0..8c2b2b0b01c 100644 --- a/src/sage/combinat/designs/difference_family.py +++ b/src/sage/combinat/designs/difference_family.py @@ -3186,11 +3186,12 @@ def difference_family(v, k, l=1, existence=False, explain_construction=False, ch sage: print(designs.difference_family(15,7,3,explain_construction=True)) Singer difference set - sage: print(designs.difference_family(91,10,1,explain_construction=True)) # needs sage.libs.pari + sage: # needs sage.libs.pari + sage: print(designs.difference_family(91,10,1,explain_construction=True)) Singer difference set - sage: print(designs.difference_family(64,28,12, explain_construction=True)) # needs sage.libs.pari + sage: print(designs.difference_family(64,28,12, explain_construction=True)) McFarland 1973 construction - sage: print(designs.difference_family(576, 276, 132, explain_construction=True)) # needs sage.libs.pari + sage: print(designs.difference_family(576, 276, 132, explain_construction=True)) Hadamard difference set product from N1=2 and N2=3 For `k=6,7` we look at the set of small prime powers for which a @@ -3203,9 +3204,10 @@ def difference_family(v, k, l=1, existence=False, explain_construction=False, ch ....: yield k ....: k += m + sage: # needs sage.libs.pari sage: from itertools import islice - sage: l6 = {True:[], False: [], Unknown: []} - sage: for q in islice(prime_power_mod(1,30), int(60)): # needs sage.libs.pari + sage: l6 = {True: [], False: [], Unknown: []} + sage: for q in islice(prime_power_mod(1,30), int(60)): ....: l6[designs.difference_family(q,6,existence=True)].append(q) sage: l6[True] [31, 121, 151, 181, 211, ..., 3061, 3121, 3181] @@ -3214,8 +3216,9 @@ def difference_family(v, k, l=1, existence=False, explain_construction=False, ch sage: l6[False] [] + sage: # needs sage.libs.pari sage: l7 = {True: [], False: [], Unknown: []} - sage: for q in islice(prime_power_mod(1,42), int(60)): # needs sage.libs.pari + sage: for q in islice(prime_power_mod(1,42), int(60)): ....: l7[designs.difference_family(q,7,existence=True)].append(q) sage: l7[True] [169, 337, 379, 421, 463, 547, 631, 673, 757, 841, 883, 967, ..., 4621, 4957, 5167] diff --git a/src/sage/combinat/free_dendriform_algebra.py b/src/sage/combinat/free_dendriform_algebra.py index 2b3ce0ae216..900b9a7ca5d 100644 --- a/src/sage/combinat/free_dendriform_algebra.py +++ b/src/sage/combinat/free_dendriform_algebra.py @@ -87,7 +87,8 @@ class FreeDendriformAlgebra(CombinatorialFreeModule): sage: F = algebras.FreeDendriform(ZZ, 'xyz') sage: x,y,z = F.gens() sage: (x * y) * z - B[x[., y[., z[., .]]]] + B[x[., z[y[., .], .]]] + B[y[x[., .], z[., .]]] + B[z[x[., y[., .]], .]] + B[z[y[x[., .], .], .]] + B[x[., y[., z[., .]]]] + B[x[., z[y[., .], .]]] + B[y[x[., .], z[., .]]] + + B[z[x[., y[., .]], .]] + B[z[y[x[., .], .], .]] The free dendriform algebra is associative:: @@ -114,7 +115,8 @@ class FreeDendriformAlgebra(CombinatorialFreeModule): sage: w = F1.gen(0); w B[[., .]] sage: w * w * w - B[[., [., [., .]]]] + B[[., [[., .], .]]] + B[[[., .], [., .]]] + B[[[., [., .]], .]] + B[[[[., .], .], .]] + B[[., [., [., .]]]] + B[[., [[., .], .]]] + B[[[., .], [., .]]] + + B[[[., [., .]], .]] + B[[[[., .], .], .]] The set `E` can be infinite:: diff --git a/src/sage/combinat/interval_posets.py b/src/sage/combinat/interval_posets.py index 363d5ad39fb..bc0cada6c37 100644 --- a/src/sage/combinat/interval_posets.py +++ b/src/sage/combinat/interval_posets.py @@ -1129,7 +1129,8 @@ def rise_contact_involution(self) -> TIP: EXAMPLES:: - sage: tip = TamariIntervalPoset(8, [(1,2), (2,4), (3,4), (6,7), (3,2), (5,4), (6,4), (8,7)]) + sage: tip = TamariIntervalPoset(8, [(1,2), (2,4), (3,4), (6,7), + ....: (3,2), (5,4), (6,4), (8,7)]) sage: t = tip.rise_contact_involution(); t The Tamari interval of size 8 induced by relations [(2, 8), (3, 8), (4, 5), (5, 7), (6, 7), (7, 8), (8, 1), (7, 2), (6, 2), (5, 3), @@ -1840,13 +1841,16 @@ def lower_binary_tree(self): EXAMPLES:: - sage: ip = TamariIntervalPoset(6,[(3,2),(4,3),(5,2),(6,5),(1,2),(4,5)]); ip - The Tamari interval of size 6 induced by relations [(1, 2), (4, 5), (6, 5), (5, 2), (4, 3), (3, 2)] + sage: ip = TamariIntervalPoset(6, [(3,2),(4,3),(5,2),(6,5),(1,2),(4,5)]); ip + The Tamari interval of size 6 induced by relations + [(1, 2), (4, 5), (6, 5), (5, 2), (4, 3), (3, 2)] sage: ip.lower_binary_tree() [[., .], [[., [., .]], [., .]]] - sage: TamariIntervalPosets.final_forest(ip.lower_binary_tree()) == ip.final_forest() + sage: ff = TamariIntervalPosets.final_forest(ip.lower_binary_tree()) + sage: ff == ip.final_forest() True - sage: ip == TamariIntervalPosets.from_binary_trees(ip.lower_binary_tree(),ip.upper_binary_tree()) + sage: ip == TamariIntervalPosets.from_binary_trees(ip.lower_binary_tree(), + ....: ip.upper_binary_tree()) True """ return self.min_linear_extension().binary_search_tree_shape(left_to_right=False) @@ -1891,9 +1895,11 @@ def upper_binary_tree(self): EXAMPLES:: sage: ip = TamariIntervalPoset(6,[(3,2),(4,3),(5,2),(6,5),(1,2),(4,5)]); ip - The Tamari interval of size 6 induced by relations [(1, 2), (4, 5), (6, 5), (5, 2), (4, 3), (3, 2)] + The Tamari interval of size 6 induced by relations + [(1, 2), (4, 5), (6, 5), (5, 2), (4, 3), (3, 2)] sage: ip.upper_binary_tree() [[., .], [., [[., .], [., .]]]] + sage: TamariIntervalPosets.initial_forest(ip.upper_binary_tree()) == ip.initial_forest() True sage: ip == TamariIntervalPosets.from_binary_trees(ip.lower_binary_tree(),ip.upper_binary_tree()) @@ -1941,8 +1947,9 @@ def subposet(self, start, end) -> TIP: EXAMPLES:: - sage: ip = TamariIntervalPoset(6,[(3,2),(4,3),(5,2),(6,5),(1,2),(3,5),(4,5)]); ip - The Tamari interval of size 6 induced by relations [(1, 2), (3, 5), (4, 5), (6, 5), (5, 2), (4, 3), (3, 2)] + sage: ip = TamariIntervalPoset(6, [(3,2),(4,3),(5,2),(6,5),(1,2),(3,5),(4,5)]); ip + The Tamari interval of size 6 induced by relations + [(1, 2), (3, 5), (4, 5), (6, 5), (5, 2), (4, 3), (3, 2)] sage: ip.subposet(1,3) The Tamari interval of size 2 induced by relations [(1, 2)] sage: ip.subposet(1,4) @@ -2109,12 +2116,16 @@ def lower_contained_intervals(self) -> Iterator[TIP]: EXAMPLES:: - sage: ip = TamariIntervalPoset(4,[(2,4),(3,4),(2,1),(3,1)]) + sage: ip = TamariIntervalPoset(4, [(2,4),(3,4),(2,1),(3,1)]) sage: list(ip.lower_contained_intervals()) - [The Tamari interval of size 4 induced by relations [(2, 4), (3, 4), (3, 1), (2, 1)], - The Tamari interval of size 4 induced by relations [(1, 4), (2, 4), (3, 4), (3, 1), (2, 1)], - The Tamari interval of size 4 induced by relations [(2, 3), (3, 4), (3, 1), (2, 1)], - The Tamari interval of size 4 induced by relations [(1, 4), (2, 3), (3, 4), (3, 1), (2, 1)]] + [The Tamari interval of size 4 induced by relations + [(2, 4), (3, 4), (3, 1), (2, 1)], + The Tamari interval of size 4 induced by relations + [(1, 4), (2, 4), (3, 4), (3, 1), (2, 1)], + The Tamari interval of size 4 induced by relations + [(2, 3), (3, 4), (3, 1), (2, 1)], + The Tamari interval of size 4 induced by relations + [(1, 4), (2, 3), (3, 4), (3, 1), (2, 1)]] sage: ip = TamariIntervalPoset(4,[]) sage: len(list(ip.lower_contained_intervals())) 14 @@ -2256,12 +2267,15 @@ def maximal_chain_tamari_intervals(self) -> Iterator[TIP]: EXAMPLES:: - sage: ip = TamariIntervalPoset(4,[(2,4),(3,4),(2,1),(3,1)]) + sage: ip = TamariIntervalPoset(4, [(2,4),(3,4),(2,1),(3,1)]) sage: list(ip.maximal_chain_tamari_intervals()) - [The Tamari interval of size 4 induced by relations [(2, 4), (3, 4), (3, 1), (2, 1)], - The Tamari interval of size 4 induced by relations [(2, 4), (3, 4), (4, 1), (3, 1), (2, 1)], - The Tamari interval of size 4 induced by relations [(2, 4), (3, 4), (4, 1), (3, 2), (2, 1)]] - sage: ip = TamariIntervalPoset(4,[]) + [The Tamari interval of size 4 induced by relations + [(2, 4), (3, 4), (3, 1), (2, 1)], + The Tamari interval of size 4 induced by relations + [(2, 4), (3, 4), (4, 1), (3, 1), (2, 1)], + The Tamari interval of size 4 induced by relations + [(2, 4), (3, 4), (4, 1), (3, 2), (2, 1)]] + sage: ip = TamariIntervalPoset(4, []) sage: list(ip.maximal_chain_tamari_intervals()) [The Tamari interval of size 4 induced by relations [], The Tamari interval of size 4 induced by relations [(2, 1)], @@ -2405,7 +2419,8 @@ def tamari_inversions_iter(self) -> Iterator[tuple[int, int]]: sage: list(T.tamari_inversions_iter()) [(4, 5)] - sage: T = TamariIntervalPoset(8, [(2, 7), (3, 7), (4, 7), (5, 7), (6, 7), (8, 7), (6, 4), (5, 4), (4, 3), (3, 2)]) + sage: T = TamariIntervalPoset(8, [(2, 7), (3, 7), (4, 7), (5, 7), (6, 7), + ....: (8, 7), (6, 4), (5, 4), (4, 3), (3, 2)]) sage: list(T.tamari_inversions_iter()) [(1, 2), (1, 7), (5, 6)] @@ -2565,12 +2580,14 @@ def decomposition_to_triple(self) -> None | tuple[TIP, TIP, int]: EXAMPLES:: - sage: tip = TamariIntervalPoset(8, [(1,2), (2,4), (3,4), (6,7), (3,2), (5,4), (6,4), (8,7)]) + sage: tip = TamariIntervalPoset(8, [(1,2), (2,4), (3,4), (6,7), + ....: (3,2), (5,4), (6,4), (8,7)]) sage: tip.decomposition_to_triple() (The Tamari interval of size 3 induced by relations [(1, 2), (3, 2)], - The Tamari interval of size 4 induced by relations [(2, 3), (4, 3)], - 2) - sage: tip == TamariIntervalPosets.recomposition_from_triple(*tip.decomposition_to_triple()) + The Tamari interval of size 4 induced by relations [(2, 3), (4, 3)], + 2) + sage: tip == TamariIntervalPosets.recomposition_from_triple( + ....: *tip.decomposition_to_triple()) True TESTS:: @@ -2600,7 +2617,8 @@ def grafting_tree(self) -> LabelledBinaryTree: EXAMPLES:: - sage: tip = TamariIntervalPoset(8, [(1,2), (2,4), (3,4), (6,7), (3,2), (5,4), (6,4), (8,7)]) + sage: tip = TamariIntervalPoset(8, [(1,2), (2,4), (3,4), (6,7), + ....: (3,2), (5,4), (6,4), (8,7)]) sage: tip.grafting_tree() 2[1[0[., .], 0[., .]], 0[., 1[0[., .], 0[., .]]]] sage: tip == TamariIntervalPosets.from_grafting_tree(tip.grafting_tree()) @@ -2906,7 +2924,7 @@ class options(GlobalOptions): sage: TIP = TamariIntervalPosets sage: TIP.options.latex_color_decreasing red - sage: TIP.options.latex_color_decreasing='green' + sage: TIP.options.latex_color_decreasing = 'green' sage: TIP.options.latex_color_decreasing green sage: TIP.options._reset() @@ -3243,7 +3261,8 @@ def from_binary_trees(tree1, tree2) -> TIP: sage: tree1 = BinaryTree([[],[[None,[]],[]]]) sage: tree2 = BinaryTree([None,[None,[None,[[],[]]]]]) sage: TamariIntervalPosets.from_binary_trees(tree1,tree2) - The Tamari interval of size 6 induced by relations [(4, 5), (6, 5), (5, 2), (4, 3), (3, 2)] + The Tamari interval of size 6 induced by relations + [(4, 5), (6, 5), (5, 2), (4, 3), (3, 2)] sage: tree3 = BinaryTree([None,[None,[[],[None,[]]]]]) sage: TamariIntervalPosets.from_binary_trees(tree1,tree3) diff --git a/src/sage/combinat/matrices/latin.py b/src/sage/combinat/matrices/latin.py index 62051eeae57..cba9aefa694 100644 --- a/src/sage/combinat/matrices/latin.py +++ b/src/sage/combinat/matrices/latin.py @@ -41,8 +41,8 @@ #. some named latin squares (back circulant, forward circulant, abelian `2`-group); -#. functions is\_partial\_latin\_square and is\_latin\_square to test - if a LatinSquare object satisfies the definition of a latin square +#. methods :meth:`is_partial_latin_square` and :meth:`is_latin_square` to test + if a :class:`LatinSquare` object satisfies the definition of a latin square or partial latin square, respectively; #. tests for completion and unique completion (these use the C++ @@ -57,7 +57,7 @@ #. a few examples of `\tau_i` representations of bitrades constructed from the action of a group on itself by right multiplication, - functions for converting to a pair of LatinSquare objects. + functions for converting to a pair of :class:`LatinSquare` objects. EXAMPLES:: diff --git a/src/sage/combinat/necklace.py b/src/sage/combinat/necklace.py index e39e04aac0f..54a3dc992f0 100644 --- a/src/sage/combinat/necklace.py +++ b/src/sage/combinat/necklace.py @@ -210,7 +210,7 @@ def cardinality(self) -> Integer: :: - sage: comps = [[],[2,2],[3,2,7],[4,2],[0,4,2],[2,0,4]]+Compositions(4).list() + sage: comps = [[],[2,2],[3,2,7],[4,2],[0,4,2],[2,0,4]] + Compositions(4).list() sage: ns = [Necklaces(comp) for comp in comps] sage: all(n.cardinality() == len(n.list()) for n in ns) # needs sage.libs.pari True diff --git a/src/sage/combinat/partition.py b/src/sage/combinat/partition.py index 3ff9bb2f1d6..7907794f5f4 100644 --- a/src/sage/combinat/partition.py +++ b/src/sage/combinat/partition.py @@ -5197,7 +5197,8 @@ def dimension(self, smaller=None, k=1): Checks that the sum of squares of dimensions of characters of the symmetric group is the order of the group:: - sage: all(sum(mu.dimension()^2 for mu in Partitions(i))==factorial(i) for i in range(10)) + sage: all(sum(mu.dimension()^2 for mu in Partitions(i)) == factorial(i) + ....: for i in range(10)) True A check coming from the theory of `k`-differentiable posets:: @@ -5699,14 +5700,14 @@ class Partitions(UniqueRepresentation, Parent): Here are some more examples illustrating ``min_part``, ``max_part``, and ``length``:: - sage: Partitions(5,min_part=2) + sage: Partitions(5, min_part=2) Partitions of the integer 5 satisfying constraints min_part=2 - sage: Partitions(5,min_part=2).list() + sage: Partitions(5, min_part=2).list() [[5], [3, 2]] :: - sage: Partitions(3,max_length=2).list() + sage: Partitions(3, max_length=2).list() [[3], [2, 1]] :: @@ -5819,7 +5820,7 @@ class Partitions(UniqueRepresentation, Parent): ... ValueError: the size must be specified with any keyword argument - sage: Partitions(max_part = 3) + sage: Partitions(max_part=3) 3-Bounded Partitions Check that :issue:`14145` has been fixed:: diff --git a/src/sage/combinat/partition_tuple.py b/src/sage/combinat/partition_tuple.py index 6dfc70d475d..182c0b8813d 100644 --- a/src/sage/combinat/partition_tuple.py +++ b/src/sage/combinat/partition_tuple.py @@ -155,7 +155,7 @@ class of modules for the algebras, which are generalisations of the Specht * ** * * * * - sage: lam=PartitionTuples(3)([[3,2],[],[1,1,1,1]]); lam + sage: lam = PartitionTuples(3)([[3,2],[],[1,1,1,1]]); lam ([3, 2], [], [1, 1, 1, 1]) sage: lam.level() 3 @@ -200,7 +200,7 @@ class of modules for the algebras, which are generalisations of the Specht Every partition tuple behaves every much like a tuple of partitions:: - sage: mu=PartitionTuple([[4,1],[],[2,2,1],[3]]) + sage: mu = PartitionTuple([[4,1],[],[2,2,1],[3]]) sage: [ nu for nu in mu ] [[4, 1], [], [2, 2, 1], [3]] sage: Set([ type(nu) for nu in mu ]) @@ -228,9 +228,11 @@ class of modules for the algebras, which are generalisations of the Specht sage: len(mu) 4 sage: mu.cells() - [(0, 0, 0), (0, 0, 1), (0, 0, 2), (0, 0, 3), (0, 1, 0), (2, 0, 0), (2, 0, 1), (2, 1, 0), (2, 1, 1), (2, 2, 0), (3, 0, 0), (3, 0, 1), (3, 0, 2)] + [(0, 0, 0), (0, 0, 1), (0, 0, 2), (0, 0, 3), (0, 1, 0), (2, 0, 0), (2, 0, 1), + (2, 1, 0), (2, 1, 1), (2, 2, 0), (3, 0, 0), (3, 0, 1), (3, 0, 2)] sage: mu.addable_cells() - [(0, 0, 4), (0, 1, 1), (0, 2, 0), (1, 0, 0), (2, 0, 2), (2, 2, 1), (2, 3, 0), (3, 0, 3), (3, 1, 0)] + [(0, 0, 4), (0, 1, 1), (0, 2, 0), (1, 0, 0), (2, 0, 2), (2, 2, 1), + (2, 3, 0), (3, 0, 3), (3, 1, 0)] sage: mu.removable_cells() [(0, 0, 3), (0, 1, 0), (2, 1, 1), (2, 2, 0), (3, 0, 2)] @@ -2376,7 +2378,7 @@ def _repr_(self): sage: PartitionTuples(4,2) Partition tuples of level 4 and size 2 - sage: PartitionTuples(size=2,level=4) + sage: PartitionTuples(size=2, level=4) Partition tuples of level 4 and size 2 """ return 'Partition tuples of level {} and size {}'.format(self._level, self._size) @@ -3037,7 +3039,7 @@ def _repr_(self): (2, 1, 4)-Regular partition tuples of level 3 and size 7 sage: PartitionTuples(4,2,3) 3-Regular partition tuples of level 4 and size 2 - sage: PartitionTuples(size=2,level=4,regular=3) + sage: PartitionTuples(size=2, level=4, regular=3) 3-Regular partition tuples of level 4 and size 2 """ if self._ell[1:] == self._ell[:-1]: diff --git a/src/sage/combinat/path_tableaux/semistandard.py b/src/sage/combinat/path_tableaux/semistandard.py index 559571e798a..f6703b32afe 100644 --- a/src/sage/combinat/path_tableaux/semistandard.py +++ b/src/sage/combinat/path_tableaux/semistandard.py @@ -12,13 +12,15 @@ EXAMPLES:: - sage: pt = path_tableaux.SemistandardPathTableau([[],[3],[3,2],[3,3,1],[3,3,2,1],[4,3,3,1,0]]) + sage: pt = path_tableaux.SemistandardPathTableau([[],[3],[3,2],[3,3,1], + ....: [3,3,2,1],[4,3,3,1,0]]) sage: pt.promotion() [(), (2,), (3, 1), (3, 2, 1), (4, 3, 1, 0), (4, 3, 3, 1, 0)] sage: pt.evacuation() [(), (2,), (4, 0), (4, 2, 0), (4, 3, 1, 0), (4, 3, 3, 1, 0)] - sage: pt = path_tableaux.SemistandardPathTableau([[],[3],[3,2],[3,3,1],[3,3,2,1],[9/2,3,3,1,0]]) + sage: pt = path_tableaux.SemistandardPathTableau([[],[3],[3,2],[3,3,1], + ....: [3,3,2,1],[9/2,3,3,1,0]]) sage: pt.promotion() [(), (2,), (3, 1), (3, 2, 1), (9/2, 3, 1, 0), (9/2, 3, 3, 1, 0)] sage: pt.evacuation() @@ -31,7 +33,8 @@ [ , , (), (4,), (4, 3), (5, 4, 1)] [ , , , (), (3,), (5, 1), (5, 4, 1)] - sage: pt2 = path_tableaux.SemistandardPathTableau([[3,2],[3,3,1],[3,3,2,1],[4,3,3,1,0]]) + sage: pt2 = path_tableaux.SemistandardPathTableau([[3,2],[3,3,1], + ....: [3,3,2,1],[4,3,3,1,0]]) sage: pt1 = path_tableaux.SemistandardPathTableau([[],[3],[3,2]]) sage: pt1.commutor(pt2) ([(), (2,), (2, 2), (4, 2, 0)], [(4, 2, 0), (4, 3, 2, 0), (4, 3, 3, 1, 0)]) @@ -41,7 +44,9 @@ [(), (2,), (2, 2), (4, 2, 0)] ([(), (2,), (2, 2), (4, 2, 0)], [(4, 2, 0), (4, 3, 2, 0), (4, 3, 3, 1, 0)]) - sage: st = SkewTableau([[None,None,None,4,4,5,6,7],[None,2,4,6,7,7,7],[None,4,5,8,8,9],[None,6,7,10],[None,8,8,11],[None],[4]]) + sage: st = SkewTableau([[None, None, None, 4, 4, 5, 6, 7], [None, 2, 4, 6, 7, 7, 7], + ....: [None, 4, 5, 8, 8, 9], [None, 6, 7, 10], [None, 8, 8, 11], + ....: [None], [4]]) sage: pt = path_tableaux.SemistandardPathTableau(st) sage: bk = [SkewTableau(st.bender_knuth_involution(i+1)) for i in range(10)] sage: lr = [pt.local_rule(i+1) for i in range(10)] @@ -198,12 +203,14 @@ def check(self): sage: path_tableaux.SemistandardPathTableau([[],[3],[2,2]]) # indirect test Traceback (most recent call last): ... - ValueError: [(), (3,), (2, 2)] does not satisfy the required inequalities in row 1 + ValueError: [(), (3,), (2, 2)] does not satisfy + the required inequalities in row 1 sage: path_tableaux.SemistandardPathTableau([[],[3/2],[2,5/2]]) # indirect test Traceback (most recent call last): ... - ValueError: [(), (3/2,), (2, 5/2)] does not satisfy the required inequalities in row 1 + ValueError: [(), (3/2,), (2, 5/2)] does not satisfy + the required inequalities in row 1 TESTS:: @@ -325,14 +332,18 @@ def rectify(self, inner=None, verbose=False): EXAMPLES:: - sage: st = SkewTableau([[None, None, None, 4],[None,None,1,6],[None,None,5],[2,3]]) + sage: st = SkewTableau([[None, None, None, 4], [None, None, 1, 6], + ....: [None, None, 5], [2, 3]]) sage: path_tableaux.SemistandardPathTableau(st).rectify() [(), (1,), (1, 1), (2, 1, 0), (3, 1, 0, 0), (3, 2, 0, 0, 0), (4, 2, 0, 0, 0, 0)] sage: path_tableaux.SemistandardPathTableau(st).rectify(verbose=True) - [[(3, 2, 2), (3, 3, 2, 0), (3, 3, 2, 1, 0), (3, 3, 2, 2, 0, 0), (4, 3, 2, 2, 0, 0, 0), (4, 3, 3, 2, 0, 0, 0, 0), (4, 4, 3, 2, 0, 0, 0, 0, 0)], - [(3, 2), (3, 3, 0), (3, 3, 1, 0), (3, 3, 2, 0, 0), (4, 3, 2, 0, 0, 0), (4, 3, 3, 0, 0, 0, 0), (4, 4, 3, 0, 0, 0, 0, 0)], - [(3,), (3, 1), (3, 1, 1), (3, 2, 1, 0), (4, 2, 1, 0, 0), (4, 3, 1, 0, 0, 0), (4, 4, 1, 0, 0, 0, 0)], - [(), (1,), (1, 1), (2, 1, 0), (3, 1, 0, 0), (3, 2, 0, 0, 0), (4, 2, 0, 0, 0, 0)]] + [[(3, 2, 2), (3, 3, 2, 0), (3, 3, 2, 1, 0), (3, 3, 2, 2, 0, 0), + (4, 3, 2, 2, 0, 0, 0), (4, 3, 3, 2, 0, 0, 0, 0), (4, 4, 3, 2, 0, 0, 0, 0, 0)], + [(3, 2), (3, 3, 0), (3, 3, 1, 0), (3, 3, 2, 0, 0), (4, 3, 2, 0, 0, 0), + (4, 3, 3, 0, 0, 0, 0), (4, 4, 3, 0, 0, 0, 0, 0)], + [(3,), (3, 1), (3, 1, 1), (3, 2, 1, 0), (4, 2, 1, 0, 0), (4, 3, 1, 0, 0, 0), + (4, 4, 1, 0, 0, 0, 0)], + [(), (1,), (1, 1), (2, 1, 0), (3, 1, 0, 0), (3, 2, 0, 0, 0), (4, 2, 0, 0, 0, 0)]] TESTS:: @@ -387,7 +398,8 @@ def to_tableau(self): EXAMPLES:: - sage: pt = path_tableaux.SemistandardPathTableau([[],[3],[3,2],[3,3,1],[3,3,2,1],[4,3,3,1,0]]) + sage: pt = path_tableaux.SemistandardPathTableau([[],[3],[3,2],[3,3,1], + ....: [3,3,2,1],[4,3,3,1,0]]) sage: pt.to_tableau() [[1, 1, 1, 5], [2, 2, 3], [3, 4, 5], [4]] @@ -416,7 +428,8 @@ def to_pattern(self): EXAMPLES:: - sage: pt = path_tableaux.SemistandardPathTableau([[],[3],[3,2],[3,3,1],[3,3,2,1],[4,3,3,1]]) + sage: pt = path_tableaux.SemistandardPathTableau([[],[3],[3,2],[3,3,1], + ....: [3,3,2,1],[4,3,3,1]]) sage: pt.to_pattern() [[4, 3, 3, 1, 0], [3, 3, 2, 1], [3, 3, 1], [3, 2], [3]] diff --git a/src/sage/combinat/permutation.py b/src/sage/combinat/permutation.py index 8152ed81a05..d859e5c5b37 100644 --- a/src/sage/combinat/permutation.py +++ b/src/sage/combinat/permutation.py @@ -5390,20 +5390,23 @@ def shifted_shuffle(self, other): def nth_roots(self, n): r""" - Return all n-th roots of ``self`` (as a generator). + Return all `n`-th roots of ``self`` (as a generator). - An n-th root of the permutation `\sigma` is a permutation `\gamma` such that `\gamma^n = \sigma`. + An `n`-th root of the permutation `\sigma` is a permutation `\gamma` such that `\gamma^n = \sigma`. - Note that the number of n-th roots only depends on the cycle type of ``self``. + Note that the number of `n`-th roots only depends on the cycle type of ``self``. EXAMPLES:: + sage: # needs sage.combinat sage: sigma = Permutations(5).identity() sage: list(sigma.nth_roots(3)) - [[1, 4, 3, 5, 2], [1, 5, 3, 2, 4], [1, 2, 4, 5, 3], [1, 2, 5, 3, 4], [4, 2, 3, 5, 1], [5, 2, 3, 1, 4], [3, 2, 5, 4, 1], - [5, 2, 1, 4, 3], [2, 5, 3, 4, 1], [5, 1, 3, 4, 2], [2, 3, 1, 4, 5], [3, 1, 2, 4, 5], [2, 4, 3, 1, 5], [4, 1, 3, 2, 5], - [3, 2, 4, 1, 5], [4, 2, 1, 3, 5], [1, 3, 4, 2, 5], [1, 4, 2, 3, 5], [1, 3, 5, 4, 2], [1, 5, 2, 4, 3], [1, 2, 3, 4, 5]] - + [[1, 4, 3, 5, 2], [1, 5, 3, 2, 4], [1, 2, 4, 5, 3], [1, 2, 5, 3, 4], + [4, 2, 3, 5, 1], [5, 2, 3, 1, 4], [3, 2, 5, 4, 1], [5, 2, 1, 4, 3], + [2, 5, 3, 4, 1], [5, 1, 3, 4, 2], [2, 3, 1, 4, 5], [3, 1, 2, 4, 5], + [2, 4, 3, 1, 5], [4, 1, 3, 2, 5], [3, 2, 4, 1, 5], [4, 2, 1, 3, 5], + [1, 3, 4, 2, 5], [1, 4, 2, 3, 5], [1, 3, 5, 4, 2], [1, 5, 2, 4, 3], + [1, 2, 3, 4, 5]] sage: sigma = Permutation('(1, 3)') sage: list(sigma.nth_roots(2)) [] @@ -5508,11 +5511,11 @@ def rewind(L, n): def has_nth_root(self, n) -> bool: r""" - Decide if ``self`` has n-th roots. + Decide if ``self`` has `n`-th roots. - An n-th root of the permutation `\sigma` is a permutation `\gamma` such that `\gamma^n = \sigma`. + An `n`-th root of the permutation `\sigma` is a permutation `\gamma` such that `\gamma^n = \sigma`. - Note that the number of n-th roots only depends on the cycle type of ``self``. + Note that the number of `n`-th roots only depends on the cycle type of ``self``. EXAMPLES:: @@ -5569,11 +5572,11 @@ def has_nth_root(self, n) -> bool: def number_of_nth_roots(self, n): r""" - Return the number of n-th roots of ``self``. + Return the number of `n`-th roots of ``self``. - An n-th root of the permutation `\sigma` is a permutation `\gamma` such that `\gamma^n = \sigma`. + An `n`-th root of the permutation `\sigma` is a permutation `\gamma` such that `\gamma^n = \sigma`. - Note that the number of n-th roots only depends on the cycle type of ``self``. + Note that the number of `n`-th roots only depends on the cycle type of ``self``. EXAMPLES:: diff --git a/src/sage/combinat/plane_partition.py b/src/sage/combinat/plane_partition.py index b4f897b64f3..392acbca718 100644 --- a/src/sage/combinat/plane_partition.py +++ b/src/sage/combinat/plane_partition.py @@ -1999,9 +1999,9 @@ def __iter__(self) -> Iterator: sage: list(PlanePartitions([2,2,1], symmetry='SPP')) # needs sage.graphs sage.modules sage.rings.finite_rings [Plane partition [], - Plane partition [[1, 1], [1, 1]], - Plane partition [[1, 1], [1]], - Plane partition [[1]]] + Plane partition [[1, 1], [1, 1]], + Plane partition [[1, 1], [1]], + Plane partition [[1]]] TESTS:: @@ -3164,7 +3164,8 @@ def from_antichain(self, acl) -> PP: sage: PP = PlanePartitions([6,6,6], symmetry='TSSCPP') sage: A = [(0, 0, 1), (1, 1, 0)] sage: PP.from_antichain(A) - Plane partition [[6, 6, 6, 5, 5, 3], [6, 5, 5, 4, 3, 1], [6, 5, 4, 3, 2, 1], [5, 4, 3, 2, 1], [5, 3, 2, 1, 1], [3, 1, 1]] + Plane partition [[6, 6, 6, 5, 5, 3], [6, 5, 5, 4, 3, 1], [6, 5, 4, 3, 2, 1], + [5, 4, 3, 2, 1], [5, 3, 2, 1, 1], [3, 1, 1]] """ # ac format ex: [x,y,z] a = self._box[0] @@ -3271,7 +3272,7 @@ def __iter__(self) -> Iterator: sage: list(PlanePartitions([4,4,4], symmetry='TSSCPP')) # needs sage.graphs sage.modules [Plane partition [[4, 4, 2, 2], [4, 4, 2, 2], [2, 2], [2, 2]], - Plane partition [[4, 4, 3, 2], [4, 3, 2, 1], [3, 2, 1], [2, 1]]] + Plane partition [[4, 4, 3, 2], [4, 3, 2, 1], [3, 2, 1], [2, 1]]] TESTS:: diff --git a/src/sage/combinat/posets/linear_extensions.py b/src/sage/combinat/posets/linear_extensions.py index 8c430e03d41..640dff1e708 100644 --- a/src/sage/combinat/posets/linear_extensions.py +++ b/src/sage/combinat/posets/linear_extensions.py @@ -57,11 +57,13 @@ class LinearExtensionOfPoset(ClonableArray, EXAMPLES:: - sage: P = Poset(([1,2,3,4], [[1,3],[1,4],[2,3]]), linear_extension=True, facade=False) + sage: P = Poset(([1,2,3,4], [[1,3],[1,4],[2,3]]), + ....: linear_extension=True, facade=False) sage: p = P.linear_extension([1,4,2,3]); p [1, 4, 2, 3] sage: p.parent() - The set of all linear extensions of Finite poset containing 4 elements with distinguished linear extension + The set of all linear extensions of + Finite poset containing 4 elements with distinguished linear extension sage: p[0], p[1], p[2], p[3] (1, 4, 2, 3) @@ -485,7 +487,8 @@ class LinearExtensionsOfPoset(UniqueRepresentation, Parent): sage: rels = [[1,3],[1,4],[2,3]] sage: P = Poset((elms, rels), linear_extension=True) sage: L = P.linear_extensions(); L - The set of all linear extensions of Finite poset containing 4 elements with distinguished linear extension + The set of all linear extensions of + Finite poset containing 4 elements with distinguished linear extension sage: L.cardinality() 5 sage: L.list() # optional - sage.modules sage.rings.finite_rings @@ -725,7 +728,7 @@ def markov_chain_digraph(self, action='promotion', labeling='identity'): EXAMPLES:: - sage: P = Poset(([1,2,3,4], [[1,3],[1,4],[2,3]]), linear_extension = True) + sage: P = Poset(([1,2,3,4], [[1,3],[1,4],[2,3]]), linear_extension=True) sage: L = P.linear_extensions() sage: G = L.markov_chain_digraph(); G Looped multi-digraph on 5 vertices @@ -733,24 +736,24 @@ def markov_chain_digraph(self, action='promotion', labeling='identity'): [[1, 2, 3, 4], [1, 2, 4, 3], [1, 4, 2, 3], [2, 1, 3, 4], [2, 1, 4, 3]] sage: G.edges(sort=True, key=repr) [([1, 2, 3, 4], [1, 2, 3, 4], 4), ([1, 2, 3, 4], [1, 2, 4, 3], 2), ([1, 2, 3, 4], [1, 2, 4, 3], 3), - ([1, 2, 3, 4], [2, 1, 4, 3], 1), ([1, 2, 4, 3], [1, 2, 3, 4], 3), ([1, 2, 4, 3], [1, 2, 4, 3], 4), - ([1, 2, 4, 3], [1, 4, 2, 3], 2), ([1, 2, 4, 3], [2, 1, 3, 4], 1), ([1, 4, 2, 3], [1, 2, 3, 4], 1), - ([1, 4, 2, 3], [1, 2, 3, 4], 2), ([1, 4, 2, 3], [1, 4, 2, 3], 3), ([1, 4, 2, 3], [1, 4, 2, 3], 4), - ([2, 1, 3, 4], [1, 2, 4, 3], 1), ([2, 1, 3, 4], [2, 1, 3, 4], 4), ([2, 1, 3, 4], [2, 1, 4, 3], 2), - ([2, 1, 3, 4], [2, 1, 4, 3], 3), ([2, 1, 4, 3], [1, 4, 2, 3], 1), ([2, 1, 4, 3], [2, 1, 3, 4], 2), - ([2, 1, 4, 3], [2, 1, 3, 4], 3), ([2, 1, 4, 3], [2, 1, 4, 3], 4)] - - sage: G = L.markov_chain_digraph(labeling = 'source') + ([1, 2, 3, 4], [2, 1, 4, 3], 1), ([1, 2, 4, 3], [1, 2, 3, 4], 3), ([1, 2, 4, 3], [1, 2, 4, 3], 4), + ([1, 2, 4, 3], [1, 4, 2, 3], 2), ([1, 2, 4, 3], [2, 1, 3, 4], 1), ([1, 4, 2, 3], [1, 2, 3, 4], 1), + ([1, 4, 2, 3], [1, 2, 3, 4], 2), ([1, 4, 2, 3], [1, 4, 2, 3], 3), ([1, 4, 2, 3], [1, 4, 2, 3], 4), + ([2, 1, 3, 4], [1, 2, 4, 3], 1), ([2, 1, 3, 4], [2, 1, 3, 4], 4), ([2, 1, 3, 4], [2, 1, 4, 3], 2), + ([2, 1, 3, 4], [2, 1, 4, 3], 3), ([2, 1, 4, 3], [1, 4, 2, 3], 1), ([2, 1, 4, 3], [2, 1, 3, 4], 2), + ([2, 1, 4, 3], [2, 1, 3, 4], 3), ([2, 1, 4, 3], [2, 1, 4, 3], 4)] + + sage: G = L.markov_chain_digraph(labeling='source') sage: G.vertices(sort=True, key=repr) [[1, 2, 3, 4], [1, 2, 4, 3], [1, 4, 2, 3], [2, 1, 3, 4], [2, 1, 4, 3]] sage: G.edges(sort=True, key=repr) [([1, 2, 3, 4], [1, 2, 3, 4], 4), ([1, 2, 3, 4], [1, 2, 4, 3], 2), ([1, 2, 3, 4], [1, 2, 4, 3], 3), - ([1, 2, 3, 4], [2, 1, 4, 3], 1), ([1, 2, 4, 3], [1, 2, 3, 4], 4), ([1, 2, 4, 3], [1, 2, 4, 3], 3), - ([1, 2, 4, 3], [1, 4, 2, 3], 2), ([1, 2, 4, 3], [2, 1, 3, 4], 1), ([1, 4, 2, 3], [1, 2, 3, 4], 1), - ([1, 4, 2, 3], [1, 2, 3, 4], 4), ([1, 4, 2, 3], [1, 4, 2, 3], 2), ([1, 4, 2, 3], [1, 4, 2, 3], 3), - ([2, 1, 3, 4], [1, 2, 4, 3], 2), ([2, 1, 3, 4], [2, 1, 3, 4], 4), ([2, 1, 3, 4], [2, 1, 4, 3], 1), - ([2, 1, 3, 4], [2, 1, 4, 3], 3), ([2, 1, 4, 3], [1, 4, 2, 3], 2), ([2, 1, 4, 3], [2, 1, 3, 4], 1), - ([2, 1, 4, 3], [2, 1, 3, 4], 4), ([2, 1, 4, 3], [2, 1, 4, 3], 3)] + ([1, 2, 3, 4], [2, 1, 4, 3], 1), ([1, 2, 4, 3], [1, 2, 3, 4], 4), ([1, 2, 4, 3], [1, 2, 4, 3], 3), + ([1, 2, 4, 3], [1, 4, 2, 3], 2), ([1, 2, 4, 3], [2, 1, 3, 4], 1), ([1, 4, 2, 3], [1, 2, 3, 4], 1), + ([1, 4, 2, 3], [1, 2, 3, 4], 4), ([1, 4, 2, 3], [1, 4, 2, 3], 2), ([1, 4, 2, 3], [1, 4, 2, 3], 3), + ([2, 1, 3, 4], [1, 2, 4, 3], 2), ([2, 1, 3, 4], [2, 1, 3, 4], 4), ([2, 1, 3, 4], [2, 1, 4, 3], 1), + ([2, 1, 3, 4], [2, 1, 4, 3], 3), ([2, 1, 4, 3], [1, 4, 2, 3], 2), ([2, 1, 4, 3], [2, 1, 3, 4], 1), + ([2, 1, 4, 3], [2, 1, 3, 4], 4), ([2, 1, 4, 3], [2, 1, 4, 3], 3)] The edges of the graph are by default colored using blue for edge 1, red for edge 2, green for edge 3, and yellow for edge 4:: @@ -765,19 +768,19 @@ def markov_chain_digraph(self, action='promotion', labeling='identity'): [[1, 2, 3, 4], [1, 2, 4, 3], [1, 4, 2, 3], [2, 1, 3, 4], [2, 1, 4, 3]] sage: G.edges(sort=True, key=repr) [([1, 2, 3, 4], [1, 2, 3, 4], 2), ([1, 2, 3, 4], [1, 2, 4, 3], 3), ([1, 2, 3, 4], [2, 1, 3, 4], 1), - ([1, 2, 4, 3], [1, 2, 3, 4], 3), ([1, 2, 4, 3], [1, 4, 2, 3], 2), ([1, 2, 4, 3], [2, 1, 4, 3], 1), - ([1, 4, 2, 3], [1, 2, 4, 3], 2), ([1, 4, 2, 3], [1, 4, 2, 3], 1), ([1, 4, 2, 3], [1, 4, 2, 3], 3), - ([2, 1, 3, 4], [1, 2, 3, 4], 1), ([2, 1, 3, 4], [2, 1, 3, 4], 2), ([2, 1, 3, 4], [2, 1, 4, 3], 3), - ([2, 1, 4, 3], [1, 2, 4, 3], 1), ([2, 1, 4, 3], [2, 1, 3, 4], 3), ([2, 1, 4, 3], [2, 1, 4, 3], 2)] + ([1, 2, 4, 3], [1, 2, 3, 4], 3), ([1, 2, 4, 3], [1, 4, 2, 3], 2), ([1, 2, 4, 3], [2, 1, 4, 3], 1), + ([1, 4, 2, 3], [1, 2, 4, 3], 2), ([1, 4, 2, 3], [1, 4, 2, 3], 1), ([1, 4, 2, 3], [1, 4, 2, 3], 3), + ([2, 1, 3, 4], [1, 2, 3, 4], 1), ([2, 1, 3, 4], [2, 1, 3, 4], 2), ([2, 1, 3, 4], [2, 1, 4, 3], 3), + ([2, 1, 4, 3], [1, 2, 4, 3], 1), ([2, 1, 4, 3], [2, 1, 3, 4], 3), ([2, 1, 4, 3], [2, 1, 4, 3], 2)] sage: view(G) # optional - dot2tex graphviz, not tested (opens external window) .. SEEALSO:: :meth:`markov_chain_transition_matrix`, :meth:`promotion`, :meth:`tau` TESTS:: - sage: P = Poset(([1,2,3,4], [[1,3],[1,4],[2,3]]), linear_extension = True, facade = True) + sage: P = Poset(([1,2,3,4], [[1,3],[1,4],[2,3]]), linear_extension=True, facade=True) sage: L = P.linear_extensions() - sage: G = L.markov_chain_digraph(labeling = 'source'); G + sage: G = L.markov_chain_digraph(labeling='source'); G Looped multi-digraph on 5 vertices """ L = sorted(self) @@ -809,8 +812,8 @@ def markov_chain_transition_matrix(self, action='promotion', labeling='identity' INPUT: - - ``action`` -- 'promotion' or 'tau' (default: 'promotion') - - ``labeling`` -- 'identity' or 'source' (default: 'identity') + - ``action`` -- ``'promotion'`` or ``'tau'`` (default: ``'promotion'``) + - ``labeling`` -- ``'identity'`` or ``'source'`` (default: ``'identity'``) This method yields the transition matrix of the Markov chain defined by the action of the generalized promotion operator `\partial_i` (resp. `\tau_i`) on the set of linear extensions of a finite poset. diff --git a/src/sage/combinat/posets/poset_examples.py b/src/sage/combinat/posets/poset_examples.py index b8b18784b58..422a6f542e5 100644 --- a/src/sage/combinat/posets/poset_examples.py +++ b/src/sage/combinat/posets/poset_examples.py @@ -969,9 +969,9 @@ def SSTPoset(s, f=None): INPUT: - - ``s`` - shape of the tableaux + - ``s`` -- shape of the tableaux - - ``f`` - maximum fill number. This is an optional + - ``f`` -- maximum fill number. This is an optional argument. If no maximal number is given, it will use the number of cells in the shape. @@ -1199,7 +1199,8 @@ def TetrahedralPoset(n, *colors, **labels): sage: posets.TetrahedralPoset(4,'green','red','yellow','silver','blue','orange') Finite poset containing 10 elements - sage: posets.TetrahedralPoset(4,'green','red','yellow','silver','blue','orange', labels='integers') + sage: posets.TetrahedralPoset(4,'green','red','yellow','silver','blue','orange', + ....: labels='integers') Finite poset containing 10 elements sage: A = AlternatingSignMatrices(3) @@ -1819,9 +1820,9 @@ def MobilePoset(ribbon, hangers, anchor=None): 7 sage: P = posets.MobilePoset(posets.RibbonPoset(7, [1,3]), - ....: {1: [posets.YoungDiagramPoset([3, 2], dual=True)], - ....: 3: [posets.DoubleTailedDiamond(6)]}, - ....: anchor=(4, 2, posets.ChainPoset(6))) + ....: {1: [posets.YoungDiagramPoset([3, 2], dual=True)], + ....: 3: [posets.DoubleTailedDiamond(6)]}, + ....: anchor=(4, 2, posets.ChainPoset(6))) sage: len(P.cover_relations()) 33 """ diff --git a/src/sage/combinat/posets/posets.py b/src/sage/combinat/posets/posets.py index 5dfa5849ad9..dda0d314e52 100644 --- a/src/sage/combinat/posets/posets.py +++ b/src/sage/combinat/posets/posets.py @@ -1604,7 +1604,8 @@ def linear_extensions(self, facade=False): sage: P = Poset((divisors(12), attrcall("divides")), linear_extension=True) sage: L = P.linear_extensions(facade=True); L - The set of all linear extensions of Finite poset containing 6 elements with distinguished linear extension + The set of all linear extensions of + Finite poset containing 6 elements with distinguished linear extension sage: L([1, 2, 3, 4, 6, 12]) Traceback (most recent call last): ... @@ -1616,7 +1617,8 @@ def linear_extensions(self, facade=False): sage: P.list() [1, 2, 3, 4, 6, 12] sage: L = P.linear_extensions(); L - The set of all linear extensions of Finite poset containing 6 elements with distinguished linear extension + The set of all linear extensions of + Finite poset containing 6 elements with distinguished linear extension sage: l = L.an_element(); l [1, 2, 3, 4, 6, 12] sage: L.cardinality() @@ -4968,7 +4970,7 @@ def chains(self, element_constructor=None, exclude=None): Eventually the following syntax will be accepted:: - sage: C.subset(size = 2) # not implemented + sage: C.subset(size=2) # not implemented .. SEEALSO:: :meth:`maximal_chains`, :meth:`antichains` """ @@ -5162,7 +5164,7 @@ def product(self, other): sage: Poset({0: [1]}).product(Poset()) # Product with empty poset Finite poset containing 0 elements - sage: Poset().product(Poset()) # Product of two empty poset + sage: Poset().product(Poset()) # Product of two empty posets Finite poset containing 0 elements We check that :issue:`19113` is fixed:: @@ -5262,7 +5264,7 @@ def factor(self): sage: Q = P*P sage: Q.factor() [Finite poset containing 5 elements, - Finite poset containing 5 elements] + Finite poset containing 5 elements] sage: P1 = posets.ChainPoset(3) sage: P2 = posets.ChainPoset(7) @@ -5270,7 +5272,7 @@ def factor(self): [Finite lattice containing 3 elements] sage: (P1 * P2).factor() [Finite poset containing 7 elements, - Finite poset containing 3 elements] + Finite poset containing 3 elements] sage: P = posets.TamariLattice(4) sage: (P*P).factor() @@ -6134,7 +6136,7 @@ def relabel(self, relabeling=None): Relabeling a (semi)lattice gives a (semi)lattice:: sage: P = JoinSemilattice({0: [1]}) - sage: P.relabel(lambda n: n+1) + sage: P.relabel(lambda n: n + 1) Finite join-semilattice containing 2 elements .. NOTE:: @@ -7003,7 +7005,8 @@ def maximal_chains(self, partial=None): sage: P = posets.BooleanLattice(3) sage: P.maximal_chains() - [[0, 1, 3, 7], [0, 1, 5, 7], [0, 2, 3, 7], [0, 2, 6, 7], [0, 4, 5, 7], [0, 4, 6, 7]] + [[0, 1, 3, 7], [0, 1, 5, 7], [0, 2, 3, 7], + [0, 2, 6, 7], [0, 4, 5, 7], [0, 4, 6, 7]] sage: P.maximal_chains(partial=[0,2]) [[0, 2, 3, 7], [0, 2, 6, 7]] sage: Q = posets.ChainPoset(6) @@ -7117,15 +7120,17 @@ def order_complex(self, on_ints=False): sage: Q.order_complex().homology() # a circle {0: 0, 1: Z} - sage: P = Poset((divisors(15), attrcall("divides")), facade = True) + sage: P = Poset((divisors(15), attrcall("divides")), facade=True) sage: P.order_complex() - Simplicial complex with vertex set (1, 3, 5, 15) and facets {(1, 3, 15), (1, 5, 15)} + Simplicial complex with vertex set (1, 3, 5, 15) and + facets {(1, 3, 15), (1, 5, 15)} If ``on_ints``, then the elements of the poset are labelled `0,1,\dots` in the chain complex:: sage: P.order_complex(on_ints=True) - Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 1, 3), (0, 2, 3)} + Simplicial complex with vertex set (0, 1, 2, 3) and + facets {(0, 1, 3), (0, 2, 3)} """ from sage.topology.simplicial_complex import SimplicialComplex L = self.list() @@ -7214,10 +7219,10 @@ def chain_polytope(self): EXAMPLES:: sage: P = posets.AntichainPoset(3) - sage: Q = P.chain_polytope();Q # needs sage.geometry.polyhedron + sage: Q = P.chain_polytope(); Q # needs sage.geometry.polyhedron A 3-dimensional polyhedron in ZZ^3 defined as the convex hull of 8 vertices sage: P = posets.PentagonPoset() - sage: Q = P.chain_polytope();Q # needs sage.geometry.polyhedron + sage: Q = P.chain_polytope(); Q # needs sage.geometry.polyhedron A 5-dimensional polyhedron in ZZ^5 defined as the convex hull of 8 vertices """ from sage.geometry.polyhedron.constructor import Polyhedron diff --git a/src/sage/combinat/q_bernoulli.pyx b/src/sage/combinat/q_bernoulli.pyx index 271a04487fb..854a80190f3 100644 --- a/src/sage/combinat/q_bernoulli.pyx +++ b/src/sage/combinat/q_bernoulli.pyx @@ -104,7 +104,8 @@ def q_bernoulli_polynomial(m): sage: all(q_bernoulli_polynomial(i)(q=1) == bernoulli_polynomial(x,i) # needs sage.libs.flint ....: for i in range(12)) True - sage: all(q_bernoulli_polynomial(i)(x=0)==q_bernoulli(i) for i in range(12)) + sage: all(q_bernoulli_polynomial(i)(x=0) == q_bernoulli(i) + ....: for i in range(12)) True The function does not accept negative arguments:: diff --git a/src/sage/combinat/schubert_polynomial.py b/src/sage/combinat/schubert_polynomial.py index f783873a4ee..1fd435591de 100644 --- a/src/sage/combinat/schubert_polynomial.py +++ b/src/sage/combinat/schubert_polynomial.py @@ -338,9 +338,13 @@ def scalar_product(self, x): sage: s = SymmetricFunctions(ZZ).schur() sage: c = s([2,1,1]) sage: b.scalar_product(a).expand() - x0^2*x1*x2 + x0*x1^2*x2 + x0*x1*x2^2 + x0^2*x1*x3 + x0*x1^2*x3 + x0^2*x2*x3 + 3*x0*x1*x2*x3 + x1^2*x2*x3 + x0*x2^2*x3 + x1*x2^2*x3 + x0*x1*x3^2 + x0*x2*x3^2 + x1*x2*x3^2 + x0^2*x1*x2 + x0*x1^2*x2 + x0*x1*x2^2 + x0^2*x1*x3 + x0*x1^2*x3 + + x0^2*x2*x3 + 3*x0*x1*x2*x3 + x1^2*x2*x3 + x0*x2^2*x3 + x1*x2^2*x3 + + x0*x1*x3^2 + x0*x2*x3^2 + x1*x2*x3^2 sage: c.expand(4) - x0^2*x1*x2 + x0*x1^2*x2 + x0*x1*x2^2 + x0^2*x1*x3 + x0*x1^2*x3 + x0^2*x2*x3 + 3*x0*x1*x2*x3 + x1^2*x2*x3 + x0*x2^2*x3 + x1*x2^2*x3 + x0*x1*x3^2 + x0*x2*x3^2 + x1*x2*x3^2 + x0^2*x1*x2 + x0*x1^2*x2 + x0*x1*x2^2 + x0^2*x1*x3 + x0*x1^2*x3 + + x0^2*x2*x3 + 3*x0*x1*x2*x3 + x1^2*x2*x3 + x0*x2^2*x3 + x1*x2^2*x3 + + x0*x1*x3^2 + x0*x2*x3^2 + x1*x2*x3^2 """ if isinstance(x, SchubertPolynomial_class): return symmetrica.scalarproduct_schubert(self, x) diff --git a/src/sage/combinat/subword_complex.py b/src/sage/combinat/subword_complex.py index 117f5599f70..0439d7f72f0 100644 --- a/src/sage/combinat/subword_complex.py +++ b/src/sage/combinat/subword_complex.py @@ -11,8 +11,8 @@ A subword complex is a shellable sphere if and only if the Demazure product of `Q` equals `w`, otherwise it is a shellable ball. -The code is optimized to be used with ReflectionGroup, it works as well -with CoxeterGroup, but many methods fail for WeylGroup. +The code is optimized to be used with :class:`ReflectionGroup`, it works as well +with :class:`CoxeterGroup`, but many methods fail for :class:`WeylGroup`. EXAMPLES:: diff --git a/src/sage/combinat/tableau_tuple.py b/src/sage/combinat/tableau_tuple.py index a163bf08d86..1568175325e 100644 --- a/src/sage/combinat/tableau_tuple.py +++ b/src/sage/combinat/tableau_tuple.py @@ -16,7 +16,7 @@ its *level* and the tableaux `t^{(1)}, t^{(2)}, \ldots, t^{(l)}` are the components of the :class:`TableauTuple`. -A tableaux can be thought of as the labelled diagram of a partition. +A tableau can be thought of as the labelled diagram of a partition. Analogously, a :class:`TableauTuple` is the labelled diagram of a :class:`PartitionTuple`. That is, a :class:`TableauTuple` is a tableau of :class:`PartitionTuple` shape. As much as possible, :class:`TableauTuples` diff --git a/src/sage/combinat/tiling.py b/src/sage/combinat/tiling.py index 63a25e0bd81..d28d85ca269 100644 --- a/src/sage/combinat/tiling.py +++ b/src/sage/combinat/tiling.py @@ -47,9 +47,11 @@ sage: T = TilingSolver([p,q], box=[3,2]) sage: it = T.solve() sage: next(it) - [Polyomino: [(0, 0), (0, 1), (1, 0), (1, 1)], Color: gray, Polyomino: [(2, 0), (2, 1)], Color: gray] + [Polyomino: [(0, 0), (0, 1), (1, 0), (1, 1)], Color: gray, + Polyomino: [(2, 0), (2, 1)], Color: gray] sage: next(it) - [Polyomino: [(1, 0), (1, 1), (2, 0), (2, 1)], Color: gray, Polyomino: [(0, 0), (0, 1)], Color: gray] + [Polyomino: [(1, 0), (1, 1), (2, 0), (2, 1)], Color: gray, + Polyomino: [(0, 0), (0, 1)], Color: gray] sage: next(it) Traceback (most recent call last): ... @@ -126,9 +128,13 @@ 15 sage: it = T.solve() sage: next(it) - [Polyomino: [(0)], Color: gray, Polyomino: [(1), (2)], Color: gray, Polyomino: [(3), (4), (5)], Color: gray] + [Polyomino: [(0)], Color: gray, + Polyomino: [(1), (2)], Color: gray, + Polyomino: [(3), (4), (5)], Color: gray] sage: next(it) - [Polyomino: [(0)], Color: gray, Polyomino: [(1), (2), (3)], Color: gray, Polyomino: [(4), (5)], Color: gray] + [Polyomino: [(0)], Color: gray, + Polyomino: [(1), (2), (3)], Color: gray, + Polyomino: [(4), (5)], Color: gray] sage: T.number_of_solutions() 6 @@ -261,7 +267,9 @@ sage: T = TilingSolver([p], box=(2,2,2,2,2), reusable=True) sage: rows = T.rows() # long time (3s) sage: rows # long time (fast) - [[0], [1], [2], [3], [4], [5], [6], [7], [8], [9], [10], [11], [12], [13], [14], [15], [16], [17], [18], [19], [20], [21], [22], [23], [24], [25], [26], [27], [28], [29], [30], [31]] + [[0], [1], [2], [3], [4], [5], [6], [7], [8], [9], [10], [11], [12], + [13], [14], [15], [16], [17], [18], [19], [20], [21], [22], [23], [24], + [25], [26], [27], [28], [29], [30], [31]] sage: T.number_of_solutions() # long time (fast) 1 @@ -657,7 +665,7 @@ def __eq__(self, other): INPUT: - - ``other`` - a polyomino + - ``other`` -- a polyomino OUTPUT: @@ -682,7 +690,7 @@ def __ne__(self, other): INPUT: - - ``other`` - a polyomino + - ``other`` -- a polyomino OUTPUT: @@ -707,7 +715,7 @@ def __le__(self, other): INPUT: - - ``other`` - a polyomino + - ``other`` -- a polyomino OUTPUT: @@ -731,7 +739,7 @@ def __ge__(self, other): INPUT: - - ``other`` - a polyomino + - ``other`` -- a polyomino OUTPUT: @@ -755,7 +763,7 @@ def __lt__(self, other): INPUT: - - ``other`` - a polyomino + - ``other`` -- a polyomino OUTPUT: @@ -779,7 +787,7 @@ def __gt__(self, other): INPUT: - - ``other`` - a polyomino + - ``other`` -- a polyomino OUTPUT: @@ -803,7 +811,7 @@ def intersection(self, other): INPUT: - - ``other`` - a polyomino + - ``other`` -- a polyomino OUTPUT: @@ -831,7 +839,7 @@ def __sub__(self, v): INPUT: - - ``v`` - tuple + - ``v`` -- tuple OUTPUT: @@ -853,7 +861,7 @@ def __add__(self, v): INPUT: - - ``v`` - tuple + - ``v`` -- tuple OUTPUT: @@ -876,7 +884,7 @@ def __rmul__(self, m): INPUT: - - ``m`` - square matrix, matching the dimension of self. + - ``m`` -- square matrix, matching the dimension of ``self``. OUTPUT: @@ -1038,10 +1046,12 @@ def translated_copies(self, box): sage: q = Polyomino([(0,0,0), (1,0,0)]) sage: list(q.translated_copies((2,2,1))) - [Polyomino: [(0, 0, 0), (1, 0, 0)], Color: gray, Polyomino: [(0, 1, 0), (1, 1, 0)], Color: gray] + [Polyomino: [(0, 0, 0), (1, 0, 0)], Color: gray, + Polyomino: [(0, 1, 0), (1, 1, 0)], Color: gray] sage: q = Polyomino([(34,7,-9), (35,7,-9)]) sage: list(q.translated_copies((2,2,1))) - [Polyomino: [(0, 0, 0), (1, 0, 0)], Color: gray, Polyomino: [(0, 1, 0), (1, 1, 0)], Color: gray] + [Polyomino: [(0, 0, 0), (1, 0, 0)], Color: gray, + Polyomino: [(0, 1, 0), (1, 1, 0)], Color: gray] Inside smaller boxes:: @@ -1111,7 +1121,8 @@ def translated_copies_intersection(self, box): sage: from sage.combinat.tiling import Polyomino sage: p = Polyomino([(0,0),(1,0)], color='deeppink') - sage: sorted(sorted(a.frozenset()) for a in p.translated_copies_intersection(box=(2,3))) + sage: sorted(sorted(a.frozenset()) + ....: for a in p.translated_copies_intersection(box=(2,3))) [[(0, 0)], [(0, 0), (1, 0)], [(0, 1)], @@ -1126,7 +1137,8 @@ def translated_copies_intersection(self, box): sage: b = Polyomino([(0,0), (0,1), (0,2), (1,0), (2,0)]) sage: p = Polyomino([(0,0), (1,0)]) - sage: sorted(sorted(a.frozenset()) for a in p.translated_copies_intersection(b)) + sage: sorted(sorted(a.frozenset()) + ....: for a in p.translated_copies_intersection(b)) [[(0, 0)], [(0, 0), (1, 0)], [(0, 1)], [(0, 2)], [(1, 0), (2, 0)], [(2, 0)]] """ @@ -1330,7 +1342,7 @@ def boundary(self): INPUT: - - ``self`` - a 2d polyomino + - ``self`` -- a 2d polyomino OUTPUT: @@ -1341,12 +1353,16 @@ def boundary(self): sage: from sage.combinat.tiling import Polyomino sage: p = Polyomino([(0,0), (1,0), (0,1), (1,1)]) sage: sorted(p.boundary()) - [((-0.5, -0.5), (-0.5, 0.5)), ((-0.5, -0.5), (0.5, -0.5)), ((-0.5, 0.5), (-0.5, 1.5)), ((-0.5, 1.5), (0.5, 1.5)), ((0.5, -0.5), (1.5, -0.5)), ((0.5, 1.5), (1.5, 1.5)), ((1.5, -0.5), (1.5, 0.5)), ((1.5, 0.5), (1.5, 1.5))] + [((-0.5, -0.5), (-0.5, 0.5)), ((-0.5, -0.5), (0.5, -0.5)), + ((-0.5, 0.5), (-0.5, 1.5)), ((-0.5, 1.5), (0.5, 1.5)), + ((0.5, -0.5), (1.5, -0.5)), ((0.5, 1.5), (1.5, 1.5)), + ((1.5, -0.5), (1.5, 0.5)), ((1.5, 0.5), (1.5, 1.5))] sage: len(_) 8 sage: p = Polyomino([(5,5)]) sage: sorted(p.boundary()) - [((4.5, 4.5), (4.5, 5.5)), ((4.5, 4.5), (5.5, 4.5)), ((4.5, 5.5), (5.5, 5.5)), ((5.5, 4.5), (5.5, 5.5))] + [((4.5, 4.5), (4.5, 5.5)), ((4.5, 4.5), (5.5, 4.5)), + ((4.5, 5.5), (5.5, 5.5)), ((5.5, 4.5), (5.5, 5.5))] """ if self._dimension != 2: raise NotImplementedError("The method boundary is currently " @@ -1377,8 +1393,8 @@ def show3d(self, size=1): INPUT: - - ``self`` - a polyomino of dimension 3 - - ``size`` - number (optional, default: ``1``), the size of each + - ``self`` -- a polyomino of dimension 3 + - ``size`` -- number (optional, default: ``1``), the size of each ``1 \times 1 \times 1`` cube. This does a homothety with respect to the center of the polyomino. @@ -1407,12 +1423,12 @@ def show2d(self, size=0.7, color='black', thickness=1): INPUT: - - ``self`` - a polyomino of dimension 2 - - ``size`` - number (optional, default: ``0.7``), the size of each + - ``self`` -- a polyomino of dimension 2 + - ``size`` -- number (optional, default: ``0.7``), the size of each square. - - ``color`` - color (optional, default: ``'black'``), color of + - ``color`` -- color (optional, default: ``'black'``), color of the boundary line. - - ``thickness`` - number (optional, default: ``1``), how thick the + - ``thickness`` -- number (optional, default: ``1``), how thick the boundary line is. EXAMPLES:: @@ -1448,8 +1464,8 @@ def self_surrounding(self, radius, remove_incomplete_copies=True, INPUT: - - ``self`` - a polyomino of dimension 2 - - ``radius`` - integer + - ``self`` -- a polyomino of dimension 2 + - ``radius`` -- integer - ``remove_incomplete_copies`` -- bool (default: ``True``), whether to keep only complete copies of ``self`` in the output - ``ncpus`` -- integer (default: ``None``), maximal number of @@ -1736,7 +1752,8 @@ def coord_to_int_dict(self): sage: T = TilingSolver([p,q,r], box=(1,1,6)) sage: A = T.coord_to_int_dict() sage: sorted(A.items()) - [((0, 0, 0), 3), ((0, 0, 1), 4), ((0, 0, 2), 5), ((0, 0, 3), 6), ((0, 0, 4), 7), ((0, 0, 5), 8)] + [((0, 0, 0), 3), ((0, 0, 1), 4), ((0, 0, 2), 5), + ((0, 0, 3), 6), ((0, 0, 4), 7), ((0, 0, 5), 8)] Reusable pieces:: @@ -1745,7 +1762,8 @@ def coord_to_int_dict(self): sage: T = TilingSolver([p,q], box=[3,2], reusable=True) sage: B = T.coord_to_int_dict() sage: sorted(B.items()) - [((0, 0), 0), ((0, 1), 1), ((1, 0), 2), ((1, 1), 3), ((2, 0), 4), ((2, 1), 5)] + [((0, 0), 0), ((0, 1), 1), ((1, 0), 2), ((1, 1), 3), + ((2, 0), 4), ((2, 1), 5)] """ if self._reusable: return dict((c, i) for i, c in enumerate(self.space())) @@ -1768,7 +1786,8 @@ def int_to_coord_dict(self): sage: T = TilingSolver([p,q,r], box=(1,1,6)) sage: B = T.int_to_coord_dict() sage: sorted(B.items()) - [(3, (0, 0, 0)), (4, (0, 0, 1)), (5, (0, 0, 2)), (6, (0, 0, 3)), (7, (0, 0, 4)), (8, (0, 0, 5))] + [(3, (0, 0, 0)), (4, (0, 0, 1)), (5, (0, 0, 2)), + (6, (0, 0, 3)), (7, (0, 0, 4)), (8, (0, 0, 5))] Reusable pieces:: @@ -1778,7 +1797,8 @@ def int_to_coord_dict(self): sage: T = TilingSolver([p,q], box=[3,2], reusable=True) sage: B = T.int_to_coord_dict() sage: sorted(B.items()) - [(0, (0, 0)), (1, (0, 1)), (2, (1, 0)), (3, (1, 1)), (4, (2, 0)), (5, (2, 1))] + [(0, (0, 0)), (1, (0, 1)), (2, (1, 0)), + (3, (1, 1)), (4, (2, 0)), (5, (2, 1))] TESTS: @@ -1932,7 +1952,8 @@ def _rows_mod_box_isometries(self, i): EXAMPLES:: sage: from sage.combinat.tiling import TilingSolver, Polyomino - sage: p = Polyomino([(0,0,0), (1,0,0), (1,1,0), (1,0,1), (2,0,1)], color='red') + sage: p = Polyomino([(0,0,0), (1,0,0), (1,1,0), (1,0,1), (2,0,1)], + ....: color='red') sage: T = TilingSolver([p], box=(3,4,2)) sage: T._rows_mod_box_isometries(0) [[0, 1, 3, 4, 11, 13], @@ -2273,13 +2294,13 @@ def solve(self, partial=None): INPUT: - - ``partial`` - string (optional, default: ``None``), whether to + - ``partial`` -- string (optional, default: ``None``), whether to include partial (incomplete) solutions. It can be one of the following: - - ``None`` - include only complete solution - - ``'common_prefix'`` - common prefix between two consecutive solutions - - ``'incremental'`` - one piece change at a time + - ``None`` -- include only complete solution + - ``'common_prefix'`` -- common prefix between two consecutive solutions + - ``'incremental'`` -- one piece change at a time OUTPUT: @@ -2400,21 +2421,21 @@ def animate(self, partial=None, stop=None, size=0.75, axes=False): INPUT: - - ``partial`` - string (optional, default: ``None``), whether to + - ``partial`` -- string (optional, default: ``None``), whether to include partial (incomplete) solutions. It can be one of the following: - - ``None`` - include only complete solutions - - ``'common_prefix'`` - common prefix between two consecutive solutions - - ``'incremental'`` - one piece change at a time + - ``None`` -- include only complete solutions + - ``'common_prefix'`` -- common prefix between two consecutive solutions + - ``'incremental'`` -- one piece change at a time - - ``stop`` - integer (optional, default:``None``), number of frames + - ``stop`` -- integer (optional, default:``None``), number of frames - - ``size`` - number (optional, default: ``0.75``), the size of each + - ``size`` -- number (optional, default: ``0.75``), the size of each ``1 \times 1`` square. This does a homothety with respect to the center of each polyomino. - - ``axes`` - bool (optional, default:``False``), whether the x and + - ``axes`` -- bool (optional, default:``False``), whether the x and y axes are shown. EXAMPLES:: diff --git a/src/sage/combinat/yang_baxter_graph.py b/src/sage/combinat/yang_baxter_graph.py index 451bdc9e82e..bcfac20fdb3 100644 --- a/src/sage/combinat/yang_baxter_graph.py +++ b/src/sage/combinat/yang_baxter_graph.py @@ -45,8 +45,8 @@ def YangBaxterGraph(partition=None, root=None, operators=None): - Either: - - :class:`YangBaxterGraph_partition` - if partition is defined - - :class:`YangBaxterGraph_generic` - if partition is ``None`` + - :class:`YangBaxterGraph_partition` -- if partition is defined + - :class:`YangBaxterGraph_generic` -- if partition is ``None`` EXAMPLES: From 2b064e1a43c4391263e16175fa1e4cad071a82c3 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 13 Mar 2024 23:57:57 -0700 Subject: [PATCH 251/518] src/sage/matrix/args.pyx: Fixup --- src/sage/matrix/args.pyx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/sage/matrix/args.pyx b/src/sage/matrix/args.pyx index 91937c22206..9a613fcdf62 100644 --- a/src/sage/matrix/args.pyx +++ b/src/sage/matrix/args.pyx @@ -230,9 +230,10 @@ cdef class MatrixArgs: [3/5 0] [ 0 3/5] - sage: ma = MatrixArgs(entries=matrix(2,2)); ma.finalized(); ma.matrix() - + sage: ma = MatrixArgs(entries=matrix(2,2)); ma.finalized() + sage: ma.matrix() [0 0] [0 0] From 246cf341dcfd514eb4a0e5b9986212712394b8ab Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Thu, 14 Mar 2024 10:58:31 +0000 Subject: [PATCH 252/518] Ensure degree and total degree return Integer --- .../polynomial/multi_polynomial_element.py | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/src/sage/rings/polynomial/multi_polynomial_element.py b/src/sage/rings/polynomial/multi_polynomial_element.py index 57ec623e5d2..376eb0e4eb6 100644 --- a/src/sage/rings/polynomial/multi_polynomial_element.py +++ b/src/sage/rings/polynomial/multi_polynomial_element.py @@ -668,11 +668,22 @@ def degree(self, x=None, std_grading=False): sage: R. = GF(3037000453)[] # needs sage.rings.finite_rings sage: R.zero().degree(x) -1 + + Ensure that :issue:`37603` is fixed:: + + sage: R. = PolynomialRing(QQbar) + sage: f = 3*x^2 - 2*y + 7*x^2*y^2 + 5 + sage: type(f.degree()) + + sage: type(f.degree(x)) + + sage: type(f.degree(x)) == type(f.degree(y)) == type(f.degree(z)) + True """ if x is None: if std_grading or not self.parent().term_order().is_weighted_degree_order(): - return self.element().degree(None) - return self.weighted_degree(self.parent().term_order().weights()) + return Integer(self.element().degree(None)) + return Integer(self.weighted_degree(self.parent().term_order().weights())) if isinstance(x, MPolynomial): if not x.parent() is self.parent(): try: @@ -683,7 +694,7 @@ def degree(self, x=None, std_grading=False): raise TypeError("x must be one of the generators of the parent") else: raise TypeError("x must be one of the generators of the parent") - return self.element().degree(x.element()) + return Integer(self.element().degree(x.element())) def total_degree(self): """ @@ -712,6 +723,16 @@ def total_degree(self): sage: f = z^9 + 10*x^4 + y^8*x^2 sage: f.total_degree() 10 + + TESTS:: + + Ensure that :issue:`37603` is fixed:: + sage: R. = QQbar[] + sage: f = 2*x*y^3*z^2 + sage: f.total_degree() + 6 + sage: type(f.total_degree()) + """ return self.degree() From 3528ba8094ce1687059de7b1b5a709eb5cb52b94 Mon Sep 17 00:00:00 2001 From: Kwankyu Lee Date: Thu, 14 Mar 2024 20:08:15 +0900 Subject: [PATCH 253/518] fix test failures --- src/sage/misc/sageinspect.py | 8 +++++--- src/sage_docbuild/conf.py | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/sage/misc/sageinspect.py b/src/sage/misc/sageinspect.py index 7a5c304f67b..75f8c02c23d 100644 --- a/src/sage/misc/sageinspect.py +++ b/src/sage/misc/sageinspect.py @@ -43,7 +43,7 @@ sage: sage_getfile(BlockFinder) '.../sage/misc/sageinspect.py' sage: sage_getdoc(BlockFinder).lstrip()[:50] - 'Provide a tokeneater() method to detect the...' + 'Provide a "tokeneater()" method to detect the end ' sage: sage_getsource(BlockFinder) 'class BlockFinder:...' @@ -103,7 +103,7 @@ class definition be found starting from the ``__init__`` method. - Simon King: in :func:`sage_getsourcelines`, get source lines for dynamic classes - Simon King: in :func:`_sage_getargspec_cython`, return an ``ArgSpec``, fix some bugs - Simon King (2011-09): added :func:`_sage_getsourcelines_name_with_dot` -- Simon King (2013-02): in :func:`_sage_getargspec_cython()`, recognise varargs and +- Simon King (2013-02): in :func:`_sage_getargspec_cython`, recognise varargs and default values in cython code, and return an ``ArgSpec`` """ @@ -2264,8 +2264,9 @@ def sage_getsourcelines(obj): EXAMPLES:: - sage: # needs sage.modules sage: from sage.misc.sageinspect import sage_getsourcelines + + sage: # needs sage.modules sage: sage_getsourcelines(matrix)[1] 21 sage: sage_getsourcelines(matrix)[0][0] @@ -2363,6 +2364,7 @@ class Element(): ' Ideal_generic):\n', ' def __init__(self, ring, gens, coerce=True):\n', ...) + """ # First try the method _sage_src_lines_(), which is meant to give # the source lines of an object (not of its type!). diff --git a/src/sage_docbuild/conf.py b/src/sage_docbuild/conf.py index cf9269c3cd4..052f0648dac 100644 --- a/src/sage_docbuild/conf.py +++ b/src/sage_docbuild/conf.py @@ -336,7 +336,7 @@ def linkcode_resolve(domain, info): for attr in fullname.split('.'): obj = getattr(obj, attr) lineno = sage_getsourcelines(obj)[-1] - except (AttributeError, TypeError, OSError): + except Exception: # catch all return None anchor = f'#L{lineno}' else: From 5204a5ee3b290b870390eb07763f1fa0d6cc53ca Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Thu, 14 Mar 2024 11:19:18 +0000 Subject: [PATCH 254/518] fix rst linting --- src/sage/rings/polynomial/multi_polynomial_element.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/polynomial/multi_polynomial_element.py b/src/sage/rings/polynomial/multi_polynomial_element.py index 376eb0e4eb6..d69c07f30ea 100644 --- a/src/sage/rings/polynomial/multi_polynomial_element.py +++ b/src/sage/rings/polynomial/multi_polynomial_element.py @@ -724,7 +724,7 @@ def total_degree(self): sage: f.total_degree() 10 - TESTS:: + TESTS: Ensure that :issue:`37603` is fixed:: sage: R. = QQbar[] From 918ff5c159eaab39bd79577570dede66203cc6ae Mon Sep 17 00:00:00 2001 From: Kwankyu Lee Date: Thu, 14 Mar 2024 21:11:04 +0900 Subject: [PATCH 255/518] Add missed docs --- src/doc/ca/intro/conf.py | 5 +++++ src/doc/de/a_tour_of_sage/conf.py | 5 +++++ src/doc/de/thematische_anleitungen/conf.py | 5 +++++ src/doc/de/tutorial/conf.py | 5 +++++ 4 files changed, 20 insertions(+) diff --git a/src/doc/ca/intro/conf.py b/src/doc/ca/intro/conf.py index 2f4eb7f1873..e46ebfb8c8d 100644 --- a/src/doc/ca/intro/conf.py +++ b/src/doc/ca/intro/conf.py @@ -20,6 +20,11 @@ # contains common paths. html_static_path = [] + html_common_static_path +# Add a small edit button. +html_theme_options.update({ + 'source_edit_link': os.path.join(source_repository, 'blob/develop/src/doc/ca/intro', '{filename}'), +}) + # General information about the project. project = "Introducció de Sage" diff --git a/src/doc/de/a_tour_of_sage/conf.py b/src/doc/de/a_tour_of_sage/conf.py index 47355ae5a22..33f9e9642b7 100644 --- a/src/doc/de/a_tour_of_sage/conf.py +++ b/src/doc/de/a_tour_of_sage/conf.py @@ -22,6 +22,11 @@ # contains common paths. html_static_path = [] + html_common_static_path +# Add a small edit button. +html_theme_options.update({ + 'source_edit_link': os.path.join(source_repository, 'blob/develop/src/doc/de/a_tour_of_sage', '{filename}'), +}) + # General information about the project. project = "Ein Rundgang durch Sage" name = "a_tour_of_sage" diff --git a/src/doc/de/thematische_anleitungen/conf.py b/src/doc/de/thematische_anleitungen/conf.py index 7f2753ab0e4..d3a843c30bd 100644 --- a/src/doc/de/thematische_anleitungen/conf.py +++ b/src/doc/de/thematische_anleitungen/conf.py @@ -20,6 +20,11 @@ # contains common paths. html_static_path = [] + html_common_static_path +# Add a small edit button. +html_theme_options.update({ + 'source_edit_link': os.path.join(source_repository, 'blob/develop/src/doc/de/thematische_anleitungen', '{filename}'), +}) + # General information about the project. project = "Thematische Anleitungen" name = 'ThematischeAnleitungen-de' diff --git a/src/doc/de/tutorial/conf.py b/src/doc/de/tutorial/conf.py index 8d18c44aa6b..fe4c5b0d3e1 100644 --- a/src/doc/de/tutorial/conf.py +++ b/src/doc/de/tutorial/conf.py @@ -20,6 +20,11 @@ # contains common paths. html_static_path = [] + html_common_static_path +# Add a small edit button. +html_theme_options.update({ + 'source_edit_link': os.path.join(source_repository, 'blob/develop/src/doc/de/tutorial', '{filename}'), +}) + # General information about the project. project = "Sage Tutorial" name = 'SageTutorial-de' From be07520c1a8c7499c0a9ad3c651a9aae00fb2629 Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Thu, 14 Mar 2024 14:21:31 +0000 Subject: [PATCH 256/518] change construction and add doctest --- src/sage/schemes/hyperelliptic_curves/constructor.py | 10 ++++++++-- .../schemes/hyperelliptic_curves/hyperelliptic_g2.py | 9 +++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/sage/schemes/hyperelliptic_curves/constructor.py b/src/sage/schemes/hyperelliptic_curves/constructor.py index faba7145ba5..4cc9abd1b91 100644 --- a/src/sage/schemes/hyperelliptic_curves/constructor.py +++ b/src/sage/schemes/hyperelliptic_curves/constructor.py @@ -277,7 +277,13 @@ def is_pAdicField(x): cls_name.append(name) break + base_cls = None + if len(superclass) == 0: + base_cls = HyperellipticCurve_generic + class_name = "_".join(cls_name) - cls = dynamic_class(class_name, tuple(superclass), - HyperellipticCurve_generic, doccls=HyperellipticCurve) + cls = dynamic_class(class_name, + tuple(superclass), + cls=base_cls, + doccls=HyperellipticCurve) return cls(PP, f, h, names=names, genus=g) diff --git a/src/sage/schemes/hyperelliptic_curves/hyperelliptic_g2.py b/src/sage/schemes/hyperelliptic_curves/hyperelliptic_g2.py index 7a8fe986448..5e5225ebff1 100644 --- a/src/sage/schemes/hyperelliptic_curves/hyperelliptic_g2.py +++ b/src/sage/schemes/hyperelliptic_curves/hyperelliptic_g2.py @@ -45,6 +45,15 @@ def jacobian(self): sage: f = x^5 - x^4 + 3 sage: HyperellipticCurve(f).jacobian() Jacobian of Hyperelliptic Curve over Rational Field defined by y^2 = x^5 - x^4 + 3 + + TESTS: + + Ensure that :issue:`37612` is fixed:: + + sage: R. = QQ[] + sage: f = x^5 - x^4 + 3 + sage: HyperellipticCurve(f).jacobian + """ return jacobian_g2.HyperellipticJacobian_g2(self) From 48ef54e18298d55382404f806afd6660930a2d5b Mon Sep 17 00:00:00 2001 From: Giacomo Pope <44242839+GiacomoPope@users.noreply.github.com> Date: Thu, 14 Mar 2024 16:23:24 +0000 Subject: [PATCH 257/518] reviewer suggestions --- src/sage/rings/polynomial/multi_polynomial_element.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/polynomial/multi_polynomial_element.py b/src/sage/rings/polynomial/multi_polynomial_element.py index d69c07f30ea..d2355aa257b 100644 --- a/src/sage/rings/polynomial/multi_polynomial_element.py +++ b/src/sage/rings/polynomial/multi_polynomial_element.py @@ -683,7 +683,7 @@ def degree(self, x=None, std_grading=False): if x is None: if std_grading or not self.parent().term_order().is_weighted_degree_order(): return Integer(self.element().degree(None)) - return Integer(self.weighted_degree(self.parent().term_order().weights())) + return self.weighted_degree(self.parent().term_order().weights()) if isinstance(x, MPolynomial): if not x.parent() is self.parent(): try: From 8adab0f1f2aad0ee3edf8531c203ab89f4a937f3 Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Thu, 14 Mar 2024 16:55:28 +0000 Subject: [PATCH 258/518] change how construction is done --- .../hyperelliptic_curves/constructor.py | 87 +++++++++++-------- 1 file changed, 49 insertions(+), 38 deletions(-) diff --git a/src/sage/schemes/hyperelliptic_curves/constructor.py b/src/sage/schemes/hyperelliptic_curves/constructor.py index 4cc9abd1b91..f5507dac5aa 100644 --- a/src/sage/schemes/hyperelliptic_curves/constructor.py +++ b/src/sage/schemes/hyperelliptic_curves/constructor.py @@ -92,11 +92,11 @@ def HyperellipticCurve(f, h=0, names=None, PP=None, check_squarefree=True): sage: HyperellipticCurve(x^8 + 1, x) Traceback (most recent call last): ... - ValueError: Not a hyperelliptic curve: highly singular at infinity. + ValueError: not a hyperelliptic curve: highly singular at infinity sage: HyperellipticCurve(x^8 + x^7 + 1, x^4) Traceback (most recent call last): ... - ValueError: Not a hyperelliptic curve: singularity in the provided affine patch. + ValueError: not a hyperelliptic curve: singularity in the provided affine patch sage: F. = PowerSeriesRing(FiniteField(2)) sage: P. = PolynomialRing(FractionField(F)) @@ -128,7 +128,7 @@ def HyperellipticCurve(f, h=0, names=None, PP=None, check_squarefree=True): sage: HyperellipticCurve((x^3-x+2)^2*(x^6-1)) Traceback (most recent call last): ... - ValueError: Not a hyperelliptic curve: singularity in the provided affine patch. + ValueError: not a hyperelliptic curve: singularity in the provided affine patch sage: HyperellipticCurve((x^3-x+2)^2*(x^6-1), check_squarefree=False) Hyperelliptic Curve over Finite Field of size 7 defined by @@ -147,7 +147,7 @@ def HyperellipticCurve(f, h=0, names=None, PP=None, check_squarefree=True): sage: HyperellipticCurve(f, h) Traceback (most recent call last): ... - ValueError: Not a hyperelliptic curve: highly singular at infinity. + ValueError: not a hyperelliptic curve: highly singular at infinity sage: HyperellipticCurve(F) Hyperelliptic Curve over Rational Field defined by y^2 = x^6 + 1 @@ -160,7 +160,7 @@ def HyperellipticCurve(f, h=0, names=None, PP=None, check_squarefree=True): sage: HyperellipticCurve(x^5 + t) Traceback (most recent call last): ... - ValueError: Not a hyperelliptic curve: singularity in the provided affine patch. + ValueError: not a hyperelliptic curve: singularity in the provided affine patch Input with integer coefficients creates objects with the integers as base ring, but only checks smoothness over `\QQ`, not over Spec(`\ZZ`). @@ -203,18 +203,18 @@ def HyperellipticCurve(f, h=0, names=None, PP=None, check_squarefree=True): """ # F is the discriminant; use this for the type check # rather than f and h, one of which might be constant. - F = h**2 + 4*f + F = h**2 + 4 * f if not isinstance(F, Polynomial): - raise TypeError("Arguments f (= %s) and h (= %s) must be polynomials" % (f, h)) + raise TypeError(f"Arguments {f = } and {h = } must be polynomials") P = F.parent() f = P(f) h = P(h) df = f.degree() - dh_2 = 2*h.degree() + dh_2 = 2 * h.degree() if dh_2 < df: - g = (df-1)//2 + g = (df - 1) // 2 else: - g = (dh_2-1)//2 + g = (dh_2 - 1) // 2 if check_squarefree: # Assuming we are working over a field, this checks that after # resolving the singularity at infinity, we get a smooth double cover @@ -222,16 +222,20 @@ def HyperellipticCurve(f, h=0, names=None, PP=None, check_squarefree=True): if P(2) == 0: # characteristic 2 if h == 0: - raise ValueError("In characteristic 2, argument h (= %s) must be non-zero." % h) - if h[g+1] == 0 and f[2*g+1]**2 == f[2*g+2]*h[g]**2: - raise ValueError("Not a hyperelliptic curve: " - "highly singular at infinity.") - should_be_coprime = [h, f*h.derivative()**2+f.derivative()**2] + raise ValueError( + f"for characteristic 2, argument {h = } must be non-zero" + ) + if h[g + 1] == 0 and f[2 * g + 1] ** 2 == f[2 * g + 2] * h[g] ** 2: + raise ValueError( + "not a hyperelliptic curve: highly singular at infinity" + ) + should_be_coprime = [h, f * h.derivative() ** 2 + f.derivative() ** 2] else: # characteristic not 2 - if F.degree() not in [2*g+1, 2*g+2]: - raise ValueError("Not a hyperelliptic curve: " - "highly singular at infinity.") + if F.degree() not in [2 * g + 1, 2 * g + 2]: + raise ValueError( + "not a hyperelliptic curve: highly singular at infinity" + ) should_be_coprime = [F, F.derivative()] try: smooth = should_be_coprime[0].gcd(should_be_coprime[1]).degree() == 0 @@ -239,23 +243,33 @@ def HyperellipticCurve(f, h=0, names=None, PP=None, check_squarefree=True): try: smooth = should_be_coprime[0].resultant(should_be_coprime[1]) != 0 except (AttributeError, NotImplementedError, TypeError): - raise NotImplementedError("Cannot determine whether " - "polynomials %s have a common root. Use " - "check_squarefree=False to skip this check." % - should_be_coprime) + raise NotImplementedError( + "cannot determine whether " + f"polynomials {should_be_coprime} have a common root, use " + "check_squarefree=False to skip this check" + ) if not smooth: - raise ValueError("Not a hyperelliptic curve: " - "singularity in the provided affine patch.") + raise ValueError( + "not a hyperelliptic curve: " + "singularity in the provided affine patch" + ) R = P.base_ring() PP = ProjectiveSpace(2, R) if names is None: names = ["x", "y"] - superclass = [] + bases = [] cls_name = ["HyperellipticCurve"] + # For certain genus we specialise to child classes with + # optimised methods genus_classes = {2: HyperellipticCurve_g2} + if g in genus_classes: + bases.append(genus_classes[g]) + cls_name.append("g%s" % g) + # For certain base fields, we specialise to child classes + # with special case methods def is_FiniteField(x): return isinstance(x, FiniteField) @@ -265,25 +279,22 @@ def is_pAdicField(x): fields = [ ("FiniteField", is_FiniteField, HyperellipticCurve_finite_field), ("RationalField", is_RationalField, HyperellipticCurve_rational_field), - ("pAdicField", is_pAdicField, HyperellipticCurve_padic_field)] - - if g in genus_classes: - superclass.append(genus_classes[g]) - cls_name.append("g%s" % g) + ("pAdicField", is_pAdicField, HyperellipticCurve_padic_field), + ] for name, test, cls in fields: if test(R): - superclass.append(cls) + bases.append(cls) cls_name.append(name) break - base_cls = None - if len(superclass) == 0: - base_cls = HyperellipticCurve_generic + # If no specialised child class was found, we simply use the + # generic class in the class construction + if not bases: + bases = [HyperellipticCurve_generic] + # Dynamically build a class from multiple inheritance. Note that + # all classes we slect from are children of HyperellipticCurve_generic class_name = "_".join(cls_name) - cls = dynamic_class(class_name, - tuple(superclass), - cls=base_cls, - doccls=HyperellipticCurve) + cls = dynamic_class(class_name, tuple(bases), doccls=HyperellipticCurve) return cls(PP, f, h, names=names, genus=g) From 97620cc928278712ed768b2c44badcef2ed00964 Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Thu, 14 Mar 2024 16:57:17 +0000 Subject: [PATCH 259/518] missed a captial --- src/sage/schemes/hyperelliptic_curves/constructor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/schemes/hyperelliptic_curves/constructor.py b/src/sage/schemes/hyperelliptic_curves/constructor.py index f5507dac5aa..65970b54725 100644 --- a/src/sage/schemes/hyperelliptic_curves/constructor.py +++ b/src/sage/schemes/hyperelliptic_curves/constructor.py @@ -205,7 +205,7 @@ def HyperellipticCurve(f, h=0, names=None, PP=None, check_squarefree=True): # rather than f and h, one of which might be constant. F = h**2 + 4 * f if not isinstance(F, Polynomial): - raise TypeError(f"Arguments {f = } and {h = } must be polynomials") + raise TypeError(f"arguments {f = } and {h = } must be polynomials") P = F.parent() f = P(f) h = P(h) From 93b99874d47183d82b8d1a3a2794e14806b7c650 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 14 Mar 2024 11:58:31 -0700 Subject: [PATCH 260/518] src/sage/graphs/generators/families.py: Fix networkx references --- src/sage/graphs/generators/families.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/sage/graphs/generators/families.py b/src/sage/graphs/generators/families.py index f1ce793f80c..0d039d8db05 100644 --- a/src/sage/graphs/generators/families.py +++ b/src/sage/graphs/generators/families.py @@ -513,12 +513,13 @@ def BalancedTree(r, h): OUTPUT: The perfectly balanced tree of height `h \geq 1` and whose root has - degree `r \geq 2`. A :class:`~networkx.NetworkXError`` is raised if `r < 2` or - `h < 1`. + degree `r \geq 2`. A :exc:`~networkx.exception.NetworkXError` is raised + if `r < 2` or `h < 1`. ALGORITHM: - Uses the :ref:`NetworkX ` function :func:`~networkx.balanced_tree`. + Uses the :ref:`NetworkX ` function + :func:`~networkx.generators.classic.balanced_tree`. EXAMPLES: From fb75af550dcd8fa0b3722f5369ddc14d74eca69e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20K=C3=B6ppe?= Date: Thu, 14 Mar 2024 12:14:34 -0700 Subject: [PATCH 261/518] Update src/sage/schemes/elliptic_curves/ell_point.py Co-authored-by: Giacomo Pope <44242839+GiacomoPope@users.noreply.github.com> --- src/sage/schemes/elliptic_curves/ell_point.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/schemes/elliptic_curves/ell_point.py b/src/sage/schemes/elliptic_curves/ell_point.py index 77b62f542aa..1b94d158615 100644 --- a/src/sage/schemes/elliptic_curves/ell_point.py +++ b/src/sage/schemes/elliptic_curves/ell_point.py @@ -3274,7 +3274,7 @@ def non_archimedean_local_height(self, v=None, prec=None, - ``v`` -- a non-archimedean place of `K`, or None (default). If `v` is a non-archimedean place, return the local height - of self at `v`. If `v` is None, return the total + of self at `v`. If `v` is ``None``, return the total non-archimedean contribution to the global height. - ``prec`` -- integer, or ``None`` (default). The precision of the From 42c2bce7a8193cfb3f87258ca7b73354275c9a89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20K=C3=B6ppe?= Date: Thu, 14 Mar 2024 12:14:45 -0700 Subject: [PATCH 262/518] Update src/sage/schemes/elliptic_curves/ell_point.py Co-authored-by: Giacomo Pope <44242839+GiacomoPope@users.noreply.github.com> --- src/sage/schemes/elliptic_curves/ell_point.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/schemes/elliptic_curves/ell_point.py b/src/sage/schemes/elliptic_curves/ell_point.py index 1b94d158615..ebe4bc79bb2 100644 --- a/src/sage/schemes/elliptic_curves/ell_point.py +++ b/src/sage/schemes/elliptic_curves/ell_point.py @@ -3281,7 +3281,7 @@ def non_archimedean_local_height(self, v=None, prec=None, computation. If ``None``, the height is returned symbolically. - ``weighted`` -- boolean. If ``False`` (default), the height is - normalised to be invariant under extension of `K`. If True, + normalised to be invariant under extension of `K`. If ``True``, return this normalised height multiplied by the local degree if `v` is a single place, or by the degree of `K` if `v` is None. From 3958c3b842a26d2e9a810f8fbad179feb8d409c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20K=C3=B6ppe?= Date: Thu, 14 Mar 2024 12:17:01 -0700 Subject: [PATCH 263/518] Apply suggestions from code review Co-authored-by: Giacomo Pope <44242839+GiacomoPope@users.noreply.github.com> --- src/sage/schemes/elliptic_curves/period_lattice.py | 10 +++++----- src/sage/schemes/hyperelliptic_curves/mestre.py | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/sage/schemes/elliptic_curves/period_lattice.py b/src/sage/schemes/elliptic_curves/period_lattice.py index e5aa953898e..7698c276417 100644 --- a/src/sage/schemes/elliptic_curves/period_lattice.py +++ b/src/sage/schemes/elliptic_curves/period_lattice.py @@ -527,7 +527,7 @@ def normalised_basis(self, prec=None, algorithm='sage'): precision if ``None``). - ``algorithm`` (string, default ``'sage'``) -- choice of - implementation (for real embeddings only) between 'sage' + implementation (for real embeddings only) between ``'sage'`` (native Sage implementation) or ``'pari'`` (use the PARI library: only available for real embeddings). @@ -710,10 +710,10 @@ def _compute_periods_complex(self, prec=None, normalise=True): INPUT: - - `prec` (int or ``None`` (default)) -- floating point precision (in bits); if None, + - ``prec`` (integer or ``None`` (default)) -- floating point precision (in bits); if ``None``, use the default precision. - - `normalise` (bool, default ``True``) -- whether to normalise the + - ``normalise`` (bool, default ``True``) -- whether to normalise the basis after computation. OUTPUT: @@ -854,7 +854,7 @@ def real_period(self, prec=None, algorithm='sage'): INPUT: - - ``prec`` (int or ``None`` (default)) -- real precision in + - ``prec`` (integer or ``None`` (default)) -- real precision in bits (default real precision if ``None``) - ``algorithm`` (string, default ``'sage'``) -- choice of @@ -1863,7 +1863,7 @@ def elliptic_exponential(self, z, to_curve=True): sage: L.elliptic_exponential(1e-100) (0.000000000000000 : 1.00000000000000 : 0.000000000000000) - The elliptic exponential of `z` is returned as (0 : 1 : 0) if + The elliptic exponential of `z` is returned as `(0 : 1 : 0)` if the coordinates of `z` with respect to the period lattice are approximately integral:: diff --git a/src/sage/schemes/hyperelliptic_curves/mestre.py b/src/sage/schemes/hyperelliptic_curves/mestre.py index b8cc8ca96be..899da8b1d3a 100644 --- a/src/sage/schemes/hyperelliptic_curves/mestre.py +++ b/src/sage/schemes/hyperelliptic_curves/mestre.py @@ -230,8 +230,8 @@ def Mestre_conic(i, xyz=False, names='u,v,w'): - ``i`` - list or tuple of length 4 containing the four Igusa-Clebsch invariants: I2, I4, I6, I10 - ``xyz`` - Boolean (default: ``False``) if ``True``, the algorithm also - returns three invariants x,y,z used in Mestre's algorithm - - ``names`` (default: 'u,v,w') - the variable names for the conic + returns three invariants `x`,`y`,`z` used in Mestre's algorithm + - ``names`` (default: ``'u,v,w'``) - the variable names for the conic OUTPUT: From dab424b4a68dd251ee64969d7e9888fc4392701f Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 14 Mar 2024 12:32:54 -0700 Subject: [PATCH 264/518] src/sage/schemes/elliptic_curves/period_lattice.py: Use :meth: more --- .../schemes/elliptic_curves/period_lattice.py | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/src/sage/schemes/elliptic_curves/period_lattice.py b/src/sage/schemes/elliptic_curves/period_lattice.py index 7698c276417..7cdc5dd6eee 100644 --- a/src/sage/schemes/elliptic_curves/period_lattice.py +++ b/src/sage/schemes/elliptic_curves/period_lattice.py @@ -39,8 +39,9 @@ True For a basis `\omega_1,\omega_2` normalised so that `\omega_1/\omega_2` -is in the fundamental region of the upper half-plane, use the function -``normalised_basis()`` instead:: +is in the fundamental region of the upper half-plane, use the method +:meth:`~sage.schemes.elliptic_curves.period_lattice.PeriodLattice_ell.normalised_basis` +instead:: sage: L.normalised_basis() # needs sage.rings.number_field (1.90726488608927 - 1.34047785962440*I, -1.90726488608927 - 1.34047785962440*I) @@ -165,8 +166,8 @@ def __init__(self, E, embedding=None): .. NOTE:: No periods are computed on creation of the lattice; see the - functions ``basis()``, ``normalised_basis()`` and - ``real_period()`` for precision setting. + methods :meth:`basis`, :meth:`normalised_basis` and + :meth:`real_period` for precision setting. EXAMPLES: @@ -403,9 +404,9 @@ def basis(self, prec=None, algorithm='sage'): - ``prec`` (default: ``None``) -- precision in bits (default precision if ``None``). - - ``algorithm`` (string, default 'sage') -- choice of - implementation (for real embeddings only) between 'sage' - (native Sage implementation) or 'pari' (use the PARI + - ``algorithm`` (string, default ``'sage'``) -- choice of + implementation (for real embeddings only) between ``'sage'`` + (native Sage implementation) or ``'pari'`` (use the PARI library: only available for real embeddings). OUTPUT: @@ -417,7 +418,7 @@ def basis(self, prec=None, algorithm='sage'): lattices) or `\frac{1}{2}` (for non-rectangular lattices). Otherwise, `\omega_1/\omega_2` is in the fundamental region of the upper half-plane. If the latter normalisation is required - for real lattices, use the function ``normalised_basis()`` + for real lattices, use the method :meth:`normalised_basis` instead. EXAMPLES:: @@ -488,9 +489,9 @@ def gens(self, prec=None, algorithm='sage'): - ``prec`` (default: ``None``) -- precision in bits (default precision if ``None``). - - ``algorithm`` (string, default 'sage') -- choice of - implementation (for real embeddings only) between 'sage' - (native Sage implementation) or 'pari' (use the PARI + - ``algorithm`` (string, default ``'sage'``) -- choice of + implementation (for real embeddings only) between ``'sage'`` + (native Sage implementation) or ``'pari'`` (use the PARI library: only available for real embeddings). OUTPUT: @@ -502,7 +503,7 @@ def gens(self, prec=None, algorithm='sage'): lattices) or `\frac{1}{2}` (for non-rectangular lattices). Otherwise, `\omega_1/\omega_2` is in the fundamental region of the upper half-plane. If the latter normalisation is required - for real lattices, use the function ``normalised_basis()`` + for real lattices, use the method :meth:`normalised_basis` instead. EXAMPLES:: @@ -537,8 +538,8 @@ def normalised_basis(self, prec=None, algorithm='sage'): the form `\ZZ\omega_1 + \ZZ\omega_2`. The basis is normalised so that `\omega_1/\omega_2` is in the fundamental region of the upper half-plane. For an alternative normalisation for - real lattices (with the first period real), use the function - basis() instead. + real lattices (with the first period real), use the method + :meth:`basis` instead. EXAMPLES:: @@ -982,7 +983,7 @@ def basis_matrix(self, prec=None, normalised=False): - ``normalised`` (bool, default ``False``) -- if ``True`` and the embedding is real, use the normalised basis (see - ``normalised_basis()``) instead of the default. + :meth:`normalised_basis`) instead of the default. OUTPUT: From 74c43cd9f82f4f94b4d8d5ca244dff8613ca1341 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 14 Mar 2024 17:07:51 -0700 Subject: [PATCH 265/518] src/sage/schemes/elliptic_curves/kraus.py: Use more math markup --- src/sage/schemes/elliptic_curves/kraus.py | 98 +++++++++++------------ 1 file changed, 49 insertions(+), 49 deletions(-) diff --git a/src/sage/schemes/elliptic_curves/kraus.py b/src/sage/schemes/elliptic_curves/kraus.py index 19895200687..1ca760de201 100644 --- a/src/sage/schemes/elliptic_curves/kraus.py +++ b/src/sage/schemes/elliptic_curves/kraus.py @@ -1,11 +1,11 @@ r""" Global and semi-global minimal models for elliptic curves over number fields -When E is an elliptic curve defined over a number field K of class +When `E` is an elliptic curve defined over a number field `K` of class number 1, then it has a global minimal model, and we have a method to -compute it, namely E.global_minimal_model(). Until Sage-6.7 this was +compute it, namely ``E.global_minimal_model()``. Until Sage-6.7 this was done using Tate's algorithm to minimise one prime at a time without -affecting the other primes. When the class number is not 1 a +affecting the other primes. When the class number is not 1, a different approach is used. In the general case global minimal models may or may not exist. This @@ -21,8 +21,8 @@ The implementation of this functionality is based on work of Kraus [Kra1989]_ which gives a local condition for when a pair of number field -elements \(c_4\), \(c_6\) belong to a Weierstrass model which is -integral at a prime \(P\), together with a global version. Only primes +elements `c_4`, `c_6` belong to a Weierstrass model which is +integral at a prime `P`, together with a global version. Only primes dividing 2 or 3 are hard to deal with. In order to compute the corresponding integral model one then needs to combine together the local transformations implicit in [Kra1989]_ into a single global one. @@ -56,7 +56,7 @@ def c4c6_nonsingular(c4, c6): r""" - Check if c4, c6 are integral with valid associated discriminant. + Check if `c_4`, `c_6` are integral with valid associated discriminant. INPUT: @@ -64,7 +64,7 @@ def c4c6_nonsingular(c4, c6): OUTPUT: - Boolean, True if c4, c6 are both integral and c4^3-c6^2 is a + Boolean, ``True`` if `c_4`, `c_6` are both integral and `c_4^3-c_6^2` is a nonzero multiple of 1728. EXAMPLES: @@ -141,11 +141,11 @@ def c4c6_model(c4, c6, assume_nonsingular=False): def make_integral(a, P, e): r""" - Returns b in O_K with P^e|(a-b), given a in O_{K,P}. + Return `b` in `O_K` with `P^e|(a-b)`, given `a` in `O_{K,P}`. INPUT: - - ``a`` -- a number field element integral at ``P`` + - ``a`` -- a number field element integral at `P` - ``P`` -- a prime ideal of the number field @@ -159,7 +159,7 @@ def make_integral(a, P, e): ALGORITHM: Totally naive, we simply test residues modulo `P^e` until one - works. We will only use this when P is a prime dividing 2 and e + works. We will only use this when `P` is a prime dividing 2 and `e` is the ramification degree, so the number of residues to check is at worst `2^d` where `d` is the degree of the field. @@ -191,7 +191,7 @@ def make_integral(a, P, e): def sqrt_mod_4(x, P): r""" - Returns a local square root mod 4, if it exists. + Return a local square root mod 4, if it exists. INPUT: @@ -201,8 +201,8 @@ def sqrt_mod_4(x, P): OUTPUT: - A pair (True, r) where that `r^2-x` has valuation at least `2e`, - or (False, 0) if there is no such `r`. Note that + A pair ``(True, r)`` where that `r^2-x` has valuation at least `2e`, + or ``(False, 0)`` if there is no such `r`. Note that `r^2\mod{P^{2e}}` only depends on `r\mod{P^e}`. EXAMPLES:: @@ -234,7 +234,7 @@ def sqrt_mod_4(x, P): def test_b2_local(c4, c6, P, b2, debug=False): r""" - Test if b2 gives a valid model at a prime dividing 3. + Test if `b_2` gives a valid model at a prime dividing 3. INPUT: @@ -246,8 +246,8 @@ def test_b2_local(c4, c6, P, b2, debug=False): OUTPUT: - The elliptic curve which is the (b2/12,0,0)-transform of - [0,0,0,-c4/48,-c6/864] if this is integral at P, else False. + The elliptic curve which is the `(b_2/12,0,0)`-transform of + `[0,0,0,-c_4/48,-c_6/864]` if this is integral at `P`, else ``False``. EXAMPLES:: @@ -259,7 +259,7 @@ def test_b2_local(c4, c6, P, b2, debug=False): sage: P3a, P3b = K.primes_above(3) sage: from sage.schemes.elliptic_curves.kraus import test_b2_local - b2=0 works at the first prime but not the second:: + `b_2=0` works at the first prime but not the second:: sage: b2 = 0 sage: test_b2_local(c4,c6,P3a,b2) # needs sage.rings.number_field @@ -269,7 +269,7 @@ def test_b2_local(c4, c6, P, b2, debug=False): sage: test_b2_local(c4,c6,P3b,b2) # needs sage.rings.number_field False - b2=-a works at the second prime but not the first:: + `b_2=-a` works at the second prime but not the first:: sage: b2 = -a # needs sage.rings.number_field sage: test_b2_local(c4,c6,P3a,b2,debug=True) # needs sage.rings.number_field @@ -280,7 +280,7 @@ def test_b2_local(c4, c6, P, b2, debug=False): y^2 = x^3 + (-1/4*a)*x^2 + (3784/3*a-192893/24)*x + (56378369/864*a-32879311/108) over Number Field in a with defining polynomial x^2 - 10 - Using CRT we can do both with the same b2:: + Using CRT we can do both with the same `b_2`:: sage: b2 = K.solve_CRT([0,-a],[P3a,P3b]); b2 # needs sage.rings.number_field a + 1 @@ -307,7 +307,7 @@ def test_b2_local(c4, c6, P, b2, debug=False): def test_b2_global(c4, c6, b2, debug=False): r""" - Test if b2 gives a valid model at all primes dividing 3. + Test if `b_2` gives a valid model at all primes dividing 3. INPUT: @@ -317,9 +317,9 @@ def test_b2_global(c4, c6, b2, debug=False): OUTPUT: - The elliptic curve which is the (b2/12,0,0)-transform of - [0,0,0,-c4/48,-c6/864] if this is integral at all primes P - dividing 3, else False. + The elliptic curve which is the `(b_2/12,0,0)`-transform of + `[0,0,0,-c_4/48,-c_6/864]` if this is integral at all primes `P` + dividing 3, else ``False``. EXAMPLES:: @@ -356,7 +356,7 @@ def test_b2_global(c4, c6, b2, debug=False): def check_Kraus_local_3(c4, c6, P, assume_nonsingular=False, debug=False): r""" - Test if c4,c6 satisfy Kraus's conditions at a prime P dividing 3. + Test if `c_4`, `c_6` satisfy Kraus's conditions at a prime `P` dividing 3. INPUT: @@ -364,14 +364,14 @@ def check_Kraus_local_3(c4, c6, P, assume_nonsingular=False, debug=False): - ``P`` -- a prime ideal of the number field which divides 3 - - ``assume_nonsingular`` (boolean, default False) -- if True, + - ``assume_nonsingular`` (boolean, default ``False``) -- if ``True``, check for integrality and nosingularity. OUTPUT: - Either (False, 0) if Kraus's conditions fail, or (True, b2) if + Either ``(False, 0)`` if Kraus's conditions fail, or ``(True, b2)`` if they pass, in which case the elliptic curve which is the - (b2/12,0,0)-transform of [0,0,0,-c4/48,-c6/864] is integral at P. + `(b_2/12,0,0)`-transform of `[0,0,0,-c_4/48,-c_6/864]` is integral at `P`. EXAMPLES:: @@ -429,7 +429,7 @@ def check_Kraus_local_3(c4, c6, P, assume_nonsingular=False, debug=False): def test_a1a3_local(c4, c6, P, a1, a3, debug=False): r""" - Test if a1,a3 are valid at a prime P dividing 2. + Test if `a_1`, `a_3` are valid at a prime `P` dividing `2`. INPUT: @@ -441,8 +441,8 @@ def test_a1a3_local(c4, c6, P, a1, a3, debug=False): OUTPUT: - The elliptic curve which is the (a1^2/12,a1/2,a3/2)-transform of - [0,0,0,-c4/48,-c6/864] if this is integral at P, else False. + The elliptic curve which is the `(a_1^2/12,a_1/2,a_3/2)`-transform of + `[0,0,0,-c_4/48,-c_6/864]` if this is integral at `P`, else ``False``. EXAMPLES:: @@ -475,7 +475,7 @@ def test_a1a3_local(c4, c6, P, a1, a3, debug=False): def test_a1a3_global(c4, c6, a1, a3, debug=False): r""" - Test if a1,a3 are valid at all primes P dividing 2. + Test if `a_1`, `a_3` are valid at all primes `P` dividing 2. INPUT: @@ -485,8 +485,8 @@ def test_a1a3_global(c4, c6, a1, a3, debug=False): OUTPUT: - The elliptic curve which is the (a1^2/12,a1/2,a3/2)-transform of - [0,0,0,-c4/48,-c6/864] if this is integral at all primes P + The elliptic curve which is the `(a_1^2/12,a_1/2,a_3/2)`-transform of + `[0,0,0,-c_4/48,-c_6/864]` if this is integral at all primes `P` dividing 2, else ``False``. EXAMPLES:: @@ -519,7 +519,7 @@ def test_a1a3_global(c4, c6, a1, a3, debug=False): def test_rst_global(c4, c6, r, s, t, debug=False): r""" - Test if the (r,s,t)-transform of the standard c4,c6-model is integral. + Test if the `(r,s,t)`-transform of the standard `c_4,c_6`-model is integral. INPUT: @@ -529,8 +529,8 @@ def test_rst_global(c4, c6, r, s, t, debug=False): OUTPUT: - The elliptic curve which is the (r,s,t)-transform of - [0,0,0,-c4/48,-c6/864] if this is integral at all primes P, else + The elliptic curve which is the `(r,s,t)`-transform of + `[0,0,0,-c_4/48,-c_6/864]` if this is integral at all primes `P`, else ``False``. EXAMPLES:: @@ -581,7 +581,7 @@ def test_rst_global(c4, c6, r, s, t, debug=False): def check_Kraus_local_2(c4, c6, P, a1=None, assume_nonsingular=False): r""" - Test if c4,c6 satisfy Kraus's conditions at a prime P dividing 2. + Test if `c_4`, `c_6` satisfy Kraus's conditions at a prime `P` dividing 2. INPUT: @@ -598,9 +598,9 @@ def check_Kraus_local_2(c4, c6, P, a1=None, assume_nonsingular=False): Either ``(False, 0, 0)`` if Kraus's conditions fail, or ``(True, a1, a3)`` if they pass, in which case the elliptic curve which is the - (a1**2/12,a1/2,a3/2)-transform of [0,0,0,-c4/48,-c6/864] is - integral at P. If a1 is provided and valid then the output will - be ``(True, a1, a3)`` for suitable a3. + `(a_1^2/12,a_1/2,a_3/2)`-transform of `[0,0,0,-c_4/48,-c_6/864]` is + integral at `P`. If `a_1` is provided and valid then the output will + be ``(True, a1, a3)`` for suitable `a_3`. EXAMPLES:: @@ -673,7 +673,7 @@ def check_Kraus_local_2(c4, c6, P, a1=None, assume_nonsingular=False): def check_Kraus_local(c4, c6, P, assume_nonsingular=False): r""" - Check Kraus's conditions locally at a prime P. + Check Kraus's conditions locally at a prime `P`. INPUT: @@ -686,8 +686,8 @@ def check_Kraus_local(c4, c6, P, assume_nonsingular=False): OUTPUT: - Tuple: either ``(True, E)`` if there is a Weierstrass model E integral - at P and with invariants c4, c6, or ``(False, None)`` if there is + Tuple: either ``(True, E)`` if there is a Weierstrass model `E` integral + at `P` and with invariants `c_4`, `c_6`, or ``(False, None)`` if there is none. EXAMPLES:: @@ -747,7 +747,7 @@ def check_Kraus_local(c4, c6, P, assume_nonsingular=False): def check_Kraus_global(c4, c6, assume_nonsingular=False, debug=False): r""" - Test if c4,c6 satisfy Kraus's conditions at all primes. + Test if `c_4`, `c_6` satisfy Kraus's conditions at all primes. INPUT: @@ -759,7 +759,7 @@ def check_Kraus_global(c4, c6, assume_nonsingular=False, debug=False): OUTPUT: Either ``False`` if Kraus's conditions fail, or, if they pass, an - elliptic curve E which is integral and has c-invariants c4,c6. + elliptic curve `E` which is integral and has c-invariants `c_4`, `c_6`. EXAMPLES:: @@ -931,12 +931,12 @@ def semi_global_minimal_model(E, debug=False): OUTPUT: - A tuple ``(Emin, I)`` where Emin is an elliptic curve which is either a - global minimal model of E if one exists (i.e., an integral model + A tuple ``(Emin, I)`` where ``Emin`` is an elliptic curve which is either a + global minimal model of `E` if one exists (i.e., an integral model which is minimal at every prime), or a semi-global minimal model (i.e., an integral model which is minimal at every prime except - one). I is the unit ideal of Emin is a global minimal model, else - is the unique prime at which Emin is not minimal. Thus in all + one). `I` is the unit ideal of ``Emin`` is a global minimal model, else + is the unique prime at which ``Emin`` is not minimal. Thus in all cases, ``Emin.minimal_discriminant_ideal() * I**12 == (E.discriminant())``. From a55ee287ac08ab13b551c51f804b378683c52fed Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 15 Mar 2024 15:00:59 -0700 Subject: [PATCH 266/518] GenericGraph.export_to_file: Update documentation for removed format='yaml' --- src/sage/graphs/generic_graph.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/sage/graphs/generic_graph.py b/src/sage/graphs/generic_graph.py index 844672fbf05..4d147efcd2f 100644 --- a/src/sage/graphs/generic_graph.py +++ b/src/sage/graphs/generic_graph.py @@ -1338,7 +1338,7 @@ def export_to_file(self, filename, format=None, **kwds): explicitly. If set to ``None`` (default), the format is set to be the file extension of ``filename``. Admissible formats are: ``'adjlist'``, ``'dot'``, ``'edgelist'``, ``'gexf'``, ``'gml'``, ``'graphml'``, - ``'multiline_adjlist'``, ``'pajek'``, ``'yaml'``. + ``'multiline_adjlist'``, ``'pajek'``. - All other arguments are forwarded to the subfunction. For more information, see their respective documentation: @@ -1356,7 +1356,6 @@ def export_to_file(self, filename, format=None, **kwds): ``'graphml'`` | :func:`networkx.readwrite.graphml.write_graphml` ``'multiline_adjlist'`` | :func:`networkx.readwrite.multiline_adjlist.write_multiline_adjlist` ``'pajek'`` | :func:`networkx.readwrite.pajek.write_pajek` - ``'yaml'`` | :func:`networkx.readwrite.nx_yaml.write_yaml` .. SEEALSO:: From 78cf7db6b6ebf42ae1350bce1166683bc7bff53f Mon Sep 17 00:00:00 2001 From: Mumtaz Ali Shah Date: Sat, 16 Mar 2024 05:21:33 +0500 Subject: [PATCH 267/518] Updated README.md, added space after -p option --- docker/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/README.md b/docker/README.md index 14c49b5d4fe..190793c202d 100644 --- a/docker/README.md +++ b/docker/README.md @@ -24,7 +24,7 @@ There are several flavours of this image. ``` You can start a graphical [Jupyter Notebook](https://jupyter.org) at http://localhost:8888 instead. To use the notebook, follow the instructions printed when you run: ``` - docker run -p8888:8888 sagemath/sagemath:latest sage-jupyter + docker run -p 8888:8888 sagemath/sagemath:latest sage-jupyter ``` * [`sagemath/sagemath-dev`![image size](https://img.shields.io/microbadger/image-size/sagemath/sagemath-dev.svg)](https://hub.docker.com/r/sagemath/sagemath-dev) contains all the build artifacts to rebuild Sage quickly (currently, this is broken, see [#34241](https://github.com/sagemath/sage/issues/34241).) This version is probably only relevant for Sage developers. Run this image with: ``` From 549bba64652b0ce989887e09a47e018312de50cb Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Sun, 17 Mar 2024 00:09:53 +0900 Subject: [PATCH 268/518] Implementation of the Potts representation of the partition algebra. --- src/doc/en/reference/references/index.rst | 5 + src/sage/combinat/diagram_algebras.py | 598 ++++++++++++++++++++++ 2 files changed, 603 insertions(+) diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index 09b78514e1c..659ffa3b2b3 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -4805,6 +4805,11 @@ REFERENCES: Journal of Algebra 177 (1995), no. 3, 967-982. http://www.lacim.uqam.ca/~christo/Publi%C3%A9s/1995/Duality.pdf +.. [MR1998] \P. P. Martin and G. Rollet. + *The Potts model representation and a Robinson-Schensted + correspondence for the partition algebra*. + Compositoio Math., **112** (1998), pp. 237-254. + .. [MR2002] \S. Murphy, M. Robshaw *Essential Algebraic Structure Within the AES*; in Advances in Cryptology \- CRYPTO 2002; LNCS 2442; Springer Verlag 2002 diff --git a/src/sage/combinat/diagram_algebras.py b/src/sage/combinat/diagram_algebras.py index 15837e34128..d2d770a6d4d 100644 --- a/src/sage/combinat/diagram_algebras.py +++ b/src/sage/combinat/diagram_algebras.py @@ -11,6 +11,8 @@ ``*Diagram`` classes and other methods to improve diagram algebras. - Mike Zabrocki (2018): Implementation of individual element diagram classes - Aaron Lauve, Mike Zabrocki (2018): Implementation of orbit basis for Partition algebra. +- Travis Scrimshaw (2024): Implemented the Potts representation of the + partition algebra. """ # **************************************************************************** @@ -3103,6 +3105,38 @@ def jucys_murphy_element(self, i): L = jucys_murphy_element + def potts_representation(self, y=None): + r""" + Return the :class:`PottsRepresentation` with magnetic field + direction ``Y`` of ``self``. + + .. NOTE:: + + The deformation parameter `d` of ``self`` must be a + positive integer. + + INPUT: + + - ``y`` -- (option) an integer between 1 and `d`; ignored + if the order of ``self`` is an integer, otherwise the + default is `1` + + EXAMPLES:: + + sage: PA = algebras.Partition(5/2, QQ(4)) + sage: PR = PA.potts_representation() + + sage: PA = algebras.Partition(5/2, 3/2) + sage: PA.potts_representation() + Traceback (most recent call last): + ... + ValueError: the partition algebra deformation parameter must + be a positive integer + """ + if self.order() not in ZZ and y is None: + y = ZZ.one() + return PottsRepresentation(self, y) + class Element(DiagramBasis.Element): def to_orbit_basis(self): """ @@ -4536,6 +4570,570 @@ def sgn(x): output = output + "\\end{tikzpicture}" #end picture return output + +class PottsRepresentation(CombinatorialFreeModule): + r""" + The Potts representation of the partition algebra. + + Let `P_n(d)` be the :class:`PartitionAlgebra` over `R` with the + deformation parameter `d \in \ZZ_{>0}` being a positive integer. + Recall the multiplication convention of diagrams in `P_n(d)` + computing `D D'` by placing `D` above `D'`. + + The *Potts representation* is the right `P_n(d)`-module on + `M = V^{\otimes n}`, with `V = R^d`, with the action given as follows. + We identify the natural basis vectors in `M` with words of length `n` + in the alphabet `\{1, \dotsc, d\}` (which we call colors). For a basis + vector `w` and diagram `D`, define `w \cdot D` as the sum over all `v` + such that every part in `w D v` (consider this as coloring the nodes + of `D`) is given by the same color. + + If `n` is a half integer, then there is an extra fixed color for the + node `\lceil n \rceil`, which is called the *magnetic field direction* + from the physics interpretation of this representation. + + EXAMPLES: + + In this example, we consider `R = \QQ` and use the Potts representation + to construct the centralizer algebra of the left `S_{d-1}`-action on + `V^{\otimes n}` with `V = \QQ^d` being the permutation action. :: + + sage: PA = algebras.Partition(5/2, QQ(2)) + sage: PR = PA.potts_representation(2) + sage: mats = [PR.representation_matrix(x) for x in PA.basis()] + sage: MS = mats[0].parent() + sage: CM = MS.submodule(mats) + sage: CM.dimension() + 16 + + We check that this commutes with the `S_{d-1}`-action:: + + sage: all((g * v) * x == g * (v * x) for g in PR.symmetric_group() + ....: for v in PR.basis() for x in PA.basis()) + True + + Next, we see that the centralizer of the `S_d`-action is smaller + than the semisimple quotient of the partition algebra:: + + sage: PA.dimension() + 52 + sage: len(PA.radical_basis()) + 9 + sage: SQ = PA.semisimple_quotient() + sage: SQ.dimension() + 43 + + Next, we get orthogonal idempontents that project onto the central + orthogonal idempotents in the semisimple quotient and construct + the corresponding Peirce summands `e_i P_n(d) e_i`:: + + sage: # long time + sage: orth_idems = PA.orthogonal_idempotents_central_mod_radical() + sage: algs = [PA.peirce_summand(idm, idm) for idm in orth_idems] + sage: [A.dimension() for A in algs] + [16, 2, 1, 25] + + We saw that we obtain the entire endomorphism algebra since `d = 2` + and `S_{d-1}` is the trivial group. Hence, the 16 dimensional Peirce + summand computed above is isomorphic to this endomorphims algebra + (both are `4 \times 4` matrix algebras over `\QQ`). Hence, we have a + natural quotient construction of the centralizer algebra from the + partition algebra. + + Next, we consider a case with a nontrivial `S_d`-action (now it is `S_d` + since the partition algebra has integer rank). We perform the same + computations as before:: + + sage: PA = algebras.Partition(2, QQ(2)) + sage: PA.dimension() + 15 + sage: PA.semisimple_quotient().dimension() + 10 + sage: orth_idems = PA.orthogonal_idempotents_central_mod_radical() + sage: algs = [PA.peirce_summand(idm, idm) for idm in orth_idems] + sage: [A.dimension() for A in algs] + [4, 2, 4, 1] + + sage: PR = PA.potts_representation() + sage: mats = [PR.representation_matrix(x) for x in PA.basis()] + sage: MS = mats[0].parent() + sage: cat = Algebras(QQ).WithBasis().Subobjects() + sage: CM = MS.submodule(mats, category=cat) + sage: CM.dimension() + 8 + + To do the remainder of the computation, we need to monkey patch a + ``product_on_basis`` method:: + + sage: CM.product_on_basis + NotImplemented + sage: CM.product_on_basis = lambda x,y: CM.retract(CM.basis()[x].lift() * CM.basis()[y].lift()) + sage: CM.orthogonal_idempotents_central_mod_radical() + (1/2*B[0] + 1/2*B[3] + 1/2*B[5] + 1/2*B[6], + 1/2*B[0] - 1/2*B[3] + 1/2*B[5] - 1/2*B[6]) + sage: CM.peirce_decomposition() + [[Free module generated by {0, 1, 2, 3} over Rational Field, + Free module generated by {} over Rational Field], + [Free module generated by {} over Rational Field, + Free module generated by {0, 1, 2, 3} over Rational Field]] + + Hence, we see that the centralizer algebra is isomorphic to a product + of two `2 \times 2` matrix algebras (over `\QQ`), which are naturally + a part of the partition algebra decomposition. + + Lastly, we verify the commuting actions:: + + sage: all((g * v) * x == g * (v * x) for g in PR.symmetric_group() + ....: for v in PR.basis() for x in PA.basis()) + True + + REFERENCES: + + - [MR1998]_ + """ + def __init__(self, PA, y): + r""" + Initialize ``self``. + + EXAMPLES:: + + sage: PA = algebras.Partition(5/2, QQ(4)) + sage: PR = PA.potts_representation() + sage: TestSuite(PR).run() + + sage: PA = algebras.Partition(5/2, QQ(2)) # not semisimple + sage: PR = PA.potts_representation() + sage: TestSuite(PR).run() + sage: PR = PA.potts_representation(2) + sage: TestSuite(PR).run() + + :: + + sage: PA = algebras.Partition(2, QQ(4)) + sage: PR = PA.potts_representation() + sage: TestSuite(PR).run() + + sage: PA = algebras.Partition(3, QQ(2)) # not semisimple + sage: PR = PA.potts_representation() + sage: TestSuite(PR).run() + + TESTS:: + + sage: PA = algebras.Partition(3, QQ(0)) + sage: PA.potts_representation() + Traceback (most recent call last): + ... + ValueError: the partition algebra deformation parameter must be a positive integer + + sage: PA = algebras.Partition(3, QQ(2)) + sage: PA.potts_representation(1) + Traceback (most recent call last): + ... + ValueError: the magnetic field direction should not be given for integer rank + + sage: PA = algebras.Partition(5/2, QQ(4)) + sage: PA.potts_representation(6) + Traceback (most recent call last): + ... + ValueError: the magnetic field direction must be an integer in [1, 4] + sage: PA.potts_representation(0) + Traceback (most recent call last): + ... + ValueError: the magnetic field direction must be an integer in [1, 4] + sage: PA.potts_representation(3/2) + Traceback (most recent call last): + ... + ValueError: the magnetic field direction must be an integer in [1, 4] + """ + if PA._q not in ZZ or PA._q <= 0: + raise ValueError("the partition algebra deformation parameter must be a positive integer") + self._d = ZZ(PA._q) + self._PA = PA + order = PA.order() + if order not in ZZ: + order = order.floor() + if y not in ZZ or y < 1 or y > self._d: + raise ValueError(f"the magnetic field direction must be an integer in [1, {self._d}]") + y = ZZ(y) + else: + if y is not None: + raise ValueError("the magnetic field direction should not be given for integer rank") + self._y = y + # _order is used to define the ordering of the basis elements + self._num_factors = ZZ(order) + from sage.combinat.words.words import Words + indices = Words(self._d, self._num_factors) + R = PA.base_ring() + from sage.categories.modules_with_basis import ModulesWithBasis + cat = ModulesWithBasis(R).FiniteDimensional() + CombinatorialFreeModule.__init__(self, R, indices, prefix='P', category=cat) + + from sage.groups.perm_gps.permgroup_named import SymmetricGroup + if self._y is None: + self._symgp = SymmetricGroup(self._d) + else: + self._symgp = SymmetricGroup([i for i in range(1, self._d+1) if i != self._y]) + self._sga = self._symgp.algebra(R) + + def _repr_(self): + r""" + Return a string representation of ``self``. + + EXAMPLES:: + + sage: PA = algebras.Partition(5/2, QQ(4)) + sage: PA.potts_representation(2) + Potts representation with 4 colors and magnetic field direction 2 of + Partition Algebra of rank 5/2 with parameter 4 over Rational Field + + sage: PA = algebras.Partition(3, QQ(4)) + sage: PA.potts_representation() + Potts representation with 4 colors of + Partition Algebra of rank 3 with parameter 4 over Rational Field + """ + if self._y is not None: + return "Potts representation with {} colors and magnetic field direction {} of {}".format(self._d, self._y, self._PA) + return "Potts representation with {} colors of {}".format(self._d, self._PA) + + def _test_representation(self, **options): + r""" + Test that ``self`` is a representation. + + EXAMPLES:: + + sage: PA = algebras.Partition(4, QQ(3)) + sage: PR = PA.potts_representation() + sage: PR._test_representation() + """ + tester = self._tester(**options) + S = tester.some_elements() + B = self._PA.basis() + from sage.misc.misc import some_tuples + for x, y in some_tuples(B, 2, tester._max_runs // len(S)): + for v in S: + tester.assertEqual((v * x) * y, v * (x * y)) + + def _monomial(self, index): + """ + Consturct a monomial of ``self`` from ``index``. + + EXAMPLES:: + + sage: PA = algebras.Partition(4, QQ(3)) + sage: PR = PA.potts_representation() + sage: PR._monomial([1, 2, 1, 3]) + P[word: 1213] + """ + return super()._monomial(self._indices(index)) + + def __getitem__(self, ind): + r""" + Return the basis element indexed by ``ind``. + + EXAMPLES:: + + sage: PA = algebras.Partition(4, QQ(3)) + sage: PR = PA.potts_representation() + sage: PR[1,2,1,3] + P[word: 1213] + + sage: PA = algebras.Partition(1, QQ(4)) + sage: PR = PA.potts_representation() + sage: PR[2] + P[word: 2] + sage: PR[10] + Traceback (most recent call last): + ... + AttributeError: 'PottsRepresentation_with_category' object has no attribute 'list' + """ + try: + if self._num_factors == 1 and ind in ZZ: + ind = self._indices([ind]) + else: + ind = self._indices(ind) + except (TypeError, ValueError): + return super().__getitem__(ind) + return self.monomial(ind) + + def partition_algebra(self): + r""" + Return the partition algebra that ``self`` is a representation of. + + EXAMPLES:: + + sage: PA = algebras.Partition(3, QQ(4)) + sage: PR = PA.potts_representation() + sage: PR.partition_algebra() is PA + True + """ + return self._PA + + def number_of_factors(self): + r""" + Return the number of factors defining ``self``. + + EXAMPLES:: + + sage: PA = algebras.Partition(7/2, QQ(4)) + sage: PR = PA.potts_representation() + sage: PR.number_of_factors() + 3 + """ + return self._num_factors + + def number_of_colors(self): + r""" + Return the number of colors defining ``self``. + + EXAMPLES:: + + sage: PA = algebras.Partition(3, QQ(4)) + sage: PR = PA.potts_representation() + sage: PR.number_of_colors() + 4 + """ + return self._d + + def magnetic_field_direction(self): + r""" + Return the magnetic field direction defining ``self``. + + EXAMPLES:: + + sage: PA = algebras.Partition(7/2, QQ(4)) + sage: PR = PA.potts_representation(2) + sage: PR.magnetic_field_direction() + 2 + """ + return self._y + + def symmetric_group(self): + r""" + Return the symmetric group that naturally acts on ``self``. + + EXAMPLES:: + + sage: PA = algebras.Partition(3, QQ(4)) + sage: PR = PA.potts_representation() + sage: PR.symmetric_group() + Symmetric group of order 4! as a permutation group + + sage: PA = algebras.Partition(7/2, QQ(4)) + sage: PR = PA.potts_representation() + sage: PR.symmetric_group().domain() + {2, 3, 4} + sage: PR = PA.potts_representation(2) + sage: PR.symmetric_group().domain() + {1, 3, 4} + sage: PR = PA.potts_representation(4) + sage: PR.symmetric_group().domain() + {1, 2, 3} + """ + return self._symgp + + def _basis_action(self, word, diagram): + r""" + Return the action of the partition algebra basis element indexed + by ``diagram`` on the basis element of ``self`` indexed by ``word``. + + EXAMPLES:: + + sage: PA = algebras.Partition(9/2, QQ(4)) + sage: PR = PA.potts_representation(2) + sage: D = PA._indices([[1,2],[3,-3],[-1,-2,-4],[4,5,-5]]) + sage: PR._basis_action(PR._indices([1,1,3,4]), D) + 0 + sage: PR._basis_action(PR._indices([1,2,3,2]), D) + 0 + sage: PR._basis_action(PR._indices([1,1,3,2]), D) + P[word: 1131] + P[word: 2232] + P[word: 3333] + P[word: 4434] + """ + order = self._num_factors + fixed = [None] * self._num_factors + word = [None] + list(word) + [self._y] # make this 1-based indexing + neg_parts = [] + for part in diagram: + if part[-1] < 0: + # This should never happen since it should be a propagating block + assert -part[0] != order + 1 + neg_parts.append(part) + continue + color = word[part[-1]] + for i in reversed(part): + if i > 0: + if word[i] != color: + return self.zero() + else: # i < 0 + if -i == order + 1: + assert color == self._y + else: + fixed[-i-1] = color # convert 1-based to 0-based + if not neg_parts: + return self._monomial(fixed) + + import itertools + ret = [] + for c in itertools.product(range(1, self._d+1), repeat=len(neg_parts)): + temp = list(fixed) # make a copy + for color, part in zip(c, neg_parts): + for j in part: + temp[-j-1] = color # convert 1-based to 0-based + ret.append(self._indices(temp)) + return self.sum_of_monomials(ret) + + def _sym_group_action(self, g, word): + r""" + Return the action of the symmetric group element ``g`` + on the basis element of ``self`` indexed by ``word``. + + EXAMPLES:: + + sage: PA = algebras.Partition(9/2, QQ(4)) + sage: PR = PA.potts_representation(2) + sage: G = PR.symmetric_group() + sage: g = G.an_element(); g + (3,4) + sage: PR._sym_group_action(g, PR._indices([1,2,3,4])) + P[word: 1243] + """ + return self._monomial([g(i) for i in word]) + + def representation_matrix(self, elt): + r""" + Return the representation matrix of ``self`` in ``self``. + + EXAMPLES:: + + sage: PA = algebras.Partition(7/2, QQ(2)) + sage: PR = PA.potts_representation() + sage: PR.representation_matrix(PA.an_element()) + [7 0 3 0 2 0 0 0] + [0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0] + + sage: all(b.to_vector() * PR.representation_matrix(x) # long time + ....: == (b * x).to_vector() + ....: for b in PR.basis() for x in PA.basis()) + True + + sage: PA = algebras.Partition(2, QQ(2)) + sage: PR = PA.potts_representation() + sage: [PR.representation_matrix(x) for x in PA.basis()] + [ + [1 0 0 0] [1 0 1 0] [1 1 0 0] [1 0 0 1] [1 1 1 1] [1 0 0 0] + [0 0 0 0] [0 0 0 0] [0 0 0 0] [0 0 0 0] [0 0 0 0] [1 0 0 0] + [0 0 0 0] [0 0 0 0] [0 0 0 0] [0 0 0 0] [0 0 0 0] [0 0 0 1] + [0 0 0 1], [0 1 0 1], [0 0 1 1], [1 0 0 1], [1 1 1 1], [0 0 0 1], + + [1 0 0 0] [1 0 1 0] [1 0 0 0] [1 0 0 0] [1 0 1 0] [1 1 0 0] + [0 0 1 0] [1 0 1 0] [0 1 0 0] [0 0 0 1] [0 1 0 1] [1 1 0 0] + [0 1 0 0] [0 1 0 1] [0 0 1 0] [1 0 0 0] [1 0 1 0] [0 0 1 1] + [0 0 0 1], [0 1 0 1], [0 0 0 1], [0 0 0 1], [0 1 0 1], [0 0 1 1], + + [1 1 0 0] [1 0 0 1] [1 1 1 1] + [0 0 1 1] [1 0 0 1] [1 1 1 1] + [1 1 0 0] [1 0 0 1] [1 1 1 1] + [0 0 1 1], [1 0 0 1], [1 1 1 1] + ] + + sage: PA = algebras.Partition(5/2, QQ(2)) + sage: PR = PA.potts_representation() + sage: all(PR.representation_matrix(x) * PR.representation_matrix(y) # long time + ....: == PR.representation_matrix(x * y) + ....: for x in PA.basis() for y in PA.basis()) + True + + sage: PA = algebras.Partition(2, QQ(4)) + sage: PR = PA.potts_representation() + sage: all(PR.representation_matrix(x) * PR.representation_matrix(y) + ....: == PR.representation_matrix(x * y) + ....: for x in PA.basis() for y in PA.basis()) + True + """ + from sage.matrix.constructor import matrix + return matrix([(b * elt).to_vector() for b in self.basis()]) + + class Element(CombinatorialFreeModule.Element): + def _acted_upon_(self, scalar, self_on_left=True): + """ + Return the action of ``scalar`` on ``self``. + + EXAMPLES:: + + sage: PA = algebras.Partition(5/2, QQ(4)) + sage: PR = PA.potts_representation() + sage: v = PR.an_element(); v + P[word: 11] + 3*P[word: 12] + 3*P[word: 13] + sage: 3 * v + 3*P[word: 11] + 9*P[word: 12] + 9*P[word: 13] + sage: v * 3 + 3*P[word: 11] + 9*P[word: 12] + 9*P[word: 13] + sage: x = PA.an_element(); x + 2*P{{-3, -2, -1, 1, 2, 3}} + 2*P{{-3, -2, 1, 2, 3}, {-1}} + + 3*P{{-3, -1, 1, 2, 3}, {-2}} + sage: v * x + 7*P[word: 11] + 3*P[word: 12] + 3*P[word: 13] + 3*P[word: 14] + + 2*P[word: 21] + 2*P[word: 31] + 2*P[word: 41] + sage: x * v + Traceback (most recent call last): + ... + TypeError: unsupported operand parent(s) for *: + 'Partition Algebra ...' and 'Potts representation ...' + + sage: PA = algebras.Partition(2, QQ(4)) + sage: PR = PA.potts_representation() + sage: v = PR.an_element(); v + P[word: 11] + 3*P[word: 12] + 3*P[word: 13] + sage: G = PR.symmetric_group() + sage: g = G.an_element(); g + (2,3,4) + sage: g * v + P[word: 11] + 3*P[word: 13] + 3*P[word: 14] + sage: SGA = G.algebra(ZZ) + sage: a = SGA.an_element(); a + () + (2,3,4) + 2*(1,3)(2,4) + 3*(1,4)(2,3) + sage: a * v + 2*P[word: 11] + 3*P[word: 12] + 6*P[word: 13] + 3*P[word: 14] + + 6*P[word: 31] + 2*P[word: 33] + 6*P[word: 34] + + 9*P[word: 42] + 9*P[word: 43] + 3*P[word: 44] + """ + # Check for scalars first + ret = super()._acted_upon_(scalar, self_on_left) + if ret is not None: + return ret + + par = self.parent() + + if not self_on_left: + # left action, so convert to the symmetric group algebra + sP = scalar.parent() + if sP is par._symgp: + scalar = par._sga.monomial(scalar) + sP = par._sga + if sP is not par._sga: + try: + scalar = par._sga(scalar) + except (ValueError, TypeError): + return None + return par.linear_combination((par._sym_group_action(g, wd), cg * cr) + for (wd, cr) in self._monomial_coefficients.items() + for (g, cg) in scalar.monomial_coefficients(copy=False).items()) + + # right action, so convert to the partition algebra + try: + scalar = par._PA(scalar) + except (ValueError, TypeError): + return None + return par.linear_combination((par._basis_action(wd, diag), cr * ca) + for (wd, cr) in self._monomial_coefficients.items() + for (diag, ca) in scalar.monomial_coefficients(copy=False).items()) + + ######################################################################### # START BORROWED CODE ######################################################################### From e81a24c830204d533d86ebd26c4e436047bceffa Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Sat, 16 Mar 2024 23:02:31 +0000 Subject: [PATCH 269/518] fix issue 37621 --- src/sage/rings/polynomial/polynomial_element.pyx | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/sage/rings/polynomial/polynomial_element.pyx b/src/sage/rings/polynomial/polynomial_element.pyx index 99f00618bc1..8b58e403d3d 100644 --- a/src/sage/rings/polynomial/polynomial_element.pyx +++ b/src/sage/rings/polynomial/polynomial_element.pyx @@ -12806,6 +12806,18 @@ cdef class Polynomial_generic_dense_inexact(Polynomial_generic_dense): ... PrecisionError: the leading coefficient is indistinguishable from 0 + TESTS: + + Ensure that :issue:`37621` is fixed:: + + sage: k. = QQ[] + sage: K = Qp(11,5) + sage: L. = K.extension(x^20-11) + sage: R. = L[] + sage: f = R.random_element() + sage: type(f.degree()) + + AUTHOR: - Xavier Caruso (2013-03) @@ -12822,7 +12834,7 @@ cdef class Polynomial_generic_dense_inexact(Polynomial_generic_dense): d -= 1 else: break - return d + return Integer(d) def prec_degree(self): r""" From bca6350045e80c1586dced3b3c887c8fc91fd4b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Sun, 17 Mar 2024 10:54:00 +0100 Subject: [PATCH 270/518] fix suggested details --- src/sage/algebras/schur_algebra.py | 8 +++----- src/sage/algebras/steenrod/steenrod_algebra_bases.py | 4 ++++ 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/sage/algebras/schur_algebra.py b/src/sage/algebras/schur_algebra.py index ef809e2ab01..6e0b6ad8583 100644 --- a/src/sage/algebras/schur_algebra.py +++ b/src/sage/algebras/schur_algebra.py @@ -663,13 +663,11 @@ def GL_irreducible_character(n, mu, KK): # The kernel of this pairing is the part of this graded piece which is # not in the irreducible module for \mu. - length = len(carter_lusztig) - phi = mbasis.zero() for aa, c_aa in enumerate(contents): - mat = [[graded_basis[aa][kk].inner_product(carter_lusztig[j]) - for j in range(length)] - for kk in range(len(JJ[aa]))] + mat = [[elt_basis_aa.inner_product(elt_carter_lusztig) + for elt_carter_lusztig in carter_lusztig] + for elt_basis_aa in graded_basis[aa]] angle = Matrix(mat) phi += (len(JJ[aa]) - angle.nullity()) * mbasis(c_aa) return phi diff --git a/src/sage/algebras/steenrod/steenrod_algebra_bases.py b/src/sage/algebras/steenrod/steenrod_algebra_bases.py index 302815af15f..f439827ff69 100644 --- a/src/sage/algebras/steenrod/steenrod_algebra_bases.py +++ b/src/sage/algebras/steenrod/steenrod_algebra_bases.py @@ -399,6 +399,10 @@ def restricted_partitions(n, l, no_repeats=False): is ignored, so it does not work. (At the moment, the ``no_repeats=True`` case is the only one used in the code.) + .. TODO:: + + This should be re-implemented in a non-recursive way. + EXAMPLES:: sage: from sage.algebras.steenrod.steenrod_algebra_bases import restricted_partitions From 2d0e936729a019b17ef14c158676aa8e8cc1d67d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20K=C3=B6ppe?= Date: Sun, 17 Mar 2024 10:36:35 -0700 Subject: [PATCH 271/518] Apply suggestions from code review Co-authored-by: Giacomo Pope <44242839+GiacomoPope@users.noreply.github.com> --- src/sage/schemes/elliptic_curves/kraus.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/schemes/elliptic_curves/kraus.py b/src/sage/schemes/elliptic_curves/kraus.py index 1ca760de201..b117e8f7ba7 100644 --- a/src/sage/schemes/elliptic_curves/kraus.py +++ b/src/sage/schemes/elliptic_curves/kraus.py @@ -113,8 +113,8 @@ def c4c6_model(c4, c6, assume_nonsingular=False): OUTPUT: - The elliptic curve with a-invariants [0,0,0,-c4/48,-c6/864], whose - c-invariants are the given c4, c6. If the supplied invariants are + The elliptic curve with a-invariants `[0,0,0,-c_4/48,-c_6/864]`, whose + c-invariants are the given `c_4`, `c_6`. If the supplied invariants are singular, returns ``None`` when ``assume_nonsingular`` is ``False`` and raises an :class:`ArithmeticError` otherwise. From 8d0341feae30825959353c0a8b3ea0c4f6c0c4be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20K=C3=B6ppe?= Date: Sun, 17 Mar 2024 10:37:46 -0700 Subject: [PATCH 272/518] Update src/sage/schemes/hyperelliptic_curves/mestre.py Co-authored-by: Giacomo Pope <44242839+GiacomoPope@users.noreply.github.com> --- src/sage/schemes/hyperelliptic_curves/mestre.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/schemes/hyperelliptic_curves/mestre.py b/src/sage/schemes/hyperelliptic_curves/mestre.py index 899da8b1d3a..9da4995bcf3 100644 --- a/src/sage/schemes/hyperelliptic_curves/mestre.py +++ b/src/sage/schemes/hyperelliptic_curves/mestre.py @@ -230,7 +230,7 @@ def Mestre_conic(i, xyz=False, names='u,v,w'): - ``i`` - list or tuple of length 4 containing the four Igusa-Clebsch invariants: I2, I4, I6, I10 - ``xyz`` - Boolean (default: ``False``) if ``True``, the algorithm also - returns three invariants `x`,`y`,`z` used in Mestre's algorithm + returns three invariants `x`,`y`,`z` used in Mestre's algorithm - ``names`` (default: ``'u,v,w'``) - the variable names for the conic OUTPUT: From 86143e84c06699c9f553452397d911dfc82208d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20K=C3=B6ppe?= Date: Sun, 17 Mar 2024 10:38:02 -0700 Subject: [PATCH 273/518] Update src/sage/schemes/elliptic_curves/ell_point.py Co-authored-by: Giacomo Pope <44242839+GiacomoPope@users.noreply.github.com> --- src/sage/schemes/elliptic_curves/ell_point.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/schemes/elliptic_curves/ell_point.py b/src/sage/schemes/elliptic_curves/ell_point.py index ebe4bc79bb2..3966fa4d39c 100644 --- a/src/sage/schemes/elliptic_curves/ell_point.py +++ b/src/sage/schemes/elliptic_curves/ell_point.py @@ -3272,7 +3272,7 @@ def non_archimedean_local_height(self, v=None, prec=None, - ``self`` -- a point on an elliptic curve over a number field `K`. - - ``v`` -- a non-archimedean place of `K`, or None (default). + - ``v`` -- a non-archimedean place of `K`, or ``None`` (default). If `v` is a non-archimedean place, return the local height of self at `v`. If `v` is ``None``, return the total non-archimedean contribution to the global height. From 7b0066add48aaab87a8b57db54774c6308df0cc8 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 11 Mar 2024 20:36:16 -0700 Subject: [PATCH 274/518] src/sage/misc/sagedoc.py: Add extlinks for Giac, Maxima, Pari, PPL, Singular manuals --- src/doc/en/developer/sage_manuals.rst | 24 ++++++++++++++++++++++++ src/sage/misc/sagedoc.py | 8 +++++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/doc/en/developer/sage_manuals.rst b/src/doc/en/developer/sage_manuals.rst index 578419384c7..4708f2ee36e 100644 --- a/src/doc/en/developer/sage_manuals.rst +++ b/src/doc/en/developer/sage_manuals.rst @@ -131,6 +131,30 @@ by Sage, you can link toward it without specifying its full path: - ``:mathscinet:`MR0100971``` - :mathscinet:`MR0100971` + * - :ref:`Giac ` + - ``:giac_cascmd:`gbasis ``` + - :giac_cascmd:`gbasis ` + + * - + - ``:giac_us:`Unary-functions``` + - :giac_us:`Unary-functions` + + * - :ref:`Maxima ` + - ``:maxima:`struve_h ``` + - :maxima:`struve_h ` + + * - :ref:`Pari ` + - ``:pari:`lfungenus2``` + - :pari:`lfungenus2` + + * - :ref:`PPL ` + - ``:ppl:`Linear_Expression ``` + - :ppl:`Linear_Expression ` + + * - :ref:`Singular ` + - ``:singular:`stdfglm ``` + - :singular:`stdfglm ` + **http links:** copy/pasting a http link in the documentation works. If you want a specific link name, use ```link name `_`` diff --git a/src/sage/misc/sagedoc.py b/src/sage/misc/sagedoc.py index 61cabbd1e79..f4a7d6a8a66 100644 --- a/src/sage/misc/sagedoc.py +++ b/src/sage/misc/sagedoc.py @@ -513,7 +513,13 @@ def process_dollars(s): 'oeis': ('https://oeis.org/%s', 'OEIS sequence %s'), 'doi': ('https://doi.org/%s', 'doi:%s'), 'pari': ('https://pari.math.u-bordeaux.fr/dochtml/help/%s', 'pari:%s'), - 'mathscinet': ('https://www.ams.org/mathscinet-getitem?mr=%s', 'MathSciNet %s') + 'mathscinet': ('https://www.ams.org/mathscinet-getitem?mr=%s', 'MathSciNet %s'), + 'giac_cascmd': ('https://www-fourier.ujf-grenoble.fr/~parisse/giac/doc/en/cascmd_en/%s.html', 'Giac: %s'), + 'giac_us': ('https://www-fourier.ujf-grenoble.fr/~parisse/giac_us.html#%s', 'Giac API: %s'), + 'maxima': ('https://maxima.sourceforge.io/docs/manual/maxima_singlepage.html#%s', 'Maxima: '), + 'ppl': ('https://www.bugseng.com/products/ppl/documentation/user/ppl-user-1.2-html/%s.html', 'PPL: %s'), + 'singular': ('https://www.singular.uni-kl.de/Manual/4-3-2/%s.htm', 'Singular: %s'), + } From e10a7258b89a37c4189e4f7dacfd004a254d9812 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 16 Mar 2024 16:00:56 -0700 Subject: [PATCH 275/518] src/doc/en/developer/sage_manuals.rst, src/sage/misc/sagedoc.py: Add :gap:, :gap_package: roles --- src/doc/en/developer/sage_manuals.rst | 8 ++++++++ src/sage/misc/sagedoc.py | 2 ++ 2 files changed, 10 insertions(+) diff --git a/src/doc/en/developer/sage_manuals.rst b/src/doc/en/developer/sage_manuals.rst index 4708f2ee36e..38f3728c7da 100644 --- a/src/doc/en/developer/sage_manuals.rst +++ b/src/doc/en/developer/sage_manuals.rst @@ -131,6 +131,14 @@ by Sage, you can link toward it without specifying its full path: - ``:mathscinet:`MR0100971``` - :mathscinet:`MR0100971` + * - :ref:`GAP ` + - ``:gap:`Groups ``` + - :gap:`Groups ` + + * - + - ``:gap_package:`guava``` + - :gap_package:`guava` + * - :ref:`Giac ` - ``:giac_cascmd:`gbasis ``` - :giac_cascmd:`gbasis ` diff --git a/src/sage/misc/sagedoc.py b/src/sage/misc/sagedoc.py index f4a7d6a8a66..d4cfa976462 100644 --- a/src/sage/misc/sagedoc.py +++ b/src/sage/misc/sagedoc.py @@ -514,6 +514,8 @@ def process_dollars(s): 'doi': ('https://doi.org/%s', 'doi:%s'), 'pari': ('https://pari.math.u-bordeaux.fr/dochtml/help/%s', 'pari:%s'), 'mathscinet': ('https://www.ams.org/mathscinet-getitem?mr=%s', 'MathSciNet %s'), + 'gap': ('https://gap-system.org/Manuals/doc/ref/%s_mj.html', 'GAP: %s'), + 'gap_package': ('https://www.gap-system.org/Packages/%s.html', 'GAP package %s'), 'giac_cascmd': ('https://www-fourier.ujf-grenoble.fr/~parisse/giac/doc/en/cascmd_en/%s.html', 'Giac: %s'), 'giac_us': ('https://www-fourier.ujf-grenoble.fr/~parisse/giac_us.html#%s', 'Giac API: %s'), 'maxima': ('https://maxima.sourceforge.io/docs/manual/maxima_singlepage.html#%s', 'Maxima: '), From eb5757c27fbceab100e493361ba0787a89636adb Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 16 Mar 2024 19:36:52 -0700 Subject: [PATCH 276/518] src/doc/en/developer/sage_manuals.rst, src/sage/misc/sagedoc.py: Add :ecl:, :common_lisp:, :qepcad: roles --- src/doc/en/developer/sage_manuals.rst | 12 ++++++++++++ src/sage/misc/sagedoc.py | 3 +++ 2 files changed, 15 insertions(+) diff --git a/src/doc/en/developer/sage_manuals.rst b/src/doc/en/developer/sage_manuals.rst index 38f3728c7da..91288714fe5 100644 --- a/src/doc/en/developer/sage_manuals.rst +++ b/src/doc/en/developer/sage_manuals.rst @@ -131,6 +131,14 @@ by Sage, you can link toward it without specifying its full path: - ``:mathscinet:`MR0100971``` - :mathscinet:`MR0100971` + * - :ref:`ECL ` + - ``:ecl:`Manipulating-Lisp-objects``` + - :ecl:`Manipulating-Lisp-objects` + + * - + - ``:common_lisp:`RENAME-PACKAGE ``` + - :common_lisp:`RENAME-PACKAGE ` + * - :ref:`GAP ` - ``:gap:`Groups ``` - :gap:`Groups ` @@ -159,6 +167,10 @@ by Sage, you can link toward it without specifying its full path: - ``:ppl:`Linear_Expression ``` - :ppl:`Linear_Expression ` + * - :ref:`QEPCAD ` + - ``:qepcad:`QEPCAD: Entering formulas ``` + - :qepcad:`QEPCAD: Entering formulas ` + * - :ref:`Singular ` - ``:singular:`stdfglm ``` - :singular:`stdfglm ` diff --git a/src/sage/misc/sagedoc.py b/src/sage/misc/sagedoc.py index d4cfa976462..885ae907b81 100644 --- a/src/sage/misc/sagedoc.py +++ b/src/sage/misc/sagedoc.py @@ -514,12 +514,15 @@ def process_dollars(s): 'doi': ('https://doi.org/%s', 'doi:%s'), 'pari': ('https://pari.math.u-bordeaux.fr/dochtml/help/%s', 'pari:%s'), 'mathscinet': ('https://www.ams.org/mathscinet-getitem?mr=%s', 'MathSciNet %s'), + 'common_lisp': ('https://www.lispworks.com/documentation/lw50/CLHS/Body/%s.htm', 'Common Lisp: %s'), + 'ecl': ('https://ecl.common-lisp.dev/static/manual/%s.html', 'ECL: %s'), 'gap': ('https://gap-system.org/Manuals/doc/ref/%s_mj.html', 'GAP: %s'), 'gap_package': ('https://www.gap-system.org/Packages/%s.html', 'GAP package %s'), 'giac_cascmd': ('https://www-fourier.ujf-grenoble.fr/~parisse/giac/doc/en/cascmd_en/%s.html', 'Giac: %s'), 'giac_us': ('https://www-fourier.ujf-grenoble.fr/~parisse/giac_us.html#%s', 'Giac API: %s'), 'maxima': ('https://maxima.sourceforge.io/docs/manual/maxima_singlepage.html#%s', 'Maxima: '), 'ppl': ('https://www.bugseng.com/products/ppl/documentation/user/ppl-user-1.2-html/%s.html', 'PPL: %s'), + 'qepcad': ('https://www.usna.edu/CS/qepcadweb/B/%s.html', 'QEPCAD: %s'), 'singular': ('https://www.singular.uni-kl.de/Manual/4-3-2/%s.htm', 'Singular: %s'), } From 5250624eb6a24fe7cb1f47850ba9ee9d68e87360 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 16 Mar 2024 20:32:27 -0700 Subject: [PATCH 277/518] src/doc/en/developer/sage_manuals.rst, src/sage/misc/sagedoc.py: Add :meson:, :polymake:, :scip:, :soplex: roles --- src/doc/en/developer/sage_manuals.rst | 18 +++++++++++++++++- src/sage/misc/sagedoc.py | 7 +++++-- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/doc/en/developer/sage_manuals.rst b/src/doc/en/developer/sage_manuals.rst index 91288714fe5..002d3a56cc6 100644 --- a/src/doc/en/developer/sage_manuals.rst +++ b/src/doc/en/developer/sage_manuals.rst @@ -159,22 +159,38 @@ by Sage, you can link toward it without specifying its full path: - ``:maxima:`struve_h ``` - :maxima:`struve_h ` + * - :ref:`Meson ` + - ``:meson:`install_subdir ``` + - :meson:`install_subdir ` + * - :ref:`Pari ` - ``:pari:`lfungenus2``` - :pari:`lfungenus2` + * - :ref:`polymake ` + - ``:polymake:`matroid``` + - :polymake:`matroid` + * - :ref:`PPL ` - - ``:ppl:`Linear_Expression ``` + - ``:ppl:`Linear_Expression ``` - :ppl:`Linear_Expression ` * - :ref:`QEPCAD ` - ``:qepcad:`QEPCAD: Entering formulas ``` - :qepcad:`QEPCAD: Entering formulas ` + * - :ref:`SCIP ` + - ``:scip:`SCIPsolve ``` + - :scip:`SCIPsolve ` + * - :ref:`Singular ` - ``:singular:`stdfglm ``` - :singular:`stdfglm ` + * - :ref:`SoPlex ` + - ``:soplex:`soplex::LinSolverRational ``` + - :soplex:`soplex::LinSolverRational ` + **http links:** copy/pasting a http link in the documentation works. If you want a specific link name, use ```link name `_`` diff --git a/src/sage/misc/sagedoc.py b/src/sage/misc/sagedoc.py index 885ae907b81..7c04c1b3ffe 100644 --- a/src/sage/misc/sagedoc.py +++ b/src/sage/misc/sagedoc.py @@ -520,11 +520,14 @@ def process_dollars(s): 'gap_package': ('https://www.gap-system.org/Packages/%s.html', 'GAP package %s'), 'giac_cascmd': ('https://www-fourier.ujf-grenoble.fr/~parisse/giac/doc/en/cascmd_en/%s.html', 'Giac: %s'), 'giac_us': ('https://www-fourier.ujf-grenoble.fr/~parisse/giac_us.html#%s', 'Giac API: %s'), - 'maxima': ('https://maxima.sourceforge.io/docs/manual/maxima_singlepage.html#%s', 'Maxima: '), + 'maxima': ('https://maxima.sourceforge.io/docs/manual/maxima_singlepage.html#%s', 'Maxima: %s'), + 'meson': ('https://mesonbuild.com/%s', 'Meson: %s'), + 'polymake': ('https://polymake.org/doku.php/documentation/latest/%s', 'polymake: %s'), 'ppl': ('https://www.bugseng.com/products/ppl/documentation/user/ppl-user-1.2-html/%s.html', 'PPL: %s'), 'qepcad': ('https://www.usna.edu/CS/qepcadweb/B/%s.html', 'QEPCAD: %s'), + 'scip': ('https://scipopt.org/doc/html/%s.php', 'SCIP: %s'), 'singular': ('https://www.singular.uni-kl.de/Manual/4-3-2/%s.htm', 'Singular: %s'), - + 'soplex': ('https://soplex.zib.de/doc/html/%s.php', 'SoPlex: %s'), } From b3949488fcc8f7b45a0b95444b62ba71ca2301a5 Mon Sep 17 00:00:00 2001 From: Dima Pasechnik Date: Mon, 18 Mar 2024 11:38:30 +0000 Subject: [PATCH 278/518] test gap3 with gap3, not gap (gap4) --- src/sage/interfaces/gap3.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sage/interfaces/gap3.py b/src/sage/interfaces/gap3.py index e73308e014e..de2a7fe4289 100644 --- a/src/sage/interfaces/gap3.py +++ b/src/sage/interfaces/gap3.py @@ -752,7 +752,8 @@ def _latex_(self): r""" EXAMPLES:: - sage: s = gap("[[1,2], [3/4, 5/6]]") + sage: # optional - gap3 + sage: s = gap3("[[1,2], [3/4, 5/6]]") sage: s._latex_() '\\left(\\begin{array}{rr} 1&2\\\\ 3/4&\\frac{5}{6}\\\\ \\end{array}\\right)' sage: latex(s) From 2b9ba8afd5995c326c8c02e2b631e747b90168f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20K=C3=B6ppe?= Date: Mon, 18 Mar 2024 09:56:26 -0700 Subject: [PATCH 279/518] src/sage/misc/sagedoc.py: Update gap manual URL Co-authored-by: Max Horn --- src/sage/misc/sagedoc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/misc/sagedoc.py b/src/sage/misc/sagedoc.py index 7c04c1b3ffe..180c54db9c8 100644 --- a/src/sage/misc/sagedoc.py +++ b/src/sage/misc/sagedoc.py @@ -516,7 +516,7 @@ def process_dollars(s): 'mathscinet': ('https://www.ams.org/mathscinet-getitem?mr=%s', 'MathSciNet %s'), 'common_lisp': ('https://www.lispworks.com/documentation/lw50/CLHS/Body/%s.htm', 'Common Lisp: %s'), 'ecl': ('https://ecl.common-lisp.dev/static/manual/%s.html', 'ECL: %s'), - 'gap': ('https://gap-system.org/Manuals/doc/ref/%s_mj.html', 'GAP: %s'), + 'gap': ('https://docs.gap-system.org/doc/ref/%s_mj.html', 'GAP: %s'), 'gap_package': ('https://www.gap-system.org/Packages/%s.html', 'GAP package %s'), 'giac_cascmd': ('https://www-fourier.ujf-grenoble.fr/~parisse/giac/doc/en/cascmd_en/%s.html', 'Giac: %s'), 'giac_us': ('https://www-fourier.ujf-grenoble.fr/~parisse/giac_us.html#%s', 'Giac API: %s'), From 36d2c6bf4a3e41af4fa204dbe88261367d918049 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Mon, 18 Mar 2024 21:19:50 +0100 Subject: [PATCH 280/518] use the polynomial ring also for non-commutative baserings as _internal_poly_ring --- src/sage/rings/lazy_series_ring.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/sage/rings/lazy_series_ring.py b/src/sage/rings/lazy_series_ring.py index 01cd6de9f0f..06fd7a5d595 100644 --- a/src/sage/rings/lazy_series_ring.py +++ b/src/sage/rings/lazy_series_ring.py @@ -2716,6 +2716,13 @@ def __init__(self, basis, sparse=True, category=None): ... ValueError: basis should be in GradedAlgebrasWithBasis + Check that :issue:`37625` is fixed:: + + sage: R = algebras.Free(QQ, ('a', 'b'), degrees=(1, 2)) + sage: L = R.completion() + sage: a, b = R.gens() + sage: (L(a) + L(b))^2 + a^2 + (a*b+b*a) + b^2 """ base_ring = basis.base_ring() self._minimal_valuation = 0 @@ -2738,11 +2745,7 @@ def __init__(self, basis, sparse=True, category=None): Parent.__init__(self, base=base_ring, category=category) self._sparse = sparse self._laurent_poly_ring = basis - if self._laurent_poly_ring not in Rings().Commutative(): - from sage.algebras.free_algebra import FreeAlgebra - self._internal_poly_ring = FreeAlgebra(self._laurent_poly_ring, 1, "DUMMY_VARIABLE") - else: - self._internal_poly_ring = PolynomialRing(self._laurent_poly_ring, "DUMMY_VARIABLE", sparse=sparse) + self._internal_poly_ring = PolynomialRing(self._laurent_poly_ring, "DUMMY_VARIABLE", sparse=sparse) def _repr_(self): """ From 41937687bb001d5eaa836eb9c66ca15217a26acc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Mon, 18 Mar 2024 12:57:45 +0100 Subject: [PATCH 281/518] Remove typo in DrinfeldModule.j_invariant docstring In DrinfeldModule.j_invariant docstring, there was a typo in the first `.. MATH::` environment: the exponent on the denominator was $d_q$ instead of $d_r$. --- .../rings/function_field/drinfeld_modules/drinfeld_module.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 b0f177dfcfa..d395784995b 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -1489,7 +1489,7 @@ def j_invariant(self, parameter=None, check=True): .. MATH:: j_{k_1, \ldots, k_n}^{d_1, \ldots, d_n, d_r}(\phi) - := \frac{1}{g_r^{d_q}}\prod_{i = 1}^n g_{k_i}^{d_i} + := \frac{1}{g_r^{d_r}}\prod_{i = 1}^n g_{k_i}^{d_i} where `1\leqslant k_1 < k_2 < \ldots < k_n \leqslant r - 1` and the integers `d_i` satisfy the *weight-0 condition*: From 35a1903a350d3bea2b59736b3bf295d5a3425be3 Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Mon, 18 Mar 2024 23:43:24 +0000 Subject: [PATCH 282/518] reviewer changes --- src/sage/schemes/hyperelliptic_curves/constructor.py | 7 +++---- src/sage/schemes/hyperelliptic_curves/hyperelliptic_g2.py | 4 ++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/sage/schemes/hyperelliptic_curves/constructor.py b/src/sage/schemes/hyperelliptic_curves/constructor.py index 65970b54725..d647312ee30 100644 --- a/src/sage/schemes/hyperelliptic_curves/constructor.py +++ b/src/sage/schemes/hyperelliptic_curves/constructor.py @@ -250,8 +250,7 @@ def HyperellipticCurve(f, h=0, names=None, PP=None, check_squarefree=True): ) if not smooth: raise ValueError( - "not a hyperelliptic curve: " - "singularity in the provided affine patch" + "not a hyperelliptic curve: singularity in the provided affine patch" ) R = P.base_ring() PP = ProjectiveSpace(2, R) @@ -266,9 +265,9 @@ def HyperellipticCurve(f, h=0, names=None, PP=None, check_squarefree=True): genus_classes = {2: HyperellipticCurve_g2} if g in genus_classes: bases.append(genus_classes[g]) - cls_name.append("g%s" % g) + cls_name.append(f"g{g}") - # For certain base fields, we specialise to child classes + # For certain base fields, we specialise to subclasses # with special case methods def is_FiniteField(x): return isinstance(x, FiniteField) diff --git a/src/sage/schemes/hyperelliptic_curves/hyperelliptic_g2.py b/src/sage/schemes/hyperelliptic_curves/hyperelliptic_g2.py index 5e5225ebff1..23cbaa7c9f1 100644 --- a/src/sage/schemes/hyperelliptic_curves/hyperelliptic_g2.py +++ b/src/sage/schemes/hyperelliptic_curves/hyperelliptic_g2.py @@ -52,8 +52,8 @@ def jacobian(self): sage: R. = QQ[] sage: f = x^5 - x^4 + 3 - sage: HyperellipticCurve(f).jacobian - + sage: type(HyperellipticCurve(f).jacobian()) + """ return jacobian_g2.HyperellipticJacobian_g2(self) From 2ce508e7e0a72ed894a682bca853e3162e652250 Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Tue, 19 Mar 2024 07:12:27 +0000 Subject: [PATCH 283/518] reviewer changes --- src/sage/schemes/hyperelliptic_curves/constructor.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sage/schemes/hyperelliptic_curves/constructor.py b/src/sage/schemes/hyperelliptic_curves/constructor.py index d647312ee30..8ed20d9ce5a 100644 --- a/src/sage/schemes/hyperelliptic_curves/constructor.py +++ b/src/sage/schemes/hyperelliptic_curves/constructor.py @@ -260,7 +260,7 @@ def HyperellipticCurve(f, h=0, names=None, PP=None, check_squarefree=True): bases = [] cls_name = ["HyperellipticCurve"] - # For certain genus we specialise to child classes with + # For certain genus we specialise to subclasses with # optimised methods genus_classes = {2: HyperellipticCurve_g2} if g in genus_classes: @@ -287,13 +287,13 @@ def is_pAdicField(x): cls_name.append(name) break - # If no specialised child class was found, we simply use the + # If no specialised subclasses are identified, we simply use the # generic class in the class construction if not bases: bases = [HyperellipticCurve_generic] # Dynamically build a class from multiple inheritance. Note that - # all classes we slect from are children of HyperellipticCurve_generic + # all classes we select from are subclasses of HyperellipticCurve_generic class_name = "_".join(cls_name) cls = dynamic_class(class_name, tuple(bases), doccls=HyperellipticCurve) return cls(PP, f, h, names=names, genus=g) From f230f2730dc224a533367c407c2c0e6a65150ae2 Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Tue, 19 Mar 2024 19:01:42 +0900 Subject: [PATCH 284/518] Fixing typos from Frederic. --- src/doc/en/reference/references/index.rst | 2 +- src/sage/combinat/diagram_algebras.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index 659ffa3b2b3..b36935df8bd 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -4808,7 +4808,7 @@ REFERENCES: .. [MR1998] \P. P. Martin and G. Rollet. *The Potts model representation and a Robinson-Schensted correspondence for the partition algebra*. - Compositoio Math., **112** (1998), pp. 237-254. + Compositio Math., **112** (1998), pp. 237-254. .. [MR2002] \S. Murphy, M. Robshaw *Essential Algebraic Structure Within the AES*; in Advances in Cryptology \- CRYPTO diff --git a/src/sage/combinat/diagram_algebras.py b/src/sage/combinat/diagram_algebras.py index d2d770a6d4d..274e3f9c7dd 100644 --- a/src/sage/combinat/diagram_algebras.py +++ b/src/sage/combinat/diagram_algebras.py @@ -4623,7 +4623,7 @@ class PottsRepresentation(CombinatorialFreeModule): sage: SQ.dimension() 43 - Next, we get orthogonal idempontents that project onto the central + Next, we get orthogonal idempotents that project onto the central orthogonal idempotents in the semisimple quotient and construct the corresponding Peirce summands `e_i P_n(d) e_i`:: @@ -4635,7 +4635,7 @@ class PottsRepresentation(CombinatorialFreeModule): We saw that we obtain the entire endomorphism algebra since `d = 2` and `S_{d-1}` is the trivial group. Hence, the 16 dimensional Peirce - summand computed above is isomorphic to this endomorphims algebra + summand computed above is isomorphic to this endomorphism algebra (both are `4 \times 4` matrix algebras over `\QQ`). Hence, we have a natural quotient construction of the centralizer algebra from the partition algebra. From a03127b6c863b09a27870d4997e1d8ffddbb5a35 Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Tue, 19 Mar 2024 19:15:43 +0900 Subject: [PATCH 285/518] Implement a bool for posets. --- src/sage/combinat/posets/posets.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/sage/combinat/posets/posets.py b/src/sage/combinat/posets/posets.py index 36f4d1eee10..6db41768e1e 100644 --- a/src/sage/combinat/posets/posets.py +++ b/src/sage/combinat/posets/posets.py @@ -1230,6 +1230,21 @@ def unwrap(self, element): else: return element.element + def __bool__(self) -> bool: + r""" + Return if ``self`` is empty or not. + + EXAMPLES:: + + sage: P = Poset((divisors(15), attrcall("divides")), facade=True) + sage: bool(P) + True + sage: P = Poset() + sage: bool(P) + False + """ + return bool(self._elements) + def __contains__(self, x) -> bool: r""" Return ``True`` if ``x`` is an element of the poset. From b4121bad8a713640a7c785a7945f2d8f2878d463 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Mon, 18 Mar 2024 21:49:15 +0100 Subject: [PATCH 286/518] Add comments on of Potemine's j-invariants We believe there was an ambiguity between our naming of j-invariants, basic j-invariants and jk-invariants, as we follow Potemine's convention, which differs from Papikian's. Consequently, we remove the ambiguity in a note in the docstring. --- src/doc/en/reference/references/index.rst | 4 +++ .../drinfeld_modules/drinfeld_module.py | 25 +++++++++++++++---- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index 09b78514e1c..5f854c44c58 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -5110,6 +5110,10 @@ REFERENCES: B46f, https://eudml.org/doc/121696 +.. [Pap2023] Mihran Papikian, *Drinfeld modules*. + Graduate Texts in Mathematics, 206, + Springer International Publishing, Cham, 2023. + .. [PALP] Maximilian Kreuzer, Harald Skarke: "PALP: A Package for Analyzing Lattice Polytopes with Applications to Toric Geometry" omput.Phys.Commun. 157 (2004) 87-106 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 d395784995b..02f2d0be604 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -1510,6 +1510,26 @@ def j_invariant(self, parameter=None, check=True): :meth:`basic_j_invariant_parameters` for computing the list of all basic `j`-invariant parameters. + .. NOTE:: + + In [Pap2023]_, Papikian follows a slightly different + convention: + + - His `j`-invariants (see Definition 3.8.7) correspond to + our basic `j`-invariants, as defined above. + - His *basic* `j`-invariant (see Example 3.8.10) correspond + to our `j_k`-invariants, as implemented in + :meth:`jk_invariants`. + + We chose to follow Potemine's convention, as he introduced + those objects in [Pot1998]_. Theorem 2.2 of [Pot1998]_ or + Theorem 3.8.11 of [Pap2023]_ assert that two Drinfeld + `\mathbb F_q[T]`-modules over `K` are isomorphic over the + separable closure of `K` if and only if their basic + `j`-invariants (as implemented here) coincide for any + well-defined couple of tuples `((k_1, k_2, \ldots, k_n), + (d_1, d_2, \ldots, d_n, d_r))`, . + INPUT: - ``parameter`` (tuple or list, integer or NoneType; default: @@ -1536,11 +1556,6 @@ def j_invariant(self, parameter=None, check=True): OUTPUT: the `j`-invariant of ``self`` for the given parameter. - REFERENCE: - - The notion of basic `j`-invariant was introduced by Potemine in - [Pot1998]_. - EXAMPLES:: sage: Fq = GF(25) From d89102f9963d4c024db50314fd34cf492a0abe10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Tue, 19 Mar 2024 17:16:44 +0100 Subject: [PATCH 287/518] tiny details in Potts model --- src/sage/combinat/diagram_algebras.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/combinat/diagram_algebras.py b/src/sage/combinat/diagram_algebras.py index 274e3f9c7dd..d5b2ccb191e 100644 --- a/src/sage/combinat/diagram_algebras.py +++ b/src/sage/combinat/diagram_algebras.py @@ -3108,7 +3108,7 @@ def jucys_murphy_element(self, i): def potts_representation(self, y=None): r""" Return the :class:`PottsRepresentation` with magnetic field - direction ``Y`` of ``self``. + direction ``y`` of ``self``. .. NOTE:: @@ -3130,7 +3130,7 @@ def potts_representation(self, y=None): sage: PA.potts_representation() Traceback (most recent call last): ... - ValueError: the partition algebra deformation parameter must + ValueError: the partition algebra deformation parameter must be a positive integer """ if self.order() not in ZZ and y is None: From 52e366b333d3a0e93c635519ebcecb22ba113d75 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Tue, 19 Mar 2024 22:04:55 +0100 Subject: [PATCH 288/518] initial version of the Franklin-Glaisher bijection --- src/sage/combinat/partition.py | 89 ++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/src/sage/combinat/partition.py b/src/sage/combinat/partition.py index 3ff9bb2f1d6..3e3b7aea788 100644 --- a/src/sage/combinat/partition.py +++ b/src/sage/combinat/partition.py @@ -2485,6 +2485,95 @@ def conjugate(self): par = Partitions_n(sum(self)) return par.element_class(par, conjugate(self)) + def franklin_glaisher(self, s): + r""" + Apply the Franklin-Glaisher bijection to ``self``. + + INPUT: + + - ``s`` -- positive integer + + OUTPUT: + + The Franklin-Glaisher bijection, with parameter `s`, returns + a partition whose set of parts that are repeated at least `s` + times equals the set of parts divisible by `s` in ``self``, + after dividing each part by `s`. + + EXAMPLES:: + + sage: Partition([4, 3, 2, 2, 1]).franklin_glaisher(2) + [3, 2, 2, 1, 1, 1, 1, 1] + + TESTS:: + + sage: d = lambda la, s: set(p / s for p in la if p % s == 0) + sage: r = lambda la, s: set(p for p in la if list(la).count(p) >= s) + sage: all(d(mu, s) == r(mu.franklin_glaisher(s), s) + ....: for n in range(20) for mu in Partitions(n) + ....: for s in range(1, 5)) + True + """ + mu = [] + for p, m in enumerate(self.to_exp(), 1): + if not p % s: + mu.extend([p//s]*(m*s)) + else: + mu.extend(p * v * s**i for i, v in enumerate(m.digits(s)) if v) + + P = self.parent() + return P.element_class(P, sorted(mu, reverse=True)) + + def franklin_glaisher_inverse(self, s): + r""" + Apply the inverse of the Franklin-Glaisher bijection to ``self``. + + INPUT: + + - ``s`` -- positive integer + + OUTPUT: + + The inverse of the Franklin-Glaisher bijection, with + parameter `s`, returns a partition whose set of parts that + are divisible by `s`, after dividing each by `s`, equals the + equals the set of parts repeated at least `s` times in + ``self``. + + EXAMPLES:: + + sage: Partition([4, 3, 2, 2, 1]).franklin_glaisher(2) + [3, 2, 2, 1, 1, 1, 1, 1] + sage: Partition([3, 2, 2, 1, 1, 1, 1, 1]).franklin_glaisher_inverse(2) + [4, 3, 2, 2, 1] + + TESTS:: + + sage: d = lambda la, s: set(p / s for p in la if p % s == 0) + sage: r = lambda la, s: set(p for p in la if list(la).count(p) >= s) + sage: all(r(mu, s) == d(mu.franklin_glaisher_inverse(s), s) + ....: for n in range(20) for mu in Partitions(n) + ....: for s in range(1, 5)) + True + """ + mu = [] + nu = [] + for p, m in enumerate(self.to_exp(), 1): + mu.extend([p*s]*(m // s)) + nu.extend([p]*(m % s)) + + i = 0 + while i < len(nu): + p = nu[i] + if not p % s: + del nu[i] + nu.extend([p // s]*s) + else: + i += 1 + + P = self.parent() + return P.element_class(P, sorted(mu + nu, reverse=True)) + def suter_diagonal_slide(self, n, exp=1): r""" Return the image of ``self`` in `Y_n` under Suter's diagonal slide From ca1d5251fd110f35ab60177e03c3e358d134c712 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 19 Mar 2024 19:54:29 -0700 Subject: [PATCH 289/518] src/doc/en/developer/sage_manuals.rst, src/sage/misc/sagedoc.py: Update :gap_pkg: --- src/doc/en/developer/sage_manuals.rst | 4 ++-- src/sage/misc/sagedoc.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/doc/en/developer/sage_manuals.rst b/src/doc/en/developer/sage_manuals.rst index 002d3a56cc6..1c541d61e1c 100644 --- a/src/doc/en/developer/sage_manuals.rst +++ b/src/doc/en/developer/sage_manuals.rst @@ -144,8 +144,8 @@ by Sage, you can link toward it without specifying its full path: - :gap:`Groups ` * - - - ``:gap_package:`guava``` - - :gap_package:`guava` + - ``:gap_package:`GAP package QuaGroup ``` + - :gap_package:`GAP package QuaGroup ` * - :ref:`Giac ` - ``:giac_cascmd:`gbasis ``` diff --git a/src/sage/misc/sagedoc.py b/src/sage/misc/sagedoc.py index 180c54db9c8..ead9037253f 100644 --- a/src/sage/misc/sagedoc.py +++ b/src/sage/misc/sagedoc.py @@ -517,7 +517,7 @@ def process_dollars(s): 'common_lisp': ('https://www.lispworks.com/documentation/lw50/CLHS/Body/%s.htm', 'Common Lisp: %s'), 'ecl': ('https://ecl.common-lisp.dev/static/manual/%s.html', 'ECL: %s'), 'gap': ('https://docs.gap-system.org/doc/ref/%s_mj.html', 'GAP: %s'), - 'gap_package': ('https://www.gap-system.org/Packages/%s.html', 'GAP package %s'), + 'gap_package': ('https://docs.gap-system.org/pkg/%s', 'GAP package %s'), 'giac_cascmd': ('https://www-fourier.ujf-grenoble.fr/~parisse/giac/doc/en/cascmd_en/%s.html', 'Giac: %s'), 'giac_us': ('https://www-fourier.ujf-grenoble.fr/~parisse/giac_us.html#%s', 'Giac API: %s'), 'maxima': ('https://maxima.sourceforge.io/docs/manual/maxima_singlepage.html#%s', 'Maxima: %s'), From bcb5c8af7d79cb298010129ef4562755ac8f2f93 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 19 Mar 2024 20:08:08 -0700 Subject: [PATCH 290/518] Use :gap:, :gap_package: roles --- src/sage/algebras/quantum_groups/quantum_group_gap.py | 5 ++--- src/sage/combinat/designs/incidence_structures.py | 3 +-- src/sage/groups/cubic_braid.py | 4 ++-- src/sage/groups/generic.py | 3 +-- src/sage/groups/matrix_gps/finitely_generated_gap.py | 3 +-- src/sage/libs/gap/util.pyx | 4 ++-- 6 files changed, 9 insertions(+), 13 deletions(-) diff --git a/src/sage/algebras/quantum_groups/quantum_group_gap.py b/src/sage/algebras/quantum_groups/quantum_group_gap.py index 0c92355503c..997fe0f3b04 100644 --- a/src/sage/algebras/quantum_groups/quantum_group_gap.py +++ b/src/sage/algebras/quantum_groups/quantum_group_gap.py @@ -6,9 +6,8 @@ - Travis Scrimshaw (03-2017): initial version -The documentation for GAP's QuaGroup package, originally authored by -Willem Adriaan de Graaf, can be found at -https://www.gap-system.org/Packages/quagroup.html. +See the :gap_package:`documentation for GAP's QuaGroup package `, +originally authored by Willem Adriaan de Graaf. """ # **************************************************************************** diff --git a/src/sage/combinat/designs/incidence_structures.py b/src/sage/combinat/designs/incidence_structures.py index 839006798a0..5d622672e43 100644 --- a/src/sage/combinat/designs/incidence_structures.py +++ b/src/sage/combinat/designs/incidence_structures.py @@ -1810,8 +1810,7 @@ def dual(self, algorithm=None): REFERENCE: - - Soicher, Leonard, Design package manual, available at - https://www.gap-system.org/Manuals/pkg/design/htm/CHAP003.htm + - Soicher, Leonard, :gap_package:`Design package manual ` """ if algorithm == "gap": libgap.load_package("design") diff --git a/src/sage/groups/cubic_braid.py b/src/sage/groups/cubic_braid.py index e2b49edf867..9dd59e069f9 100644 --- a/src/sage/groups/cubic_braid.py +++ b/src/sage/groups/cubic_braid.py @@ -274,8 +274,8 @@ def _richcmp_(self, other, op): :class:`FinitelyPresentedGroupElement` (via Gap) does not terminate in the case of more than 5 strands (not only infinite cases). On less than 5 strands comparison is not assumed to be deterministic - (see the :issue:`33498` and section 47.3-2 of the - `Gap Reference manual `__). + (see the :issue:`33498` and :gap:`section 47.3-2 of the + Gap Reference manual `). Therefore, the comparison is done via the Burau representation. diff --git a/src/sage/groups/generic.py b/src/sage/groups/generic.py index 206e8561e5a..9d41dd49d3f 100644 --- a/src/sage/groups/generic.py +++ b/src/sage/groups/generic.py @@ -1611,8 +1611,7 @@ def structure_description(G, latex=False): This methods wraps GAP's ``StructureDescription`` method. For full details, including the form of the returned string and the - algorithm to build it, see `GAP's documentation - `_. + algorithm to build it, see :gap:`GAP's documentation `. INPUT: diff --git a/src/sage/groups/matrix_gps/finitely_generated_gap.py b/src/sage/groups/matrix_gps/finitely_generated_gap.py index 5dd566a96c2..14bf551789f 100644 --- a/src/sage/groups/matrix_gps/finitely_generated_gap.py +++ b/src/sage/groups/matrix_gps/finitely_generated_gap.py @@ -223,8 +223,7 @@ def module_composition_factors(self, algorithm=None): Type ``G.module_composition_factors(algorithm='verbose')`` to get a more verbose version. - For more on MeatAxe notation, see - https://www.gap-system.org/Manuals/doc/ref/chap69.html + For more on MeatAxe notation, see :gap:`chap69`. """ from sage.libs.gap.libgap import libgap F = self.base_ring() diff --git a/src/sage/libs/gap/util.pyx b/src/sage/libs/gap/util.pyx index 8685dc08fa5..81df83a6e0d 100644 --- a/src/sage/libs/gap/util.pyx +++ b/src/sage/libs/gap/util.pyx @@ -188,8 +188,8 @@ cdef initialize() noexcept: """ Initialize the GAP library, if it hasn't already been initialized. It is safe to call this multiple times. One can set - :envvar:`SAGE_GAP_MEMORY` to a particular value, as desribed in - `GAP Manual `_ + :envvar:`SAGE_GAP_MEMORY` to a particular value, as described in + the :gap:`GAP Manual `. Specifically, the value is for `-s` and `-o` options. TESTS:: From 0f7b0307a3ef3b50774ff2d129aa1916919c362a Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 19 Mar 2024 23:51:20 -0700 Subject: [PATCH 291/518] src/sage/combinat/path_tableaux/semistandard.py: Spaces after commas in doctests --- .../combinat/path_tableaux/semistandard.py | 91 ++++++++++--------- 1 file changed, 49 insertions(+), 42 deletions(-) diff --git a/src/sage/combinat/path_tableaux/semistandard.py b/src/sage/combinat/path_tableaux/semistandard.py index f6703b32afe..040710d48f0 100644 --- a/src/sage/combinat/path_tableaux/semistandard.py +++ b/src/sage/combinat/path_tableaux/semistandard.py @@ -12,30 +12,30 @@ EXAMPLES:: - sage: pt = path_tableaux.SemistandardPathTableau([[],[3],[3,2],[3,3,1], - ....: [3,3,2,1],[4,3,3,1,0]]) + sage: pt = path_tableaux.SemistandardPathTableau([[], [3], [3,2], [3,3,1], + ....: [3,3,2,1], [4,3,3,1,0]]) sage: pt.promotion() [(), (2,), (3, 1), (3, 2, 1), (4, 3, 1, 0), (4, 3, 3, 1, 0)] sage: pt.evacuation() [(), (2,), (4, 0), (4, 2, 0), (4, 3, 1, 0), (4, 3, 3, 1, 0)] - sage: pt = path_tableaux.SemistandardPathTableau([[],[3],[3,2],[3,3,1], - ....: [3,3,2,1],[9/2,3,3,1,0]]) + sage: pt = path_tableaux.SemistandardPathTableau([[], [3], [3,2], [3,3,1], + ....: [3,3,2,1], [9/2,3,3,1,0]]) sage: pt.promotion() [(), (2,), (3, 1), (3, 2, 1), (9/2, 3, 1, 0), (9/2, 3, 3, 1, 0)] sage: pt.evacuation() [(), (5/2,), (9/2, 0), (9/2, 2, 0), (9/2, 3, 1, 0), (9/2, 3, 3, 1, 0)] - sage: pt = path_tableaux.SemistandardPathTableau([[],[3],[4,2],[5,4,1]]) + sage: pt = path_tableaux.SemistandardPathTableau([[], [3], [4,2], [5,4,1]]) sage: path_tableaux.CylindricalDiagram(pt) [ (), (3,), (4, 2), (5, 4, 1)] [ , (), (3,), (5, 2), (5, 4, 1)] [ , , (), (4,), (4, 3), (5, 4, 1)] [ , , , (), (3,), (5, 1), (5, 4, 1)] - sage: pt2 = path_tableaux.SemistandardPathTableau([[3,2],[3,3,1], - ....: [3,3,2,1],[4,3,3,1,0]]) - sage: pt1 = path_tableaux.SemistandardPathTableau([[],[3],[3,2]]) + sage: pt2 = path_tableaux.SemistandardPathTableau([[3,2], [3,3,1], + ....: [3,3,2,1], [4,3,3,1,0]]) + sage: pt1 = path_tableaux.SemistandardPathTableau([[], [3], [3,2]]) sage: pt1.commutor(pt2) ([(), (2,), (2, 2), (4, 2, 0)], [(4, 2, 0), (4, 3, 2, 0), (4, 3, 3, 1, 0)]) sage: pt1.commutor(pt2,verbose=True) @@ -55,17 +55,17 @@ TESTS:: - sage: pt = path_tableaux.SemistandardPathTableau([[],[3],[3,2],[3,3,1],[3,3,2,1],[4,3,3,1,0]]) + sage: pt = path_tableaux.SemistandardPathTableau([[], [3], [3,2], [3,3,1], [3,3,2,1], [4,3,3,1,0]]) sage: TestSuite(pt).run() - sage: pt = path_tableaux.SemistandardPathTableau([[],[3],[3,2],[3,3,1],[7/2,3,2,1],[4,3,3,1,0]]) + sage: pt = path_tableaux.SemistandardPathTableau([[], [3], [3,2], [3,3,1], [7/2,3,2,1], [4,3,3,1,0]]) sage: TestSuite(pt).run() Failure in _test_jdt_promotion: Traceback (most recent call last): ... The following tests failed: _test_jdt_promotion - sage: pt = path_tableaux.SemistandardPathTableau([[3,2],[3,3,1],[3,3,2,1],[4,3,3,1,0]]) + sage: pt = path_tableaux.SemistandardPathTableau([[3,2], [3,3,1], [3,3,2,1], [4,3,3,1,0]]) sage: pt.promotion() [(3, 2), (3, 2, 2), (4, 3, 2, 0), (4, 3, 3, 1, 0)] @@ -114,29 +114,29 @@ class SemistandardPathTableau(PathTableau): EXAMPLES:: - sage: path_tableaux.SemistandardPathTableau([[],[2],[2,1]]) + sage: path_tableaux.SemistandardPathTableau([[], [2], [2,1]]) [(), (2,), (2, 1)] - sage: gt = GelfandTsetlinPattern([[2,1],[2]]) + sage: gt = GelfandTsetlinPattern([[2,1], [2]]) sage: path_tableaux.SemistandardPathTableau(gt) [(), (2,), (2, 1)] - sage: st = SemistandardTableau([[1,1],[2]]) + sage: st = SemistandardTableau([[1,1], [2]]) sage: path_tableaux.SemistandardPathTableau(st) [(), (2,), (2, 1)] - sage: st = SkewTableau([[1,1],[2]]) + sage: st = SkewTableau([[1,1], [2]]) sage: path_tableaux.SemistandardPathTableau(st) [(), (2,), (2, 1)] - sage: st = SkewTableau([[None,1,1],[2]]) + sage: st = SkewTableau([[None,1,1], [2]]) sage: path_tableaux.SemistandardPathTableau(st) [(1,), (3, 0), (3, 1, 0)] - sage: path_tableaux.SemistandardPathTableau([[],[5/2],[7/2,2]]) + sage: path_tableaux.SemistandardPathTableau([[], [5/2], [7/2,2]]) [(), (5/2,), (7/2, 2)] - sage: path_tableaux.SemistandardPathTableau([[],[2.5],[3.5,2]]) + sage: path_tableaux.SemistandardPathTableau([[], [2.5], [3.5,2]]) [(), (2.50000000000000,), (3.50000000000000, 2)] """ @@ -148,7 +148,7 @@ def __classcall_private__(cls, st, check=True): EXAMPLES:: - sage: t = path_tableaux.SemistandardPathTableau([[],[2]]) + sage: t = path_tableaux.SemistandardPathTableau([[], [2]]) sage: t.parent() """ @@ -200,13 +200,13 @@ def check(self): EXAMPLES:: - sage: path_tableaux.SemistandardPathTableau([[],[3],[2,2]]) # indirect test + sage: path_tableaux.SemistandardPathTableau([[], [3], [2,2]]) # indirect doctest Traceback (most recent call last): ... ValueError: [(), (3,), (2, 2)] does not satisfy the required inequalities in row 1 - sage: path_tableaux.SemistandardPathTableau([[],[3/2],[2,5/2]]) # indirect test + sage: path_tableaux.SemistandardPathTableau([[], [3/2], [2,5/2]]) # indirect doctest Traceback (most recent call last): ... ValueError: [(), (3/2,), (2, 5/2)] does not satisfy @@ -215,12 +215,12 @@ def check(self): TESTS:: - sage: path_tableaux.SemistandardPathTableau([[],[2],[1,2]]) + sage: path_tableaux.SemistandardPathTableau([[], [2], [1,2]]) Traceback (most recent call last): ... ValueError: [(), (2,), (1, 2)] does not satisfy the required inequalities in row 1 - sage: path_tableaux.SemistandardPathTableau([[],[2],[1,2]],check=False) + sage: path_tableaux.SemistandardPathTableau([[], [2], [1,2]], check=False) [(), (2,), (1, 2)] """ for i in range(1,len(self)-1): @@ -235,7 +235,7 @@ def size(self): EXAMPLES:: - sage: path_tableaux.SemistandardPathTableau([[],[3],[3,2],[3,3,1],[3,3,2,1]]).size() + sage: path_tableaux.SemistandardPathTableau([[], [3], [3,2], [3,3,1], [3,3,2,1]]).size() 5 """ return len(self) @@ -246,7 +246,7 @@ def is_skew(self): EXAMPLES:: - sage: path_tableaux.SemistandardPathTableau([[],[2]]).is_skew() + sage: path_tableaux.SemistandardPathTableau([[], [2]]).is_skew() False sage: path_tableaux.SemistandardPathTableau([[2,1]]).is_skew() True @@ -259,11 +259,11 @@ def is_integral(self) -> bool: EXAMPLES:: - sage: path_tableaux.SemistandardPathTableau([[],[3],[3,2]]).is_integral() + sage: path_tableaux.SemistandardPathTableau([[], [3], [3,2]]).is_integral() True - sage: path_tableaux.SemistandardPathTableau([[],[5/2],[7/2,2]]).is_integral() + sage: path_tableaux.SemistandardPathTableau([[], [5/2], [7/2,2]]).is_integral() False - sage: path_tableaux.SemistandardPathTableau([[],[3],[3,-2]]).is_integral() + sage: path_tableaux.SemistandardPathTableau([[], [3], [3,-2]]).is_integral() False """ return all(i in NN for a in self for i in a) @@ -278,7 +278,8 @@ def local_rule(self, i): EXAMPLES:: - sage: pt = path_tableaux.SemistandardPathTableau([[],[3],[3,2],[3,3,1],[3,3,2,1]]) + sage: pt = path_tableaux.SemistandardPathTableau([[], [3], [3,2], + ....: [3,3,1], [3,3,2,1]]) sage: pt.local_rule(1) [(), (2,), (3, 2), (3, 3, 1), (3, 3, 2, 1)] sage: pt.local_rule(2) @@ -288,7 +289,8 @@ def local_rule(self, i): TESTS:: - sage: pt = path_tableaux.SemistandardPathTableau([[],[3],[3,2],[3,3,1],[3,3,2,1]]) + sage: pt = path_tableaux.SemistandardPathTableau([[], [3], [3,2], + ....: [3,3,1], [3,3,2,1]]) sage: pt.local_rule(0) Traceback (most recent call last): ... @@ -347,15 +349,17 @@ def rectify(self, inner=None, verbose=False): TESTS:: - sage: S = SemistandardSkewTableaux([[5,3,3],[3,1]],[3,2,2]) + sage: S = SemistandardSkewTableaux([[5,3,3], [3,1]], [3,2,2]) sage: LHS = [path_tableaux.SemistandardPathTableau(st.rectify()) for st in S] sage: RHS = [path_tableaux.SemistandardPathTableau(st).rectify() for st in S] sage: LHS == RHS True - sage: st = SkewTableau([[None, None, None, 4],[None,None,1,6],[None,None,5],[2,3]]) + sage: st = SkewTableau([[None, None, None, 4], [None, None, 1, 6], + ....: [None, None, 5], [2, 3]]) sage: pt = path_tableaux.SemistandardPathTableau(st) - sage: SP = [path_tableaux.SemistandardPathTableau(it) for it in StandardTableaux([3,2,2])] + sage: SP = [path_tableaux.SemistandardPathTableau(it) + ....: for it in StandardTableaux([3,2,2])] sage: len(set(pt.rectify(inner=ip) for ip in SP)) 1 """ @@ -398,8 +402,8 @@ def to_tableau(self): EXAMPLES:: - sage: pt = path_tableaux.SemistandardPathTableau([[],[3],[3,2],[3,3,1], - ....: [3,3,2,1],[4,3,3,1,0]]) + sage: pt = path_tableaux.SemistandardPathTableau([[], [3], [3,2], [3,3,1], + ....: [3,3,2,1], [4,3,3,1,0]]) sage: pt.to_tableau() [[1, 1, 1, 5], [2, 2, 3], [3, 4, 5], [4]] @@ -428,14 +432,14 @@ def to_pattern(self): EXAMPLES:: - sage: pt = path_tableaux.SemistandardPathTableau([[],[3],[3,2],[3,3,1], - ....: [3,3,2,1],[4,3,3,1]]) + sage: pt = path_tableaux.SemistandardPathTableau([[], [3], [3,2], [3,3,1], + ....: [3,3,2,1], [4,3,3,1]]) sage: pt.to_pattern() [[4, 3, 3, 1, 0], [3, 3, 2, 1], [3, 3, 1], [3, 2], [3]] TESTS:: - sage: pt = path_tableaux.SemistandardPathTableau([[3,2],[3,3,1],[3,3,2,1],[4,3,3,1]]) + sage: pt = path_tableaux.SemistandardPathTableau([[3,2], [3,3,1], [3,3,2,1], [4,3,3,1]]) sage: pt.to_pattern() Traceback (most recent call last): ... @@ -466,14 +470,17 @@ def _test_jdt_promotion(self, **options): TESTS:: - sage: pt = path_tableaux.SemistandardPathTableau([(),(1,),(2,1),(4,2),(4,3,1),(4,3,3)]) + sage: pt = path_tableaux.SemistandardPathTableau([(), (1,), (2,1), (4,2), + ....: (4,3,1), (4,3,3)]) sage: pt._test_jdt_promotion() - sage: pt = path_tableaux.SemistandardPathTableau([(),(1,),(2,1),(4,2),(4,3,1),(9/2,3,3)]) + sage: pt = path_tableaux.SemistandardPathTableau([(), (1,), (2,1), (4,2), + ....: (4,3,1), (9/2,3,3)]) sage: pt._test_jdt_promotion() Traceback (most recent call last): ... - ValueError: [(), (1,), (2, 1), (4, 2, 0), (4, 3, 1, 0), (9/2, 3, 3, 0, 0)] must have all entries nonnegative integers + ValueError: [(), (1,), (2, 1), (4, 2, 0), (4, 3, 1, 0), (9/2, 3, 3, 0, 0)] + must have all entries nonnegative integers """ if not self.is_integral(): raise ValueError(f"{self} must have all entries nonnegative integers") @@ -498,6 +505,6 @@ def _an_element_(self): sage: path_tableaux.SemistandardPathTableaux()._an_element_() [(), (2,), (2, 1)] """ - return SemistandardPathTableau([[],[2],[2,1]]) + return SemistandardPathTableau([[], [2], [2,1]]) Element = SemistandardPathTableau From 873c7801028ccd765e744bc57fc53e64cfbe689e Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 20 Mar 2024 00:04:55 -0700 Subject: [PATCH 292/518] src/sage/combinat/posets/poset_examples.py: Docstring cosmetics --- src/sage/combinat/posets/poset_examples.py | 30 +++++++++++----------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/sage/combinat/posets/poset_examples.py b/src/sage/combinat/posets/poset_examples.py index 422a6f542e5..ae6887e5e89 100644 --- a/src/sage/combinat/posets/poset_examples.py +++ b/src/sage/combinat/posets/poset_examples.py @@ -741,9 +741,9 @@ def RandomPoset(n, p): INPUT: - - ``n`` - number of elements, a non-negative integer + - ``n`` -- number of elements, a non-negative integer - - ``p`` - a probability, a real number between 0 and 1 (inclusive) + - ``p`` -- a probability, a real number between 0 and 1 (inclusive) OUTPUT: @@ -971,9 +971,8 @@ def SSTPoset(s, f=None): - ``s`` -- shape of the tableaux - - ``f`` -- maximum fill number. This is an optional - argument. If no maximal number is given, it will use - the number of cells in the shape. + - ``f`` -- integer (default: ``None``); the maximum fill number. + By default (``None``), the method uses the number of cells in the shape. .. NOTE:: @@ -1010,8 +1009,8 @@ def tableaux_is_less_than(a, b): @staticmethod def StandardExample(n, facade=None): r""" - Return the partially ordered set on ``2n`` elements with - dimension ``n``. + Return the partially ordered set on `2n` elements with + dimension `n`. Let `P` be the poset on `\{0, 1, 2, \ldots, 2n-1\}` whose defining relations are that `i < j` for every `0 \leq i < n \leq j < 2n` @@ -1021,7 +1020,8 @@ def StandardExample(n, facade=None): INPUT: - ``n`` -- an integer `\ge 2`, dimension of the constructed poset - - ``facade`` (boolean) -- whether to make the returned poset a + + - ``facade`` -- boolean; whether to make the returned poset a facade poset (see :mod:`sage.categories.facade_sets`); the default behaviour is the same as the default behaviour of the :func:`~sage.combinat.posets.posets.Poset` constructor @@ -1081,9 +1081,9 @@ def SymmetricGroupBruhatIntervalPoset(start, end): INPUT: - - ``start`` - list permutation + - ``start`` -- list permutation - - ``end`` - list permutation (same n, of course) + - ``end`` -- list permutation (same n, of course) .. note:: @@ -1167,7 +1167,7 @@ def TetrahedralPoset(n, *colors, **labels): r""" Return the tetrahedral poset based on the input colors. - This method will return the tetrahedral poset with n-1 layers and + This method will return the tetrahedral poset with `n-1` layers and covering relations based on the input colors of 'green', 'red', 'orange', 'silver', 'yellow' and 'blue' as defined in [Striker2011]_. For particular color choices, the order ideals of the resulting @@ -1323,7 +1323,7 @@ def SymmetricGroupAbsoluteOrderPoset(n, labels="permutations"): - ``label`` -- (default: ``'permutations'``) a label for the elements of the poset returned by the function; the options are - * ``'permutations'`` - labels the elements are given by their + * ``'permutations'`` - labels the elements by their one-line notation * ``'reduced_words'`` - labels the elements by the lexicographically minimal reduced word @@ -1362,8 +1362,8 @@ def UpDownPoset(n, m=1): INPUT: - - ``n`` - nonnegative integer, number of elements in the poset - - ``m`` - nonnegative integer (default 1), how frequently down + - ``n`` -- nonnegative integer, number of elements in the poset + - ``m`` -- nonnegative integer (default 1), how frequently down steps occur OUTPUT: @@ -1602,7 +1602,7 @@ def DoubleTailedDiamond(n): def PermutationPattern(n): r""" Return the poset of permutations under pattern containment - up to rank ``n``. + up to rank `n`. INPUT: From ebea641dcbce1ce71d1860790ba7b596f428aadb Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 20 Mar 2024 00:05:13 -0700 Subject: [PATCH 293/518] src/sage/combinat/permutation.py: Reword docstring as suggested --- src/sage/combinat/permutation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/combinat/permutation.py b/src/sage/combinat/permutation.py index d859e5c5b37..2fcd323dccf 100644 --- a/src/sage/combinat/permutation.py +++ b/src/sage/combinat/permutation.py @@ -5511,7 +5511,7 @@ def rewind(L, n): def has_nth_root(self, n) -> bool: r""" - Decide if ``self`` has `n`-th roots. + Check if ``self`` has `n`-th roots. An `n`-th root of the permutation `\sigma` is a permutation `\gamma` such that `\gamma^n = \sigma`. From 82266d0f331bde39f945005e5ccd7bff62595bc6 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 20 Mar 2024 00:12:46 -0700 Subject: [PATCH 294/518] src/sage/combinat/tiling.py: Doctest cosmetics --- src/sage/combinat/tiling.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/sage/combinat/tiling.py b/src/sage/combinat/tiling.py index d28d85ca269..a6284b83e1b 100644 --- a/src/sage/combinat/tiling.py +++ b/src/sage/combinat/tiling.py @@ -1823,11 +1823,11 @@ def int_to_coord_dict(self): @cached_method def rows_for_piece(self, i, mod_box_isometries=False): r""" - Return the rows for the i-th piece. + Return the rows for the `i`-th piece. INPUT: - - ``i`` -- integer, the i-th piece + - ``i`` -- integer, the `i`-th piece - ``mod_box_isometries`` -- bool (default: ``False``), whether to consider only rows for positions up to the action of the @@ -1938,7 +1938,7 @@ def _rows_mod_box_isometries(self, i): Return a list of rows representing the solutions up to isometries of the box. - The positions of the ``i``-th pieces are chosen up to isometries of + The positions of the `i`-th pieces are chosen up to isometries of the box. In dimension 3, there are four times less rows for that piece. @@ -1946,7 +1946,7 @@ def _rows_mod_box_isometries(self, i): INPUT: - - ``i`` - integer, the i-th piece to consider, that piece must not + - ``i`` -- integer, the `i`-th piece to consider, that piece must not be isometric to itself by a isometry that preserve the box. EXAMPLES:: @@ -2054,7 +2054,7 @@ def row_to_polyomino(self, row_number): INPUT: - - ``row_number`` -- integer, the i-th row + - ``row_number`` -- integer, the `i`-th row OUTPUT: From c4c26ee1bf8ca0fe529175d9fd939c811ef206d3 Mon Sep 17 00:00:00 2001 From: Kwankyu Lee Date: Wed, 20 Mar 2024 18:14:09 +0900 Subject: [PATCH 295/518] Update jupyter-sphinx and pin thebe --- build/pkgs/jupyter_sphinx/checksums.ini | 6 +-- build/pkgs/jupyter_sphinx/package-version.txt | 2 +- .../0001-Patch-for-sage-live-doc.patch | 42 +++++++++++-------- 3 files changed, 28 insertions(+), 22 deletions(-) diff --git a/build/pkgs/jupyter_sphinx/checksums.ini b/build/pkgs/jupyter_sphinx/checksums.ini index 14f1bdf7594..a61d047a41d 100644 --- a/build/pkgs/jupyter_sphinx/checksums.ini +++ b/build/pkgs/jupyter_sphinx/checksums.ini @@ -1,5 +1,5 @@ tarball=jupyter_sphinx-VERSION.tar.gz -sha1=fb2abdd5e35da0886b12d45a6373c4dbcc24b244 -md5=130daa6be810976c9f8e30aa04011e50 -cksum=2882523000 +sha1=85e6e1665488fac3131da2e3ab9648037c0d1da9 +md5=397d3b20c8df015bf6a1e50d0208590d +cksum=524174657 upstream_url=https://pypi.io/packages/source/j/jupyter_sphinx/jupyter_sphinx-VERSION.tar.gz diff --git a/build/pkgs/jupyter_sphinx/package-version.txt b/build/pkgs/jupyter_sphinx/package-version.txt index 8af2848288a..2c86bfd3a12 100644 --- a/build/pkgs/jupyter_sphinx/package-version.txt +++ b/build/pkgs/jupyter_sphinx/package-version.txt @@ -1 +1 @@ -0.4.0.p0 +0.5.3.p0 diff --git a/build/pkgs/jupyter_sphinx/patches/0001-Patch-for-sage-live-doc.patch b/build/pkgs/jupyter_sphinx/patches/0001-Patch-for-sage-live-doc.patch index afbb0c1273f..1e5cf6a1c0d 100644 --- a/build/pkgs/jupyter_sphinx/patches/0001-Patch-for-sage-live-doc.patch +++ b/build/pkgs/jupyter_sphinx/patches/0001-Patch-for-sage-live-doc.patch @@ -1,15 +1,15 @@ -From 5bffbe38302c695123779f87300d84090b4bd118 Mon Sep 17 00:00:00 2001 +From 7951c3183b422bc7afe115952ef7ae15b79e45d3 Mon Sep 17 00:00:00 2001 From: Kwankyu Lee Date: Mon, 28 Aug 2023 00:18:59 +0900 Subject: [PATCH] Patch for sage live doc --- jupyter_sphinx/__init__.py | 4 ++-- - jupyter_sphinx/execute.py | 11 +++++++++++ - 2 files changed, 13 insertions(+), 2 deletions(-) + jupyter_sphinx/execute.py | 17 +++++++++++++++++ + 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/jupyter_sphinx/__init__.py b/jupyter_sphinx/__init__.py -index 34af884..b7ca8ee 100644 +index 34af884..920131c 100644 --- a/jupyter_sphinx/__init__.py +++ b/jupyter_sphinx/__init__.py @@ -31,7 +31,7 @@ from .thebelab import ThebeButton, ThebeButtonNode, ThebeOutputNode, ThebeSource @@ -17,41 +17,47 @@ index 34af884..b7ca8ee 100644 "https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.4/require.min.js" ) -THEBELAB_URL_DEFAULT = "https://unpkg.com/thebelab@^0.4.0" -+THEBELAB_URL_DEFAULT = "https://unpkg.com/thebe@latest/lib/index.js" - ++THEBELAB_URL_DEFAULT = "https://unpkg.com/thebe@0.8.2/lib/index.js" + logger = logging.getLogger(__name__) - + @@ -186,7 +186,7 @@ def setup(app): app.add_config_value("jupyter_sphinx_embed_url", None, "html") - + # thebelab config, can be either a filename or a dict - app.add_config_value("jupyter_sphinx_thebelab_config", None, "html") + app.add_config_value("jupyter_sphinx_thebelab_config", None, "env") app.add_config_value("jupyter_sphinx_thebelab_url", THEBELAB_URL_DEFAULT, "html") - + # linenos config diff --git a/jupyter_sphinx/execute.py b/jupyter_sphinx/execute.py -index 558a26b..de44455 100644 +index cb473cf..898d86b 100644 --- a/jupyter_sphinx/execute.py +++ b/jupyter_sphinx/execute.py -@@ -152,6 +152,17 @@ class ExecuteJupyterCells(SphinxTransform): +@@ -152,6 +152,23 @@ class ExecuteJupyterCells(SphinxTransform): kernel_name = default_kernel file_name = next(default_names) - + + # Save time when jupyter notebook execution is not necessary -+ if not any(not "execute" in node or node["execute"] for node in nodes): -+ # mimics empty cell output for each node ++ if all("execute" in node and not node["execute"] for node in nodes): ++ notebook = blank_nb(kernel_name) ++ try: ++ cm_language = notebook.metadata.language_info.codemirror_mode.name ++ except AttributeError: ++ cm_language = notebook.metadata.kernelspec.language ++ # Mimic empty cell output for each node + for node in nodes: + source = node.children[0] + source.attributes["classes"].append("code_cell") -+ node.attributes["cm_language"] = kernel_name -+ node += CellOutputNode(classes=["cell_output"]) ++ node.attributes["cm_language"] = cm_language ++ if len(node.children) < 2: ++ node += CellOutputNode(classes=["cell_output"]) + apply_styling(node, thebe_config) + continue + # Add empty placeholder cells for non-executed nodes so nodes # and cells can be zipped and the provided input/output # can be inserted later --- -2.42.0 +-- +2.44.0 From f0cc54ef57a814fd67dd6fdeb8daea29f84c0482 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Wed, 20 Mar 2024 12:26:00 +0100 Subject: [PATCH 296/518] fix algorithm, add doctests --- src/sage/combinat/partition.py | 67 ++++++++++++++++++++++++---------- 1 file changed, 47 insertions(+), 20 deletions(-) diff --git a/src/sage/combinat/partition.py b/src/sage/combinat/partition.py index 3e3b7aea788..3008a07bbab 100644 --- a/src/sage/combinat/partition.py +++ b/src/sage/combinat/partition.py @@ -2505,7 +2505,24 @@ def franklin_glaisher(self, s): sage: Partition([4, 3, 2, 2, 1]).franklin_glaisher(2) [3, 2, 2, 1, 1, 1, 1, 1] - TESTS:: + TESTS: + + The map preserves the size:: + + sage: all(mu.franklin_glaisher(s).size() == n + ....: for n in range(20) for mu in Partitions(n) + ....: for s in range(1, 5)) + True + + The map is bijective:: + + sage: l = [[mu.franklin_glaisher(s) + ....: for n in range(20) for mu in Partitions(n)] + ....: for s in range(1, 5)] + sage: all(len(set(ls)) == len(ls) for ls in l) + True + + The map transports the statistics:: sage: d = lambda la, s: set(p / s for p in la if p % s == 0) sage: r = lambda la, s: set(p for p in la if list(la).count(p) >= s) @@ -2513,13 +2530,23 @@ def franklin_glaisher(self, s): ....: for n in range(20) for mu in Partitions(n) ....: for s in range(1, 5)) True + + For `s=2`, the map is known to findstat:: + + sage: findmap(Partitions, lambda mu: mu.franklin_glaisher(2)) # optional - internet + 0: Mp00312 (quality [100]) """ + s = ZZ(s) + if s.is_one(): + return self mu = [] for p, m in enumerate(self.to_exp(), 1): if not p % s: - mu.extend([p//s]*(m*s)) + mu.extend([p // s]*(m*s)) else: - mu.extend(p * v * s**i for i, v in enumerate(m.digits(s)) if v) + mu.extend(p1 for i, v in enumerate(m.digits(s)) + if (p1 := p * s**i) + for _ in range(v)) P = self.parent() return P.element_class(P, sorted(mu, reverse=True)) @@ -2547,32 +2574,32 @@ def franklin_glaisher_inverse(self, s): sage: Partition([3, 2, 2, 1, 1, 1, 1, 1]).franklin_glaisher_inverse(2) [4, 3, 2, 2, 1] - TESTS:: + TESTS: - sage: d = lambda la, s: set(p / s for p in la if p % s == 0) - sage: r = lambda la, s: set(p for p in la if list(la).count(p) >= s) - sage: all(r(mu, s) == d(mu.franklin_glaisher_inverse(s), s) + The map is inverse to :meth:`franklin_glaisher`:: + + sage: all(mu.franklin_glaisher(s).franklin_glaisher_inverse(s) == mu ....: for n in range(20) for mu in Partitions(n) ....: for s in range(1, 5)) True + + For `s=2`, the map is known to findstat:: + + sage: findmap(Partitions, lambda mu: mu.franklin_glaisher_inverse(2)) # optional - internet + 0: Mp00313 (quality [100]) """ + s = ZZ(s) + if s.is_one(): + return self mu = [] - nu = [] for p, m in enumerate(self.to_exp(), 1): - mu.extend([p*s]*(m // s)) - nu.extend([p]*(m % s)) - - i = 0 - while i < len(nu): - p = nu[i] - if not p % s: - del nu[i] - nu.extend([p // s]*s) - else: - i += 1 + p = ZZ(p) + mu.extend([p * s]*(m // s)) + m1, p1 = p.val_unit(s) + mu.extend([p1]*((m % s) * s**m1)) P = self.parent() - return P.element_class(P, sorted(mu + nu, reverse=True)) + return P.element_class(P, sorted(mu, reverse=True)) def suter_diagonal_slide(self, n, exp=1): r""" From c5c8ef2c7403b8e8308ea4f570fe6f7587549ac2 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Wed, 20 Mar 2024 12:36:54 +0100 Subject: [PATCH 297/518] correct name --- src/sage/combinat/partition.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/sage/combinat/partition.py b/src/sage/combinat/partition.py index 3008a07bbab..2a189448059 100644 --- a/src/sage/combinat/partition.py +++ b/src/sage/combinat/partition.py @@ -2485,9 +2485,9 @@ def conjugate(self): par = Partitions_n(sum(self)) return par.element_class(par, conjugate(self)) - def franklin_glaisher(self, s): + def glaisher_franklin(self, s): r""" - Apply the Franklin-Glaisher bijection to ``self``. + Apply the Glaisher-Franklin bijection to ``self``. INPUT: @@ -2502,21 +2502,21 @@ def franklin_glaisher(self, s): EXAMPLES:: - sage: Partition([4, 3, 2, 2, 1]).franklin_glaisher(2) + sage: Partition([4, 3, 2, 2, 1]).glaisher_franklin(2) [3, 2, 2, 1, 1, 1, 1, 1] TESTS: The map preserves the size:: - sage: all(mu.franklin_glaisher(s).size() == n + sage: all(mu.glaisher_franklin(s).size() == n ....: for n in range(20) for mu in Partitions(n) ....: for s in range(1, 5)) True The map is bijective:: - sage: l = [[mu.franklin_glaisher(s) + sage: l = [[mu.glaisher_franklin(s) ....: for n in range(20) for mu in Partitions(n)] ....: for s in range(1, 5)] sage: all(len(set(ls)) == len(ls) for ls in l) @@ -2526,14 +2526,14 @@ def franklin_glaisher(self, s): sage: d = lambda la, s: set(p / s for p in la if p % s == 0) sage: r = lambda la, s: set(p for p in la if list(la).count(p) >= s) - sage: all(d(mu, s) == r(mu.franklin_glaisher(s), s) + sage: all(d(mu, s) == r(mu.glaisher_franklin(s), s) ....: for n in range(20) for mu in Partitions(n) ....: for s in range(1, 5)) True For `s=2`, the map is known to findstat:: - sage: findmap(Partitions, lambda mu: mu.franklin_glaisher(2)) # optional - internet + sage: findmap(Partitions, lambda mu: mu.glaisher_franklin(2)) # optional - internet 0: Mp00312 (quality [100]) """ s = ZZ(s) @@ -2551,9 +2551,9 @@ def franklin_glaisher(self, s): P = self.parent() return P.element_class(P, sorted(mu, reverse=True)) - def franklin_glaisher_inverse(self, s): + def glaisher_franklin_inverse(self, s): r""" - Apply the inverse of the Franklin-Glaisher bijection to ``self``. + Apply the inverse of the Glaisher-Franklin bijection to ``self``. INPUT: @@ -2569,23 +2569,23 @@ def franklin_glaisher_inverse(self, s): EXAMPLES:: - sage: Partition([4, 3, 2, 2, 1]).franklin_glaisher(2) + sage: Partition([4, 3, 2, 2, 1]).glaisher_franklin(2) [3, 2, 2, 1, 1, 1, 1, 1] - sage: Partition([3, 2, 2, 1, 1, 1, 1, 1]).franklin_glaisher_inverse(2) + sage: Partition([3, 2, 2, 1, 1, 1, 1, 1]).glaisher_franklin_inverse(2) [4, 3, 2, 2, 1] TESTS: - The map is inverse to :meth:`franklin_glaisher`:: + The map is inverse to :meth:`glaisher_franklin`:: - sage: all(mu.franklin_glaisher(s).franklin_glaisher_inverse(s) == mu + sage: all(mu.glaisher_franklin(s).glaisher_franklin_inverse(s) == mu ....: for n in range(20) for mu in Partitions(n) ....: for s in range(1, 5)) True For `s=2`, the map is known to findstat:: - sage: findmap(Partitions, lambda mu: mu.franklin_glaisher_inverse(2)) # optional - internet + sage: findmap(Partitions, lambda mu: mu.glaisher_franklin_inverse(2)) # optional - internet 0: Mp00313 (quality [100]) """ s = ZZ(s) From 78d31e395fd74388266ebbdfb6278e05c3e9d35f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Wed, 20 Mar 2024 16:00:35 +0100 Subject: [PATCH 298/518] use strings as label in modular-decomposition trees --- src/sage/combinat/rooted_tree.py | 10 +++++++--- src/sage/graphs/graph.py | 12 ++++++++++-- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/sage/combinat/rooted_tree.py b/src/sage/combinat/rooted_tree.py index 9bb17754233..74aeeff0bba 100644 --- a/src/sage/combinat/rooted_tree.py +++ b/src/sage/combinat/rooted_tree.py @@ -191,7 +191,7 @@ def __init__(self, parent=None, children=[], check=True): try: children = list(children) except TypeError: - raise TypeError("input ({}) is not a valid tree".format(children)) + raise TypeError(f"input ({children}) is not a valid tree") #if not (children.__class__ is self.__class__ # and children.parent() == parent): children = [self.__class__(parent, x) for x in children] @@ -215,7 +215,7 @@ def sort_key(self): .. NOTE:: The tree ``self`` must be normalized before calling this - method (see :meth:`normalize`). This doesn't matter + method (see :meth:`normalize`). This does not matter unless you are inside the :meth:`clone` context manager, because outside of it every rooted tree is already normalized. @@ -813,6 +813,10 @@ class LabelledRootedTree(AbstractLabelledClonableTree, RootedTree): - ``label`` -- any hashable Sage object (default is ``None``) + .. NOTE:: + + It is required that all labels are comparable. + EXAMPLES:: sage: x = LabelledRootedTree([], label = 3); x @@ -904,7 +908,7 @@ def sort_key(self): .. NOTE:: The tree ``self`` must be normalized before calling this - method (see :meth:`normalize`). This doesn't matter + method (see :meth:`normalize`). This does not matter unless you are inside the :meth:`clone` context manager, because outside of it every rooted tree is already normalized. diff --git a/src/sage/graphs/graph.py b/src/sage/graphs/graph.py index f7eac6cd39c..049baed05ad 100644 --- a/src/sage/graphs/graph.py +++ b/src/sage/graphs/graph.py @@ -8091,6 +8091,13 @@ def modular_decomposition(self, algorithm=None, style='tuple'): (PRIME, [1, 2, 5, 6, 0, (PARALLEL, [3, 4])]) sage: G2.modular_decomposition() (PRIME, [5, 6, 3, 4, 2, (PARALLEL, [0, 1])]) + + Check that :issue:`37631` is fixed:: + + sage: G = Graph('GxJEE?') + sage: G.modular_decomposition(style='tree') + PRIME[2[], PARALLEL[3[], 4[]], SERIES[0[], 1[]], + PARALLEL[5[], 6[], 7[]]] """ from sage.graphs.graph_decompositions.modular_decomposition import (NodeType, habib_maurer_algorithm, @@ -8128,8 +8135,9 @@ def relabel(x): def to_tree(x): if x.node_type == NodeType.NORMAL: - return LabelledRootedTree([], label=x.children[0]) - return LabelledRootedTree([to_tree(y) for y in x.children], label=x.node_type) + return LabelledRootedTree([], label=str(x.children[0])) + return LabelledRootedTree([to_tree(y) for y in x.children], + label=str(x.node_type)) return to_tree(D) From bf4ec156ebe5b5cabf7e323c6f95e03ef92eb52f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Wed, 20 Mar 2024 16:35:00 +0100 Subject: [PATCH 299/518] add Legendre transform and suspension for lazy symmetric functions --- src/sage/rings/lazy_series.py | 80 +++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/src/sage/rings/lazy_series.py b/src/sage/rings/lazy_series.py index 8d225451fed..4f913a4dceb 100644 --- a/src/sage/rings/lazy_series.py +++ b/src/sage/rings/lazy_series.py @@ -6567,6 +6567,8 @@ def revert(self): - `f = a + b p_1` with `a, b \neq 0`. + .. SEEALSO:: :meth:`legendre_transform` + EXAMPLES:: sage: # needs sage.modules @@ -6673,6 +6675,60 @@ def coefficient(n): compositional_inverse = revert + def legendre_transform(self): + r""" + Return the Legendre transform of ``self``. + + Given a symmetric function `f` of valuation 2, the Legendre + transform of `f` is the unique symmetric function `g` of + valuation 2 over the same base ring, such that + + .. MATH:: + + g \circ \partial_{p_1} f + f = p_1 \partial_{p_1} f. + + This implies that the derivatives of `f` and `g` w.r.t. `p_1` + are inverses of each other with respect to plethystic substitution. + + The Legendre transform is an involution. + + .. SEEALSO:: :meth:`revert` + + EXAMPLES:: + + sage: p = SymmetricFunctions(QQ).p() + sage: s = SymmetricFunctions(QQ).s() + sage: lp = LazySymmetricFunctions(p) + sage: A = lp(s([2])) + sage: A.legendre_transform() + (1/2*p[1,1]-1/2*p[2]) + O^9 + + sage: def asso(n): + ....: return p.sum_of_terms((Partition([d] * (n // d)), + ....: euler_phi(d) / n) for d in divisors(n)) + + sage: A = lp(asso, valuation=2) + sage: A.legendre_transform()[:5] + [1/2*p[1, 1] - 1/2*p[2], + -1/3*p[1, 1, 1] - 2/3*p[3], + 1/4*p[1, 1, 1, 1] + 1/4*p[2, 2] - 1/2*p[4]] + + TESTS:: + + sage: p = SymmetricFunctions(QQ).p() + sage: lp = LazySymmetricFunctions(p) + sage: A = lp(p([1])) + sage: A.legendre_transform() + Traceback (most recent call last): + ... + ValueError: only for series of valuation 2 + """ + if self.valuation() != 2: + raise ValueError("only for series of valuation 2") + p1 = self.parent()([1]) + derived_p1 = self.derivative_with_respect_to_p1() + return (p1 * derived_p1 - self).plethysm(derived_p1.revert()) + def derivative_with_respect_to_p1(self, n=1): r""" Return the symmetric function obtained by taking the @@ -6735,6 +6791,30 @@ def derivative_with_respect_to_p1(self, n=1): coeff_stream = Stream_shift(coeff_stream, -n) return P.element_class(P, coeff_stream) + def suspension(self): + r""" + Return the suspension of `self``. + + This is an involution, that maps the homogeneous component + `f_n` of degree `n` to `(-1)^{n - 1} \omega(f_n)`, where + `omega` is the usual involution of symmetric functions. + + EXAMPLES:: + + sage: s = SymmetricFunctions(QQ).s() + sage: ls = LazySymmetricFunctions(s) + sage: f = ls(lambda n: s([n]), valuation=1) + sage: g = f.revert().suspension(); g + s[1] + (s[1,1]) + (s[2,1]) + (s[2,1,1]+s[3,1]) + ... + sage: g.revert().suspension() + s[1] + s[2] + s[3] + s[4] + s[5] + ... + """ + P = self.parent() + coeff_stream = Stream_map_coefficients(self._coeff_stream, + lambda c: (-1)**(c.degree() + 1) * c.omega(), + P.is_sparse()) + return P.element_class(P, coeff_stream) + def functorial_composition(self, *args): r""" Return the functorial composition of ``self`` and ``g``. From 33e90467937a47a642a2b16244db00c47319efbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Wed, 20 Mar 2024 16:40:54 +0100 Subject: [PATCH 300/518] use suggestion --- src/sage/graphs/graph.py | 4 ++-- .../graphs/graph_decompositions/modular_decomposition.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/sage/graphs/graph.py b/src/sage/graphs/graph.py index 049baed05ad..35926629500 100644 --- a/src/sage/graphs/graph.py +++ b/src/sage/graphs/graph.py @@ -8096,7 +8096,7 @@ def modular_decomposition(self, algorithm=None, style='tuple'): sage: G = Graph('GxJEE?') sage: G.modular_decomposition(style='tree') - PRIME[2[], PARALLEL[3[], 4[]], SERIES[0[], 1[]], + PRIME[2[], SERIES[0[], 1[]], PARALLEL[3[], 4[]], PARALLEL[5[], 6[], 7[]]] """ from sage.graphs.graph_decompositions.modular_decomposition import (NodeType, @@ -8137,7 +8137,7 @@ def to_tree(x): if x.node_type == NodeType.NORMAL: return LabelledRootedTree([], label=str(x.children[0])) return LabelledRootedTree([to_tree(y) for y in x.children], - label=str(x.node_type)) + label=x.node_type) return to_tree(D) diff --git a/src/sage/graphs/graph_decompositions/modular_decomposition.py b/src/sage/graphs/graph_decompositions/modular_decomposition.py index 084aeaee168..6b1e825ba2e 100644 --- a/src/sage/graphs/graph_decompositions/modular_decomposition.py +++ b/src/sage/graphs/graph_decompositions/modular_decomposition.py @@ -11,10 +11,10 @@ # 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 enum import Enum +from enum import Enum, IntEnum from sage.misc.lazy_import import lazy_import from sage.misc.random_testing import random_testing @@ -22,7 +22,7 @@ lazy_import('sage.groups.perm_gps.permgroup_element', 'PermutationGroupElement') -class NodeType(Enum): +class NodeType(IntEnum): """ NodeType is an enumeration class used to define the various types of nodes in modular decomposition tree. @@ -45,7 +45,7 @@ class NodeType(Enum): NORMAL = 3 FOREST = -1 - def __repr__(self): + def __repr__(self) -> str: r""" String representation of this node type. From afb4a3133dd24267db094a8763649bd7d103706b Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Wed, 20 Mar 2024 20:22:10 +0100 Subject: [PATCH 301/518] Modified `.is_reduced()`of `binary_qf.py` Adapted the checks in `.is_reduced()` to avoid the square root computation. Furthermore added a non-singularity check. --- src/sage/quadratic_forms/binary_qf.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/sage/quadratic_forms/binary_qf.py b/src/sage/quadratic_forms/binary_qf.py index db36d9c7a3e..0b104e26eca 100755 --- a/src/sage/quadratic_forms/binary_qf.py +++ b/src/sage/quadratic_forms/binary_qf.py @@ -1474,20 +1474,31 @@ def is_reduced(self): sage: BinaryQF(1, 5, -1).is_reduced() True + TESTS: + + We check that :issue:`37635` is fixed:: + + sage: list = range(0xa19ae44106b09bfffffffffff0, 0xa19ae44106b09c000000000010) + sage: all(BinaryQF([1, 0, -x]).reduced_form().is_reduced() for x in list) + True """ D = self.discriminant() + if D.is_zero(): + raise ValueError('the quadratic form must be non-singular') + a = self._a b = self._b c = self._c if D < 0 and a > 0: return ((-a < b <= a < c) or (ZZ(0) <= b <= a == c)) - elif D < 0 and self._a < 0: + elif D < 0 and a < 0: return ((a < b <= -a < -c) or (ZZ(0) <= b <= -a == -c)) + + # Note that a = 0 implies D > 0 here else: - d = D.sqrt(prec=53) - return (((d - 2*a.abs()).abs() < b < d) + return ((b > 0 and a*c < 0 and (a-c)**2 < D) or (0 == a and -b < 2*c <= b) or (0 == c and -b < 2*a <= b)) From 06bec89d0a4803b2d23d33d38f8068237bab2c97 Mon Sep 17 00:00:00 2001 From: Kwankyu Lee Date: Thu, 21 Mar 2024 08:56:36 +0900 Subject: [PATCH 302/518] Use Issue # for github issue links --- src/sage/misc/dev_tools.py | 2 +- src/sage/misc/sagedoc.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/sage/misc/dev_tools.py b/src/sage/misc/dev_tools.py index c323fad538b..64a7cd60181 100644 --- a/src/sage/misc/dev_tools.py +++ b/src/sage/misc/dev_tools.py @@ -612,7 +612,7 @@ def expand_comma_separated_names(obj): except IndexError: if deprecation: raise LookupError( - "object named {!r} is deprecated (see github issue " + "object named {!r} is deprecated (see Issue #" "{})".format(name, deprecation)) else: raise LookupError("no object named {!r}".format(name)) diff --git a/src/sage/misc/sagedoc.py b/src/sage/misc/sagedoc.py index 61cabbd1e79..215939604d7 100644 --- a/src/sage/misc/sagedoc.py +++ b/src/sage/misc/sagedoc.py @@ -506,8 +506,7 @@ def process_dollars(s): pythonversion = sys.version.split(' ')[0] extlinks = { 'python': (f'https://docs.python.org/release/{pythonversion}/%s', None), - 'trac': ('https://github.com/sagemath/sage/issues/%s', 'github issue #%s'), # support :issue: for backward compatibility - 'issue': ('https://github.com/sagemath/sage/issues/%s', 'github issue #%s'), + 'issue': ('https://github.com/sagemath/sage/issues/%s', 'Issue #%s'), 'wikipedia': ('https://en.wikipedia.org/wiki/%s', 'Wikipedia article %s'), 'arxiv': ('https://arxiv.org/abs/%s', 'arXiv %s'), 'oeis': ('https://oeis.org/%s', 'OEIS sequence %s'), From 3f71c67f3ae0c374dd0311e2e3aa0eb4ecdb8aa3 Mon Sep 17 00:00:00 2001 From: Kwankyu Lee Date: Tue, 20 Feb 2024 17:00:14 +0900 Subject: [PATCH 303/518] Fix a doctest failure --- src/sage/misc/dev_tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/misc/dev_tools.py b/src/sage/misc/dev_tools.py index 64a7cd60181..8a4420f2314 100644 --- a/src/sage/misc/dev_tools.py +++ b/src/sage/misc/dev_tools.py @@ -501,7 +501,7 @@ def import_statements(*objects, **kwds): sage: import_statements('deprecated_RR') Traceback (most recent call last): ... - LookupError: object named 'deprecated_RR' is deprecated (see github issue 17458) + LookupError: object named 'deprecated_RR' is deprecated (see Issue #17458) sage: lazy_import('sage.all', 'RR', namespace=sage.__dict__, deprecation=17458) sage: import_statements('RR') from sage.rings.real_mpfr import RR From 89ef009dde424f50ed73c78b740fe3a87cd757bb Mon Sep 17 00:00:00 2001 From: Kwankyu Lee Date: Thu, 21 Mar 2024 10:31:23 +0900 Subject: [PATCH 304/518] Replace trac to Issue --- src/sage/graphs/path_enumeration.pyx | 2 +- src/sage/libs/giac/giac.pyx | 2 +- src/sage/libs/ntl/ntl_GF2EX.pyx | 2 +- src/sage/libs/ntl/ntl_ZZ_p.pyx | 2 +- src/sage/libs/ntl/ntl_ZZ_pE.pyx | 4 ++-- src/sage/libs/ntl/ntl_ZZ_pX.pyx | 2 +- src/sage/matrix/matrix0.pyx | 2 +- src/sage/matrix/matrix2.pyx | 2 +- src/sage/misc/reset.pyx | 2 +- src/sage/modules/with_basis/indexed_element.pyx | 2 +- src/sage/rings/finite_rings/finite_field_base.pyx | 2 +- src/sage/rings/polynomial/polynomial_real_mpfr_dense.pyx | 2 +- src/sage/rings/rational.pyx | 2 +- src/sage/sets/family.pyx | 2 +- src/sage/structure/factory.pyx | 2 +- src/sage/structure/parent.pyx | 2 +- src/sage/symbolic/expression.pyx | 4 ++-- src/sage/symbolic/function.pyx | 2 +- 18 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/sage/graphs/path_enumeration.pyx b/src/sage/graphs/path_enumeration.pyx index bae2335cc77..78832f89134 100644 --- a/src/sage/graphs/path_enumeration.pyx +++ b/src/sage/graphs/path_enumeration.pyx @@ -1597,7 +1597,7 @@ def _all_paths_iterator(self, vertex, ending_vertices=None, # the first vertex in the path). In this latter case we must # not exit the new vertex again, so we do not consider it # for further extension, but just yield it immediately. See - # trac #12385. + # Issue #12385. frozen_path = frozenset(path) for neighbor in neighbor_iterator(path[-1]): if neighbor not in frozen_path: diff --git a/src/sage/libs/giac/giac.pyx b/src/sage/libs/giac/giac.pyx index 3096e00f8af..f65d0a64b4b 100644 --- a/src/sage/libs/giac/giac.pyx +++ b/src/sage/libs/giac/giac.pyx @@ -2102,7 +2102,7 @@ class GiacInstance: libgiac=GiacInstance() -# trac #23976 (bound threads with SAGE_NUM_THREADS) +# Issue #23976 (bound threads with SAGE_NUM_THREADS) import os try: ncpus = int(os.environ['SAGE_NUM_THREADS']) diff --git a/src/sage/libs/ntl/ntl_GF2EX.pyx b/src/sage/libs/ntl/ntl_GF2EX.pyx index 4e1c4324915..8c6850fd62e 100644 --- a/src/sage/libs/ntl/ntl_GF2EX.pyx +++ b/src/sage/libs/ntl/ntl_GF2EX.pyx @@ -58,7 +58,7 @@ cdef class ntl_GF2EX(): if modulus is None: raise ValueError("You must specify a modulus when creating a GF2E.") - str_x = str(x) # can cause modulus to change trac #25790 + str_x = str(x) # can cause modulus to change; Issue #25790 self.c.restore_c() ccreadstr(self.x, str_x) diff --git a/src/sage/libs/ntl/ntl_ZZ_p.pyx b/src/sage/libs/ntl/ntl_ZZ_p.pyx index cca767303f7..261898e5ed6 100644 --- a/src/sage/libs/ntl/ntl_ZZ_p.pyx +++ b/src/sage/libs/ntl/ntl_ZZ_p.pyx @@ -127,7 +127,7 @@ cdef class ntl_ZZ_p(): mpz_to_ZZ(&den, (v.denominator()).value) ZZ_p_div(self.x, ZZ_to_ZZ_p(num), ZZ_to_ZZ_p(den)) else: - str_v = str(v) # can cause modulus to change trac #25790 + str_v = str(v) # can cause modulus to change; Issue #25790 self.c.restore_c() ccreadstr(self.x, str_v) diff --git a/src/sage/libs/ntl/ntl_ZZ_pE.pyx b/src/sage/libs/ntl/ntl_ZZ_pE.pyx index b0882217e86..a1eeb1c45a5 100644 --- a/src/sage/libs/ntl/ntl_ZZ_pE.pyx +++ b/src/sage/libs/ntl/ntl_ZZ_pE.pyx @@ -109,7 +109,7 @@ cdef class ntl_ZZ_pE(): self.x = ZZ_pX_to_ZZ_pE((v).x) elif isinstance(v, (list, tuple)): tmp_zzpx = ntl_ZZ_pX(v, self.c.pc) - self.c.restore_c() # allocating tmp_zzpx can change the current modulus trac #25790 + self.c.restore_c() # allocating tmp_zzpx can change the current modulus; Issue #25790 self.x = ZZ_pX_to_ZZ_pE(tmp_zzpx.x) elif isinstance(v, int): PyLong_to_ZZ(&temp, v) @@ -122,7 +122,7 @@ cdef class ntl_ZZ_pE(): mpz_to_ZZ(&temp, (v).value) self.x = ZZ_to_ZZ_pE(temp) else: - str_v = str(v) # can cause modulus to change trac #25790 + str_v = str(v) # can cause modulus to change; Issue #25790 self.c.restore_c() ccreadstr(self.x, str_v) diff --git a/src/sage/libs/ntl/ntl_ZZ_pX.pyx b/src/sage/libs/ntl/ntl_ZZ_pX.pyx index 3422239aaac..4db615bcd6c 100644 --- a/src/sage/libs/ntl/ntl_ZZ_pX.pyx +++ b/src/sage/libs/ntl/ntl_ZZ_pX.pyx @@ -111,7 +111,7 @@ cdef class ntl_ZZ_pX(): cc = x ZZ_pX_SetCoeff(self.x, i, cc.x) elif v is not None: - s = str(v).replace(',', ' ').replace('L', '') # can change the modulus trac #25790 + s = str(v).replace(',', ' ').replace('L', '') # can change the modulus; Issue #25790 self.c.restore_c() ccreadstr(self.x, s) diff --git a/src/sage/matrix/matrix0.pyx b/src/sage/matrix/matrix0.pyx index 9eb3f9f4213..6ee61a05598 100644 --- a/src/sage/matrix/matrix0.pyx +++ b/src/sage/matrix/matrix0.pyx @@ -5748,7 +5748,7 @@ cdef class Matrix(sage.structure.element.Matrix): # that it's 1. # # However, doing this naively causes trouble over inexact - # fields -- see trac #2256. The *right* thing to do would + # fields -- see Issue #2256. The *right* thing to do would # probably be to make sure that self.det() is nonzero. That # doesn't work here, because our det over an arbitrary field # just does expansion by minors and is unusable for even 10x10 diff --git a/src/sage/matrix/matrix2.pyx b/src/sage/matrix/matrix2.pyx index 47a836a0952..0f5c694a8be 100644 --- a/src/sage/matrix/matrix2.pyx +++ b/src/sage/matrix/matrix2.pyx @@ -7252,7 +7252,7 @@ cdef class Matrix(Matrix1): if e[1] and defect >= 0: rows.extend(e[1] + [e[1][0].parent().zero_vector()] * defect) else: - # see trac #27842 + # see Issue #27842 raise RuntimeError( "failed to compute eigenvectors for eigenvalue %s, " "check eigenvectors_left() for partial results" % e[0]) diff --git a/src/sage/misc/reset.pyx b/src/sage/misc/reset.pyx index f0e1f07a90d..1d979197d3d 100644 --- a/src/sage/misc/reset.pyx +++ b/src/sage/misc/reset.pyx @@ -7,7 +7,7 @@ import sys # Exclude these from the reset command. # DATA, base64 -- needed by the notebook -# Add exit and quit to EXCLUDE to resolve trac #22529 and trac #16704 +# Add exit and quit to EXCLUDE to resolve Issue #22529 and Issue #16704 EXCLUDE = set(['sage_mode', '__DIR__', 'DIR', 'DATA', 'base64', 'exit', 'quit']) diff --git a/src/sage/modules/with_basis/indexed_element.pyx b/src/sage/modules/with_basis/indexed_element.pyx index 5e875ef3929..31ecd6c16d2 100644 --- a/src/sage/modules/with_basis/indexed_element.pyx +++ b/src/sage/modules/with_basis/indexed_element.pyx @@ -994,7 +994,7 @@ def _unpickle_element(C, d): """ return C._from_dict(d, coerce=False, remove_zeros=False) -# Handle old CombinatorialFreeModuleElement pickles, see trac #22632 +# Handle old CombinatorialFreeModuleElement pickles, see Issue #22632 from sage.misc.persist import register_unpickle_override register_unpickle_override("sage.combinat.free_module", "CombinatorialFreeModuleElement", diff --git a/src/sage/rings/finite_rings/finite_field_base.pyx b/src/sage/rings/finite_rings/finite_field_base.pyx index 00b30bb5a44..8ceb2211a9a 100644 --- a/src/sage/rings/finite_rings/finite_field_base.pyx +++ b/src/sage/rings/finite_rings/finite_field_base.pyx @@ -876,7 +876,7 @@ cdef class FiniteField(Field): return self.characteristic()**self.degree() # cached because constructing the Factorization is slow; - # see trac #11628. + # see Issue #11628. @cached_method def factored_order(self): """ diff --git a/src/sage/rings/polynomial/polynomial_real_mpfr_dense.pyx b/src/sage/rings/polynomial/polynomial_real_mpfr_dense.pyx index 573f788d68c..00608f73403 100644 --- a/src/sage/rings/polynomial/polynomial_real_mpfr_dense.pyx +++ b/src/sage/rings/polynomial/polynomial_real_mpfr_dense.pyx @@ -705,7 +705,7 @@ cdef class PolynomialRealDense(Polynomial): TESTS:: - sage: R. = RR[] # trac #17311 + sage: R. = RR[] # Issue #17311 sage: (x^2+1)(x=5) 26.0000000000000 """ diff --git a/src/sage/rings/rational.pyx b/src/sage/rings/rational.pyx index 62091386e5d..f7fd991b817 100644 --- a/src/sage/rings/rational.pyx +++ b/src/sage/rings/rational.pyx @@ -1640,7 +1640,7 @@ cdef class Rational(sage.structure.element.FieldElement): # We should be able to run the code in the sign == 1 case # below for both cases. However, we need to do extra work to - # avoid a bug in GMP's mpz_perfect_power_p; see trac #4612 for + # avoid a bug in GMP's mpz_perfect_power_p; see Issue #4612 for # more details. # # The code in the case of sign == -1 could definitely be diff --git a/src/sage/sets/family.pyx b/src/sage/sets/family.pyx index 0e1cafa1212..36a6b0d6b1f 100644 --- a/src/sage/sets/family.pyx +++ b/src/sage/sets/family.pyx @@ -935,7 +935,7 @@ class FiniteFamilyWithHiddenKeys(FiniteFamily): hidden_function = unpickle_function(hidden_function) self.__init__(d['dictionary'], d['hidden_keys'], hidden_function) self.hidden_dictionary = d['hidden_dictionary'] - # Old pickles from before trac #22955 may not have a 'keys' + # Old pickles from before Issue #22955 may not have a 'keys' if 'keys' in d: self._keys = d['keys'] else: diff --git a/src/sage/structure/factory.pyx b/src/sage/structure/factory.pyx index ba692e1852b..8bd4bcd769e 100644 --- a/src/sage/structure/factory.pyx +++ b/src/sage/structure/factory.pyx @@ -722,7 +722,7 @@ def generic_factory_unpickle(factory, *args): return F.get_object(*args) except TypeError: pass - # See trac #16349: When replacing a UniqueFactory by something else (e.g., + # See Issue #16349: When replacing a UniqueFactory by something else (e.g., # a UniqueRepresentation), then we get the object by calling. # # The first argument of a UniqueFactory pickle is a version number. We diff --git a/src/sage/structure/parent.pyx b/src/sage/structure/parent.pyx index b63e8aa9341..533b37af6d8 100644 --- a/src/sage/structure/parent.pyx +++ b/src/sage/structure/parent.pyx @@ -2503,7 +2503,7 @@ cdef class Parent(sage.structure.category_object.CategoryObject): return self._convert_from_hash.get(S) except KeyError: mor = self.discover_convert_map_from(S) - # Before trac #14711, the morphism has been + # Before Issue #14711, the morphism has been # put both into _convert_from_list and into # _convert_from_hash. But there is no reason # to have a double book-keeping, specifically diff --git a/src/sage/symbolic/expression.pyx b/src/sage/symbolic/expression.pyx index 6a725a35102..e55bd7e09e7 100644 --- a/src/sage/symbolic/expression.pyx +++ b/src/sage/symbolic/expression.pyx @@ -7828,9 +7828,9 @@ cdef class Expression(Expression_abc): sage: gcd(I + I*x, x^2 - 1) x + 1 sage: alg = SR(QQbar(sqrt(2) + I*sqrt(3))) - sage: gcd(alg + alg*x, x^2 - 1) # known bug (trac #28489) + sage: gcd(alg + alg*x, x^2 - 1) # known bug (Issue #28489) x + 1 - sage: gcd(alg - alg*x, x^2 - 1) # known bug (trac #28489) + sage: gcd(alg - alg*x, x^2 - 1) # known bug (Issue #28489) x - 1 sage: sqrt2 = SR(QQbar(sqrt(2))) sage: gcd(sqrt2 + x, x^2 - 2) # known bug diff --git a/src/sage/symbolic/function.pyx b/src/sage/symbolic/function.pyx index 78e2cd7b51b..b8529ae2fb9 100644 --- a/src/sage/symbolic/function.pyx +++ b/src/sage/symbolic/function.pyx @@ -1105,7 +1105,7 @@ cdef class BuiltinFunction(Function): sage: p3 = AFunction('p3', 3) sage: p3(x) # needs sage.symbolic x^3 - sage: loads(dumps(cot)) == cot # trac #15138 + sage: loads(dumps(cot)) == cot # Issue #15138 True """ # check if already defined From 4889ac691078d23adf0aa9a5551e3d5167a09ecd Mon Sep 17 00:00:00 2001 From: Kwankyu Lee Date: Thu, 21 Mar 2024 15:08:45 +0900 Subject: [PATCH 305/518] Fix trac --- build/pkgs/cylp/SPKG.rst | 2 +- src/doc/en/reference/coercion/index.rst | 2 +- src/doc/en/reference/combinat/module_list.rst | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build/pkgs/cylp/SPKG.rst b/build/pkgs/cylp/SPKG.rst index 10b8192e39c..de448e0a89b 100644 --- a/build/pkgs/cylp/SPKG.rst +++ b/build/pkgs/cylp/SPKG.rst @@ -13,7 +13,7 @@ Eclipse Public License (EPL) version 2 (without a Secondary Licenses Notice). Note: This license is incompatible with the GPL according to https://www.gnu.org/licenses/license-list.html#EPL2; -see also the discussion in :trac:`26511`. +see also the discussion in :issue:`26511`. Upstream Contact ---------------- diff --git a/src/doc/en/reference/coercion/index.rst b/src/doc/en/reference/coercion/index.rst index 73dfd978008..89ae3140ff1 100644 --- a/src/doc/en/reference/coercion/index.rst +++ b/src/doc/en/reference/coercion/index.rst @@ -271,7 +271,7 @@ discovered between steps 1 and 2 above. sage: f(3).parent() Rational Field -Note that by :trac:`14711` Sage's coercion system uses maps with weak +Note that by :issue:`14711` Sage's coercion system uses maps with weak references to the domain. Such maps should only be used internally, and so a copy should be used instead (unless one knows what one is doing):: diff --git a/src/doc/en/reference/combinat/module_list.rst b/src/doc/en/reference/combinat/module_list.rst index 771d335d88d..b95632f9674 100644 --- a/src/doc/en/reference/combinat/module_list.rst +++ b/src/doc/en/reference/combinat/module_list.rst @@ -10,7 +10,7 @@ Comprehensive Module List and copy pasting the result back there. -.. TODO:: See :trac:`17421` for desirable improvements. +.. TODO:: See :issue:`17421` for desirable improvements. .. toctree:: :maxdepth: 1 From 451295c8a47d1f8b861a832076e13970f04c2b06 Mon Sep 17 00:00:00 2001 From: Kwankyu Lee Date: Thu, 21 Mar 2024 16:50:15 +0900 Subject: [PATCH 306/518] Fix trac --- src/sage_docbuild/builders.py | 4 ++-- src/sage_docbuild/conf.py | 2 +- src/sage_docbuild/sphinxbuild.py | 2 +- src/sage_docbuild/utils.py | 2 +- src/sage_setup/excepthook.py | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/sage_docbuild/builders.py b/src/sage_docbuild/builders.py index b687c0ee10b..caee6ec7be2 100644 --- a/src/sage_docbuild/builders.py +++ b/src/sage_docbuild/builders.py @@ -30,7 +30,7 @@ build. Then new rst files are generated for new and updated modules. See :meth:`get_new_and_updated_modules()`. -After :trac:`31948`, when Sage is built, :class:`ReferenceBuilder` is not used +After :issue:`31948`, when Sage is built, :class:`ReferenceBuilder` is not used and its responsibility is now taken by the ``Makefile`` in ``$SAGE_ROOT/src/doc``. """ @@ -109,7 +109,7 @@ def builder_helper(type): TESTS: - Check that :trac:`25161` has been resolved:: + Check that :issue:`25161` has been resolved:: sage: from sage_docbuild.builders import DocBuilder sage: from sage_docbuild.__main__ import setup_parser diff --git a/src/sage_docbuild/conf.py b/src/sage_docbuild/conf.py index c5329e79cfd..d659b54814a 100644 --- a/src/sage_docbuild/conf.py +++ b/src/sage_docbuild/conf.py @@ -581,7 +581,7 @@ def call_intersphinx(app, env, node, contnode): TESTS: Check that the link from the thematic tutorials to the reference - manual is relative, see :trac:`20118`:: + manual is relative, see :issue:`20118`:: sage: from sage.env import SAGE_DOC sage: thematic_index = os.path.join(SAGE_DOC, "html", "en", "thematic_tutorials", "index.html") diff --git a/src/sage_docbuild/sphinxbuild.py b/src/sage_docbuild/sphinxbuild.py index 0422b6f6866..5621fe9e456 100644 --- a/src/sage_docbuild/sphinxbuild.py +++ b/src/sage_docbuild/sphinxbuild.py @@ -205,7 +205,7 @@ def _log_line(self, line): TESTS: - Verify that :trac:`25160` has been resolved:: + Verify that :issue:`25160` has been resolved:: sage: logger = SageSphinxLogger(stdout, "#25160") sage: import traceback diff --git a/src/sage_docbuild/utils.py b/src/sage_docbuild/utils.py index d9c1109850e..50b54b2aa6b 100644 --- a/src/sage_docbuild/utils.py +++ b/src/sage_docbuild/utils.py @@ -111,7 +111,7 @@ def build_many(target, args, processes=None): * When PARI is built with multi-threading support, forking a Sage process from a thread leaves the main Pari interface instance broken - (see :trac:`26608#comment:38`). + (see :issue:`26608#comment:38`). In the future this may be replaced by a generalized version of the more robust parallel processing implementation from ``sage.doctest.forker``. diff --git a/src/sage_setup/excepthook.py b/src/sage_setup/excepthook.py index d316df06183..e5206d9c082 100644 --- a/src/sage_setup/excepthook.py +++ b/src/sage_setup/excepthook.py @@ -7,7 +7,7 @@ def excepthook(*exc): messages from ``sage-spkg``. In particular, ``build/make/install`` will recognize "sage" as a failed - package, see :trac:`16774`. + package, see :issue:`16774`. """ stars = '*' * 72 From 342f11dfa4892c1df42f04559fa6af389f635af1 Mon Sep 17 00:00:00 2001 From: Kwankyu Lee Date: Thu, 21 Mar 2024 17:17:00 +0900 Subject: [PATCH 307/518] Fix trac in doc --- src/doc/en/developer/packaging.rst | 8 ++++---- src/doc/en/developer/packaging_sage_library.rst | 6 +++--- src/doc/en/installation/source.rst | 4 ++-- .../en/prep/Quickstarts/Statistics-and-Distributions.rst | 2 +- src/doc/en/thematic_tutorials/coercion_and_categories.rst | 4 ++-- .../explicit_methods_in_number_theory/nf_introduction.rst | 2 +- src/doc/en/thematic_tutorials/lie/weyl_groups.rst | 2 +- src/doc/en/thematic_tutorials/linear_programming.rst | 2 +- .../vector_calculus/vector_calc_advanced.rst | 2 +- .../vector_calculus/vector_calc_plane.rst | 2 +- 10 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/doc/en/developer/packaging.rst b/src/doc/en/developer/packaging.rst index ea391941c9c..b4d9ad475fb 100644 --- a/src/doc/en/developer/packaging.rst +++ b/src/doc/en/developer/packaging.rst @@ -240,7 +240,7 @@ before or after the package has been installed into ``$SAGE_LOCAL``. It is encouraged to put steps which modify already installed files in a separate ``spkg-postinst.in`` script template rather than combining them with ``spkg-install.in``. This is because -since :trac:`24106`, ``spkg-install`` does not necessarily install +since :issue:`24106`, ``spkg-install`` does not necessarily install packages directly to ``$SAGE_LOCAL``. However, by the time ``spkg-postinst`` is run, the installation to ``$SAGE_LOCAL`` is complete. @@ -308,7 +308,7 @@ Likewise for :envvar:`CXXFLAGS`, :envvar:`FCFLAGS`, and :envvar:`F77FLAGS`. Prior to Sage 8.1 the shebang line was included, and the scripts were marked executable. However, this is no longer the case as of - :trac:`23179`. Now the scripts in the source tree are deliberately + :issue:`23179`. Now the scripts in the source tree are deliberately written not to be directly executed, and are only made into executable scripts when they are copied to the package's build directory. @@ -491,7 +491,7 @@ The following are also available, but rarely used. platforms.) Check shared libraries loaded by ``EXECUTABLE`` (may be a program or another library) for a library starting with ``SONAME``, and if found appends ``SONAME`` to the ``LD_PRELOAD`` environment variable. - See :trac:`24885`. + See :issue:`24885`. .. _spkg-configure.m4: @@ -642,7 +642,7 @@ constraints based on their experience and tests. When a package update is made in order to pick up a critical bug fix from a newer version, then the lower bound should be adjusted. Setting upper bounds to guard against incompatible future changes is -a complex topic; see :trac:`33520`. +a complex topic; see :issue:`33520`. .. _section-spkg-SPKG-txt: diff --git a/src/doc/en/developer/packaging_sage_library.rst b/src/doc/en/developer/packaging_sage_library.rst index d5f5a3dac4b..048a14ced5c 100644 --- a/src/doc/en/developer/packaging_sage_library.rst +++ b/src/doc/en/developer/packaging_sage_library.rst @@ -265,7 +265,7 @@ that are imposed by the build-time dependencies. We can define some meaningful small distributions that just consist of a single or a few Cython modules. For example, **sagemath-tdlib** -(:trac:`29864`) would just package the single +(:issue:`29864`) would just package the single Cython module that must be linked with ``tdlib``, :mod:`sage.graphs.graph_decompositions.tdlib`. Starting with the Sage 9.6 development cycle, as soon as namespace packages are activated, we @@ -701,7 +701,7 @@ The whole ``.tox`` directory can be safely deleted at any time. We can do the same with other distributions, for example the large distribution **sagemath-standard-no-symbolics** -(from :trac:`35095`), which is intended to provide +(from :issue:`35095`), which is intended to provide everything that is currently in the standard Sage library, i.e., without depending on optional packages, but without the packages :mod:`sage.symbolic`, :mod:`sage.calculus`, etc. @@ -713,7 +713,7 @@ Again we can run the test with ``tox`` in a separate virtual environment:: Some small distributions, for example the ones providing the two lowest levels, `sagemath-objects `_ and `sagemath-categories `_ -(from :trac:`29865`), can be installed and tested +(from :issue:`29865`), can be installed and tested without relying on the wheels from the Sage build:: $ ./bootstrap && ./sage -sh -c '(cd pkgs/sagemath-objects && SAGE_NUM_THREADS=16 tox -v -v -v -e sagepython)' diff --git a/src/doc/en/installation/source.rst b/src/doc/en/installation/source.rst index 26454e4d6d3..cc1b7159c0a 100644 --- a/src/doc/en/installation/source.rst +++ b/src/doc/en/installation/source.rst @@ -170,7 +170,7 @@ animations, Sage needs to use one of the packages :ref:`FFmpeg .. literalinclude:: void-recommended.txt In addition to these, if you don't want Sage to build optional packages that might -be available from your OS, cf. the growing list of such packages on :trac:`27330`, +be available from your OS, cf. the growing list of such packages on :issue:`27330`, install: .. tab:: Debian/Ubuntu @@ -1016,7 +1016,7 @@ Environment variables dealing with specific Sage packages .. envvar:: OPENBLAS_CONFIGURE Adds additional configuration flags for - the OpenBLAS package that gets added to the ``make`` command. (see :trac:`23272`) + the OpenBLAS package that gets added to the ``make`` command. (see :issue:`23272`) .. envvar:: PARI_CONFIGURE diff --git a/src/doc/en/prep/Quickstarts/Statistics-and-Distributions.rst b/src/doc/en/prep/Quickstarts/Statistics-and-Distributions.rst index e83516d6833..b5eb3d842a2 100644 --- a/src/doc/en/prep/Quickstarts/Statistics-and-Distributions.rst +++ b/src/doc/en/prep/Quickstarts/Statistics-and-Distributions.rst @@ -38,7 +38,7 @@ harmonic mean:: 2.5531914893617023 We do not recommend to use Python's built in ``statistics`` module with Sage. -It has a known incompatibility with number types defined in Sage, see :trac:`28234`. +It has a known incompatibility with number types defined in Sage, see :issue:`28234`. Distributions diff --git a/src/doc/en/thematic_tutorials/coercion_and_categories.rst b/src/doc/en/thematic_tutorials/coercion_and_categories.rst index 5294b37fb7f..edd89ad3c42 100644 --- a/src/doc/en/thematic_tutorials/coercion_and_categories.rst +++ b/src/doc/en/thematic_tutorials/coercion_and_categories.rst @@ -723,7 +723,7 @@ A first note on performance --------------------------- The category framework is sometimes blamed for speed regressions, as in -:trac:`9138` and :trac:`11900`. But if the category framework is *used +:issue:`9138` and :issue:`11900`. But if the category framework is *used properly*, then it is fast. For illustration, we determine the time needed to access an attribute inherited from the element class. First, we consider an element that uses the class that we implemented above, but does not use the @@ -1427,7 +1427,7 @@ Being able to do arithmetics involving elements of different parents, with the automatic creation of a pushout to contain the result, is certainly convenient\---but one should not rely on it, if speed matters. Simply the conversion of elements into different parents takes time. Moreover, by -:trac:`14058`, the pushout may be subject to Python's cyclic garbage +:issue:`14058`, the pushout may be subject to Python's cyclic garbage collection. Hence, if one does not keep a strong reference to it, the same parent may be created repeatedly, which is a waste of time. In the following example, we illustrate the slow\--down resulting from blindly relying on diff --git a/src/doc/en/thematic_tutorials/explicit_methods_in_number_theory/nf_introduction.rst b/src/doc/en/thematic_tutorials/explicit_methods_in_number_theory/nf_introduction.rst index 658154854b3..43696457a27 100644 --- a/src/doc/en/thematic_tutorials/explicit_methods_in_number_theory/nf_introduction.rst +++ b/src/doc/en/thematic_tutorials/explicit_methods_in_number_theory/nf_introduction.rst @@ -310,7 +310,7 @@ root is :math:`x^6 + 10x^3 - 2x^2 + 25`. .. warning:: - The following tests are currently broken until :trac:`5338` is + The following tests are currently broken until :issue:`5338` is fixed. .. skip diff --git a/src/doc/en/thematic_tutorials/lie/weyl_groups.rst b/src/doc/en/thematic_tutorials/lie/weyl_groups.rst index f936073a53a..6831d95af90 100644 --- a/src/doc/en/thematic_tutorials/lie/weyl_groups.rst +++ b/src/doc/en/thematic_tutorials/lie/weyl_groups.rst @@ -122,7 +122,7 @@ and whose values are the roots, you may use the inverse family:: .. NOTE:: - The behaviour of this function was changed in :trac:`20027`. + The behaviour of this function was changed in :issue:`20027`. The Weyl group is implemented as a GAP matrix group. You therefore can display its character table as follows:: diff --git a/src/doc/en/thematic_tutorials/linear_programming.rst b/src/doc/en/thematic_tutorials/linear_programming.rst index c8bf14c4eb4..4baf2936f93 100644 --- a/src/doc/en/thematic_tutorials/linear_programming.rst +++ b/src/doc/en/thematic_tutorials/linear_programming.rst @@ -455,7 +455,7 @@ following libraries are currently supported: * `CPLEX `_: Proprietary, but available for free for researchers and students through - IBM's Academic Initiative. Since :trac:`27790`, only versions 12.8 and + IBM's Academic Initiative. Since :issue:`27790`, only versions 12.8 and above are supported. Install CPLEX according to the instructions on the diff --git a/src/doc/en/thematic_tutorials/vector_calculus/vector_calc_advanced.rst b/src/doc/en/thematic_tutorials/vector_calculus/vector_calc_advanced.rst index 7bd9934cffd..78f81f6558c 100644 --- a/src/doc/en/thematic_tutorials/vector_calculus/vector_calc_advanced.rst +++ b/src/doc/en/thematic_tutorials/vector_calculus/vector_calc_advanced.rst @@ -39,7 +39,7 @@ manifold endowed with a positive definite metric tensor:: Riemannian metric g on the Euclidean space E^3 Actually ``RR`` is used here as a proxy for the real field (this should be -replaced in the future, see the discussion at :trac:`24456`) and the 53 bits of precision play +replaced in the future, see the discussion at :issue:`24456`) and the 53 bits of precision play of course no role for the symbolic computations. Let us introduce spherical and cylindrical coordinates on diff --git a/src/doc/en/thematic_tutorials/vector_calculus/vector_calc_plane.rst b/src/doc/en/thematic_tutorials/vector_calculus/vector_calc_plane.rst index 921724105a0..d790e7a36dc 100644 --- a/src/doc/en/thematic_tutorials/vector_calculus/vector_calc_plane.rst +++ b/src/doc/en/thematic_tutorials/vector_calculus/vector_calc_plane.rst @@ -500,7 +500,7 @@ manifold endowed with a positive definite metric tensor:: Actually ``RR`` is used here as a proxy for the real field (this should be replaced in the future, see the discussion at -:trac:`24456`) and the 53 bits of +:issue:`24456`) and the 53 bits of precision play of course no role for the symbolic computations. The user atlas of `\mathbb{E}^2` has two charts:: From 2dc3428b304a6cc320c6a1cc0319fa22fa64b539 Mon Sep 17 00:00:00 2001 From: Marc Mezzarobba Date: Sat, 17 Feb 2024 12:12:15 +0100 Subject: [PATCH 308/518] fix bad Frac(sparse polynomial ring over finite field) The construction of the fraction field of a polynomial ring R over a small finite fields tried ot use a class dedicated to the dense case regardless whether R was dense or sparse, resulting in corrupted elements represented as dense polynomials but with a parent set to a sparse ring. In passing, add support for fraction fields of polynomial rings over prime fields based on NTL. Fixes #37374 --- src/sage/rings/fraction_field_FpT.pyx | 12 ++++- .../polynomial/polynomial_modn_dense_ntl.pyx | 23 ++++++--- src/sage/rings/polynomial/polynomial_ring.py | 49 +++++++++++++------ 3 files changed, 61 insertions(+), 23 deletions(-) diff --git a/src/sage/rings/fraction_field_FpT.pyx b/src/sage/rings/fraction_field_FpT.pyx index 3ea53e589b8..c0b4367f00e 100644 --- a/src/sage/rings/fraction_field_FpT.pyx +++ b/src/sage/rings/fraction_field_FpT.pyx @@ -44,16 +44,26 @@ class FpT(FractionField_1poly_field): """ INPUT: - - ``R`` -- A polynomial ring over a finite field of prime order `p` with `2 < p < 2^16` + - ``R`` -- A dense polynomial ring over a finite field of prime order `p` with `2 < p < 2^16` EXAMPLES:: sage: R. = GF(31)[] sage: K = R.fraction_field(); K Fraction Field of Univariate Polynomial Ring in x over Finite Field of size 31 + + TESTS:: + + sage: from sage.rings.fraction_field_FpT import FpT + sage: FpT(PolynomialRing(GF(37), ['x'], sparse=True)) + Traceback (most recent call last): + ... + TypeError: unsupported polynomial ring """ cdef long p = R.base_ring().characteristic() assert 2 < p < FpT.INTEGER_LIMIT + if not issubclass(R.element_class, Polynomial_zmod_flint): + raise TypeError("unsupported polynomial ring") self.p = p self.poly_ring = R FractionField_1poly_field.__init__(self, R, element_class=FpTElement) diff --git a/src/sage/rings/polynomial/polynomial_modn_dense_ntl.pyx b/src/sage/rings/polynomial/polynomial_modn_dense_ntl.pyx index b5f4c7fed04..35c453338c8 100644 --- a/src/sage/rings/polynomial/polynomial_modn_dense_ntl.pyx +++ b/src/sage/rings/polynomial/polynomial_modn_dense_ntl.pyx @@ -852,14 +852,6 @@ cdef class Polynomial_dense_modn_ntl_zz(Polynomial_dense_mod_n): sage: (x-1)^5 x^5 + 95*x^4 + 10*x^3 + 90*x^2 + 5*x + 99 - Negative powers will not work:: - - sage: R. = PolynomialRing(Integers(101), implementation='NTL') - sage: (x-1)^(-5) - Traceback (most recent call last): - ... - NotImplementedError: Fraction fields not implemented for this type. - We define ``0^0`` to be unity, :issue:`13895`:: sage: R. = PolynomialRing(Integers(100), implementation='NTL') @@ -872,6 +864,21 @@ cdef class Polynomial_dense_modn_ntl_zz(Polynomial_dense_mod_n): sage: type(R(0)^0) == type(R(0)) True + Negative powers work (over prime fields) but use a generic + implementation of fraction fields:: + + sage: R. = PolynomialRing(Integers(101), implementation='NTL') + sage: f = (x-1)^(-5) + sage: type(f) + + sage: (f + 2).numerator() + 2*x^5 + 91*x^4 + 20*x^3 + 81*x^2 + 10*x + 100 + + sage: R. = PolynomialRing(Integers(100), implementation='NTL') + sage: (x-1)^(-5) + Traceback (most recent call last): + ... + TypeError: self must be an integral domain. """ cdef bint recip = 0, do_sig cdef long e = ee diff --git a/src/sage/rings/polynomial/polynomial_ring.py b/src/sage/rings/polynomial/polynomial_ring.py index b9c64884a57..129bce3942f 100644 --- a/src/sage/rings/polynomial/polynomial_ring.py +++ b/src/sage/rings/polynomial/polynomial_ring.py @@ -2460,10 +2460,9 @@ def fraction_field(self): EXAMPLES:: - sage: R. = GF(5)[] - sage: R.fraction_field() - Fraction Field of Univariate Polynomial Ring in t - over Finite Field of size 5 + sage: QQbar['x'].fraction_field() + Fraction Field of Univariate Polynomial Ring in x over Algebraic + Field TESTS: @@ -2483,17 +2482,14 @@ def fraction_field(self): sage: t(x) x + Issue :issue:`37374`: + + sage: x = PolynomialRing(GF(37), ['x'], sparse=True).fraction_field().gen() + sage: type(x.numerator()) + + sage: (x^8 + 16*x^6 + 4*x^4 + x^2 + 12).numerator() - 1 + x^8 + 16*x^6 + 4*x^4 + x^2 + 11 """ - R = self.base_ring() - p = R.characteristic() - if p != 0 and R.is_prime_field(): - try: - from sage.rings.fraction_field_FpT import FpT - except ImportError: - pass - else: - if 2 < p and p < FpT.INTEGER_LIMIT: - return FpT(self) from sage.rings.fraction_field import FractionField_1poly_field return FractionField_1poly_field(self) @@ -3566,6 +3562,31 @@ def irreducible_element(self, n, algorithm=None): # No suitable algorithm found, try algorithms from the base class. return PolynomialRing_dense_finite_field.irreducible_element(self, n, algorithm) + @cached_method + def fraction_field(self): + """ + Returns the fraction field of self. + + EXAMPLES:: + + sage: R. = GF(5)[] + sage: R.fraction_field() + Fraction Field of Univariate Polynomial Ring in t + over Finite Field of size 5 + """ + try: + from sage.rings.fraction_field_FpT import FpT + from sage.rings.polynomial.polynomial_zmod_flint import Polynomial_zmod_flint + except ImportError: + pass + else: + p = self.base_ring().characteristic() + if (issubclass(self.element_class, Polynomial_zmod_flint) + and 2 < p < FpT.INTEGER_LIMIT): + return FpT(self) + return super().fraction_field() + + def polygen(ring_or_element, name="x"): """ Return a polynomial indeterminate. From 7aa97fac5898d7b52611fd90812ca05c3d3b0d81 Mon Sep 17 00:00:00 2001 From: Marc Mezzarobba Date: Tue, 20 Feb 2024 14:47:38 +0100 Subject: [PATCH 309/518] #37377 fix/improve docs based on reviewer comments --- src/sage/rings/fraction_field_FpT.pyx | 3 ++- src/sage/rings/polynomial/polynomial_modn_dense_ntl.pyx | 2 +- src/sage/rings/polynomial/polynomial_ring.py | 6 +++--- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/sage/rings/fraction_field_FpT.pyx b/src/sage/rings/fraction_field_FpT.pyx index c0b4367f00e..75675060c69 100644 --- a/src/sage/rings/fraction_field_FpT.pyx +++ b/src/sage/rings/fraction_field_FpT.pyx @@ -44,7 +44,8 @@ class FpT(FractionField_1poly_field): """ INPUT: - - ``R`` -- A dense polynomial ring over a finite field of prime order `p` with `2 < p < 2^16` + - ``R`` -- a dense polynomial ring over a finite field of prime order + `p` with `2 < p < 2^{16}` EXAMPLES:: diff --git a/src/sage/rings/polynomial/polynomial_modn_dense_ntl.pyx b/src/sage/rings/polynomial/polynomial_modn_dense_ntl.pyx index 35c453338c8..42db7d258c7 100644 --- a/src/sage/rings/polynomial/polynomial_modn_dense_ntl.pyx +++ b/src/sage/rings/polynomial/polynomial_modn_dense_ntl.pyx @@ -864,7 +864,7 @@ cdef class Polynomial_dense_modn_ntl_zz(Polynomial_dense_mod_n): sage: type(R(0)^0) == type(R(0)) True - Negative powers work (over prime fields) but use a generic + Negative powers work (over prime fields) but use the generic implementation of fraction fields:: sage: R. = PolynomialRing(Integers(101), implementation='NTL') diff --git a/src/sage/rings/polynomial/polynomial_ring.py b/src/sage/rings/polynomial/polynomial_ring.py index 129bce3942f..1e9838e0a67 100644 --- a/src/sage/rings/polynomial/polynomial_ring.py +++ b/src/sage/rings/polynomial/polynomial_ring.py @@ -2456,7 +2456,7 @@ def lagrange_polynomial(self, points, algorithm="divided_difference", previous_r @cached_method def fraction_field(self): """ - Returns the fraction field of self. + Return the fraction field of ``self``. EXAMPLES:: @@ -2482,7 +2482,7 @@ def fraction_field(self): sage: t(x) x - Issue :issue:`37374`: + Fixed :issue:`37374`:: sage: x = PolynomialRing(GF(37), ['x'], sparse=True).fraction_field().gen() sage: type(x.numerator()) @@ -3565,7 +3565,7 @@ def irreducible_element(self, n, algorithm=None): @cached_method def fraction_field(self): """ - Returns the fraction field of self. + Return the fraction field of ``self``. EXAMPLES:: From d75e9af65b73291b877c5bb89b884abb77f9b617 Mon Sep 17 00:00:00 2001 From: Marc Mezzarobba Date: Fri, 23 Feb 2024 11:20:29 +0100 Subject: [PATCH 310/518] #37377 relax test --- src/sage/rings/polynomial/polynomial_modn_dense_ntl.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/polynomial/polynomial_modn_dense_ntl.pyx b/src/sage/rings/polynomial/polynomial_modn_dense_ntl.pyx index 42db7d258c7..c9d9ca24868 100644 --- a/src/sage/rings/polynomial/polynomial_modn_dense_ntl.pyx +++ b/src/sage/rings/polynomial/polynomial_modn_dense_ntl.pyx @@ -878,7 +878,7 @@ cdef class Polynomial_dense_modn_ntl_zz(Polynomial_dense_mod_n): sage: (x-1)^(-5) Traceback (most recent call last): ... - TypeError: self must be an integral domain. + TypeError: ... """ cdef bint recip = 0, do_sig cdef long e = ee From d7021096c4d34fb4989ec59647c8e403dc2cf8b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Thu, 21 Mar 2024 16:10:42 +0100 Subject: [PATCH 311/518] remove str wrapper --- src/sage/graphs/graph.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/graphs/graph.py b/src/sage/graphs/graph.py index 35926629500..f3ebf0369a9 100644 --- a/src/sage/graphs/graph.py +++ b/src/sage/graphs/graph.py @@ -8135,7 +8135,7 @@ def relabel(x): def to_tree(x): if x.node_type == NodeType.NORMAL: - return LabelledRootedTree([], label=str(x.children[0])) + return LabelledRootedTree([], label=x.children[0]) return LabelledRootedTree([to_tree(y) for y in x.children], label=x.node_type) @@ -8144,7 +8144,7 @@ def to_tree(x): raise ValueError("style must be 'tuple' or 'tree'") @doc_index("Graph properties") - def is_polyhedral(self): + def is_polyhedral(self) -> bool: """ Check whether the graph is the graph of the polyhedron. From 74cb087060e7458fef9b5c02b4235901c2cd6c02 Mon Sep 17 00:00:00 2001 From: "John H. Palmieri" Date: Thu, 21 Mar 2024 09:49:22 -0700 Subject: [PATCH 312/518] Update list of committee members Change the list of committee members based on the recent sage-devel vote --- CODE_OF_CONDUCT.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 5e29250d6b2..5450e81edd0 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -117,11 +117,12 @@ confidence. You can report issues to the Sage Code of Conduct Committee at sage-conduct@googlegroups.com. Currently, the committee consists of: -- Vincent Delecroix -- David Joyner +- Nils Bruin +- J-P Labbé - John Palmieri +- Viviane Pons - David Roe -- William Stein +- Julian Rüth If your report involves any members of the committee, or if they feel they have a conflict of interest in handling it, then they will recuse From bf9ffbc7ef324d35a3c1764ec08128d063b37b19 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 22 Mar 2024 00:13:31 -0700 Subject: [PATCH 313/518] build/pkgs/pari_jupyter: Update to 1.4.3 --- build/pkgs/pari_jupyter/checksums.ini | 6 +++--- build/pkgs/pari_jupyter/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/pari_jupyter/checksums.ini b/build/pkgs/pari_jupyter/checksums.ini index ee550eead3c..43f7d3085ca 100644 --- a/build/pkgs/pari_jupyter/checksums.ini +++ b/build/pkgs/pari_jupyter/checksums.ini @@ -1,5 +1,5 @@ tarball=pari-jupyter-VERSION.tar.gz -sha1=442ab166c9229df1ad1b40e26fb6d0c23b8282f4 -md5=2fdaa74ca8502aa8832c4c3eeb49d8c3 -cksum=1047550915 +sha1=b410ee0352cd58f5f140246540b71b5ff83ddf73 +md5=5a8353259bbaec1f0314d1b1b726c9cb +cksum=1481362803 upstream_url=https://pypi.io/packages/source/p/pari_jupyter/pari-jupyter-VERSION.tar.gz diff --git a/build/pkgs/pari_jupyter/package-version.txt b/build/pkgs/pari_jupyter/package-version.txt index 88c5fb891dc..428b770e3e2 100644 --- a/build/pkgs/pari_jupyter/package-version.txt +++ b/build/pkgs/pari_jupyter/package-version.txt @@ -1 +1 @@ -1.4.0 +1.4.3 From 5f32f5f882f8611dbd604c7e20dfc264ea2131bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Fri, 22 Mar 2024 09:36:56 +0100 Subject: [PATCH 314/518] use parent in asymptotic ring --- src/sage/rings/asymptotic/asymptotic_ring.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/sage/rings/asymptotic/asymptotic_ring.py b/src/sage/rings/asymptotic/asymptotic_ring.py index 49fd9ece256..be776f8d9c6 100644 --- a/src/sage/rings/asymptotic/asymptotic_ring.py +++ b/src/sage/rings/asymptotic/asymptotic_ring.py @@ -411,11 +411,11 @@ # 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.rings.ring import Algebra from sage.structure.element import CommutativeAlgebraElement +from sage.structure.parent import Parent from sage.structure.unique_representation import UniqueRepresentation from sage.misc.defaults import series_precision from sage.categories.pushout import ConstructionFunctor @@ -3405,7 +3405,7 @@ def B(self, valid_from=0): for element in self.summands.elements()) -class AsymptoticRing(Algebra, UniqueRepresentation, WithLocals): +class AsymptoticRing(Parent, UniqueRepresentation, WithLocals): r""" A ring consisting of :class:`asymptotic expansions `. @@ -3702,7 +3702,7 @@ def __init__(self, growth_group, coefficient_ring, self._default_prec_ = default_prec self._term_monoid_factory_ = term_monoid_factory self._locals_ = locals - super().__init__(base_ring=coefficient_ring, + super().__init__(base=coefficient_ring, category=category) @property @@ -4732,8 +4732,8 @@ def __init__(self, growth_group, self._term_monoid_factory_ = term_monoid_factory self._locals_ = locals - from sage.categories.rings import Rings - super().__init__(Rings(), Rings()) + from sage.categories.commutative_rings import CommutativeRings + super().__init__(CommutativeRings(), CommutativeRings()) def _repr_(self) -> str: r""" From 78a30908561392ad68127bf69e64a66a1555bd00 Mon Sep 17 00:00:00 2001 From: Peter Bruin Date: Fri, 22 Mar 2024 11:51:17 +0100 Subject: [PATCH 315/518] use factor method instead of function --- 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 b2bd0502c95..791631b724e 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -952,7 +952,7 @@ def order_with_level(self, level): # for y in self.basis()]) # O = self.quaternion_order(P) - fact = factor(M1) + fact = M1.factor() B = O.basis() for (p, r) in fact: From 4a4b9c9919574321ccd090a2d1b0a45d4e7b6a1b Mon Sep 17 00:00:00 2001 From: Lorenz Panny Date: Tue, 3 Oct 2023 12:53:14 +0200 Subject: [PATCH 316/518] =?UTF-8?q?add=20simple=20function=20to=20count=20?= =?UTF-8?q?irreducible=20polynomials=20over=20=F0=9D=94=BDq?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/sage/arith/all.py | 1 + src/sage/arith/misc.py | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/src/sage/arith/all.py b/src/sage/arith/all.py index 3446336de68..5dff477a3b1 100644 --- a/src/sage/arith/all.py +++ b/src/sage/arith/all.py @@ -81,6 +81,7 @@ prime_factors, prime_range, valuation, + number_of_irreducible_polynomials, ) lazy_import("sage.arith.misc", ("Sigma", "Moebius", "Euler_Phi"), deprecation=30322) diff --git a/src/sage/arith/misc.py b/src/sage/arith/misc.py index eef9d79e121..54f55c79165 100644 --- a/src/sage/arith/misc.py +++ b/src/sage/arith/misc.py @@ -6383,3 +6383,40 @@ def dedekind_psi(N): """ N = Integer(N) return Integer(N * prod(1 + 1 / p for p in N.prime_divisors())) + + +def number_of_irreducible_polynomials(q, n): + r""" + Return the number of irreducible polynomials of degree ``n`` + over the finite field with ``q`` elements. + + INPUT: + + - ``q`` -- prime power + - ``n`` -- positive integer + + OUTPUT: integer + + EXAMPLES:: + + sage: number_of_irreducible_polynomials(2, 8) + 30 + sage: number_of_irreducible_polynomials(9, 9) + 43046640 + + This function is *much* faster than enumerating the polynomials:: + + sage: num = number_of_irreducible_polynomials(101, 99) + sage: num.bit_length() + 653 + + ALGORITHM: + + Classical formula `\frac1n \sum_{d\mid n} \mu(n/d) q^d` using the + Möbius function `\mu`; see :func:`moebius`. + """ + q, n = ZZ(q), ZZ(n) + r = ZZ.zero() + for d in n.divisors(): + r += moebius(n//d) * q**d + return r // n From f65167cec1641fca487a69e1fa65928fa14d724c Mon Sep 17 00:00:00 2001 From: Lorenz Panny Date: Thu, 5 Oct 2023 13:21:40 +0200 Subject: [PATCH 317/518] move from sage.arith.misc to sage.combinat.q_analogues --- src/sage/arith/all.py | 1 - src/sage/arith/misc.py | 37 ------------------------------- src/sage/combinat/all.py | 2 +- src/sage/combinat/q_analogues.py | 38 ++++++++++++++++++++++++++++++++ 4 files changed, 39 insertions(+), 39 deletions(-) diff --git a/src/sage/arith/all.py b/src/sage/arith/all.py index 5dff477a3b1..3446336de68 100644 --- a/src/sage/arith/all.py +++ b/src/sage/arith/all.py @@ -81,7 +81,6 @@ prime_factors, prime_range, valuation, - number_of_irreducible_polynomials, ) lazy_import("sage.arith.misc", ("Sigma", "Moebius", "Euler_Phi"), deprecation=30322) diff --git a/src/sage/arith/misc.py b/src/sage/arith/misc.py index 54f55c79165..eef9d79e121 100644 --- a/src/sage/arith/misc.py +++ b/src/sage/arith/misc.py @@ -6383,40 +6383,3 @@ def dedekind_psi(N): """ N = Integer(N) return Integer(N * prod(1 + 1 / p for p in N.prime_divisors())) - - -def number_of_irreducible_polynomials(q, n): - r""" - Return the number of irreducible polynomials of degree ``n`` - over the finite field with ``q`` elements. - - INPUT: - - - ``q`` -- prime power - - ``n`` -- positive integer - - OUTPUT: integer - - EXAMPLES:: - - sage: number_of_irreducible_polynomials(2, 8) - 30 - sage: number_of_irreducible_polynomials(9, 9) - 43046640 - - This function is *much* faster than enumerating the polynomials:: - - sage: num = number_of_irreducible_polynomials(101, 99) - sage: num.bit_length() - 653 - - ALGORITHM: - - Classical formula `\frac1n \sum_{d\mid n} \mu(n/d) q^d` using the - Möbius function `\mu`; see :func:`moebius`. - """ - q, n = ZZ(q), ZZ(n) - r = ZZ.zero() - for d in n.divisors(): - r += moebius(n//d) * q**d - return r // n diff --git a/src/sage/combinat/all.py b/src/sage/combinat/all.py index 79690ff7305..d1f391013e2 100644 --- a/src/sage/combinat/all.py +++ b/src/sage/combinat/all.py @@ -247,7 +247,7 @@ from .integer_vector_weighted import WeightedIntegerVectors from .integer_vectors_mod_permgroup import IntegerVectorsModPermutationGroup -lazy_import('sage.combinat.q_analogues', ['gaussian_binomial', 'q_binomial']) +lazy_import('sage.combinat.q_analogues', ['gaussian_binomial', 'q_binomial', 'number_of_irreducible_polynomials']) from .species.all import * diff --git a/src/sage/combinat/q_analogues.py b/src/sage/combinat/q_analogues.py index f31cbe41589..3d84552c47a 100644 --- a/src/sage/combinat/q_analogues.py +++ b/src/sage/combinat/q_analogues.py @@ -974,3 +974,41 @@ def q_stirling_number2(n, k, q=None): return parent(q)(0) return (q**(k-1)*q_stirling_number2(n - 1, k - 1, q=q) + q_int(k, q=q) * q_stirling_number2(n - 1, k, q=q)) + + +def number_of_irreducible_polynomials(q, n): + r""" + Return the number of irreducible polynomials of degree ``n`` + over the finite field with ``q`` elements. + + INPUT: + + - ``q`` -- prime power + - ``n`` -- positive integer + + OUTPUT: integer + + EXAMPLES:: + + sage: number_of_irreducible_polynomials(2, 8) + 30 + sage: number_of_irreducible_polynomials(9, 9) + 43046640 + + This function is *much* faster than enumerating the polynomials:: + + sage: num = number_of_irreducible_polynomials(101, 99) + sage: num.bit_length() + 653 + + ALGORITHM: + + Classical formula `\frac1n \sum_{d\mid n} \mu(n/d) q^d` using the + Möbius function `\mu`; see :func:`moebius`. + """ + from sage.arith.misc import moebius + q, n = ZZ(q), ZZ(n) + r = ZZ.zero() + for d in n.divisors(): + r += moebius(n//d) * q**d + return r // n From 6702f8405bcd5acfe017c13f54397b3f4806de75 Mon Sep 17 00:00:00 2001 From: Lorenz Panny Date: Thu, 5 Oct 2023 13:27:15 +0200 Subject: [PATCH 318/518] support symbolic q in number_of_irreducible_polynomials() --- src/sage/combinat/q_analogues.py | 37 +++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/src/sage/combinat/q_analogues.py b/src/sage/combinat/q_analogues.py index 3d84552c47a..58b5c6ba3c2 100644 --- a/src/sage/combinat/q_analogues.py +++ b/src/sage/combinat/q_analogues.py @@ -976,28 +976,36 @@ def q_stirling_number2(n, k, q=None): q_int(k, q=q) * q_stirling_number2(n - 1, k, q=q)) -def number_of_irreducible_polynomials(q, n): +def number_of_irreducible_polynomials(n, q=None): r""" - Return the number of irreducible polynomials of degree ``n`` - over the finite field with ``q`` elements. + Return the number of monic irreducible polynomials of degree ``n`` + over the finite field with ``q`` elements. If ``q`` is not given, + the result is returned as a polynomial in `\QQ[q]`. INPUT: - - ``q`` -- prime power - ``n`` -- positive integer + - ``q`` -- ``None`` (default) or a prime power OUTPUT: integer EXAMPLES:: - sage: number_of_irreducible_polynomials(2, 8) + sage: number_of_irreducible_polynomials(8, q=2) 30 - sage: number_of_irreducible_polynomials(9, 9) + sage: number_of_irreducible_polynomials(9, q=9) 43046640 + :: + + sage: poly = number_of_irreducible_polynomials(12); poly + 1/12*q^12 - 1/12*q^6 - 1/12*q^4 + 1/12*q^2 + sage: poly(5) == number_of_irreducible_polynomials(12, q=5) + True + This function is *much* faster than enumerating the polynomials:: - sage: num = number_of_irreducible_polynomials(101, 99) + sage: num = number_of_irreducible_polynomials(99, q=101) sage: num.bit_length() 653 @@ -1006,9 +1014,14 @@ def number_of_irreducible_polynomials(q, n): Classical formula `\frac1n \sum_{d\mid n} \mu(n/d) q^d` using the Möbius function `\mu`; see :func:`moebius`. """ + n = ZZ(n) + if n <= 0: + raise ValueError('n must be positive') + if q is None: + q = ZZ['q'].gen() + R = parent(q) from sage.arith.misc import moebius - q, n = ZZ(q), ZZ(n) - r = ZZ.zero() - for d in n.divisors(): - r += moebius(n//d) * q**d - return r // n + r = sum((moebius(n//d) * q**d for d in ZZ(n).divisors()), R.zero()) + if R is ZZ: + return r // n + return r / n From 2c9908c2a816f068f05760862dd11386b06a8175 Mon Sep 17 00:00:00 2001 From: Max Alekseyev Date: Mon, 5 Feb 2024 16:32:58 +0100 Subject: [PATCH 319/518] count irreducible multivariate polynomials, too --- src/doc/en/reference/references/index.rst | 10 +++++ src/sage/combinat/q_analogues.py | 45 +++++++++++++++++------ 2 files changed, 44 insertions(+), 11 deletions(-) diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index 09b78514e1c..0722536b0bb 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -47,6 +47,11 @@ REFERENCES: *Quantum compution with anyons: an F-matrix and braid calculator* (2022). https://arxiv.org/abs/2212.00831 +.. [Alekseyev2006] \M. Alekseyev: + (Forum post on counting irreducible multivariate polynomials), + 2006. + https://dxdy.ru/post7034.html + .. [AB2007] \M. Aschenbrenner, C. Hillar, *Finite generation of symmetric ideals*. Trans. Amer. Math. Soc. 359 (2007), no. 11, 5171--5192. @@ -503,6 +508,11 @@ REFERENCES: *A new construction for Hadamard matrices*. Bulletin of the American Mathematical Society 71(1):169-170, 1965. +.. [Bodin2007] \A. Bodin: + Number of irreducible polynomials in several variables over finite fields, + The American Mathematical Monthly 115(7), pp. 653-660, 2008. + https://arxiv.org/abs/0706.0157 + .. [BH2012] \A. Brouwer and W. Haemers, Spectra of graphs, Springer, 2012, diff --git a/src/sage/combinat/q_analogues.py b/src/sage/combinat/q_analogues.py index 58b5c6ba3c2..95a11233fbc 100644 --- a/src/sage/combinat/q_analogues.py +++ b/src/sage/combinat/q_analogues.py @@ -976,18 +976,21 @@ def q_stirling_number2(n, k, q=None): q_int(k, q=q) * q_stirling_number2(n - 1, k, q=q)) -def number_of_irreducible_polynomials(n, q=None): +def number_of_irreducible_polynomials(n, q=None, m=1): r""" Return the number of monic irreducible polynomials of degree ``n`` - over the finite field with ``q`` elements. If ``q`` is not given, - the result is returned as a polynomial in `\QQ[q]`. + in ``m`` variables over the finite field with ``q`` elements. + + If ``q`` is not given, the result is returned as an integer-valued + polynomial in `\QQ[q]`. INPUT: - ``n`` -- positive integer - ``q`` -- ``None`` (default) or a prime power + - ``m`` -- positive integer (default `1`) - OUTPUT: integer + OUTPUT: integer or integer-valued polynomial over `\QQ` EXAMPLES:: @@ -995,6 +998,8 @@ def number_of_irreducible_polynomials(n, q=None): 30 sage: number_of_irreducible_polynomials(9, q=9) 43046640 + sage: number_of_irreducible_polynomials(5, q=11, m=3) + 2079650567184059145647246367401741345157369643207055703168 :: @@ -1002,6 +1007,10 @@ def number_of_irreducible_polynomials(n, q=None): 1/12*q^12 - 1/12*q^6 - 1/12*q^4 + 1/12*q^2 sage: poly(5) == number_of_irreducible_polynomials(12, q=5) True + sage: poly = number_of_irreducible_polynomials(5, m=3); poly + q^55 + q^54 + q^53 + q^52 + q^51 + q^50 + ... + 1/5*q^5 - 1/5*q^3 - 1/5*q^2 - 1/5*q + sage: poly(11) == number_of_irreducible_polynomials(5, q=11, m=3) + True This function is *much* faster than enumerating the polynomials:: @@ -1011,17 +1020,31 @@ def number_of_irreducible_polynomials(n, q=None): ALGORITHM: - Classical formula `\frac1n \sum_{d\mid n} \mu(n/d) q^d` using the - Möbius function `\mu`; see :func:`moebius`. + In the univariate case, classical formula + `\frac1n \sum_{d\mid n} \mu(n/d) q^d` + using the Möbius function `\mu`; + see :func:`moebius`. + + In the multivariate case, formula from [Bodin2007]_, + independently [Alekseyev2006]_. """ n = ZZ(n) if n <= 0: raise ValueError('n must be positive') if q is None: - q = ZZ['q'].gen() + from sage.rings.rational_field import QQ + q = QQ['q'].gen() # for m > 1, we produce an integer-valued polynomial in q, but it does not necessarily have integer coefficients R = parent(q) - from sage.arith.misc import moebius - r = sum((moebius(n//d) * q**d for d in ZZ(n).divisors()), R.zero()) - if R is ZZ: + if m == 1: + from sage.arith.misc import moebius + r = sum((moebius(n//d) * q**d for d in n.divisors()), R.zero()) return r // n - return r / n + elif m > 1: + from sage.functions.other import binomial + from sage.combinat.partition import Partitions + r = [] + for d in range(n): + r.append( (q**binomial(d+m,m-1) - 1) // (q-1) * q**binomial(d+m,m) - sum(prod(binomial(r_+t-1,t) for r_,t in zip(r,p.to_exp(d))) for p in Partitions(d+1,max_part=d)) ) + return r[-1] + else: + raise ValueError('m must be positive') From 4935e12a289fbc543ad2529e6c2a9ab90eaabdcf Mon Sep 17 00:00:00 2001 From: Lorenz Panny Date: Wed, 21 Feb 2024 04:58:43 +0100 Subject: [PATCH 320/518] reviewer feedback --- src/sage/combinat/q_analogues.py | 42 +++++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/src/sage/combinat/q_analogues.py b/src/sage/combinat/q_analogues.py index 95a11233fbc..4db8c47d324 100644 --- a/src/sage/combinat/q_analogues.py +++ b/src/sage/combinat/q_analogues.py @@ -1031,20 +1031,38 @@ def number_of_irreducible_polynomials(n, q=None, m=1): n = ZZ(n) if n <= 0: raise ValueError('n must be positive') + if m <= 0: + raise ValueError('m must be positive') + if q is None: from sage.rings.rational_field import QQ - q = QQ['q'].gen() # for m > 1, we produce an integer-valued polynomial in q, but it does not necessarily have integer coefficients - R = parent(q) + q = QQ['q'].gen() # we produce an integer-valued polynomial in q, but it does not necessarily have integer coefficients + if m == 1: from sage.arith.misc import moebius - r = sum((moebius(n//d) * q**d for d in n.divisors()), R.zero()) + r = sum((moebius(n//d) * q**d for d in n.divisors()), parent(q).zero()) return r // n - elif m > 1: - from sage.functions.other import binomial - from sage.combinat.partition import Partitions - r = [] - for d in range(n): - r.append( (q**binomial(d+m,m-1) - 1) // (q-1) * q**binomial(d+m,m) - sum(prod(binomial(r_+t-1,t) for r_,t in zip(r,p.to_exp(d))) for p in Partitions(d+1,max_part=d)) ) - return r[-1] - else: - raise ValueError('m must be positive') + + from sage.functions.other import binomial + from sage.combinat.partition import Partitions + + def monic_reducible(irreducible, d): + """ + Compute the number of monic reducible polynomials of degree `d` + given the numbers of irreducible polynomials up to degree `d-1`. + """ + res = 0 + for p in Partitions(d+1, max_part=d): + tmp = 1 + for r, t in zip(irreducible, p.to_exp(d)): + tmp *= binomial(r+t-1, t) + res += tmp + return res + + r = [] + for d in range(n): + monic = (q**binomial(d + m, m - 1) - 1) * q**binomial(d + m, m) // (q - 1) + reducible = monic_reducible(r, d) + r.append(monic - reducible) + + return r[-1] From d952887dff36f7ccd758e74898a6b394aa2fffe8 Mon Sep 17 00:00:00 2001 From: Lorenz Panny <84067835+yyyyx4@users.noreply.github.com> Date: Wed, 21 Feb 2024 16:46:16 +0100 Subject: [PATCH 321/518] reviewer suggestion Co-authored-by: Giacomo Pope <44242839+GiacomoPope@users.noreply.github.com> --- src/sage/combinat/q_analogues.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/sage/combinat/q_analogues.py b/src/sage/combinat/q_analogues.py index 4db8c47d324..f2b744d179f 100644 --- a/src/sage/combinat/q_analogues.py +++ b/src/sage/combinat/q_analogues.py @@ -1053,10 +1053,7 @@ def monic_reducible(irreducible, d): """ res = 0 for p in Partitions(d+1, max_part=d): - tmp = 1 - for r, t in zip(irreducible, p.to_exp(d)): - tmp *= binomial(r+t-1, t) - res += tmp + res += prod(binomial(r+t-1, t) for r, t in zip(irreducible, p.to_exp(d))) return res r = [] From 3d4c5c24c910b76483b8105aa5c00e75df464911 Mon Sep 17 00:00:00 2001 From: Eloi Torrents Date: Fri, 9 Feb 2024 15:14:05 +0100 Subject: [PATCH 322/518] remove unused variable --- src/sage/modular/btquotients/pautomorphicform.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/sage/modular/btquotients/pautomorphicform.py b/src/sage/modular/btquotients/pautomorphicform.py index 069433bb4c5..51756bda468 100644 --- a/src/sage/modular/btquotients/pautomorphicform.py +++ b/src/sage/modular/btquotients/pautomorphicform.py @@ -511,9 +511,7 @@ def riemann_sum(self, f, center=1, level=0, E=None): else: E = self.parent()._X._BT.subdivide(E, level) value = 0 - ii = 0 for e in E: - ii += 1 expansion = ((R1([e[1, 1], e[1, 0]]) ** (self.parent()._k - 2) * e.determinant() ** (-(self.parent()._k - 2) / 2)) * f(R1([e[0, 1], e[0, 0]]) / R1([e[1, 1], e[1, 0]]))).truncate(self.parent()._k - 1) dist = self.parent()._Sigma0(e.inverse(), check=False) * self.evaluate(e) value += eval_dist_at_powseries(dist, expansion) From d605b55f155431420f83681cdfa67cb0c47dca5a Mon Sep 17 00:00:00 2001 From: Eloi Torrents Date: Tue, 20 Feb 2024 18:58:32 +0100 Subject: [PATCH 323/518] Remove unused variable --- src/sage/modular/btquotients/pautomorphicform.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/sage/modular/btquotients/pautomorphicform.py b/src/sage/modular/btquotients/pautomorphicform.py index 51756bda468..a175040bfc6 100644 --- a/src/sage/modular/btquotients/pautomorphicform.py +++ b/src/sage/modular/btquotients/pautomorphicform.py @@ -1910,10 +1910,8 @@ def integrate(self, f, center=1, level=0, method='moments'): R2 = PolynomialRing(f.base_ring(), 'x') x = R2.gen() value = 0 - ii = 0 if method == 'riemann_sum': for e in E: - ii += 1 exp = ((R1([e[1, 1], e[1, 0]])) ** (self.parent()._U.weight()) * e.determinant() ** (-(self.parent()._U.weight()) / 2)) * f(R1([e[0, 1], e[0, 0]]) / R1([e[1, 1], e[1, 0]])) # exp = R2([tmp[jj] for jj in range(self.parent()._k-1)]) new = eval_dist_at_powseries(self.evaluate(e), exp.truncate(self.parent()._U.weight() + 1)) @@ -1921,7 +1919,6 @@ def integrate(self, f, center=1, level=0, method='moments'): elif method == 'moments': n = self.parent()._U.weight() for e in E: - ii += 1 a, b, c, d = e.list() delta = e.determinant() verbose('%s' % (R2([e[0, 1], e[0, 0]]) From 41f8853e3c098c15eae31b0862cd29543ef8d31a Mon Sep 17 00:00:00 2001 From: Eloi Torrents Date: Tue, 20 Feb 2024 19:00:00 +0100 Subject: [PATCH 324/518] Remove unused parameter in Coleman integral --- src/sage/modular/btquotients/pautomorphicform.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/sage/modular/btquotients/pautomorphicform.py b/src/sage/modular/btquotients/pautomorphicform.py index a175040bfc6..f92f530a9a8 100644 --- a/src/sage/modular/btquotients/pautomorphicform.py +++ b/src/sage/modular/btquotients/pautomorphicform.py @@ -2084,8 +2084,7 @@ def F(z, level=level, method=method): # So far we cannot break it into two integrals because of the pole # at infinity. - def coleman(self, t1, t2, E=None, method='moments', mult=False, - delta=-1): + def coleman(self, t1, t2, E=None, method='moments', mult=False): r""" If ``self`` is a `p`-adic automorphic form that corresponds to a rigid modular form, then this computes the From f5f28a922990a8d24696dbb544be89a03b56e420 Mon Sep 17 00:00:00 2001 From: Eloi Torrents Date: Thu, 22 Feb 2024 10:34:17 +0100 Subject: [PATCH 325/518] Remove not needed dependency on magma in example --- .../modular/btquotients/pautomorphicform.py | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/sage/modular/btquotients/pautomorphicform.py b/src/sage/modular/btquotients/pautomorphicform.py index f92f530a9a8..fb3bcad56bf 100644 --- a/src/sage/modular/btquotients/pautomorphicform.py +++ b/src/sage/modular/btquotients/pautomorphicform.py @@ -2118,21 +2118,21 @@ def coleman(self, t1, t2, E=None, method='moments', mult=False): sage: p = 7 sage: lev = 2 sage: prec = 10 - sage: X = BruhatTitsQuotient(p,lev, use_magma = True) # optional - magma - sage: k = 2 # optional - magma - sage: M = X.harmonic_cocycles(k,prec) # optional - magma - sage: B = M.basis() # optional - magma - sage: f = 3*B[0] # optional - magma - sage: MM = X.padic_automorphic_forms(k,prec,overconvergent = True) # optional - magma - sage: D = -11 # optional - magma - sage: X.is_admissible(D) # optional - magma + sage: X = BruhatTitsQuotient(p,lev) + sage: k = 2 + sage: M = X.harmonic_cocycles(k,prec) + sage: B = M.basis() + sage: f = 3*B[0] + sage: MM = X.padic_automorphic_forms(k,prec,overconvergent = True) + sage: D = -11 + sage: X.is_admissible(D) True - sage: K. = QuadraticField(D) # optional - magma - sage: Kp. = Qq(p**2,prec) # optional - magma - sage: P = Kp.gen() # optional - magma - sage: Q = 2+Kp.gen()+ p*(Kp.gen() +1) # optional - magma - sage: F = MM.lift(f) # long time, optional - magma - sage: J0 = F.coleman(P,Q,mult = True) # long time, optional - magma + sage: K. = QuadraticField(D) + sage: Kp. = Qq(p**2,prec) + sage: P = Kp.gen() + sage: Q = 2+Kp.gen()+ p*(Kp.gen() +1) + sage: F = MM.lift(f) # long time + sage: J0 = F.coleman(P,Q,mult = True) # long time AUTHORS: From 760106184ed8e684cf14ea086a0dd2522c5f67ff Mon Sep 17 00:00:00 2001 From: Eloi Torrents Date: Fri, 22 Mar 2024 13:49:46 +0100 Subject: [PATCH 326/518] remove comments --- src/sage/modular/btquotients/pautomorphicform.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/sage/modular/btquotients/pautomorphicform.py b/src/sage/modular/btquotients/pautomorphicform.py index fb3bcad56bf..f08a8f18af8 100644 --- a/src/sage/modular/btquotients/pautomorphicform.py +++ b/src/sage/modular/btquotients/pautomorphicform.py @@ -1913,7 +1913,6 @@ def integrate(self, f, center=1, level=0, method='moments'): if method == 'riemann_sum': for e in E: exp = ((R1([e[1, 1], e[1, 0]])) ** (self.parent()._U.weight()) * e.determinant() ** (-(self.parent()._U.weight()) / 2)) * f(R1([e[0, 1], e[0, 0]]) / R1([e[1, 1], e[1, 0]])) - # exp = R2([tmp[jj] for jj in range(self.parent()._k-1)]) new = eval_dist_at_powseries(self.evaluate(e), exp.truncate(self.parent()._U.weight() + 1)) value += new elif method == 'moments': @@ -2146,17 +2145,14 @@ def coleman(self, t1, t2, E=None, method='moments', mult=False): R1 = LaurentSeriesRing(K, 'r1', default_prec=self.parent()._U.base_ring().precision_cap()) if E is None: E = self.parent()._source._BT.find_covering(t1, t2) - # print('Got ', len(E), ' open balls.') value = 0 - ii = 0 value_exp = K(1) if method == 'riemann_sum': for e in E: - ii += 1 b = e[0, 1] d = e[1, 1] y = (b - d * t1) / (b - d * t2) - poly = R1(y.log()) # R1(our_log(y)) + poly = R1(y.log()) c_e = self.evaluate(e) new = eval_dist_at_powseries(c_e, poly) value += new From 41c24de2dfb679f7f5fda6cd92524f178d593ba1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Fri, 22 Mar 2024 13:56:06 +0100 Subject: [PATCH 327/518] fix typos and no "Algebra" in the doc --- src/doc/en/developer/coding_basics.rst | 2 +- src/doc/en/developer/coding_in_python.rst | 4 ++-- src/doc/en/developer/git_advanced.rst | 2 +- src/doc/en/developer/tools.rst | 2 +- src/doc/en/prep/Advanced-2DPlotting.rst | 2 +- src/doc/en/reference/references/index.rst | 2 +- src/doc/en/thematic_tutorials/lie/branching_rules.rst | 2 +- src/doc/en/thematic_tutorials/lie/crystals.rst | 4 ++-- src/doc/en/thematic_tutorials/numerical_sage/cvxopt.rst | 2 +- .../en/thematic_tutorials/numerical_sage/numerical_tools.rst | 2 +- src/doc/en/thematic_tutorials/sws2rst.rst | 2 +- 11 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/doc/en/developer/coding_basics.rst b/src/doc/en/developer/coding_basics.rst index 764d3781d33..85b0fdef4f4 100644 --- a/src/doc/en/developer/coding_basics.rst +++ b/src/doc/en/developer/coding_basics.rst @@ -1118,7 +1118,7 @@ framework. Here is a comprehensive list: This is mathematically correct, as it is guaranteed to terminate. However, there is a - nonzero probability of a timout. + nonzero probability of a timeout. - **long time:** The line is only tested if the ``--long`` option is given, e.g. ``sage -t --long f.py``. diff --git a/src/doc/en/developer/coding_in_python.rst b/src/doc/en/developer/coding_in_python.rst index 6b3b936662d..ecf9d4599d2 100644 --- a/src/doc/en/developer/coding_in_python.rst +++ b/src/doc/en/developer/coding_in_python.rst @@ -94,9 +94,9 @@ or from some other already existing Sage class: .. CODE-BLOCK:: python - from sage.rings.ring import Algebra + from sage.structure.parent import Parent - class MyFavoriteAlgebra(Algebra): + class MyFavoriteAlgebra(Parent): ... You should implement the ``_latex_`` and ``_repr_`` method for every diff --git a/src/doc/en/developer/git_advanced.rst b/src/doc/en/developer/git_advanced.rst index 650f3492695..0848e3ea2e7 100644 --- a/src/doc/en/developer/git_advanced.rst +++ b/src/doc/en/developer/git_advanced.rst @@ -359,7 +359,7 @@ Now we start by making an identical branch to the first branch:: [alice@localhost sage]$ git rebase -i HEAD~3 This will open an editor with the last 3 (corresponding to ``HEAD~3``) -commits and instuctions for how to modify them: +commits and instructions for how to modify them: .. CODE-BLOCK:: text diff --git a/src/doc/en/developer/tools.rst b/src/doc/en/developer/tools.rst index 3bc3aad8093..58ff3937d09 100644 --- a/src/doc/en/developer/tools.rst +++ b/src/doc/en/developer/tools.rst @@ -367,7 +367,7 @@ package :mod:`sage.numerical.backends` and some modules in ``./sage -pytest -n auto`` will spawn a number of workers processes equal to the number of available CPUs. -- VS Code: Install the `Python extension `_ and follow the `offical VS Code documentation `__. +- VS Code: Install the `Python extension `_ and follow the `official VS Code documentation `__. *Configuration:* ``SAGE_ROOT/src/conftest.py`` diff --git a/src/doc/en/prep/Advanced-2DPlotting.rst b/src/doc/en/prep/Advanced-2DPlotting.rst index 4ac7465ccf7..19d612850ba 100644 --- a/src/doc/en/prep/Advanced-2DPlotting.rst +++ b/src/doc/en/prep/Advanced-2DPlotting.rst @@ -213,7 +213,7 @@ to put together. ....: axes = False ....: ) sage: def sine_and_unit_circle( angle=30, instant_show = True, show_pi=True ): - ....: ccenter_x, ccenter_y = -radius, 0 # center of cirlce on real coords + ....: ccenter_x, ccenter_y = -radius, 0 # center of circle on real coords ....: sine_x = angle # the big magic to sync both graphs :) ....: current_y = circle_y = sine_y = radius * sin(angle*pi/180) ....: circle_x = ccenter_x + radius * cos(angle*pi/180) diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index 09b78514e1c..94605da564f 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -3888,7 +3888,7 @@ REFERENCES: .. [Kob1993] Neal Koblitz, *Introduction to Elliptic Curves and Modular Forms*. Springer GTM 97, 1993. -.. [Koe1999] Wolfram Koepf: Effcient Computation of Chebyshev +.. [Koe1999] Wolfram Koepf: Efficient Computation of Chebyshev Polynomials in Computer Algebra Systems: A Practical Guide. John Wiley, Chichester (1999): 79-99. diff --git a/src/doc/en/thematic_tutorials/lie/branching_rules.rst b/src/doc/en/thematic_tutorials/lie/branching_rules.rst index 40eaf488f1d..f2c24f0e8be 100644 --- a/src/doc/en/thematic_tutorials/lie/branching_rules.rst +++ b/src/doc/en/thematic_tutorials/lie/branching_rules.rst @@ -222,7 +222,7 @@ to the right of the colon onto the command line:: sage:branching_rule("E7","A2","miscellaneous") miscellaneous branching rule E7 => A2 -There are two distict embeddings of `A_1=\hbox{SL}(2)` into +There are two distinct embeddings of `A_1=\hbox{SL}(2)` into `E_7` as maximal subgroups, so the ``maximal_subgroup`` method will return a list of rules:: diff --git a/src/doc/en/thematic_tutorials/lie/crystals.rst b/src/doc/en/thematic_tutorials/lie/crystals.rst index 21f81053586..fc3d867e532 100644 --- a/src/doc/en/thematic_tutorials/lie/crystals.rst +++ b/src/doc/en/thematic_tutorials/lie/crystals.rst @@ -731,7 +731,7 @@ and `(2,1,0)`. This decomposition is predicted by Frobenius-Schur duality: the multiplicity of `\pi_\lambda^{GL(n)}` in `\otimes^3\mathbf{C}^3` is the degree of `\pi_\lambda^{S_3}`. -It is useful to be able to select one irreducible constitutent of +It is useful to be able to select one irreducible constituent of ``T``. If we only want one of the irreducible constituents of ``T``, we can specify a list of highest weight vectors by the option ``generators``. If the list has only one element, then we get an @@ -881,7 +881,7 @@ subcrystal of ``Cspin`` `\otimes \mathcal{B}_\mu`, where B3(1/2,1/2,1/2) + B3(3/2,1/2,1/2) + B3(3/2,3/2,1/2) We see that just taking the tensor product of these two crystals will -produce a reducible crystal with three constitutents, and we want to +produce a reducible crystal with three constituents, and we want to extract the one we want. We do that as follows:: sage: B3 = WeylCharacterRing("B3") diff --git a/src/doc/en/thematic_tutorials/numerical_sage/cvxopt.rst b/src/doc/en/thematic_tutorials/numerical_sage/cvxopt.rst index 56ffac780ad..da3e04051f6 100644 --- a/src/doc/en/thematic_tutorials/numerical_sage/cvxopt.rst +++ b/src/doc/en/thematic_tutorials/numerical_sage/cvxopt.rst @@ -101,7 +101,7 @@ For a simple linear programming example, if we want to solve .. math:: \begin{array}{cc} - \text{minimze} & -4x_1-5x_2\\ + \text{minimize} & -4x_1-5x_2\\ \text{subject to} & 2x_1 +x_2\le 3\\ & x_1+2x_2\le 3\\ & x_1 \ge 0 \\ diff --git a/src/doc/en/thematic_tutorials/numerical_sage/numerical_tools.rst b/src/doc/en/thematic_tutorials/numerical_sage/numerical_tools.rst index 49150c90401..4e8172773dd 100644 --- a/src/doc/en/thematic_tutorials/numerical_sage/numerical_tools.rst +++ b/src/doc/en/thematic_tutorials/numerical_sage/numerical_tools.rst @@ -18,7 +18,7 @@ packages. Before we start let us point out http://www.scipy.org/NumPy_for_Matlab_Users, which has a comparison between matlab and numpy and gives numpy equivalents of -matlab commands. If you're not familiar with matlab, thats fine, even +matlab commands. If you are not familiar with matlab, that's fine, even better, it means you won't have any pre-conceived notions of how things should work. Also this http://www.scipy.org/Wiki/Documentation?action=AttachFile&do=get&target=scipy_tutorial.pdf diff --git a/src/doc/en/thematic_tutorials/sws2rst.rst b/src/doc/en/thematic_tutorials/sws2rst.rst index 3aecd37a5b9..1756fa4dbc4 100644 --- a/src/doc/en/thematic_tutorials/sws2rst.rst +++ b/src/doc/en/thematic_tutorials/sws2rst.rst @@ -17,7 +17,7 @@ this with the path to your Sage installation, such as ``/Applications/Sage-9.2.app/Contents/Resources/sage/sage`` if you are using the Mac app and have placed it in your Applications directory. -* Next, you will need an optional package to covert your worksheet. Use the +* Next, you will need an optional package to convert your worksheet. Use the command: .. CODE-BLOCK:: shell-session From 51d423d5a2d00364c49ae6a6bb107fcc701dd2bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Fri, 22 Mar 2024 14:13:26 +0100 Subject: [PATCH 328/518] more typos --- src/doc/en/reference/references/index.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index 94605da564f..d069bc4ad1c 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -1191,7 +1191,7 @@ REFERENCES: .. [BR2010] Matthew Baker and Robert Rumely. Potential theory and dynamics on the Berkovich projective line. Mathematical Surveys and Monographs, - Volumne 159. 2010. + Volume 159. 2010. .. [BR2010a] Jean Berstel and Christophe Reutenauer, *Noncommutative Rational Series With Applications*. @@ -1584,7 +1584,7 @@ REFERENCES: .. [Cha18] Frédéric Chapoton, *Some properties of a new partial order on Dyck paths*, 2018, :arxiv:`1809.10981` -.. [Cha22005] \B. Cha. Vanishing of some cohomology goups and bounds +.. [Cha22005] \B. Cha. Vanishing of some cohomology groups and bounds for the Shafarevich-Tate groups of elliptic curves. J. Number Theory, 111:154-178, 2005. @@ -1638,7 +1638,7 @@ REFERENCES: Wiley, 1989. .. [CK2008] Derek G. Corneil and Richard M. Krueger, *A Unified View - of Graph Searching*, SIAM Jounal on Discrete Mathematics, + of Graph Searching*, SIAM Journal on Discrete Mathematics, 22(4), 1259–-1276, 2008. :doi:`10.1137/050623498` From 6211e21e65cc41bc33cd8c6509f2a9ffcc68ae63 Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Sat, 23 Mar 2024 01:31:19 +0100 Subject: [PATCH 329/518] Removed caching of determinant --- src/sage/matrix/matrix_integer_dense.pyx | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/src/sage/matrix/matrix_integer_dense.pyx b/src/sage/matrix/matrix_integer_dense.pyx index b197ba8825e..2e981ed7f7b 100644 --- a/src/sage/matrix/matrix_integer_dense.pyx +++ b/src/sage/matrix/matrix_integer_dense.pyx @@ -2980,9 +2980,9 @@ cdef class Matrix_integer_dense(Matrix_dense): zero rows, so that the output has the same dimensions as the input. The transformation matrix is always invertible over the integers. - Also the rank (and the determinant) of ``self`` are cached if those are - computed during the reduction. Note that in general this only happens - when ``self.rank() == self.ncols()`` and the exact algorithm is used. + Also the rank of ``self`` is cached if it is computed during the + reduction. Note that in general this only happens when + ``self.rank() == self.ncols()`` and the exact algorithm is used. INPUT: @@ -3229,15 +3229,9 @@ cdef class Matrix_integer_dense(Matrix_dense): if algorithm == "NTL:LLL": if transformation: - r, det2, UNTL = A.LLL(a,b, verbose=verb, return_U=True) + r, _, UNTL = A.LLL(a,b, verbose=verb, return_U=True) else: - r, det2 = A.LLL(a,b, verbose=verb) - det2 = ZZ(det2) - try: - det = ZZ(det2.sqrt()) - self.cache("det", det) - except TypeError: - pass + r, _ = A.LLL(a,b, verbose=verb) elif algorithm == "NTL:LLL_FP": if use_givens: r = A.G_LLL_FP(delta, verbose=verb, return_U=transformation) From 5e66b35335d593c00ca4b4969f97e8f4a3114077 Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Sat, 23 Mar 2024 01:43:32 +0100 Subject: [PATCH 330/518] Added test for #37236 --- src/sage/matrix/matrix_integer_dense.pyx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/sage/matrix/matrix_integer_dense.pyx b/src/sage/matrix/matrix_integer_dense.pyx index 2e981ed7f7b..0da185d8c4c 100644 --- a/src/sage/matrix/matrix_integer_dense.pyx +++ b/src/sage/matrix/matrix_integer_dense.pyx @@ -3155,6 +3155,12 @@ cdef class Matrix_integer_dense(Matrix_dense): sage: M._cache {'rank': 2} + Check that :issue:`37236` is fixed: + + sage: m = matrix(ZZ, 2, 2, [-1,1,1,1]) + sage: m.LLL(algorithm="NTL:LLL"); m.det() + -2 + .. NOTE:: See :mod:`sage.libs.ntl.ntl_mat_ZZ.ntl_mat_ZZ.LLL` and From 7a9473d11104593860767692cabf4ed07c9b1f2b Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Sat, 23 Mar 2024 02:13:12 +0100 Subject: [PATCH 331/518] Fix doctest --- src/sage/matrix/matrix_integer_dense.pyx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sage/matrix/matrix_integer_dense.pyx b/src/sage/matrix/matrix_integer_dense.pyx index 0da185d8c4c..d747aa360a6 100644 --- a/src/sage/matrix/matrix_integer_dense.pyx +++ b/src/sage/matrix/matrix_integer_dense.pyx @@ -3157,9 +3157,9 @@ cdef class Matrix_integer_dense(Matrix_dense): Check that :issue:`37236` is fixed: - sage: m = matrix(ZZ, 2, 2, [-1,1,1,1]) - sage: m.LLL(algorithm="NTL:LLL"); m.det() - -2 + sage: M = matrix(ZZ, 2, 2, [-1,1,1,1]) + sage: L = M.LLL(algorithm="NTL:LLL"); M.det() == L.det() + True .. NOTE:: From 679b9efa5ef25ebd487dd4acc3a82d6e64d4d8f3 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Sat, 23 Mar 2024 13:54:07 +0100 Subject: [PATCH 332/518] fix_link_is_isotopic initial --- src/sage/knots/knotinfo.py | 72 ++++++- src/sage/knots/link.py | 423 +++++++++++++++++++++++++------------ 2 files changed, 350 insertions(+), 145 deletions(-) diff --git a/src/sage/knots/knotinfo.py b/src/sage/knots/knotinfo.py index 18587bd8cf0..8deb3fbe37a 100644 --- a/src/sage/knots/knotinfo.py +++ b/src/sage/knots/knotinfo.py @@ -204,7 +204,7 @@ ....: [17,19,8,18], [9,10,11,14], [10,12,13,11], ....: [12,19,15,13], [20,16,14,15], [16,20,17,2]]) sage: L.get_knotinfo() - (, None) + (, ) REFERENCES: @@ -335,6 +335,47 @@ def knotinfo_bool(string): return False raise ValueError('%s is not a KnotInfo boolean') + +class SymmetryMutant(Enum): + r""" + Enum to specify the symmetry mutant link of the prime link listed in the + KnotInfo and LinkInfo databases. From the KnotInfo description page: + + If a knot is viewed as the oriented diffeomorphism + class of an oriented pair, `K = (S_3, S_1)`, with `S_i` + diffeomorphic to `S^i`, there are four oriented knots + associated to any particular knot `K`. In addition to + `K` itself, there is the reverse, `K^r = (S_3, -S_1)`, + the concordance inverse, `-K = (-S_3, -S_1)`, and the + mirror image, `K^m = (-S_3, S_1)`. + """ + itself ='s' + reverse ='r' + concordance_inverse ='mr' + mirror_image ='m' + mixed = 'x' # to be used in connection with KnotInfoSeries + unknown ='?' + + def __gt__(self, other): + r""" + Implement comparison of different items in order to have ``sorted`` work. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import SymmetryMutant + sage: sorted(SymmetryMutant) # indirect doctest + [, + , + , + , + , + ] + """ + if self.__class__ is other.__class__: + return self.value < other.value + return NotImplemented + + # --------------------------------------------------------------------------------- # KnotInfoBase # --------------------------------------------------------------------------------- @@ -946,12 +987,22 @@ def is_reversible(self): sage: KnotInfo.K6_3.is_reversible() True """ - symmetry_type = self.symmetry_type() - if symmetry_type == 'reversible': - return True - if symmetry_type == 'fully amphicheiral': + if self.is_knot(): + symmetry_type = self.symmetry_type() + if symmetry_type == 'reversible': + return True + if symmetry_type == 'fully amphicheiral': + return True + return False + + # revert orientation + b = self.braid() + bt = list(b.Tietze()) + bt.reverse() + br = b.parent()(tuple(bt)) + if b.is_conjugated(br): return True - return False + return None @cached_method def is_amphicheiral(self, positive=False): @@ -2123,6 +2174,9 @@ def recover(mirror, braid): else: l = self.link() if mirror: + if self.is_amphicheiral(): + # no need to test again + return True l = l.mirror_image() def check_result(L, m): @@ -2131,12 +2185,10 @@ def check_result(L, m): """ if L != self: return False - if m is None or m == '?': - return True if mirror: - return m + return m is SymmetryMutant.mirror_image else: - return not m + return m is SymmetryMutant.itself try: L, m = l.get_knotinfo() diff --git a/src/sage/knots/link.py b/src/sage/knots/link.py index d39cb97c862..054f1c47eb0 100644 --- a/src/sage/knots/link.py +++ b/src/sage/knots/link.py @@ -2410,16 +2410,16 @@ def remove_loops(self): EXAMPLES:: sage: b = BraidGroup(4)((3, 2, -1, -1)) - sage: L = Link(b) - sage: L.remove_loops() - Link with 2 components represented by 2 crossings - sage: K4 = Link([[1, 7, 2, 6], [3, 1, 4, 8], [5, 5, 6, 4], [7, 3, 8, 2]]) - sage: K3 = K4.remove_loops() - sage: K3.pd_code() - [[1, 7, 2, 4], [3, 1, 4, 8], [7, 3, 8, 2]] - sage: U = Link([[1, 2, 2, 1]]) - sage: U.remove_loops() - Link with 1 component represented by 0 crossings + sage: L = Link(b) + sage: L.remove_loops() + Link with 2 components represented by 2 crossings + sage: K4 = Link([[1, 7, 2, 6], [3, 1, 4, 8], [5, 5, 6, 4], [7, 3, 8, 2]]) + sage: K3 = K4.remove_loops() + sage: K3.pd_code() + [[1, 7, 2, 4], [3, 1, 4, 8], [7, 3, 8, 2]] + sage: U = Link([[1, 2, 2, 1]]) + sage: U.remove_loops() + Link with 1 component represented by 0 crossings """ pd = self.pd_code() new_pd = [] @@ -2579,7 +2579,8 @@ def reverse(self): if self._reverse: return self._reverse - if self._braid: + b = self._braid + if b and len(b.Tietze()) <= len(self.pd_code()): self._reverse = type(self)(self._braid.reverse()) self._reverse._reverse = self return self._reverse @@ -2992,7 +2993,7 @@ def homfly_polynomial(self, var1=None, var2=None, normalization='lm'): Comparison with KnotInfo:: sage: KI, m = K.get_knotinfo(); KI, m - (, False) + (, ) sage: K.homfly_polynomial(normalization='vz') == KI.homfly_polynomial() True @@ -3930,13 +3931,12 @@ def _knotinfo_matching_list(self): EXAMPLES:: - sage: from sage.knots.knotinfo import KnotInfo sage: KnotInfo.L5a1_0.inject() Defining L5a1_0 - sage: L5a1_0.link()._knotinfo_matching_list() - ([], True) - sage: Link(L5a1_0.braid())._knotinfo_matching_list() + sage: ML = L5a1_0.link()._knotinfo_matching_list(); ML ([, ], True) + sage: ML == Link(L5a1_0.braid())._knotinfo_matching_list() + True Care is needed for links having non irreducible HOMFLY-PT polynomials:: @@ -3993,7 +3993,9 @@ def _knotinfo_matching_list(self): res = [] for L in l: if L.pd_notation() == pd_code: - return [L], True # pd_notation is unique in the KnotInfo database + # pd_notation is unique in the KnotInfo database + res.append(L) + continue Lbraid = L.braid() if Lbraid.strands() <= br_ind: @@ -4005,6 +4007,45 @@ def _knotinfo_matching_list(self): return res, True return l, False + def _knotinfo_matching_dict(self): + r""" + Return a dictionary mapping items of the enum :class:`~sage.knots.knotinfo.SymmetryType` + to list of links from the KnotInfo and LinkInfo databases which match + the properties of the according symmetry mutant of ``self`` as much as + possible. + + OUTPUT: + + A pair (``match_lists, proves``) of dictionaries with keys from the + enum :class:`~sage.knots.knotinfo.SymmetryType`. The first dictionary maps these keys to + the corresponding matching list and ``proves`` maps them to booleans + telling if the entries of the corresponding ``match_lists`` are checked + to be isotopic to the symmetry mutant of ``self`` or not. + + EXAMPLES:: + + sage: KnotInfo.L4a1_0.inject() + Defining L4a1_0 + sage: L4a1_0.link()._knotinfo_matching_dict() + ({: [], + : [], + : [], + : []}, + {: True, + : True, + : False, + : False}) + """ + from sage.knots.knotinfo import SymmetryMutant + mutant = {} + mutant[SymmetryMutant.itself] = self + mutant[SymmetryMutant.mirror_image] = self.mirror_image() + mutant[SymmetryMutant.reverse] = self.reverse() + mutant[SymmetryMutant.concordance_inverse] = mutant[SymmetryMutant.mirror_image].reverse() + match_lists = {k: list(mutant[k]._knotinfo_matching_list()[0]) for k in mutant.keys()} + proves = {k: mutant[k]._knotinfo_matching_list()[1] for k in mutant.keys()} + return match_lists, proves + def get_knotinfo(self, mirror_version=True, unique=True): r""" Identify this link as an item of the KnotInfo database (if possible). @@ -4013,7 +4054,7 @@ def get_knotinfo(self, mirror_version=True, unique=True): - ``mirror_version`` -- boolean (default is ``True``). If set to ``False`` the result of the method will be just the instance of :class:`~sage.knots.knotinfo.KnotInfoBase` - (by default the result is a tuple of the instance and a boolean, see + (by default the result is a tuple of the instance and an enum, see explanation of the output below) - ``unique`` -- boolean (default is ``True``). This only affects the case @@ -4023,10 +4064,10 @@ def get_knotinfo(self, mirror_version=True, unique=True): OUTPUT: A tuple ``(K, m)`` where ``K`` is an instance of :class:`~sage.knots.knotinfo.KnotInfoBase` - and ``m`` a boolean (for chiral links) telling if ``self`` corresponds - to the mirrored version of ``K`` or not. The value of ``m`` is ``None`` - for amphicheiral links and ``?`` if it cannot be determined uniquely - and the keyword option ``unique=False`` is given. + and ``m`` an instance of :class:`~sage.knots.knotinfo.SymmetryMutant` + (for chiral links) specifying the symmetry mutant of ``K`` to which + ``self`` is isotopic. The value of ``m`` is ``unknown`` if it cannot + be determined uniquely and the keyword option ``unique=False`` is given. For proper links, if the orientation mutant cannot be uniquely determined, K will be a series of links gathering all links having the same unoriented @@ -4065,11 +4106,22 @@ def get_knotinfo(self, mirror_version=True, unique=True): ....: [18,10,19,9], [2,12,3,11], [13,21,14,20], [15,7,16,6], ....: [22,17,1,18], [8,20,9,19], [21,15,22,14]]) sage: L.get_knotinfo() - (, True) + (, ) sage: K = KnotInfo.K10_25 sage: l = K.link() sage: l.get_knotinfo() - (, False) + (, ) + sage: k11 = KnotInfo.K11n_82.link() + sage: k11m = k11.mirror_image() + sage: k11mr = k11m.reverse() + sage: k11mr.get_knotinfo() + (, ) + sage: k11r = k11.reverse() + sage: k11r.get_knotinfo() + (, ) + sage: k11rm = k11r.mirror_image() + sage: k11rm.get_knotinfo() + (, ) Knots with more than 13 and proper links having more than 11 crossings cannot be identified. In addition non prime links or even links whose @@ -4077,7 +4129,7 @@ def get_knotinfo(self, mirror_version=True, unique=True): sage: b, = BraidGroup(2).gens() sage: Link(b**13).get_knotinfo() # optional - database_knotinfo - (, False) + (, ) sage: Link(b**14).get_knotinfo() Traceback (most recent call last): ... @@ -4096,7 +4148,7 @@ def get_knotinfo(self, mirror_version=True, unique=True): ....: [17,19,8,18], [9,10,11,14], [10,12,13,11], ....: [12,19,15,13], [20,16,14,15], [16,20,17,2]]) sage: L.get_knotinfo() - (, None) + (, ) Usage of option ``mirror_version``:: @@ -4113,17 +4165,10 @@ def get_knotinfo(self, mirror_version=True, unique=True): NotImplementedError: this link cannot be uniquely determined use keyword argument `unique` to obtain more details sage: l.get_knotinfo(unique=False) - [(, False), (, False)] - sage: k11 = KnotInfo.K11n_82.link() - sage: k11m = k11.mirror_image() - sage: k11mr = k11m.reverse() - sage: k11mr.get_knotinfo() - Traceback (most recent call last): - ... - NotImplementedError: mirror type of this link cannot be uniquely determined - use keyword argument `unique` to obtain more details - sage: k11mr.get_knotinfo(unique=False) - [(, '?')] + [(, ), + (, ), + (, ), + (, )] sage: t = (1, -2, 1, 1, -2, 1, -2, -2) sage: l8 = Link(BraidGroup(3)(t)) sage: l8.get_knotinfo() @@ -4132,8 +4177,8 @@ def get_knotinfo(self, mirror_version=True, unique=True): NotImplementedError: this link cannot be uniquely determined use keyword argument `unique` to obtain more details sage: l8.get_knotinfo(unique=False) - [(, None), - (, None)] + [(, ), + (, )] sage: t = (2, -3, -3, -2, 3, 3, -2, 3, 1, -2, -2, 1) sage: l12 = Link(BraidGroup(5)(t)) sage: l12.get_knotinfo() @@ -4142,10 +4187,14 @@ def get_knotinfo(self, mirror_version=True, unique=True): NotImplementedError: this link having more than 11 crossings cannot be uniquely determined use keyword argument `unique` to obtain more details sage: l12.get_knotinfo(unique=False) - [(, '?'), - (, None), - (, None), - (, None)] + [(, ), + (, ), + (, ), + (, + ), + (, ), + (, ), + (, )] Furthermore, if the result is a complete series of oriented links having the same unoriented name (according to the note above) the option can be @@ -4153,33 +4202,39 @@ def get_knotinfo(self, mirror_version=True, unique=True): sage: L2a1 = Link(b**2) sage: L2a1.get_knotinfo() - (Series of links L2a1, None) + (Series of links L2a1, ) sage: L2a1.get_knotinfo(unique=False) - [(, True), (, False)] + [(, ), + (, ), + (, ), + (, )] sage: KnotInfo.L5a1_0.inject() Defining L5a1_0 sage: l5 = Link(L5a1_0.braid()) sage: l5.get_knotinfo() - (Series of links L5a1, False) + (Series of links L5a1, ) sage: _[0].inject() Defining L5a1 sage: list(L5a1) [, ] sage: l5.get_knotinfo(unique=False) - [(, False), (, False)] + [(, ), + (, ), + (, ), + (, )] Clarifying the series around the Perko pair (:wikipedia:`Perko_pair`):: sage: for i in range(160, 166): # optional - database_knotinfo ....: K = Knots().from_table(10, i) ....: print('%s_%s' %(10, i), '--->', K.get_knotinfo()) - 10_160 ---> (, False) - 10_161 ---> (, True) - 10_162 ---> (, False) - 10_163 ---> (, False) - 10_164 ---> (, False) - 10_165 ---> (, True) + 10_160 ---> (, ) + 10_161 ---> (, ) + 10_162 ---> (, ) + 10_163 ---> (, ) + 10_164 ---> (, ) + 10_165 ---> (, ) Clarifying ther Perko series against `SnapPy `__:: @@ -4197,27 +4252,29 @@ def get_knotinfo(self, mirror_version=True, unique=True): ....: K = K10(i) ....: k = K.link(K.items.name, snappy=True) ....: print(k, '--->', k.sage_link().get_knotinfo()) - ---> (, False) - ---> (, True) - ---> (, False) - ---> (, False) - ---> (, False) - ---> (, False) + ---> (, ) + ---> (, ) + ---> (, ) + ---> (, ) + ---> (, ) + ---> (, ) sage: snappy.Link('10_166') sage: _.sage_link().get_knotinfo() - (, True) + (, ) Another pair of confusion (see the corresponding `Warning `__):: - sage: # optional - database_knotinfo snappy - sage: Ks10_86 = snappy.Link('10_86') - sage: Ks10_83 = snappy.Link('10_83') - sage: Ks10_86.sage_link().get_knotinfo() - (, True) - sage: Ks10_83.sage_link().get_knotinfo() - (, False) + sage: # optional - database_knotinfo snappy + sage: Ks10_86 = snappy.Link('10_86') + sage: Ks10_83 = snappy.Link('10_83') + sage: Ks10_86.sage_link().get_knotinfo(unique=False) + [(, ), + (, )] + sage: Ks10_83.sage_link().get_knotinfo(unique=False) + [(, ), + (, )] TESTS:: @@ -4225,15 +4282,30 @@ def get_knotinfo(self, mirror_version=True, unique=True): sage: L = KnotInfo.L10a171_1_1_0 sage: l = L.link(L.items.braid_notation) sage: l.get_knotinfo(unique=False) - [(, True), - (, True), - (, False), - (, False)] + [(, + ), + (, + ), + (, + ), + (, + ), + (, ), + (, ), + (, ), + (, )] + + sage: L = KnotInfo.L6a2_0 + sage: L1 = L.link() + sage: L2 = L.link(L.items.braid_notation) + sage: L1.get_knotinfo() == L2.get_knotinfo() + True """ # ToDo: extension to non prime links in which case an element of the monoid # over :class:`KnotInfo` should be returned non_unique_hint = '\nuse keyword argument `unique` to obtain more details' + from sage.knots.knotinfo import SymmetryMutant def answer(L): r""" @@ -4246,33 +4318,45 @@ def answer(L): chiral = True ach = L.is_amphicheiral() achp = L.is_amphicheiral(positive=True) - if ach is None and achp is None: + rev = L.is_reversible() + if ach is None and achp is None and rev is None: if unique: raise NotImplementedError('this link cannot be uniquely determined (unknown chirality)%s' % non_unique_hint) - elif L.is_amphicheiral() or L.is_amphicheiral(positive=True): + chiral = None + elif ach and achp: chiral = False - if not chiral: - mirrored = None - elif proved_m and not proved_s and L in lm: - mirrored = True - elif proved_s and not proved_m and L in l: - mirrored = False + sym_mut = None + if chiral is None: + sym_mut = SymmetryMutant.unknown + elif not chiral: + sym_mut = SymmetryMutant.itself else: - # nothing proved - if L in ls and L in lm: - # In case of a chiral link this means that the HOMFLY-PT - # polynomial does not distinguish mirror images (see the above - # example ``k11m``). - if unique: - raise NotImplementedError('mirror type of this link cannot be uniquely determined%s' % non_unique_hint) - mirrored = '?' - elif L in lm: - mirrored = True - else: - mirrored = False + for k in match_lists.keys(): + lk = match_lists[k] + if proves[k] and L in lk: + lk.remove(L) + sym_mut = k + break + + if not sym_mut: + for k in match_lists.keys(): + lk = match_lists[k] + if L in lk: + lk.remove(L) + sym_mut = k + break + + if not sym_mut: + # In case of a chiral link this means that the HOMFLY-PT + # polynomial does not distinguish mirror images (see the above + # example ``L10n36_0``). + sym_mut = SymmetryMutant.unknown - return L, mirrored + if unique and sym_mut is SymmetryMutant.unknown: + raise NotImplementedError('symmetry mutant of this link cannot be uniquely determined%s' % non_unique_hint) + + return L, sym_mut def answer_unori(S): r""" @@ -4282,21 +4366,18 @@ def answer_unori(S): if not mirror_version: return S - mirrored = [answer(L)[1] for L in S] - if all(mirrored): + sym_mut = [answer(L)[1] for L in S] + if all(i is SymmetryMutant.mirror_image for i in sym_mut): # all matching links are mirrored to self - return S, True - if any(i == '?' for i in mirrored): + return S, SymmetryMutant.mirror_image + if all(i is SymmetryMutant.itself for i in sym_mut): + # all matching links are self itself + return S, SymmetryMutant.itself + if any(i is SymmetryMutant.unknown for i in sym_mut): # unknown chirality for a matching link - return S, '?' - if any(i is None for i in mirrored): - # an amphicheiral link matches - return S, None - if not any(mirrored): - # no matching link is mirrored to self - return S, False - # finally both mirror types match - return S, None + return S, SymmetryMutant.unknown + # finally several mirror types match + return S, SymmetryMutant.mixed def answer_list(l): r""" @@ -4304,9 +4385,9 @@ def answer_list(l): argument ``unique``. """ if not unique: - return sorted([answer(L) for L in l]) + return sorted(set([answer(L) for L in l])) - if len(l) == 1: + if len(set(l)) == 1: return answer(l[0]) if not l[0].is_knot(): @@ -4316,23 +4397,28 @@ def answer_list(l): raise NotImplementedError('this link cannot be uniquely determined%s' % non_unique_hint) - self_m = self.mirror_image() - ls, proved_s = self._knotinfo_matching_list() - lm, proved_m = self_m._knotinfo_matching_list() - l = list(set(ls + lm)) + match_lists, proves = self._knotinfo_matching_dict() + + # first add only proved matching lists + proved = any(proves[k] for k in proves.keys()) + + l = [] + if proved and unique and self.is_knot(): + for k in match_lists.keys(): + if proves[k]: + l += match_lists[k] + else: + # for multi-component links there could regularily be more than one + # matching entry + for k in match_lists.keys(): + l += match_lists[k] if l and not unique: return answer_list(l) - if proved_s and proved_m: + if proved: return answer_list(l) - if proved_s: - return answer_list(ls) - - if proved_m: - return answer_list(lm) - # here we come if we cannot be sure about the found result uniq_txt = ('', '') @@ -4386,7 +4472,6 @@ def is_isotopic(self, other): False sage: # optional - database_knotinfo - sage: from sage.knots.knotinfo import KnotInfo sage: L = KnotInfo.L7a7_0_0 sage: L.series(oriented=True).inject() Defining L7a7 @@ -4401,6 +4486,37 @@ def is_isotopic(self, other): True sage: l.is_isotopic(L7a7(3).link()) False + + Using verbosity:: + + sage: set_verbose(1) + sage: l1.is_isotopic(l2) + verbose 1 (... link.py, is_isotopic) identified by KnotInfo (KnotInfo.K7_2, SymmetryMutant.mirror_image) + True + sage: l1.is_isotopic(l3) + verbose 1 (... link.py, is_isotopic) different Homfly-PT polynomials + False + sage: set_verbose(0) + + TESTS:: + + sage: L = KnotInfo.L6a2_0 + sage: L1 = L.link() + sage: L2 = L.link(L.items.braid_notation) + sage: set_verbose(1) + sage: L1.is_isotopic(L2) + verbose 1 (... link.py, is_isotopic) identified by KnotInfo uniquely (KnotInfo.L6a2_0, SymmetryMutant.itself) + True + + sage: # optional - database_knotinfo + sage: K = KnotInfo.K10_67 + sage: K1 = K.link() + sage: K1r = K.link().reverse() + sage: K1.is_isotopic(K1r) + verbose 1 (... link.py, is_isotopic) unidentified by KnotInfo ([], SymmetryMutant.itself != [], SymmetryMutant.reverse) + False + sage: set_verbose(0) + """ from sage.misc.verbose import verbose if not isinstance(other, Link): @@ -4412,6 +4528,11 @@ def is_isotopic(self, other): verbose('identified by representation') return True + if self.number_of_components() != other.number_of_components(): + # surely non isotopic + verbose('different number of components') + return False + if self.homfly_polynomial() != other.homfly_polynomial(): # surely non isotopic verbose('different Homfly-PT polynomials') @@ -4422,21 +4543,53 @@ def is_isotopic(self, other): verbose('identified via Markov moves') return True - try: - ki, m = self.get_knotinfo() - verbose('KnotInfo self: %s mirrored %s' % (ki, m)) - try: - if ki.is_unique(): - try: - kio = other.get_knotinfo() - verbose('KnotInfo other: %s mirrored %s' % kio) - return (ki, m) == kio - except NotImplementedError: - pass - except AttributeError: - # ki is a series - pass - except NotImplementedError: - pass + slists, sproves = self._knotinfo_matching_dict() + olists, oproves = other._knotinfo_matching_dict() + proved_s = None + proved_o = None + for k in slists.keys(): + sl = slists[k] + ol = olists[k] + sp = sproves[k] + op = oproves[k] + if sp and op: + if sorted(sl) == sorted(ol): + if len(sl) == 1: + verbose('identified by KnotInfo uniquely (%s, %s)' % (sl[0], k)) + return True + elif not self.is_knot(): + if len(set([l[0].series(oriented=True) for l in sl])) == 1: + # all matches are orientation mutants of each other + verbose('identified by KnotInfoSeries (%s, %s)' % (sl, k)) + return True + else: + verbose('KnotInfoSeries non-unique (%s, %s)' % (sl, k)) + else: + verbose('KnotInfo non-unique (%s, %s)' % (sl, k)) + else: + common = [l for l in sl if l in ol] + if common: + # better don't trust + verbose('KnotInfo common: %s' % common) + else: + verbose('unidentified by KnotInfo (%s != %s, %s)' % (sl, ol, k)) + return False + elif sp: + proved_s = (sl, k) + elif op: + proved_o = (ol, k) + if proved_s and proved_o: + sl, sk = proved_s + ol, ok = proved_o + verbose('unidentified by KnotInfo (%s, %s != %s, %s)' % (sl, sk, ol, ok)) + return False + + for k in slists.keys(): + # second loop without provings + sl = slists[k] + ol = olists[k] + if sorted(sl) == sorted(ol) and len(sl) == 1: + verbose('identified by KnotInfo (%s, %s)' % (sl[0], k)) + return True raise NotImplementedError('comparison not possible!') From e87fd8466d10c21d8f16bd08187c11b4dc116020 Mon Sep 17 00:00:00 2001 From: George Huebner Date: Mon, 18 Mar 2024 13:25:51 -0500 Subject: [PATCH 333/518] src/sage/rings/polynomial: fix cython codegen function pointers to `cpdef`d functions don't work because of dispatch --- src/sage/rings/polynomial/hilbert.pyx | 2 +- src/sage/rings/polynomial/polydict.pxd | 2 ++ src/sage/rings/polynomial/polydict.pyx | 16 +++++++++++++++- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/sage/rings/polynomial/hilbert.pyx b/src/sage/rings/polynomial/hilbert.pyx index 147db5691c3..9e61c4b129e 100644 --- a/src/sage/rings/polynomial/hilbert.pyx +++ b/src/sage/rings/polynomial/hilbert.pyx @@ -106,7 +106,7 @@ cdef inline list interred(list L) noexcept: # that appears later in L. if not L: return [] - L.sort(key=ETuple.unweighted_degree) + L.sort(key=ETuple._unweighted_degree) cdef size_t i cdef ETuple m cdef list result = [ PyList_GET_ITEM(L, 0)] diff --git a/src/sage/rings/polynomial/polydict.pxd b/src/sage/rings/polynomial/polydict.pxd index 432ae7d8c47..af56cd80364 100644 --- a/src/sage/rings/polynomial/polydict.pxd +++ b/src/sage/rings/polynomial/polydict.pxd @@ -14,6 +14,8 @@ cdef class ETuple: cdef ETuple _new(self) noexcept cdef int get_exp(self, size_t i) noexcept + # need a cdef version for function pointers + cdef int _unweighted_degree(self) except * cpdef int unweighted_degree(self) except * cpdef int weighted_degree(self, tuple w) except * cpdef int unweighted_quotient_degree(self, ETuple other) except * diff --git a/src/sage/rings/polynomial/polydict.pyx b/src/sage/rings/polynomial/polydict.pyx index af24e7ff245..0ecf35e452f 100644 --- a/src/sage/rings/polynomial/polydict.pyx +++ b/src/sage/rings/polynomial/polydict.pyx @@ -1845,7 +1845,7 @@ cdef class ETuple: # additional methods - cpdef int unweighted_degree(self) except *: + cdef int _unweighted_degree(self) except *: r""" Return the sum of entries. @@ -1863,6 +1863,20 @@ cdef class ETuple: degree += self._data[2 * i + 1] return degree + cpdef int unweighted_degree(self) except *: + r""" + Return the sum of entries. + + EXAMPLES:: + + sage: from sage.rings.polynomial.polydict import ETuple + sage: ETuple([1, 1, 0, 2, 0]).unweighted_degree() + 4 + sage: ETuple([-1, 1]).unweighted_degree() + 0 + """ + return self._unweighted_degree() + @cython.boundscheck(False) @cython.wraparound(False) cpdef int weighted_degree(self, tuple w) except *: From 13e0581eca293dc4cd58e13052072b781217fc54 Mon Sep 17 00:00:00 2001 From: George Huebner Date: Tue, 19 Mar 2024 10:53:07 -0500 Subject: [PATCH 334/518] src/sage/modular/arithgroup/farey.cpp: use std::bind std::bind2nd removed in c++17 --- src/sage/modular/arithgroup/farey.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sage/modular/arithgroup/farey.cpp b/src/sage/modular/arithgroup/farey.cpp index 209391676de..d224b2a8d13 100644 --- a/src/sage/modular/arithgroup/farey.cpp +++ b/src/sage/modular/arithgroup/farey.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -737,7 +738,7 @@ size_t FareySymbol::nu3() const { size_t FareySymbol::rank_pi() const { if( index() == 2 ) return 1; return count_if(pairing.begin(), pairing.end(), - bind2nd(greater(), 0))/2; + bind(greater(), placeholders::_1, 0))/2; } size_t FareySymbol::number_of_cusps() const { From f78dafb17bd4b33d3c1500eff90e3c9a45ff0433 Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Sat, 23 Mar 2024 19:01:48 +0100 Subject: [PATCH 335/518] Implemented reviewer feedback --- src/sage/matrix/matrix_integer_dense.pyx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/sage/matrix/matrix_integer_dense.pyx b/src/sage/matrix/matrix_integer_dense.pyx index d747aa360a6..f3b107efe2a 100644 --- a/src/sage/matrix/matrix_integer_dense.pyx +++ b/src/sage/matrix/matrix_integer_dense.pyx @@ -3158,7 +3158,8 @@ cdef class Matrix_integer_dense(Matrix_dense): Check that :issue:`37236` is fixed: sage: M = matrix(ZZ, 2, 2, [-1,1,1,1]) - sage: L = M.LLL(algorithm="NTL:LLL"); M.det() == L.det() + sage: L = M.LLL(algorithm="NTL:LLL") + sage: M.det() == L.det() True .. NOTE:: @@ -3235,9 +3236,9 @@ cdef class Matrix_integer_dense(Matrix_dense): if algorithm == "NTL:LLL": if transformation: - r, _, UNTL = A.LLL(a,b, verbose=verb, return_U=True) + r, _, UNTL = A.LLL(a, b, verbose=verb, return_U=True) else: - r, _ = A.LLL(a,b, verbose=verb) + r, _ = A.LLL(a, b, verbose=verb) elif algorithm == "NTL:LLL_FP": if use_givens: r = A.G_LLL_FP(delta, verbose=verb, return_U=transformation) From 4fa6f099849470e08e70de0c0e6005db108faea3 Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Sat, 23 Mar 2024 19:33:35 +0100 Subject: [PATCH 336/518] Added missing colons --- src/sage/matrix/matrix_integer_dense.pyx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/matrix/matrix_integer_dense.pyx b/src/sage/matrix/matrix_integer_dense.pyx index f3b107efe2a..6a695689080 100644 --- a/src/sage/matrix/matrix_integer_dense.pyx +++ b/src/sage/matrix/matrix_integer_dense.pyx @@ -3144,7 +3144,7 @@ cdef class Matrix_integer_dense(Matrix_dense): sage: A = random_matrix(ZZ, 0, 0) sage: R, U = A.LLL(transformation=True) - Test rank caching: + Test rank caching:: sage: M = matrix(4,3,[1,2,3,2,4,6,7,0,1,-1,-2,-3]) sage: R = M.LLL(algorithm="NTL:LLL") @@ -3155,7 +3155,7 @@ cdef class Matrix_integer_dense(Matrix_dense): sage: M._cache {'rank': 2} - Check that :issue:`37236` is fixed: + Check that :issue:`37236` is fixed:: sage: M = matrix(ZZ, 2, 2, [-1,1,1,1]) sage: L = M.LLL(algorithm="NTL:LLL") From 23c45b1d61b2aa3e1fc027542d65ce6ecb5e04ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Sun, 24 Mar 2024 08:48:06 +0100 Subject: [PATCH 337/518] fix ruff codes UP012 and UP023 --- src/sage/features/csdp.py | 2 +- src/sage/graphs/isgci.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/features/csdp.py b/src/sage/features/csdp.py index e8aa55e50ea..8f2e18dc182 100644 --- a/src/sage/features/csdp.py +++ b/src/sage/features/csdp.py @@ -59,7 +59,7 @@ def is_functional(self): tf_name = tmp_filename() with open(tf_name, 'wb') as tf: - tf.write("2\n1\n1 1".encode()) + tf.write(b"2\n1\n1 1") with open(os.devnull, 'wb') as devnull: command = ['theta', tf_name] try: diff --git a/src/sage/graphs/isgci.py b/src/sage/graphs/isgci.py index 440135956c3..2315ea4679d 100644 --- a/src/sage/graphs/isgci.py +++ b/src/sage/graphs/isgci.py @@ -818,7 +818,7 @@ def _parse_db(self): sage: graph_classes._parse_db() """ - import xml.etree.cElementTree as ET + import xml.etree.ElementTree as ET from sage.graphs.graph import Graph data_dir = os.path.dirname(DatabaseGraphs().absolute_filename()) From d04c3e05605667d4d43964f9983775f81b411b27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Sun, 24 Mar 2024 09:32:52 +0100 Subject: [PATCH 338/518] fix pep8 E302 in libs and modules (pyx files) --- .../projective_ds_helper.pyx | 3 ++- src/sage/libs/arb/arith.pyx | 3 ++- src/sage/libs/braiding.pyx | 15 +++++++++++---- src/sage/libs/coxeter3/coxeter.pyx | 2 ++ src/sage/libs/eclib/mwrank.pyx | 1 + src/sage/libs/giac/giac.pyx | 14 ++++++++------ src/sage/libs/glpk/error.pyx | 3 ++- src/sage/libs/lcalc/lcalc_Lfunction.pyx | 1 + src/sage/libs/libecm.pyx | 1 + src/sage/libs/mpmath/ext_impl.pyx | 2 ++ src/sage/libs/mpmath/utils.pyx | 3 +++ src/sage/libs/ntl/ntl_GF2.pyx | 1 + src/sage/libs/ntl/ntl_GF2EContext.pyx | 1 + src/sage/libs/ntl/ntl_ZZ.pyx | 6 +++++- src/sage/libs/ntl/ntl_ZZ_p.pyx | 1 + src/sage/libs/pari/convert_sage_matrix.pyx | 1 + src/sage/libs/singular/function.pyx | 17 +++++++++-------- src/sage/libs/singular/groebner_strategy.pyx | 2 ++ src/sage/libs/singular/singular.pyx | 1 + src/sage/modules/free_module_element.pyx | 5 +++++ src/sage/modules/module.pyx | 3 ++- .../modules/vector_complex_double_dense.pyx | 1 + src/sage/modules/vector_integer_dense.pyx | 2 ++ src/sage/modules/vector_mod2_dense.pyx | 3 ++- src/sage/modules/vector_modn_dense.pyx | 2 ++ src/sage/modules/vector_real_double_dense.pyx | 1 + 26 files changed, 71 insertions(+), 24 deletions(-) diff --git a/src/sage/dynamics/arithmetic_dynamics/projective_ds_helper.pyx b/src/sage/dynamics/arithmetic_dynamics/projective_ds_helper.pyx index 9b0009629de..145c0533bb2 100644 --- a/src/sage/dynamics/arithmetic_dynamics/projective_ds_helper.pyx +++ b/src/sage/dynamics/arithmetic_dynamics/projective_ds_helper.pyx @@ -151,7 +151,8 @@ cpdef _fast_possible_periods(self, return_points=False) noexcept: if not return_points: return sorted(periods) else: - return(points_periods) + return points_periods + def _enum_points(int prime, int dimension): """ diff --git a/src/sage/libs/arb/arith.pyx b/src/sage/libs/arb/arith.pyx index 69281d916ce..347e3ccc7f7 100644 --- a/src/sage/libs/arb/arith.pyx +++ b/src/sage/libs/arb/arith.pyx @@ -9,7 +9,7 @@ Arithmetic functions using the arb library # 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.libs.flint.types cimport ulong @@ -21,6 +21,7 @@ from sage.rings.polynomial.polynomial_integer_dense_flint cimport Polynomial_int from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.rings.integer_ring import ZZ + def bernoulli(n): """ Return the ``n``-th Bernoulli number using ``arb``. diff --git a/src/sage/libs/braiding.pyx b/src/sage/libs/braiding.pyx index e8f37dad588..5527e9bee8f 100644 --- a/src/sage/libs/braiding.pyx +++ b/src/sage/libs/braiding.pyx @@ -76,6 +76,7 @@ def conjugatingbraid(braid1, braid2): sig_off() return rop + def leftnormalform(braid): r""" Return the left normal form of a braid. @@ -106,6 +107,7 @@ def leftnormalform(braid): sig_off() return rop + def rightnormalform(braid): r""" Return the right normal form of a braid. @@ -136,6 +138,7 @@ def rightnormalform(braid): sig_off() return rop + def greatestcommondivisor(braid1, braid2): r""" Return the greatest common divisor of two braids. @@ -167,6 +170,7 @@ def greatestcommondivisor(braid1, braid2): sig_off() return rop + def leastcommonmultiple(braid1, braid2): r""" Return the least common multiple of two braids. @@ -188,7 +192,6 @@ def leastcommonmultiple(braid1, braid2): sage: b2 = B([2, 2, 2]) sage: leastcommonmultiple(b1, b2) [[1], [1], [1]] - """ nstrands = max(braid1.parent().strands(), braid2.parent().strands()) l1 = braid1.Tietze() @@ -198,6 +201,7 @@ def leastcommonmultiple(braid1, braid2): sig_off() return rop + def centralizer(braid): r""" Return a list of generators of the centralizer of a braid. @@ -235,6 +239,7 @@ def centralizer(braid): sig_off() return rop + def supersummitset(braid): r""" Return a list with the super-summit-set of a braid. @@ -263,6 +268,7 @@ def supersummitset(braid): sig_off() return rop + def ultrasummitset(braid): r""" Return a list with the orbits forming the ultra-summit-set of the braid. @@ -283,7 +289,6 @@ def ultrasummitset(braid): sage: b = B([1,2,-1]) sage: ultrasummitset(b) [[[[0], [2]]], [[[0], [1]]]] - """ nstrands = braid.parent().strands() l = braid.Tietze() @@ -326,11 +331,12 @@ def thurston_type(braid): sig_off() if i == 1: return 'periodic' - elif i==2: + if i==2: return 'reducible' - elif i==3: + if i==3: return 'pseudo-anosov' + def rigidity(braid): r""" Return the rigidity of the braid. @@ -359,6 +365,7 @@ def rigidity(braid): sig_off() return i + def sliding_circuits(braid): r""" Return the set of sliding circuits of the braid. diff --git a/src/sage/libs/coxeter3/coxeter.pyx b/src/sage/libs/coxeter3/coxeter.pyx index 0ef308c7aee..9cb3feb8b3f 100644 --- a/src/sage/libs/coxeter3/coxeter.pyx +++ b/src/sage/libs/coxeter3/coxeter.pyx @@ -1213,6 +1213,8 @@ class CoxGroupIterator(): CoxGroup_cache = {} + + def get_CoxGroup(cartan_type): """ TESTS:: diff --git a/src/sage/libs/eclib/mwrank.pyx b/src/sage/libs/eclib/mwrank.pyx index 49b20a33c0e..b91dccf74b9 100644 --- a/src/sage/libs/eclib/mwrank.pyx +++ b/src/sage/libs/eclib/mwrank.pyx @@ -79,6 +79,7 @@ cdef object string_sigoff(char* s) noexcept: # set the default bit precision mwrank_set_precision(150) + def get_precision(): """ Return the working floating point bit precision of mwrank, which is diff --git a/src/sage/libs/giac/giac.pyx b/src/sage/libs/giac/giac.pyx index 3096e00f8af..321c9603ab5 100644 --- a/src/sage/libs/giac/giac.pyx +++ b/src/sage/libs/giac/giac.pyx @@ -198,6 +198,7 @@ Pygen('I:=sqrt(-1)').eval() # WTF? # self._giac_() does. SRexpressiontoGiac = InterfaceInit(giac) + ####################################################### # The wrapper to eval with giac ####################################################### @@ -1978,6 +1979,7 @@ class GiacFunction(Pygen): args = (Pygen(args[0]).eval(),) return Pygen.__call__(self, *args) + class GiacFunctionNoEV(Pygen): # a class to allow to write the __doc__ attribute. """ @@ -2007,19 +2009,19 @@ for i in mostkeywords: if i in NoEvArgsFunc: # do not eval args before calling this function. Ex purge #tmp=Pygen(i) - tmp=GiacFunctionNoEV(i) + tmp = GiacFunctionNoEV(i) else: - tmp=GiacFunction(i) + tmp = GiacFunction(i) # in the sage version we remove: globals()[i]=tmp - GiacMethods[i]=tmp + GiacMethods[i] = tmp # We put the giac names that should not be exported to Python in moremethods. for i in moremethods: - tmp=GiacFunction(i) - GiacMethods[i]=tmp + tmp = GiacFunction(i) + GiacMethods[i] = tmp for i in mostkeywords+moremethods: - GiacMethods[i].__doc__=eval("Pygen."+i+".__doc__") + GiacMethods[i].__doc__ = eval("Pygen."+i+".__doc__") # To avoid conflicts we export only these few ones. Most giac keywords will be # avaible through: libgiac.keywordname diff --git a/src/sage/libs/glpk/error.pyx b/src/sage/libs/glpk/error.pyx index b3db031043a..b8dce0d3085 100644 --- a/src/sage/libs/glpk/error.pyx +++ b/src/sage/libs/glpk/error.pyx @@ -9,7 +9,7 @@ Error handler for the GLPK library # 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 cysignals.signals cimport sig_error @@ -19,6 +19,7 @@ from cpython.exc cimport PyErr_SetObject from sage.cpython.string cimport char_to_str from sage.numerical.mip import MIPSolverException + class GLPKError(MIPSolverException): """ A low-level error that is raised by ``sage_glpk_term_hook``. diff --git a/src/sage/libs/lcalc/lcalc_Lfunction.pyx b/src/sage/libs/lcalc/lcalc_Lfunction.pyx index 4950ed1d97f..2bb8fb200ea 100644 --- a/src/sage/libs/lcalc/lcalc_Lfunction.pyx +++ b/src/sage/libs/lcalc/lcalc_Lfunction.pyx @@ -899,6 +899,7 @@ cdef class Lfunction_Zeta(Lfunction): """ del_c_Lfunction_Zeta((self.thisptr)) + ############################################################################## # Tools ############################################################################## diff --git a/src/sage/libs/libecm.pyx b/src/sage/libs/libecm.pyx index 886a9b816e4..90b98bcaa1e 100644 --- a/src/sage/libs/libecm.pyx +++ b/src/sage/libs/libecm.pyx @@ -61,6 +61,7 @@ cdef extern from "ecm.h": void ecm_clear (ecm_params) int ECM_NO_FACTOR_FOUND + def ecmfactor(number, double B1, verbose=False, sigma=0): """ Try to find a factor of a positive integer using ECM (Elliptic Curve Method). diff --git a/src/sage/libs/mpmath/ext_impl.pyx b/src/sage/libs/mpmath/ext_impl.pyx index 1827b85b948..7860fbd3227 100644 --- a/src/sage/libs/mpmath/ext_impl.pyx +++ b/src/sage/libs/mpmath/ext_impl.pyx @@ -1453,6 +1453,7 @@ def exp_fixed(Integer x, int prec, ln2=None): mpz_clear(n) return v + def cos_sin_fixed(Integer x, int prec, pi2=None): """ Returns fixed-point approximations of cos(x), sin(x) where @@ -1506,6 +1507,7 @@ cdef mpz_log_int(mpz_t v, mpz_t n, int prec) noexcept: mpfr_get_z(v, f, MPFR_RNDN) mpfr_clear(f) + def log_int_fixed(n, long prec, ln2=None): """ Returns fixed-point approximation of log(n). diff --git a/src/sage/libs/mpmath/utils.pyx b/src/sage/libs/mpmath/utils.pyx index 15286fa830b..4cda927d798 100644 --- a/src/sage/libs/mpmath/utils.pyx +++ b/src/sage/libs/mpmath/utils.pyx @@ -211,6 +211,7 @@ cdef mpfr_to_mpfval(mpfr_t value) noexcept: bc = mpz_sizeinbase(man.value, 2) return (sign, man, int(exp), bc) + def mpmath_to_sage(x, prec): """ Convert any mpmath number (mpf or mpc) to a Sage RealNumber or @@ -280,6 +281,7 @@ def mpmath_to_sage(x, prec): else: raise TypeError("cannot convert %r to Sage", x) + def sage_to_mpmath(x, prec): """ Convert any Sage number that can be coerced into a RealNumber @@ -340,6 +342,7 @@ def sage_to_mpmath(x, prec): return dict([(k, sage_to_mpmath(v, prec)) for (k, v) in x.items()]) return x + def call(func, *args, **kwargs): """ Call an mpmath function with Sage objects as inputs and diff --git a/src/sage/libs/ntl/ntl_GF2.pyx b/src/sage/libs/ntl/ntl_GF2.pyx index 7bbdaa09d05..9d4d8a0b5e4 100644 --- a/src/sage/libs/ntl/ntl_GF2.pyx +++ b/src/sage/libs/ntl/ntl_GF2.pyx @@ -251,6 +251,7 @@ cdef class ntl_GF2(): cdef long l = GF2_conv_to_long(self.x) return int(l) + def unpickle_class_value(cls, x): """ Here for unpickling. diff --git a/src/sage/libs/ntl/ntl_GF2EContext.pyx b/src/sage/libs/ntl/ntl_GF2EContext.pyx index f00bda404df..ad3b08aa9dc 100644 --- a/src/sage/libs/ntl/ntl_GF2EContext.pyx +++ b/src/sage/libs/ntl/ntl_GF2EContext.pyx @@ -108,6 +108,7 @@ cdef class ntl_GF2EContext_class(): cdef void restore_c(self) noexcept: self.x.restore() + def ntl_GF2EContext( v ): """ Create a new GF2EContext. diff --git a/src/sage/libs/ntl/ntl_ZZ.pyx b/src/sage/libs/ntl/ntl_ZZ.pyx index 37acfef79a8..62dddf369c6 100644 --- a/src/sage/libs/ntl/ntl_ZZ.pyx +++ b/src/sage/libs/ntl/ntl_ZZ.pyx @@ -420,6 +420,7 @@ def unpickle_class_value(cls, x): """ return cls(x) + def unpickle_class_args(cls, x): """ Here for unpickling. @@ -433,6 +434,7 @@ def unpickle_class_args(cls, x): """ return cls(*x) + # Random-number generation def ntl_setSeed(x=None): r""" @@ -509,6 +511,7 @@ def randomBnd(q): sig_off() return ans + def randomBits(long n): r""" Return a pseudo-random number in the range `[0, 2^n)`. @@ -523,7 +526,8 @@ def randomBits(long n): True AUTHOR: - -- Didier Deshommes + + - Didier Deshommes """ current_randstate().set_seed_ntl(False) diff --git a/src/sage/libs/ntl/ntl_ZZ_p.pyx b/src/sage/libs/ntl/ntl_ZZ_p.pyx index cca767303f7..f552a6bfb47 100644 --- a/src/sage/libs/ntl/ntl_ZZ_p.pyx +++ b/src/sage/libs/ntl/ntl_ZZ_p.pyx @@ -44,6 +44,7 @@ from sage.misc.randstate cimport current_randstate ZZ_sage = IntegerRing() + def ntl_ZZ_p_random_element(v): """ Return a random number modulo p. diff --git a/src/sage/libs/pari/convert_sage_matrix.pyx b/src/sage/libs/pari/convert_sage_matrix.pyx index 9900e26dd64..1efcea03e72 100644 --- a/src/sage/libs/pari/convert_sage_matrix.pyx +++ b/src/sage/libs/pari/convert_sage_matrix.pyx @@ -13,6 +13,7 @@ from sage.matrix.args cimport (MatrixArgs, MA_ENTRIES_SEQ_SEQ, from .convert_sage cimport gen_to_sage + def gen_to_sage_matrix(Gen z, locals=None): cdef GEN g = z.g nc = lg(g) - 1 diff --git a/src/sage/libs/singular/function.pyx b/src/sage/libs/singular/function.pyx index 834ffd163d8..e01d8486b0c 100644 --- a/src/sage/libs/singular/function.pyx +++ b/src/sage/libs/singular/function.pyx @@ -364,6 +364,7 @@ cdef free_leftv(leftv *args, ring *r = NULL) noexcept: args.CleanUp(r) omFreeBin(args, sleftv_bin) + # ===================================== # = Singular/Plural Abstraction Layer = # ===================================== @@ -401,9 +402,10 @@ cdef new_sage_polynomial(ring, poly *p) noexcept: return new_NCP(ring, p) raise ValueError("not a singular or plural ring") + def is_singular_poly_wrapper(p): """ - Checks if p is some data type corresponding to some singular ``poly``. + Check if ``p`` is some data type corresponding to some singular ``poly``. EXAMPLES:: @@ -412,9 +414,9 @@ def is_singular_poly_wrapper(p): sage: H. = A.g_algebra({z*x:x*z+2*x, z*y:y*z-2*y}) sage: is_singular_poly_wrapper(x+y) True - """ - return isinstance(p, MPolynomial_libsingular) or isinstance(p, NCPolynomial_plural) + return isinstance(p, (MPolynomial_libsingular, NCPolynomial_plural)) + def all_singular_poly_wrapper(s): """ @@ -430,10 +432,8 @@ def all_singular_poly_wrapper(s): sage: all_singular_poly_wrapper([x+1, y, 1]) False """ - for p in s: - if not is_singular_poly_wrapper(p): - return False - return True + return all(is_singular_poly_wrapper(p) for p in s) + cdef poly* access_singular_poly(p) except -1: """ @@ -1772,12 +1772,12 @@ def singular_function(name): sage: ring(l) """ - try: return SingularKernelFunction(name) except NameError: return SingularLibraryFunction(name) + def lib(name): """ Load the Singular library ``name``. @@ -1812,6 +1812,7 @@ def lib(name): if failure: raise NameError("Singular library {!r} not found".format(name)) + def list_of_functions(packages=False): """ Return a list of all function names currently available. diff --git a/src/sage/libs/singular/groebner_strategy.pyx b/src/sage/libs/singular/groebner_strategy.pyx index ceda88c6362..9ad31df1bdb 100644 --- a/src/sage/libs/singular/groebner_strategy.pyx +++ b/src/sage/libs/singular/groebner_strategy.pyx @@ -542,6 +542,7 @@ cdef class NCGroebnerStrategy(SageObject): _p = redtailBba(_p, max_ind, self._strat) return new_NCP(self._parent, _p) + def unpickle_NCGroebnerStrategy0(I): """ EXAMPLES:: @@ -556,6 +557,7 @@ def unpickle_NCGroebnerStrategy0(I): """ return NCGroebnerStrategy(I) + def unpickle_GroebnerStrategy0(I): """ EXAMPLES:: diff --git a/src/sage/libs/singular/singular.pyx b/src/sage/libs/singular/singular.pyx index 051e1dea01c..a331b43c876 100644 --- a/src/sage/libs/singular/singular.pyx +++ b/src/sage/libs/singular/singular.pyx @@ -1814,6 +1814,7 @@ cdef void libsingular_error_callback(const_char_ptr s) noexcept: _s = char_to_str(s) error_messages.append(_s) + def get_resource(id): """ Return a Singular "resource". diff --git a/src/sage/modules/free_module_element.pyx b/src/sage/modules/free_module_element.pyx index ff43d239493..134d821dbde 100644 --- a/src/sage/modules/free_module_element.pyx +++ b/src/sage/modules/free_module_element.pyx @@ -145,6 +145,7 @@ def is_FreeModuleElement(x): """ return isinstance(x, FreeModuleElement) + def vector(arg0, arg1=None, arg2=None, sparse=None, immutable=False): r""" Return a vector or free module element with specified entries. @@ -595,6 +596,7 @@ def vector(arg0, arg1=None, arg2=None, sparse=None, immutable=False): free_module_element = vector + def prepare(v, R, degree=None): r""" Converts an object describing elements of a vector into a list of entries in a common ring. @@ -690,6 +692,7 @@ def prepare(v, R, degree=None): raise TypeError("unable to find a common ring for all elements") return v, ring + def zero_vector(arg0, arg1=None): r""" Returns a vector or free module element with a specified number of zeros. @@ -4148,6 +4151,7 @@ cdef class FreeModuleElement(Vector): # abstract base class nintegrate=nintegral + ############################################# # Generic dense element ############################################# @@ -4610,6 +4614,7 @@ cdef class FreeModuleElement_generic_dense(FreeModuleElement): from sage.symbolic.callable import CallableSymbolicExpressionRing return vector(CallableSymbolicExpressionRing(args), self.list()) + ############################################# # Generic sparse element ############################################# diff --git a/src/sage/modules/module.pyx b/src/sage/modules/module.pyx index 8cf23ad3e76..9962bcba5d9 100644 --- a/src/sage/modules/module.pyx +++ b/src/sage/modules/module.pyx @@ -270,6 +270,7 @@ cdef class Module(Parent): from sage.categories.homset import End return End(self) + def is_Module(x): """ Return ``True`` if ``x`` is a module, ``False`` otherwise. @@ -286,10 +287,10 @@ def is_Module(x): True sage: is_Module(10) False - """ return isinstance(x, Module) + def is_VectorSpace(x): """ Return ``True`` if ``x`` is a vector space, ``False`` otherwise. diff --git a/src/sage/modules/vector_complex_double_dense.pyx b/src/sage/modules/vector_complex_double_dense.pyx index d9715fbdc82..59b585f912c 100644 --- a/src/sage/modules/vector_complex_double_dense.pyx +++ b/src/sage/modules/vector_complex_double_dense.pyx @@ -97,6 +97,7 @@ def unpickle_v0(parent, entries, degree): """ return unpickle_v1(parent, entries, degree) + def unpickle_v1(parent, entries, degree, is_mutable=None): """ Create a complex double vector with the given parent, entries, diff --git a/src/sage/modules/vector_integer_dense.pyx b/src/sage/modules/vector_integer_dense.pyx index 52e0bbd4064..37c6ff21155 100644 --- a/src/sage/modules/vector_integer_dense.pyx +++ b/src/sage/modules/vector_integer_dense.pyx @@ -331,6 +331,7 @@ cdef class Vector_integer_dense(free_module_element.FreeModuleElement): from sage.interfaces.singular import SingularElement return SingularElement(singular, 'foobar', name, True) + def unpickle_v0(parent, entries, degree): # If you think you want to change this function, don't. # Instead make a new version with a name like @@ -346,6 +347,7 @@ def unpickle_v0(parent, entries, degree): mpz_set(v._entries[i], z.value) return v + def unpickle_v1(parent, entries, degree, is_mutable): cdef Vector_integer_dense v v = Vector_integer_dense.__new__(Vector_integer_dense) diff --git a/src/sage/modules/vector_mod2_dense.pyx b/src/sage/modules/vector_mod2_dense.pyx index 94f63f35c56..05b2bf04318 100644 --- a/src/sage/modules/vector_mod2_dense.pyx +++ b/src/sage/modules/vector_mod2_dense.pyx @@ -485,6 +485,7 @@ cdef class Vector_mod2_dense(free_module_element.FreeModuleElement): v[i] = switch[mzd_read_bit(self._entries, 0, i)] return v + def unpickle_v0(parent, entries, degree, is_immutable): """ EXAMPLES:: @@ -500,7 +501,7 @@ def unpickle_v0(parent, entries, degree, is_immutable): v._init(degree, parent) cdef int xi - for i from 0 <= i < degree: + for i in range(degree): if isinstance(entries[i], (IntegerMod_int, int, Integer)): xi = entries[i] mzd_write_bit(v._entries, 0, i, xi%2) diff --git a/src/sage/modules/vector_modn_dense.pyx b/src/sage/modules/vector_modn_dense.pyx index c2155a68990..f03899546f1 100644 --- a/src/sage/modules/vector_modn_dense.pyx +++ b/src/sage/modules/vector_modn_dense.pyx @@ -356,6 +356,7 @@ cdef class Vector_modn_dense(free_module_element.FreeModuleElement): z._entries[i] = 0 return z + def unpickle_v0(parent, entries, degree, p): # If you think you want to change this function, don't. # Instead make a new version with a name like @@ -368,6 +369,7 @@ def unpickle_v0(parent, entries, degree, p): v._entries[i] = entries[i] return v + def unpickle_v1(parent, entries, degree, p, is_mutable): cdef Vector_modn_dense v v = Vector_modn_dense.__new__(Vector_modn_dense) diff --git a/src/sage/modules/vector_real_double_dense.pyx b/src/sage/modules/vector_real_double_dense.pyx index e05895f0011..a5d4c236722 100644 --- a/src/sage/modules/vector_real_double_dense.pyx +++ b/src/sage/modules/vector_real_double_dense.pyx @@ -107,6 +107,7 @@ def unpickle_v0(parent, entries, degree): """ return unpickle_v1(parent, entries, degree) + def unpickle_v1(parent, entries, degree, is_mutable=None): """ Create a real double vector with the given parent, entries, From 210c5d8389417e1fc4ce08cc479ae3c5736305a2 Mon Sep 17 00:00:00 2001 From: Marc Mezzarobba Date: Sun, 24 Mar 2024 10:07:47 +0100 Subject: [PATCH 339/518] some cross-references btw doc of RR, RBF, ... (mainly intended to make RBF and CBF, which are the most powerful implementations for most purposes, more discoverable) --- src/sage/rings/complex_arb.pyx | 7 +++++++ src/sage/rings/complex_interval_field.py | 6 ++++++ src/sage/rings/complex_mpfr.pyx | 13 +++++++++++++ src/sage/rings/real_arb.pyx | 8 ++++++++ src/sage/rings/real_mpfi.pyx | 7 +++++++ src/sage/rings/real_mpfr.pyx | 14 ++++++++++++-- 6 files changed, 53 insertions(+), 2 deletions(-) diff --git a/src/sage/rings/complex_arb.pyx b/src/sage/rings/complex_arb.pyx index 46c01da4eb6..88371b30ac6 100644 --- a/src/sage/rings/complex_arb.pyx +++ b/src/sage/rings/complex_arb.pyx @@ -336,6 +336,13 @@ class ComplexBallField(UniqueRepresentation, sage.rings.abc.ComplexBallField): sage: loads(dumps(ComplexBallField(60))) is ComplexBallField(60) True + + .. SEEALSO:: + + - :mod:`sage.rings.complex_arb` + - :mod:`sage.rings.complex_mpfr` + - :mod:`sage.rings.complex_mpfi` + - :mod:`sage.rings.real_arb` """ Element = ComplexBall diff --git a/src/sage/rings/complex_interval_field.py b/src/sage/rings/complex_interval_field.py index 09cc326f361..78c49a328c0 100644 --- a/src/sage/rings/complex_interval_field.py +++ b/src/sage/rings/complex_interval_field.py @@ -171,6 +171,12 @@ class ComplexIntervalField_class(sage.rings.abc.ComplexIntervalField): sage: CIF.is_finite() False + + .. SEEALSO:: + + - :mod:`sage.rings.real_mpfi` + - :class:`sage.rings.complex_arb.ComplexBallField` (alternative + implementation of complex intervals, with more features) """ Element = complex_interval.ComplexIntervalFieldElement diff --git a/src/sage/rings/complex_mpfr.pyx b/src/sage/rings/complex_mpfr.pyx index 872ca8c60a3..38016480b97 100644 --- a/src/sage/rings/complex_mpfr.pyx +++ b/src/sage/rings/complex_mpfr.pyx @@ -176,6 +176,12 @@ def ComplexField(prec=53, names=None): sage: i = ComplexField(200).gen() sage: i^2 -1.0000000000000000000000000000000000000000000000000000000000 + + .. SEEALSO:: + + - :class:`~sage.rings.complex_mpfr.ComplexField_class` + - :class:`~sage.rings.real_arb.ComplexBallField` (complex numbers with + rigorous error bounds) """ global cache if prec in cache: @@ -261,6 +267,13 @@ class ComplexField_class(sage.rings.abc.ComplexField): False sage: CC == 1.1 False + + .. SEEALSO:: + + - :func:`~sage.rings.complex_mpfr.ComplexField` (constructor) + - :class:`~sage.rings.real_arb.ComplexBallField` (complex numbers with + rigorous error bounds) + - :mod:`~sage.rings.real_mpfr` """ def __init__(self, prec=53): """ diff --git a/src/sage/rings/real_arb.pyx b/src/sage/rings/real_arb.pyx index e1bc0c08f16..f13568a0876 100644 --- a/src/sage/rings/real_arb.pyx +++ b/src/sage/rings/real_arb.pyx @@ -368,6 +368,14 @@ class RealBallField(UniqueRepresentation, sage.rings.abc.RealBallField): sage: loads(dumps(RealBallField(60))) is RealBallField(60) True + + .. SEEALSO:: + + - :mod:`sage.rings.real_arb` + - :mod:`sage.rings.real_mpfr` + - :mod:`sage.rings.real_mpfi` (real intervals represented by their + endpoints) + - :mod:`sage.rings.complex_arb` """ Element = RealBall diff --git a/src/sage/rings/real_mpfi.pyx b/src/sage/rings/real_mpfi.pyx index 69143c243f3..675d17ccc85 100644 --- a/src/sage/rings/real_mpfi.pyx +++ b/src/sage/rings/real_mpfi.pyx @@ -511,6 +511,13 @@ cdef class RealIntervalField_class(sage.rings.abc.RealIntervalField): sage: RealIntervalField(10).is_finite() False + + .. SEEALSO:: + + - :mod:`sage.rings.real_mpfi` + - :mod:`sage.rings.complex_interval_field` + - :class:`sage.rings.real_arb.RealBallField` (alternative + implementation of real intervals, with more features) """ Element = RealIntervalFieldElement diff --git a/src/sage/rings/real_mpfr.pyx b/src/sage/rings/real_mpfr.pyx index 20ba8dfa284..642552100d3 100644 --- a/src/sage/rings/real_mpfr.pyx +++ b/src/sage/rings/real_mpfr.pyx @@ -427,6 +427,12 @@ cpdef RealField(mpfr_prec_t prec=53, int sci_not=0, rnd=MPFR_RNDN) noexcept: computations with double-precision machine floating-point numbers (double type in C), except the default exponent range is much wider and subnormal numbers are not implemented.' + + .. SEEALSO:: + + - :mod:`sage.rings.real_mpfr` + - :class:`sage.rings.real_arb.RealBallField` (real numbers with rigorous + error bounds) """ # We allow specifying the rounding mode as string or integer. # But we pass an integer to __init__ @@ -457,8 +463,12 @@ cdef class RealField_class(sage.rings.abc.RealField): numbers. This is due to the rounding errors inherent to finite precision calculations. - See the documentation for the module :mod:`sage.rings.real_mpfr` for more - details. + .. SEEALSO:: + + - :mod:`sage.rings.real_mpfr` + - :class:`sage.rings.real_arb.RealBallField` (real numbers with rigorous + error bounds) + - :mod:`sage.rings.complex_mpfr` """ def __init__(self, mpfr_prec_t prec=53, int sci_not=0, long rnd=MPFR_RNDN): """ From 8edbd05aef84b1894b57dc35bb74307d46a7bc2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Sun, 24 Mar 2024 20:40:42 +0100 Subject: [PATCH 340/518] fix suggested details --- src/sage/libs/arb/arith.pyx | 4 ++-- src/sage/libs/braiding.pyx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sage/libs/arb/arith.pyx b/src/sage/libs/arb/arith.pyx index 347e3ccc7f7..8bc843aa2a3 100644 --- a/src/sage/libs/arb/arith.pyx +++ b/src/sage/libs/arb/arith.pyx @@ -2,7 +2,7 @@ Arithmetic functions using the arb library """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2016 Jeroen Demeyer # # This program is free software: you can redistribute it and/or modify @@ -10,7 +10,7 @@ Arithmetic functions using the arb library # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. # https://www.gnu.org/licenses/ -#***************************************************************************** +# **************************************************************************** from sage.libs.flint.types cimport ulong from sage.libs.flint.fmpq cimport fmpq_t, fmpq_init, fmpq_clear, fmpq_get_mpq diff --git a/src/sage/libs/braiding.pyx b/src/sage/libs/braiding.pyx index 5527e9bee8f..f3849a55d7e 100644 --- a/src/sage/libs/braiding.pyx +++ b/src/sage/libs/braiding.pyx @@ -331,9 +331,9 @@ def thurston_type(braid): sig_off() if i == 1: return 'periodic' - if i==2: + if i == 2: return 'reducible' - if i==3: + if i == 3: return 'pseudo-anosov' From 9cb320881fe75c6cc12919f0d40eaf894450f957 Mon Sep 17 00:00:00 2001 From: Giorgos Mousa Date: Sun, 24 Mar 2024 22:53:32 +0200 Subject: [PATCH 341/518] Suggestions by mkoeppe --- src/sage/matroids/constructor.py | 16 ++++++++++++++-- src/sage/matroids/flats_matroid.pyx | 4 ++-- src/sage/matroids/unpickling.pyx | 2 +- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/sage/matroids/constructor.py b/src/sage/matroids/constructor.py index ef14148947b..cecfead568a 100644 --- a/src/sage/matroids/constructor.py +++ b/src/sage/matroids/constructor.py @@ -179,7 +179,7 @@ def Matroid(groundset=None, data=None, **kwds): matroid. - ``independent_sets`` -- The list of independent sets of the matroid. - ``circuits`` -- The list of circuits of the matroid. - - ``nonspanning_circuits`` -- The list of nonspanning_circuits of the + - ``nonspanning_circuits`` -- The list of nonspanning circuits of the matroid. - ``flats`` -- The dictionary of flats indexed by their rank. - ``graph`` -- A graph, whose edges form the elements of the matroid. @@ -226,6 +226,8 @@ def Matroid(groundset=None, data=None, **kwds): The ``Matroid()`` method will return instances of type :class:`BasisMatroid `, + :class:`CircuitsMatroid `, + :class:`FlatsMatroid `, :class:`CircuitClosuresMatroid `, :class:`LinearMatroid `, :class:`BinaryMatroid `, @@ -307,7 +309,7 @@ def Matroid(groundset=None, data=None, **kwds): sage: M1 = Matroid(groundset='abc', circuits=['bc']) A matroid specified by a list of circuits gets converted to a - :class:`CircuitsMatroid ` + :class:`CircuitsMatroid ` internally:: sage: from sage.matroids.circuits_matroid import CircuitsMatroid @@ -331,6 +333,16 @@ def Matroid(groundset=None, data=None, **kwds): sage: M.is_valid() False + #. Dictionary of flats: + + :: + + sage: M = Matroid(flats={0: [''], 1: ['a', 'b'], 2: ['ab']}) + sage: M.is_valid() + True + sage: type(M) + + #. Graph: Sage has great support for graphs, see :mod:`sage.graphs.graph`. diff --git a/src/sage/matroids/flats_matroid.pyx b/src/sage/matroids/flats_matroid.pyx index 14816ddd7b5..44dec61c93d 100644 --- a/src/sage/matroids/flats_matroid.pyx +++ b/src/sage/matroids/flats_matroid.pyx @@ -111,7 +111,7 @@ cdef class FlatsMatroid(Matroid): INPUT: - - ``X`` -- an object with Python's ``frozenset`` interface + - ``X`` -- an object with Python's :class:`frozenset` interface OUTPUT: an integer; the rank of ``X`` in the matroid @@ -357,7 +357,7 @@ cdef class FlatsMatroid(Matroid): - ``k`` -- an integer - OUTPUT: a SetSystem + OUTPUT: a :class:`SetSystem` EXAMPLES:: diff --git a/src/sage/matroids/unpickling.pyx b/src/sage/matroids/unpickling.pyx index da9ef0a5e5f..9e41c28a477 100644 --- a/src/sage/matroids/unpickling.pyx +++ b/src/sage/matroids/unpickling.pyx @@ -181,7 +181,7 @@ def unpickle_circuit_closures_matroid(version, data): def unpickle_flats_matroid(version, data): """ - Unpickle a FlatsMatroid. + Unpickle a :class:`FlatsMatroid`. *Pickling* is Python's term for the loading and saving of objects. Functions like these serve to reconstruct a saved object. This all happens From 39db3c5da87658c803a2be31fd6e92f77ed858aa Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 24 Mar 2024 19:41:31 -0700 Subject: [PATCH 342/518] src/sage/modular/arithgroup/farey_symbol.pyx: Add 'std=c++...' --- src/sage/modular/arithgroup/farey_symbol.pyx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/sage/modular/arithgroup/farey_symbol.pyx b/src/sage/modular/arithgroup/farey_symbol.pyx index 34546c082da..4cfb4b12363 100644 --- a/src/sage/modular/arithgroup/farey_symbol.pyx +++ b/src/sage/modular/arithgroup/farey_symbol.pyx @@ -1,4 +1,6 @@ # distutils: sources = sage/modular/arithgroup/sl2z.cpp sage/modular/arithgroup/farey.cpp +# distutils: language = c++ +# distutils: extra_compile_args = -std=c++11 # sage.doctest: needs sage.libs.pari r""" Farey symbol for arithmetic subgroups of `\PSL_2(\ZZ)` From 4aaa3f3d9dace2c4d9f22146bfe398a74a044fac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Mon, 25 Mar 2024 07:52:18 +0100 Subject: [PATCH 343/518] fix forgotten detail --- src/sage/libs/glpk/error.pyx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sage/libs/glpk/error.pyx b/src/sage/libs/glpk/error.pyx index b8dce0d3085..4fb01782205 100644 --- a/src/sage/libs/glpk/error.pyx +++ b/src/sage/libs/glpk/error.pyx @@ -2,7 +2,7 @@ Error handler for the GLPK library """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2015 Jeroen Demeyer # # This program is free software: you can redistribute it and/or modify @@ -10,7 +10,7 @@ Error handler for the GLPK library # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. # https://www.gnu.org/licenses/ -#***************************************************************************** +# **************************************************************************** from cysignals.signals cimport sig_error @@ -24,8 +24,8 @@ class GLPKError(MIPSolverException): """ A low-level error that is raised by ``sage_glpk_term_hook``. - The GLPK API considers these errors non-recoverable. User code should not try - to catch this exception. + The GLPK API considers these errors non-recoverable. + User code should not try to catch this exception. EXAMPLES:: From 73fbbc2ec58077e005db1e2f939846d7121a4eaa Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 25 Mar 2024 08:19:44 +0100 Subject: [PATCH 344/518] 37668: add link to PR --- src/sage/knots/link.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/sage/knots/link.py b/src/sage/knots/link.py index 054f1c47eb0..34fc52a7400 100644 --- a/src/sage/knots/link.py +++ b/src/sage/knots/link.py @@ -4498,7 +4498,9 @@ def is_isotopic(self, other): False sage: set_verbose(0) - TESTS:: + TESTS: + + check that :issue:`37668` is fixed:: sage: L = KnotInfo.L6a2_0 sage: L1 = L.link() From 72e41219e9639ee41d0e01c793def7de3ebefbbc Mon Sep 17 00:00:00 2001 From: Giorgos Mousa Date: Mon, 25 Mar 2024 12:31:36 +0200 Subject: [PATCH 345/518] Remove noexcept-s and simplify __copy__ and __deepcopy__ --- src/sage/matroids/flats_matroid.pxd | 16 +++++++-------- src/sage/matroids/flats_matroid.pyx | 31 ++++++++++------------------- 2 files changed, 18 insertions(+), 29 deletions(-) diff --git a/src/sage/matroids/flats_matroid.pxd b/src/sage/matroids/flats_matroid.pxd index f3ceb787f30..e97b929429b 100644 --- a/src/sage/matroids/flats_matroid.pxd +++ b/src/sage/matroids/flats_matroid.pxd @@ -4,17 +4,17 @@ cdef class FlatsMatroid(Matroid): cdef frozenset _groundset # _E cdef int _matroid_rank # _R cdef dict _F # flats - cpdef groundset(self) noexcept - cpdef _rank(self, X) noexcept - cpdef full_rank(self) noexcept - cpdef _is_independent(self, F) noexcept + cpdef groundset(self) + cpdef _rank(self, X) + cpdef full_rank(self) + cpdef _is_independent(self, F) # enumeration - cpdef flats(self, k) noexcept - cpdef whitney_numbers2(self) noexcept + cpdef flats(self, k) + cpdef whitney_numbers2(self) # isomorphism - cpdef _is_isomorphic(self, other, certificate=*) noexcept + cpdef _is_isomorphic(self, other, certificate=*) # verification - cpdef is_valid(self) noexcept + cpdef is_valid(self) diff --git a/src/sage/matroids/flats_matroid.pyx b/src/sage/matroids/flats_matroid.pyx index 44dec61c93d..0629d987dd1 100644 --- a/src/sage/matroids/flats_matroid.pyx +++ b/src/sage/matroids/flats_matroid.pyx @@ -85,7 +85,7 @@ cdef class FlatsMatroid(Matroid): self._F[i].add(frozenset(F)) self._matroid_rank = max(self._F, default=-1) - cpdef groundset(self) noexcept: + cpdef groundset(self): """ Return the groundset of the matroid. @@ -102,7 +102,7 @@ cdef class FlatsMatroid(Matroid): """ return self._groundset - cpdef _rank(self, X) noexcept: + cpdef _rank(self, X): """ Return the rank of a set ``X``. @@ -138,7 +138,7 @@ cdef class FlatsMatroid(Matroid): # optional - cpdef full_rank(self) noexcept: + cpdef full_rank(self): r""" Return the rank of the matroid. @@ -156,7 +156,7 @@ cdef class FlatsMatroid(Matroid): """ return self._matroid_rank - cpdef _is_isomorphic(self, other, certificate=False) noexcept: + cpdef _is_isomorphic(self, other, certificate=False): """ Test if ``self`` is isomorphic to ``other``. @@ -286,12 +286,7 @@ cdef class FlatsMatroid(Matroid): sage: M.groundset() is N.groundset() True """ - N = FlatsMatroid(groundset=[], flats={}) - N._groundset = self._groundset - N._F = self._F - N._matroid_rank = self._matroid_rank - N.rename(self.get_custom_name()) - return N + return self def __deepcopy__(self, memo=None): """ @@ -309,15 +304,9 @@ cdef class FlatsMatroid(Matroid): sage: M == N True sage: M.groundset() is N.groundset() - False + True """ - if memo is None: - memo = {} - from copy import deepcopy - # Since matroids are immutable, N cannot reference itself in correct code, so no need to worry about the recursion. - N = FlatsMatroid(groundset=deepcopy(self._groundset, memo), flats=deepcopy(self._F, memo)) - N.rename(deepcopy(self.get_custom_name(), memo)) - return N + return self def __reduce__(self): """ @@ -349,7 +338,7 @@ cdef class FlatsMatroid(Matroid): # enumeration - cpdef flats(self, k) noexcept: + cpdef flats(self, k): r""" Return the flats of the matroid of specified rank. @@ -394,7 +383,7 @@ cdef class FlatsMatroid(Matroid): for F in self._F[k]: yield F - cpdef whitney_numbers2(self) noexcept: + cpdef whitney_numbers2(self): r""" Return the Whitney numbers of the second kind of the matroid. @@ -425,7 +414,7 @@ cdef class FlatsMatroid(Matroid): # verification - cpdef is_valid(self) noexcept: + cpdef is_valid(self): r""" Test if ``self`` obeys the matroid axioms. From 951bfaf21f4bb3ed95fe84c165682f8aa6b90663 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Mon, 25 Mar 2024 13:49:56 +0100 Subject: [PATCH 346/518] test both left and right inverse Co-authored-by: Travis Scrimshaw --- src/sage/combinat/partition.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sage/combinat/partition.py b/src/sage/combinat/partition.py index 2a189448059..38772f4b0b7 100644 --- a/src/sage/combinat/partition.py +++ b/src/sage/combinat/partition.py @@ -2579,6 +2579,7 @@ def glaisher_franklin_inverse(self, s): The map is inverse to :meth:`glaisher_franklin`:: sage: all(mu.glaisher_franklin(s).glaisher_franklin_inverse(s) == mu + ....: mu.glaisher_franklin_inverse(s).glaisher_franklin(s) == mu ....: for n in range(20) for mu in Partitions(n) ....: for s in range(1, 5)) True From 91a2e21daf616bce575bed7d9a2efcc9d510d0f7 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Mon, 25 Mar 2024 13:50:35 +0100 Subject: [PATCH 347/518] pep8 Co-authored-by: Travis Scrimshaw --- src/sage/combinat/partition.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/combinat/partition.py b/src/sage/combinat/partition.py index 38772f4b0b7..6fad59e31f3 100644 --- a/src/sage/combinat/partition.py +++ b/src/sage/combinat/partition.py @@ -2595,9 +2595,9 @@ def glaisher_franklin_inverse(self, s): mu = [] for p, m in enumerate(self.to_exp(), 1): p = ZZ(p) - mu.extend([p * s]*(m // s)) + mu.extend([p * s] * (m // s)) m1, p1 = p.val_unit(s) - mu.extend([p1]*((m % s) * s**m1)) + mu.extend([p1] * ((m % s) * s**m1)) P = self.parent() return P.element_class(P, sorted(mu, reverse=True)) From ea36328093efef2a188b5efb9d7307074a54be60 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Mon, 25 Mar 2024 13:52:25 +0100 Subject: [PATCH 348/518] optimize list comprehension Co-authored-by: Travis Scrimshaw --- src/sage/combinat/partition.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/sage/combinat/partition.py b/src/sage/combinat/partition.py index 6fad59e31f3..4f676f7f9bd 100644 --- a/src/sage/combinat/partition.py +++ b/src/sage/combinat/partition.py @@ -2542,11 +2542,10 @@ def glaisher_franklin(self, s): mu = [] for p, m in enumerate(self.to_exp(), 1): if not p % s: - mu.extend([p // s]*(m*s)) + mu.extend([p // s] * (m*s)) else: - mu.extend(p1 for i, v in enumerate(m.digits(s)) - if (p1 := p * s**i) - for _ in range(v)) + mu.extend([p1]*v for i, v in enumerate(m.digits(s)) + if (p1 := p * s**i)) P = self.parent() return P.element_class(P, sorted(mu, reverse=True)) From 562b4149afa9af03a42403e7f4be0517fb00d99c Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Mon, 25 Mar 2024 13:55:54 +0100 Subject: [PATCH 349/518] move explanation of output to description, where it really belongs --- src/sage/combinat/partition.py | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/sage/combinat/partition.py b/src/sage/combinat/partition.py index 4f676f7f9bd..aba7793a089 100644 --- a/src/sage/combinat/partition.py +++ b/src/sage/combinat/partition.py @@ -2489,17 +2489,15 @@ def glaisher_franklin(self, s): r""" Apply the Glaisher-Franklin bijection to ``self``. - INPUT: - - - ``s`` -- positive integer - - OUTPUT: - The Franklin-Glaisher bijection, with parameter `s`, returns a partition whose set of parts that are repeated at least `s` times equals the set of parts divisible by `s` in ``self``, after dividing each part by `s`. + INPUT: + + - ``s`` -- positive integer + EXAMPLES:: sage: Partition([4, 3, 2, 2, 1]).glaisher_franklin(2) @@ -2554,18 +2552,16 @@ def glaisher_franklin_inverse(self, s): r""" Apply the inverse of the Glaisher-Franklin bijection to ``self``. - INPUT: - - - ``s`` -- positive integer - - OUTPUT: - The inverse of the Franklin-Glaisher bijection, with parameter `s`, returns a partition whose set of parts that are divisible by `s`, after dividing each by `s`, equals the equals the set of parts repeated at least `s` times in ``self``. + INPUT: + + - ``s`` -- positive integer + EXAMPLES:: sage: Partition([4, 3, 2, 2, 1]).glaisher_franklin(2) From 0266ff84ffda8c957f89a82e44774ec5a6b92b34 Mon Sep 17 00:00:00 2001 From: Eloi Torrents Date: Mon, 25 Mar 2024 15:08:36 +0100 Subject: [PATCH 350/518] remove unused variable --- src/sage/modular/btquotients/pautomorphicform.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/sage/modular/btquotients/pautomorphicform.py b/src/sage/modular/btquotients/pautomorphicform.py index f08a8f18af8..b4c6492c19d 100644 --- a/src/sage/modular/btquotients/pautomorphicform.py +++ b/src/sage/modular/btquotients/pautomorphicform.py @@ -2161,7 +2161,6 @@ def coleman(self, t1, t2, E=None, method='moments', mult=False): elif method == 'moments': for e in E: - ii += 1 f = (x - t1) / (x - t2) a, b, c, d = e.list() y0 = f(R1([b, a]) / R1([d, c])) # f( (ax+b)/(cx+d) ) From 13e15e9db03eee9b6636ebf7d4a963d296dfc1cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20K=C3=B6ppe?= Date: Mon, 25 Mar 2024 16:28:26 -0700 Subject: [PATCH 351/518] Update src/sage/combinat/designs/incidence_structures.py --- src/sage/combinat/designs/incidence_structures.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/combinat/designs/incidence_structures.py b/src/sage/combinat/designs/incidence_structures.py index 5d622672e43..79c95d2c4fb 100644 --- a/src/sage/combinat/designs/incidence_structures.py +++ b/src/sage/combinat/designs/incidence_structures.py @@ -1810,7 +1810,7 @@ def dual(self, algorithm=None): REFERENCE: - - Soicher, Leonard, :gap_package:`Design package manual ` + - Leonard Soicher, :gap_package:`Design package manual ` """ if algorithm == "gap": libgap.load_package("design") From 378ec7479449ba2fa9479b36bac0273c94373975 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 25 Mar 2024 16:34:40 -0700 Subject: [PATCH 352/518] pkgs/sagemath-standard/setup.py: Support gpep517 (apply https://github.com/cschwan/sage-on-gentoo/blob/master/sci-mathematics/sagemath-standard/files/sagemath-standard-9.8-build_ext.patch) --- pkgs/sagemath-standard/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/sagemath-standard/setup.py b/pkgs/sagemath-standard/setup.py index 9bd3ac3acf5..fe5c5380481 100755 --- a/pkgs/sagemath-standard/setup.py +++ b/pkgs/sagemath-standard/setup.py @@ -73,7 +73,7 @@ ######################################################### if any(x in sys.argv - for x in ['build', 'bdist_wheel', 'install']): + for x in ['build', 'build_ext', 'bdist_wheel', 'install']): log.info("Generating auto-generated sources") from sage_setup.autogen import autogen_all autogen_all() From 15bae26f87f8ca8d25003af1ea923a6fcaa5b149 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 22 Feb 2024 17:27:05 -0800 Subject: [PATCH 353/518] src/setup.py, pkgs/sagemath-standard.py: Do not touch the lazy_import_cache --- pkgs/sagemath-standard/setup.py | 9 --------- src/setup.py | 11 ----------- 2 files changed, 20 deletions(-) diff --git a/pkgs/sagemath-standard/setup.py b/pkgs/sagemath-standard/setup.py index 9bd3ac3acf5..dec74abd491 100755 --- a/pkgs/sagemath-standard/setup.py +++ b/pkgs/sagemath-standard/setup.py @@ -59,15 +59,6 @@ develop=sage_develop, install=sage_install_and_clean) -######################################################### -### Testing related stuff -######################################################### - -# Remove (potentially invalid) star import caches -import sage.misc.lazy_import_cache -if os.path.exists(sage.misc.lazy_import_cache.get_cache_file()): - os.unlink(sage.misc.lazy_import_cache.get_cache_file()) - ######################################################### ### Discovering Sources ######################################################### diff --git a/src/setup.py b/src/setup.py index 0f28b20d6dc..7340affa6ca 100755 --- a/src/setup.py +++ b/src/setup.py @@ -18,8 +18,6 @@ # PEP 517 builds do not have . in sys.path sys.path.insert(0, os.path.dirname(__file__)) -import sage.misc.lazy_import_cache - from sage.misc.package import is_package_installed_and_updated from sage_setup.command.sage_build_ext_minimal import sage_build_ext_minimal from sage_setup.command.sage_install import sage_develop, sage_install @@ -65,15 +63,6 @@ else: sdist = False -# ######################################################## -# ## Testing related stuff -# ######################################################## - -# Remove (potentially invalid) star import caches -if os.path.exists(sage.misc.lazy_import_cache.get_cache_file()): - os.unlink(sage.misc.lazy_import_cache.get_cache_file()) - - # ######################################################## # ## Discovering Sources # ######################################################## From 20932043c447ef83662ac2cc819594b3ccfd4ac2 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 25 Mar 2024 17:45:10 -0700 Subject: [PATCH 354/518] build/pkgs/sagelib/spkg-install.in: Remove the lazy import cache here, after installation --- build/pkgs/sagelib/spkg-install.in | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/build/pkgs/sagelib/spkg-install.in b/build/pkgs/sagelib/spkg-install.in index 8e3efb92da7..667b3da98c0 100644 --- a/build/pkgs/sagelib/spkg-install.in +++ b/build/pkgs/sagelib/spkg-install.in @@ -76,6 +76,11 @@ else fi fi +# Remove (potentially invalid) star import caches. +# lazy star imports are not used by the Sage library, but could still be used by +# downstream code. +python3 -c 'import pathlib; from sage.misc.lazy_import_cache import get_cache_file; pathlib.Path(get_cache_file()).unlink(missing_ok=True)' + # Issue #33103: The temp.* directories are large after a full build. # We remove them to save space; they are not needed for fast rebuilds. rm -rf build/temp.* From b2a56e2faf88c5149c3088d574f2516172522cd4 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 22 Feb 2024 21:10:52 -0800 Subject: [PATCH 355/518] src/sage/misc/lazy_import_cache.py: Remove outdated comment --- src/sage/misc/lazy_import_cache.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/sage/misc/lazy_import_cache.py b/src/sage/misc/lazy_import_cache.py index 191a2058e2b..d47b6d5edfd 100644 --- a/src/sage/misc/lazy_import_cache.py +++ b/src/sage/misc/lazy_import_cache.py @@ -1,7 +1,5 @@ """ Lazy import cache - -This is a pure Python file with no dependencies so it can be used in setup.py. """ import os import hashlib From 18d7e971b25a3b4de85fe7f28f77ee1375a0b46e Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 13 Mar 2024 01:40:43 -0700 Subject: [PATCH 356/518] Matrix.str: New parameters top_border, bottom_border, left_border, right_border --- src/sage/matrix/matrix0.pyx | 99 +++++++++++++++++++++++++++++++------ 1 file changed, 83 insertions(+), 16 deletions(-) diff --git a/src/sage/matrix/matrix0.pyx b/src/sage/matrix/matrix0.pyx index 9eb3f9f4213..395220fc765 100644 --- a/src/sage/matrix/matrix0.pyx +++ b/src/sage/matrix/matrix0.pyx @@ -1795,7 +1795,9 @@ cdef class Matrix(sage.structure.element.Matrix): return self.str() def str(self, rep_mapping=None, zero=None, plus_one=None, minus_one=None, - *, unicode=False, shape=None, character_art=False): + *, unicode=False, shape=None, character_art=False, + left_border=None, right_border=None, + top_border=None, bottom_border=None): r""" Return a nice string representation of the matrix. @@ -1920,6 +1922,21 @@ cdef class Matrix(sage.structure.element.Matrix): [ 0.333333333333333 66.6666666666667] [ -3.00000000000000 1.00000000000000e6] + Matrices with borders:: + + sage: M = matrix([[1,2,3], [4,5,6], [7,8,9]]) + sage: M.subdivide(None, 2) + sage: print(M.str(unicode=True, + ....: top_border=['ab', 'cde', 'f'], + ....: bottom_border=['*', '', ''], + ....: left_border=[1, 10, 100], + ....: right_border=['', ' <', ''])) + ab cde f + ab⎛ 1 2│ 3⎞ + cde⎜ 4 5│ 6⎟ < + f⎝ 7 8│ 9⎠ + * + TESTS: Prior to :issue:`11544` this could take a full minute to run (2011). :: @@ -2062,6 +2079,12 @@ cdef class Matrix(sage.structure.element.Matrix): # compute column widths S = [] + if top_border is not None: + for x in top_border: + S.append(str(x)) + top_count = 1 + else: + top_count = 0 for x in entries: # Override the usual representations with those specified if callable(rep_mapping): @@ -2074,39 +2097,83 @@ cdef class Matrix(sage.structure.element.Matrix): else: rep = repr(x) S.append(rep) + if bottom_border is not None: + for x in bottom_border: + S.append(str(x)) + bottom_count = 1 + else: + bottom_count = 0 width = max(map(len, S)) + left = [] rows = [] + right = [] hline = cl.join(hl * ((width + 1)*(b - a) - 1) - for a,b in zip([0] + col_divs, col_divs + [nc])) + for a,b in zip([0] + col_divs, col_divs + [nc])) # compute rows - for r from 0 <= r < nr: - rows += [hline] * row_divs.count(r) + for r in range(-top_count, nr + bottom_count): + if 0 <= r < nr: + n = row_divs.count(r) + if n: + left.extend([""] * n) + rows.extend([hline] * n) + right.extend([""] * n) + if top_border is not None and 0 <= r < nr: + left.append(str(top_border[r])) + else: + left.append("") s = "" for c from 0 <= c < nc: if col_div_counts[c]: - sep = vl * col_div_counts[c] + if 0 <= r < nr: + sep = vl * col_div_counts[c] + else: + sep = " " * col_div_counts[c] elif c == 0: sep = "" else: sep = " " - entry = S[r * nc + c] + entry = S[(r + top_count) * nc + c] entry = " " * (width - len(entry)) + entry s = s + sep + entry - s = s + vl * col_div_counts[nc] + else: + if 0 <= r < nr: + s = s + vl * col_div_counts[nc] + else: + s = s + " " * col_div_counts[nc] rows.append(s) - rows += [hline] * row_divs.count(nr) - - last_row = len(rows) - 1 - if last_row == 0: - rows[0] = slb + rows[0] + srb + if bottom_border is not None and 0 <= r < nr: + right.append(str(right_border[r])) + else: + right.append("") + else: + if nr == nr + bottom_count: + n = row_divs.count(nr) + if n: + left.extend([""] * n) + rows.extend([hline] * n) + right.extend([""] * n) + + # left and right brackets + for i in range(top_count): + rows[i] = " "*len(slb) + rows[i] + " "*len(srb) + if len(rows) == top_count + 1 + bottom_count: + rows[top_count] = slb + rows[top_count] + srb else: - rows[0] = tlb + rows[0] + trb - for r from 1 <= r < last_row: - rows[r] = mlb + rows[r] + mrb - rows[last_row] = blb + rows[last_row] + brb + rows[top_count] = tlb + rows[top_count] + trb + for i in range(top_count + 1, len(rows) - bottom_count - 1): + rows[i] = mlb + rows[i] + mrb + rows[-1 - bottom_count] = blb + rows[-1 - bottom_count] + brb + for i in range(bottom_count): + rows[-1 - i] = " "*len(slb) + rows[-1 - i] + " "*len(srb) + + # left and right border + left_width = max(len(s) for s in left) + right_width = max(len(s) for s in right) + for i in range(len(rows)): + rows[i] = left[i].rjust(left_width) + rows[i] + right[i].rjust(right_width) if character_art: breakpoints = [] From 9ced71d5c15d4df93dfd9926fd5d99299d8880e3 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 13 Mar 2024 10:28:14 -0700 Subject: [PATCH 357/518] Matrix.str: New parameters top_border, bottom_border, left_border, right_border (fixup) --- src/sage/matrix/matrix0.pyx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/sage/matrix/matrix0.pyx b/src/sage/matrix/matrix0.pyx index 395220fc765..611c677c424 100644 --- a/src/sage/matrix/matrix0.pyx +++ b/src/sage/matrix/matrix0.pyx @@ -1931,11 +1931,11 @@ cdef class Matrix(sage.structure.element.Matrix): ....: bottom_border=['*', '', ''], ....: left_border=[1, 10, 100], ....: right_border=['', ' <', ''])) - ab cde f - ab⎛ 1 2│ 3⎞ - cde⎜ 4 5│ 6⎟ < - f⎝ 7 8│ 9⎠ - * + ab cde f + 1⎛ 1 2│ 3⎞ + 10⎜ 4 5│ 6⎟ < + 100⎝ 7 8│ 9⎠ + * TESTS: @@ -2121,7 +2121,7 @@ cdef class Matrix(sage.structure.element.Matrix): rows.extend([hline] * n) right.extend([""] * n) if top_border is not None and 0 <= r < nr: - left.append(str(top_border[r])) + left.append(str(left_border[r])) else: left.append("") s = "" From 06f16d2b477f348de9f8364e5d27430894207f46 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 26 Feb 2024 12:16:08 -0800 Subject: [PATCH 358/518] src/doc/en/developer/packaging.rst: Move section on patching to end, add subsection on importing patch from GitHub PR --- src/doc/en/developer/packaging.rst | 434 ++++++++++++++++------------- 1 file changed, 246 insertions(+), 188 deletions(-) diff --git a/src/doc/en/developer/packaging.rst b/src/doc/en/developer/packaging.rst index 3e014169a7c..14b83d6701c 100644 --- a/src/doc/en/developer/packaging.rst +++ b/src/doc/en/developer/packaging.rst @@ -75,6 +75,8 @@ the following source types: - its version number is defined by the required file ``package-version.txt``; + - can be patched; + - Sage installs the package using build and install scripts (see :ref:`section-spkg-install`); @@ -92,6 +94,8 @@ the following source types: - its version number is defined by the required file ``package-version.txt``; + - cannot be patched; + - no build and install scripts are needed (with one exception: the package :ref:`spkg_pip` installs itself from its wheel using a custom install script); @@ -109,6 +113,8 @@ the following source types: contains the name of the package (more details at https://pip.pypa.io/en/stable/user_guide/#requirements-files); + - cannot be patched; + - Sage installs the package using the ``pip`` package manager; - Sage delegates the recording of installed package version numbers to it; @@ -121,6 +127,8 @@ the following source types: - the file ``package-version.txt`` is optional; + - may be associated with a source tree included in the repository; + - installing the package runs the installation script ``spkg-install`` or ``spkg-install.in`` (see :ref:`section-spkg-install`); @@ -157,8 +165,7 @@ Third-party packages in Sage consist of two parts: the filename of the tarball. In any case, the actual code must be unmodified: if you need to change the sources, add a :ref:`patch ` - instead. See also :ref:`section-spkg-src` for automating the - modifications to the upstream tarball. + instead. #. The build scripts and associated files are in a subdirectory ``SAGE_ROOT/build/pkgs/``, where you replace ```` @@ -215,8 +222,8 @@ See :ref:`section-package-types` for the meaning of these types. .. _section-spkg-install: -Build and install scripts of normal packages --------------------------------------------- +Build and install scripts of ``normal`` packages +------------------------------------------------ The ``spkg-build.in`` and ``spkg-install.in`` files are templates for ``bash`` scripts ``spkg-build`` and ``spkg-install``, which build @@ -359,14 +366,27 @@ at build time, which should to the appropriate system-specific packages should prefix all commands in ``spkg-install.in`` that write into the installation hierarchy with ``$SAGE_SUDO``. -Install scripts of script packages ----------------------------------- +If an ``spkg-src`` file is present, it indicates that the tarball is not +an unmodified third-party tarball (see :ref:`section-spkg-patching`). +It documents how the tarball was generated (either by changing an upstream +tarball or generating it from an repository). As ideally +our tarballs are not modified, for most packages there is no ``spkg-src`` file. + -For script packages, it is also possible to use an install script named ``spkg-install``. +Install and source scripts of ``script`` packages +------------------------------------------------- + +For ``script`` packages, it is also possible to use an install script named ``spkg-install``. It needs to be an executable shell script; it is not subject to the templating described in the previous section and will be executed directly from the build directory. +Most of our ``script`` packages are associated with a source tree included in the +repository, in a subdirectory of ``$SAGE_ROOT/pkgs/``. In this case, there +is a symlink ``src`` that points to the source tree and a script ``spkg-src`` +that builds a tarball for the package. + + .. _section-sdh-helpers: Helper functions @@ -826,184 +846,6 @@ word ``SAGE_VENV``, and ``build/pkgs/sagemath_doc_html/trees.txt`` contains the word ``SAGE_DOCS``. -.. _section-spkg-patching: - -Patching sources ----------------- - -Actual changes to the source code must be via patches, which should be placed -in the ``patches/`` directory, and must have the ``.patch`` extension. GNU -patch is distributed with Sage, so you can rely on it being available. Patches -must include documentation in their header (before the first diff hunk), and -must have only one "prefix" level in the paths (that is, only one path level -above the root of the upstream sources being patched). So a typical patch file -should look like this: - -.. CODE-BLOCK:: diff - - Add autodoc_builtin_argspec config option - - Following the title line you can add a multi-line description of - what the patch does, where you got it from if you did not write it - yourself, if they are platform specific, if they should be pushed - upstream, etc... - - diff -dru Sphinx-1.2.2/sphinx/ext/autodoc.py.orig Sphinx-1.2.2/sphinx/ext/autodoc.py - --- Sphinx-1.2.2/sphinx/ext/autodoc.py.orig 2014-03-02 20:38:09.000000000 +1300 - +++ Sphinx-1.2.2/sphinx/ext/autodoc.py 2014-10-19 23:02:09.000000000 +1300 - @@ -1452,6 +1462,7 @@ - - app.add_config_value('autoclass_content', 'class', True) - app.add_config_value('autodoc_member_order', 'alphabetic', True) - + app.add_config_value('autodoc_builtin_argspec', None, True) - app.add_config_value('autodoc_default_flags', [], True) - app.add_config_value('autodoc_docstring_signature', True, True) - app.add_event('autodoc-process-docstring') - -Patches directly under the ``patches/`` directly are applied automatically -before running the ``spkg-install`` script (so long as they have the ``.patch`` -extension). If you need to apply patches conditionally (such as only on -a specifically platform), you can place those patches in a subdirectory of -``patches/`` and apply them manually using the ``sage-apply-patches`` script. -For example, considering the layout: - -.. CODE-BLOCK:: text - - SAGE_ROOT/build/pkgs/foo - |-- patches - | |-- solaris - | | |-- solaris.patch - | |-- bar.patch - | `-- baz.patch - -The patches ``bar.patch`` and ``baz.patch`` are applied to the unpacked -upstream sources in ``src/`` before running ``spkg-install``. To conditionally -apply the patch for Solaris the ``spkg-install`` should contain a section like -this: - -.. CODE-BLOCK:: bash - - if [ $UNAME == "SunOS" ]; then - sage-apply-patches -d solaris - fi - -where the ``-d`` flag applies all patches in the ``solaris/`` subdirectory of -the main ``patches/`` directory. - - -.. _section-spkg-patch-or-repackage: - -When to patch, when to repackage, when to autoconfiscate --------------------------------------------------------- - -- Use unpatched original upstream tarball when possible. - - Sometimes it may seem as if you need to patch a (hand-written) - ``Makefile`` because it "hard-codes" some paths or compiler flags: - - .. CODE-BLOCK:: diff - - --- a/Makefile - +++ b/Makefile - @@ -77,7 +77,7 @@ - # This is a Makefile. - # Handwritten. - - -DESTDIR = /usr/local - +DESTDIR = $(SAGE_ROOT)/local - BINDIR = $(DESTDIR)/bin - INCDIR = $(DESTDIR)/include - LIBDIR = $(DESTDIR)/lib - - Don't use patching for that. Makefile variables can be overridden - from the command-line. Just use the following in ``spkg-install``: - - .. CODE-BLOCK:: bash - - $(MAKE) DESTDIR="$SAGE_ROOT/local" - -- Check if Debian or another distribution already provides patches - for upstream. Use them, don't reinvent the wheel. - -- If the upstream Makefile does not build shared libraries, - don't bother trying to patch it. - - Autoconfiscate the package instead and use the standard facilities - of Automake and Libtool. This ensures that the shared library build - is portable between Linux and macOS. - -- If you have to make changes to ``configure.ac`` or other source - files of the autotools build system (or if you are autoconfiscating - the package), then you can't use patching; make a :ref:`modified - tarball ` instead. - -- If the patch would be huge, don't use patching. Make a - :ref:`modified tarball ` instead. - -- Otherwise, :ref:`maintain a set of patches - `. - - -.. _section-spkg-patch-maintenance: - -How to maintain a set of patches --------------------------------- - -We recommend the following workflow for maintaining a set of patches. - -- Fork the package and put it on a public git repository. - - If upstream has a public version control repository, import it from - there. If upstream does not have a public version control - repository, import the current sources from the upstream tarball. - Let's call the branch ``upstream``. - -- Create a branch for the changes necessary for Sage, let's call it - ``sage_package_VERSION``, where ``version`` is the upstream version - number. - -- Make the changes and commit them to the branch. - -- Generate the patches against the ``upstream`` branch: - - .. CODE-BLOCK:: bash - - rm -Rf SAGE_ROOT/build/pkgs/PACKAGE/patches - mkdir SAGE_ROOT/build/pkgs/PACKAGE/patches - git format-patch -o SAGE_ROOT/build/pkgs/PACKAGE/patches/ upstream - -- Optionally, create an ``spkg-src`` file in the Sage package's - directory that regenerates the patch directory using the above - commands. - -- When a new upstream version becomes available, merge (or import) it - into ``upstream``, then create a new branch and rebase it on top of - the updated upstream: - - .. CODE-BLOCK:: bash - - git checkout sage_package_OLDVERSION - git checkout -b sage_package_NEWVERSION - git rebase upstream - - Then regenerate the patches. - - -.. _section-spkg-src: - -Modified tarballs ------------------ - -The ``spkg-src`` file is optional and only to document how the upstream -tarball was changed. Ideally it is not modified, then there would be no -``spkg-src`` file present either. - -However, if you really must modify the upstream tarball then it is -recommended that you write a script, called ``spkg-src``, that makes the -changes. This not only serves as documentation but also makes it easier -to apply the same modifications to future versions. - - .. _section-spkg-versioning: Package versioning @@ -1023,9 +865,8 @@ This particular Sage package for ``database_stein_watkins`` was created in 2014, but the data it contains was last updated in 2011. If you apply any patches, or if you made changes to the upstream tarball -(see :ref:`section-directory-structure` for allowable changes), -then you should append a ``.p0`` to the version to indicate that it's -not a vanilla package. +(see :ref:`section-spkg-patching` below), then you should append a ``.p0`` +to the version to indicate that it's not an unmodified package. Additionally, whenever you make changes to a package *without* changing the upstream tarball (for example, you add an additional patch or you @@ -1366,3 +1207,220 @@ must meet the following requirements: :ref:`chapter-github`. +.. _section-spkg-patching: + +Modifying third-party code +========================== + +In the Sage distribution, we try to use unpatched original upstream tarballs +of stable versions of third-party packages whenever possible. + +Sometimes, however, modifications are necessary, either to fix a bug or +to make the package build on the platforms supported by Sage. + + +.. _section-spkg-patch-or-repackage: + +When to patch, when to repackage, when to autoconfiscate +-------------------------------------------------------- + +- First check whether there is already a newer stable version of the package + available that fixes the problem. In this case, try to upgrade the package. + +- Check if Debian or another distribution already provides patches + for upstream. Use them if possible, don't reinvent the wheel. + +- If the upstream project is maintained on GitHub, check if there is a Pull + Request that can be imported; see :ref:`section-spkg-patch-from-pr` below. + +- Sometimes it may seem as if you need to patch a (hand-written) + ``Makefile`` because it "hard-codes" some paths or compiler flags: + + .. CODE-BLOCK:: diff + + --- a/Makefile + +++ b/Makefile + @@ -77,7 +77,7 @@ + # This is a Makefile. + # Handwritten. + + -DESTDIR = /usr/local + +DESTDIR = $(SAGE_ROOT)/local + BINDIR = $(DESTDIR)/bin + INCDIR = $(DESTDIR)/include + LIBDIR = $(DESTDIR)/lib + + Don't use patching for that. Makefile variables can be overridden + from the command-line. Just use the following in ``spkg-install``: + + .. CODE-BLOCK:: bash + + $(MAKE) DESTDIR="$SAGE_ROOT/local" + +- If the upstream Makefile does not build shared libraries, + don't bother trying to patch it. + + Autoconfiscate the package instead and use the standard facilities + of Automake and Libtool. This ensures that the shared library build + is portable between Linux and macOS. + +- If you have to make changes to ``configure.ac`` or other source + files of the autotools build system (or if you are autoconfiscating + the package), then you can't use patching; make a :ref:`modified + tarball ` instead. + +- If the patch would be huge, don't use patching. Make a + :ref:`modified tarball ` instead. + +- Otherwise, :ref:`maintain a set of patches + `. + + +.. _section-spkg-patch-from-pr: + +Patching sources by importing a pull request from GitHub +-------------------------------------------------------- + +Only ``normal`` packages can be patched; see :ref:`section-package-source-types`. +If a Python package is currently a ``wheel`` package +and you need to patch it, change it to a ``normal`` package first. + +In the easiest and quite commmon case, a pull request is already available on +the upstream package's GitHub repository. + +For example, if https://github.com/discopt/cmr/pull/64 is the PR that we wish to use, +change the URL to https://github.com/discopt/cmr/pull/64.patch and save this file +in the ``patches/`` subdirectory of the package directory (create the subdirectory +if it does not exist yet). Make sure that it has the ``.patch`` +file name extension; if your browser saved it with a ``.patch.txt`` extension, +rename it. + +Modify the ``package-version.txt`` file to indicate the changed patch level; see +:ref:`section-spkg-versioning`. This ensures that the package will be rebuilt, +even though its upstream version did not change. This is important because when +you open a pull request in the Sage repository that adds the patch, also +our automatic test runs on GitHub Actions need to rebuild the packages, +and likewise when other people are testing your pull request. + +Next, test building the package with the patch, for example using ``make build``. +You should see a message like ``Applying 64.patch``. Messages such as +``Hunk #1 succeeded at 144 with fuzz 1 (offset 9 lines)`` are safe to +ignore. They appear when the PR from which you prepared the patch is based +on a version that differs from the version that the Sage package uses, or +when there are other patches that make changes to the same file. + + +Manual preparation of patches +----------------------------- + +Patches must include documentation in their header (before the first diff hunk), and +must have only one "prefix" level in the paths (that is, only one path level +above the root of the upstream sources being patched). So a typical patch file +should look like this: + +.. CODE-BLOCK:: diff + + Add autodoc_builtin_argspec config option + + Following the title line you can add a multi-line description of + what the patch does, where you got it from if you did not write it + yourself, if they are platform specific, if they should be pushed + upstream, etc... + + diff -dru Sphinx-1.2.2/sphinx/ext/autodoc.py.orig Sphinx-1.2.2/sphinx/ext/autodoc.py + --- Sphinx-1.2.2/sphinx/ext/autodoc.py.orig 2014-03-02 20:38:09.000000000 +1300 + +++ Sphinx-1.2.2/sphinx/ext/autodoc.py 2014-10-19 23:02:09.000000000 +1300 + @@ -1452,6 +1462,7 @@ + + app.add_config_value('autoclass_content', 'class', True) + app.add_config_value('autodoc_member_order', 'alphabetic', True) + + app.add_config_value('autodoc_builtin_argspec', None, True) + app.add_config_value('autodoc_default_flags', [], True) + app.add_config_value('autodoc_docstring_signature', True, True) + app.add_event('autodoc-process-docstring') + +Patches directly under the ``patches/`` directly are applied automatically +before running the ``spkg-install`` script (so long as they have the ``.patch`` +extension). If you need to apply patches conditionally (such as only on +a specifically platform), you can place those patches in a subdirectory of +``patches/`` and apply them manually using the ``sage-apply-patches`` script. +For example, considering the layout: + +.. CODE-BLOCK:: text + + SAGE_ROOT/build/pkgs/foo + |-- patches + | |-- solaris + | | |-- solaris.patch + | |-- bar.patch + | `-- baz.patch + +The patches ``bar.patch`` and ``baz.patch`` are applied to the unpacked +upstream sources in ``src/`` before running ``spkg-install``. To conditionally +apply the patch for Solaris the ``spkg-install`` should contain a section like +this: + +.. CODE-BLOCK:: bash + + if [ $UNAME == "SunOS" ]; then + sage-apply-patches -d solaris + fi + +where the ``-d`` flag applies all patches in the ``solaris/`` subdirectory of +the main ``patches/`` directory. + + +.. _section-spkg-patch-maintenance: + +How to maintain a set of patches +-------------------------------- + +We recommend the following workflow for maintaining a set of patches. + +- Fork the package and put it on a public git repository. + + If upstream has a public version control repository, import it from + there. If upstream does not have a public version control + repository, import the current sources from the upstream tarball. + Let's call the branch ``upstream``. + +- Create a branch for the changes necessary for Sage, let's call it + ``sage_package_VERSION``, where ``version`` is the upstream version + number. + +- Make the changes and commit them to the branch. + +- Generate the patches against the ``upstream`` branch: + + .. CODE-BLOCK:: bash + + rm -Rf SAGE_ROOT/build/pkgs/PACKAGE/patches + mkdir SAGE_ROOT/build/pkgs/PACKAGE/patches + git format-patch -o SAGE_ROOT/build/pkgs/PACKAGE/patches/ upstream + +- Optionally, create an ``spkg-src`` file in the Sage package's + directory that regenerates the patch directory using the above + commands. + +- When a new upstream version becomes available, merge (or import) it + into ``upstream``, then create a new branch and rebase it on top of + the updated upstream: + + .. CODE-BLOCK:: bash + + git checkout sage_package_OLDVERSION + git checkout -b sage_package_NEWVERSION + git rebase upstream + + Then regenerate the patches. + + +.. _section-spkg-src: + +Modified tarballs +----------------- + +If you really must modify the upstream tarball, then it is +recommended that you write a script, called ``spkg-src``, that makes the +changes. This not only serves as documentation but also makes it easier +to apply the same modifications to future versions. From 0082bddd5effc8026cab3067339a170c59bed82e Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 21 Feb 2024 20:35:33 -0800 Subject: [PATCH 359/518] sage -b: Just use 'make sagelib-no-deps' from SAGE_ROOT, do not use build system internals --- src/bin/sage | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/bin/sage b/src/bin/sage index c1db00734dc..98d0646c3f0 100755 --- a/src/bin/sage +++ b/src/bin/sage @@ -287,7 +287,7 @@ sage_setup() { echo >&2 '************************************************************************' echo >&2 'It seems that you are attempting to run Sage from an unpacked source' echo >&2 'tarball, but you have not compiled it yet (or maybe the build has not' - echo >&2 'finished). You should run `make` in the Sage root directory first.' + echo >&2 'finished). You should run `make` in the SAGE_ROOT directory first.' echo >&2 'If you did not intend to build Sage from source, you should download' echo >&2 'a binary tarball instead. Read README.txt for more information.' echo >&2 '************************************************************************' @@ -869,7 +869,7 @@ fi # build/bin/sage-site. See #29111. build_sage() { - ( cd "$SAGE_ROOT/build/make" && ./install sagelib-no-deps ) || exit $? + ( cd "$SAGE_ROOT" && make sagelib-no-deps ) || exit $? } if [[ "$1" =~ ^--notebook=.* || "$1" =~ ^-n=.* || "$1" =~ ^-notebook=.* ]] ; then @@ -924,13 +924,13 @@ if [ "$1" = '-ba-force' -o "$1" = '--ba-force' ]; then echo echo "WARNING: 'sage --ba-force' is deprecated; use 'sage -ba' instead." echo - ( cd "$SAGE_ROOT/build/make" && make sagelib-clean ) + ( cd "$SAGE_ROOT" && make sagelib-clean ) build_sage exit $? fi if [ "$1" = '-ba' ]; then - ( cd "$SAGE_ROOT/build/make" && make sagelib-clean ) + ( cd "$SAGE_ROOT" && make sagelib-clean ) build_sage exit $? fi From a6ba6308ff6ba2a867577c6e333973aa5e1f70b3 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 21 Feb 2024 20:42:37 -0800 Subject: [PATCH 360/518] src/setup.py: Use SAGE_NUM_THREADS for cythonizing instead of hardcoded 4 --- src/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/setup.py b/src/setup.py index 0f28b20d6dc..ede23e5efee 100755 --- a/src/setup.py +++ b/src/setup.py @@ -124,7 +124,7 @@ aliases=cython_aliases(), create_extension=create_extension, gdb_debug=gdb_debug, - nthreads=4) + nthreads=sage_build_ext_minimal.get_default_number_build_jobs()) except Exception as exception: log.warn(f"Exception while cythonizing source files: {repr(exception)}") raise From 6e50ba981bbfc446f3c5aac5250f8151ae82eff6 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 21 Feb 2024 21:31:53 -0800 Subject: [PATCH 361/518] src/bin/sage: Handle -b... options before sourcing sage-env --- src/bin/sage | 72 +++++++++++++++++++++++----------------------------- 1 file changed, 32 insertions(+), 40 deletions(-) diff --git a/src/bin/sage b/src/bin/sage index 98d0646c3f0..8ab5144a2e4 100755 --- a/src/bin/sage +++ b/src/bin/sage @@ -216,6 +216,13 @@ if [ "$1" = '-patchbot' -o "$1" = "--patchbot" ]; then exit 127 fi +# build_sage, sage -b, sage -br, etc. could also be moved to +# build/bin/sage-site. See #29111; but OTOH #34627. + +build_sage() { + ( cd "$SAGE_ROOT" && make sagelib-no-deps ) || exit $? +} + # Check for '-i' etc. before sourcing sage-env: running "make" # should be run outside of the Sage shell. case "$1" in @@ -229,6 +236,28 @@ case "$1" in echo >&2 "Error: unknown option: $1" exit 1 ;; + -b) + build_sage + exit 0 + ;; + -br|--br) + build_sage + shift; set -- -r "$@" # delegate to handling of "-r" below, after sourcing sage-env + ;; + -bn|--build-and-notebook) + build_sage + shift; set -- -n "$@" # delegate to handling of "-n" below, after sourcing sage-env + ;; + -ba) + ( cd "$SAGE_ROOT" && make sagelib-clean ) + build_sage + exit 0 + ;; + -bt*) + build_sage + switch_without_b=-${1#-b} + shift; set -- $switch_without_b "$@" # delegate to handling of "-t...." below, after sourcing sage-env + ;; esac ##################################################################### @@ -865,12 +894,6 @@ fi # The notebook, grep, building Sage, testing Sage ##################################################################### -# build_sage, sage -b, sage -br, etc. could be moved to -# build/bin/sage-site. See #29111. - -build_sage() { - ( cd "$SAGE_ROOT" && make sagelib-no-deps ) || exit $? -} if [[ "$1" =~ ^--notebook=.* || "$1" =~ ^-n=.* || "$1" =~ ^-notebook=.* ]] ; then sage-cleaner &>/dev/null & @@ -905,47 +928,19 @@ if [ -n "$SAGE_SRC" -a -d "$SAGE_SRC" ]; then fi fi -if [ "$1" = '-b' ]; then - build_sage - exit $? -fi - -if [ "$1" = '-br' -o "$1" = "--br" ]; then - build_sage - interactive_sage -fi - if [ "$1" = '-r' ]; then shift interactive_sage fi -if [ "$1" = '-ba-force' -o "$1" = '--ba-force' ]; then - echo - echo "WARNING: 'sage --ba-force' is deprecated; use 'sage -ba' instead." - echo - ( cd "$SAGE_ROOT" && make sagelib-clean ) - build_sage - exit $? -fi - -if [ "$1" = '-ba' ]; then - ( cd "$SAGE_ROOT" && make sagelib-clean ) - build_sage - exit $? -fi - exec-runtests() { sage_setup export PYTHONIOENCODING="utf-8" # Fix encoding for doctests exec sage-runtests "$@" } -if [ "$1" = '-t' -o "$1" = '-bt' -o "$1" = '-tp' -o "$1" = '-btp' ]; then - if [ "$1" = '-bt' -o "$1" = '-btp' ]; then - build_sage - fi - if [ "$1" = '-tp' -o "$1" = '-btp' ]; then +if [ "$1" = '-t' -o "$1" = '-tp' ]; then + if [ "$1" = '-tp' ]; then shift exec-runtests -p "$@" else @@ -954,10 +949,7 @@ if [ "$1" = '-t' -o "$1" = '-bt' -o "$1" = '-tp' -o "$1" = '-btp' ]; then fi fi -if [ "$1" = '-tnew' -o "$1" = '-btnew' ]; then - if [ "$1" = '-btnew' ]; then - build_sage - fi +if [ "$1" = '-tnew' ]; then shift exec-runtests --new "$@" fi From 30009fa75dd3bef2b74fcc2355bad9b32745f572 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 21 Feb 2024 21:34:28 -0800 Subject: [PATCH 362/518] src/setup.py: More detailed logging around cythonizing --- src/setup.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/setup.py b/src/setup.py index ede23e5efee..21ea105d977 100755 --- a/src/setup.py +++ b/src/setup.py @@ -85,8 +85,7 @@ from sage_setup.autogen import autogen_all autogen_all() - log.info("Discovering Python/Cython source code....") - t = time.time() + log.info("Discovering Python/Cython source code...") # Exclude a few files if the corresponding distribution is not loaded optional_packages = ['mcqd', 'bliss', 'tdlib', @@ -103,13 +102,19 @@ python_packages = find_namespace_packages(where=SAGE_SRC, include=['sage', 'sage.*']) log.debug(f"python_packages = {python_packages}") - log.info(f"Discovered Python/Cython sources, time: {(time.time() - t):.2f} seconds.") + log.info(f"Discovering Python/Cython source code... done") # from sage_build_cython: import Cython.Compiler.Options Cython.Compiler.Options.embed_pos_in_docstring = True gdb_debug = os.environ.get('SAGE_DEBUG', None) != 'no' + aliases = cython_aliases() + log.debug(f"aliases = {aliases}") + include_path = sage_include_directories(use_sources=True) + ['.'] + log.debug(f"include_path = {include_path}") + nthreads = sage_build_ext_minimal.get_default_number_build_jobs() + log.info(f"Cythonizing with {nthreads} threads...") try: from Cython.Build import cythonize from sage.env import cython_aliases, sage_include_directories @@ -118,16 +123,17 @@ extensions = cythonize( ["sage/**/*.pyx"], exclude=files_to_exclude, - include_path=sage_include_directories(use_sources=True) + ['.'], + include_path=include_path, compile_time_env=compile_time_env_variables(), compiler_directives=compiler_directives(False), - aliases=cython_aliases(), + aliases=aliases, create_extension=create_extension, gdb_debug=gdb_debug, - nthreads=sage_build_ext_minimal.get_default_number_build_jobs()) + nthreads=nthreads) except Exception as exception: log.warn(f"Exception while cythonizing source files: {repr(exception)}") raise + log.info(f"Cythonizing with {nthreads} threads... done") # ######################################################## # ## Distutils From f9ddc324b8dc9488d85ba7cbd05a671a0272f4b4 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 25 Mar 2024 21:20:50 -0700 Subject: [PATCH 363/518] src/sage/modular/quasimodform/ring.py: Fix pycodestyle warning --- src/sage/modular/quasimodform/ring.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/modular/quasimodform/ring.py b/src/sage/modular/quasimodform/ring.py index 53fcea616bd..dcdf8e470c2 100644 --- a/src/sage/modular/quasimodform/ring.py +++ b/src/sage/modular/quasimodform/ring.py @@ -822,6 +822,6 @@ def basis_of_weight(self, weight): basis += [f*E2_pow for f in M.modular_forms_of_weight(weight - 2*j).basis()] E2_pow *= E2 - if not weight%2: + if not weight % 2: basis.append(E2_pow) return basis From 18d2eb707b2dac8d92df2ec71ccf3075d975cc84 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 19 Feb 2024 10:38:43 -0800 Subject: [PATCH 364/518] src/doc/en/developer/coding_basics.rst: Large data files should not be added to the Sage source tree --- src/doc/en/developer/coding_basics.rst | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/doc/en/developer/coding_basics.rst b/src/doc/en/developer/coding_basics.rst index 7471a9d4022..4d2e3b23b9a 100644 --- a/src/doc/en/developer/coding_basics.rst +++ b/src/doc/en/developer/coding_basics.rst @@ -104,7 +104,7 @@ of the directory containing the Sage sources: setup.py ... sage/ # Sage library - ext_data/ # extra Sage resources (formerly src/ext) + ext_data/ # extra Sage resources (legacy) bin/ # the scripts in local/bin that are tracked upstream/ # tarballs of upstream sources local/ # installed binaries @@ -161,6 +161,16 @@ of the following places: os.path.join(os.path.dirname(__file__), 'sage-maxima.lisp') +- Large data files should not be added to the Sage source tree. + Instead: + + - Create a separate git repository for them + - Add metadata in your repository that make it a pip-installable + package (distribution package) + - Upload it to PyPI + - Create metadata in ``SAGE_ROOT/build/pkgs`` that make your new + pip-installable package known to Sage + - In an appropriate subdirectory of ``SAGE_ROOT/src/sage/ext_data/``. (At runtime, it is then available in the directory indicated by ``SAGE_EXTCODE``). For example, if ``file`` is placed in From 894b667cf9763f82fdad5e767e1ddc51fd4a25d6 Mon Sep 17 00:00:00 2001 From: gmou3 <32706872+gmou3@users.noreply.github.com> Date: Tue, 20 Feb 2024 01:04:51 +0200 Subject: [PATCH 365/518] Add compression suggestion and guiding example repos (#26) --- src/doc/en/developer/coding_basics.rst | 43 +++++++++++++++++--------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/src/doc/en/developer/coding_basics.rst b/src/doc/en/developer/coding_basics.rst index 4d2e3b23b9a..7f4fe24d08f 100644 --- a/src/doc/en/developer/coding_basics.rst +++ b/src/doc/en/developer/coding_basics.rst @@ -89,9 +89,9 @@ In particular, Files and directory structure ============================= -Roughly, the Sage directory tree is layout like this. Note that we use -``SAGE_ROOT`` in the following as a shortcut for the (arbitrary) name -of the directory containing the Sage sources: +Roughly, the Sage directory tree is laid out like this. Note that we +use ``SAGE_ROOT`` in the following as a shortcut for the name of the +directory containing the Sage sources: .. CODE-BLOCK:: text @@ -149,8 +149,8 @@ Adding new top-level packages below :mod:`sage` should be done sparingly. It is often better to create subpackages of existing packages. -Non-Python Sage source code and supporting files can be included in one -of the following places: +Non-Python Sage source code and small supporting files can be +included in one of the following places: - In the directory of the Python code that uses that file. When the Sage library is installed, the file will be installed in the same @@ -161,16 +161,6 @@ of the following places: os.path.join(os.path.dirname(__file__), 'sage-maxima.lisp') -- Large data files should not be added to the Sage source tree. - Instead: - - - Create a separate git repository for them - - Add metadata in your repository that make it a pip-installable - package (distribution package) - - Upload it to PyPI - - Create metadata in ``SAGE_ROOT/build/pkgs`` that make your new - pip-installable package known to Sage - - In an appropriate subdirectory of ``SAGE_ROOT/src/sage/ext_data/``. (At runtime, it is then available in the directory indicated by ``SAGE_EXTCODE``). For example, if ``file`` is placed in @@ -184,6 +174,29 @@ the section ``options.package_data`` of the file ``SAGE_ROOT/pkgs/sagemath-standard/setup.cfg.m4`` (or the corresponding file of another distribution). +Large data files should not be added to the Sage source tree. Instead, it +is proposed to do the following: + +- create a separate git repository and upload them there [2]_, + +- add metadata to the repository that make it a pip-installable + package (distribution package), + +- upload it to PyPI, + +- create metadata in ``SAGE_ROOT/build/pkgs`` that make your new + pip-installable package known to Sage. + +For guiding examples of external repositories that host large data +files, see https://github.com/sagemath/conway-polynomials, and +https://github.com/gmou3/matroid-database. + +.. [2] + + It is also suggested that the files are compressed, e.g., through + the command ``xz -e``. They can then be read via a command such as + ``lzma.open(file, 'rt')``. + Learn by copy/paste =================== From e9038c502cbc964abb7ef1ae11825a91046b70af Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 19 Feb 2024 17:24:58 -0800 Subject: [PATCH 366/518] src/doc/en/developer/coding_basics.rst: Add links to packaging instructions --- src/doc/en/developer/coding_basics.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/doc/en/developer/coding_basics.rst b/src/doc/en/developer/coding_basics.rst index 7f4fe24d08f..43ca573b239 100644 --- a/src/doc/en/developer/coding_basics.rst +++ b/src/doc/en/developer/coding_basics.rst @@ -180,12 +180,13 @@ is proposed to do the following: - create a separate git repository and upload them there [2]_, - add metadata to the repository that make it a pip-installable - package (distribution package), + package (distribution package), as explained for example in the + `Python Packaging User Guide `_, -- upload it to PyPI, +- `upload it to PyPI `_, - create metadata in ``SAGE_ROOT/build/pkgs`` that make your new - pip-installable package known to Sage. + pip-installable package known to Sage; see :ref:`chapter-packaging`. For guiding examples of external repositories that host large data files, see https://github.com/sagemath/conway-polynomials, and From 0f74867d40bee3f2b61f51d9f6788264bf1aa958 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 19 Feb 2024 17:40:07 -0800 Subject: [PATCH 367/518] src/doc/en/developer/coding_basics.rst: Mention importlib.resources.files --- src/doc/en/developer/coding_basics.rst | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/doc/en/developer/coding_basics.rst b/src/doc/en/developer/coding_basics.rst index 43ca573b239..5774539b817 100644 --- a/src/doc/en/developer/coding_basics.rst +++ b/src/doc/en/developer/coding_basics.rst @@ -154,12 +154,23 @@ included in one of the following places: - In the directory of the Python code that uses that file. When the Sage library is installed, the file will be installed in the same - location as the Python code. For example, - ``SAGE_ROOT/src/sage/interfaces/maxima.py`` needs to use the file - ``SAGE_ROOT/src/sage/interfaces/maxima.lisp`` at runtime, so it refers - to it as :: + location as the Python code. This is referred to as "package data". - os.path.join(os.path.dirname(__file__), 'sage-maxima.lisp') + The preferred way to access the data from Python is using the + function :func:`importlib.resources.files`. + + .. NOTE:: + + You may notice that some older code in the Sage library accesses + the package data in more direct ways. For example, + ``SAGE_ROOT/src/sage/interfaces/maxima.py`` uses the file + ``SAGE_ROOT/src/sage/interfaces/maxima.lisp`` at runtime, so it + refers to it as:: + + os.path.join(os.path.dirname(__file__), 'sage-maxima.lisp') + + This is no longer recommended, and PRs that update such uses + are welcome. - In an appropriate subdirectory of ``SAGE_ROOT/src/sage/ext_data/``. (At runtime, it is then available in the directory indicated by From 59a5204a757aaf40395882e840ce5a7ed85a812d Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 19 Feb 2024 17:49:48 -0800 Subject: [PATCH 368/518] src/doc/en/developer/coding_basics.rst: Show how to import importlib.resources.files --- src/doc/en/developer/coding_basics.rst | 8 +++++++- src/doc/en/developer/coding_in_python.rst | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/doc/en/developer/coding_basics.rst b/src/doc/en/developer/coding_basics.rst index 5774539b817..4511e6a2fff 100644 --- a/src/doc/en/developer/coding_basics.rst +++ b/src/doc/en/developer/coding_basics.rst @@ -157,7 +157,13 @@ included in one of the following places: location as the Python code. This is referred to as "package data". The preferred way to access the data from Python is using the - function :func:`importlib.resources.files`. + function :func:`importlib.resources.files`. It should be imported + as follows (see :ref:`section-python-language-standard`):: + + try: + from importlib_resources import files + except ImportError: + from importlib.resources import files .. NOTE:: diff --git a/src/doc/en/developer/coding_in_python.rst b/src/doc/en/developer/coding_in_python.rst index 6b3b936662d..fef7a1011f0 100644 --- a/src/doc/en/developer/coding_in_python.rst +++ b/src/doc/en/developer/coding_in_python.rst @@ -7,6 +7,7 @@ Coding in Python for Sage This chapter discusses some issues with, and advice for, coding in Sage. +.. _section-python-language-standard: Python language standard ======================== From 827372e71488a79ee32620edd055fcc1f7683e06 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 19 Feb 2024 18:09:16 -0800 Subject: [PATCH 369/518] src/doc/en/developer/coding_basics.rst: Mention importlib.resources.as_file, add links to tutorial --- src/doc/en/developer/coding_basics.rst | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/doc/en/developer/coding_basics.rst b/src/doc/en/developer/coding_basics.rst index 4511e6a2fff..0dcaa2d092a 100644 --- a/src/doc/en/developer/coding_basics.rst +++ b/src/doc/en/developer/coding_basics.rst @@ -157,14 +157,28 @@ included in one of the following places: location as the Python code. This is referred to as "package data". The preferred way to access the data from Python is using the - function :func:`importlib.resources.files`. It should be imported - as follows (see :ref:`section-python-language-standard`):: + `importlib.resources API `, + in particular the function :func:`importlib.resources.files`. + It should be imported as follows + (see :ref:`section-python-language-standard`):: try: + # Use backport package providing Python 3.11 features from importlib_resources import files except ImportError: from importlib.resources import files + If the file needs to be used outside of Python, then the + preferred way is using the context manager + :func:`importlib.resources.as_file`. It should be imported as + follows:: + + try: + # Use backport package providing Python 3.11 features + from importlib_resources import as_file + except ImportError: + from importlib.resources import as_file + .. NOTE:: You may notice that some older code in the Sage library accesses From a72e2fadfb82fc3a51981dfc00e8b42832762007 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Tornar=C3=ADa?= Date: Mon, 19 Feb 2024 18:37:28 -0800 Subject: [PATCH 370/518] src/doc/en/developer/coding_basics.rst: Add importlib.resources examples --- src/doc/en/developer/coding_basics.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/doc/en/developer/coding_basics.rst b/src/doc/en/developer/coding_basics.rst index 0dcaa2d092a..8ed7071724d 100644 --- a/src/doc/en/developer/coding_basics.rst +++ b/src/doc/en/developer/coding_basics.rst @@ -168,6 +168,15 @@ included in one of the following places: except ImportError: from importlib.resources import files + After this import, you can: + + - open a resource for text reading: `fd = files(package).joinpath(resource).open('rt')` + - open a resource for binary reading: `fd = files(package).joinpath(resource).open('rb')` + - read a resource as text: `text = files(package).joinpath(resource).read_text()` + - read a resource as bytes: `bytes = files(package).joinpath(resource).read_bytes()` + - open a xz resource for text reading: `fd = lzma.open(files(package).joinpath(resource), 'rt')` + - open a xz resource for binary reading: `fd = lzma.open(files(package).joinpath(resource), 'rb')` + If the file needs to be used outside of Python, then the preferred way is using the context manager :func:`importlib.resources.as_file`. It should be imported as From bab3d03ad9a6bf04bea4d285efb4bee82c6f10e3 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 20 Feb 2024 08:53:29 -0800 Subject: [PATCH 371/518] src/doc/en/developer/coding_basics.rst: Use sys.version_info instead of try..except --- src/doc/en/developer/coding_basics.rst | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/doc/en/developer/coding_basics.rst b/src/doc/en/developer/coding_basics.rst index 8ed7071724d..8936cea7a5d 100644 --- a/src/doc/en/developer/coding_basics.rst +++ b/src/doc/en/developer/coding_basics.rst @@ -162,10 +162,12 @@ included in one of the following places: It should be imported as follows (see :ref:`section-python-language-standard`):: - try: + import sys + + if sys.version_info < (3, 11): # Use backport package providing Python 3.11 features from importlib_resources import files - except ImportError: + else: from importlib.resources import files After this import, you can: @@ -179,14 +181,8 @@ included in one of the following places: If the file needs to be used outside of Python, then the preferred way is using the context manager - :func:`importlib.resources.as_file`. It should be imported as - follows:: - - try: - # Use backport package providing Python 3.11 features - from importlib_resources import as_file - except ImportError: - from importlib.resources import as_file + :func:`importlib.resources.as_file`. It should be imported in the + same way as shown above. .. NOTE:: From 515b4b36bb2bc92768d72ea5ecb206606628ef1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20K=C3=B6ppe?= Date: Tue, 20 Feb 2024 09:05:00 -0800 Subject: [PATCH 372/518] src/doc/en/developer/coding_basics.rst: Details on resource `open` examples MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Gonzalo Tornaría --- src/doc/en/developer/coding_basics.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/doc/en/developer/coding_basics.rst b/src/doc/en/developer/coding_basics.rst index 8936cea7a5d..ee39c463196 100644 --- a/src/doc/en/developer/coding_basics.rst +++ b/src/doc/en/developer/coding_basics.rst @@ -176,8 +176,8 @@ included in one of the following places: - open a resource for binary reading: `fd = files(package).joinpath(resource).open('rb')` - read a resource as text: `text = files(package).joinpath(resource).read_text()` - read a resource as bytes: `bytes = files(package).joinpath(resource).read_bytes()` - - open a xz resource for text reading: `fd = lzma.open(files(package).joinpath(resource), 'rt')` - - open a xz resource for binary reading: `fd = lzma.open(files(package).joinpath(resource), 'rb')` + - open a xz resource for text reading: `fd = lzma.open(files(package).joinpath(resource).open('rb'), 'rt')` + - open a xz resource for binary reading: `fd = lzma.open(files(package).joinpath(resource).open('rb'), 'rb')` If the file needs to be used outside of Python, then the preferred way is using the context manager From 421c37fc2f04446a397c7ce7a3e60ab47e719b99 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 26 Feb 2024 15:15:57 -0800 Subject: [PATCH 373/518] src/doc/en/developer/coding_basics.rst: Don't encourage PRs that update direct access to data files using __file__ for now --- src/doc/en/developer/coding_basics.rst | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/doc/en/developer/coding_basics.rst b/src/doc/en/developer/coding_basics.rst index ee39c463196..cec72b8add5 100644 --- a/src/doc/en/developer/coding_basics.rst +++ b/src/doc/en/developer/coding_basics.rst @@ -184,18 +184,13 @@ included in one of the following places: :func:`importlib.resources.as_file`. It should be imported in the same way as shown above. - .. NOTE:: - - You may notice that some older code in the Sage library accesses - the package data in more direct ways. For example, - ``SAGE_ROOT/src/sage/interfaces/maxima.py`` uses the file - ``SAGE_ROOT/src/sage/interfaces/maxima.lisp`` at runtime, so it - refers to it as:: - - os.path.join(os.path.dirname(__file__), 'sage-maxima.lisp') +- Older code in the Sage library accesses + the package data in more direct ways. For example, + ``SAGE_ROOT/src/sage/interfaces/maxima.py`` uses the file + ``SAGE_ROOT/src/sage/interfaces/maxima.lisp`` at runtime, so it + refers to it as:: - This is no longer recommended, and PRs that update such uses - are welcome. + os.path.join(os.path.dirname(__file__), 'sage-maxima.lisp') - In an appropriate subdirectory of ``SAGE_ROOT/src/sage/ext_data/``. (At runtime, it is then available in the directory indicated by From 0c28f6f05aedb6fa110481a9a94cbc24ac350783 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 26 Feb 2024 15:16:37 -0800 Subject: [PATCH 374/518] src/doc/en/developer/coding_basics.rst: Link to https://github.com/sagemath/sage/issues/33037 for SAGE_EXTCODE deprecation --- src/doc/en/developer/coding_basics.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/doc/en/developer/coding_basics.rst b/src/doc/en/developer/coding_basics.rst index cec72b8add5..3bc8457b646 100644 --- a/src/doc/en/developer/coding_basics.rst +++ b/src/doc/en/developer/coding_basics.rst @@ -200,7 +200,9 @@ included in one of the following places: from sage.env import SAGE_EXTCODE file = os.path.join(SAGE_EXTCODE, 'directory', 'file') -In both cases, the files must be listed (explicitly or via wildcards) in + This practice is deprecated, see :issue:`33037`. + +In all cases, the files must be listed (explicitly or via wildcards) in the section ``options.package_data`` of the file ``SAGE_ROOT/pkgs/sagemath-standard/setup.cfg.m4`` (or the corresponding file of another distribution). From 81741c5c102f45d05060296b9ee52e240f6b9e22 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 16 Mar 2024 12:15:40 -0700 Subject: [PATCH 375/518] src/doc/en/developer/coding_basics.rst: Remove advice on how to import from importlib.resources, fix markup --- src/doc/en/developer/coding_basics.rst | 27 ++++++++------------------ 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/src/doc/en/developer/coding_basics.rst b/src/doc/en/developer/coding_basics.rst index 3bc8457b646..a1f00905869 100644 --- a/src/doc/en/developer/coding_basics.rst +++ b/src/doc/en/developer/coding_basics.rst @@ -159,25 +159,14 @@ included in one of the following places: The preferred way to access the data from Python is using the `importlib.resources API `, in particular the function :func:`importlib.resources.files`. - It should be imported as follows - (see :ref:`section-python-language-standard`):: - - import sys - - if sys.version_info < (3, 11): - # Use backport package providing Python 3.11 features - from importlib_resources import files - else: - from importlib.resources import files - - After this import, you can: - - - open a resource for text reading: `fd = files(package).joinpath(resource).open('rt')` - - open a resource for binary reading: `fd = files(package).joinpath(resource).open('rb')` - - read a resource as text: `text = files(package).joinpath(resource).read_text()` - - read a resource as bytes: `bytes = files(package).joinpath(resource).read_bytes()` - - open a xz resource for text reading: `fd = lzma.open(files(package).joinpath(resource).open('rb'), 'rt')` - - open a xz resource for binary reading: `fd = lzma.open(files(package).joinpath(resource).open('rb'), 'rb')` + Using it, you can: + + - open a resource for text reading: ``fd = files(package).joinpath(resource).open('rt')`` + - open a resource for binary reading: ``fd = files(package).joinpath(resource).open('rb')`` + - read a resource as text: ``text = files(package).joinpath(resource).read_text()`` + - read a resource as bytes: ``bytes = files(package).joinpath(resource).read_bytes()`` + - open an xz-compressed resource for text reading: ``fd = lzma.open(files(package).joinpath(resource).open('rb'), 'rt')`` + - open an xz-compressed resource for binary reading: ``fd = lzma.open(files(package).joinpath(resource).open('rb'), 'rb')`` If the file needs to be used outside of Python, then the preferred way is using the context manager From d3a4313f026985e4d4cce01db169f386b5a1e042 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 22 Mar 2024 14:19:10 -0700 Subject: [PATCH 376/518] src/doc/en/developer/coding_basics.rst: Fix markup, break long lines --- src/doc/en/developer/coding_basics.rst | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/doc/en/developer/coding_basics.rst b/src/doc/en/developer/coding_basics.rst index a1f00905869..00844de6f4a 100644 --- a/src/doc/en/developer/coding_basics.rst +++ b/src/doc/en/developer/coding_basics.rst @@ -157,7 +157,8 @@ included in one of the following places: location as the Python code. This is referred to as "package data". The preferred way to access the data from Python is using the - `importlib.resources API `, + `importlib.resources API + `_, in particular the function :func:`importlib.resources.files`. Using it, you can: @@ -203,9 +204,11 @@ is proposed to do the following: - add metadata to the repository that make it a pip-installable package (distribution package), as explained for example in the - `Python Packaging User Guide `_, + `Python Packaging User Guide + `_, -- `upload it to PyPI `_, +- `upload it to PyPI + `_, - create metadata in ``SAGE_ROOT/build/pkgs`` that make your new pip-installable package known to Sage; see :ref:`chapter-packaging`. From f5d37017e0dd948c8b45a4a8b8313e7b7767dac2 Mon Sep 17 00:00:00 2001 From: Giorgos Mousa Date: Tue, 26 Mar 2024 11:46:17 +0200 Subject: [PATCH 377/518] Docsting suggestions by mkoeppe --- src/sage/matroids/circuits_matroid.pyx | 6 ++-- src/sage/matroids/graphic_matroid.py | 8 +++--- src/sage/matroids/matroid.pyx | 39 +++++++++++++------------- 3 files changed, 26 insertions(+), 27 deletions(-) diff --git a/src/sage/matroids/circuits_matroid.pyx b/src/sage/matroids/circuits_matroid.pyx index 77744a2b888..c42e03f24f8 100644 --- a/src/sage/matroids/circuits_matroid.pyx +++ b/src/sage/matroids/circuits_matroid.pyx @@ -419,7 +419,7 @@ cdef class CircuitsMatroid(Matroid): r""" Return the bases of the matroid. - OUTPUT: a SetSystem + OUTPUT: a :class:`SetSystem` EXAMPLES:: @@ -478,7 +478,7 @@ cdef class CircuitsMatroid(Matroid): - ``k`` -- an integer (optional); the length of the circuits - OUTPUT: a SetSystem + OUTPUT: a :class:`SetSystem` EXAMPLES:: @@ -541,7 +541,7 @@ cdef class CircuitsMatroid(Matroid): """ Return the nonspanning circuits of the matroid. - OUTPUT: a SetSystem + OUTPUT: a :class:`SetSystem` EXAMPLES:: diff --git a/src/sage/matroids/graphic_matroid.py b/src/sage/matroids/graphic_matroid.py index e0d0b792f0a..57bbae15f22 100644 --- a/src/sage/matroids/graphic_matroid.py +++ b/src/sage/matroids/graphic_matroid.py @@ -738,7 +738,7 @@ def _closure(self, X): - ``X`` -- an iterable container of ground set elements - OUTPUT: ``frozenset`` instance containing a subset of the groundset + OUTPUT: a subset of the groundset as a :class:`frozenset` EXAMPLES:: @@ -789,7 +789,7 @@ def _max_independent(self, X): - ``X`` -- An object with Python's ``frozenset`` interface containing a subset of ``self.groundset()`` - OUTPUT: ``frozenset`` instance containing a subset of the groundset + OUTPUT: a subset of the groundset as a :class:`frozenset` EXAMPLES:: @@ -828,7 +828,7 @@ def _max_coindependent(self, X): - ``X`` -- an iterable container of ground set elements - OUTPUT: ``frozenset`` instance containing a subset of the groundset + OUTPUT: a subset of the groundset as a :class:`frozenset` EXAMPLES:: @@ -942,7 +942,7 @@ def _coclosure(self, X): - ``X`` -- an iterable container of ground set elements - OUTPUT: ``frozenset`` instance containing a subset of the groundset + OUTPUT: a subset of the groundset as a :class:`frozenset` EXAMPLES:: diff --git a/src/sage/matroids/matroid.pyx b/src/sage/matroids/matroid.pyx index f9b3549272e..08906e02412 100644 --- a/src/sage/matroids/matroid.pyx +++ b/src/sage/matroids/matroid.pyx @@ -531,7 +531,7 @@ cdef class Matroid(SageObject): - ``X`` -- An object with Python's ``frozenset`` interface containing a subset of ``self.groundset()``. - OUTPUT: ``frozenset`` instance containing a subset of the groundset + OUTPUT: a subset of the groundset as a :class:`frozenset` EXAMPLES:: @@ -620,7 +620,7 @@ cdef class Matroid(SageObject): - ``X`` -- An object with Python's ``frozenset`` interface containing a subset of ``self.groundset()``. - OUTPUT: ``frozenset`` instance containing a subset of the groundset + OUTPUT: a subset of the groundset as a :class:`frozenset` EXAMPLES:: @@ -665,7 +665,7 @@ cdef class Matroid(SageObject): - ``X`` -- An object with Python's ``frozenset`` interface containing a subset of ``self.groundset()``. - OUTPUT: ``frozenset`` instance containing a subset of the groundset + OUTPUT: a subset of the groundset as a :class:`frozenset` EXAMPLES:: @@ -754,7 +754,7 @@ cdef class Matroid(SageObject): - ``X`` -- An object with Python's ``frozenset`` interface containing a subset of ``self.groundset()``. - OUTPUT: ``frozenset`` instance containing a subset of the groundset + OUTPUT: a subset of the groundset as a :class:`frozenset` EXAMPLES:: @@ -785,7 +785,7 @@ cdef class Matroid(SageObject): - ``Y`` -- An object with Python's ``frozenset`` interface containing a subset of ``self.groundset()``, and disjoint from ``X``. - OUTPUT: ``frozenset`` instance containing a subset of the groundset + OUTPUT: a subset of the groundset as a :class:`frozenset` EXAMPLES:: @@ -2274,7 +2274,7 @@ cdef class Matroid(SageObject): """ Return the circuits of the matroid. - OUTPUT: a SetSystem + OUTPUT: a :class:`SetSystem` .. SEEALSO:: @@ -2336,7 +2336,7 @@ cdef class Matroid(SageObject): A *nonspanning circuit* is a circuit whose rank is strictly smaller than the rank of the matroid. - OUTPUT: a SetSystem + OUTPUT: a :class:`SetSystem` .. SEEALSO:: @@ -2388,7 +2388,7 @@ cdef class Matroid(SageObject): """ Return the cocircuits of the matroid. - OUTPUT: a SetSystem + OUTPUT: a :class:`SetSystem` .. SEEALSO:: @@ -2414,7 +2414,7 @@ cdef class Matroid(SageObject): A *noncospanning cocircuit* is a cocircuit whose corank is strictly smaller than the corank of the matroid. - OUTPUT: a SetSystem + OUTPUT: a :class:`SetSystem` .. SEEALSO:: @@ -2503,7 +2503,7 @@ cdef class Matroid(SageObject): A *nonbasis* is a set with cardinality ``self.full_rank()`` that is not a basis. - OUTPUT: a SetSystem + OUTPUT: a :class:`SetSystem` .. SEEALSO:: @@ -2617,7 +2617,7 @@ cdef class Matroid(SageObject): A *basis* is a maximal independent set. - OUTPUT: a SetSystem + OUTPUT: a :class:`SetSystem` EXAMPLES:: @@ -2884,7 +2884,7 @@ cdef class Matroid(SageObject): - ``r`` -- A natural number. - OUTPUT: a SetSystem + OUTPUT: a :class:`SetSystem` .. SEEALSO:: @@ -2910,7 +2910,7 @@ cdef class Matroid(SageObject): - ``r`` -- a nonnegative integer - OUTPUT: a SetSystem + OUTPUT: a :class:`SetSystem` .. SEEALSO:: @@ -3248,7 +3248,7 @@ cdef class Matroid(SageObject): - ``invariant`` -- (optional) either a semigroup ``G`` whose ``__call__`` acts on the groundset, or pair ``(G, action)`` where ``G`` is a semigroup and ``action`` is a function ``action(g,e)`` - which takes a pair of a group element and a grounset element and + which takes a pair of a group element and a groundset element and returns the groundset element which is the result of ``e`` acted upon by ``g`` @@ -5419,7 +5419,7 @@ cdef class Matroid(SageObject): - ``None`` -- The most appropriate algorithm is chosen automatically. - ``"intersection"`` -- an algorithm based on matroid intersection, equivalent - to calling ``is_kconnected(4,certificate)``. + to calling ``is_kconnected(4, certificate)``. - ``"shifting"`` -- an algorithm based on the shifting algorithm [Raj1987]_. OUTPUT: boolean, or a tuple ``(boolean, frozenset)`` @@ -5782,8 +5782,8 @@ cdef class Matroid(SageObject): OUTPUT: - - `False, None` -- if there is no ``m``-separator - - `True, E` -- if there exist an ``m``-separator ``E`` + - ``False, None`` -- if there is no ``m``-separator + - ``True, E`` -- if there exists an ``m``-separator ``E`` EXAMPLES:: @@ -6196,7 +6196,7 @@ cdef class Matroid(SageObject): ``False``, any output will represent ``self`` if and only if the matroid is binary - OUTPUT: either a ``BinaryMatroid``, or ``None`` + OUTPUT: either a :class:`BinaryMatroid`, or ``None`` ALGORITHM: @@ -7765,7 +7765,6 @@ cdef class Matroid(SageObject): :meth:`~sage.matroids.matroid.Matroid.whitney_numbers` - TESTS:: sage: M = Matroid(groundset=[0,1,2], circuits=[[0]]) @@ -7774,7 +7773,7 @@ cdef class Matroid(SageObject): sage: l = -1 sage: for M in matroids.AllMatroids(6): # optional - matroid_database ....: r = M.rank() - ....: assert M.characteristic_polynomial(l) == (-1)**r * M.tutte_polynomial(1-l, 0) + ....: assert M.characteristic_polynomial(l) == (-1)**r * M.tutte_polynomial(1 - l, 0) ....: if not M.loops(): ....: assert (-1)**r * M.characteristic_polynomial(l) == sum(M.broken_circuit_complex().f_vector()) """ From d4659d2bba497b34dc9b7fbb32826bea2e3fd3c2 Mon Sep 17 00:00:00 2001 From: Giorgos Mousa Date: Tue, 26 Mar 2024 12:05:49 +0200 Subject: [PATCH 378/518] Remove copy and deepcopy TODO: add FlatsMatroid copy tests in matroid.pyx after #37670 --- src/sage/matroids/advanced.py | 2 ++ src/sage/matroids/flats_matroid.pyx | 36 ----------------------------- 2 files changed, 2 insertions(+), 36 deletions(-) diff --git a/src/sage/matroids/advanced.py b/src/sage/matroids/advanced.py index b38075bb39c..d290aca2060 100644 --- a/src/sage/matroids/advanced.py +++ b/src/sage/matroids/advanced.py @@ -15,6 +15,7 @@ - :class:`RankMatroid ` - :class:`CircuitClosuresMatroid ` - :class:`BasisMatroid ` + - :class:`FlatsMatroid ` - :class:`LinearMatroid ` - :class:`RegularMatroid ` - :class:`BinaryMatroid ` @@ -54,6 +55,7 @@ from .rank_matroid import RankMatroid from .circuit_closures_matroid import CircuitClosuresMatroid from .basis_matroid import BasisMatroid +from .flats_matroid import FlatsMatroid from .linear_matroid import LinearMatroid, RegularMatroid, BinaryMatroid, TernaryMatroid, QuaternaryMatroid from .utilities import setprint, newlabel, get_nonisomorphic_matroids, lift_cross_ratios, lift_map from . import lean_matrix diff --git a/src/sage/matroids/flats_matroid.pyx b/src/sage/matroids/flats_matroid.pyx index 0629d987dd1..ae95455bb2f 100644 --- a/src/sage/matroids/flats_matroid.pyx +++ b/src/sage/matroids/flats_matroid.pyx @@ -272,42 +272,6 @@ cdef class FlatsMatroid(Matroid): # copying, loading, saving - def __copy__(self): - """ - Create a shallow copy. - - EXAMPLES:: - - sage: from sage.matroids.flats_matroid import FlatsMatroid - sage: M = FlatsMatroid(matroids.catalog.Vamos()) - sage: N = copy(M) # indirect doctest - sage: M == N - True - sage: M.groundset() is N.groundset() - True - """ - return self - - def __deepcopy__(self, memo=None): - """ - Create a deep copy. - - .. NOTE:: - - Since matroids are immutable, a shallow copy normally suffices. - - EXAMPLES:: - - sage: from sage.matroids.flats_matroid import FlatsMatroid - sage: M = FlatsMatroid(matroids.catalog.Vamos()) - sage: N = deepcopy(M) # indirect doctest - sage: M == N - True - sage: M.groundset() is N.groundset() - True - """ - return self - def __reduce__(self): """ Save the matroid for later reloading. From df6609eae470c2601cff2b30647d7ee618ebe5bc Mon Sep 17 00:00:00 2001 From: Dima Pasechnik Date: Tue, 26 Mar 2024 10:35:58 +0000 Subject: [PATCH 379/518] upgrade msolve to 0.6.5 --- build/pkgs/msolve/checksums.ini | 8 +-- build/pkgs/msolve/package-version.txt | 2 +- .../0001-Make-msolve-build-with-flint3.patch | 53 ------------------- 3 files changed, 5 insertions(+), 58 deletions(-) delete mode 100644 build/pkgs/msolve/patches/0001-Make-msolve-build-with-flint3.patch diff --git a/build/pkgs/msolve/checksums.ini b/build/pkgs/msolve/checksums.ini index ae12f77abc9..5b8cb2b9a6e 100644 --- a/build/pkgs/msolve/checksums.ini +++ b/build/pkgs/msolve/checksums.ini @@ -1,5 +1,5 @@ tarball=msolve-VERSION.tar.gz -sha1=bfd1d4f2e5dc0eb321592b3add6665a9d3eadf8c -md5=33a16c21ea8dea9e796d40f1dfd52fa9 -cksum=117017965 -upstream_url=https://github.com/algebraic-solving/msolve/releases/download/vVERSION/msolve-VERSION.tar.gz +sha1=01b7c4b7b6a7df5051c1357e69d9b1fd381d2907 +md5=b45cdaa5a0e588a5382660ce1c245e65 +cksum=3873680457 +upstream_url=https://msolve.lip6.fr/downloads/vVERSION/msolve-VERSION.tar.gz diff --git a/build/pkgs/msolve/package-version.txt b/build/pkgs/msolve/package-version.txt index 8f0916f768f..ef5e4454454 100644 --- a/build/pkgs/msolve/package-version.txt +++ b/build/pkgs/msolve/package-version.txt @@ -1 +1 @@ -0.5.0 +0.6.5 diff --git a/build/pkgs/msolve/patches/0001-Make-msolve-build-with-flint3.patch b/build/pkgs/msolve/patches/0001-Make-msolve-build-with-flint3.patch deleted file mode 100644 index 27a642a13b0..00000000000 --- a/build/pkgs/msolve/patches/0001-Make-msolve-build-with-flint3.patch +++ /dev/null @@ -1,53 +0,0 @@ -From fe730579476de0b2d4181a38efa7f63dff9c81d7 Mon Sep 17 00:00:00 2001 -From: Marc Mezzarobba -Date: Tue, 12 Sep 2023 08:23:08 +0200 -Subject: [PATCH] Make msolve build with flint3 - ---- - src/fglm/berlekamp_massey.c | 3 +++ - src/fglm/data_fglm.c | 7 +++++-- - 2 files changed, 8 insertions(+), 2 deletions(-) - -diff --git a/src/fglm/berlekamp_massey.c b/src/fglm/berlekamp_massey.c -index b0f2052..998af1c 100644 ---- a/src/fglm/berlekamp_massey.c -+++ b/src/fglm/berlekamp_massey.c -@@ -30,6 +30,9 @@ - */ - - #include -+#if __FLINT_VERSION >= 3 -+# include -+#endif - //#include "nmod_poly.h" - //#include "mpn_extras.h" - -diff --git a/src/fglm/data_fglm.c b/src/fglm/data_fglm.c -index 0726760..0e1da6f 100644 ---- a/src/fglm/data_fglm.c -+++ b/src/fglm/data_fglm.c -@@ -24,6 +24,7 @@ - #include - #include - #include -+#include - - - typedef uint32_t szmat_t; -@@ -299,9 +300,11 @@ static inline void nmod_poly_set_prime(nmod_poly_t poly, - mp_limb_t ninv = n_preinvert_limb(prime); - poly->mod.n = prime; - poly->mod.ninv = ninv; -+#if __FLINT_VERSION < 3 - count_leading_zeros(poly->mod.norm, prime); -- /* poly->mod.norm = flint_clz(prime); */ -- -+#else -+ poly->mod.norm = flint_clz(prime); -+#endif - } - - static inline void fglm_param_set_prime(param_t *param, mp_limb_t prime){ --- -2.40.1 - From 5fb67176c70433bc5918b1959d93e833ad804907 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Tue, 26 Mar 2024 12:01:49 +0100 Subject: [PATCH 380/518] some pep8 and ruff cleanups in modules/ --- src/sage/modules/all.py | 2 +- src/sage/modules/diamond_cutting.py | 2 +- src/sage/modules/fg_pid/fgp_element.py | 6 ++-- src/sage/modules/fg_pid/fgp_morphism.py | 14 ++++---- src/sage/modules/fp_graded/morphism.py | 7 ++-- src/sage/modules/free_module_morphism.py | 11 +++--- ...free_quadratic_module_integer_symmetric.py | 35 ++++++++++--------- src/sage/modules/quotient_module.py | 3 +- src/sage/modules/tensor_operations.py | 12 ++----- src/sage/modules/torsion_quadratic_module.py | 11 +++--- .../modules/vector_callable_symbolic_dense.py | 9 ++--- src/sage/modules/vector_symbolic_dense.py | 6 ++-- src/sage/modules/vector_symbolic_sparse.py | 6 ++-- src/sage/modules/with_basis/cell_module.py | 14 ++++---- .../modules/with_basis/indexed_element.pyx | 2 +- src/sage/modules/with_basis/invariant.py | 10 +++--- src/sage/modules/with_basis/morphism.py | 21 ++++++----- src/sage/modules/with_basis/representation.py | 12 ++++--- src/sage/modules/with_basis/subquotient.py | 6 ++-- 19 files changed, 100 insertions(+), 89 deletions(-) diff --git a/src/sage/modules/all.py b/src/sage/modules/all.py index 660f1ef3807..929d16b79d0 100644 --- a/src/sage/modules/all.py +++ b/src/sage/modules/all.py @@ -10,7 +10,7 @@ # # The full text of the GPL is available at: # -# http://www.gnu.org/licenses/ +# https://www.gnu.org/licenses/ # ***************************************************************************** from sage.modules.free_module import FreeModule, VectorSpace, span diff --git a/src/sage/modules/diamond_cutting.py b/src/sage/modules/diamond_cutting.py index 786a783fabf..14adddd4b54 100644 --- a/src/sage/modules/diamond_cutting.py +++ b/src/sage/modules/diamond_cutting.py @@ -260,7 +260,7 @@ def calculate_voronoi_cell(basis, radius=None, verbose=False): if dim[0] < dim[1]: # introduce "artificial" basis points (representing infinity) def approx_norm(v): - r,r1 = (v.inner_product(v)).sqrtrem() + r, r1 = (v.inner_product(v)).sqrtrem() return r + (r1 > 0) artificial_length = max(approx_norm(v) for v in basis) * 2 additional_vectors = identity_matrix(dim[1]) * artificial_length diff --git a/src/sage/modules/fg_pid/fgp_element.py b/src/sage/modules/fg_pid/fgp_element.py index dbb13dadbad..9152c09d187 100644 --- a/src/sage/modules/fg_pid/fgp_element.py +++ b/src/sage/modules/fg_pid/fgp_element.py @@ -445,10 +445,10 @@ def additive_order(self): from sage.rings.integer import Integer from sage.arith.functions import lcm n = Integer(1) - for i, a in enumerate(I): + for vi, a in zip(v, I): if a == 0: - if v[i] != 0: + if vi != 0: return infinity else: - n = lcm(n, Mod(v[i],a).additive_order()) + n = lcm(n, Mod(vi, a).additive_order()) return n diff --git a/src/sage/modules/fg_pid/fgp_morphism.py b/src/sage/modules/fg_pid/fgp_morphism.py index d1c8b221994..c3b5374f706 100644 --- a/src/sage/modules/fg_pid/fgp_morphism.py +++ b/src/sage/modules/fg_pid/fgp_morphism.py @@ -17,13 +17,16 @@ # # The full text of the GPL is available at: # -# http://www.gnu.org/licenses/ +# https://www.gnu.org/licenses/ # ************************************************************************* from sage.categories.morphism import Morphism, is_Morphism from .fgp_module import DEBUG from sage.structure.richcmp import richcmp, op_NE from sage.misc.cachefunc import cached_method +from sage.categories.homset import Homset +import sage.misc.weak_dict + class FGP_Morphism(Morphism): """ @@ -93,7 +96,7 @@ def __init__(self, parent, phi, check=True): if phi.parent() != parent: raise TypeError phi = phi._phi - check = False # no need + check = False # no need # input: phi is a morphism from MO = M.optimized().V() to N.V() # that sends MO.W() to N.W() @@ -443,10 +446,10 @@ def lift(self, x): # Write back in terms of rows of B, and delete rows not corresponding to A, # since those corresponding to relations - v = (z*U)[:A.nrows()] + v = (z * U)[:A.nrows()] # Take the linear combination that v defines. - y = v*self.domain().optimized()[0].V().basis_matrix() + y = v * self.domain().optimized()[0].V().basis_matrix() # Return the finitely generated module element defined by y. y = self.domain()(y) @@ -454,9 +457,6 @@ def lift(self, x): return y -from sage.categories.homset import Homset - -import sage.misc.weak_dict _fgp_homset = sage.misc.weak_dict.WeakValueDictionary() diff --git a/src/sage/modules/fp_graded/morphism.py b/src/sage/modules/fp_graded/morphism.py index 92c2753bddf..219058b4af8 100755 --- a/src/sage/modules/fp_graded/morphism.py +++ b/src/sage/modules/fp_graded/morphism.py @@ -292,10 +292,9 @@ def change_ring(self, algebra): """ new_codomain = self.codomain().change_ring(algebra) # We have to change the ring for the values, too: - new_values = [] - for v in self._values: - new_values.append(new_codomain([algebra(a) - for a in v.dense_coefficient_list()])) + new_values = [new_codomain([algebra(a) + for a in v.dense_coefficient_list()]) + for v in self._values] return Hom(self.domain().change_ring(algebra), new_codomain)(new_values) def degree(self): diff --git a/src/sage/modules/free_module_morphism.py b/src/sage/modules/free_module_morphism.py index 3c84527f8b7..497af831d61 100644 --- a/src/sage/modules/free_module_morphism.py +++ b/src/sage/modules/free_module_morphism.py @@ -360,7 +360,7 @@ def inverse_image(self, V): # A and explicitly represents each element in this basis # as the image of some element of the domain (the rows of # U give these elements of the domain). - H, U = A.hermite_form(transformation=True,include_zero_rows=False) + H, U = A.hermite_form(transformation=True, include_zero_rows=False) # 2. Next we find the unique solution to the equation # Y*H = B. This writes each basis element of V in @@ -462,7 +462,7 @@ def lift(self, x): # see inverse_image for similar code but with comments if not hasattr(A, 'hermite_form'): raise NotImplementedError("base ring (%s) must have hermite_form algorithm in order to compute inverse image" % R) - H, U = A.hermite_form(transformation=True,include_zero_rows=False) + H, U = A.hermite_form(transformation=True, include_zero_rows=False) Y = H.solve_left(vector(self.codomain().coordinates(x))) C = Y*U try: @@ -562,7 +562,7 @@ def eigenvectors(self, extend=True): for i in seigenvec: V = self.domain().base_extend(i[0].parent()) svectors = Sequence([V(j * V.basis_matrix()) for j in i[1]], cr=True) - resu.append((i[0],svectors,i[2])) + resu.append((i[0], svectors, i[2])) return resu else: raise TypeError("not an endomorphism") @@ -635,7 +635,7 @@ def eigenspaces(self, extend=True): return [(vec[0], Sequence(vec[1]).universe().subspace(vec[1])) for vec in ev] - def minimal_polynomial(self,var='x'): + def minimal_polynomial(self, var='x'): r""" Computes the minimal polynomial. @@ -684,6 +684,7 @@ def minimal_polynomial(self,var='x'): minpoly = minimal_polynomial + class BaseIsomorphism1D(Morphism): """ An isomorphism between a ring and a free rank-1 module over the ring. @@ -745,6 +746,7 @@ def _richcmp_(self, other, op): else: return rich_to_bool(op, 1) + class BaseIsomorphism1D_to_FM(BaseIsomorphism1D): """ An isomorphism from a ring to its 1-dimensional free module @@ -801,6 +803,7 @@ def _call_(self, x): x *= self._basis return self.codomain()([x]) + class BaseIsomorphism1D_from_FM(BaseIsomorphism1D): """ An isomorphism to a ring from its 1-dimensional free module diff --git a/src/sage/modules/free_quadratic_module_integer_symmetric.py b/src/sage/modules/free_quadratic_module_integer_symmetric.py index cac77eda355..bdb26f7b304 100644 --- a/src/sage/modules/free_quadratic_module_integer_symmetric.py +++ b/src/sage/modules/free_quadratic_module_integer_symmetric.py @@ -67,6 +67,7 @@ # ############################################################################### + def IntegralLattice(data, basis=None): r""" Return the integral lattice spanned by ``basis`` in the ambient space. @@ -237,7 +238,7 @@ def IntegralLattice(data, basis=None): elif isinstance(data, Integer): inner_product_matrix = matrix.identity(ZZ, data) elif data == "U" or data == "H": - inner_product_matrix = matrix([[0,1],[1,0]]) + inner_product_matrix = matrix([[0, 1], [1, 0]]) else: from sage.combinat.root_system.cartan_matrix import CartanMatrix inner_product_matrix = CartanMatrix(data) @@ -255,6 +256,7 @@ def IntegralLattice(data, basis=None): inner_product_matrix=A.inner_product_matrix(), already_echelonized=False) + def IntegralLatticeDirectSum(Lattices, return_embeddings=False): r""" Return the direct sum of the lattices contained in the list ``Lattices``. @@ -350,7 +352,7 @@ def IntegralLatticeDirectSum(Lattices, return_embeddings=False): basis = [matrix.block(1, 3, [matrix.zero(dims[i], sum_degree[i]), Lattices[i].basis_matrix(), matrix.zero(dims[i], sum_degree[-1] - sum_degree[i+1]) - ]) for i in range(N)] + ]) for i in range(N)] basis_matrix = matrix.block(N, 1, basis) ipm = ambient.inner_product_matrix() direct_sum = FreeQuadraticModule_integer_symmetric(ambient=ambient, @@ -618,6 +620,7 @@ def IntegralLatticeGluing(Lattices, glue, return_embeddings=False): # ############################################################################### + class FreeQuadraticModule_integer_symmetric(FreeQuadraticModule_submodule_with_basis_pid): r""" This class represents non-degenerate, integral, @@ -650,19 +653,19 @@ def __init__(self, ambient, basis, inner_product_matrix, sage: TestSuite(L).run() """ FreeQuadraticModule_submodule_with_basis_pid.__init__( - self, - ambient, - basis, - inner_product_matrix, - check=check, - already_echelonized=already_echelonized) + self, + ambient, + basis, + inner_product_matrix, + check=check, + already_echelonized=already_echelonized) if self.determinant() == 0: raise ValueError("lattices must be nondegenerate; " - "use FreeQuadraticModule instead") + "use FreeQuadraticModule instead") if self.gram_matrix().base_ring() is not ZZ: if self.gram_matrix().denominator() != 1: raise ValueError("lattices must be integral; " - "use FreeQuadraticModule instead") + "use FreeQuadraticModule instead") def _mul_(self, other, switch_sides=False): r""" @@ -717,7 +720,7 @@ def _repr_(self): else: s += "Lattice " s += "of degree %s and rank %s over %s\n" % ( - self.degree(), self.rank(), self.base_ring()) + self.degree(), self.rank(), self.base_ring()) if self.basis_matrix().is_one(): s += "Standard basis \n" else: @@ -887,14 +890,14 @@ def direct_sum(self, M): smzero = matrix.zero(self.rank(), M.degree()) mszero = matrix.zero(M.rank(), self.degree()) basis = self.basis_matrix().augment(smzero).stack( - mszero.augment(M.basis_matrix())) + mszero.augment(M.basis_matrix())) ipm = ambient.inner_product_matrix() return FreeQuadraticModule_integer_symmetric(ambient=ambient, basis=basis, inner_product_matrix=ipm, already_echelonized=False) - def is_primitive(self, M): + def is_primitive(self, M) -> bool: r""" Return whether ``M`` is a primitive submodule of this lattice. @@ -922,7 +925,7 @@ def is_primitive(self, M): sage: (L1 + L2).index_in(U) 2 """ - return (gcd((self/M).invariants()) == 0) + return gcd((self/M).invariants()) == 0 def orthogonal_complement(self, M): r""" @@ -954,7 +957,7 @@ def orthogonal_complement(self, M): Standard scalar product """ from sage.modules.free_module import FreeModule_generic - if not isinstance(M,FreeModule_generic): + if not isinstance(M, FreeModule_generic): M = self.span(M) elif M.ambient_vector_space() != self.ambient_vector_space(): raise ValueError("M must have the same " @@ -1626,7 +1629,7 @@ def local_modification(M, G, p, check=True): [ 0 -12 0 24] """ from sage.quadratic_forms.genera.normal_form import p_adic_normal_form - from sage.quadratic_forms.genera.genus import Genus_Symbol_p_adic_ring,p_adic_symbol + from sage.quadratic_forms.genera.genus import Genus_Symbol_p_adic_ring, p_adic_symbol # notation d = G.inverse().denominator() diff --git a/src/sage/modules/quotient_module.py b/src/sage/modules/quotient_module.py index 3cb75a7de96..3c34f56abe1 100644 --- a/src/sage/modules/quotient_module.py +++ b/src/sage/modules/quotient_module.py @@ -31,6 +31,7 @@ # ############################################################################### + class QuotientModule_free_ambient(Module_free_ambient): """ Quotients of ambient free modules by a submodule. @@ -179,7 +180,7 @@ def _coerce_map_from_(self, M): if isinstance(M, Submodule_free_ambient): return self._module.has_coerce_map_from(self.ambient_module()) if (isinstance(M, QuotientModule_free_ambient) - and M.free_cover() == self.free_cover()): + and M.free_cover() == self.free_cover()): try: return M.free_relations().is_submodule(self.free_relations()) except NotImplementedError: diff --git a/src/sage/modules/tensor_operations.py b/src/sage/modules/tensor_operations.py index 8d341541dbe..8ca968133a2 100644 --- a/src/sage/modules/tensor_operations.py +++ b/src/sage/modules/tensor_operations.py @@ -122,17 +122,11 @@ def antisymmetrized_coordinate_sums(dim, n): ((0, 1) - (1, 0), (0, 2) - (2, 0), (1, 2) - (2, 1)) """ from sage.structure.formal_sum import FormalSum - table = [] from sage.groups.perm_gps.permgroup_named import SymmetricGroup - S_d = SymmetricGroup(n) from sage.combinat.combination import Combinations - for i in Combinations(range(dim), n): - i = tuple(i) - x = [] - for g in S_d: - x.append([g.sign(), g(i)]) - x = FormalSum(x) - table.append(x) + S_d = SymmetricGroup(n) + table = [FormalSum([[g.sign(), g(tuple(i))] for g in S_d]) + for i in Combinations(range(dim), n)] return tuple(table) diff --git a/src/sage/modules/torsion_quadratic_module.py b/src/sage/modules/torsion_quadratic_module.py index f7be88a6000..2285d8e2774 100644 --- a/src/sage/modules/torsion_quadratic_module.py +++ b/src/sage/modules/torsion_quadratic_module.py @@ -13,7 +13,7 @@ # 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.modules.fg_pid.fgp_module import FGP_Module_class @@ -31,6 +31,7 @@ from sage.arith.misc import legendre_symbol from sage.structure.unique_representation import CachedRepresentation + def TorsionQuadraticForm(q): r""" Create a torsion quadratic form module from a rational matrix. @@ -695,7 +696,7 @@ def genus(self, signature_pair): and (b[2] - d) % 4 == 0 and (b[4] - t) % 4 == 0 and (b[2] - d) % 8 == (b[4] - t) % 8 # if the oddity is altered by 4 then so is the determinant - ] + ] elif self.value_module_qf().n == 2: # the form is even block0 = [b for b in _blocks(sym2[0]) if b[3] == 0] @@ -712,8 +713,10 @@ def genus(self, signature_pair): if b[3] == o and (b[2] - d) % 4 == 0 and (b[4] - t) % 4 == 0 - and (b[2] - d) % 8 == (b[4] - t) % 8 # if the oddity is altered by 4 then so is the determinant - ] + and (b[2] - d) % 8 == (b[4] - t) % 8 + # if the oddity is altered by 4 + # then so is the determinant + ] # this is completely determined block2 = [sym2[2]] else: diff --git a/src/sage/modules/vector_callable_symbolic_dense.py b/src/sage/modules/vector_callable_symbolic_dense.py index 70b05d98b66..5727003a22d 100644 --- a/src/sage/modules/vector_callable_symbolic_dense.py +++ b/src/sage/modules/vector_callable_symbolic_dense.py @@ -2,7 +2,8 @@ Vectors over callable symbolic rings AUTHOR: - -- Jason Grout (2010) + +- Jason Grout (2010) EXAMPLES:: @@ -34,7 +35,7 @@ """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2010 Jason Grout # # Distributed under the terms of the GNU General Public License (GPL) @@ -46,8 +47,8 @@ # # The full text of the GPL is available at: # -# http://www.gnu.org/licenses/ -#***************************************************************************** +# https://www.gnu.org/licenses/ +# **************************************************************************** from sage.modules import free_module_element from sage.symbolic.ring import SR diff --git a/src/sage/modules/vector_symbolic_dense.py b/src/sage/modules/vector_symbolic_dense.py index 58edfa5ccc8..bd50cedec7b 100644 --- a/src/sage/modules/vector_symbolic_dense.py +++ b/src/sage/modules/vector_symbolic_dense.py @@ -45,14 +45,14 @@ """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2011 Joris Vankerschaver (jv@caltech.edu) # # 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/ -#***************************************************************************** +# https://www.gnu.org/licenses/ +# **************************************************************************** from sage.modules import free_module_element from sage.symbolic.expression import Expression diff --git a/src/sage/modules/vector_symbolic_sparse.py b/src/sage/modules/vector_symbolic_sparse.py index 223e72d8609..41cbbda5241 100644 --- a/src/sage/modules/vector_symbolic_sparse.py +++ b/src/sage/modules/vector_symbolic_sparse.py @@ -47,14 +47,14 @@ """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2011 Joris Vankerschaver (jv@caltech.edu) # # 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/ -#***************************************************************************** +# https://www.gnu.org/licenses/ +# **************************************************************************** from sage.modules import free_module_element from sage.symbolic.expression import Expression diff --git a/src/sage/modules/with_basis/cell_module.py b/src/sage/modules/with_basis/cell_module.py index 25e6509dde7..2c5f54de21b 100644 --- a/src/sage/modules/with_basis/cell_module.py +++ b/src/sage/modules/with_basis/cell_module.py @@ -2,15 +2,15 @@ r""" Cell modules """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2015-2018 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.categories.modules_with_basis import ModulesWithBasis @@ -247,7 +247,7 @@ def nonzero_bilinear_form(self): # Since the bilinear form is symmetric, it is sufficient # to check on the upper triangular part return any(self._bilinear_form_on_basis(s, t) - for i,s in enumerate(C) for t in C[i:]) + for i, s in enumerate(C) for t in C[i:]) @cached_method def radical_basis(self): @@ -289,8 +289,8 @@ def radical(self): Finite family {} """ radical = self.submodule(self.radical_basis(), - category=self.category().Subobjects(), - already_echelonized=True) + category=self.category().Subobjects(), + already_echelonized=True) radical.rename("Radical of {}".format(self)) return radical @@ -362,7 +362,7 @@ def _acted_upon_(self, scalar, self_on_left=False): if self_on_left: raise NotImplementedError - #scalar = scalar.cellular_involution() + # scalar = scalar.cellular_involution() mc = self._monomial_coefficients scalar_mc = scalar.monomial_coefficients(copy=False) D = linear_combination([(P._action_basis(x, k)._monomial_coefficients, diff --git a/src/sage/modules/with_basis/indexed_element.pyx b/src/sage/modules/with_basis/indexed_element.pyx index 5e875ef3929..ae2c8c2a6b4 100644 --- a/src/sage/modules/with_basis/indexed_element.pyx +++ b/src/sage/modules/with_basis/indexed_element.pyx @@ -15,7 +15,7 @@ AUTHORS: # 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.structure.element cimport parent diff --git a/src/sage/modules/with_basis/invariant.py b/src/sage/modules/with_basis/invariant.py index edcd603185f..7e7600f3069 100644 --- a/src/sage/modules/with_basis/invariant.py +++ b/src/sage/modules/with_basis/invariant.py @@ -883,11 +883,11 @@ def __sided_action__(g, x): else: raise ValueError("side must either be 'left' or 'right'") - proj_matrix = Matrix(M.dimension()) #initialize the zero-matrix + proj_matrix = Matrix(M.dimension()) # initialize the zero-matrix for g in self._group: - proj_matrix += self._chi(g)*Matrix((self.__sided_action__(g,b)).to_vector() for b in M.basis()) + proj_matrix += self._chi(g)*Matrix((self.__sided_action__(g, b)).to_vector() for b in M.basis()) - n = self._chi(self._group.identity()) # chi(1) is the dimension + n = self._chi(self._group.identity()) # chi(1) is the dimension g = self._group.order() self._projection_matrix = (n/g)*proj_matrix @@ -960,7 +960,7 @@ def project(self, x): """ return self.retract(self.project_ambient(x)) - def project_ambient(self,x): + def project_ambient(self, x): r""" Project ``x`` in the ambient representation onto the submodule of the ambient representation to which ``self`` is isomorphic as a module. @@ -1005,7 +1005,7 @@ def project_ambient(self,x): sage: G.rename(); M.rename() # reset names """ if (isinstance(self._ambient, Representation) - and x.parent() is self._ambient._module): + and x.parent() is self._ambient._module): x = self._ambient._element_constructor_(x) return self._project_ambient(x) diff --git a/src/sage/modules/with_basis/morphism.py b/src/sage/modules/with_basis/morphism.py index 51250e70dab..ea1dcf93f65 100644 --- a/src/sage/modules/with_basis/morphism.py +++ b/src/sage/modules/with_basis/morphism.py @@ -123,6 +123,7 @@ from sage.categories.sets_with_partial_maps import SetsWithPartialMaps from sage.structure.richcmp import op_EQ, op_NE from sage.structure.element import is_Matrix +from sage.structure.sage_object import SageObject class ModuleMorphism(Morphism): @@ -767,14 +768,15 @@ def _test_triangular(self, **options): from sage.misc.lazy_format import LazyFormat tester = self._tester(**options) on_basis = self.on_basis() - for x in self.domain().basis().keys().some_elements(): # any better set? + for x in self.domain().basis().keys().some_elements(): + # is there any better set to use ? bs, co = self._dominant_item(on_basis(x)) if self._unitriangular: tester.assertEqual(co, self.domain().base_ring().one(), - LazyFormat("morphism is not unitriangular on %s") % (x)) + LazyFormat("morphism is not unitriangular on %s") % x) xback = self._inverse_on_support(bs) tester.assertEqual(x, xback, - LazyFormat("morphism is not triangular on %s") % (x)) + LazyFormat("morphism is not triangular on %s") % x) def __invert__(self): """ @@ -962,7 +964,7 @@ def preimage(self, f): out = F.zero() while not remainder.is_zero(): - (j,c) = self._dominant_item(remainder) + (j, c) = self._dominant_item(remainder) j_preimage = self._inverse_on_support(j) if j_preimage is None: @@ -1058,10 +1060,10 @@ def coreduced(self, y): remainder = y while not remainder.is_zero(): - (j,c) = self._dominant_item(remainder) + (j, c) = self._dominant_item(remainder) j_preimage = self._inverse_on_support(j) if j_preimage is None: - dom_term = G.term(j,c) + dom_term = G.term(j, c) remainder -= dom_term result += dom_term else: @@ -1158,6 +1160,7 @@ def cokernel_projection(self, category=None): return codomain.module_morphism(function=self.coreduced, codomain=codomain, category=category) + class TriangularModuleMorphismByLinearity(ModuleMorphismByLinearity, TriangularModuleMorphism): r""" A concrete class for triangular module morphisms obtained by extending a function by linearity. @@ -1208,6 +1211,7 @@ def _richcmp_(self, other, op): return not (self == other) return NotImplemented + class TriangularModuleMorphismFromFunction(ModuleMorphismFromFunction, TriangularModuleMorphism): r""" A concrete class for triangular module morphisms implemented by a function. @@ -1234,7 +1238,8 @@ def __init__(self, domain, function, codomain=None, category=None, **keywords): sage: TestSuite(phi).run() """ ModuleMorphismFromFunction.__init__(self, function=function, - domain=domain, codomain=codomain, category=category) + domain=domain, codomain=codomain, + category=category) TriangularModuleMorphism.__init__(self, **keywords) @@ -1393,6 +1398,7 @@ def _richcmp_(self, other, op): return not (self == other) return NotImplemented + class DiagonalModuleMorphism(ModuleMorphismByLinearity): r""" A class for diagonal module morphisms. @@ -1550,7 +1556,6 @@ def pointwise_inverse_function(f): return PointwiseInverseFunction(f) -from sage.structure.sage_object import SageObject class PointwiseInverseFunction(SageObject): r""" A class for pointwise inverse functions. diff --git a/src/sage/modules/with_basis/representation.py b/src/sage/modules/with_basis/representation.py index a781fc39ec3..5329bdb660a 100644 --- a/src/sage/modules/with_basis/representation.py +++ b/src/sage/modules/with_basis/representation.py @@ -14,7 +14,7 @@ # # 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/ +# https://www.gnu.org/licenses/ ############################################################################## from sage.misc.abstract_method import abstract_method @@ -357,7 +357,7 @@ def _test_representation(self, **options): S = tester.some_elements() L = [] max_len = int(sqrt(tester._max_runs)) + 1 - for i,x in enumerate(self._semigroup): + for i, x in enumerate(self._semigroup): L.append(x) if i >= max_len: break @@ -571,17 +571,17 @@ def _acted_upon_(self, scalar, self_on_left=False): if self_on_left == P._left_repr: scalar = ~scalar return P.linear_combination(((P._on_basis(scalar, m), c) - for m,c in self), not self_on_left) + for m, c in self), not self_on_left) if sP is P._semigroup_algebra: if not self: return self ret = P.zero() - for ms,cs in scalar: + for ms, cs in scalar: if self_on_left == P._left_repr: ms = ~ms ret += P.linear_combination(((P._on_basis(ms, m), cs*c) - for m,c in self), not self_on_left) + for m, c in self), not self_on_left) return ret if P._semigroup.has_coerce_map_from(sP): @@ -602,6 +602,7 @@ def _acted_upon_(self, scalar, self_on_left=False): return CombinatorialFreeModule.Element._acted_upon_(self, scalar, self_on_left) + class RegularRepresentation(Representation): r""" The regular representation of a semigroup. @@ -683,6 +684,7 @@ def _right_on_basis(self, g, m): """ return self.monomial(m*g) + class TrivialRepresentation(Representation_abstract): """ The trivial representation of a semigroup. diff --git a/src/sage/modules/with_basis/subquotient.py b/src/sage/modules/with_basis/subquotient.py index d331e6243ad..48a235b4ab9 100644 --- a/src/sage/modules/with_basis/subquotient.py +++ b/src/sage/modules/with_basis/subquotient.py @@ -219,9 +219,9 @@ def __classcall_private__(cls, basis, support_order, ambient=None, if category is None and ambient.category().is_subcategory(Mod.Filtered()): default_category = default_category.Filtered() category = default_category.or_subcategory(category, join=True) - return super().__classcall__(cls, - basis, tuple(support_order), ambient, unitriangular, category, - *args, **opts) + return super().__classcall__(cls, basis, tuple(support_order), + ambient, unitriangular, category, + *args, **opts) def __init__(self, basis, support_order, ambient, unitriangular, category, *args, **opts): From bf0c3695cc4c6da74190e090534c56334f818394 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Tue, 26 Mar 2024 12:04:36 +0100 Subject: [PATCH 381/518] fix suggested detail --- src/sage/rings/lazy_series.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/lazy_series.py b/src/sage/rings/lazy_series.py index 4f913a4dceb..f4c46a03ca5 100644 --- a/src/sage/rings/lazy_series.py +++ b/src/sage/rings/lazy_series.py @@ -6687,7 +6687,7 @@ def legendre_transform(self): g \circ \partial_{p_1} f + f = p_1 \partial_{p_1} f. - This implies that the derivatives of `f` and `g` w.r.t. `p_1` + This implies that the derivatives of `f` and `g` with respect to `p_1` are inverses of each other with respect to plethystic substitution. The Legendre transform is an involution. From 5b477296cd2333bdc2dbe24bbee6de2f31676175 Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Tue, 26 Mar 2024 11:43:06 +0000 Subject: [PATCH 382/518] remove unused variables --- src/sage/libs/flint/nmod_poly_linkage.pxi | 1 - 1 file changed, 1 deletion(-) diff --git a/src/sage/libs/flint/nmod_poly_linkage.pxi b/src/sage/libs/flint/nmod_poly_linkage.pxi index dc28c6f2f76..239f87a7dd2 100644 --- a/src/sage/libs/flint/nmod_poly_linkage.pxi +++ b/src/sage/libs/flint/nmod_poly_linkage.pxi @@ -578,7 +578,6 @@ cdef inline int celement_gcd(nmod_poly_t res, nmod_poly_t a, nmod_poly_t b, unsi sage: (G//d)*d == G True """ - cdef unsigned long leadcoeff, modulus, leadcoeff_res if celement_is_zero(b, n): nmod_poly_set(res, a) return 0 From 53e601e0207700226ca22e1876ec915175c3b61f Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Tue, 26 Mar 2024 13:23:27 +0100 Subject: [PATCH 383/518] Implemented reviewer feedback - Fixed errors caused by unexpected behavior of reduction map - Added examples for bimultiplicativity - Corrected docstring --- .../rings/function_field/function_field.py | 86 ++++++++----------- 1 file changed, 36 insertions(+), 50 deletions(-) diff --git a/src/sage/rings/function_field/function_field.py b/src/sage/rings/function_field/function_field.py index b98636308b6..ab5f1de7681 100644 --- a/src/sage/rings/function_field/function_field.py +++ b/src/sage/rings/function_field/function_field.py @@ -1228,19 +1228,20 @@ def completion(self, place, name=None, prec=None, gen_name=None): return FunctionFieldCompletion(self, place, name=name, prec=prec, gen_name=gen_name) def hilbert_symbol(self, a, b, P): - """ + r""" Return the Hilbert symbol `(a,b)_{F_P}` (where `F_P` is the - completion of ``self`` at the place ``P``). + completion of this function field at the place `P`). - The Hilbert symbol `(a,b)_{F_P}` is `0` if one of ``a`` or ``b`` is + The Hilbert symbol `(a,b)_{F_P}` is `0` if one of `a` or `b` is zero. Otherwise it takes the value `1` if the quaternion algebra defined by `(a,b)` over `F_P` is split, and `-1` if it is division. - We use the completion at the place `P` to compute the valuations `v(a)` - and `v(b)` as well as elements `a0` and `b0` such that, for a uniformizer - `pi` of the unique maximal ideal of the completion, the elememts - `a*pi^{-v(a))}` and `a0` respectively the elements `b*pi^{-v(b)}` and `b0` - are congruent modulo `pi`. Motivated by formula 12.4.10 in [Voi2021]_. + For the valuation `\nu` belonging to the completion of the function + field at `P`, we compute the valuations `\nu(a)` and `\nu(b)` as well + as elements `a_0` and `b_0` such that, for a uniformizer `\pi` of + `\nu`, the elememts `a*\pi^{-\nu(a))}` and `a_0` respectively the + elements `b*\pi^{-\nu(b)}` and `b_0` are congruent modulo `\pi`. + Motivated by formula 12.4.10 in [Voi2021]_. Currently only tested for function fields separable over their base since places are not fully supported for other function fields. Only @@ -1248,9 +1249,9 @@ def hilbert_symbol(self, a, b, P): INPUT: - - ``a`` and ``b``: Elements of ``self`` + - ``a`` and ``b``: elements of this function field - - ``P``: A place of ``self`` + - ``P``: a place of this function field EXAMPLES:: @@ -1269,53 +1270,38 @@ def hilbert_symbol(self, a, b, P): sage: K.hilbert_symbol(c, d, Q) 1 + Check that the Hilbert symbol is bimultiplicative:: + sage: K. = FunctionField(GF(5)); R. = PolynomialRing(K) sage: f = ((x^2 + 2*x + 2)*T^5 + (4*x^2 + 2*x + 3)*T^4 + 3*T^3 + 4*T^2 ....: + (2/(x^2 + 4*x + 1))*T + 3*x^2 + 2*x + 4) sage: L. = K.extension(f) + sage: a = L.random_element() + ....: b = L.random_element() + ....: c = L.random_element() + sage: P = L.places_above(K.places()[0])[1] - sage: a = (((3*x^3 + 2*x^2 + x + 1)/(x + 2))*y^4 + (4/(x + 4))*y^3 - ....: + ((3*x + 4)/(x + 4))*y^2 - ....: + ((x^4 + 2*x^3 + 3*x^2 + x + 2)/(x^4 + x^3 + x^2 + 2))*y - ....: + (x^4 + x^2 + 2*x + 4)/(x^2 + 2*x + 2)) - sage: b = (((x + 1)/(x + 4))*y^4 - ....: + ((2*x^4 + 3*x^3 + 3*x^2 + 3)/(x^2 + 2*x + 2))*y^3 + (2/(x + 4))*y^2 - ....: + ((4*x^4 + 4*x^3 + 4*x^2 + 2)/(x^4 + x^3 + x^2 + 2))*y - ....: + (x^3 + x)/(x^3 + x^2 + x + 2)) - sage: L.hilbert_symbol(a, b, P) - 1 - sage: Q = L.places_above(K.places()[1])[0] - sage: c = (((3*x^3 + 3*x^2 + 3*x + 3)/(x^3 + 3*x^2 + x))*y^4 - ....: + ((3*x^4 + 2*x^3 + 2*x^2 + 4*x + 3)/(x^3 + 3*x^2 + x))*y^3 - ....: + ((3*x + 4)/(x^3 + 3*x^2 + x))*y^2 - ....: + ((4*x + 2)/(x^5 + 2*x^4 + 4*x^3 + 2*x^2 + x))*y - ....: + (x^3 + 2*x^2 + 4)/(x^3 + 3*x^2 + x)) - sage: d = (((2*x^4 + x^2 + 2*x + 3)/(x^2 + 2*x + 2))*y^4 - ....: + ((4*x^2 + 2)/(x^2 + 2*x + 2))*y^3 + ((3*x^2 + 3*x + 3)/(x^2 + 1))*y^2 - ....: + ((x^6 + 4*x^5 + 4*x^4 + 3*x^3 + 3*x^2 + x + 3)/(x^4 + x^3 + x^2 + 2))*y - ....: + (4*x^3 + 4*x^2 + 2*x + 2)/(x + 4)) - sage: L.hilbert_symbol(c, d, Q) - -1 + sage: (L.hilbert_symbol(a, b, P) * L.hilbert_symbol(a, c, P) + ....: == L.hilbert_symbol(a, b*c, P)) + True + sage: (L.hilbert_symbol(a, c, P) * L.hilbert_symbol(b, c, P) + ....: == L.hilbert_symbol(a*b, c, P) + True - sage: K. = FunctionField(GF(3)); R. = PolynomialRing(K) - sage: g = (((2*x + 1)/x)*T^5 + ((2*x + 1)/(x + 1))*T^4 + ((x^2 + 1)/x)*T^3 - ....: + (2*x/(x^2 + 2*x + 2))*T^2 + 2*T + (2*x + 2)/(x + 2)) - sage: L. = K.extension(g) - sage: P = L.places_above(K.places()[1])[1] - sage: a = (((x + 2)/(x + 1))*y^4 + ((x^4 + 2*x^2 + 1)/(x^2 + 2*x))*y^3 - ....: + ((x^5 + x^2 + 2*x + 1)/(x^4 + 2*x^3 + x^2 + x + 1))*y^2 - ....: + ((2*x + 2)/(x + 2))*y + (2*x^4 + x^3 + x^2 + 2*x + 2)/(x^2 + x + 1)) - sage: b = (((x^2 + x + 1)/(x^2 + 2*x + 1))*y^4 + ((2*x^2 + x + 1)/(x + 1))*y^3 - ....: + ((x^5 + x^4 + 2*x^3 + x^2 + 2*x + 2)/(x^3 + x + 2))*y^2 - ....: + (2*x/(x^2 + 2))*y + 2*x^2 + 2*x + 1) - sage: L.hilbert_symbol(a, b, P) - -1 + sage: Q = L.places_above(K.places()[1])[0] + sage: (L.hilbert_symbol(a, b, Q) * L.hilbert_symbol(a, c, Q) + ....: == L.hilbert_symbol(a, b*c, Q)) + True + sage: (L.hilbert_symbol(a, c, Q) * L.hilbert_symbol(b, c, Q) + ....: == L.hilbert_symbol(a*b, c, Q) + True """ if not self.is_global(): raise NotImplementedError('only supported for global function fields') if self.characteristic() == 2: - raise NotImplementedError('only supported in odd characteristic') + raise NotImplementedError('Hilbert symbol currently only implemented' + ' for odd characteristic function fields') if not (a in self and b in self): raise ValueError('a and b must be elements of the function field') @@ -1340,13 +1326,13 @@ def hilbert_symbol(self, a, b, P): v_b = ser_b.valuation() b0 = ser_b.coefficients()[0] - # Get the residue field of the completion, together with the residue map - k, _, tau = self.residue_field(P) + # Get the residue field of the completion together with the necssary exponent + k = sigma.codomain().base_ring() e = (k.order() - 1) >> 1 # Use Euler's criterion to compute the powers of Legendre symbols - a_rd_pw = tau(a0)**(v_b * e) - b_rd_pw = tau(b0)**(v_a * e) + a_rd_pw = a0**(v_b * e) + b_rd_pw = b0**(v_a * e) # Finally, put the result together and transform it into the correct output res = k(-1)**(v_a * v_b * e) * a_rd_pw * b_rd_pw From a06d3f059b68759739dd1a324f927d76404e8c4c Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Tue, 26 Mar 2024 13:29:03 +0100 Subject: [PATCH 384/518] Removed redundant function call in examples --- src/sage/rings/function_field/function_field.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/sage/rings/function_field/function_field.py b/src/sage/rings/function_field/function_field.py index ab5f1de7681..668433d5f84 100644 --- a/src/sage/rings/function_field/function_field.py +++ b/src/sage/rings/function_field/function_field.py @@ -1281,19 +1281,17 @@ def hilbert_symbol(self, a, b, P): ....: c = L.random_element() sage: P = L.places_above(K.places()[0])[1] - sage: (L.hilbert_symbol(a, b, P) * L.hilbert_symbol(a, c, P) - ....: == L.hilbert_symbol(a, b*c, P)) + sage: hs_a_c = L.hilbert_symbol(a, c, P) + sage: L.hilbert_symbol(a, b, P) * hs_a_c == L.hilbert_symbol(a, b*c, P) True - sage: (L.hilbert_symbol(a, c, P) * L.hilbert_symbol(b, c, P) - ....: == L.hilbert_symbol(a*b, c, P) + sage: hs_a_c * L.hilbert_symbol(b, c, P) == L.hilbert_symbol(a*b, c, P) True sage: Q = L.places_above(K.places()[1])[0] - sage: (L.hilbert_symbol(a, b, Q) * L.hilbert_symbol(a, c, Q) - ....: == L.hilbert_symbol(a, b*c, Q)) + sage: hs_a_c = L.hilbert_symbol(a, c, P) + sage: L.hilbert_symbol(a, b, Q) * hs_a_c == L.hilbert_symbol(a, b*c, Q) True - sage: (L.hilbert_symbol(a, c, Q) * L.hilbert_symbol(b, c, Q) - ....: == L.hilbert_symbol(a*b, c, Q) + sage: hs_a_c * L.hilbert_symbol(b, c, Q) == L.hilbert_symbol(a*b, c, Q) True """ if not self.is_global(): From 894a6b4b38d04a65dc6aec70130bc5ad6e0ef880 Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Tue, 26 Mar 2024 13:54:01 +0100 Subject: [PATCH 385/518] Change characteristic error type Amend: Fixed weird spacing --- src/sage/rings/function_field/function_field.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/rings/function_field/function_field.py b/src/sage/rings/function_field/function_field.py index 668433d5f84..ee2a985e9a9 100644 --- a/src/sage/rings/function_field/function_field.py +++ b/src/sage/rings/function_field/function_field.py @@ -1298,8 +1298,8 @@ def hilbert_symbol(self, a, b, P): raise NotImplementedError('only supported for global function fields') if self.characteristic() == 2: - raise NotImplementedError('Hilbert symbol currently only implemented' - ' for odd characteristic function fields') + raise ValueError('Hilbert symbol is only defined for' + ' odd characteristic function fields') if not (a in self and b in self): raise ValueError('a and b must be elements of the function field') From 0e6ef5cb7272da3d41d90aa205873f66e1cc0fc1 Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Tue, 26 Mar 2024 16:52:07 +0100 Subject: [PATCH 386/518] Fixed doctest --- src/sage/rings/function_field/function_field.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sage/rings/function_field/function_field.py b/src/sage/rings/function_field/function_field.py index ee2a985e9a9..b328749f8f9 100644 --- a/src/sage/rings/function_field/function_field.py +++ b/src/sage/rings/function_field/function_field.py @@ -1277,8 +1277,8 @@ def hilbert_symbol(self, a, b, P): ....: + (2/(x^2 + 4*x + 1))*T + 3*x^2 + 2*x + 4) sage: L. = K.extension(f) sage: a = L.random_element() - ....: b = L.random_element() - ....: c = L.random_element() + sage: b = L.random_element() + sage: c = L.random_element() sage: P = L.places_above(K.places()[0])[1] sage: hs_a_c = L.hilbert_symbol(a, c, P) @@ -1288,7 +1288,7 @@ def hilbert_symbol(self, a, b, P): True sage: Q = L.places_above(K.places()[1])[0] - sage: hs_a_c = L.hilbert_symbol(a, c, P) + sage: hs_a_c = L.hilbert_symbol(a, c, Q) sage: L.hilbert_symbol(a, b, Q) * hs_a_c == L.hilbert_symbol(a, b*c, Q) True sage: hs_a_c * L.hilbert_symbol(b, c, Q) == L.hilbert_symbol(a*b, c, Q) From 705958c620f8ca859f39b6d360b43281c3d5768b Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Sun, 18 Feb 2024 15:58:02 +0900 Subject: [PATCH 387/518] Initial implementation of tensor and wedge reprs; Coxeter group reflection reprs; fixing some group latex corner cases. --- src/sage/algebras/clifford_algebra.py | 87 +- src/sage/categories/coxeter_groups.py | 36 +- src/sage/groups/artin.py | 9 +- .../groups/perm_gps/permgroup_element.pyx | 7 +- .../modules/with_basis/indexed_element.pyx | 22 +- src/sage/modules/with_basis/representation.py | 973 +++++++++++++----- src/sage/structure/indexed_generators.py | 4 + 7 files changed, 883 insertions(+), 255 deletions(-) diff --git a/src/sage/algebras/clifford_algebra.py b/src/sage/algebras/clifford_algebra.py index 7aeddbed2b2..9283162698c 100644 --- a/src/sage/algebras/clifford_algebra.py +++ b/src/sage/algebras/clifford_algebra.py @@ -51,7 +51,7 @@ class CliffordAlgebraIndices(UniqueRepresentation, Parent): A facade parent for the indices of Clifford algebra. Users should not create instances of this class directly. """ - def __init__(self, Qdim): + def __init__(self, Qdim, degree=None): r""" Initialize ``self``. @@ -67,9 +67,22 @@ def __init__(self, Qdim): 1111 sage: type(i) + + sage: idx = CliffordAlgebraIndices(7, 3) + sage: idx._nbits + 7 + sage: idx._degree + 3 + sage: idx._cardinality + 35 """ self._nbits = Qdim - self._cardinality = 2 ** Qdim + if degree is None: + self._cardinality = 2 ** Qdim + else: + from sage.functions.other import binomial + self._cardinality = binomial(Qdim, degree) + self._degree = degree # the if statement here is in case Qdim is 0. category = FiniteEnumeratedSets().Facade() Parent.__init__(self, category=category, facade=True) @@ -129,6 +142,12 @@ def cardinality(self): True sage: len(idx) == 2^7 True + + sage: idx = CliffordAlgebraIndices(7, 3) + sage: idx.cardinality() == binomial(7, 3) + True + sage: len(idx) == binomial(7, 3) + True """ return self._cardinality @@ -149,14 +168,20 @@ def _repr_(self): Subsets of {0} sage: CliffordAlgebraIndices(2) Subsets of {0,1} + sage: CliffordAlgebraIndices(5, 3) + Subsets of {0,1,...,4} of size 3 """ + if self._degree is not None: + extra = f" of size {self._degree}" + else: + extra = "" if self._nbits == 0: - return "Subsets of {}" + return "Subsets of {}" + extra if self._nbits == 1: - return "Subsets of {0}" + return "Subsets of {0}" + extra if self._nbits == 2: - return "Subsets of {0,1}" - return f"Subsets of {{0,1,...,{self._nbits-1}}}" + return "Subsets of {0,1}" + extra + return f"Subsets of {{0,1,...,{self._nbits-1}}}" + extra def _latex_(self): r""" @@ -166,21 +191,27 @@ def _latex_(self): sage: from sage.algebras.clifford_algebra import CliffordAlgebraIndices sage: latex(CliffordAlgebraIndices(7)) - \mathcal{P}({0,1,\ldots,6}) + \mathcal{P}(\{0,1,\ldots,6\}) sage: latex(CliffordAlgebraIndices(0)) \mathcal{P}(\emptyset) sage: latex(CliffordAlgebraIndices(1)) - \mathcal{P}({0}) + \mathcal{P}(\{0\}) sage: latex(CliffordAlgebraIndices(2)) - \mathcal{P}({0,1}) + \mathcal{P}(\{0,1\}) + sage: latex(CliffordAlgebraIndices(2, 1)) + \mathcal{P}(\{0,1\}, 1) """ + if self._degree is not None: + extra = f", {self._degree}" + else: + extra = "" if self._nbits == 0: - return "\\mathcal{P}(\\emptyset)" + return f"\\mathcal{{P}}(\\emptyset{extra})" if self._nbits == 1: - return "\\mathcal{P}({0})" + return f"\\mathcal{{P}}(\\{{0\\}}{extra})" if self._nbits == 2: - return "\\mathcal{P}({0,1})" - return f"\\mathcal{{P}}({{0,1,\\ldots,{self._nbits-1}}})" + return f"\\mathcal{{P}}(\\{{0,1\\}}{extra})" + return f"\\mathcal{{P}}(\\{{0,1,\\ldots,{self._nbits-1}\\}}{extra})" def __iter__(self): r""" @@ -200,9 +231,18 @@ def __iter__(self): 101 011 111 + + sage: idx = CliffordAlgebraIndices(5, 3) + sage: list(idx) + [111, 1101, 11001, 1011, 10101, 10011, 0111, 01101, 01011, 00111] """ import itertools n = self._nbits + if self._degree is not None: + for C in itertools.combinations(range(n), self._degree): + yield FrozenBitset(C) + return + yield FrozenBitset() k = 1 while k <= n: @@ -226,11 +266,25 @@ def __contains__(self, elt): True sage: FrozenBitset('000001') in idx False + + sage: idx = CliffordAlgebraIndices(6, 3) + sage: FrozenBitset('01011') in idx + True + sage: FrozenBitset('00011') in idx + False + sage: int(7) in idx + True + sage: int(8) in idx + False """ if isinstance(elt, int): + if self._degree is not None and sum(ZZ(elt).bits()) != self._degree: + return False return elt < self._cardinality and elt >= 0 if not isinstance(elt, FrozenBitset): return False + if self._degree is not None and len(elt) != self._degree: + return False return elt.capacity() <= self._nbits def _an_element_(self): @@ -252,14 +306,21 @@ def _an_element_(self): sage: idx = CliffordAlgebraIndices(3) sage: idx._an_element_() 11 + sage: idx = CliffordAlgebraIndices(5, 3) + sage: idx._an_element_() + 111 """ if not self._nbits: return FrozenBitset() + if self._degree is not None: + return FrozenBitset(range(self._degree)) + from sage.combinat.subset import SubsetsSorted X = SubsetsSorted(range(self._nbits)) return FrozenBitset(X.an_element()) + class CliffordAlgebra(CombinatorialFreeModule): r""" The Clifford algebra of a quadratic form. diff --git a/src/sage/categories/coxeter_groups.py b/src/sage/categories/coxeter_groups.py index 0f18f4f5ccd..90da998848c 100644 --- a/src/sage/categories/coxeter_groups.py +++ b/src/sage/categories/coxeter_groups.py @@ -952,7 +952,6 @@ def sign_representation(self, base_ring=None, side="twosided"): Sign representation of Weyl Group of type ['A', 1, 1] (as a matrix group acting on the root space) over Integer Ring - """ if base_ring is None: from sage.rings.integer_ring import ZZ @@ -960,6 +959,36 @@ def sign_representation(self, base_ring=None, side="twosided"): from sage.modules.with_basis.representation import SignRepresentationCoxeterGroup return SignRepresentationCoxeterGroup(self, base_ring) + def reflection_representation(self, base_ring=None, side="left"): + r""" + Return the reflection representation of ``self``. + + This is also the canonical faithful representation of a + Coxeter group. + + INPUT: + + - ``base_ring`` -- (optional) the base ring; the default is + the base ring of :meth:`canonical_representation` + - ``side`` -- ignored + + EXAMPLES:: + + sage: W = CoxeterGroup(['D', 4]) + sage: W.reflection_representation() + + sage: W = CoxeterGroup(['I', 13]) + sage: W.reflection_representation() + + sage: W = WeylGroup(["B", 3, 1]) + sage: W.reflection_representation(QQ) + Sign representation of + Weyl Group of type ['A', 1, 1] (as a matrix group acting on the root space) + over Integer Ring + """ + from sage.modules.with_basis.representation import ReflectionRepresentation + return ReflectionRepresentation(self, base_ring) + def demazure_product(self, Q): r""" Return the Demazure product of the list ``Q`` in ``self``. @@ -1169,6 +1198,11 @@ def canonical_representation(self): r""" Return the canonical faithful representation of ``self``. + .. SEEALSO:: + + To obtain the underlying module with the action, use + :meth:`reflection_representation`. + EXAMPLES:: sage: W = WeylGroup("A3") # needs sage.combinat sage.groups diff --git a/src/sage/groups/artin.py b/src/sage/groups/artin.py index 407c95deb53..afb40463206 100644 --- a/src/sage/groups/artin.py +++ b/src/sage/groups/artin.py @@ -57,7 +57,7 @@ def _latex_(self): TESTS:: - sage: A = ArtinGroup(['B',3]) # needs sage.rings.number_field + sage: A = ArtinGroup(['B', 3]) # needs sage.rings.number_field sage: b = A([1, 2, 3, -1, 2, -3]) # needs sage.rings.number_field sage: b._latex_() # needs sage.rings.number_field '\\sigma_{1}\\sigma_{2}\\sigma_{3}\\sigma_{1}^{-1}\\sigma_{2}\\sigma_{3}^{-1}' @@ -66,9 +66,14 @@ def _latex_(self): sage: b = B([1, 2, 3, -1, 2, -3]) sage: b._latex_() '\\sigma_{1}\\sigma_{2}\\sigma_{3}\\sigma_{1}^{-1}\\sigma_{2}\\sigma_{3}^{-1}' + sage: B.one()._latex_() + '1' """ + word = self.Tietze() + if not word: + return '1' return ''.join(r"\sigma_{%s}^{-1}" % (-i) if i < 0 else r"\sigma_{%s}" % i - for i in self.Tietze()) + for i in word) def exponent_sum(self): """ diff --git a/src/sage/groups/perm_gps/permgroup_element.pyx b/src/sage/groups/perm_gps/permgroup_element.pyx index 5868052a876..73247ffe9d8 100644 --- a/src/sage/groups/perm_gps/permgroup_element.pyx +++ b/src/sage/groups/perm_gps/permgroup_element.pyx @@ -948,10 +948,15 @@ cdef class PermutationGroupElement(MultiplicativeGroupElement): sage: S = SymmetricGroup(['a', 'b']) sage: latex(S.gens()) \left((\text{\texttt{a}},\text{\texttt{b}})\right) + sage: latex(S.one()) + 1 """ + cycle_tuples = self.cycle_tuples() + if not cycle_tuples: + return '1' from sage.misc.latex import latex return "".join(("(" + ",".join(latex(x) for x in cycle) + ")") - for cycle in self.cycle_tuples()) + for cycle in cycle_tuples) def __getitem__(self, i): r""" diff --git a/src/sage/modules/with_basis/indexed_element.pyx b/src/sage/modules/with_basis/indexed_element.pyx index 5e875ef3929..14c5c17576f 100644 --- a/src/sage/modules/with_basis/indexed_element.pyx +++ b/src/sage/modules/with_basis/indexed_element.pyx @@ -363,6 +363,15 @@ cdef class IndexedFreeModuleElement(ModuleElement): * * ** * * * * + + We can get the unicode art when there is no ``one_basis`` method + (and the basis keys do not compare with ``None``):: + + sage: DC3 = groups.permutation.DiCyclic(3) + sage: L = DC3.regular_representation(QQ, side='left') + sage: E2 = L.exterior_power(2) + sage: ascii_art(E2.an_element()) + 2*()/\(5,6,7) + 2*()/\(5,7,6) + 3*()/\(1,2)(3,4) """ from sage.misc.repr import coeff_repr terms = self._sorted_items_for_printing() @@ -395,7 +404,7 @@ cdef class IndexedFreeModuleElement(ModuleElement): elif coeff == "-1": coeff = "-" elif b._l > 0: - if len(coeff) > 0 and monomial == one_basis and strip_one: + if one_basis is not None and len(coeff) > 0 and monomial == one_basis and strip_one: b = empty_ascii_art # "" else: b = AsciiArt([scalar_mult]) + b @@ -437,6 +446,15 @@ cdef class IndexedFreeModuleElement(ModuleElement): sage: unicode_art([M.zero()]) # indirect doctest # needs sage.combinat [ 0 ] + + We can get the unicode art when there is no ``one_basis`` method + (and the basis keys do not compare with ``None``):: + + sage: DC3 = groups.permutation.DiCyclic(3) + sage: L = DC3.regular_representation(QQ, side='left') + sage: E2 = L.exterior_power(2) + sage: unicode_art(E2.an_element()) + 2*()∧(5,6,7) + 2*()∧(5,7,6) + 3*()∧(1,2)(3,4) """ from sage.misc.repr import coeff_repr terms = self._sorted_items_for_printing() @@ -469,7 +487,7 @@ cdef class IndexedFreeModuleElement(ModuleElement): elif coeff == "-1": coeff = "-" elif b._l > 0: - if len(coeff) > 0 and monomial == one_basis and strip_one: + if one_basis is not None and len(coeff) > 0 and monomial == one_basis and strip_one: b = empty_unicode_art # "" else: b = UnicodeArt([scalar_mult]) + b diff --git a/src/sage/modules/with_basis/representation.py b/src/sage/modules/with_basis/representation.py index a781fc39ec3..ff681493f1a 100644 --- a/src/sage/modules/with_basis/representation.py +++ b/src/sage/modules/with_basis/representation.py @@ -6,6 +6,7 @@ - Travis Scrimshaw (2015-11-21): initial version - Siddharth Singh (2020-03-21): signed representation +- Travis Scrimshaw (2024-02-17): tensor products """ @@ -19,9 +20,10 @@ from sage.misc.abstract_method import abstract_method from sage.structure.element import Element -from sage.combinat.free_module import CombinatorialFreeModule +from sage.combinat.free_module import CombinatorialFreeModule, CombinatorialFreeModule_Tensor from sage.categories.modules import Modules - +from sage.algebras.clifford_algebra import CliffordAlgebraIndices +from sage.matrix.constructor import matrix class Representation_abstract(CombinatorialFreeModule): """ @@ -32,7 +34,7 @@ class Representation_abstract(CombinatorialFreeModule): - ``semigroup`` -- a semigroup - ``base_ring`` -- a commutative ring """ - def __init__(self, semigroup, base_ring, *args, **opts): + def __init__(self, semigroup, base_ring, side, *args, **opts): """ Initialize ``self``. @@ -44,6 +46,11 @@ def __init__(self, semigroup, base_ring, *args, **opts): """ self._semigroup = semigroup self._semigroup_algebra = semigroup.algebra(base_ring) + self._side = side + if side not in ["left", "right", "twosided"]: + raise ValueError("the side must be either 'left', 'right', or 'twosided'") + self._left_repr = bool(side == "left" or side == "twosided") + self._right_repr = bool(side == "right" or side == "twosided") CombinatorialFreeModule.__init__(self, base_ring, *args, **opts) def semigroup(self): @@ -78,7 +85,6 @@ def semigroup_algebra(self): """ return self._semigroup_algebra - @abstract_method def side(self): """ Return whether ``self`` is a left, right, or two-sided representation. @@ -93,7 +99,17 @@ def side(self): sage: R = G.regular_representation() sage: R.side() 'left' + sage: S = G.regular_representation(side="right") + sage: S.side() + 'right' + sage: R = G.sign_representation() + sage: R.side() + 'twosided' + sage: R = G.trivial_representation() + sage: R.side() + 'twosided' """ + return self._side def invariant_module(self, S=None, **kwargs): r""" @@ -201,9 +217,214 @@ def twisted_invariant_module(self, chi, G=None, **kwargs): return super().twisted_invariant_module(G, chi, side=side, **kwargs) + def matrix_representation(self, g, side=None, sparse=False): + r""" + Return the matrix representation of ``g`` acting on ``self``. + + EXAMPLES:: + + sage: S3 = SymmetricGroup(3) + sage: g = S3.an_element(); g + (2,3) + sage: L = S3.regular_representation(side="left") + sage: R = S3.regular_representation(side="right") + sage: R.matrix_representation(g) + [0 0 0 1 0 0] + [0 0 0 0 0 1] + [0 0 0 0 1 0] + [1 0 0 0 0 0] + [0 0 1 0 0 0] + [0 1 0 0 0 0] + sage: L.matrix_representation(g) + [0 0 0 1 0 0] + [0 0 0 0 1 0] + [0 0 0 0 0 1] + [1 0 0 0 0 0] + [0 1 0 0 0 0] + [0 0 1 0 0 0] + sage: A = S3.algebra(ZZ) + sage: R.matrix_representation(sum(A.basis()), side='right') + [1 1 1 1 1 1] + [1 1 1 1 1 1] + [1 1 1 1 1 1] + [1 1 1 1 1 1] + [1 1 1 1 1 1] + [1 1 1 1 1 1] + + We verify tensor products agree:: + + sage: T = tensor([L, R]) + sage: for g in S3: + ....: gL = L.matrix_representation(g, side='left') + ....: gR = R.matrix_representation(g, side='left') + ....: gT = T.matrix_representation(g, side='left') + ....: assert gL.tensor_product(gR) == gT + """ + if self.dimension() == float('inf'): + raise NotImplementedError("only implemented for finite dimensional modules") + + B = self.basis() + order = self.get_order() + inv_order = {b: i for i, b in enumerate(order)} + ret = matrix.zero(self.base_ring(), len(order), sparse=sparse) + if side is None: + if self._side == "twosided": + side = "left" + else: + side = self._side + use_left = side == "left" + for i, k in enumerate(order): + if use_left: + temp = g * B[k] + else: + temp = B[k] * g + for m, c in temp._monomial_coefficients.items(): + if not use_left: + ret[i, inv_order[m]] = c + else: + ret[inv_order[m], i] = c + return ret + + def exterior_power(self, degree=None): + r""" + Return the exterior power of ``self``. + + INPUT: + + - ``degree`` -- (optional) if given, then only consider the + given degree + + EXAMPLES:: + + sage: DC3 = groups.permutation.DiCyclic(3) + sage: L = DC3.regular_representation(QQ, side='left') + sage: E5 = L.exterior_power(5) + sage: E5 + Exterior algebra representation of Left Regular Representation of + Dicyclic group of order 12 as a permutation group over Rational Field + in degree 5 + sage: L.exterior_power() + Exterior algebra representation of Left Regular Representation of + Dicyclic group of order 12 as a permutation group over Rational Field + """ + return Representation_Exterior(self, degree) + + @abstract_method + def _semigroup_action(self, g, vec, vec_on_left): + """ + Return the action of the semigroup element ``g`` on the + vector ``vec`` of ``self``. + + EXAMPLES:: + + sage: DC3 = groups.permutation.DiCyclic(3) + sage: T = DC3.trivial_representation() + sage: T._semigroup_action(DC3.an_element(), T.basis()['v'], True) + B['v'] + """ + + class Element(CombinatorialFreeModule.Element): + def _acted_upon_(self, scalar, self_on_left=False): + """ + Return the action of ``scalar`` on ``self``. + + EXAMPLES:: + + sage: G = groups.misc.WeylGroup(['B',2], prefix='s') + sage: R = G.regular_representation() + sage: s1,s2 = G.gens() + sage: x = R.an_element(); x + 2*s2*s1*s2 + s1*s2 + 3*s2 + 1 + sage: 2 * x + 4*s2*s1*s2 + 2*s1*s2 + 6*s2 + 2 + sage: s1 * x + 2*s2*s1*s2*s1 + 3*s1*s2 + s1 + s2 + sage: s2 * x + s2*s1*s2 + 2*s1*s2 + s2 + 3 + + sage: G = groups.misc.WeylGroup(['B',2], prefix='s') + sage: R = G.regular_representation(side="right") + sage: s1,s2 = G.gens() + sage: x = R.an_element(); x + 2*s2*s1*s2 + s1*s2 + 3*s2 + 1 + sage: x * s1 + 2*s2*s1*s2*s1 + s1*s2*s1 + 3*s2*s1 + s1 + sage: x * s2 + 2*s2*s1 + s1 + s2 + 3 + + sage: G = groups.misc.WeylGroup(['B',2], prefix='s') + sage: R = G.regular_representation() + sage: R.base_ring() + Integer Ring + sage: A = G.algebra(ZZ) + sage: s1,s2 = A.algebra_generators() + sage: x = R.an_element(); x + 2*s2*s1*s2 + s1*s2 + 3*s2 + 1 + sage: s1 * x + 2*s2*s1*s2*s1 + 3*s1*s2 + s1 + s2 + sage: s2 * x + s2*s1*s2 + 2*s1*s2 + s2 + 3 + sage: (2*s1 - s2) * x + 4*s2*s1*s2*s1 - s2*s1*s2 + 4*s1*s2 + 2*s1 + s2 - 3 + sage: (3*s1 + s2) * R.zero() + 0 + + sage: A = G.algebra(QQ) + sage: s1,s2 = A.algebra_generators() + sage: a = 1/2 * s1 + sage: a * x + Traceback (most recent call last): + ... + TypeError: unsupported operand parent(s) for *: + 'Algebra of Weyl Group of type ['B', 2] ... over Rational Field' + and 'Left Regular Representation of Weyl Group of type ['B', 2] ... over Integer Ring' + + Check that things that coerce into the group (algebra) also have + an action:: + + sage: D4 = groups.permutation.Dihedral(4) + sage: S4 = SymmetricGroup(4) + sage: S4.has_coerce_map_from(D4) + True + sage: R = S4.regular_representation() + sage: D4.an_element() * R.an_element() + 2*(2,4) + 3*(1,2,3,4) + (1,3) + (1,4,2,3) + """ + if isinstance(scalar, Element): + P = self.parent() + sP = scalar.parent() + if sP is P._semigroup: + if not self: + return self + return P._semigroup_action(scalar, self, self_on_left) + + if sP is P._semigroup_algebra: + if not self: + return self + return P.linear_combination(((P._semigroup_action(ms, self, self_on_left), cs) + for ms, cs in scalar), not self_on_left) + + if P._semigroup.has_coerce_map_from(sP): + scalar = P._semigroup(scalar) + return self._acted_upon_(scalar, self_on_left) + + # Check for scalars first before general coercion to the semigroup algebra. + # This will result in a faster action for the scalars. + ret = CombinatorialFreeModule.Element._acted_upon_(self, scalar, self_on_left) + if ret is not None: + return ret + + if P._semigroup_algebra.has_coerce_map_from(sP): + scalar = P._semigroup_algebra(scalar) + return self._acted_upon_(scalar, self_on_left) + + return None + + return CombinatorialFreeModule.Element._acted_upon_(self, scalar, self_on_left) + class Representation(Representation_abstract): - """ + r""" Representation of a semigroup. INPUT: @@ -319,19 +540,17 @@ def __init__(self, semigroup, module, on_basis, side="left", **kwargs): category = kwargs.pop('category', Modules(module.base_ring()).WithBasis()) - if side not in ["left", "right"]: - raise ValueError('side must be "left" or "right"') - - self._left_repr = (side == "left") self._on_basis = on_basis self._module = module + if side == "twosided": + raise ValueError("the defined action must be either left or right") indices = module.basis().keys() if 'FiniteDimensional' in module.category().axioms(): category = category.FiniteDimensional() - Representation_abstract.__init__(self, semigroup, module.base_ring(), indices, + Representation_abstract.__init__(self, semigroup, module.base_ring(), side, indices, category=category, **module.print_options()) def _test_representation(self, **options): @@ -431,9 +650,9 @@ def _element_constructor_(self, x): return super()._element_constructor_(x) def product_by_coercion(self, left, right): - """ - Return the product of ``left`` and ``right`` by passing to ``self._module`` - and then building a new element of ``self``. + r""" + Return the product of ``left`` and ``right`` by passing to + ``self._module`` and then building a new element of ``self``. EXAMPLES:: @@ -445,9 +664,9 @@ def product_by_coercion(self, left, right): sage: r = R.an_element(); r 1 + 2*e0 + 3*e1 + e1*e2 sage: g = G.an_element(); - sage: g*r == r + sage: g * r == r # indirect doctest True - sage: r*r + sage: r * r # indirect doctest Traceback (most recent call last): ... TypeError: unsupported operand parent(s) for *: @@ -461,11 +680,10 @@ def product_by_coercion(self, left, right): sage: T = Representation(G, E, on_basis, category=category) sage: t = T.an_element(); t 1 + 2*e0 + 3*e1 + e1*e2 - sage: g*t == t + sage: g * t == t # indirect doctest True - sage: t*t + sage: t * t # indirect doctest 1 + 4*e0 + 4*e0*e1*e2 + 6*e1 + 2*e1*e2 - """ M = self._module @@ -475,132 +693,357 @@ def product_by_coercion(self, left, right): # Convert from a term in self._module to a term in self return self._from_dict(p.monomial_coefficients(copy=False), False, False) - def side(self): + def _semigroup_action(self, g, vec, vec_on_left): """ - Return whether ``self`` is a left or a right representation. + Return the action of the semigroup element ``g`` on the + vector ``vec`` of ``self``. - OUTPUT: + EXAMPLES:: + + sage: G = groups.permutation.KleinFour() + sage: E = algebras.Exterior(QQ,'e',4) + sage: on_basis = lambda g,m: E.monomial(m) # the trivial representation + sage: from sage.modules.with_basis.representation import Representation + sage: R = Representation(G, E, on_basis) + sage: R._semigroup_action(G.an_element(), R.an_element(), True) + 1 + 2*e0 + 3*e1 + e1*e2 + """ + if self._left_repr == vec_on_left: + g = ~g + return self.linear_combination(((self._on_basis(g, m), c) + for m, c in vec._monomial_coefficients.items()), not vec_on_left) - - the string ``"left"`` or ``"right"`` + +class Representation_Tensor(CombinatorialFreeModule_Tensor, Representation_abstract): + r""" + Tensor product of representations. + """ + @staticmethod + def __classcall_private__(cls, reps, **options): + r""" + Normalize input to ensure a unique representation. EXAMPLES:: - sage: G = groups.permutation.Dihedral(4) - sage: R = G.regular_representation() - sage: R.side() - 'left' - sage: S = G.regular_representation(side="right") - sage: S.side() - 'right' + sage: S3 = SymmetricGroup(3) + sage: L = S3.regular_representation(side='left') + sage: S = S3.sign_representation() + sage: R = S3.regular_representation(side='right') + sage: tensor([tensor([L, S]), R]) == tensor([L, S, R]) + True + sage: tensor([L, tensor([S, R])]) == tensor([L, S, R]) + True """ - return "left" if self._left_repr else "right" + assert (len(reps) > 0) + R = reps[0].base_ring() + S = reps[0].semigroup() + if not all(module in Modules(R).WithBasis() and module.semigroup() is S for module in reps): + raise ValueError("not all representations of the same semigroup over the same base ring") + # flatten the list of modules so that tensor(A, tensor(B,C)) gets rewritten into tensor(A, B, C) + reps = sum((module._sets if isinstance(module, Representation_Tensor) else (module,) for module in reps), ()) + if all('FiniteDimensional' in M.category().axioms() for M in reps): + options['category'] = options['category'].FiniteDimensional() + return super(Representation_Tensor, cls).__classcall__(cls, reps, **options) + + def __init__(self, reps, **options): + r""" + Initialize ``self``. - class Element(CombinatorialFreeModule.Element): - def _acted_upon_(self, scalar, self_on_left=False): - """ - Return the action of ``scalar`` on ``self``. + EXAMPLES:: - EXAMPLES:: + sage: G = groups.permutation.Alternating(5) + sage: L = G.regular_representation(side='left') + sage: S = G.sign_representation() + sage: T = tensor([L, S, L]) + sage: TestSuite(T).run() + """ + sides = set(M.side() for M in reps) + if "left" and "right" in sides: + self._side = reps[0].side() # make a choice as this is not fundamentally important + else: + if len(sides) == 2: # mix of one side and twosided + sides.remove("twosided") + self._side, = sides # get the unique side remaining + self._semigroup = reps[0].semigroup() + self._semigroup_algebra = reps[0].semigroup_algebra() + self._left_repr = bool(self._side == "left" or self._side == "twosided") + self._right_repr = bool(self._side == "right" or self._side == "twosided") + CombinatorialFreeModule_Tensor.__init__(self, reps, **options) + + def _semigroup_action(self, g, vec, vec_on_left): + """ + Return the action of the semigroup element ``g`` on the + vector ``vec`` of ``self``. - sage: G = groups.misc.WeylGroup(['B',2], prefix='s') - sage: R = G.regular_representation() - sage: s1,s2 = G.gens() - sage: x = R.an_element(); x - 2*s2*s1*s2 + s1*s2 + 3*s2 + 1 - sage: 2 * x - 4*s2*s1*s2 + 2*s1*s2 + 6*s2 + 2 - sage: s1 * x - 2*s2*s1*s2*s1 + 3*s1*s2 + s1 + s2 - sage: s2 * x - s2*s1*s2 + 2*s1*s2 + s2 + 3 + EXAMPLES:: - sage: G = groups.misc.WeylGroup(['B',2], prefix='s') - sage: R = G.regular_representation(side="right") - sage: s1,s2 = G.gens() - sage: x = R.an_element(); x - 2*s2*s1*s2 + s1*s2 + 3*s2 + 1 - sage: x * s1 - 2*s2*s1*s2*s1 + s1*s2*s1 + 3*s2*s1 + s1 - sage: x * s2 - 2*s2*s1 + s1 + s2 + 3 + sage: S3 = SymmetricGroup(3) + sage: L = S3.regular_representation(side="left") + sage: R = S3.regular_representation(side="right") + sage: T = tensor([R, L]) + sage: g = S3.an_element(); g + (2,3) + sage: v = T.an_element(); v + 2*() # () + 3*() # (1,2,3) + 2*() # (1,3,2) + sage: g * v + 2*(2,3) # (2,3) + 3*(2,3) # (1,2) + 2*(2,3) # (1,3) + sage: T._semigroup_action(g, v, True) + 2*(2,3) # (2,3) + 3*(2,3) # (1,2) + 2*(2,3) # (1,3) + """ + bases = [M.basis() for M in self._sets] + if vec_on_left: + return self.linear_combination((self._tensor_of_elements([B[k] * g for B, k in zip(bases, b)]), c) + for b, c in vec._monomial_coefficients.items()) + return self.linear_combination((self._tensor_of_elements([g * B[k] for B, k in zip(bases, b)]), c) + for b, c in vec._monomial_coefficients.items()) - sage: G = groups.misc.WeylGroup(['B',2], prefix='s') - sage: R = G.regular_representation() - sage: R.base_ring() - Integer Ring - sage: A = G.algebra(ZZ) - sage: s1,s2 = A.algebra_generators() - sage: x = R.an_element(); x - 2*s2*s1*s2 + s1*s2 + 3*s2 + 1 - sage: s1 * x - 2*s2*s1*s2*s1 + 3*s1*s2 + s1 + s2 - sage: s2 * x - s2*s1*s2 + 2*s1*s2 + s2 + 3 - sage: (2*s1 - s2) * x - 4*s2*s1*s2*s1 - s2*s1*s2 + 4*s1*s2 + 2*s1 + s2 - 3 - sage: (3*s1 + s2) * R.zero() - 0 + class Element(Representation_abstract.Element): + pass - sage: A = G.algebra(QQ) - sage: s1,s2 = A.algebra_generators() - sage: a = 1/2 * s1 - sage: a * x - Traceback (most recent call last): - ... - TypeError: unsupported operand parent(s) for *: - 'Algebra of Weyl Group of type ['B', 2] ... over Rational Field' - and 'Left Regular Representation of Weyl Group of type ['B', 2] ... over Integer Ring' - Check that things that coerce into the group (algebra) also have - an action:: +Representation_abstract.Tensor = Representation_Tensor - sage: D4 = groups.permutation.Dihedral(4) - sage: S4 = SymmetricGroup(4) - sage: S4.has_coerce_map_from(D4) - True - sage: R = S4.regular_representation() - sage: D4.an_element() * R.an_element() - 2*(2,4) + 3*(1,2,3,4) + (1,3) + (1,4,2,3) - """ - if isinstance(scalar, Element): - P = self.parent() - sP = scalar.parent() - if sP is P._semigroup: - if not self: - return self - if self_on_left == P._left_repr: - scalar = ~scalar - return P.linear_combination(((P._on_basis(scalar, m), c) - for m,c in self), not self_on_left) - if sP is P._semigroup_algebra: - if not self: - return self - ret = P.zero() - for ms,cs in scalar: - if self_on_left == P._left_repr: - ms = ~ms - ret += P.linear_combination(((P._on_basis(ms, m), cs*c) - for m,c in self), not self_on_left) - return ret +class Representation_Exterior(Representation_abstract): + r""" + The exterior algebra representation (possibly in a fixed degree). + """ + def __init__(self, rep, degree=None, **options): + r""" + Initialize ``self``. - if P._semigroup.has_coerce_map_from(sP): - scalar = P._semigroup(scalar) - return self._acted_upon_(scalar, self_on_left) + EXAMPLES:: - # Check for scalars first before general coercion to the semigroup algebra. - # This will result in a faster action for the scalars. - ret = CombinatorialFreeModule.Element._acted_upon_(self, scalar, self_on_left) - if ret is not None: - return ret + sage: G = groups.matrix.GL(3, 2) + sage: R = G.regular_representation(side="right") + sage: E2 = R.exterior_power(2) + sage: TestSuite(E2).run() + sage: E0 = R.exterior_power(0) + sage: TestSuite(E2).run() + + sage: G = groups.matrix.GL(2, 3) + sage: L = G.regular_representation(side="left") + sage: E = L.exterior_power() + sage: TestSuite(E).run() + sage: E48 = L.exterior_power(48) + sage: TestSuite(E48).run() + + sage: L.exterior_power(-2) + Traceback (most recent call last): + ... + ValueError: the degree must be in [0, 48] + sage: L.exterior_power(120) + Traceback (most recent call last): + ... + ValueError: the degree must be in [0, 48] + """ + from sage.algebras.clifford_algebra import ExteriorAlgebra + self._degree = degree + self._rep = rep + R = rep.base_ring() + dim = rep.dimension() + if degree is not None and (degree > dim or degree < 0): + raise ValueError(f"the degree must be in [0, {dim}]") + self._extalg = ExteriorAlgebra(R, dim) + self._basis_order = list(rep.basis().keys()) + self._inv_map = {b: i for i, b in enumerate(self._basis_order)} + ind = CliffordAlgebraIndices(dim, degree) + Representation_abstract.__init__(self, rep.semigroup(), rep.base_ring(), rep.side(), + ind, **options) - if P._semigroup_algebra.has_coerce_map_from(sP): - scalar = P._semigroup_algebra(scalar) - return self._acted_upon_(scalar, self_on_left) + def _repr_(self): + r""" + Return a string representation of ``self``. - return None + EXAMPLES:: + + sage: DC3 = groups.permutation.DiCyclic(3) + sage: L = DC3.regular_representation(QQ, side='left') + sage: L.exterior_power(7) + Exterior algebra representation of Left Regular Representation of + Dicyclic group of order 12 as a permutation group over Rational Field + in degree 7 + sage: L.exterior_power() + Exterior algebra representation of Left Regular Representation of + Dicyclic group of order 12 as a permutation group over Rational Field + """ + ret = "Exterior algebra representation of {}".format(repr(self._rep)) + if self._degree is not None: + ret += " in degree {}".format(self._degree) + return ret + + def _latex_(self): + r""" + Return a latex representation of ``self``. + + EXAMPLES:: + + sage: DC3 = groups.permutation.DiCyclic(3) + sage: L = DC3.regular_representation(QQ, side='left') + sage: latex(L.exterior_power(4)) + \bigwedge^{4} ... + sage: latex(L.exterior_power()) + \bigwedge ... + """ + from sage.misc.latex import latex + if self._degree is not None: + return "\\bigwedge^{{{}}} {}".format(self._degree, latex(self._rep)) + return "\\bigwedge " + latex(self._rep) + + def _repr_term(self, m): + r""" + Return a string representation of the basis element indexed by + ``m``. + + EXAMPLES:: + + sage: DC3 = groups.permutation.DiCyclic(3) + sage: L = DC3.regular_representation(QQ, side='left') + sage: E2 = L.exterior_power(2) + sage: E2._repr_term(E2.an_element().leading_support()) + '()*(5,6,7)' + """ + if len(m) == 0: + return '1' + B = self._rep.basis() + return '*'.join(repr(B[self._basis_order[i]]) for i in m) + + def _ascii_art_term(self, m): + r""" + Return ascii art for the basis element indexed by ``m``. + + EXAMPLES:: + + sage: DC3 = groups.permutation.DiCyclic(3) + sage: L = DC3.regular_representation(QQ, side='left') + sage: E2 = L.exterior_power(2) + sage: E2._ascii_art_term(E2.an_element().leading_support()) + ()/\(5,6,7) + sage: ascii_art(E2.an_element()) + 2*()/\(5,6,7) + 2*()/\(5,7,6) + 3*()/\(1,2)(3,4) + """ + from sage.typeset.ascii_art import ascii_art + if len(m) == 0: + return ascii_art('1') + wedge = '/\\' + B = self._rep.basis() + return ascii_art(*[B[self._basis_order[i]] for i in m], sep=wedge) + + def _unicode_art_term(self, m): + r""" + Return unicode art for the basis element indexed by ``m``. + + EXAMPLES:: + + sage: DC3 = groups.permutation.DiCyclic(3) + sage: L = DC3.regular_representation(QQ, side='left') + sage: E2 = L.exterior_power(2) + sage: E2._unicode_art_term(E2.an_element().leading_support()) + ()∧(5,6,7) + sage: unicode_art(E2.an_element()) + 2*()∧(5,6,7) + 2*()∧(5,7,6) + 3*()∧(1,2)(3,4) + """ + from sage.typeset.unicode_art import unicode_art + if len(m) == 0: + return unicode_art('1') + import unicodedata + wedge = unicodedata.lookup('LOGICAL AND') + B = self._rep.basis() + return unicode_art(*[B[self._basis_order[i]] for i in m], sep=wedge) + + def _latex_term(self, m): + r""" + Return a `\LaTeX` representation of the basis element indexed + by ``m``. + + EXAMPLES:: + + sage: DC3 = groups.permutation.DiCyclic(3) + sage: L = DC3.regular_representation(QQ, side='left') + sage: E2 = L.exterior_power(2) + sage: E2._latex_term(E2.an_element().leading_support()) + '1 \\wedge (5,6,7)' + """ + if len(m) == 0: + return '1' + from sage.misc.latex import latex + B = self._rep.basis() + return " \\wedge ".join(latex(B[self._basis_order[i]]) for i in m) + + def _from_repr_to_ext(self, elt): + r""" + Return the element ``elt`` from the defining representation + to the corresponding exterior algebra. + + EXAMPLES:: + + sage: G = groups.matrix.GL(2, 2) + sage: L = G.regular_representation(side="left") + sage: E = L.exterior_power() + sage: E._from_repr_to_ext(sum(i*b for i,b in enumerate(L.basis(), start=1))) + e0 + 2*e1 + 3*e2 + 4*e3 + 5*e4 + 6*e5 + """ + ind = self._indices + data = {ind([self._inv_map[k]]): c for k, c in elt.monomial_coefficients(copy=False).items()} + return self._extalg.element_class(self._extalg, data) + + def _semigroup_action(self, g, vec, vec_on_left): + r""" + Return the action of the semigroup element ``g`` on the + vector ``vec`` of ``self``. + + EXAMPLES:: + + sage: DC3 = groups.permutation.DiCyclic(3) + sage: g = DC3.an_element(); g + (1,4,2,3)(5,6) + sage: R = DC3.regular_representation(side="right") + sage: E2 = R.exterior_power(2) + sage: vec = E2.an_element(); vec + 2*()*(5,6,7) + 2*()*(5,7,6) + 3*()*(1,2)(3,4) + sage: E2._semigroup_action(g, vec, True) + -2*(1,4,2,3)(6,7)*(1,4,2,3)(5,6) + 2*(1,4,2,3)(5,6)*(1,4,2,3)(5,7) + + 3*(1,4,2,3)(5,6)*(1,3,2,4)(5,6) + sage: E2._semigroup_action(g, vec, False) + -2*(1,3,2,4)(6,7)*(1,3,2,4)(5,6) + 2*(1,3,2,4)(5,6)*(1,3,2,4)(5,7) + - 3*(1,4,2,3)(5,6)*(1,3,2,4)(5,6) + """ + return self.linear_combination(((self._action_on_basis(g, b, vec_on_left), c) + for b, c in vec._monomial_coefficients.items()), not vec_on_left) + + def _action_on_basis(self, g, b, vec_on_left): + r""" + Return the action of ``g`` on the basis element indexed by ``b``. + + EXAMPLES:: + + sage: S3 = SymmetricGroup(3) + sage: g = S3.an_element(); g + (2,3) + sage: L = S3.regular_representation(side="left") + sage: E2 = L.exterior_power(2) + sage: vec = E2.an_element(); vec + 2*()*(1,3,2) + 2*()*(1,2,3) + 3*()*(2,3) + sage: g * vec + 2*(2,3)*(1,3) + 2*(2,3)*(1,2) - 3*()*(2,3) + sage: vec * g + 2*(2,3)*(1,3) + 2*(2,3)*(1,2) - 3*()*(2,3) + sage: supp = vec.leading_support(); supp + 11 + sage: E2._action_on_basis(g, supp, True) + (2,3)*(1,3) + sage: E2._action_on_basis(g, supp, False) + (2,3)*(1,3) + """ + B = self._rep.basis() + if vec_on_left: + temp = self._extalg.prod(self._from_repr_to_ext(B[self._basis_order[bk]] * g) + for bk in b) + else: + temp = self._extalg.prod(self._from_repr_to_ext(g * B[self._basis_order[bk]]) + for bk in b) + return self.element_class(self, temp._monomial_coefficients) - return CombinatorialFreeModule.Element._acted_upon_(self, scalar, self_on_left) class RegularRepresentation(Representation): r""" @@ -669,7 +1112,7 @@ def _left_on_basis(self, g, m): sage: R = G.regular_representation() sage: R._test_representation() # indirect doctest """ - return self.monomial(g*m) + return self.monomial(g * m) def _right_on_basis(self, g, m): """ @@ -681,7 +1124,8 @@ def _right_on_basis(self, g, m): sage: R = G.regular_representation(side="right") sage: R._test_representation() # indirect doctest """ - return self.monomial(m*g) + return self.monomial(m * g) + class TrivialRepresentation(Representation_abstract): """ @@ -713,7 +1157,9 @@ def __init__(self, semigroup, base_ring): sage: TestSuite(V).run() """ cat = Modules(base_ring).WithBasis().FiniteDimensional() - Representation_abstract.__init__(self, semigroup, base_ring, ['v'], category=cat) + from sage.sets.finite_enumerated_set import FiniteEnumeratedSet + indices = FiniteEnumeratedSet(['v']) + Representation_abstract.__init__(self, semigroup, base_ring, "twosided", indices, category=cat) def _repr_(self): """ @@ -729,24 +1175,22 @@ def _repr_(self): return "Trivial representation of {} over {}".format(self._semigroup, self.base_ring()) - def side(self): - """ - Return that ``self`` is a two-sided representation. - - OUTPUT: - - - the string ``"twosided"`` + def _semigroup_action(self, g, vec, vec_on_left): + r""" + Return the action of the semigroup element ``g`` on the + vector ``vec`` of ``self``. EXAMPLES:: - sage: G = groups.permutation.Dihedral(4) - sage: R = G.trivial_representation() - sage: R.side() - 'twosided' + sage: SGA = SymmetricGroupAlgebra(QQ, 4) + sage: V = SGA.trivial_representation() + sage: x = V.an_element() + sage: V._semigroup_action(SGA.group().random_element(), x, True) == x + True """ - return "twosided" + return vec - class Element(CombinatorialFreeModule.Element): + class Element(Representation_abstract.Element): def _acted_upon_(self, scalar, self_on_left=False): """ Return the action of ``scalar`` on ``self``. @@ -804,7 +1248,8 @@ class SignRepresentation_abstract(Representation_abstract): The sign representation of a semigroup `S` over a commutative ring `R` is the `1`-dimensional `R`-module on which every element of `S` - acts by 1 if order of element is even (including 0) or -1 if order of element if odd. + acts by `1` if order of element is even (including 0) or `-1` if + order of element if odd. This is simultaneously a left and right representation. @@ -812,13 +1257,13 @@ class SignRepresentation_abstract(Representation_abstract): - ``permgroup`` -- a permgroup - ``base_ring`` -- the base ring for the representation - - ``sign_function`` -- a function which returns 1 or -1 depending on the elements sign + - ``sign_function`` -- a function which returns `1` or `-1` depending + on the elements sign REFERENCES: - :wikipedia:`Representation_theory_of_the_symmetric_group` """ - def __init__(self, group, base_ring, sign_function=None): """ Initialize ``self``. @@ -838,7 +1283,7 @@ def __init__(self, group, base_ring, sign_function=None): cat = Modules(base_ring).WithBasis().FiniteDimensional() - Representation_abstract.__init__(self, group, base_ring, ["v"], category=cat) + Representation_abstract.__init__(self, group, base_ring, "twosided", ["v"], category=cat) def _repr_(self): """ @@ -855,101 +1300,64 @@ def _repr_(self): self._semigroup, self.base_ring() ) - def side(self): - """ - Return that ``self`` is a two-sided representation. - - OUTPUT: - - - the string ``"twosided"`` + def _semigroup_action(self, g, vec, vec_on_left): + r""" + Return the action of the semigroup element ``g`` on the + vector ``vec`` of ``self``. EXAMPLES:: - sage: G = groups.permutation.Dihedral(4) + sage: G = PermutationGroup(gens=[(1,2,3), (1,2)]) + sage: S = G.sign_representation() + sage: x = S.an_element(); x + 2*B['v'] + sage: s, c = G.gens(); c + (1,2,3) + sage: S._semigroup_action(s, x, True) + -2*B['v'] + sage: S._semigroup_action(s, x, False) + -2*B['v'] + sage: s * x + -2*B['v'] + sage: s*x*s + 2*B['v'] + sage: s*x*s*s*c + -2*B['v'] + sage: A = G.algebra(ZZ) + sage: s,c = A.algebra_generators() + sage: c + (1,2,3) + sage: s + (1,2) + sage: c*x + 2*B['v'] + sage: c*c*x + 2*B['v'] + sage: c*x*s + -2*B['v'] + sage: c*x*s*s + 2*B['v'] + sage: (c+s)*x + 0 + sage: (c-s)*x + 4*B['v'] + + sage: H = groups.permutation.Dihedral(4) + sage: G = SymmetricGroup(4) + sage: G.has_coerce_map_from(H) + True sage: R = G.sign_representation() - sage: R.side() - 'twosided' - """ - return "twosided" - - class Element(CombinatorialFreeModule.Element): - def _acted_upon_(self, scalar, self_on_left=False): - """ - Return the action of ``scalar`` on ``self``. + sage: H.an_element() * R.an_element() + -2*B['v'] - EXAMPLES:: - - sage: G = PermutationGroup(gens=[(1,2,3), (1,2)]) - sage: S = G.sign_representation() - sage: x = S.an_element(); x - 2*B['v'] - sage: s,c = G.gens(); c - (1,2,3) - sage: s*x - -2*B['v'] - sage: s*x*s - 2*B['v'] - sage: s*x*s*s*c - -2*B['v'] - sage: A = G.algebra(ZZ) - sage: s,c = A.algebra_generators() - sage: c - (1,2,3) - sage: s - (1,2) - sage: c*x - 2*B['v'] - sage: c*c*x - 2*B['v'] - sage: c*x*s - -2*B['v'] - sage: c*x*s*s - 2*B['v'] - sage: (c+s)*x - 0 - sage: (c-s)*x - 4*B['v'] - - sage: H = groups.permutation.Dihedral(4) - sage: G = SymmetricGroup(4) - sage: G.has_coerce_map_from(H) - True - sage: R = G.sign_representation() - sage: H.an_element() * R.an_element() - -2*B['v'] - - sage: AG = G.algebra(ZZ) - sage: AH = H.algebra(ZZ) - sage: AG.has_coerce_map_from(AH) - True - sage: AH.an_element() * R.an_element() - -2*B['v'] - """ - if isinstance(scalar, Element): - P = self.parent() - if P._semigroup.has_coerce_map_from(scalar.parent()): - scalar = P._semigroup(scalar) - return self if P.sign_function(scalar) > 0 else -self - - # We need to check for scalars first - ret = CombinatorialFreeModule.Element._acted_upon_(self, scalar, self_on_left) - if ret is not None: - return ret - - if P._semigroup_algebra.has_coerce_map_from(scalar.parent()): - if not self: - return self - sum_scalar_coeff = 0 - scalar = P._semigroup_algebra(scalar) - for ms, cs in scalar: - sum_scalar_coeff += P.sign_function(ms) * cs - return sum_scalar_coeff * self - - return None - - return CombinatorialFreeModule.Element._acted_upon_( - self, scalar, self_on_left - ) + sage: AG = G.algebra(ZZ) + sage: AH = H.algebra(ZZ) + sage: AG.has_coerce_map_from(AH) + True + sage: AH.an_element() * R.an_element() + -2*B['v'] + """ + return vec if self.sign_function(g) > 0 else -vec class SignRepresentationPermgroup(SignRepresentation_abstract): @@ -964,7 +1372,7 @@ class SignRepresentationPermgroup(SignRepresentation_abstract): """ def _default_sign(self, elem): """ - Return the sign of the element + Return the sign of the element. INPUT: @@ -995,7 +1403,7 @@ class SignRepresentationMatrixGroup(SignRepresentation_abstract): """ def _default_sign(self, elem): """ - Return the sign of the element + Return the sign of the element. INPUT: @@ -1027,7 +1435,7 @@ class SignRepresentationCoxeterGroup(SignRepresentation_abstract): """ def _default_sign(self, elem): """ - Return the sign of the element + Return the sign of the element. INPUT: @@ -1042,3 +1450,96 @@ def _default_sign(self, elem): 1 """ return -1 if elem.length() % 2 else 1 + + +class ReflectionRepresentation(Representation_abstract): + r""" + The reflection representation of a Coxeter group. + + This is the canonical faithful representation of a Coxeter group. + + EXAMPLES:: + + sage: W = CoxeterGroup(['B', 4]) + sage: R = W.reflection_representation() + sage: all(g.matrix() == R.matrix_representation(g) for g in W) + True + """ + @staticmethod + def __classcall_private__(cls, W, base_ring=None): + r""" + Normalize input to ensure a unique representation. + + EXAMPLES:: + + sage: W = CoxeterGroup(['D', 4]) + sage: R1 = W.reflection_representation() + sage: R2 = W.reflection_representation(ZZ) + sage: R1 is R2 + True + """ + if base_ring is None: + base_ring = W.one().canonical_matrix().base_ring() + return super().__classcall__(cls, W, base_ring) + + def __init__(self, W, base_ring): + r""" + Initialize ``self``. + + EXAMPLES:: + + sage: W = CoxeterGroup(['C', 3]) + sage: R = W.reflection_representation() + sage: TestSuite(R).run() + + sage: W = CoxeterGroup(['E', 6, 1]) + sage: R = W.reflection_representation() + sage: TestSuite(R).run() + """ + self._W = W + rk = W.coxeter_matrix().rank() + from sage.sets.finite_enumerated_set import FiniteEnumeratedSet + indices = FiniteEnumeratedSet(range(rk)) + super().__init__(W, base_ring, "left", indices, prefix='e', bracket=False) + + def _repr_(self): + r""" + Return a string representation of ``self``. + + EXAMPLES:: + + sage: W = CoxeterGroup(['E', 8]) + sage: W.reflection_representation() + Reflection representation of Finite Coxeter group over Integer Ring with Coxeter matrix: + [1 2 3 2 2 2 2 2] + [2 1 2 3 2 2 2 2] + [3 2 1 3 2 2 2 2] + [2 3 3 1 3 2 2 2] + [2 2 2 3 1 3 2 2] + [2 2 2 2 3 1 3 2] + [2 2 2 2 2 3 1 3] + [2 2 2 2 2 2 3 1] + """ + return "Reflection representation of {}".format(self._W) + + def _semigroup_action(self, g, vec, vec_on_left): + r""" + Return the action of the Coxeter group element ``g`` on the + vector ``vec`` of ``self``. + + EXAMPLES:: + + sage: W = CoxeterGroup(['E', 8]) + sage: R = W.reflection_representation() + sage: g = W.an_element() + sage: g == ~g + False + sage: vec = R.an_element() + sage: R._semigroup_action(g, vec, True) + e0 - 2*e1 - 2*e2 - 4*e3 - 4*e4 - 4*e5 - 4*e6 - 4*e7 + sage: R._semigroup_action(g, vec, False) + 2*e0 + 3*e1 + 4*e2 + 5*e3 + """ + if vec_on_left: + g = ~g + return self.from_vector(g.canonical_matrix() * vec.to_vector()) diff --git a/src/sage/structure/indexed_generators.py b/src/sage/structure/indexed_generators.py index 3ccff8a6706..f0d521bee87 100644 --- a/src/sage/structure/indexed_generators.py +++ b/src/sage/structure/indexed_generators.py @@ -491,6 +491,8 @@ def _ascii_art_generator(self, m): return ascii_art(ret) pref = AsciiArt([self.prefix()]) + if not pref: + return ascii_art(m) r = pref * (AsciiArt([" " * len(pref)]) + ascii_art(m)) r._baseline = r._h - 1 return r @@ -530,6 +532,8 @@ def _unicode_art_generator(self, m): return unicode_art(ret) pref = UnicodeArt([self.prefix()]) + if not pref: + return unicode_art(m) r = pref * (UnicodeArt([" " * len(pref)]) + unicode_art(m)) r._baseline = r._h - 1 return r From 0591dc6b4fdabfe44b30037e16d9fa57090dbc51 Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Mon, 19 Feb 2024 13:20:20 +0900 Subject: [PATCH 388/518] Adding symmetric power representation. --- src/sage/modules/with_basis/representation.py | 302 +++++++++++++++++- 1 file changed, 295 insertions(+), 7 deletions(-) diff --git a/src/sage/modules/with_basis/representation.py b/src/sage/modules/with_basis/representation.py index ff681493f1a..d88c353ac9c 100644 --- a/src/sage/modules/with_basis/representation.py +++ b/src/sage/modules/with_basis/representation.py @@ -22,7 +22,6 @@ from sage.structure.element import Element from sage.combinat.free_module import CombinatorialFreeModule, CombinatorialFreeModule_Tensor from sage.categories.modules import Modules -from sage.algebras.clifford_algebra import CliffordAlgebraIndices from sage.matrix.constructor import matrix class Representation_abstract(CombinatorialFreeModule): @@ -309,6 +308,24 @@ def exterior_power(self, degree=None): """ return Representation_Exterior(self, degree) + def symmetric_power(self, degree=None): + r""" + Return the symmetric power of ``self`` in degree ``degree``. + + EXAMPLES:: + + sage: W = CoxeterGroup(['H', 3]) + sage: R = W.reflection_representation() + sage: S3R = R.symmetric_power(3) + sage: S3R + Symmetric power representation of Reflection representation of + Finite Coxeter group over ... with Coxeter matrix: + [1 3 2] + [3 1 5] + [2 5 1] in degree 3 + """ + return Representation_Symmetric(self, degree) + @abstract_method def _semigroup_action(self, g, vec, vec_on_left): """ @@ -831,19 +848,25 @@ def __init__(self, rep, degree=None, **options): sage: L.exterior_power(-2) Traceback (most recent call last): ... - ValueError: the degree must be in [0, 48] + ValueError: the degree must be an integer in [0, 48] sage: L.exterior_power(120) Traceback (most recent call last): ... - ValueError: the degree must be in [0, 48] + ValueError: the degree must be an integer in [0, 48] + sage: L.exterior_power(5/6) + Traceback (most recent call last): + ... + ValueError: the degree must be an integer in [0, 48] """ from sage.algebras.clifford_algebra import ExteriorAlgebra + from sage.algebras.clifford_algebra import CliffordAlgebraIndices + from sage.rings.integer_ring import ZZ self._degree = degree self._rep = rep R = rep.base_ring() dim = rep.dimension() - if degree is not None and (degree > dim or degree < 0): - raise ValueError(f"the degree must be in [0, {dim}]") + if degree is not None and (degree not in ZZ or degree > dim or degree < 0): + raise ValueError(f"the degree must be an integer in [0, {dim}]") self._extalg = ExteriorAlgebra(R, dim) self._basis_order = list(rep.basis().keys()) self._inv_map = {b: i for i, b in enumerate(self._basis_order)} @@ -984,7 +1007,7 @@ def _from_repr_to_ext(self, elt): e0 + 2*e1 + 3*e2 + 4*e3 + 5*e4 + 6*e5 """ ind = self._indices - data = {ind([self._inv_map[k]]): c for k, c in elt.monomial_coefficients(copy=False).items()} + data = {ind([self._inv_map[k]]): c for k, c in elt._monomial_coefficients.items()} return self._extalg.element_class(self._extalg, data) def _semigroup_action(self, g, vec, vec_on_left): @@ -1027,7 +1050,7 @@ def _action_on_basis(self, g, b, vec_on_left): sage: g * vec 2*(2,3)*(1,3) + 2*(2,3)*(1,2) - 3*()*(2,3) sage: vec * g - 2*(2,3)*(1,3) + 2*(2,3)*(1,2) - 3*()*(2,3) + 2*(2,3)*(1,3) + 2*(2,3)*(1,2) - 3*()*(2,3) sage: supp = vec.leading_support(); supp 11 sage: E2._action_on_basis(g, supp, True) @@ -1045,6 +1068,271 @@ def _action_on_basis(self, g, b, vec_on_left): return self.element_class(self, temp._monomial_coefficients) +class Representation_Symmetric(Representation_abstract): + r""" + The symmetric algebra representation in a fixed degree. + """ + def __init__(self, rep, degree, **options): + r""" + Initialize ``self``. + + EXAMPLES:: + + sage: G = groups.matrix.GL(3, 2) + sage: R = G.regular_representation(side="right") + sage: S2 = R.symmetric_power(2) + sage: TestSuite(S2).run() + sage: S0 = R.symmetric_power(0) + sage: TestSuite(S2).run() + + sage: R.symmetric_power(-2) + Traceback (most recent call last): + ... + ValueError: the degree must be a nonnegative integer + sage: R.symmetric_power(3/2) + Traceback (most recent call last): + ... + ValueError: the degree must be a nonnegative integer + """ + from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing + from sage.combinat.integer_vector import IntegerVectors + from sage.rings.integer_ring import ZZ + self._degree = degree + self._rep = rep + R = rep.base_ring() + dim = rep.dimension() + if degree not in ZZ or degree < 0: + raise ValueError(f"the degree must be a nonnegative integer") + self._symalg = PolynomialRing(R, 'e', dim) + self._basis_order = list(rep.basis().keys()) + G = self._symalg.gens() + self._inv_map = {b: G[i] for i, b in enumerate(self._basis_order)} + ind = IntegerVectors(degree, dim) + Representation_abstract.__init__(self, rep.semigroup(), rep.base_ring(), rep.side(), + ind, **options) + + def _repr_(self): + r""" + Return a string representation of ``self``. + + EXAMPLES:: + + sage: DC3 = groups.permutation.DiCyclic(3) + sage: L = DC3.regular_representation(QQ, side='left') + sage: L.symmetric_power(7) + Symmetric power representation of Left Regular Representation of + Dicyclic group of order 12 as a permutation group over Rational Field + in degree 7 + """ + return "Symmetric power representation of {} in degree {}".format(repr(self._rep), self._degree) + + def _latex_(self): + r""" + Return a latex representation of ``self``. + + EXAMPLES:: + + sage: DC3 = groups.permutation.DiCyclic(3) + sage: L = DC3.regular_representation(QQ, side='left') + sage: latex(L.symmetric_power(4)) + S^{4} ... + """ + from sage.misc.latex import latex + return "S^{{{}}} {}".format(self._degree, latex(self._rep)) + + def _repr_term(self, m): + r""" + Return a string representation of the basis element indexed by + ``m``. + + EXAMPLES:: + + sage: DC3 = groups.permutation.DiCyclic(3) + sage: L = DC3.regular_representation(QQ, side='left') + sage: S2L = L.symmetric_power(2) + sage: S2L.an_element() + 3*()*(5,7,6) + 2*()*(5,6,7) + 2*()^2 + sage: S2L._repr_term(S2L.an_element().trailing_support()) + '()*(5,7,6)' + sage: S2L._repr_term(S2L.an_element().leading_support()) + '()^2' + sage: L.symmetric_power(0).an_element() + 2 + """ + if not self._degree: + return '1' + B = self._rep.basis() + return '*'.join(repr(B[self._basis_order[i]]) if e == 1 else repr(B[self._basis_order[i]]) + f'^{e}' + for i,e in enumerate(m) if e) + + def _ascii_art_term(self, m): + r""" + Return ascii art for the basis element indexed by ``m``. + + EXAMPLES:: + + sage: DC3 = groups.permutation.DiCyclic(3) + sage: L = DC3.regular_representation(QQ, side='left') + sage: S2L = L.symmetric_power(2) + sage: S2L._ascii_art_term(S2L.an_element().leading_support()) + 2 + () + sage: ascii_art(S2L.an_element()) + 2 + 3*()*(5,7,6) + 2*()*(5,6,7) + 2*() + sage: ascii_art(L.symmetric_power(0).an_element()) + 2*1 + """ + from sage.typeset.ascii_art import ascii_art + if not self._degree: + return ascii_art('1') + B = self._rep.basis() + ret = ascii_art("") + for i, e in enumerate(m): + if not e: + continue + cur = ascii_art(B[self._basis_order[i]]) + if e > 1: + cur += ascii_art(e, baseline=-cur.height()) + if ret: + ret += ascii_art('*') + ret += cur + return ret + + def _unicode_art_term(self, m): + r""" + Return unicode art for the basis element indexed by ``m``. + + EXAMPLES:: + + sage: DC3 = groups.permutation.DiCyclic(3) + sage: L = DC3.regular_representation(QQ, side='left') + sage: S2L = L.symmetric_power(2) + sage: S2L._unicode_art_term(S2L.an_element().leading_support()) + 2 + () + sage: unicode_art(S2L.an_element()) + 2 + 3*()*(5,7,6) + 2*()*(5,6,7) + 2*() + sage: unicode_art(L.symmetric_power(0).an_element()) + 2*1 + """ + from sage.typeset.unicode_art import unicode_art + if not self._degree: + return unicode_art('1') + B = self._rep.basis() + ret = unicode_art("") + for i, e in enumerate(m): + if not e: + continue + cur = unicode_art(B[self._basis_order[i]]) + if e > 1: + cur += unicode_art(e, baseline=-cur.height()) + if ret: + ret += unicode_art('*') + ret += cur + return ret + + def _latex_term(self, m): + r""" + Return a `\LaTeX` representation of the basis element indexed + by ``m``. + + EXAMPLES:: + + sage: DC3 = groups.permutation.DiCyclic(3) + sage: L = DC3.regular_representation(QQ, side='left') + sage: S2L = L.symmetric_power(2) + sage: S2L._latex_term(S2L.an_element().leading_support()) + '1 ^{2}' + sage: latex(S2L.an_element()) + 3 1 (5,7,6) + 2 1 (5,6,7) + 2 1 ^{2} + sage: latex(L.symmetric_power(0).an_element()) + 2 + """ + if not self._degree: + return '1' + from sage.misc.latex import latex + B = self._rep.basis() + return " ".join(latex(B[self._basis_order[i]]) if e == 1 else latex(B[self._basis_order[i]]) + f"^{{{e}}}" + for i, e in enumerate(m) if e) + + def _from_repr_to_sym(self, elt): + r""" + Return the element ``elt`` from the defining representation + to the corresponding exterior algebra. + + EXAMPLES:: + + sage: G = groups.matrix.GL(2, 2) + sage: L = G.regular_representation(side="left") + sage: S3L = L.symmetric_power(3) + sage: S3L._from_repr_to_sym(sum(i*b for i,b in enumerate(L.basis(), start=1))) + e0 + 2*e1 + 3*e2 + 4*e3 + 5*e4 + 6*e5 + """ + return self._symalg.sum(c * self._inv_map[k] + for k, c in elt._monomial_coefficients.items()) + + def _semigroup_action(self, g, vec, vec_on_left): + r""" + Return the action of the semigroup element ``g`` on the + vector ``vec`` of ``self``. + + EXAMPLES:: + + sage: DC3 = groups.permutation.DiCyclic(3) + sage: g = DC3.an_element(); g + (1,4,2,3)(5,6) + sage: R = DC3.regular_representation(side="right") + sage: S2L = R.symmetric_power(2) + sage: vec = S2L.an_element(); vec + 3*()*(5,7,6) + 2*()*(5,6,7) + 2*()^2 + sage: S2L._semigroup_action(g, vec, True) + 3*(1,4,2,3)(5,6)*(1,4,2,3)(5,7) + 2*(1,4,2,3)(5,6)^2 + + 2*(1,4,2,3)(6,7)*(1,4,2,3)(5,6) + sage: S2L._semigroup_action(g, vec, False) + 3*(1,3,2,4)(5,6)*(1,3,2,4)(5,7) + 2*(1,3,2,4)(5,6)^2 + + 2*(1,3,2,4)(6,7)*(1,3,2,4)(5,6) + """ + return self.linear_combination(((self._action_on_basis(g, b, vec_on_left), c) + for b, c in vec._monomial_coefficients.items()), not vec_on_left) + + def _action_on_basis(self, g, b, vec_on_left): + r""" + Return the action of ``g`` on the basis element indexed by ``b``. + + EXAMPLES:: + + sage: S3 = SymmetricGroup(3) + sage: g = S3.an_element(); g + (2,3) + sage: L = S3.regular_representation(side="left") + sage: S2L = L.symmetric_power(2) + sage: vec = S2L.an_element(); vec + 3*()*(1,2,3) + 2*()*(1,3,2) + 2*()^2 + sage: g * vec + 3*(2,3)*(1,2) + 2*(2,3)*(1,3) + 2*(2,3)^2 + sage: vec * g + 3*(2,3)*(1,2) + 2*(2,3)*(1,3) + 2*(2,3)^2 + sage: supp = vec.leading_support(); supp + [2, 0, 0, 0, 0, 0] + sage: S2L._action_on_basis(g, supp, True) + (2,3)^2 + sage: S2L._action_on_basis(g, supp, False) + (2,3)^2 + """ + B = self._rep.basis() + if vec_on_left: + temp = self._symalg.prod(self._from_repr_to_sym(B[self._basis_order[bk]] * g) ** e + for bk, e in enumerate(b)) + else: + temp = self._symalg.prod(self._from_repr_to_sym(g * B[self._basis_order[bk]]) ** e + for bk, e in enumerate(b)) + ind = self._indices + data = {ind(mon.exponents()[0]): c for c, mon in temp} + return self.element_class(self, data) + + class RegularRepresentation(Representation): r""" The regular representation of a semigroup. From 0d19d26cd9ec378e1258ce08d1e8fc41c15217d9 Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Mon, 19 Feb 2024 14:00:01 +0900 Subject: [PATCH 389/518] Fixing linter issue. --- src/sage/modules/with_basis/indexed_element.pyx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/modules/with_basis/indexed_element.pyx b/src/sage/modules/with_basis/indexed_element.pyx index 14c5c17576f..d1a9f616326 100644 --- a/src/sage/modules/with_basis/indexed_element.pyx +++ b/src/sage/modules/with_basis/indexed_element.pyx @@ -343,7 +343,7 @@ cdef class IndexedFreeModuleElement(ModuleElement): strip_one = True) def _ascii_art_(self): - """ + r""" TESTS:: sage: # needs sage.combinat @@ -430,7 +430,7 @@ cdef class IndexedFreeModuleElement(ModuleElement): return s def _unicode_art_(self): - """ + r""" TESTS:: sage: M = QuasiSymmetricFunctions(QQ).M() # needs sage.combinat From 7c05b2fca3334729794536d9cf5f0c14894ca727 Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Tue, 20 Feb 2024 10:24:36 +0900 Subject: [PATCH 390/518] Changing matrix_representation -> representation_matrix to match Specht modules. --- src/sage/modules/with_basis/representation.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/sage/modules/with_basis/representation.py b/src/sage/modules/with_basis/representation.py index d88c353ac9c..2ffe0826513 100644 --- a/src/sage/modules/with_basis/representation.py +++ b/src/sage/modules/with_basis/representation.py @@ -216,7 +216,7 @@ def twisted_invariant_module(self, chi, G=None, **kwargs): return super().twisted_invariant_module(G, chi, side=side, **kwargs) - def matrix_representation(self, g, side=None, sparse=False): + def representation_matrix(self, g, side=None, sparse=False): r""" Return the matrix representation of ``g`` acting on ``self``. @@ -227,14 +227,14 @@ def matrix_representation(self, g, side=None, sparse=False): (2,3) sage: L = S3.regular_representation(side="left") sage: R = S3.regular_representation(side="right") - sage: R.matrix_representation(g) + sage: R.representation_matrix(g) [0 0 0 1 0 0] [0 0 0 0 0 1] [0 0 0 0 1 0] [1 0 0 0 0 0] [0 0 1 0 0 0] [0 1 0 0 0 0] - sage: L.matrix_representation(g) + sage: L.representation_matrix(g) [0 0 0 1 0 0] [0 0 0 0 1 0] [0 0 0 0 0 1] @@ -242,7 +242,7 @@ def matrix_representation(self, g, side=None, sparse=False): [0 1 0 0 0 0] [0 0 1 0 0 0] sage: A = S3.algebra(ZZ) - sage: R.matrix_representation(sum(A.basis()), side='right') + sage: R.representation_matrix(sum(A.basis()), side='right') [1 1 1 1 1 1] [1 1 1 1 1 1] [1 1 1 1 1 1] @@ -254,9 +254,9 @@ def matrix_representation(self, g, side=None, sparse=False): sage: T = tensor([L, R]) sage: for g in S3: - ....: gL = L.matrix_representation(g, side='left') - ....: gR = R.matrix_representation(g, side='left') - ....: gT = T.matrix_representation(g, side='left') + ....: gL = L.representation_matrix(g, side='left') + ....: gR = R.representation_matrix(g, side='left') + ....: gT = T.representation_matrix(g, side='left') ....: assert gL.tensor_product(gR) == gT """ if self.dimension() == float('inf'): @@ -1750,7 +1750,7 @@ class ReflectionRepresentation(Representation_abstract): sage: W = CoxeterGroup(['B', 4]) sage: R = W.reflection_representation() - sage: all(g.matrix() == R.matrix_representation(g) for g in W) + sage: all(g.matrix() == R.representation_matrix(g) for g in W) True """ @staticmethod From d3676bec05e09aa56fde5de0d66f037046e254c2 Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Wed, 21 Feb 2024 09:15:07 +0900 Subject: [PATCH 391/518] Allowing tensor products of more general objects with a representation. --- src/sage/categories/coxeter_groups.py | 15 +++++++++--- src/sage/modules/with_basis/representation.py | 23 +++++++++++++++---- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/src/sage/categories/coxeter_groups.py b/src/sage/categories/coxeter_groups.py index 90da998848c..7bbb286778d 100644 --- a/src/sage/categories/coxeter_groups.py +++ b/src/sage/categories/coxeter_groups.py @@ -976,15 +976,24 @@ def reflection_representation(self, base_ring=None, side="left"): sage: W = CoxeterGroup(['D', 4]) sage: W.reflection_representation() + Reflection representation of Finite Coxeter group over + Integer Ring with Coxeter matrix: + [1 3 2 2] + [3 1 3 3] + [2 3 1 2] + [2 3 2 1] sage: W = CoxeterGroup(['I', 13]) sage: W.reflection_representation() + Reflection representation of Finite Coxeter group over + Universal Cyclotomic Field with Coxeter matrix: + [ 1 13] + [13 1] sage: W = WeylGroup(["B", 3, 1]) sage: W.reflection_representation(QQ) - Sign representation of - Weyl Group of type ['A', 1, 1] (as a matrix group acting on the root space) - over Integer Ring + Reflection representation of Weyl Group of type ['B', 3, 1] + (as a matrix group acting on the root space) """ from sage.modules.with_basis.representation import ReflectionRepresentation return ReflectionRepresentation(self, base_ring) diff --git a/src/sage/modules/with_basis/representation.py b/src/sage/modules/with_basis/representation.py index 2ffe0826513..5306f1b27dd 100644 --- a/src/sage/modules/with_basis/representation.py +++ b/src/sage/modules/with_basis/representation.py @@ -750,12 +750,27 @@ def __classcall_private__(cls, reps, **options): True sage: tensor([L, tensor([S, R])]) == tensor([L, S, R]) True + + Check that the tensor product with more general modules + can be constructed:: + + sage: C = CombinatorialFreeModule(ZZ, ['a','b']) + sage: T = tensor([C, R]) + sage: type(T) + + sage: T = tensor([R, C]) + sage: type(T) + """ - assert (len(reps) > 0) - R = reps[0].base_ring() + assert len(reps) > 0 + assert isinstance(reps[0], Representation_abstract) S = reps[0].semigroup() - if not all(module in Modules(R).WithBasis() and module.semigroup() is S for module in reps): - raise ValueError("not all representations of the same semigroup over the same base ring") + if not all(isinstance(module, Representation_abstract) + and module.semigroup() == S for module in reps): + return CombinatorialFreeModule_Tensor(reps, **options) + R = reps[0].base_ring() + if not all(module in Modules(R).WithBasis() for module in reps): + raise ValueError("not all representations over the same base ring") # flatten the list of modules so that tensor(A, tensor(B,C)) gets rewritten into tensor(A, B, C) reps = sum((module._sets if isinstance(module, Representation_Tensor) else (module,) for module in reps), ()) if all('FiniteDimensional' in M.category().axioms() for M in reps): From d9e99696e2d02e71d20905c5c662c9fb565346b0 Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Wed, 21 Feb 2024 11:01:09 +0900 Subject: [PATCH 392/518] Adding doctests for degree 0 and catching corner cases. --- src/sage/algebras/clifford_algebra.py | 30 +++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/sage/algebras/clifford_algebra.py b/src/sage/algebras/clifford_algebra.py index 9283162698c..6683ecccee0 100644 --- a/src/sage/algebras/clifford_algebra.py +++ b/src/sage/algebras/clifford_algebra.py @@ -75,6 +75,14 @@ def __init__(self, Qdim, degree=None): 3 sage: idx._cardinality 35 + + sage: idx = CliffordAlgebraIndices(7, 0) + sage: idx._nbits + 7 + sage: idx._degree + 0 + sage: idx._cardinality + 1 """ self._nbits = Qdim if degree is None: @@ -235,10 +243,17 @@ def __iter__(self): sage: idx = CliffordAlgebraIndices(5, 3) sage: list(idx) [111, 1101, 11001, 1011, 10101, 10011, 0111, 01101, 01011, 00111] + + sage: idx = CliffordAlgebraIndices(7, 0) + sage: list(idx) + [0] """ import itertools n = self._nbits if self._degree is not None: + if self._degree == 0: # special corner case + yield FrozenBitset() + return for C in itertools.combinations(range(n), self._degree): yield FrozenBitset(C) return @@ -276,6 +291,16 @@ def __contains__(self, elt): True sage: int(8) in idx False + + sage: idx = CliffordAlgebraIndices(7, 0) + sage: FrozenBitset() in idx + True + sage: FrozenBitset('01') in idx + False + sage: int(0) in idx + True + sage: int(5) in idx + False """ if isinstance(elt, int): if self._degree is not None and sum(ZZ(elt).bits()) != self._degree: @@ -309,11 +334,16 @@ def _an_element_(self): sage: idx = CliffordAlgebraIndices(5, 3) sage: idx._an_element_() 111 + sage: idx = CliffordAlgebraIndices(7, 0) + sage: idx._an_element_() + 0 """ if not self._nbits: return FrozenBitset() if self._degree is not None: + if self._degree == 0: # special corner case + return FrozenBitset() return FrozenBitset(range(self._degree)) from sage.combinat.subset import SubsetsSorted From 7f5839ecfdf195bbdceafdacd3beedf5390cefb3 Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Tue, 19 Mar 2024 01:18:57 +0900 Subject: [PATCH 393/518] One fix in the doc "unicode" -> "ascii". --- src/sage/modules/with_basis/indexed_element.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/modules/with_basis/indexed_element.pyx b/src/sage/modules/with_basis/indexed_element.pyx index d1a9f616326..9a74d2a91ff 100644 --- a/src/sage/modules/with_basis/indexed_element.pyx +++ b/src/sage/modules/with_basis/indexed_element.pyx @@ -364,7 +364,7 @@ cdef class IndexedFreeModuleElement(ModuleElement): * * * * - We can get the unicode art when there is no ``one_basis`` method + We can get the ascii art when there is no ``one_basis`` method (and the basis keys do not compare with ``None``):: sage: DC3 = groups.permutation.DiCyclic(3) From 8e53778ed02a3eddc049a80d28ff3c6edd9a2f17 Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Tue, 19 Mar 2024 18:58:31 +0900 Subject: [PATCH 394/518] Refactoring to support the algebra portion of the exterior algebra; fixed typos. --- src/sage/algebras/clifford_algebra.py | 6 + .../modules/with_basis/indexed_element.pyx | 16 ++- src/sage/modules/with_basis/representation.py | 109 ++++++++++++++---- 3 files changed, 105 insertions(+), 26 deletions(-) diff --git a/src/sage/algebras/clifford_algebra.py b/src/sage/algebras/clifford_algebra.py index 6683ecccee0..5046c43d1c5 100644 --- a/src/sage/algebras/clifford_algebra.py +++ b/src/sage/algebras/clifford_algebra.py @@ -113,10 +113,16 @@ def _element_constructor_(self, x): 00001 000001 0000001 + + sage: idx = CliffordAlgebraIndices(0) + sage: idx([]) + 0 """ if isinstance(x, (list, tuple, set, frozenset)): if len(x) > self._nbits: raise ValueError(f"{x=} is too long") + if not x: + return FrozenBitset() return FrozenBitset(x) if isinstance(x, int): diff --git a/src/sage/modules/with_basis/indexed_element.pyx b/src/sage/modules/with_basis/indexed_element.pyx index 9a74d2a91ff..520310ab1bb 100644 --- a/src/sage/modules/with_basis/indexed_element.pyx +++ b/src/sage/modules/with_basis/indexed_element.pyx @@ -388,10 +388,12 @@ cdef class IndexedFreeModuleElement(ModuleElement): if scalar_mult is None: scalar_mult = "*" + one_basis = None try: - one_basis = self.parent().one_basis() - except AttributeError: - one_basis = None + if self.parent().one_basis is not NotImplemented: + one_basis = self.parent().one_basis() + except (AttributeError, NotImplementedError, ValueError, TypeError): + pass for monomial, c in terms: b = repr_monomial(monomial) # PCR @@ -471,10 +473,12 @@ cdef class IndexedFreeModuleElement(ModuleElement): if scalar_mult is None: scalar_mult = "*" + one_basis = None try: - one_basis = self.parent().one_basis() - except AttributeError: - one_basis = None + if self.parent().one_basis is not NotImplemented: + one_basis = self.parent().one_basis() + except (AttributeError, NotImplementedError, ValueError, TypeError): + pass for (monomial, c) in terms: b = repr_monomial(monomial) # PCR diff --git a/src/sage/modules/with_basis/representation.py b/src/sage/modules/with_basis/representation.py index 5306f1b27dd..9ba96f0197d 100644 --- a/src/sage/modules/with_basis/representation.py +++ b/src/sage/modules/with_basis/representation.py @@ -19,6 +19,7 @@ ############################################################################## from sage.misc.abstract_method import abstract_method +from sage.misc.cachefunc import cached_method from sage.structure.element import Element from sage.combinat.free_module import CombinatorialFreeModule, CombinatorialFreeModule_Tensor from sage.categories.modules import Modules @@ -182,7 +183,7 @@ def twisted_invariant_module(self, chi, G=None, **kwargs): - ``G`` -- a finitely-generated semigroup (default: the semigroup this is a representation of) - This also accepts the group to be the first argument to be the group. + This also accepts the first argument to be the group. OUTPUT: @@ -198,7 +199,7 @@ def twisted_invariant_module(self, chi, G=None, **kwargs): sage: [T.lift(b) for b in T.basis()] [() - (1,2,3), -(1,2,3) + (1,3,2), (2,3) - (1,2), -(1,2) + (1,3)] - We check the different inputs work + We check the different inputs work:: sage: R.twisted_invariant_module([2,0,-1], G) is T True @@ -299,13 +300,15 @@ def exterior_power(self, degree=None): sage: L = DC3.regular_representation(QQ, side='left') sage: E5 = L.exterior_power(5) sage: E5 - Exterior algebra representation of Left Regular Representation of + Exterior power representation of Left Regular Representation of Dicyclic group of order 12 as a permutation group over Rational Field in degree 5 sage: L.exterior_power() Exterior algebra representation of Left Regular Representation of Dicyclic group of order 12 as a permutation group over Rational Field """ + if degree is None or degree == 0: + return Representation_ExteriorAlgebra(self, degree) return Representation_Exterior(self, degree) def symmetric_power(self, degree=None): @@ -838,9 +841,9 @@ class Element(Representation_abstract.Element): class Representation_Exterior(Representation_abstract): r""" - The exterior algebra representation (possibly in a fixed degree). + The exterior power representation (in a fixed degree). """ - def __init__(self, rep, degree=None, **options): + def __init__(self, rep, degree=None, category=None, **options): r""" Initialize ``self``. @@ -849,14 +852,12 @@ def __init__(self, rep, degree=None, **options): sage: G = groups.matrix.GL(3, 2) sage: R = G.regular_representation(side="right") sage: E2 = R.exterior_power(2) - sage: TestSuite(E2).run() - sage: E0 = R.exterior_power(0) + sage: E2.category() + Category of finite dimensional modules with basis over Integer Ring sage: TestSuite(E2).run() sage: G = groups.matrix.GL(2, 3) sage: L = G.regular_representation(side="left") - sage: E = L.exterior_power() - sage: TestSuite(E).run() sage: E48 = L.exterior_power(48) sage: TestSuite(E48).run() @@ -886,8 +887,10 @@ def __init__(self, rep, degree=None, **options): self._basis_order = list(rep.basis().keys()) self._inv_map = {b: i for i, b in enumerate(self._basis_order)} ind = CliffordAlgebraIndices(dim, degree) - Representation_abstract.__init__(self, rep.semigroup(), rep.base_ring(), rep.side(), - ind, **options) + R = rep.base_ring() + category = Modules(R).WithBasis().or_subcategory(category) + Representation_abstract.__init__(self, rep.semigroup(), R, rep.side(), + ind, category=category, **options) def _repr_(self): r""" @@ -898,17 +901,16 @@ def _repr_(self): sage: DC3 = groups.permutation.DiCyclic(3) sage: L = DC3.regular_representation(QQ, side='left') sage: L.exterior_power(7) - Exterior algebra representation of Left Regular Representation of + Exterior power representation of Left Regular Representation of Dicyclic group of order 12 as a permutation group over Rational Field in degree 7 sage: L.exterior_power() Exterior algebra representation of Left Regular Representation of Dicyclic group of order 12 as a permutation group over Rational Field """ - ret = "Exterior algebra representation of {}".format(repr(self._rep)) - if self._degree is not None: - ret += " in degree {}".format(self._degree) - return ret + if self._degree is None: + return "Exterior algebra representation of {}".format(repr(self._rep)) + return "Exterior power representation of {} in degree {}".format(repr(self._rep), self._degree) def _latex_(self): r""" @@ -924,9 +926,9 @@ def _latex_(self): \bigwedge ... """ from sage.misc.latex import latex - if self._degree is not None: - return "\\bigwedge^{{{}}} {}".format(self._degree, latex(self._rep)) - return "\\bigwedge " + latex(self._rep) + if self._degree is None: + return "\\bigwedge " + latex(self._rep) + return "\\bigwedge^{{{}}} ".format(self._degree) + latex(self._rep) def _repr_term(self, m): r""" @@ -1083,9 +1085,76 @@ def _action_on_basis(self, g, b, vec_on_left): return self.element_class(self, temp._monomial_coefficients) +class Representation_ExteriorAlgebra(Representation_Exterior): + r""" + The exterior algebra representation. + """ + def __init__(self, rep, degree=None, category=None, **options): + r""" + Initialize ``self``. + + EXAMPLES:: + + sage: G = groups.matrix.GL(3, 2) + sage: R = G.regular_representation(side="right") + sage: E0 = R.exterior_power(0) + sage: E0.category() + Category of finite dimensional algebras with basis over Integer Ring + sage: TestSuite(E0).run() + + sage: G = groups.matrix.GL(2, 3) + sage: L = G.regular_representation(side="left") + sage: E = L.exterior_power() + sage: E.category() + Category of finite dimensional algebras with basis over Integer Ring + sage: TestSuite(E).run() + """ + R = rep.base_ring() + from sage.categories.algebras_with_basis import AlgebrasWithBasis + category = AlgebrasWithBasis(R).or_subcategory(category) + Representation_Exterior.__init__(self, rep, degree=degree, category=category, **options) + + @cached_method + def one_basis(self): + r""" + Return the basis element indexing `1` in ``self`` if it exists. + + EXAMPLES:: + + sage: S3 = SymmetricGroup(3) + sage: L = S3.regular_representation(side="left") + sage: E = L.exterior_power() + sage: E.one_basis() + 0 + sage: E0 = L.exterior_power(0) + sage: E0.one_basis() + 0 + """ + return self._indices([]) + + def product_on_basis(self, x, y): + r""" + Return the product of basis elements indexed by ``x`` and ``y``. + + EXAMPLES:: + + sage: S3 = SymmetricGroup(3) + sage: L = S3.regular_representation(side="left") + sage: E = L.exterior_power() + sage: B = list(E.basis()) + sage: B[:7] + [1, (), (1,3,2), (1,2,3), (2,3), (1,3), (1,2)] + sage: B[2] * B[4] # indirect doctest + (1,3,2)*(2,3) + """ + B = self._extalg.basis() + temp = B[x] * B[y] + return self.element_class(self, temp._monomial_coefficients) + + class Representation_Symmetric(Representation_abstract): r""" - The symmetric algebra representation in a fixed degree. + The symmetric power representation in a fixed degree. """ def __init__(self, rep, degree, **options): r""" From 3f4de53c18be869b6867352a7274562909942916 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Mon, 25 Mar 2024 09:59:01 +0100 Subject: [PATCH 395/518] Update clifford_algebra.py use the simpler binomial --- src/sage/algebras/clifford_algebra.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/algebras/clifford_algebra.py b/src/sage/algebras/clifford_algebra.py index 5046c43d1c5..f87d52c94ce 100644 --- a/src/sage/algebras/clifford_algebra.py +++ b/src/sage/algebras/clifford_algebra.py @@ -88,7 +88,7 @@ def __init__(self, Qdim, degree=None): if degree is None: self._cardinality = 2 ** Qdim else: - from sage.functions.other import binomial + from sage.arith.misc import binomial self._cardinality = binomial(Qdim, degree) self._degree = degree # the if statement here is in case Qdim is 0. From 9d61601d415c8ce652e414553b05cceb10b39ed1 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Tue, 26 Mar 2024 20:01:40 +0100 Subject: [PATCH 396/518] provide a construction functor for symmetric functions --- src/sage/combinat/sf/character.py | 19 ++ src/sage/combinat/sf/dual.py | 121 ++++++- src/sage/combinat/sf/hall_littlewood.py | 28 +- src/sage/combinat/sf/hecke.py | 28 +- src/sage/combinat/sf/jack.py | 29 +- src/sage/combinat/sf/llt.py | 32 +- src/sage/combinat/sf/macdonald.py | 32 +- src/sage/combinat/sf/orthotriang.py | 82 ++++- src/sage/combinat/sf/sf.py | 3 - src/sage/combinat/sf/sfa.py | 431 +++++++++++++++++++----- src/sage/combinat/sf/witt.py | 14 +- 11 files changed, 705 insertions(+), 114 deletions(-) diff --git a/src/sage/combinat/sf/character.py b/src/sage/combinat/sf/character.py index 3bd8ab5e0a1..091c7bf1511 100644 --- a/src/sage/combinat/sf/character.py +++ b/src/sage/combinat/sf/character.py @@ -37,6 +37,25 @@ class generic_character(SFA_generic): + + 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: ht = SymmetricFunctions(QQ).ht() + sage: ht.construction() + (SymmetricFunctionsFunctor[induced trivial symmetric group character], + Rational Field) + """ + from sage.combinat.sf.sfa import SymmetricFunctionsFunctor + return (SymmetricFunctionsFunctor(self, self.basis_name(), + self._prefix), + self.base_ring()) + def _my_key(self, la): r""" A rank function for partitions. diff --git a/src/sage/combinat/sf/dual.py b/src/sage/combinat/sf/dual.py index 97f81551f8b..cad463cd6d1 100644 --- a/src/sage/combinat/sf/dual.py +++ b/src/sage/combinat/sf/dual.py @@ -26,7 +26,16 @@ 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): + """ + Normalize the arguments. + """ + 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 +81,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(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 @@ -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,6 +163,20 @@ 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): + """ + Return a pair ``(F, R)``, where ``F`` is a + :class:`SymmetricFunctionsFunctor` and `R` is a ring, such + that ``F(R)`` returns ``self``. + + EXAMPLES:: + + sage: w = SymmetricFunctions(ZZ).witt() + sage: w.dual_basis().construction() + (SymmetricFunctionsFunctor[dual Witt], Integer Ring) + """ + return DualBasisFunctor(self), self.base_ring() + def _dual_to_self(self, x): """ Coerce an element of the dual of ``self`` canonically into ``self``. @@ -259,6 +279,24 @@ def _dual_basis_default(self): """ return self._dual_basis + def basis_name(self): + r""" + 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. + + EXAMPLES:: + + sage: Sym = SymmetricFunctions(QQ) + sage: f = Sym.f() + sage: f.basis_name() + 'forgotten' + """ + if self._basis_name is None: + return "dual " + self._dual_basis.basis_name() + return self._basis_name + def _repr_(self): """ Representation of ``self``. @@ -276,12 +314,11 @@ def _repr_(self): sage: h = m.dual_basis(scalar=zee, scalar_name='Hall scalar product'); h #indirect doctest Dual basis to Symmetric Functions over Rational Field in the monomial basis with respect to the Hall scalar product """ - if hasattr(self, "_basis"): + if self._basis_name is not None: return super()._repr_() if self._scalar_name: return "Dual basis to %s" % self._dual_basis + " with respect to the " + self._scalar_name - else: - return "Dual basis to %s" % self._dual_basis + return "Dual basis to %s" % self._dual_basis def _precompute(self, n): """ @@ -890,6 +927,72 @@ def expand(self, n, alphabet='x'): return self._dual.expand(n, alphabet) +from sage.combinat.sf.sfa import SymmetricFunctionsFunctor +class DualBasisFunctor(SymmetricFunctionsFunctor): + """ + A constructor for algebras of symmetric functions constructed by + duality. + + EXAMPLES:: + + sage: w = SymmetricFunctions(ZZ).witt() + sage: w.dual_basis().construction() + (SymmetricFunctionsFunctor[dual Witt], Integer Ring) + """ + def __init__(self, basis): + r""" + Initialise the functor. + + INPUT: + + - ``basis`` -- the basis of the symmetric function algebra + """ + self._dual_basis = basis._dual_basis + self._basis_name = basis._basis_name + self._scalar = basis._scalar + self._scalar_name = basis._scalar_name + self._prefix = basis._prefix + super().__init__(basis, self._basis_name) + + def _apply_functor(self, R): + """ + Apply the functor to an object of ``self``'s domain. + + EXAMPLES:: + + sage: m = SymmetricFunctions(ZZ).monomial() + sage: zee = sage.combinat.sf.sfa.zee + sage: h = m.dual_basis(scalar=zee) + sage: F, R = h.construction() # indirect doctest + sage: F(QQ) + Dual basis to Symmetric Functions over Rational Field in the monomial basis + + sage: b = m.dual_basis(scalar=zee).dual_basis(scalar=lambda x: 1) + sage: F, R = b.construction() # indirect doctest + sage: F(QQ) + Dual basis to Dual basis to Symmetric Functions over Rational Field in the monomial basis + """ + dual_basis = self._dual_basis.change_ring(R) + return self._basis(dual_basis, self._scalar, self._scalar_name, + self._basis_name, self._prefix) + + def _repr_(self): + """ + Return a string representation of ``self``. + + EXAMPLES:: + + sage: w = SymmetricFunctions(ZZ).witt() + sage: w.dual_basis().construction() + (SymmetricFunctionsFunctor[dual Witt], Integer Ring) + """ + if self._basis_name is None: + name = "dual " + self._dual_basis.basis_name() + else: + name = self._basis_name + return "SymmetricFunctionsFunctor[" + name + "]" + + # Backward compatibility for unpickling from sage.misc.persist import register_unpickle_override register_unpickle_override('sage.combinat.sf.dual', 'SymmetricFunctionAlgebraElement_dual', SymmetricFunctionAlgebra_dual.Element) diff --git a/src/sage/combinat/sf/hall_littlewood.py b/src/sage/combinat/sf/hall_littlewood.py index c3376537a35..03e6435d28d 100644 --- a/src/sage/combinat/sf/hall_littlewood.py +++ b/src/sage/combinat/sf/hall_littlewood.py @@ -77,7 +77,14 @@ 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'): + """ + Normalize the arguments. + """ + return super().__classcall__(cls, Sym, Sym.base_ring()(t)) + + def __init__(self, Sym, t): """ Initialize ``self``. @@ -387,6 +394,25 @@ def __init__(self, hall_littlewood): self .register_coercion(SetMorphism(Hom(self._s, self, category), self._s_to_self)) self._s.register_coercion(SetMorphism(Hom(self, self._s, category), self._self_to_s)) + 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: P = SymmetricFunctions(QQ).hall_littlewood(t=2).P() + sage: P.construction() + (SymmetricFunctionsFunctor[Hall-Littlewood P with t=2], Rational Field) + """ + + return (sfa.SymmetricFunctionsFamilyFunctor(self, + HallLittlewood, + self.basis_name(), + self.t), + self.base_ring()) + def _s_to_self(self, x): r""" Isomorphism from the Schur basis into ``self`` diff --git a/src/sage/combinat/sf/hecke.py b/src/sage/combinat/sf/hecke.py index 5cea11ccb2c..db6ff7c478a 100644 --- a/src/sage/combinat/sf/hecke.py +++ b/src/sage/combinat/sf/hecke.py @@ -121,8 +121,14 @@ class HeckeCharacter(SymmetricFunctionAlgebra_multiplicative): - [Ram1991]_ - [RR1997]_ """ + @staticmethod + def __classcall__(cls, Sym, q='q'): + """ + Normalize the arguments. + """ + return super().__classcall__(cls, Sym, Sym.base_ring()(q)) - def __init__(self, sym, q='q'): + def __init__(self, sym, q): r""" Initialize ``self``. @@ -156,7 +162,7 @@ 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") @@ -169,6 +175,24 @@ def __init__(self, sym, q='q'): self._p.register_coercion(self._module_morphism(self._qbar_to_p_on_basis, codomain=self._p)) + 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: qbar = SymmetricFunctions(QQ['q']).qbar('q') + sage: qbar.construction() + (SymmetricFunctionsFunctor[Hecke character with q=q], + Univariate Polynomial Ring in q over Rational Field) + """ + + from sage.combinat.sf.sfa import SymmetricFunctionsFunctor + return (SymmetricFunctionsFunctor(self, self.basis_name(), self.q), + self.base_ring()) + def _p_to_qbar_on_generator(self, n): r""" Convert `p_n` to ``self`` diff --git a/src/sage/combinat/sf/jack.py b/src/sage/combinat/sf/jack.py index a7a0fec6186..9f06e8d6679 100644 --- a/src/sage/combinat/sf/jack.py +++ b/src/sage/combinat/sf/jack.py @@ -50,8 +50,14 @@ class Jack(UniqueRepresentation): + @staticmethod + def __classcall__(cls, Sym, t='t'): + """ + Normalize the arguments. + """ + 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 +76,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 @@ -533,6 +539,25 @@ def __init__(self, jack): self .register_coercion(SetMorphism(Hom(self._h, self, category), self._h_to_self)) self._h.register_coercion(SetMorphism(Hom(self, self._h, category), self._self_to_h)) + 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: Sym = SymmetricFunctions(FractionField(QQ['t'])) + sage: JP = Sym.jack().P() + sage: JP.construction() + (SymmetricFunctionsFunctor[Jack P], + Fraction Field of Univariate Polynomial Ring in t over Rational Field) + """ + return (sfa.SymmetricFunctionsFamilyFunctor(self, Jack, + self.basis_name(), + self.t), + self.base_ring()) + def _m_to_self(self, x): r""" Isomorphism from the monomial basis into ``self`` diff --git a/src/sage/combinat/sf/llt.py b/src/sage/combinat/sf/llt.py index 6e0e20d49ba..08c69817188 100644 --- a/src/sage/combinat/sf/llt.py +++ b/src/sage/combinat/sf/llt.py @@ -91,8 +91,14 @@ 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'): + """ + Normalize the arguments. + """ + 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 +135,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 @@ -441,7 +447,7 @@ def __init__(self, llt, prefix): self._llt = llt self._k = llt._k - sfa.SymmetricFunctionAlgebra_generic.__init__(self, self._sym) + sfa.SymmetricFunctionAlgebra_generic.__init__(self, self._sym, self._basis_name) # temporary until Hom(GradedHopfAlgebrasWithBasis work better) category = sage.categories.all.ModulesWithBasis(self._sym.base_ring()) @@ -449,6 +455,26 @@ def __init__(self, llt, prefix): self .register_coercion(SetMorphism(Hom(self._m, self, category), self._m_to_self)) self._m.register_coercion(SetMorphism(Hom(self, self._m, category), self._self_to_m)) + 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: Sym = SymmetricFunctions(FractionField(QQ['t'])) + sage: HSp3 = Sym.llt(3).hspin() + sage: HSp3.construction() + (SymmetricFunctionsFunctor[level 3 LLT spin], + Fraction Field of Univariate Polynomial Ring in t over Rational Field) + """ + from sage.combinat.sf.sfa import SymmetricFunctionsFamilyFunctor + return (SymmetricFunctionsFamilyFunctor(self, LLT_class, + self.basis_name(), + self._k, self.t), + self.base_ring()) + def _m_to_self(self, x): r""" Isomorphism from the monomial basis into ``self`` diff --git a/src/sage/combinat/sf/macdonald.py b/src/sage/combinat/sf/macdonald.py index 1358c5779df..c94038e8022 100644 --- a/src/sage/combinat/sf/macdonald.py +++ b/src/sage/combinat/sf/macdonald.py @@ -97,7 +97,14 @@ def __repr__(self): """ return self._name - def __init__(self, Sym, q='q', t='t'): + @staticmethod + def __classcall__(cls, Sym, q='q', t='t'): + """ + Normalize the arguments. + """ + 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 +126,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 @@ -767,6 +774,25 @@ def __init__(self, macdonald): self.register_coercion(SetMorphism(Hom(self._s, self, category), self._s_to_self)) self._s.register_coercion(SetMorphism(Hom(self, self._s, category), self._self_to_s)) + 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: Sym = SymmetricFunctions(FractionField(QQ['q'])) + sage: J = Sym.macdonald(t=2).J() + sage: J.construction() + (SymmetricFunctionsFunctor[Macdonald J with t=2], + Fraction Field of Univariate Polynomial Ring in q over Rational Field) + """ + return (sfa.SymmetricFunctionsFamilyFunctor(self, Macdonald, + self.basis_name(), + self.q, self.t), + self.base_ring()) + def _s_to_self(self, x): r""" Isomorphism from the Schur basis into self diff --git a/src/sage/combinat/sf/orthotriang.py b/src/sage/combinat/sf/orthotriang.py index 2e1650e57a7..b14df92bd1d 100644 --- a/src/sage/combinat/sf/orthotriang.py +++ b/src/sage/combinat/sf/orthotriang.py @@ -49,8 +49,14 @@ class SymmetricFunctionAlgebra_orthotriang(sfa.SymmetricFunctionAlgebra_generic) class Element(sfa.SymmetricFunctionAlgebra_generic.Element): pass + @staticmethod + def __classcall__(cls, Sym, base, scalar, prefix, basis_name, leading_coeff=None): + """ + Normalize the arguments. + """ + return super().__classcall__(cls, Sym, base, scalar, prefix, basis_name, leading_coeff) - def __init__(self, Sym, base, scalar, prefix, basis_name, leading_coeff=None): + def __init__(self, Sym, base, scalar, prefix, basis_name, leading_coeff): r""" Initialization of the symmetric function algebra defined via orthotriangular rules. @@ -84,8 +90,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(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 +108,24 @@ 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): + """ + Return a pair ``(F, R)``, where ``F`` is a + :class:`SymmetricFunctionsFunctor` and `R` is a ring, such + that ``F(R)`` returns ``self``. + + EXAMPLES:: + + sage: from sage.combinat.sf.sfa import zee + sage: from sage.combinat.sf.orthotriang import SymmetricFunctionAlgebra_orthotriang + sage: Sym = SymmetricFunctions(QQ) + sage: m = Sym.m() + sage: s = SymmetricFunctionAlgebra_orthotriang(Sym, m, zee, 's', 'Schur') + sage: s.construction() + (SymmetricFunctionsFunctor[Schur], Rational Field) + """ + return OrthotriangBasisFunctor(self), self.base_ring() + def _base_to_self(self, x): """ Coerce a symmetric function in base ``x`` into ``self``. @@ -252,6 +276,58 @@ def product(self, left, right): return self(self._sf_base(left) * self._sf_base(right)) +from sage.combinat.sf.sfa import SymmetricFunctionsFunctor +class OrthotriangBasisFunctor(SymmetricFunctionsFunctor): + """ + A constructor for algebras of symmetric functions constructed by + orthogonality and triangularity. + + EXAMPLES:: + + sage: from sage.combinat.sf.sfa import zee + sage: from sage.combinat.sf.orthotriang import SymmetricFunctionAlgebra_orthotriang + sage: Sym = SymmetricFunctions(QQ) + sage: m = Sym.m() + sage: s = SymmetricFunctionAlgebra_orthotriang(Sym, m, zee, 's', 'Schur') + sage: s.construction() + (SymmetricFunctionsFunctor[Schur], Rational Field) + """ + def __init__(self, basis): + r""" + Initialise the functor. + + INPUT: + + - ``basis`` -- the basis of the symmetric function algebra + """ + self._basis_name = basis.basis_name() + self._sf_base = basis._sf_base + self._scalar = basis._scalar + self._leading_coeff = basis._leading_coeff + self._prefix = basis._prefix + super().__init__(basis, self._basis_name) + + def _apply_functor(self, R): + """ + Apply the functor to an object of ``self``'s domain. + + EXAMPLES:: + + sage: from sage.combinat.sf.sfa import zee + sage: from sage.combinat.sf.orthotriang import SymmetricFunctionAlgebra_orthotriang + sage: Sym = SymmetricFunctions(ZZ) + sage: m = Sym.m() + sage: s = SymmetricFunctionAlgebra_orthotriang(Sym, m, zee, 's', 'Schur functions') + sage: F, R = s.construction() # indirect doctest + sage: F(QQbar) + Symmetric Functions over Algebraic Field in the Schur functions basis + """ + from sage.combinat.sf.sf import SymmetricFunctions + return self._basis(SymmetricFunctions(R), self._sf_base.change_ring(R), + self._scalar, self._prefix, self._basis_name, + self._leading_coeff) + + # Backward compatibility for unpickling from sage.misc.persist import register_unpickle_override register_unpickle_override('sage.combinat.sf.orthotriang', 'SymmetricFunctionAlgebraElement_orthotriang', SymmetricFunctionAlgebra_orthotriang.Element) diff --git a/src/sage/combinat/sf/sf.py b/src/sage/combinat/sf/sf.py index ad86bfd6c7f..aab65028db6 100644 --- a/src/sage/combinat/sf/sf.py +++ b/src/sage/combinat/sf/sf.py @@ -908,7 +908,6 @@ def schur(self): return schur.SymmetricFunctionAlgebra_schur(self) s = schur Schur = schur # Currently needed by SymmetricFunctions.__init_extra__ - # and sfa.GradedSymmetricFunctionsBases.corresponding_basis_over def powersum(self): r""" @@ -976,8 +975,6 @@ def witt(self, coerce_h=True, coerce_e=False, coerce_p=False): from . import witt return witt.SymmetricFunctionAlgebra_witt(self, coerce_h=coerce_h, coerce_e=coerce_e, coerce_p=coerce_p) w = witt - # Currently needed by sfa.GradedSymmetricFunctionsBases.corresponding_basis_over - Witt = witt def irreducible_symmetric_group_character(self): r""" diff --git a/src/sage/combinat/sf/sfa.py b/src/sage/combinat/sf/sfa.py index de6498ec6f2..942f20a0a71 100644 --- a/src/sage/combinat/sf/sfa.py +++ b/src/sage/combinat/sf/sfa.py @@ -170,21 +170,22 @@ BACKWARD INCOMPATIBLE CHANGES (:issue:`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. @@ -221,7 +222,6 @@ from sage.rings.polynomial.polynomial_element import Polynomial from sage.rings.polynomial.multi_polynomial import MPolynomial from sage.combinat.partition import _Partitions, Partitions, Partitions_n, Partition -from sage.categories.commutative_rings import CommutativeRings from sage.categories.hopf_algebras import HopfAlgebras from sage.categories.hopf_algebras_with_basis import HopfAlgebrasWithBasis from sage.categories.principal_ideal_domains import PrincipalIdealDomains @@ -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: @@ -644,43 +650,64 @@ def corresponding_basis_over(self, R): sage: P = QQ['q','t'] sage: Sym = SymmetricFunctions(P) sage: Q = CyclotomicField()['q','t'] - sage: Sym.s().corresponding_basis_over(CyclotomicField()) + sage: Sym.s().change_ring(CyclotomicField()) Symmetric Functions over Universal Cyclotomic Field in the Schur basis - sage: Sym.p().corresponding_basis_over(CyclotomicField()) + sage: Sym.p().change_ring(CyclotomicField()) Symmetric Functions over Universal Cyclotomic Field in the powersum basis - sage: Sym.m().corresponding_basis_over(CyclotomicField()) + sage: Sym.m().change_ring(CyclotomicField()) Symmetric Functions over Universal Cyclotomic Field in the monomial basis - sage: Sym.e().corresponding_basis_over(CyclotomicField()) + sage: Sym.e().change_ring(CyclotomicField()) Symmetric Functions over Universal Cyclotomic Field in the elementary basis - sage: Sym.h().corresponding_basis_over(CyclotomicField()) + sage: Sym.h().change_ring(CyclotomicField()) Symmetric Functions over Universal Cyclotomic Field in the homogeneous basis - sage: Sym.f().corresponding_basis_over(CyclotomicField()) + sage: Sym.f().change_ring(CyclotomicField()) Symmetric Functions over Universal Cyclotomic Field in the forgotten basis - sage: Sym.w().corresponding_basis_over(CyclotomicField()) + sage: Sym.w().change_ring(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(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()) - 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()) - sage: Sym.jack(t=1).J().corresponding_basis_over(CyclotomicField()) - sage: Sym.zonal().corresponding_basis_over(CyclotomicField()) + sage: Sym.macdonald().P().change_ring(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().change_ring(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().change_ring(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().change_ring(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().change_ring(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().change_ring(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().change_ring(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().change_ring(CyclotomicField()) + Symmetric Functions over Universal Cyclotomic Field in the Macdonald P with q=1 and t=3 basis + sage: Sym.hall_littlewood().P().change_ring(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().change_ring(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().change_ring(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().change_ring(CyclotomicField()) + Symmetric Functions over Universal Cyclotomic Field in the Hall-Littlewood P with t=1 basis + sage: Sym.jack().J().change_ring(CyclotomicField()['t']) + Symmetric Functions over Univariate Polynomial Ring in t over Universal Cyclotomic Field in the Jack J basis + sage: Sym.jack().P().change_ring(CyclotomicField()['t']) + Symmetric Functions over Univariate Polynomial Ring in t over Universal Cyclotomic Field in the Jack P basis + sage: Sym.jack().Q().change_ring(CyclotomicField()['t']) + Symmetric Functions over Univariate Polynomial Ring in t over Universal Cyclotomic Field in the Jack Q basis + sage: Sym.jack().Qp().change_ring(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().change_ring(CyclotomicField()) + Symmetric Functions over Universal Cyclotomic Field in the Jack J with t=1 basis + sage: Sym.zonal().change_ring(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, t=1).hspin().corresponding_basis_over(CyclotomicField()) - sage: Sym.llt(3, t=1).hcospin().corresponding_basis_over(CyclotomicField()) + sage: Sym.llt(3).hspin().change_ring(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().change_ring(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().change_ring(CyclotomicField()) + Symmetric Functions over Universal Cyclotomic Field in the level 3 LLT spin with t=1 basis + sage: Sym.llt(3, t=1).hcospin().change_ring(CyclotomicField()) + Symmetric Functions over Universal Cyclotomic Field in the level 3 LLT cospin with t=1 basis .. TODO:: @@ -688,26 +715,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 +1048,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 @@ -1051,7 +1066,7 @@ def component(i, g): # == h_g[L_i] corresponding_result = corresponding_parent_over_QQ.gessel_reutenauer(lam) comp_base_ring = comp_parent.base_ring() result = comp_parent.sum_of_terms((nu, comp_base_ring(c)) - for nu, c in corresponding_result) + for nu, c in corresponding_result) return self(result) # just in case comp_parent != self. higher_lie_character = gessel_reutenauer @@ -1188,8 +1203,8 @@ def lehrer_solomon(self, lam): def component(i, g): # == h_g[L_i] or e_g[L_i] L_i = p.sum_of_terms(((_Partitions([d] * (i//d)), R(mu(d))) - for d in squarefree_divisors(i)), - distinct=True) / i + for d in squarefree_divisors(i)), + distinct=True) / i if not i % 2: return p(e[g]).plethysm(L_i.omega()) else: @@ -1205,8 +1220,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 @@ -1222,7 +1238,7 @@ def component(i, g): # == h_g[L_i] or e_g[L_i] corresponding_result = corresponding_parent_over_QQ.lehrer_solomon(lam) comp_base_ring = comp_parent.base_ring() result = comp_parent.sum_of_terms((nu, comp_base_ring(c)) - for nu, c in corresponding_result) + for nu, c in corresponding_result) return self(result) # just in case comp_parent != self. whitney_homology_character = lehrer_solomon @@ -1766,11 +1782,11 @@ def is_unit(self): return len(m) <= 1 and self.coefficient([]).is_unit() -# SymmetricFunctionsBases.Filtered = FilteredSymmetricFunctionsBases -# SymmetricFunctionsBases.Graded = GradedSymmetricFunctionsBases +#SymmetricFunctionsBases.Filtered = FilteredSymmetricFunctionsBases +#SymmetricFunctionsBases.Graded = GradedSymmetricFunctionsBases ##################################################################### -# ABC for bases of the symmetric functions +## ABC for bases of the symmetric functions class SymmetricFunctionAlgebra_generic(CombinatorialFreeModule): r""" @@ -1788,7 +1804,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. @@ -1818,8 +1833,7 @@ def __init__(self, Sym, basis_name=None, prefix=None, graded=True): except (TypeError, ValueError): raise ValueError("R must have a unit element") - if basis_name is not None: - self._basis = basis_name + self._basis_name = basis_name if prefix is not None: self._prefix = prefix self._sym = Sym @@ -2845,7 +2859,7 @@ def basis_name(self): sage: f.basis_name() 'forgotten' """ - return self._basis + return self._basis_name def get_print_style(self): r""" @@ -3015,6 +3029,36 @@ 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: s.construction() + (SymmetricFunctionsFunctor[Schur], Integer Ring) + """ + return (SymmetricFunctionsFunctor(self, self.basis_name()), + 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""" @@ -3035,7 +3079,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. @@ -3091,7 +3134,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) @@ -4087,8 +4130,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 @@ -4286,7 +4330,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: @@ -4728,8 +4772,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 @@ -5886,7 +5931,7 @@ def hl_creation_operator(self, nu, t=None): elif isinstance(nu, list) and all(isinstance(a, (int,Integer)) for a in nu): return P(s.sum(t**la.size() * c * d * s(la) * s._repeated_bernstein_creation_operator_on_basis(ga, nu) - for ((la, mu), c) in s(self).coproduct() + for ((la,mu),c) in s(self).coproduct() for (ga, d) in s(mu).plethysm((1-t)*s[1]) )) else: raise ValueError("nu must be a list of integers") @@ -6379,6 +6424,224 @@ 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): + """ + A constructor for algebras of symmetric functions. + + EXAMPLES:: + + sage: s = SymmetricFunctions(QQ).s() + sage: s.construction() + (SymmetricFunctionsFunctor[Schur], Rational Field) + """ + rank = 9 + + def __init__(self, basis, name, *args): + r""" + Initialise the functor. + + INPUT: + + - ``basis`` -- the basis of the symmetric function algebra + - ``name`` -- the name of the basis + - ``args`` -- any further arguments necessary to initialize the basis + + .. 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. Apart from that, the + codomain of this functor could actually be + :class:`CommutativeAlgebras` over the given ring, but + parameterized functors are currently not available. + + EXAMPLES:: + + sage: from sage.combinat.sf.sfa import SymmetricFunctionsFunctor + sage: R. = ZZ[] + sage: qbar = SymmetricFunctions(R).hecke_character() + sage: SymmetricFunctionsFunctor(type(qbar), qbar.basis_name(), q) + SymmetricFunctionsFunctor[Hecke character with q=q] + + """ + self._basis = basis.__class__.__base__ + self._name = name + self._args = args + Functor.__init__(self, CommutativeRings(), CommutativeRings()) + + def _apply_functor(self, R): + """ + Apply the functor to an object of ``self``'s domain. + + EXAMPLES:: + + sage: Sym = SymmetricFunctions(QQ) + sage: qbar = Sym.qbar(q=1/2) + sage: F, R = qbar.construction() # indirect doctest + sage: F(QQbar) + Symmetric Functions over Algebraic Field in the Hecke character + with q=1/2 basis + + TESTS:: + + sage: F(ZZ) + Traceback (most recent call last): + ... + TypeError: no conversion of this rational to integer + + """ + from sage.combinat.sf.sf import SymmetricFunctions + return self._basis(SymmetricFunctions(R), *self._args) + + 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()) + + 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): + """ + EXAMPLES:: + + sage: Sym = SymmetricFunctions(QQ) + sage: qbar1 = Sym.qbar(q=1/2) + sage: qbar2 = Sym.qbar(q=1) + sage: qbar1.construction()[0] == qbar2.construction()[0] + False + """ + if not isinstance(other, type(self)): + return False + return (self._basis == other._basis + and self._name == other._name + and self._args == other._args) + + def _repr_(self): + """ + Return a string representation of ``self``. + + EXAMPLES:: + + sage: w = SymmetricFunctions(ZZ).witt() + sage: F, R = w.construction() + sage: F + SymmetricFunctionsFunctor[Witt] + + sage: R. = ZZ[] + sage: H = SymmetricFunctions(R).macdonald().H() + sage: F, R = H.construction() + sage: F + SymmetricFunctionsFunctor[Macdonald H] + """ + return "SymmetricFunctionsFunctor[" + self._name + "]" + + +class SymmetricFunctionsFamilyFunctor(SymmetricFunctionsFunctor): + def __init__(self, basis, family, name, *args): + r""" + Initialise the functor. + + INPUT: + + - ``basis`` -- the basis of the symmetric function algebra + + .. 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. Apart from that, the + codomain of this functor could actually be + :class:`CommutativeAlgebras` over the given ring, but + parameterized functors are currently not available. + + EXAMPLES:: + + sage: from sage.combinat.sf.sfa import SymmetricFunctionsFamilyFunctor + sage: R. = ZZ[] + sage: H = SymmetricFunctions(R).macdonald(q=1).H() + sage: SymmetricFunctionsFamilyFunctor(type(H), sage.combinat.sf.macdonald.Macdonald, H.basis_name(), 1, t) + SymmetricFunctionsFunctor[Macdonald H with q=1] + """ + super().__init__(basis, name, *args) + self._family = family + + def _apply_functor(self, R): + """ + Apply the functor to an object of ``self``'s domain. + + EXAMPLES:: + + sage: Sym = SymmetricFunctions(QQ['q','t']) + sage: P = Sym.macdonald(q=1/2).P(); P + Symmetric Functions over Multivariate Polynomial Ring in q, t + over Rational Field in the Macdonald P with q=1/2 basis + sage: F, R = P.construction() # indirect doctest + sage: F(QQ['t']) + Symmetric Functions over Univariate Polynomial Ring in t + over Rational Field in the Macdonald P with q=1/2 basis + + TESTS:: + + sage: F(QQ) + Traceback (most recent call last): + ... + TypeError: not a constant polynomial + """ + from sage.combinat.sf.sf import SymmetricFunctions + return self._basis(self._family(SymmetricFunctions(R), *self._args)) + + 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 + """ + return super().__eq__(other) and self._family == other._family + ################### def _lmax(x): diff --git a/src/sage/combinat/sf/witt.py b/src/sage/combinat/sf/witt.py index 138b2647826..2a22ccec58b 100644 --- a/src/sage/combinat/sf/witt.py +++ b/src/sage/combinat/sf/witt.py @@ -396,8 +396,14 @@ class SymmetricFunctionAlgebra_witt(multiplicative.SymmetricFunctionAlgebra_mult ....: #this holds for all odd i and is easily proven by induction True """ + @staticmethod + def __classcall__(cls, Sym, coerce_h=True, coerce_e=False, coerce_p=False): + """ + Normalize the arguments. + """ + return super().__classcall__(cls, Sym, coerce_h, coerce_e, coerce_p) - def __init__(self, Sym, coerce_h=True, coerce_e=False, coerce_p=False): + def __init__(self, Sym, coerce_h, coerce_e, coerce_p): """ Initialize ``self``. @@ -609,7 +615,7 @@ def _precompute_h(self, n): by choosing a ground ring unlikely to appear elsewhere:: sage: Sym = SymmetricFunctions(ZZ['hell', 'yeah']) - sage: w = Sym.Witt() + sage: w = Sym.witt() sage: l = lambda c: [ (i[0],[j for j in sorted(i[1].items())]) for i in sorted(c.items())] sage: l(w._h_to_self_cache) [] @@ -673,7 +679,7 @@ def _precompute_e(self, n): by choosing a ground ring unlikely to appear elsewhere:: sage: Sym = SymmetricFunctions(ZZ['hell', 'yeah']) - sage: w = Sym.Witt(coerce_e=True) + sage: w = Sym.witt(coerce_e=True) sage: l = lambda c: [ (i[0],[j for j in sorted(i[1].items())]) for i in sorted(c.items())] sage: l(w._e_to_self_cache) [] @@ -736,7 +742,7 @@ def _precompute_p(self, n): by choosing a ground ring unlikely to appear elsewhere:: sage: Sym = SymmetricFunctions(QQ['hell', 'yeah']) - sage: w = Sym.Witt(coerce_h=False, coerce_e=True, coerce_p=True) + sage: w = Sym.witt(coerce_h=False, coerce_e=True, coerce_p=True) sage: l = lambda c: [ (i[0],[j for j in sorted(i[1].items())]) for i in sorted(c.items())] sage: l(w._p_to_self_cache) [] From c55e0d33a0d9813c5cfba29de5385a6a3a466d58 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Tue, 26 Mar 2024 21:51:58 +0100 Subject: [PATCH 397/518] 37668: Linter and Codecov fixes --- src/sage/knots/knotinfo.py | 20 ++++++++++++-------- src/sage/knots/link.py | 23 +++++++++++++++++++++-- 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/src/sage/knots/knotinfo.py b/src/sage/knots/knotinfo.py index 8deb3fbe37a..019f56ba6c2 100644 --- a/src/sage/knots/knotinfo.py +++ b/src/sage/knots/knotinfo.py @@ -349,12 +349,12 @@ class of an oriented pair, `K = (S_3, S_1)`, with `S_i` the concordance inverse, `-K = (-S_3, -S_1)`, and the mirror image, `K^m = (-S_3, S_1)`. """ - itself ='s' - reverse ='r' - concordance_inverse ='mr' - mirror_image ='m' + itself = 's' + reverse = 'r' + concordance_inverse = 'mr' + mirror_image = 'm' mixed = 'x' # to be used in connection with KnotInfoSeries - unknown ='?' + unknown = '?' def __gt__(self, other): r""" @@ -371,9 +371,7 @@ def __gt__(self, other): , ] """ - if self.__class__ is other.__class__: - return self.value < other.value - return NotImplemented + return self.value < other.value # --------------------------------------------------------------------------------- @@ -986,6 +984,12 @@ def is_reversible(self): sage: KnotInfo.K6_3.is_reversible() True + + TESTS:: + + sage: KnotInfo.K10_67.is_reversible() # optional - database_knotinfo + False + sage: KnotInfo.L7a4_0.is_reversible() # optional - database_knotinfo """ if self.is_knot(): symmetry_type = self.symmetry_type() diff --git a/src/sage/knots/link.py b/src/sage/knots/link.py index 34fc52a7400..681499655de 100644 --- a/src/sage/knots/link.py +++ b/src/sage/knots/link.py @@ -4294,6 +4294,16 @@ def get_knotinfo(self, mirror_version=True, unique=True): (, ), (, ), (, )] + sage: KnotInfo.L10a151_0_0.link().get_knotinfo() + Traceback (most recent call last): + ... + NotImplementedError: this link cannot be uniquely determined (unknown chirality) + use keyword argument `unique` to obtain more details + sage: KnotInfo.L10a151_0_0.link().get_knotinfo(unique=False) + [(, ), + (, ), + (, ), + (, )] sage: L = KnotInfo.L6a2_0 sage: L1 = L.link() @@ -4509,6 +4519,9 @@ def is_isotopic(self, other): sage: L1.is_isotopic(L2) verbose 1 (... link.py, is_isotopic) identified by KnotInfo uniquely (KnotInfo.L6a2_0, SymmetryMutant.itself) True + sage: KnotInfo.K0_1.link().is_isotopic(KnotInfo.L2a1_0.link()) + verbose 1 (... link.py, is_isotopic) different number of components + False sage: # optional - database_knotinfo sage: K = KnotInfo.K10_67 @@ -4517,6 +4530,12 @@ def is_isotopic(self, other): sage: K1.is_isotopic(K1r) verbose 1 (... link.py, is_isotopic) unidentified by KnotInfo ([], SymmetryMutant.itself != [], SymmetryMutant.reverse) False + sage: KnotInfo.K10_25.link().is_isotopic(KnotInfo.K10_56.link()) + verbose 1 (... link.py, is_isotopic) unidentified by KnotInfo ([] != [], SymmetryMutant.itself) + False + sage: KnotInfo.L8n2_0.link().is_isotopic(KnotInfo.L8n2_1.link()) + verbose 1 (... link.py, is_isotopic) identified by KnotInfoSeries ([, ], SymmetryMutant.reverse) + True sage: set_verbose(0) """ @@ -4560,7 +4579,7 @@ def is_isotopic(self, other): verbose('identified by KnotInfo uniquely (%s, %s)' % (sl[0], k)) return True elif not self.is_knot(): - if len(set([l[0].series(oriented=True) for l in sl])) == 1: + if len(set([l.series(oriented=True) for l in sl])) == 1: # all matches are orientation mutants of each other verbose('identified by KnotInfoSeries (%s, %s)' % (sl, k)) return True @@ -4569,7 +4588,7 @@ def is_isotopic(self, other): else: verbose('KnotInfo non-unique (%s, %s)' % (sl, k)) else: - common = [l for l in sl if l in ol] + common = [l for l in sl if l in ol] if common: # better don't trust verbose('KnotInfo common: %s' % common) From 42b4b671c326bb4f96e4e8018f4fab2c50d36618 Mon Sep 17 00:00:00 2001 From: Dima Pasechnik Date: Tue, 26 Mar 2024 21:20:09 +0000 Subject: [PATCH 398/518] don't check exit code of "msolve -h" for v. 0.6.5 this is a regression in 0.6.5, reported in https://github.com/algebraic-solving/msolve/issues/131 can be reverted as soon as it's fixed --- src/sage/features/msolve.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sage/features/msolve.py b/src/sage/features/msolve.py index 31cc42e6176..c90f7e5aaf6 100644 --- a/src/sage/features/msolve.py +++ b/src/sage/features/msolve.py @@ -55,10 +55,10 @@ def is_functional(self): """ msolve_out = subprocess.run(["msolve", "-h"], capture_output=True) - if msolve_out.returncode != 0: - return FeatureTestResult(self, False, reason="msolve -h returned " - f"non-zero exit status {msolve_out.returncode}") - elif (msolve_out.stdout[:46] != +# if msolve_out.returncode != 0: +# return FeatureTestResult(self, False, reason="msolve -h returned " +# f"non-zero exit status {msolve_out.returncode}") + if (msolve_out.stdout[:46] != b'\nmsolve library for polynomial system solving\n'): return FeatureTestResult(self, False, reason="output of msolve -h not recognized") From bc80bf97971adbd6532a70fcf0f78469c4af8c35 Mon Sep 17 00:00:00 2001 From: Giorgos Mousa Date: Wed, 27 Mar 2024 00:04:31 +0200 Subject: [PATCH 399/518] Suggestions by tscrim --- src/sage/matroids/matroid.pyx | 40 ++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/src/sage/matroids/matroid.pyx b/src/sage/matroids/matroid.pyx index 08906e02412..0a4c5b4742e 100644 --- a/src/sage/matroids/matroid.pyx +++ b/src/sage/matroids/matroid.pyx @@ -1115,9 +1115,9 @@ cdef class Matroid(SageObject): if certificate: return False, None return False - D = self.dual() + YY = self.dual().independent_r_sets(cd) for X in self.independent_r_sets_iterator(rd): - for Y in D.independent_r_sets_iterator(cd): + for Y in YY: if X.isdisjoint(Y): if N._is_isomorphic(self._minor(contractions=X, deletions=Y)): if certificate: @@ -2317,17 +2317,20 @@ cdef class Matroid(SageObject): ['b', 'd', 'f', 'g'], ['b', 'e', 'g'], ['c', 'd', 'e', 'g'], ['c', 'f', 'g'], ['d', 'e', 'f']] """ - if k and k < self.rank() + 2: - for X in combinations(self.groundset(), k): + rk = self.rank() + if k is None: + start = 0 + stop = rk + 2 + else: + if k >= rk + 2: + return + start = k + stop = k + 1 + for j in range(start, stop): + for X in combinations(self.groundset(), j): X = frozenset(X) if self._is_circuit(X): yield X - elif not k: - for k in range(self.rank() + 2): - for X in combinations(self.groundset(), k): - X = frozenset(X) - if self._is_circuit(X): - yield X cpdef nonspanning_circuits(self) noexcept: """ @@ -2769,7 +2772,7 @@ cdef class Matroid(SageObject): :meth:`M.independent_sets() ` :meth:`M.bases() ` """ - res = [] + cdef list res = [] for X in combinations(self.groundset(), r): X = frozenset(X) if self._rank(X) == len(X): @@ -2966,7 +2969,7 @@ cdef class Matroid(SageObject): r""" Return the `f`-vector of the matroid. - The `f`-*vector* is a vector `(f_0, \dots, f_r)`, where `f_i` is the + The `f`-*vector* is a vector `(f_0, \ldots, f_r)`, where `f_i` is the number of independent sets of rank `i`, and `r` is the rank of the matroid. @@ -2997,8 +3000,8 @@ cdef class Matroid(SageObject): Return the Whitney numbers of the first kind of the matroid. The Whitney numbers of the first kind -- here encoded as a vector - `(w_0=1, ..., w_r)` -- are numbers of alternating sign, where `w_i` is - the value of the coefficient of the `(r-i)`-th degree term of the + `(w_0=1, \ldots, w_r)` -- are numbers of alternating sign, where `w_i` + is the value of the coefficient of the `(r-i)`-th degree term of the matroid's characteristic polynomial. Moreover, `|w_i|` is the number of `(i-1)`-dimensional faces of the broken circuit complex of the matroid. @@ -3026,8 +3029,8 @@ cdef class Matroid(SageObject): Return the Whitney numbers of the second kind of the matroid. The Whitney numbers of the second kind are here encoded as a vector - `(W_0, ..., W_r)`, where `W_i` is the number of flats of rank `i`, and - `r` is the rank of the matroid. + `(W_0, \ldots, W_r)`, where `W_i` is the number of flats of rank `i`, + and `r` is the rank of the matroid. OUTPUT: a list of integers @@ -3204,7 +3207,6 @@ cdef class Matroid(SageObject): and facets {(1, 3, 5), (2, 3, 5), (2, 4, 5), (3, 4, 5)} """ if not self.loops(): - if ordering is None: rev_order = sorted(self.groundset(), key=cmp_elements_key, reverse=True) else: @@ -3311,7 +3313,7 @@ cdef class Matroid(SageObject): over all bases `B` of the matroid. Here `e_i` are the standard basis vectors of `\RR^n`. An arbitrary labelling of the - groundset by `{0,\dots,n-1}` is chosen. + groundset by `{0,\ldots,n-1}` is chosen. .. SEEALSO:: @@ -3358,7 +3360,7 @@ cdef class Matroid(SageObject): over all independent sets `I` of the matroid. Here `e_i` are the standard basis vectors of `\RR^n`. An arbitrary labelling - of the groundset by `{0,\dots,n-1}` is chosen. + of the groundset by `{0,\ldots,n-1}` is chosen. .. SEEALSO:: From 2caff02d88dedfd2954b35671d774d9fd08c0b57 Mon Sep 17 00:00:00 2001 From: Giorgos Mousa Date: Wed, 27 Mar 2024 00:52:19 +0200 Subject: [PATCH 400/518] More suggestions by mkoeppe --- src/sage/matroids/matroid.pyx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/sage/matroids/matroid.pyx b/src/sage/matroids/matroid.pyx index 0a4c5b4742e..2a31bcf7303 100644 --- a/src/sage/matroids/matroid.pyx +++ b/src/sage/matroids/matroid.pyx @@ -3313,7 +3313,7 @@ cdef class Matroid(SageObject): over all bases `B` of the matroid. Here `e_i` are the standard basis vectors of `\RR^n`. An arbitrary labelling of the - groundset by `{0,\ldots,n-1}` is chosen. + groundset by `\{0,\ldots,n-1\}` is chosen. .. SEEALSO:: @@ -3360,7 +3360,7 @@ cdef class Matroid(SageObject): over all independent sets `I` of the matroid. Here `e_i` are the standard basis vectors of `\RR^n`. An arbitrary labelling - of the groundset by `{0,\ldots,n-1}` is chosen. + of the groundset by `\{0,\ldots,n-1\}` is chosen. .. SEEALSO:: @@ -3411,7 +3411,7 @@ cdef class Matroid(SageObject): - ``other`` -- a matroid - ``certificate`` -- boolean (default: ``False``) - OUTPUT: boolean, and, if ``certificate = True``, a dictionary or + OUTPUT: boolean, and, if ``certificate=True``, a dictionary or ``None`` EXAMPLES:: @@ -6081,7 +6081,7 @@ cdef class Matroid(SageObject): False """ if self.rank() >= 2: - for X in combinations(self.groundset(), self.rank()-1): + for X in combinations(self.groundset(), self.rank() - 1): if not self._is_independent(frozenset(X)): return False return True @@ -8343,7 +8343,7 @@ cdef class Matroid(SageObject): matroids. Let `(M_1, M_2, \ldots, M_k)` be a list of matroids where each `M_i` - has groundset `E_i`. The matroid sum `(E_1,I_1),\ldots,(E_n,I_n)` + has groundset `E_i`. The matroid sum of `(E_1,I_1),\ldots,(E_n,I_n)` is a matroid `(E,I)` where `E= \bigsqcup_{i=1}^n E_i` and `I= \bigsqcup_{i=1}^n I_i`. From 1ae0c4aac4809b3cb73e31686cd2198488ce1cb2 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 22 Feb 2024 00:57:27 -0800 Subject: [PATCH 401/518] Remove mention of patchbot, remove 'make buildbot-python3' --- Makefile | 10 +--------- src/.relint.yml | 5 +---- src/bin/sage | 23 ----------------------- src/doc/en/developer/tools.rst | 5 +---- src/tox.ini | 1 - tox.ini | 2 +- 6 files changed, 4 insertions(+), 42 deletions(-) diff --git a/Makefile b/Makefile index 56e11c69f50..228b81a7088 100644 --- a/Makefile +++ b/Makefile @@ -78,14 +78,6 @@ reconfigure: exit 1; \ fi -# This is used to monitor progress towards Python 3 and prevent -# regressions. Should be removed after the full switch to python3. -# -# As of Sage 9.0: keep the build target for backward compatibility, -# but it just runs "make". -buildbot-python3: - $(MAKE) - # Preemptively download all source tarballs of normal packages. download: export SAGE_ROOT=$$(pwd) && \ @@ -376,5 +368,5 @@ list: misc-clean bdist-clean distclean bootstrap-clean maintainer-clean \ test check testoptional testall testlong testoptionallong testallong \ ptest ptestoptional ptestall ptestlong ptestoptionallong ptestallong \ - buildbot-python3 list \ + list \ doc-clean clean sagelib-clean build-clean diff --git a/src/.relint.yml b/src/.relint.yml index 61dcf109c88..80dcb8600c5 100644 --- a/src/.relint.yml +++ b/src/.relint.yml @@ -1,7 +1,4 @@ -# From https://github.com/sagemath/sage-patchbot/blob/master/sage_patchbot/plugins.py -# (simple pattern-exclusion plugins) -# The patterns have been edited slightly because relint tests the regexp to the whole file, -# whereas the patchbots tests them line by line. +# Simple pattern exclusions - name: 'python3: Python3 incompatible code' hint: | diff --git a/src/bin/sage b/src/bin/sage index c1db00734dc..6247531b01b 100755 --- a/src/bin/sage +++ b/src/bin/sage @@ -193,29 +193,6 @@ if [ "$1" = '--nodotsage' ]; then exit $status fi -# Check for '--patchbot' before sourcing sage-env: patchbot needs -# an unclobbered environment before testing unsafe tickets. -if [ "$1" = '-patchbot' -o "$1" = "--patchbot" ]; then - shift - # We ask the Python from Sage where the patchbot is installed. - # We set PYTHONPATH to that directory such that the system Python - # should also find the sage_patchbot package. - cmd='import sage_patchbot as p; import os; print(os.path.dirname(p.__path__[0]))' - export PYTHONPATH=`"$SAGE_ROOT/sage" --python3 -c "$cmd"` - if [ -z "$PYTHONPATH" ]; then - # Something went wrong, assume that the patchbot is not installed - echo >&2 "Error: cannot find installation path for sage_patchbot" - echo >&2 "See https://wiki.sagemath.org/buildbot for instructions" - exit 1 - fi - - shopt -s execfail # Do not exit if "exec" fails - exec python3 -m sage_patchbot.patchbot "$@" - echo >&2 "Error: cannot find a suitable Python 3 program." - echo >&2 "The SageMath patchbot requires a system Python 3 installation." - exit 127 -fi - # Check for '-i' etc. before sourcing sage-env: running "make" # should be run outside of the Sage shell. case "$1" in diff --git a/src/doc/en/developer/tools.rst b/src/doc/en/developer/tools.rst index 3bc3aad8093..7e7c48786e4 100644 --- a/src/doc/en/developer/tools.rst +++ b/src/doc/en/developer/tools.rst @@ -58,7 +58,6 @@ available:: (same as "sage --startuptime") pycodestyle-minimal -- check against Sage's minimal style conventions relint -- check whether some forbidden patterns appear - (includes all patchbot pattern-exclusion plugins) codespell -- check for misspelled words in source code rst -- validate Python docstrings markup as reStructuredText coverage.py -- run the Sage doctester with Coverage.py @@ -180,9 +179,7 @@ As of Sage 9.5, the entire Sage library conforms to this configuration:: congratulations :) When preparing a branch for a Sage ticket, developers should verify that ``./sage -tox -e -pycodestyle-minimal`` passes. When the Sage patchbot runs on the ticket, it will perform similar -coding style checks; but running the check locally reduces the turnaround time from hours -to seconds. +pycodestyle-minimal`` passes. The second configuration is used with the command ``./sage -tox -e pycodestyle`` and runs a more thorough check:: diff --git a/src/tox.ini b/src/tox.ini index 8e562cced8e..0f78cc24836 100644 --- a/src/tox.ini +++ b/src/tox.ini @@ -172,7 +172,6 @@ statistics = True [testenv:relint] description = check whether some forbidden patterns appear - (includes all patchbot pattern-exclusion plugins) # https://github.com/codingjoe/relint # The patterns are in .relint.yml deps = relint diff --git a/tox.ini b/tox.ini index 99d04aad388..df7ec10f674 100644 --- a/tox.ini +++ b/tox.ini @@ -1033,7 +1033,7 @@ allowlist_externals = {[sage_src]allowlist_externals} [testenv:relint] description = - check whether some forbidden patterns appear - similar to patchbot plugins + check whether some forbidden patterns appear passenv = {[sage_src]passenv} envdir = {[sage_src]envdir} commands = {[sage_src]commands} From 38259b6e19dee72a0cd7352622b2067627c4745d Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Wed, 27 Mar 2024 10:44:38 +0100 Subject: [PATCH 402/518] Small changes - Fixed Lint issue - Implemented reviewer feedback - Added checks for symmetry --- .../rings/function_field/function_field.py | 34 +++++++++++-------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/src/sage/rings/function_field/function_field.py b/src/sage/rings/function_field/function_field.py index b328749f8f9..d1652873c71 100644 --- a/src/sage/rings/function_field/function_field.py +++ b/src/sage/rings/function_field/function_field.py @@ -1238,7 +1238,7 @@ def hilbert_symbol(self, a, b, P): For the valuation `\nu` belonging to the completion of the function field at `P`, we compute the valuations `\nu(a)` and `\nu(b)` as well - as elements `a_0` and `b_0` such that, for a uniformizer `\pi` of + as elements `a_0` and `b_0` such that, for a uniformizer `\pi` of `\nu`, the elememts `a*\pi^{-\nu(a))}` and `a_0` respectively the elements `b*\pi^{-\nu(b)}` and `b_0` are congruent modulo `\pi`. Motivated by formula 12.4.10 in [Voi2021]_. @@ -1249,9 +1249,9 @@ def hilbert_symbol(self, a, b, P): INPUT: - - ``a`` and ``b``: elements of this function field + - ``a`` and ``b`` -- elements of this function field - - ``P``: a place of this function field + - ``P`` -- a place of this function field EXAMPLES:: @@ -1270,7 +1270,7 @@ def hilbert_symbol(self, a, b, P): sage: K.hilbert_symbol(c, d, Q) 1 - Check that the Hilbert symbol is bimultiplicative:: + Check that the Hilbert symbol is symmetric and bimultiplicative:: sage: K. = FunctionField(GF(5)); R. = PolynomialRing(K) sage: f = ((x^2 + 2*x + 2)*T^5 + (4*x^2 + 2*x + 3)*T^4 + 3*T^3 + 4*T^2 @@ -1279,19 +1279,23 @@ def hilbert_symbol(self, a, b, P): sage: a = L.random_element() sage: b = L.random_element() sage: c = L.random_element() - sage: P = L.places_above(K.places()[0])[1] - sage: hs_a_c = L.hilbert_symbol(a, c, P) - sage: L.hilbert_symbol(a, b, P) * hs_a_c == L.hilbert_symbol(a, b*c, P) + sage: Q = L.places_above(K.places()[1])[0] + + sage: hP_a_c = L.hilbert_symbol(a, c, P) + sage: hP_a_c == L.hilbert_symbol(c, a, P) + True + sage: L.hilbert_symbol(a, b, P) * hP_a_c == L.hilbert_symbol(a, b*c, P) True - sage: hs_a_c * L.hilbert_symbol(b, c, P) == L.hilbert_symbol(a*b, c, P) + sage: hP_a_c * L.hilbert_symbol(b, c, P) == L.hilbert_symbol(a*b, c, P) True - sage: Q = L.places_above(K.places()[1])[0] - sage: hs_a_c = L.hilbert_symbol(a, c, Q) - sage: L.hilbert_symbol(a, b, Q) * hs_a_c == L.hilbert_symbol(a, b*c, Q) + sage: hQ_a_c = L.hilbert_symbol(a, c, Q) + sage: hQ_a_c == L.hilbert_symbol(c, a, Q) + True + sage: L.hilbert_symbol(a, b, Q) * hQ_a_c == L.hilbert_symbol(a, b*c, Q) True - sage: hs_a_c * L.hilbert_symbol(b, c, Q) == L.hilbert_symbol(a*b, c, Q) + sage: hQ_a_c * L.hilbert_symbol(b, c, Q) == L.hilbert_symbol(a*b, c, Q) True """ if not self.is_global(): @@ -1326,7 +1330,7 @@ def hilbert_symbol(self, a, b, P): # Get the residue field of the completion together with the necssary exponent k = sigma.codomain().base_ring() - e = (k.order() - 1) >> 1 + e = (k.order() - 1) // 2 # Use Euler's criterion to compute the powers of Legendre symbols a_rd_pw = a0**(v_b * e) @@ -1334,7 +1338,9 @@ def hilbert_symbol(self, a, b, P): # Finally, put the result together and transform it into the correct output res = k(-1)**(v_a * v_b * e) * a_rd_pw * b_rd_pw - return (res.is_one() << 1) - 1 + + from sage.rings.integer import Integer + return Integer(1) if res.is_one() else Integer(-1) def extension_constant_field(self, k): """ From 791f82e59bab186c8e3adbf1a732d9dd8c534cb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Wed, 27 Mar 2024 14:01:22 +0100 Subject: [PATCH 403/518] some simplifications in moment-angle complex --- src/sage/topology/moment_angle_complex.py | 66 +++++++++++------------ 1 file changed, 31 insertions(+), 35 deletions(-) diff --git a/src/sage/topology/moment_angle_complex.py b/src/sage/topology/moment_angle_complex.py index 4d900e3ae15..2b80d30fb46 100644 --- a/src/sage/topology/moment_angle_complex.py +++ b/src/sage/topology/moment_angle_complex.py @@ -15,18 +15,18 @@ # (at your option) any later version. # https://www.gnu.org/licenses/ # **************************************************************************** +from itertools import combinations +from sage.homology.homology_group import HomologyGroup from sage.misc.cachefunc import cached_method from sage.misc.lazy_attribute import lazy_attribute -from sage.homology.homology_group import HomologyGroup from sage.rings.integer_ring import ZZ from sage.rings.rational_field import QQ from sage.structure.sage_object import SageObject from sage.structure.unique_representation import UniqueRepresentation -from .cubical_complex import CubicalComplex, cubical_complexes -from .simplicial_complex import SimplicialComplex, copy from sage.topology import simplicial_complex_catalog as simplicial_complexes -from itertools import combinations +from sage.topology.cubical_complex import CubicalComplex, cubical_complexes +from sage.topology.simplicial_complex import SimplicialComplex, copy def _cubical_complex_union(c1, c2): @@ -208,20 +208,15 @@ def __init__(self, simplicial_complex): """ # The underlying simplicial complex self._simplicial_complex = copy(simplicial_complex) - # A dictionary of components indexed by facets - self._components = {} - vertices = self._simplicial_complex.vertices() - # it suffices to perform union only over facets - for facet in self._simplicial_complex.maximal_faces(): - Y = [] - for j in vertices: - if j in facet: - Y.append(simplicial_complexes.Simplex(2)) - else: - Y.append(simplicial_complexes.Sphere(1)) - self._components[facet] = Y + disk = simplicial_complexes.Simplex(2) + circle = simplicial_complexes.Sphere(1) + + # A dictionary of components indexed by facets + self._components = {facet: [disk if j in facet else circle + for j in vertices] + for facet in self._simplicial_complex.maximal_faces()} @lazy_attribute def _moment_angle_complex(self): @@ -233,7 +228,7 @@ def _moment_angle_complex(self): .. WARNING:: - The construction can be very slow, it is not reccomended unless + The construction can be very slow, it is not recommended unless the corresponding simplicial complex has 5 or less vertices. TESTS:: @@ -251,21 +246,20 @@ def _moment_angle_complex(self): sage: Z.cubical_complex() == Z._moment_angle_complex True """ - n = len(self._simplicial_complex.vertices()) - D = [cubical_complexes.Cube(2)] * n - S = [cubical_complexes.Sphere(1)] * n + cube = cubical_complexes.Cube(2) + sphere = cubical_complexes.Sphere(1) moment_angle_complex = CubicalComplex() for component in self._components.values(): - x = D[0] if component[0] == simplicial_complexes.Simplex(2) else S[0] + x = cube if component[0] == simplicial_complexes.Simplex(2) else sphere for j in range(1, len(component)): - y = D[j] if component[j] == simplicial_complexes.Simplex(2) else S[j] + y = cube if component[j] == simplicial_complexes.Simplex(2) else sphere x = x.product(y) moment_angle_complex = _cubical_complex_union(moment_angle_complex, x) return moment_angle_complex - def _repr_(self): + def _repr_(self) -> str: """ Return a string representation of ``self``. @@ -488,15 +482,15 @@ def _homology_group(self, i, base_ring, cohomology, algorithm, verbose, reduced) n = len(vertices) invfac = [] - for j in range(n+1): + for j in range(n + 1): for x in combinations(vertices, j): S = self._simplicial_complex.generated_subcomplex(x) if base_ring.is_field(): - invfac.append(S.homology(i-j-1, base_ring=base_ring, + invfac.append(S.homology(i - j - 1, base_ring=base_ring, cohomology=cohomology, algorithm=algorithm, verbose=verbose, reduced=True).dimension()) else: - invfac.extend(S.homology(i-j-1, base_ring=base_ring, + invfac.extend(S.homology(i - j - 1, base_ring=base_ring, cohomology=cohomology, algorithm=algorithm, verbose=verbose, reduced=True)._original_invts) @@ -633,11 +627,10 @@ def homology(self, dim=None, base_ring=ZZ, cohomology=False, high = dim dims = range(low, high + 1) else: - dims = range(self.dimension()+1) + dims = range(self.dimension() + 1) - answer = {i: self._homology_group(i, base_ring=base_ring, cohomology=cohomology, - algorithm=algorithm, verbose=verbose, reduced=reduced) for i in dims} - return answer + return {i: self._homology_group(i, base_ring=base_ring, cohomology=cohomology, + algorithm=algorithm, verbose=verbose, reduced=reduced) for i in dims} def cohomology(self, dim=None, base_ring=ZZ, algorithm='pari', verbose=False, reduced=True): @@ -698,7 +691,7 @@ def betti(self, dim=None): dict = {} H = self.homology(dim=dim, base_ring=QQ) try: - for n in H.keys(): + for n in H: dict[n] = H[n].dimension() if n == 0: dict[n] += 1 @@ -706,13 +699,16 @@ def betti(self, dim=None): except AttributeError: return H.dimension() - def euler_characteristic(self): + def euler_characteristic(self) -> int: """ Return the Euler characteristic of ``self``. The Euler characteristic is defined as the alternating sum of the Betti numbers of ``self``. + The Euler characteristic of a moment-angle complex is 0 + if the associated simplicial complex is not a simplex. + EXAMPLES:: sage: X = SimplicialComplex([[0,1,2,3,4,5], [0,1,2,3,4,6], @@ -724,8 +720,8 @@ def euler_characteristic(self): sage: Z.euler_characteristic() 1 """ - betti_numbers = self.betti() - return ZZ.sum((-1)**n * betti_numbers[n] for n in range(self.dimension() + 1)) + sc = self.simplicial_complex() + return 1 if sc.dimension() + 1 == len(sc.vertices()) else 0 def product(self, other): """ @@ -810,4 +806,4 @@ def has_trivial_lowest_deg_massey_product(self): Graph([(1, 2), (1, 4), (2, 3), (3, 5), (5, 6), (3, 4), (2, 6), (4, 6)]), ] - return not any(one_skeleton.subgraph_search(g) is not None for g in obstruction_graphs) + return all(one_skeleton.subgraph_search(g) is None for g in obstruction_graphs) From 032c75329e7236eee20f50f0cd7de9deb97afa65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Wed, 27 Mar 2024 14:13:30 +0100 Subject: [PATCH 404/518] one more fix --- src/sage/topology/moment_angle_complex.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/sage/topology/moment_angle_complex.py b/src/sage/topology/moment_angle_complex.py index 2b80d30fb46..09fcbf9707b 100644 --- a/src/sage/topology/moment_angle_complex.py +++ b/src/sage/topology/moment_angle_complex.py @@ -17,6 +17,7 @@ # **************************************************************************** from itertools import combinations +from sage.categories.fields import Fields from sage.homology.homology_group import HomologyGroup from sage.misc.cachefunc import cached_method from sage.misc.lazy_attribute import lazy_attribute @@ -485,7 +486,7 @@ def _homology_group(self, i, base_ring, cohomology, algorithm, verbose, reduced) for j in range(n + 1): for x in combinations(vertices, j): S = self._simplicial_complex.generated_subcomplex(x) - if base_ring.is_field(): + if base_ring in Fields(): invfac.append(S.homology(i - j - 1, base_ring=base_ring, cohomology=cohomology, algorithm=algorithm, verbose=verbose, reduced=True).dimension()) @@ -494,7 +495,7 @@ def _homology_group(self, i, base_ring, cohomology, algorithm, verbose, reduced) cohomology=cohomology, algorithm=algorithm, verbose=verbose, reduced=True)._original_invts) - if base_ring.is_field(): + if base_ring in Fields(): return HomologyGroup(sum(invfac), base_ring) m = len(invfac) From 20ce42e7b188b5d0ad8e62a124ef9d14a2ab73d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Wed, 27 Mar 2024 14:21:45 +0100 Subject: [PATCH 405/518] simplifications in symmetric_ideal.py --- src/sage/rings/polynomial/symmetric_ideal.py | 88 ++++++++++---------- 1 file changed, 45 insertions(+), 43 deletions(-) diff --git a/src/sage/rings/polynomial/symmetric_ideal.py b/src/sage/rings/polynomial/symmetric_ideal.py index f9cae63ea4b..8705fabaf2b 100644 --- a/src/sage/rings/polynomial/symmetric_ideal.py +++ b/src/sage/rings/polynomial/symmetric_ideal.py @@ -57,11 +57,13 @@ # # https://www.gnu.org/licenses/ # **************************************************************************** +import sys + +from sage.categories.fields import Fields +from sage.misc.cachefunc import cached_method from sage.rings.ideal import Ideal_generic from sage.rings.integer import Integer from sage.structure.sequence import Sequence -from sage.misc.cachefunc import cached_method -import sys class SymmetricIdeal(Ideal_generic): @@ -210,7 +212,7 @@ def __repr__(self): Symmetric Ideal (x_1^2 + y_2^2, x_2*x_1*y_3 + x_1*y_4) of Infinite polynomial ring in x, y over Rational Field """ - return "Symmetric Ideal %s of %s" % (self._repr_short(), self.ring()) + return f"Symmetric Ideal {self._repr_short()} of {self.ring()}" def _latex_(self): r""" @@ -224,7 +226,7 @@ def _latex_(self): """ from sage.misc.latex import latex - return '\\left(%s\\right)%s[\\mathfrak{S}_{\\infty}]' % (", ".join(latex(g) for g in self.gens()), latex(self.ring())) + return '\\left({}\\right){}[\\mathfrak{{S}}_{{\\infty}}]'.format(", ".join(latex(g) for g in self.gens()), latex(self.ring())) def _contains_(self, p): """ @@ -265,16 +267,15 @@ def __mul__(self, other): sage: I = X * (x[1]) sage: I*I # indirect doctest # needs sage.combinat Symmetric Ideal (x_1^2, x_2*x_1) of Infinite polynomial ring in x over Rational Field - """ # determine maximal generator index PARENT = self.ring() if (not isinstance(other, self.__class__)) or self.ring() != other.ring(): - if hasattr(other,'gens'): + if hasattr(other, 'gens'): other = SymmetricIdeal(PARENT, other.gens(), coerce=True) other = other.symmetrisation() - sN = max([X.max_index() for X in self.gens()] + [1]) - oN = max([X.max_index() for X in other.gens()] + [1]) + sN = max((X.max_index() for X in self.gens()), default=1) + oN = max((X.max_index() for X in other.gens()), default=1) from sage.combinat.permutation import Permutation P = Permutation(list(range(2, sN + oN + 1)) + [1]) @@ -286,7 +287,7 @@ def __mul__(self, other): # Now, SymL contains all necessary permutations of the second factor OUT = [] for X in self.gens(): - OUT.extend([X*Y for Y in SymL]) + OUT.extend([X * Y for Y in SymL]) return SymmetricIdeal(PARENT, OUT, coerce=False).interreduction() def __pow__(self, n): @@ -351,9 +352,8 @@ def is_maximal(self): """ if not self.base_ring().is_field(): raise NotImplementedError - if len(self.gens()) == 1: - if self.is_trivial() and not self.is_zero(): - return True + if len(self.gens()) == 1 and self.is_trivial() and not self.is_zero(): + return True V = [p.variables() for p in self.gens()] V = [x for x in V if len(x) == 1] V = [str(x[0]).split('_')[0] for x in V] @@ -418,16 +418,17 @@ def reduce(self, I, tailreduce=False): Infinite polynomial ring in x, y over Rational Field """ - if I in self.ring(): # we want to reduce a polynomial by self + if I in self.ring(): # we want to reduce a polynomial by self return self.ring()(I).reduce(self) from sage.rings.polynomial.symmetric_reduction import SymmetricReductionStrategy - if hasattr(I,'gens'): + if hasattr(I, 'gens'): I = I.gens() if (not I): return self I = list(I) - S = SymmetricReductionStrategy(self.ring(),I, tailreduce) - return SymmetricIdeal(self.ring(),[S.reduce(X) for X in self.gens()], coerce=False) + S = SymmetricReductionStrategy(self.ring(), I, tailreduce) + return SymmetricIdeal(self.ring(), [S.reduce(X) for X in self.gens()], + coerce=False) def interreduction(self, tailreduce=True, sorted=False, report=None, RStrat=None): """ @@ -532,7 +533,7 @@ def interreduction(self, tailreduce=True, sorted=False, report=None, RStrat=None print('Symmetric interreduction') from sage.rings.polynomial.symmetric_reduction import SymmetricReductionStrategy if RStrat is None: - RStrat = SymmetricReductionStrategy(self.ring(),tailreduce=tailreduce) + RStrat = SymmetricReductionStrategy(self.ring(), tailreduce=tailreduce) GroundState = RStrat.gens() while True: RStrat.setgens(GroundState) @@ -543,8 +544,8 @@ def interreduction(self, tailreduce=True, sorted=False, report=None, RStrat=None sys.stdout.flush() p = RStrat.reduce(TODO[i], report=report) if p._p != 0: - if p.is_unit(): # self generates all of self.ring() - return SymmetricIdeal(self.ring(),[self.ring().one()], + if p.is_unit(): # self generates all of self.ring() + return SymmetricIdeal(self.ring(), [self.ring().one()], coerce=False) RStrat.add_generator(p, good_input=True) DONE.append(p) @@ -562,7 +563,7 @@ def interreduction(self, tailreduce=True, sorted=False, report=None, RStrat=None if bla == DONE: break TODO = DONE - return SymmetricIdeal(self.ring(),DONE, coerce=False) + return SymmetricIdeal(self.ring(), DONE, coerce=False) def interreduced_basis(self): """ @@ -635,12 +636,12 @@ def symmetrisation(self, N=None, tailreduce=False, report=None, use_full_group=F """ newOUT = self.interreduction(tailreduce=tailreduce, report=report).squeezed() R = self.ring() - OUT = R*() + OUT = R * () if N is None: - N = max([Y.max_index() for Y in newOUT.gens()]+[1]) + N = max((Y.max_index() for Y in newOUT.gens()), default=1) else: N = Integer(N) - if hasattr(R,'_max') and R._max < N: + if hasattr(R, '_max') and R._max < N: R.gen()[N] if report is not None: print("Symmetrise %d polynomials at level %d" % @@ -651,24 +652,26 @@ def symmetrisation(self, N=None, tailreduce=False, report=None, use_full_group=F Gens = self.gens() for P in Permutations(N): NewGens.extend([p**P for p in Gens]) - return (NewGens * R).interreduction(tailreduce=tailreduce,report=report) + return (NewGens * R).interreduction(tailreduce=tailreduce, report=report) from sage.combinat.permutation import Permutation from sage.rings.polynomial.symmetric_reduction import SymmetricReductionStrategy - RStrat = SymmetricReductionStrategy(self.ring(),OUT.gens(),tailreduce=tailreduce) - while OUT != newOUT: + RStrat = SymmetricReductionStrategy(self.ring(), OUT.gens(), + tailreduce=tailreduce) + while newOUT != OUT: OUT = newOUT PermutedGens = list(OUT.gens()) - if not (report is None): + if report is not None: print("Apply permutations") for i in range(1, N): for j in range(i + 1, N + 1): - P = Permutation(((i, j))) + P = Permutation((i, j)) for X in OUT.gens(): - p = RStrat.reduce(X**P,report=report) + p = RStrat.reduce(X**P, report=report) if p._p != 0: PermutedGens.append(p) - RStrat.add_generator(p,good_input=True) - newOUT = (PermutedGens * R).interreduction(tailreduce=tailreduce,report=report) + RStrat.add_generator(p, good_input=True) + newOUT = (PermutedGens * R).interreduction(tailreduce=tailreduce, + report=report) return OUT def symmetric_basis(self): @@ -705,7 +708,7 @@ def normalisation(self): Infinite polynomial ring in x over Rational Field """ - return SymmetricIdeal(self.ring(), [X/X.lc() for X in self.gens() if X._p != 0]) + return SymmetricIdeal(self.ring(), [X / X.lc() for X in self.gens() if X._p != 0]) def squeezed(self): """ @@ -926,17 +929,17 @@ def groebner_basis(self, tailreduce=False, reduced=True, algorithm=None, report= sage: I = ['-y_3'] * Y sage: I.groebner_basis() # needs sage.combinat [y_1] - """ # determine maximal generator index # and construct a common parent for the generators of self if algorithm is None: algorithm = '' PARENT = self.ring() - if not (hasattr(PARENT.base_ring(),'is_field') and PARENT.base_ring().is_field()): + if PARENT.base_ring() not in Fields(): raise TypeError("The base ring (= %s) must be a field" % PARENT.base_ring()) - OUT = self.symmetrisation(tailreduce=tailreduce,report=report,use_full_group=use_full_group) - if not (report is None): + OUT = self.symmetrisation(tailreduce=tailreduce, report=report, + use_full_group=use_full_group) + if report is not None: print("Symmetrisation done") VarList = set() for P in OUT.gens(): @@ -948,11 +951,10 @@ def groebner_basis(self, tailreduce=False, reduced=True, algorithm=None, report= if not VarList: return Sequence([PARENT(0)], PARENT, check=False) from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing - N = max([int(X.split('_')[1]) for X in VarList]+[1]) + N = max((int(X.split('_')[1]) for X in VarList), default=1) - #from sage.combinat.permutation import Permutations while True: - if hasattr(PARENT,'_P'): + if hasattr(PARENT, '_P'): CommonR = PARENT._P else: VarList = set() @@ -965,18 +967,18 @@ def groebner_basis(self, tailreduce=False, reduced=True, algorithm=None, report= VarList.sort(key=PARENT.varname_key, reverse=True) CommonR = PolynomialRing(PARENT._base, VarList, order=PARENT._order) - try: # working around one libsingular bug and one libsingular oddity - DenseIdeal = [CommonR(P._p) if ((CommonR is P._p.parent()) or CommonR.ngens() != P._p.parent().ngens()) else CommonR(repr(P._p)) for P in OUT.gens()]*CommonR + try: # working around one libsingular bug and one libsingular oddity + DenseIdeal = [CommonR(P._p) if ((CommonR is P._p.parent()) or CommonR.ngens() != P._p.parent().ngens()) else CommonR(repr(P._p)) for P in OUT.gens()] * CommonR except Exception: if report is not None: print("working around a libsingular bug") - DenseIdeal = [repr(P._p) for P in OUT.gens()]*CommonR + DenseIdeal = [repr(P._p) for P in OUT.gens()] * CommonR if report is not None: print("Classical Groebner basis") if algorithm != '': print("(using %s)" % algorithm) - newOUT = (DenseIdeal.groebner_basis(algorithm)*PARENT) + newOUT = (DenseIdeal.groebner_basis(algorithm) * PARENT) if report is not None: print("->", len(newOUT.gens()), 'generators') # Symmetrise out to the next index: From 31645a4f35947cc19c145d4fd8312eaef90cc3ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Wed, 27 Mar 2024 18:04:26 +0100 Subject: [PATCH 406/518] some shortcuts using bool --- src/sage/doctest/control.py | 6 +++--- src/sage/graphs/digraph.py | 14 +++++++------- src/sage/graphs/graph.py | 6 +++--- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/sage/doctest/control.py b/src/sage/doctest/control.py index 138e5241024..38c99e6dd72 100644 --- a/src/sage/doctest/control.py +++ b/src/sage/doctest/control.py @@ -946,7 +946,7 @@ def all_doc_sources(): filename.endswith(".pyx") or filename.endswith(".rst")) and not skipfile(opj(SAGE_ROOT, filename), - True if self.options.optional else False, + bool(self.options.optional), if_installed=self.options.if_installed)): self.files.append(os.path.relpath(opj(SAGE_ROOT, filename))) @@ -1008,11 +1008,11 @@ def expand(): dirs.remove(dir) for file in files: if not skipfile(os.path.join(root, file), - True if self.options.optional else False, + bool(self.options.optional), if_installed=self.options.if_installed): yield os.path.join(root, file) else: - if not skipfile(path, True if self.options.optional else False, + if not skipfile(path, bool(self.options.optional), if_installed=self.options.if_installed, log=self.log): # log when directly specified filenames are skipped yield path self.sources = [FileDocTestSource(path, self.options) for path in expand()] diff --git a/src/sage/graphs/digraph.py b/src/sage/graphs/digraph.py index 92ebff7a7c7..e59b62bff9e 100644 --- a/src/sage/graphs/digraph.py +++ b/src/sage/graphs/digraph.py @@ -748,8 +748,8 @@ def __init__(self, data=None, pos=None, loops=None, format=None, if format == 'dig6': if weighted is None: self._weighted = False - self.allow_loops(True if loops else False, check=False) - self.allow_multiple_edges(True if multiedges else False, check=False) + self.allow_loops(bool(loops), check=False) + self.allow_multiple_edges(bool(multiedges), check=False) from .graph_input import from_dig6 from_dig6(self, data) @@ -788,7 +788,7 @@ def __init__(self, data=None, pos=None, loops=None, format=None, loops = any(f(v, v) for v in data[0]) if weighted is None: weighted = False - self.allow_multiple_edges(True if multiedges else False, check=False) + self.allow_multiple_edges(bool(multiedges), check=False) self.allow_loops(loops, check=False) self.add_vertices(data[0]) self.add_edges((u, v) for u in data[0] for v in data[0] if f(u, v)) @@ -832,17 +832,17 @@ def __init__(self, data=None, pos=None, loops=None, format=None, elif format == 'int': if weighted is None: weighted = False - self.allow_loops(True if loops else False, check=False) - self.allow_multiple_edges(True if multiedges else False, + self.allow_loops(bool(loops), check=False) + self.allow_multiple_edges(bool(multiedges), check=False) if data < 0: raise ValueError("the number of vertices cannot be strictly negative") elif data: self.add_vertices(range(data)) elif format == 'list_of_edges': - self.allow_multiple_edges(True if multiedges else False, + self.allow_multiple_edges(bool(multiedges), check=False) - self.allow_loops(True if loops else False, check=False) + self.allow_loops(bool(loops), check=False) self.add_edges(data) else: raise ValueError("unknown input format '{}'".format(format)) diff --git a/src/sage/graphs/graph.py b/src/sage/graphs/graph.py index 729b001a202..e4edc9b6f4c 100644 --- a/src/sage/graphs/graph.py +++ b/src/sage/graphs/graph.py @@ -1223,7 +1223,7 @@ def __init__(self, data=None, pos=None, loops=None, format=None, if weighted is None: weighted = False self.allow_loops(loops, check=False) - self.allow_multiple_edges(True if multiedges else False, check=False) + self.allow_multiple_edges(bool(multiedges), check=False) self.add_vertices(verts) self.add_edges(e for e in itertools.combinations(verts, 2) if f(*e)) if loops: @@ -1254,9 +1254,9 @@ def __init__(self, data=None, pos=None, loops=None, format=None, self.add_vertices(range(data)) elif format == 'list_of_edges': - self.allow_multiple_edges(True if multiedges else False, + self.allow_multiple_edges(bool(multiedges), check=False) - self.allow_loops(True if loops else False, check=False) + self.allow_loops(bool(loops), check=False) self.add_edges(data) else: raise ValueError("Unknown input format '{}'".format(format)) From 449864f0af28020f4251e0467bc7a4875d6a2fb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Wed, 27 Mar 2024 14:30:43 +0100 Subject: [PATCH 407/518] a few more uses of "in Fields()" --- src/sage/combinat/designs/evenly_distributed_sets.pyx | 5 +++-- src/sage/matrix/matrix_gap.pyx | 7 +++---- src/sage/matrix/matrix_mpolynomial_dense.pyx | 7 ++++--- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/sage/combinat/designs/evenly_distributed_sets.pyx b/src/sage/combinat/designs/evenly_distributed_sets.pyx index db567154ab4..013288872e4 100644 --- a/src/sage/combinat/designs/evenly_distributed_sets.pyx +++ b/src/sage/combinat/designs/evenly_distributed_sets.pyx @@ -18,6 +18,7 @@ Classes and methods cimport cython +from sage.categories.fields import Fields from libc.limits cimport UINT_MAX from libc.string cimport memset, memcpy @@ -214,8 +215,8 @@ cdef class EvenlyDistributedSetsBacktracker: cdef unsigned int i,j - if not K.is_field(): - raise ValueError("{} is not a field".format(K)) + if K not in Fields(): + raise ValueError(f"{K} is not a field") cdef unsigned int q = K.cardinality() cdef unsigned int e = k*(k-1)/2 if (q-1) % (2*e) != 0: diff --git a/src/sage/matrix/matrix_gap.pyx b/src/sage/matrix/matrix_gap.pyx index 9e1d1066a48..94abb4aff9e 100644 --- a/src/sage/matrix/matrix_gap.pyx +++ b/src/sage/matrix/matrix_gap.pyx @@ -10,7 +10,7 @@ Wrappers on GAP matrices # (at your option) any later version. # https://www.gnu.org/licenses/ # **************************************************************************** - +from sage.categories.fields import Fields from sage.libs.gap.libgap import libgap from sage.structure.element cimport Matrix from sage.matrix.args cimport MatrixArgs_init @@ -277,12 +277,11 @@ cdef class Matrix_gap(Matrix_dense): [-1/2 1] """ cdef Matrix_gap M - if self._base_ring.is_field(): + if self._base_ring in Fields(): M = self._new(self._nrows, self._ncols) M._libgap = self._libgap.Inverse() return M - else: - return Matrix_dense.__invert__(self) + return Matrix_dense.__invert__(self) cpdef _add_(left, right) noexcept: r""" diff --git a/src/sage/matrix/matrix_mpolynomial_dense.pyx b/src/sage/matrix/matrix_mpolynomial_dense.pyx index be6654e7929..5b4e1c398ee 100644 --- a/src/sage/matrix/matrix_mpolynomial_dense.pyx +++ b/src/sage/matrix/matrix_mpolynomial_dense.pyx @@ -9,14 +9,15 @@ AUTHOR: * Martin Albrecht """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2013 Martin Albrecht # # 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. # https://www.gnu.org/licenses/ -#***************************************************************************** +# **************************************************************************** +from sage.categories.fields import Fields from sage.matrix.matrix_generic_dense cimport Matrix_generic_dense from sage.matrix.matrix2 cimport Matrix @@ -605,7 +606,7 @@ cdef class Matrix_mpolynomial_dense(Matrix_generic_dense): else: R = self._base_ring - if isinstance(R, MPolynomialRing_libsingular) and R.base_ring().is_field(): + if isinstance(R, MPolynomialRing_libsingular) and R.base_ring() in Fields(): singular_det = singular_function("det") d = singular_det(self) From f25c72c614c924f2cb56ad130927825e77c5e492 Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Thu, 28 Mar 2024 02:54:13 +0100 Subject: [PATCH 408/518] Implemented review feedback - Rewrote docstring accordings to suggestions - Fixed typo in comment --- .../rings/function_field/function_field.py | 36 ++++++++++--------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/src/sage/rings/function_field/function_field.py b/src/sage/rings/function_field/function_field.py index d1652873c71..c7538a2bdb7 100644 --- a/src/sage/rings/function_field/function_field.py +++ b/src/sage/rings/function_field/function_field.py @@ -1229,23 +1229,10 @@ def completion(self, place, name=None, prec=None, gen_name=None): def hilbert_symbol(self, a, b, P): r""" - Return the Hilbert symbol `(a,b)_{F_P}` (where `F_P` is the - completion of this function field at the place `P`). + Return the Hilbert symbol `(a,b)_{F_P}` for the local field `F_P`. - The Hilbert symbol `(a,b)_{F_P}` is `0` if one of `a` or `b` is - zero. Otherwise it takes the value `1` if the quaternion algebra - defined by `(a,b)` over `F_P` is split, and `-1` if it is division. - - For the valuation `\nu` belonging to the completion of the function - field at `P`, we compute the valuations `\nu(a)` and `\nu(b)` as well - as elements `a_0` and `b_0` such that, for a uniformizer `\pi` of - `\nu`, the elememts `a*\pi^{-\nu(a))}` and `a_0` respectively the - elements `b*\pi^{-\nu(b)}` and `b_0` are congruent modulo `\pi`. - Motivated by formula 12.4.10 in [Voi2021]_. - - Currently only tested for function fields separable over their base - since places are not fully supported for other function fields. Only - implemented for global function fields of odd characteristic. + The local field `F_P` is the completion of this function field `F` + at the place `P`. INPUT: @@ -1253,6 +1240,21 @@ def hilbert_symbol(self, a, b, P): - ``P`` -- a place of this function field + The Hilbert symbol `(a,b)_{F_P}` is `0` if `a` or `b` is zero. + Otherwise it takes the value `1` if the quaternion algebra defined + by `(a,b)` over `F_P` is split, and `-1` if it is a division ring. + + ALGORITHM: + + For the valuation `\nu = \nu_P` of `F`, we compute the valuations + `\nu(a)` and `\nu(b)` as well as elements `a_0` and `b_0` of the + residue field such that for a uniformizer `\pi` at `P`, + `a\pi^{-\nu(a))}` respectively `b\pi^{-\nu(b)}`has the residue class + `a_0` respectively `b_0` modulo `P`. Then the Hilbert symbol is + computed by formula 12.4.10 in [Voi2021]_. + + Currently only implemented for global function fields. + EXAMPLES:: sage: K. = FunctionField(GF(17)) @@ -1328,7 +1330,7 @@ def hilbert_symbol(self, a, b, P): v_b = ser_b.valuation() b0 = ser_b.coefficients()[0] - # Get the residue field of the completion together with the necssary exponent + # Get the residue field of the completion together with the necessary exponent k = sigma.codomain().base_ring() e = (k.order() - 1) // 2 From 4f1e025ac846849d6aebc9297553db4ec27afaa7 Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Thu, 28 Mar 2024 04:05:18 +0100 Subject: [PATCH 409/518] Small corrections --- src/sage/rings/function_field/function_field.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/sage/rings/function_field/function_field.py b/src/sage/rings/function_field/function_field.py index c7538a2bdb7..16f83aaea84 100644 --- a/src/sage/rings/function_field/function_field.py +++ b/src/sage/rings/function_field/function_field.py @@ -1241,16 +1241,17 @@ def hilbert_symbol(self, a, b, P): - ``P`` -- a place of this function field The Hilbert symbol `(a,b)_{F_P}` is `0` if `a` or `b` is zero. - Otherwise it takes the value `1` if the quaternion algebra defined - by `(a,b)` over `F_P` is split, and `-1` if it is a division ring. + Otherwise it takes the value `1` if the quaternion algebra + defined by `(a,b)` over `F_P` is split, and `-1` if said + algebra is a division ring. ALGORITHM: For the valuation `\nu = \nu_P` of `F`, we compute the valuations `\nu(a)` and `\nu(b)` as well as elements `a_0` and `b_0` of the residue field such that for a uniformizer `\pi` at `P`, - `a\pi^{-\nu(a))}` respectively `b\pi^{-\nu(b)}`has the residue class - `a_0` respectively `b_0` modulo `P`. Then the Hilbert symbol is + `a\pi^{-\nu(a))}` respectively `b\pi^{-\nu(b)}` has the residue class + `a_0` respectively `b_0` modulo `\pi`. Then the Hilbert symbol is computed by formula 12.4.10 in [Voi2021]_. Currently only implemented for global function fields. From 7ef0584411266a7546aa73e51be5ebf52f06024f Mon Sep 17 00:00:00 2001 From: "John H. Palmieri" Date: Wed, 27 Mar 2024 20:45:10 -0700 Subject: [PATCH 410/518] Update approval date --- CODE_OF_CONDUCT.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 5450e81edd0..f7a8e35c9c8 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,9 +1,7 @@ # Code of Conduct # This Code was approved by the Sage community by a vote which ended on -November 24, 2014. - -**The date needs to be updated if these changes are approved.** +March 31, 2024. ## Introduction ## From 51fddd84824d746ded4abd0f92f677babf5704a1 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 27 Mar 2024 21:16:50 -0700 Subject: [PATCH 411/518] src/doc/en/developer/packaging.rst: Suggested changes --- src/doc/en/developer/packaging.rst | 37 +++++++++++++++++------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/src/doc/en/developer/packaging.rst b/src/doc/en/developer/packaging.rst index 14b83d6701c..5d1fce231eb 100644 --- a/src/doc/en/developer/packaging.rst +++ b/src/doc/en/developer/packaging.rst @@ -127,7 +127,7 @@ the following source types: - the file ``package-version.txt`` is optional; - - may be associated with a source tree included in the repository; + - may be associated with a source tree in the repository; - installing the package runs the installation script ``spkg-install`` or ``spkg-install.in`` (see :ref:`section-spkg-install`); @@ -368,8 +368,8 @@ at build time, which should to the appropriate system-specific If an ``spkg-src`` file is present, it indicates that the tarball is not an unmodified third-party tarball (see :ref:`section-spkg-patching`). -It documents how the tarball was generated (either by changing an upstream -tarball or generating it from an repository). As ideally +It documents how the tarball was generated (either by modifying an upstream +tarball or generating it from a repository). As ideally our tarballs are not modified, for most packages there is no ``spkg-src`` file. @@ -1214,10 +1214,13 @@ Modifying third-party code In the Sage distribution, we try to use unpatched original upstream tarballs of stable versions of third-party packages whenever possible. - Sometimes, however, modifications are necessary, either to fix a bug or to make the package build on the platforms supported by Sage. +Only ``normal`` packages can be patched; see :ref:`section-package-source-types`. +If a Python package is currently a ``wheel`` package +and you need to patch it, change it to a ``normal`` package first. + .. _section-spkg-patch-or-repackage: @@ -1278,12 +1281,8 @@ When to patch, when to repackage, when to autoconfiscate .. _section-spkg-patch-from-pr: -Patching sources by importing a pull request from GitHub --------------------------------------------------------- - -Only ``normal`` packages can be patched; see :ref:`section-package-source-types`. -If a Python package is currently a ``wheel`` package -and you need to patch it, change it to a ``normal`` package first. +Preparing a patch by importing a pull request from GitHub +--------------------------------------------------------- In the easiest and quite commmon case, a pull request is already available on the upstream package's GitHub repository. @@ -1297,10 +1296,8 @@ rename it. Modify the ``package-version.txt`` file to indicate the changed patch level; see :ref:`section-spkg-versioning`. This ensures that the package will be rebuilt, -even though its upstream version did not change. This is important because when -you open a pull request in the Sage repository that adds the patch, also -our automatic test runs on GitHub Actions need to rebuild the packages, -and likewise when other people are testing your pull request. +even though its upstream version did not change. This is important in particular +when other people are testing your added patch. Next, test building the package with the patch, for example using ``make build``. You should see a message like ``Applying 64.patch``. Messages such as @@ -1309,9 +1306,17 @@ ignore. They appear when the PR from which you prepared the patch is based on a version that differs from the version that the Sage package uses, or when there are other patches that make changes to the same file. +Be sure add the patch file to your branch using ``git add``. When you commit it, +use a commit message such as +``build/pkgs/cmr: Add https://github.com/discopt/cmr/pull/64 as a patch``. +When you open your PR from this branch, our automatic test runs on GitHub +Actions will automatically rebuild the patched package. + + +.. _section-spkg-patch-manually: -Manual preparation of patches ------------------------------ +Preparing a patch manually +-------------------------- Patches must include documentation in their header (before the first diff hunk), and must have only one "prefix" level in the paths (that is, only one path level From a69a4664c08b98501fe21af7038bc06873c1c0a0 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 27 Mar 2024 21:21:43 -0700 Subject: [PATCH 412/518] src/doc/en/developer/packaging.rst: Move 'inclusion procedure' to the end --- src/doc/en/developer/packaging.rst | 178 ++++++++++++++--------------- 1 file changed, 89 insertions(+), 89 deletions(-) diff --git a/src/doc/en/developer/packaging.rst b/src/doc/en/developer/packaging.rst index 5d1fce231eb..f9e45dd2ffb 100644 --- a/src/doc/en/developer/packaging.rst +++ b/src/doc/en/developer/packaging.rst @@ -1118,95 +1118,6 @@ If all went fine, open a PR with the code under ``SAGE_ROOT/build/pkgs``. -.. _section-inclusion-procedure: - -Inclusion procedure for new and updated packages -================================================ - -Packages that are not part of Sage will first become optional or -experimental (the latter if they will not build on all supported -systems). After they have been in optional for some time without -problems they can be proposed to be included as standard packages in -Sage. - -To propose a package for optional/experimental inclusion please open a GitHub -PR added with labels ``c: packages: experimental`` or ``c: packages: -optional``. The associated code requirements are described in the following -sections. - -After the PR was reviewed and included, optional packages stay in -that status for at least a year, after which they can be proposed to be -included as standard packages in Sage. For this a GitHub PR is opened -with the label ``c: packages: standard``. Then make -a proposal in the Google Group ``sage-devel``. - -Upgrading packages to new upstream versions or with additional patches -includes opening a PR in the respective category too, as described -above. - -License information -------------------- - -If you are patching a standard Sage spkg, then you should make sure that -the license information for that package is up-to-date, both in its -``SPKG.rst`` or ``SPKG.txt`` file and in the file ``SAGE_ROOT/COPYING.txt``. For -example, if you are producing an spkg which upgrades the vanilla source -to a new version, check whether the license changed between versions. - -If an upstream tarball of a package cannot be redistributed for license -reasons, rename it to include the string ``do-not-distribute``. This -will keep the release management scripts from uploading it to the Sage mirrors. - -Sometimes an upstream tarball contains some distributable parts using -a free software license and some non-free parts. In this case, it can -be a good solution to make a custom tarball consisting of only the free -parts; see :ref:`section-spkg-src` and the ``giac`` package as an example. - - -Prerequisites for new standard packages ---------------------------------------- - -For a package to become part of Sage's standard distribution, it -must meet the following requirements: - -- **License**. For standard packages, the license must be compatible - with the GNU General Public License, version 3. The Free Software - Foundation maintains a long list of `licenses and comments about - them `_. - -- **Build Support**. The code must build on all the fully supported - platforms (Linux, macOS); see :ref:`chapter-portability_testing`. - It must be installed either from source as a normal package, - or as a Python (platform-independent) wheel package, see - :ref:`section-package-source-types`. - -- **Quality**. The code should be "better" than any other available - code (that passes the two above criteria), and the authors need to - justify this. The comparison should be made to both Python and other - software. Criteria in passing the quality test include: - - - Speed - - - Documentation - - - Usability - - - Absence of memory leaks - - - Maintainable - - - Portability - - - Reasonable build time, size, dependencies - -- **Previously an optional package**. A new standard package must have - spent some time as an optional package. Or have a good reason why - this is not possible. - -- **Refereeing**. The code must be refereed, as discussed in - :ref:`chapter-github`. - - .. _section-spkg-patching: Modifying third-party code @@ -1429,3 +1340,92 @@ If you really must modify the upstream tarball, then it is recommended that you write a script, called ``spkg-src``, that makes the changes. This not only serves as documentation but also makes it easier to apply the same modifications to future versions. + + +.. _section-inclusion-procedure: + +Inclusion procedure for new and updated packages +================================================ + +Packages that are not part of Sage will first become optional or +experimental (the latter if they will not build on all supported +systems). After they have been in optional for some time without +problems they can be proposed to be included as standard packages in +Sage. + +To propose a package for optional/experimental inclusion please open a GitHub +PR added with labels ``c: packages: experimental`` or ``c: packages: +optional``. The associated code requirements are described in the following +sections. + +After the PR was reviewed and included, optional packages stay in +that status for at least a year, after which they can be proposed to be +included as standard packages in Sage. For this a GitHub PR is opened +with the label ``c: packages: standard``. Then make +a proposal in the Google Group ``sage-devel``. + +Upgrading packages to new upstream versions or with additional patches +includes opening a PR in the respective category too, as described +above. + +License information +------------------- + +If you are patching a standard Sage spkg, then you should make sure that +the license information for that package is up-to-date, both in its +``SPKG.rst`` or ``SPKG.txt`` file and in the file ``SAGE_ROOT/COPYING.txt``. For +example, if you are producing an spkg which upgrades the vanilla source +to a new version, check whether the license changed between versions. + +If an upstream tarball of a package cannot be redistributed for license +reasons, rename it to include the string ``do-not-distribute``. This +will keep the release management scripts from uploading it to the Sage mirrors. + +Sometimes an upstream tarball contains some distributable parts using +a free software license and some non-free parts. In this case, it can +be a good solution to make a custom tarball consisting of only the free +parts; see :ref:`section-spkg-src` and the ``giac`` package as an example. + + +Prerequisites for new standard packages +--------------------------------------- + +For a package to become part of Sage's standard distribution, it +must meet the following requirements: + +- **License**. For standard packages, the license must be compatible + with the GNU General Public License, version 3. The Free Software + Foundation maintains a long list of `licenses and comments about + them `_. + +- **Build Support**. The code must build on all the fully supported + platforms (Linux, macOS); see :ref:`chapter-portability_testing`. + It must be installed either from source as a normal package, + or as a Python (platform-independent) wheel package, see + :ref:`section-package-source-types`. + +- **Quality**. The code should be "better" than any other available + code (that passes the two above criteria), and the authors need to + justify this. The comparison should be made to both Python and other + software. Criteria in passing the quality test include: + + - Speed + + - Documentation + + - Usability + + - Absence of memory leaks + + - Maintainable + + - Portability + + - Reasonable build time, size, dependencies + +- **Previously an optional package**. A new standard package must have + spent some time as an optional package. Or have a good reason why + this is not possible. + +- **Refereeing**. The code must be refereed, as discussed in + :ref:`chapter-github`. From 851fd75f5a21e5b58e403121b0ff27e8ed407bd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Thu, 28 Mar 2024 07:36:33 +0100 Subject: [PATCH 413/518] Apply suggestions from code review 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/symmetric_ideal.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/sage/rings/polynomial/symmetric_ideal.py b/src/sage/rings/polynomial/symmetric_ideal.py index 8705fabaf2b..dc25bb89a89 100644 --- a/src/sage/rings/polynomial/symmetric_ideal.py +++ b/src/sage/rings/polynomial/symmetric_ideal.py @@ -226,7 +226,7 @@ def _latex_(self): """ from sage.misc.latex import latex - return '\\left({}\\right){}[\\mathfrak{{S}}_{{\\infty}}]'.format(", ".join(latex(g) for g in self.gens()), latex(self.ring())) + return r'\left({}\right){}[\mathfrak{{S}}_{{\infty}}]'.format(", ".join(latex(g) for g in self.gens()), latex(self.ring())) def _contains_(self, p): """ @@ -968,7 +968,8 @@ def groebner_basis(self, tailreduce=False, reduced=True, algorithm=None, report= CommonR = PolynomialRing(PARENT._base, VarList, order=PARENT._order) try: # working around one libsingular bug and one libsingular oddity - DenseIdeal = [CommonR(P._p) if ((CommonR is P._p.parent()) or CommonR.ngens() != P._p.parent().ngens()) else CommonR(repr(P._p)) for P in OUT.gens()] * CommonR + DenseIdeal = [CommonR(P._p) if ((CommonR is P._p.parent()) or CommonR.ngens() != P._p.parent().ngens()) else CommonR(repr(P._p)) + for P in OUT.gens()] * CommonR except Exception: if report is not None: print("working around a libsingular bug") @@ -978,7 +979,7 @@ def groebner_basis(self, tailreduce=False, reduced=True, algorithm=None, report= print("Classical Groebner basis") if algorithm != '': print("(using %s)" % algorithm) - newOUT = (DenseIdeal.groebner_basis(algorithm) * PARENT) + newOUT = DenseIdeal.groebner_basis(algorithm) * PARENT if report is not None: print("->", len(newOUT.gens()), 'generators') # Symmetrise out to the next index: From a8671aef339297b7234fba15829ab5ecd032ee7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Thu, 28 Mar 2024 11:26:08 +0100 Subject: [PATCH 414/518] fix linter --- src/sage/rings/polynomial/symmetric_ideal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/polynomial/symmetric_ideal.py b/src/sage/rings/polynomial/symmetric_ideal.py index dc25bb89a89..b8a5c2dea6f 100644 --- a/src/sage/rings/polynomial/symmetric_ideal.py +++ b/src/sage/rings/polynomial/symmetric_ideal.py @@ -968,7 +968,7 @@ def groebner_basis(self, tailreduce=False, reduced=True, algorithm=None, report= CommonR = PolynomialRing(PARENT._base, VarList, order=PARENT._order) try: # working around one libsingular bug and one libsingular oddity - DenseIdeal = [CommonR(P._p) if ((CommonR is P._p.parent()) or CommonR.ngens() != P._p.parent().ngens()) else CommonR(repr(P._p)) + DenseIdeal = [CommonR(P._p) if ((CommonR is P._p.parent()) or CommonR.ngens() != P._p.parent().ngens()) else CommonR(repr(P._p)) for P in OUT.gens()] * CommonR except Exception: if report is not None: From 5d4a91fa85e41eaee7176cac7b1fd4df765193c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Thu, 28 Mar 2024 14:04:13 +0100 Subject: [PATCH 415/518] fix suggested details --- src/sage/topology/moment_angle_complex.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/sage/topology/moment_angle_complex.py b/src/sage/topology/moment_angle_complex.py index 09fcbf9707b..2f9d1d8105a 100644 --- a/src/sage/topology/moment_angle_complex.py +++ b/src/sage/topology/moment_angle_complex.py @@ -483,10 +483,12 @@ def _homology_group(self, i, base_ring, cohomology, algorithm, verbose, reduced) n = len(vertices) invfac = [] + in_field = base_ring in Fields() + for j in range(n + 1): for x in combinations(vertices, j): S = self._simplicial_complex.generated_subcomplex(x) - if base_ring in Fields(): + if in_field: invfac.append(S.homology(i - j - 1, base_ring=base_ring, cohomology=cohomology, algorithm=algorithm, verbose=verbose, reduced=True).dimension()) @@ -495,7 +497,7 @@ def _homology_group(self, i, base_ring, cohomology, algorithm, verbose, reduced) cohomology=cohomology, algorithm=algorithm, verbose=verbose, reduced=True)._original_invts) - if base_ring in Fields(): + if in_field: return HomologyGroup(sum(invfac), base_ring) m = len(invfac) @@ -700,7 +702,7 @@ def betti(self, dim=None): except AttributeError: return H.dimension() - def euler_characteristic(self) -> int: + def euler_characteristic(self): """ Return the Euler characteristic of ``self``. @@ -722,7 +724,8 @@ def euler_characteristic(self) -> int: 1 """ sc = self.simplicial_complex() - return 1 if sc.dimension() + 1 == len(sc.vertices()) else 0 + return (ZZ.one() if sc.dimension() + 1 == len(sc.vertices()) + else ZZ.zero()) def product(self, other): """ From c1d38c77d2faa84282d7148bb09b21164cb8cb48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Thu, 7 Mar 2024 11:16:00 +0100 Subject: [PATCH 416/518] a simple change of detail in unique_representation --- src/sage/structure/unique_representation.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/sage/structure/unique_representation.py b/src/sage/structure/unique_representation.py index ce0e84cd9be..bef7563001e 100644 --- a/src/sage/structure/unique_representation.py +++ b/src/sage/structure/unique_representation.py @@ -1008,7 +1008,7 @@ def __classcall__(cls, *args, **options): True """ instance = typecall(cls, *args, **options) - assert isinstance( instance, cls ) + assert isinstance(instance, cls) if instance.__class__.__reduce__ == CachedRepresentation.__reduce__: instance._reduction = (cls, args, options) return instance @@ -1090,16 +1090,14 @@ def _clear_cache_(cls): sage: c is C(3) False """ - del_list = [] cache = None + # not clear why the loop below is taking the last C for C in cls.mro(): try: cache = C.__classcall__.cache except AttributeError: pass - for k in cache: - if issubclass(k[0][0],cls): - del_list.append(k) + del_list = [k for k in cache if issubclass(k[0][0], cls)] for k in del_list: del cache[k] @@ -1149,6 +1147,7 @@ def __deepcopy__(self, memo): """ return self + def unreduce(cls, args, keywords): """ Calls a class on the given arguments:: @@ -1166,7 +1165,7 @@ def unreduce(cls, args, keywords): class UniqueRepresentation(CachedRepresentation, WithEqualityById): r""" - Classes derived from UniqueRepresentation inherit a unique + Classes derived from ``UniqueRepresentation`` inherit a unique representation behavior for their instances. .. SEEALSO:: From bf0d16cc5d7c7dc524da8b53ec1b0f008a232fb6 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Thu, 28 Mar 2024 17:11:41 +0100 Subject: [PATCH 417/518] provide hash, simplify and harmonise character basis --- src/sage/combinat/sf/character.py | 30 ++++--------------- src/sage/combinat/sf/dual.py | 2 +- src/sage/combinat/sf/orthotriang.py | 2 +- src/sage/combinat/sf/sf.py | 8 ++--- src/sage/combinat/sf/sfa.py | 45 ++++++++++++++++++++++++----- 5 files changed, 49 insertions(+), 38 deletions(-) diff --git a/src/sage/combinat/sf/character.py b/src/sage/combinat/sf/character.py index 091c7bf1511..eb630d2c2f9 100644 --- a/src/sage/combinat/sf/character.py +++ b/src/sage/combinat/sf/character.py @@ -38,24 +38,6 @@ class generic_character(SFA_generic): - 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: ht = SymmetricFunctions(QQ).ht() - sage: ht.construction() - (SymmetricFunctionsFunctor[induced trivial symmetric group character], - Rational Field) - """ - from sage.combinat.sf.sfa import SymmetricFunctionsFunctor - return (SymmetricFunctionsFunctor(self, self.basis_name(), - self._prefix), - self.base_ring()) - def _my_key(self, la): r""" A rank function for partitions. @@ -170,7 +152,7 @@ def _b_power_k(self, k): for d in divisors(k)) -class induced_trivial_character_basis(generic_character): +class InducedTrivialCharacterBasis(generic_character): r""" The induced trivial symmetric group character basis of the symmetric functions. @@ -222,7 +204,7 @@ class induced_trivial_character_basis(generic_character): sage: TestSuite(ht).run() """ - def __init__(self, Sym, pfix): + def __init__(self, Sym): r""" Initialize the basis and register coercions. @@ -242,7 +224,7 @@ def __init__(self, Sym, pfix): """ SFA_generic.__init__(self, Sym, basis_name="induced trivial symmetric group character", - prefix=pfix, graded=False) + prefix="ht", graded=False) self._other = Sym.complete() self._p = Sym.powersum() @@ -400,7 +382,7 @@ def _self_to_other_on_basis(self, lam): return self._other(self._self_to_power_on_basis(lam)) -class irreducible_character_basis(generic_character): +class IrreducibleCharacterBasis(generic_character): r""" The irreducible symmetric group character basis of the symmetric functions. @@ -446,7 +428,7 @@ class irreducible_character_basis(generic_character): sage: TestSuite(st).run() """ - def __init__(self, Sym, pfix): + def __init__(self, Sym): r""" Initialize the basis and register coercions. @@ -469,7 +451,7 @@ def __init__(self, Sym, pfix): """ SFA_generic.__init__(self, Sym, basis_name="irreducible symmetric group character", - prefix=pfix, graded=False) + prefix="st", graded=False) self._other = Sym.Schur() self._p = Sym.powersum() diff --git a/src/sage/combinat/sf/dual.py b/src/sage/combinat/sf/dual.py index cad463cd6d1..e15f17cc734 100644 --- a/src/sage/combinat/sf/dual.py +++ b/src/sage/combinat/sf/dual.py @@ -941,7 +941,7 @@ class DualBasisFunctor(SymmetricFunctionsFunctor): """ def __init__(self, basis): r""" - Initialise the functor. + Initialize the functor. INPUT: diff --git a/src/sage/combinat/sf/orthotriang.py b/src/sage/combinat/sf/orthotriang.py index b14df92bd1d..524095a1b61 100644 --- a/src/sage/combinat/sf/orthotriang.py +++ b/src/sage/combinat/sf/orthotriang.py @@ -294,7 +294,7 @@ class OrthotriangBasisFunctor(SymmetricFunctionsFunctor): """ def __init__(self, basis): r""" - Initialise the functor. + Initialize the functor. INPUT: diff --git a/src/sage/combinat/sf/sf.py b/src/sage/combinat/sf/sf.py index aab65028db6..0afae0b696a 100644 --- a/src/sage/combinat/sf/sf.py +++ b/src/sage/combinat/sf/sf.py @@ -1015,8 +1015,8 @@ def irreducible_symmetric_group_character(self): ....: for rho in Partitions(5)])) [4, 2, 0, 1, -1, 0, -1] """ - from .character import irreducible_character_basis - return irreducible_character_basis(self, 'st') + from .character import IrreducibleCharacterBasis + return IrreducibleCharacterBasis(self) st = irreducible_symmetric_group_character @@ -1071,8 +1071,8 @@ def induced_trivial_character(self): sage: [ht([1]).eval_at_permutation_roots(rho) for rho in Partitions(5)] [0, 1, 0, 2, 1, 3, 5] """ - from .character import induced_trivial_character_basis - return induced_trivial_character_basis(self, 'ht') + from .character import InducedTrivialCharacterBasis + return InducedTrivialCharacterBasis(self) ht = induced_trivial_character diff --git a/src/sage/combinat/sf/sfa.py b/src/sage/combinat/sf/sfa.py index 942f20a0a71..d569b6ce1dd 100644 --- a/src/sage/combinat/sf/sfa.py +++ b/src/sage/combinat/sf/sfa.py @@ -6442,7 +6442,7 @@ class SymmetricFunctionsFunctor(ConstructionFunctor): def __init__(self, basis, name, *args): r""" - Initialise the functor. + Initialize the functor. INPUT: @@ -6466,7 +6466,7 @@ def __init__(self, basis, name, *args): sage: from sage.combinat.sf.sfa import SymmetricFunctionsFunctor sage: R. = ZZ[] sage: qbar = SymmetricFunctions(R).hecke_character() - sage: SymmetricFunctionsFunctor(type(qbar), qbar.basis_name(), q) + sage: SymmetricFunctionsFunctor(qbar, qbar.basis_name(), q) SymmetricFunctionsFunctor[Hecke character with q=q] """ @@ -6549,6 +6549,20 @@ def __eq__(self, other): and self._name == other._name and self._args == other._args) + def __hash__(self): + """ + Return the hash of ``self``. + + EXAMPLES:: + + sage: Sym = SymmetricFunctions(QQ) + sage: F1 = Sym.qbar(q=1/2).construction()[0] + sage: F2 = Sym.qbar(q=1).construction()[0] + sage: hash(F1) == hash(F2) + False + """ + return hash(repr(self)) + def _repr_(self): """ Return a string representation of ``self``. @@ -6572,7 +6586,7 @@ def _repr_(self): class SymmetricFunctionsFamilyFunctor(SymmetricFunctionsFunctor): def __init__(self, basis, family, name, *args): r""" - Initialise the functor. + Initialize the functor. INPUT: @@ -6593,8 +6607,10 @@ def __init__(self, basis, family, name, *args): sage: from sage.combinat.sf.sfa import SymmetricFunctionsFamilyFunctor sage: R. = ZZ[] - sage: H = SymmetricFunctions(R).macdonald(q=1).H() - sage: SymmetricFunctionsFamilyFunctor(type(H), sage.combinat.sf.macdonald.Macdonald, H.basis_name(), 1, t) + sage: basis = SymmetricFunctions(R).macdonald(q=1).H() + sage: family = sage.combinat.sf.macdonald.Macdonald + sage: name = basis.basis_name() + sage: SymmetricFunctionsFamilyFunctor(basis, family, name, 1, t) SymmetricFunctionsFunctor[Macdonald H with q=1] """ super().__init__(basis, name, *args) @@ -6607,9 +6623,7 @@ def _apply_functor(self, R): EXAMPLES:: sage: Sym = SymmetricFunctions(QQ['q','t']) - sage: P = Sym.macdonald(q=1/2).P(); P - Symmetric Functions over Multivariate Polynomial Ring in q, t - over Rational Field in the Macdonald P with q=1/2 basis + sage: P = Sym.macdonald(q=1/2).P() sage: F, R = P.construction() # indirect doctest sage: F(QQ['t']) Symmetric Functions over Univariate Polynomial Ring in t @@ -6642,6 +6656,21 @@ def __eq__(self, other): """ return super().__eq__(other) and self._family == other._family + def __hash__(self): + """ + Return the hash of ``self``. + + EXAMPLES:: + + sage: P1 = SymmetricFunctions(QQ['q','t']).macdonald(q=1).P() + sage: F1 = P1.construction()[0] + sage: P2 = SymmetricFunctions(ZZ['t']).macdonald(q=1).P() + sage: F2 = P2.construction()[0] + sage: hash(F1) == hash(F2) + True + """ + return hash(repr(self)) + ################### def _lmax(x): From f85804e4d475224901bcfc6c06d9fffc389c606c Mon Sep 17 00:00:00 2001 From: Sebastian Oehms <47305845+soehms@users.noreply.github.com> Date: Thu, 28 Mar 2024 17:46:42 +0100 Subject: [PATCH 418/518] 37668: codestyle fix Co-authored-by: Travis Scrimshaw --- src/sage/knots/link.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/knots/link.py b/src/sage/knots/link.py index 681499655de..c5da2dc06cc 100644 --- a/src/sage/knots/link.py +++ b/src/sage/knots/link.py @@ -4342,7 +4342,7 @@ def answer(L): elif not chiral: sym_mut = SymmetryMutant.itself else: - for k in match_lists.keys(): + for k in match_lists: lk = match_lists[k] if proves[k] and L in lk: lk.remove(L) From b053315a4865dbd7fb8dfbdec5e08c82afccc677 Mon Sep 17 00:00:00 2001 From: Sebastian Oehms <47305845+soehms@users.noreply.github.com> Date: Thu, 28 Mar 2024 17:47:21 +0100 Subject: [PATCH 419/518] 37668: codestyle fix second occurence Co-authored-by: Travis Scrimshaw --- src/sage/knots/link.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/knots/link.py b/src/sage/knots/link.py index c5da2dc06cc..0417d2f1bf7 100644 --- a/src/sage/knots/link.py +++ b/src/sage/knots/link.py @@ -4350,7 +4350,7 @@ def answer(L): break if not sym_mut: - for k in match_lists.keys(): + for k in match_lists: lk = match_lists[k] if L in lk: lk.remove(L) From 9312a36e06700dd9f96bb603443d52253be3cce2 Mon Sep 17 00:00:00 2001 From: Sebastian Oehms <47305845+soehms@users.noreply.github.com> Date: Thu, 28 Mar 2024 17:48:06 +0100 Subject: [PATCH 420/518] 37668: codestyle fix whitespace Co-authored-by: Travis Scrimshaw --- src/sage/knots/link.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/knots/link.py b/src/sage/knots/link.py index 0417d2f1bf7..7a3aa6dfcc3 100644 --- a/src/sage/knots/link.py +++ b/src/sage/knots/link.py @@ -4377,7 +4377,7 @@ def answer_unori(S): return S sym_mut = [answer(L)[1] for L in S] - if all(i is SymmetryMutant.mirror_image for i in sym_mut): + if all(i is SymmetryMutant.mirror_image for i in sym_mut): # all matching links are mirrored to self return S, SymmetryMutant.mirror_image if all(i is SymmetryMutant.itself for i in sym_mut): From f98266e90fe26ecbcb87df050ce72f5f8bd76e29 Mon Sep 17 00:00:00 2001 From: Sebastian Oehms <47305845+soehms@users.noreply.github.com> Date: Thu, 28 Mar 2024 17:54:40 +0100 Subject: [PATCH 421/518] 37668: doctest-style Co-authored-by: Travis Scrimshaw --- src/sage/knots/link.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/knots/link.py b/src/sage/knots/link.py index 7a3aa6dfcc3..7ddbe5879c3 100644 --- a/src/sage/knots/link.py +++ b/src/sage/knots/link.py @@ -4510,7 +4510,7 @@ def is_isotopic(self, other): TESTS: - check that :issue:`37668` is fixed:: + Check that :issue:`37668` is fixed:: sage: L = KnotInfo.L6a2_0 sage: L1 = L.link() From bfddab591005d39991aa86c04b9ede9d726ae40d Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Thu, 28 Mar 2024 18:17:58 +0100 Subject: [PATCH 422/518] more doctests, more polishing in character --- src/sage/combinat/sf/character.py | 6 +++--- src/sage/combinat/sf/dual.py | 14 ++++++++++++++ src/sage/combinat/sf/hall_littlewood.py | 11 +++++++++++ src/sage/combinat/sf/hecke.py | 11 +++++++++++ src/sage/combinat/sf/jack.py | 11 +++++++++++ src/sage/combinat/sf/llt.py | 11 +++++++++++ src/sage/combinat/sf/macdonald.py | 11 +++++++++++ src/sage/combinat/sf/orthotriang.py | 21 +++++++++++++++++++++ src/sage/combinat/sf/sf.py | 1 + 9 files changed, 94 insertions(+), 3 deletions(-) diff --git a/src/sage/combinat/sf/character.py b/src/sage/combinat/sf/character.py index eb630d2c2f9..6fd2490ff11 100644 --- a/src/sage/combinat/sf/character.py +++ b/src/sage/combinat/sf/character.py @@ -36,7 +36,7 @@ from sage.rings.integer import Integer -class generic_character(SFA_generic): +class Character_generic(SFA_generic): def _my_key(self, la): r""" @@ -152,7 +152,7 @@ def _b_power_k(self, k): for d in divisors(k)) -class InducedTrivialCharacterBasis(generic_character): +class InducedTrivialCharacterBasis(Character_generic): r""" The induced trivial symmetric group character basis of the symmetric functions. @@ -382,7 +382,7 @@ def _self_to_other_on_basis(self, lam): return self._other(self._self_to_power_on_basis(lam)) -class IrreducibleCharacterBasis(generic_character): +class IrreducibleCharacterBasis(Character_generic): r""" The irreducible symmetric group character basis of the symmetric functions. diff --git a/src/sage/combinat/sf/dual.py b/src/sage/combinat/sf/dual.py index e15f17cc734..aae10602f2d 100644 --- a/src/sage/combinat/sf/dual.py +++ b/src/sage/combinat/sf/dual.py @@ -30,6 +30,14 @@ class SymmetricFunctionAlgebra_dual(classical.SymmetricFunctionAlgebra_classical def __classcall__(cls, dual_basis, scalar, scalar_name="", basis_name=None, prefix=None): """ Normalize the arguments. + + TESTS:: + + sage: w = SymmetricFunctions(QQ).w() + sage: B1 = w.dual_basis() + sage: B2 = w.dual_basis(prefix="d_w") + sage: B1 is B2 + True """ if prefix is None: prefix = 'd_'+dual_basis.prefix() @@ -946,6 +954,12 @@ def __init__(self, basis): INPUT: - ``basis`` -- the basis of the symmetric function algebra + + TESTS:: + + sage: w = SymmetricFunctions(ZZ).witt() + sage: F = w.dual_basis().construction()[0] + sage: TestSuite(F).run() """ self._dual_basis = basis._dual_basis self._basis_name = basis._basis_name diff --git a/src/sage/combinat/sf/hall_littlewood.py b/src/sage/combinat/sf/hall_littlewood.py index 03e6435d28d..8d656533166 100644 --- a/src/sage/combinat/sf/hall_littlewood.py +++ b/src/sage/combinat/sf/hall_littlewood.py @@ -81,6 +81,17 @@ def __repr__(self): def __classcall__(cls, Sym, t='t'): """ Normalize the arguments. + + TESTS:: + + sage: R. = QQ[] + sage: B1 = SymmetricFunctions(R).hall_littlewood() + sage: B2 = SymmetricFunctions(R).hall_littlewood(t) + sage: B3 = SymmetricFunctions(R).hall_littlewood(q) + sage: B1 is B2 + True + sage: B1 == B3 + False """ return super().__classcall__(cls, Sym, Sym.base_ring()(t)) diff --git a/src/sage/combinat/sf/hecke.py b/src/sage/combinat/sf/hecke.py index db6ff7c478a..e0b7f965ba7 100644 --- a/src/sage/combinat/sf/hecke.py +++ b/src/sage/combinat/sf/hecke.py @@ -125,6 +125,17 @@ class HeckeCharacter(SymmetricFunctionAlgebra_multiplicative): def __classcall__(cls, Sym, q='q'): """ Normalize the arguments. + + TESTS:: + + sage: R. = QQ[] + sage: B1 = SymmetricFunctions(R).qbar() + sage: B2 = SymmetricFunctions(R).qbar(q) + sage: B3 = SymmetricFunctions(R).qbar(t) + sage: B1 is B2 + True + sage: B1 == B3 + False """ return super().__classcall__(cls, Sym, Sym.base_ring()(q)) diff --git a/src/sage/combinat/sf/jack.py b/src/sage/combinat/sf/jack.py index 9f06e8d6679..8c62cfb08df 100644 --- a/src/sage/combinat/sf/jack.py +++ b/src/sage/combinat/sf/jack.py @@ -54,6 +54,17 @@ class Jack(UniqueRepresentation): def __classcall__(cls, Sym, t='t'): """ Normalize the arguments. + + TESTS:: + + sage: R. = QQ[] + sage: B1 = SymmetricFunctions(R).jack().P() + sage: B2 = SymmetricFunctions(R).jack(t).P() + sage: B3 = SymmetricFunctions(R).jack(q).P() + sage: B1 is B2 + True + sage: B1 == B3 + False """ return super().__classcall__(cls, Sym, Sym.base_ring()(t)) diff --git a/src/sage/combinat/sf/llt.py b/src/sage/combinat/sf/llt.py index 08c69817188..05a4f50c92a 100644 --- a/src/sage/combinat/sf/llt.py +++ b/src/sage/combinat/sf/llt.py @@ -95,6 +95,17 @@ class LLT_class(UniqueRepresentation): def __classcall__(cls, Sym, k, t='t'): """ Normalize the arguments. + + TESTS:: + + sage: R. = QQ[] + sage: B1 = SymmetricFunctions(R).llt(3).hspin() + sage: B2 = SymmetricFunctions(R).llt(3, t).hspin() + sage: B3 = SymmetricFunctions(R).llt(3, q).hspin() + sage: B1 is B2 + True + sage: B1 == B3 + False """ return super().__classcall__(cls, Sym, k, Sym.base_ring()(t)) diff --git a/src/sage/combinat/sf/macdonald.py b/src/sage/combinat/sf/macdonald.py index c94038e8022..c6021ac25de 100644 --- a/src/sage/combinat/sf/macdonald.py +++ b/src/sage/combinat/sf/macdonald.py @@ -101,6 +101,17 @@ def __repr__(self): def __classcall__(cls, Sym, q='q', t='t'): """ Normalize the arguments. + + TESTS:: + + sage: R. = QQ[] + sage: B1 = SymmetricFunctions(R).macdonald().P() + sage: B2 = SymmetricFunctions(R).macdonald(q, t).P() + sage: B3 = SymmetricFunctions(R).macdonald(t, q).P() + sage: B1 is B2 + True + sage: B1 == B3 + False """ return super().__classcall__(cls, Sym, Sym.base_ring()(q), Sym.base_ring()(t)) diff --git a/src/sage/combinat/sf/orthotriang.py b/src/sage/combinat/sf/orthotriang.py index 524095a1b61..17c636ce5ea 100644 --- a/src/sage/combinat/sf/orthotriang.py +++ b/src/sage/combinat/sf/orthotriang.py @@ -53,6 +53,17 @@ class Element(sfa.SymmetricFunctionAlgebra_generic.Element): def __classcall__(cls, Sym, base, scalar, prefix, basis_name, leading_coeff=None): """ Normalize the arguments. + + TESTS:: + + sage: from sage.combinat.sf.sfa import zee + sage: from sage.combinat.sf.orthotriang import SymmetricFunctionAlgebra_orthotriang + sage: Sym = SymmetricFunctions(QQ) + sage: m = Sym.m() + sage: B1 = SymmetricFunctionAlgebra_orthotriang(Sym, m, zee, 's', 'Schur') + sage: B2 = SymmetricFunctionAlgebra_orthotriang(Sym, m, zee, 's', 'Schur', None) + sage: B1 is B2 + True """ return super().__classcall__(cls, Sym, base, scalar, prefix, basis_name, leading_coeff) @@ -299,6 +310,16 @@ def __init__(self, basis): INPUT: - ``basis`` -- the basis of the symmetric function algebra + + TESTS:: + + sage: from sage.combinat.sf.sfa import zee + sage: from sage.combinat.sf.orthotriang import SymmetricFunctionAlgebra_orthotriang + sage: Sym = SymmetricFunctions(QQ) + sage: m = Sym.m() + sage: s = SymmetricFunctionAlgebra_orthotriang(Sym, m, zee, 's', 'Schur') + sage: F, R = s.construction() + sage: TestSuite(F).run() """ self._basis_name = basis.basis_name() self._sf_base = basis._sf_base diff --git a/src/sage/combinat/sf/sf.py b/src/sage/combinat/sf/sf.py index 0afae0b696a..b6bae12df53 100644 --- a/src/sage/combinat/sf/sf.py +++ b/src/sage/combinat/sf/sf.py @@ -975,6 +975,7 @@ def witt(self, coerce_h=True, coerce_e=False, coerce_p=False): from . import witt return witt.SymmetricFunctionAlgebra_witt(self, coerce_h=coerce_h, coerce_e=coerce_e, coerce_p=coerce_p) w = witt + Witt = witt def irreducible_symmetric_group_character(self): r""" From b8757d814ed5a339ce250e5df0c5c1a8b10baaba Mon Sep 17 00:00:00 2001 From: Sebastian Date: Thu, 28 Mar 2024 18:34:05 +0100 Subject: [PATCH 423/518] 37668: add a comment about the order of SymmetryMutant --- src/sage/knots/knotinfo.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/sage/knots/knotinfo.py b/src/sage/knots/knotinfo.py index 019f56ba6c2..d30fd678735 100644 --- a/src/sage/knots/knotinfo.py +++ b/src/sage/knots/knotinfo.py @@ -371,6 +371,8 @@ def __gt__(self, other): , ] """ + # We use the reversal of the alphabetical order of the values so that + # `itself` occurs before the mirrored cases return self.value < other.value From 9b9059a1dd034580da0075f04354c79d92d70658 Mon Sep 17 00:00:00 2001 From: Dennis Yurichev Date: Thu, 28 Mar 2024 20:56:08 +0100 Subject: [PATCH 424/518] typo --- src/sage/logic/logictable.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/logic/logictable.py b/src/sage/logic/logictable.py index b43fbef66d8..7b7c92372df 100644 --- a/src/sage/logic/logictable.py +++ b/src/sage/logic/logictable.py @@ -62,7 +62,7 @@ True True False True True True True True -Get the letex code for a truth table:: +Get the latex code for a truth table:: sage: latex(s.truthtable(5,11)) \\\begin{tabular}{llll}c & b & a & value \\\hline True & False & True & False \\True & True & False & True \\True & True & True & True\end{tabular} From d63ab2607ee1b63438f64ade948cb90608c5684c Mon Sep 17 00:00:00 2001 From: Kwankyu Lee Date: Fri, 29 Mar 2024 15:40:41 +0900 Subject: [PATCH 425/518] Fix base doc url --- .ci/create-changes-html.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/create-changes-html.sh b/.ci/create-changes-html.sh index cdae3a49df8..40322ca86b9 100755 --- a/.ci/create-changes-html.sh +++ b/.ci/create-changes-html.sh @@ -19,8 +19,8 @@ echo '' >> CHANGES.html cat >> CHANGES.html << EOF