From 6185e8caed47a2958f8713ea2e7338ef8200268e Mon Sep 17 00:00:00 2001 From: DavidAyotte Date: Tue, 7 Feb 2023 11:06:36 +0100 Subject: [PATCH 001/278] first implementation --- src/sage/modular/quasimodform/ring.py | 33 +++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/sage/modular/quasimodform/ring.py b/src/sage/modular/quasimodform/ring.py index 5579aa621d6..4302150eafe 100644 --- a/src/sage/modular/quasimodform/ring.py +++ b/src/sage/modular/quasimodform/ring.py @@ -778,3 +778,36 @@ def from_polynomial(self, polynomial): raise ValueError("the number of variables (%s) of the given polynomial cannot exceed the number of generators (%s) of the quasimodular forms ring" % (nb_var, self.ngens())) gens_dict = {poly_parent.gen(i):self.gen(i) for i in range(0, nb_var)} return self(polynomial.subs(gens_dict)) + + def basis_of_weight(self, weight): + r""" + Return a basis of elements generating the subspace of the given weight. + + EXAMPLES:: + + sage: QM = QuasiModularForms(1) + sage: QM.basis_of_weight(12) + [q - 24*q^2 + 252*q^3 - 1472*q^4 + 4830*q^5 + O(q^6), + 1 + 65520/691*q + 134250480/691*q^2 + 11606736960/691*q^3 + 274945048560/691*q^4 + 3199218815520/691*q^5 + O(q^6), + 1 - 288*q - 129168*q^2 - 1927296*q^3 + 65152656*q^4 + 1535768640*q^5 + O(q^6), + 1 + 432*q + 39312*q^2 - 1711296*q^3 - 14159664*q^4 + 317412000*q^5 + O(q^6), + 1 - 576*q + 21168*q^2 + 308736*q^3 - 15034608*q^4 - 39208320*q^5 + O(q^6), + 1 + 144*q - 17712*q^2 + 524736*q^3 - 2279088*q^4 - 79760160*q^5 + O(q^6), + 1 - 144*q + 8208*q^2 - 225216*q^3 + 2634192*q^4 + 1488672*q^5 + O(q^6)] + sage: QM = QuasiModularForms(Gamma1(3)) + sage: QM.basis_of_weight(3) + [1 + 54*q^2 + 72*q^3 + 432*q^5 + O(q^6), + q + 3*q^2 + 9*q^3 + 13*q^4 + 24*q^5 + O(q^6)] + sage: QM.basis_of_weight(5) + [1 - 90*q^2 - 240*q^3 - 3744*q^5 + O(q^6), + q + 15*q^2 + 81*q^3 + 241*q^4 + 624*q^5 + O(q^6), + 1 - 24*q - 18*q^2 - 1320*q^3 - 5784*q^4 - 10080*q^5 + O(q^6), + q - 21*q^2 - 135*q^3 - 515*q^4 - 1392*q^5 + O(q^6)] + """ + basis = [] + E2 = self.weight_2_eisenstein_series() + for j in range(weight//2): + basis += [f*E2**j for f in self.__modular_forms_subring.modular_forms_of_weight(weight - 2*j).basis()] + if not weight%2: + basis.append(E2**(Integer(weight/2))) + return basis From ef73ceb0d84af46d83e8a507a7e2826056d0f0c5 Mon Sep 17 00:00:00 2001 From: DavidAyotte Date: Thu, 16 Mar 2023 12:08:18 -0400 Subject: [PATCH 002/278] some details --- src/sage/modular/quasimodform/ring.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/sage/modular/quasimodform/ring.py b/src/sage/modular/quasimodform/ring.py index 4302150eafe..76b2b632dff 100644 --- a/src/sage/modular/quasimodform/ring.py +++ b/src/sage/modular/quasimodform/ring.py @@ -781,7 +781,16 @@ def from_polynomial(self, polynomial): def basis_of_weight(self, weight): r""" - Return a basis of elements generating the subspace of the given weight. + Return a basis of elements generating the subspace of the given + weight. + + INPUT: + + - ``weight`` (integer) -- the weight of the subspace + + OUTPUT: + + A list of quasimodular forms of the given weight. EXAMPLES:: @@ -806,8 +815,10 @@ def basis_of_weight(self, weight): """ basis = [] E2 = self.weight_2_eisenstein_series() + M = self.__modular_forms_subring for j in range(weight//2): - basis += [f*E2**j for f in self.__modular_forms_subring.modular_forms_of_weight(weight - 2*j).basis()] + basis += [f*E2**j for f + in M.modular_forms_of_weight(weight - 2*j).basis()] if not weight%2: basis.append(E2**(Integer(weight/2))) return basis From 563b666b8df755dfe741c0692e40c5b81145d128 Mon Sep 17 00:00:00 2001 From: DavidAyotte Date: Sat, 15 Apr 2023 09:42:25 -0400 Subject: [PATCH 003/278] initial implementation --- src/sage/modular/quasimodform/element.py | 39 ++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/sage/modular/quasimodform/element.py b/src/sage/modular/quasimodform/element.py index b92c31722ac..07873379e33 100644 --- a/src/sage/modular/quasimodform/element.py +++ b/src/sage/modular/quasimodform/element.py @@ -294,6 +294,45 @@ def __bool__(self): """ return bool(self._polynomial) + def depth(self): + r""" + Return the depth of the given quasimodular form. + + Note that the quasimodular form must be homogeneous of weight + `k`. Recall that the *depth* is the integer `p` such that + + .. MATH:: + + f = f_0 + f_1 E_2 + \cdots + f_p E_2^p, + + where `f_i` is a modular form of weight `k - 2i` and `f_p` is + nonzero. + + EXAMPLES:: + + sage: QM = QuasiModularForms(1) + sage: E2, E4, E6 = QM.gens() + sage: E2.depth() + 1 + sage: F = E4^2 + E6*E2 + E4*E2^2 + E2^4 + sage: F.depth() + 4 + sage: QM(7/11).depth() + 0 + + TESTS:: + + sage: QM = QuasiModularForms(1) + sage: (QM.0 + QM.1).depth() + Traceback (most recent call last): + ... + ValueError: the given graded quasiform is not an homogeneous element + """ + if not self.is_homogeneous(): + raise ValueError("the given graded quasiform is not an " + "homogeneous element") + return self._polynomial.degree() + def is_zero(self): r""" Return whether the given quasimodular form is zero. From 0f486e3bfb8bb03f76adbec2696cc21805302ca6 Mon Sep 17 00:00:00 2001 From: DavidAyotte Date: Sat, 15 Apr 2023 09:45:05 -0400 Subject: [PATCH 004/278] Revert "initial implementation" This reverts commit 563b666b8df755dfe741c0692e40c5b81145d128. --- src/sage/modular/quasimodform/element.py | 39 ------------------------ 1 file changed, 39 deletions(-) diff --git a/src/sage/modular/quasimodform/element.py b/src/sage/modular/quasimodform/element.py index 07873379e33..b92c31722ac 100644 --- a/src/sage/modular/quasimodform/element.py +++ b/src/sage/modular/quasimodform/element.py @@ -294,45 +294,6 @@ def __bool__(self): """ return bool(self._polynomial) - def depth(self): - r""" - Return the depth of the given quasimodular form. - - Note that the quasimodular form must be homogeneous of weight - `k`. Recall that the *depth* is the integer `p` such that - - .. MATH:: - - f = f_0 + f_1 E_2 + \cdots + f_p E_2^p, - - where `f_i` is a modular form of weight `k - 2i` and `f_p` is - nonzero. - - EXAMPLES:: - - sage: QM = QuasiModularForms(1) - sage: E2, E4, E6 = QM.gens() - sage: E2.depth() - 1 - sage: F = E4^2 + E6*E2 + E4*E2^2 + E2^4 - sage: F.depth() - 4 - sage: QM(7/11).depth() - 0 - - TESTS:: - - sage: QM = QuasiModularForms(1) - sage: (QM.0 + QM.1).depth() - Traceback (most recent call last): - ... - ValueError: the given graded quasiform is not an homogeneous element - """ - if not self.is_homogeneous(): - raise ValueError("the given graded quasiform is not an " - "homogeneous element") - return self._polynomial.degree() - def is_zero(self): r""" Return whether the given quasimodular form is zero. From 3a245618c4b08627e875236533775dcb1cc6cf4a Mon Sep 17 00:00:00 2001 From: DavidAyotte Date: Sat, 15 Apr 2023 09:51:52 -0400 Subject: [PATCH 005/278] implement the depth of a quasimodular form --- src/sage/modular/quasimodform/element.py | 39 ++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/sage/modular/quasimodform/element.py b/src/sage/modular/quasimodform/element.py index b92c31722ac..07873379e33 100644 --- a/src/sage/modular/quasimodform/element.py +++ b/src/sage/modular/quasimodform/element.py @@ -294,6 +294,45 @@ def __bool__(self): """ return bool(self._polynomial) + def depth(self): + r""" + Return the depth of the given quasimodular form. + + Note that the quasimodular form must be homogeneous of weight + `k`. Recall that the *depth* is the integer `p` such that + + .. MATH:: + + f = f_0 + f_1 E_2 + \cdots + f_p E_2^p, + + where `f_i` is a modular form of weight `k - 2i` and `f_p` is + nonzero. + + EXAMPLES:: + + sage: QM = QuasiModularForms(1) + sage: E2, E4, E6 = QM.gens() + sage: E2.depth() + 1 + sage: F = E4^2 + E6*E2 + E4*E2^2 + E2^4 + sage: F.depth() + 4 + sage: QM(7/11).depth() + 0 + + TESTS:: + + sage: QM = QuasiModularForms(1) + sage: (QM.0 + QM.1).depth() + Traceback (most recent call last): + ... + ValueError: the given graded quasiform is not an homogeneous element + """ + if not self.is_homogeneous(): + raise ValueError("the given graded quasiform is not an " + "homogeneous element") + return self._polynomial.degree() + def is_zero(self): r""" Return whether the given quasimodular form is zero. From dbff04d79524a5fac7903a5c5c4b1b3d87c8ba0e Mon Sep 17 00:00:00 2001 From: Miguel Marco Date: Mon, 25 Sep 2023 13:43:21 +0200 Subject: [PATCH 006/278] Implement call method for elements in CDGA's --- src/sage/algebras/commutative_dga.py | 56 ++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/src/sage/algebras/commutative_dga.py b/src/sage/algebras/commutative_dga.py index 953364d6813..456a5dfc0d1 100644 --- a/src/sage/algebras/commutative_dga.py +++ b/src/sage/algebras/commutative_dga.py @@ -1562,6 +1562,62 @@ def dict(self): """ return self.lift().dict() + def __call__(self, *values, **kwargs): + r""" + Evaluate the reduced expression of this element at ``x``, where ``x`` + is either the tuple of values to evaluate in, a dictionary indicating + to which value is each generator evaluated, or keywords giving + the value to which generators should be evaluated. + + INPUT: + + - ``values`` -- (optional) either a tuple or a dictionary + + OUTPUT: + + this element evaluated in the given values + + EXAMPLES:: + + sage: A. = GradedCommutativeAlgebra(QQ, degrees=(1, 2, 2, 3)) + sage: f = x*y - 5*y*z + 7*x*y^2*z^3*t + sage: f(3, y, x^2, x*z) + 3*y + sage: f(x=3) + 21*y^2*z^3*t - 5*y*z + 3*y + sage: f({x:3, z:x^2}) + 3*y + + If the wrong number of values is provided, it results in an error:: + + sage: f(3, 5, y) + Traceback (most recent call last): + ... + ValueError: number of arguments does not match number of variables in parent + + """ + gens = self.parent().gens() + if len(values) == 1 and isinstance(values[0], dict): + images = list(gens) + for (i, g) in enumerate(gens): + if g in values[0].keys(): + images[i] = values[0][g] + elif len(values) == len(gens): + images = list(values) + elif values: + raise ValueError("number of arguments does not match number of variables in parent") + else: + images = list(gens) + for (i, g) in enumerate(gens): + gstr = str(g) + if gstr in kwargs.keys(): + images[i] = kwargs[gstr] + res = 0 + for (m, c) in self.dict().items(): + term = prod([gen**y for (y, gen) in zip(m, images)], c) + res += term + return res + def basis_coefficients(self, total=False): """ Return the coefficients of this homogeneous element with From cdd41427028409ad79484b9590f922933df20548 Mon Sep 17 00:00:00 2001 From: miguelmarco Date: Tue, 24 Oct 2023 15:03:28 +0200 Subject: [PATCH 007/278] Update src/sage/algebras/commutative_dga.py Co-authored-by: Travis Scrimshaw --- src/sage/algebras/commutative_dga.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/algebras/commutative_dga.py b/src/sage/algebras/commutative_dga.py index 456a5dfc0d1..d0434552919 100644 --- a/src/sage/algebras/commutative_dga.py +++ b/src/sage/algebras/commutative_dga.py @@ -1566,7 +1566,7 @@ def __call__(self, *values, **kwargs): r""" Evaluate the reduced expression of this element at ``x``, where ``x`` is either the tuple of values to evaluate in, a dictionary indicating - to which value is each generator evaluated, or keywords giving + to which value is each generator evaluated, or keywords giving the value to which generators should be evaluated. INPUT: From 396c07a85bf0592adf5e7263f9994125929ba624 Mon Sep 17 00:00:00 2001 From: miguelmarco Date: Tue, 24 Oct 2023 15:03:38 +0200 Subject: [PATCH 008/278] Update src/sage/algebras/commutative_dga.py Co-authored-by: Travis Scrimshaw --- src/sage/algebras/commutative_dga.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/algebras/commutative_dga.py b/src/sage/algebras/commutative_dga.py index d0434552919..df8317c24fd 100644 --- a/src/sage/algebras/commutative_dga.py +++ b/src/sage/algebras/commutative_dga.py @@ -1575,7 +1575,7 @@ def __call__(self, *values, **kwargs): OUTPUT: - this element evaluated in the given values + this element evaluated at the given values EXAMPLES:: From bc1e77d016c0adc3c4aaabfed6de6590b52d82c4 Mon Sep 17 00:00:00 2001 From: miguelmarco Date: Tue, 24 Oct 2023 15:03:59 +0200 Subject: [PATCH 009/278] Update src/sage/algebras/commutative_dga.py Co-authored-by: Travis Scrimshaw --- src/sage/algebras/commutative_dga.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/sage/algebras/commutative_dga.py b/src/sage/algebras/commutative_dga.py index df8317c24fd..d2f262ab1a3 100644 --- a/src/sage/algebras/commutative_dga.py +++ b/src/sage/algebras/commutative_dga.py @@ -1594,7 +1594,6 @@ def __call__(self, *values, **kwargs): Traceback (most recent call last): ... ValueError: number of arguments does not match number of variables in parent - """ gens = self.parent().gens() if len(values) == 1 and isinstance(values[0], dict): From f099194abc8e1d598430ac62a2035ac7256e0b52 Mon Sep 17 00:00:00 2001 From: miguelmarco Date: Tue, 24 Oct 2023 15:04:47 +0200 Subject: [PATCH 010/278] Update src/sage/algebras/commutative_dga.py Co-authored-by: Travis Scrimshaw --- src/sage/algebras/commutative_dga.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/algebras/commutative_dga.py b/src/sage/algebras/commutative_dga.py index d2f262ab1a3..619b34e3ab3 100644 --- a/src/sage/algebras/commutative_dga.py +++ b/src/sage/algebras/commutative_dga.py @@ -1599,7 +1599,7 @@ def __call__(self, *values, **kwargs): if len(values) == 1 and isinstance(values[0], dict): images = list(gens) for (i, g) in enumerate(gens): - if g in values[0].keys(): + if g in values[0]: images[i] = values[0][g] elif len(values) == len(gens): images = list(values) From f933f0caec3db617098e73b4d2cfc82a1c445923 Mon Sep 17 00:00:00 2001 From: miguelmarco Date: Tue, 24 Oct 2023 15:05:13 +0200 Subject: [PATCH 011/278] Update src/sage/algebras/commutative_dga.py Co-authored-by: Travis Scrimshaw --- src/sage/algebras/commutative_dga.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/algebras/commutative_dga.py b/src/sage/algebras/commutative_dga.py index 619b34e3ab3..8da7c552c3d 100644 --- a/src/sage/algebras/commutative_dga.py +++ b/src/sage/algebras/commutative_dga.py @@ -1613,7 +1613,7 @@ def __call__(self, *values, **kwargs): images[i] = kwargs[gstr] res = 0 for (m, c) in self.dict().items(): - term = prod([gen**y for (y, gen) in zip(m, images)], c) + term = prod((gen**y for (y, gen) in zip(m, images)), c) res += term return res From 58774c7e3f0b597dbb5881d57c85fdba506ea719 Mon Sep 17 00:00:00 2001 From: Miguel Marco Date: Tue, 24 Oct 2023 17:41:18 +0200 Subject: [PATCH 012/278] Improve documentation --- src/sage/algebras/commutative_dga.py | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/sage/algebras/commutative_dga.py b/src/sage/algebras/commutative_dga.py index 8da7c552c3d..ef10db191ca 100644 --- a/src/sage/algebras/commutative_dga.py +++ b/src/sage/algebras/commutative_dga.py @@ -1571,7 +1571,8 @@ def __call__(self, *values, **kwargs): INPUT: - - ``values`` -- (optional) either a tuple or a dictionary + - ``values`` -- (optional) either the values in which the variables + will be evaluated or a dictionary. OUTPUT: @@ -1594,8 +1595,30 @@ def __call__(self, *values, **kwargs): Traceback (most recent call last): ... ValueError: number of arguments does not match number of variables in parent + + It is also possible to use keywords like this:: + + sage: A. = GradedCommutativeAlgebra(QQ, degrees=(1, 2, 2, 3)) + sage: f = x*y - 5*y*z + 7*x*y^2*z^3*t + sage: f(x=3) + 21*y^2*z^3*t - 5*y*z + 3*y + sage: f(t=x,y=z) + -5*z^2 + x*z + + If both a dictionary and keywords are used, only the dictionary is + considered:: + + sage: A. = GradedCommutativeAlgebra(QQ, degrees=(1, 2, 2, 3)) + sage: f = x*y - 5*y*z + 7*x*y^2*z^3*t + sage: f({x:1}, t=x,y=z) + 7*y^2*z^3*t - 5*y*z + y + """ gens = self.parent().gens() + images = list(gens) + if values and not isinstance(values[0], dict): + for (i, p) in enumerate(values): + images[i] = p if len(values) == 1 and isinstance(values[0], dict): images = list(gens) for (i, g) in enumerate(gens): From 49fdd158a0154fdaad960b57dc30842d445a718b Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Sat, 16 Sep 2023 07:55:02 +0300 Subject: [PATCH 013/278] Compute normalisation const of lattice DGS faster I rewrote the code for `_normalisation_factor_zz`. It used to enumerate short vectors, which might be imprecise even for a short basis. The new method uses `pari.qfrep` to enumerate vectors of a bounded norm + speeds up convergence of series using Poisson summation. TODO: The code doesn't work for non-integral lattices (i.e. over QQ), fixable by rescaling. The code is incorrect for non self-dual lattices, fixable by implementing the correct formula. --- .../discrete_gaussian_lattice.py | 98 +++++++++++++++---- 1 file changed, 79 insertions(+), 19 deletions(-) diff --git a/src/sage/stats/distributions/discrete_gaussian_lattice.py b/src/sage/stats/distributions/discrete_gaussian_lattice.py index f140d18f4c5..46b78589389 100644 --- a/src/sage/stats/distributions/discrete_gaussian_lattice.py +++ b/src/sage/stats/distributions/discrete_gaussian_lattice.py @@ -57,14 +57,15 @@ #*****************************************************************************/ from sage.functions.log import exp -from sage.functions.other import ceil from sage.rings.real_mpfr import RealField from sage.rings.real_mpfr import RR from sage.rings.integer_ring import ZZ from sage.rings.rational_field import QQ from .discrete_gaussian_integer import DiscreteGaussianDistributionIntegerSampler from sage.structure.sage_object import SageObject -from sage.matrix.constructor import matrix, identity_matrix +from sage.misc.functional import sqrt +from sage.symbolic.constants import pi +from sage.matrix.constructor import matrix from sage.modules.free_module import FreeModule from sage.modules.free_module_element import vector @@ -156,7 +157,7 @@ def compute_precision(precision, sigma): INPUT: - - ``precision`` - an integer `> 53` nor ``None``. + - ``precision`` - an integer `>= 53` nor ``None``. - ``sigma`` - if ``precision`` is ``None`` then the precision of ``sigma`` is used. @@ -185,19 +186,20 @@ def compute_precision(precision, sigma): precision = max(53, precision) return precision - def _normalisation_factor_zz(self, tau=3): + def _normalisation_factor_zz(self, tau=None, prec=None): r""" - This function returns an approximation of `∑_{x ∈ \ZZ^n} + This function returns an approximation of `∑_{x ∈ B} \exp(-|x|_2^2/(2σ²))`, i.e. the normalisation factor such that the sum - over all probabilities is 1 for `\ZZⁿ`. - - If this ``self.B`` is not an identity matrix over `\ZZ` a - ``NotImplementedError`` is raised. + over all probabilities is 1 for `B`, via Poisson summation. INPUT: - - ``tau`` -- all vectors `v` with `|v|_∞ ≤ τ·σ` are enumerated - (default: ``3``). + - ``tau`` -- (default: ``None``) all vectors `v` with `|v|_2^2 ≤ τ·σ` + are enumerated; if none is provided, enumerate vectors with + increasing norm until the sum converges to given precision. For high + dimension lattice, this is recommended. + + - ``prec`` -- (default: ``None``) Passed to :meth:`compute_precision` EXAMPLES:: @@ -206,7 +208,7 @@ def _normalisation_factor_zz(self, tau=3): sage: D = DiscreteGaussianDistributionLatticeSampler(ZZ^n, sigma) sage: f = D.f sage: c = D._normalisation_factor_zz(); c - 15.528... + 15.7496.. sage: from collections import defaultdict sage: counter = defaultdict(Integer) @@ -228,15 +230,73 @@ def _normalisation_factor_zz(self, tau=3): sage: while v not in counter: add_samples(1000) sage: while abs(m*f(v)*1.0/c/counter[v] - 1.0) >= 0.2: add_samples(1000) # long time + + sage: D = DiscreteGaussianDistributionLatticeSampler(ZZ^8, 0.5) + sage: D._normalisation_factor_zz(tau=3) + 3.1653... + sage: D._normalisation_factor_zz() + 6.8249... + sage: D = DiscreteGaussianDistributionLatticeSampler(ZZ^8, 1000) + sage: round(D._normalisation_factor_zz(prec=100)) + 1558545456544038969634991553 + + sage: M = Matrix(ZZ, [[1, 3, 0], [-2, 5, 1], [3, -4, 2]]) + sage: D = DiscreteGaussianDistributionLatticeSampler(M, 3) + + sage: M = Matrix(ZZ, [[1, 3, 0], [-2, 5, 1]]) + sage: D = DiscreteGaussianDistributionLatticeSampler(M, 3) """ - if self.B != identity_matrix(ZZ, self.B.nrows()): - raise NotImplementedError("This function is only implemented when B is an identity matrix.") - f = self.f - n = self.B.ncols() + # If σ > 1: + # We use the Fourier transform g(t) of f(x) = exp(-k^2 / 2σ^2), but + # taking the norm of vector t^2 as input, and with norm_factor factored. + # If σ ≤ 1: + # The formula in docstring converges quickly since it has -1 / σ^2 in + # the exponent + def f(x): + # Fun fact: If you remove this R() and delay the call to return, + # It might give an error due to precision error. For example, + # RR(1 + 100 * exp(-5.0 * pi^2)) == 0 + if sigma > 1: + return R(exp(-pi**2 * (2 * sigma**2) * x)) + return R(exp(-x / (2 * sigma**2))) + + if self.B != 1: + # TODO: Implement + raise NotImplementedError("Implement") + sigma = self._sigma - return sum(f(x) for x in _iter_vectors(n, -ceil(tau * sigma), - ceil(tau * sigma))) + prec = DiscreteGaussianDistributionLatticeSampler.compute_precision( + prec, sigma + ) + R = RealField(prec=prec) + if sigma > 1: + B = self.B + # TODO: Take B dual + raise NotImplementedError("oh no") + norm_factor = (sigma * sqrt(2 * pi))**self.B.ncols() + else: + B = self.B + norm_factor = 1 + + # qfrep computes theta series of a quadratic form, which is *half* the + # generating function of number of vectors with given norm (and no 0) + if tau is not None: + freq = self.Q.__pari__().qfrep(tau * sigma, 0) + res = R(1) + for x, fq in enumerate(freq): + res += 2 * ZZ(fq) * f(x + 1) + return R(norm_factor * res) + + res = R(1) + bound = 0 + while True: + bound += 1 + cnt = ZZ(self.Q.__pari__().qfrep(bound, 0)[bound - 1]) + inc = 2 * cnt * f(bound) + if cnt > 0 and res == res + inc: + return R(norm_factor * res) + res += inc def __init__(self, B, sigma=1, c=None, precision=None): r""" @@ -262,7 +322,7 @@ def __init__(self, B, sigma=1, c=None, precision=None): sage: D = DiscreteGaussianDistributionLatticeSampler(ZZ^n, sigma) sage: f = D.f sage: c = D._normalisation_factor_zz(); c - 56.2162803067524 + 56.5486677646162 sage: from collections import defaultdict sage: counter = defaultdict(Integer) From 7e35966ac3a936f1e0aea1f347b43e0208a4cb2f Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Thu, 21 Sep 2023 08:00:48 +0300 Subject: [PATCH 014/278] Compute `_normalisation_factor_zz` for sigma > 0 The formula has been cross-checked with an external C program for a smaller lattice. --- .../discrete_gaussian_lattice.py | 116 ++++++++++++------ 1 file changed, 81 insertions(+), 35 deletions(-) diff --git a/src/sage/stats/distributions/discrete_gaussian_lattice.py b/src/sage/stats/distributions/discrete_gaussian_lattice.py index 46b78589389..504abb81e5e 100644 --- a/src/sage/stats/distributions/discrete_gaussian_lattice.py +++ b/src/sage/stats/distributions/discrete_gaussian_lattice.py @@ -208,7 +208,7 @@ def _normalisation_factor_zz(self, tau=None, prec=None): sage: D = DiscreteGaussianDistributionLatticeSampler(ZZ^n, sigma) sage: f = D.f sage: c = D._normalisation_factor_zz(); c - 15.7496.. + 15.7496... sage: from collections import defaultdict sage: counter = defaultdict(Integer) @@ -241,10 +241,19 @@ def _normalisation_factor_zz(self, tau=None, prec=None): 1558545456544038969634991553 sage: M = Matrix(ZZ, [[1, 3, 0], [-2, 5, 1], [3, -4, 2]]) - sage: D = DiscreteGaussianDistributionLatticeSampler(M, 3) + sage: D = DiscreteGaussianDistributionLatticeSampler(M, 1.7) + sage: D._normalisation_factor_zz() # long time + 7247.1975... sage: M = Matrix(ZZ, [[1, 3, 0], [-2, 5, 1]]) sage: D = DiscreteGaussianDistributionLatticeSampler(M, 3) + sage: D._normalisation_factor_zz() + Traceback (most recent call last): + ... + NotImplementedError: Basis must be a square matrix for now. + + sage: c = vector([3, 7, 1]) + sage: D = DiscreteGaussianDistributionLatticeSampler(M, 3, c = c) """ # If σ > 1: @@ -261,9 +270,13 @@ def f(x): return R(exp(-pi**2 * (2 * sigma**2) * x)) return R(exp(-x / (2 * sigma**2))) - if self.B != 1: - # TODO: Implement - raise NotImplementedError("Implement") + if self.B.nrows() != self.B.ncols(): + raise NotImplementedError("Basis must be a square matrix for now.") + + if not self._c_in_lattice: + raise NotImplementedError("Lattice must contain 0 for now.") + + n = self.B.nrows() sigma = self._sigma prec = DiscreteGaussianDistributionLatticeSampler.compute_precision( @@ -271,29 +284,29 @@ def f(x): ) R = RealField(prec=prec) if sigma > 1: - B = self.B - # TODO: Take B dual - raise NotImplementedError("oh no") - norm_factor = (sigma * sqrt(2 * pi))**self.B.ncols() + det = self.B.det() + norm_factor = (sigma * sqrt(2 * pi))**n / det else: - B = self.B + det = 1 norm_factor = 1 # qfrep computes theta series of a quadratic form, which is *half* the # generating function of number of vectors with given norm (and no 0) + Q = self.B * self.B.T if tau is not None: - freq = self.Q.__pari__().qfrep(tau * sigma, 0) + freq = Q.__pari__().qfrep(tau * sigma, 0) res = R(1) for x, fq in enumerate(freq): - res += 2 * ZZ(fq) * f(x + 1) + res += 2 * ZZ(fq) * f((x + 1) / det**n) return R(norm_factor * res) res = R(1) bound = 0 + # There might still be precision issue but whatever while True: bound += 1 - cnt = ZZ(self.Q.__pari__().qfrep(bound, 0)[bound - 1]) - inc = 2 * cnt * f(bound) + cnt = ZZ(Q.__pari__().qfrep(bound, 0)[bound - 1]) + inc = 2 * cnt * f(bound / det**n) if cnt > 0 and res == res + inc: return R(norm_factor * res) res += inc @@ -311,7 +324,12 @@ def __init__(self, B, sigma=1, c=None, precision=None): - an object with a ``matrix()`` method, e.g. ``ZZ^n``, or - an object where ``matrix(B)`` succeeds, e.g. a list of vectors. - - ``sigma`` -- Gaussian parameter `σ>0`. + - ``sigma`` -- Gaussian parameter, one of the following: + + - a real number `σ > 0`, + - a positive definite matrix `Σ`, or + - any matrix-like ``B``, equivalent to ``Σ = BBᵀ`` + - ``c`` -- center `c`, any vector in `\ZZ^n` is supported, but `c ∈ Λ(B)` is faster. - ``precision`` -- bit precision `≥ 53`. @@ -322,7 +340,7 @@ def __init__(self, B, sigma=1, c=None, precision=None): sage: D = DiscreteGaussianDistributionLatticeSampler(ZZ^n, sigma) sage: f = D.f sage: c = D._normalisation_factor_zz(); c - 56.5486677646162 + 56.5486677646... sage: from collections import defaultdict sage: counter = defaultdict(Integer) @@ -357,7 +375,17 @@ def __init__(self, B, sigma=1, c=None, precision=None): precision = DiscreteGaussianDistributionLatticeSampler.compute_precision(precision, sigma) self._RR = RealField(precision) - self._sigma = self._RR(sigma) + # Check if sigma is a (real) number or a scaled identity matrix + self.is_spherical = True + try: + self._sigma = self._RR(sigma) + except TypeError as e: + print("error:", e) + self._sigma = matrix(self._RR, sigma) + if self._sigma == self._sigma[0, 0]: + self._sigma = self._RR(self._sigma[0, 0]) + else: + self.is_spherical = False try: B = matrix(B) @@ -384,25 +412,32 @@ def __init__(self, B, sigma=1, c=None, precision=None): self.f = lambda x: exp(-(vector(ZZ, B.ncols(), x) - c).norm() ** 2 / (2 * self._sigma ** 2)) - # deal with trivial case first, it is common - if self._G == 1 and self._c == 0: - self._c_in_lattice = True - D = DiscreteGaussianDistributionIntegerSampler(sigma=sigma) - self.D = tuple([D for _ in range(self.B.nrows())]) - self.VS = FreeModule(ZZ, B.nrows()) - return - - w = B.solve_left(c) - if w in ZZ ** B.nrows(): - self._c_in_lattice = True - D = [] - for i in range(self.B.nrows()): - sigma_ = self._sigma / self._G[i].norm() - D.append(DiscreteGaussianDistributionIntegerSampler(sigma=sigma_)) - self.D = tuple(D) - self.VS = FreeModule(ZZ, B.nrows()) + if self.is_spherical: + # deal with trivial case first, it is common + if self._G == 1 and self._c == 0: + self._c_in_lattice = True + D = DiscreteGaussianDistributionIntegerSampler(sigma=sigma) + self.D = tuple([D for _ in range(self.B.nrows())]) + self.VS = FreeModule(ZZ, B.nrows()) + return + + else: + self._c_in_lattice = False + try: + w = B.solve_left(c) + if w in ZZ ** B.nrows(): + self._c_in_lattice = True + D = [] + for i in range(self.B.nrows()): + sigma_ = self._sigma / self._G[i].norm() + D.append(DiscreteGaussianDistributionIntegerSampler(sigma=sigma_)) + self.D = tuple(D) + self.VS = FreeModule(ZZ, B.nrows()) + except ValueError: + pass else: - self._c_in_lattice = False + # TODO: Precompute basis of sqrt(sigma), change _str_ + raise NotImplementedError def __call__(self): r""" @@ -535,3 +570,14 @@ def _call(self): c = c - z * B[i] v = v + z * B[i] return v + + def _call_non_spherical(self): + """ + Non-spherical sampler + + .. note:: + + Do not call this method directly, call :func:`DiscreteGaussianDistributionLatticeSampler.__call__` instead. + """ + # TODO: Implement + raise NotImplementedError From d43a17e9d5804487d77575715cc69a216bfb6981 Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Fri, 22 Sep 2023 16:24:12 +0300 Subject: [PATCH 015/278] Implement non-spherical discrete Gaussian sampling --- src/doc/en/reference/references/index.rst | 5 + src/sage/stats/all.py | 1 + src/sage/stats/distributions/__init__.py | 3 + .../discrete_gaussian_integer.pyx | 3 + .../discrete_gaussian_lattice.py | 312 +++++++++++++++--- .../discrete_gaussian_polynomial.py | 3 + 6 files changed, 276 insertions(+), 51 deletions(-) create mode 100644 src/sage/stats/distributions/__init__.py diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index 475a34f5dea..761f15c38f9 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -5021,6 +5021,11 @@ REFERENCES: small Schroeder paths*, JCTA 125 (2014), 357-378, :doi:`10.1016/j.jcta.2014.04.002` +.. [Pei2010] Peikert, C. (2010). An Efficient and Parallel Gaussian Sampler for + Lattices. In: Rabin, T. (eds) Advances in Cryptology – CRYPTO 2010. + CRYPTO 2010. Lecture Notes in Computer Science, vol 6223. Springer, + Berlin, Heidelberg. :doi:`10.1007/978-3-642-14623-7_5` + .. [Pen2012] \R. Pendavingh, On the evaluation at `(-i, i)` of the Tutte polynomial of a binary matroid. Preprint: :arxiv:`1203.0910` diff --git a/src/sage/stats/all.py b/src/sage/stats/all.py index 69fb8f01abd..3a437b6b6a3 100644 --- a/src/sage/stats/all.py +++ b/src/sage/stats/all.py @@ -2,6 +2,7 @@ from .r import ttest from .basic_stats import (mean, mode, std, variance, median, moving_average) from .hmm import all as hmm +from .distributions import (DGI, DGL, DGP) # We lazy_import the following modules since they import numpy which # slows down sage startup diff --git a/src/sage/stats/distributions/__init__.py b/src/sage/stats/distributions/__init__.py new file mode 100644 index 00000000000..5e9426ef1c0 --- /dev/null +++ b/src/sage/stats/distributions/__init__.py @@ -0,0 +1,3 @@ +from .discrete_gaussian_integer import DGI +from .discrete_gaussian_lattice import DGL +from .discrete_gaussian_polynomial import DGP diff --git a/src/sage/stats/distributions/discrete_gaussian_integer.pyx b/src/sage/stats/distributions/discrete_gaussian_integer.pyx index 87fbe6ca5cd..593f4d7110b 100644 --- a/src/sage/stats/distributions/discrete_gaussian_integer.pyx +++ b/src/sage/stats/distributions/discrete_gaussian_integer.pyx @@ -494,3 +494,6 @@ cdef class DiscreteGaussianDistributionIntegerSampler(SageObject): 'Discrete Gaussian sampler over the Integers with sigma = 3.000000 and c = 2.000000' """ return f"Discrete Gaussian sampler over the Integers with sigma = {self.sigma:.6f} and c = {self.c:.6f}" + + +DGI = DiscreteGaussianDistributionIntegerSampler diff --git a/src/sage/stats/distributions/discrete_gaussian_lattice.py b/src/sage/stats/distributions/discrete_gaussian_lattice.py index 504abb81e5e..32c7d6cbf96 100644 --- a/src/sage/stats/distributions/discrete_gaussian_lattice.py +++ b/src/sage/stats/distributions/discrete_gaussian_lattice.py @@ -14,6 +14,8 @@ - Martin Albrecht (2014-06-28): initial version +- Gareth Ma (2023-09-22): implement non-spherical sampling + EXAMPLES:: sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler @@ -58,12 +60,14 @@ from sage.functions.log import exp from sage.rings.real_mpfr import RealField -from sage.rings.real_mpfr import RR from sage.rings.integer_ring import ZZ from sage.rings.rational_field import QQ -from .discrete_gaussian_integer import DiscreteGaussianDistributionIntegerSampler +from sage.stats.distributions.discrete_gaussian_integer import DGI from sage.structure.sage_object import SageObject +from sage.misc.cachefunc import cached_method from sage.misc.functional import sqrt +from sage.misc.prandom import normalvariate +from sage.misc.verbose import verbose from sage.symbolic.constants import pi from sage.matrix.constructor import matrix from sage.modules.free_module import FreeModule @@ -118,7 +122,7 @@ class DiscreteGaussianDistributionLatticeSampler(SageObject): sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler sage: D = DiscreteGaussianDistributionLatticeSampler(ZZ^10, 3.0); D - Discrete Gaussian sampler with σ = 3.000000, c=(0, 0, 0, 0, 0, 0, 0, 0, 0, 0) over lattice with basis + Discrete Gaussian sampler with Gaussian parameter σ = 3.00000000000000, c=(0, 0, 0, 0, 0, 0, 0, 0, 0, 0) over lattice with basis [1 0 0 0 0 0 0 0 0 0] [0 1 0 0 0 0 0 0 0 0] @@ -207,7 +211,7 @@ def _normalisation_factor_zz(self, tau=None, prec=None): sage: n = 3; sigma = 1.0 sage: D = DiscreteGaussianDistributionLatticeSampler(ZZ^n, sigma) sage: f = D.f - sage: c = D._normalisation_factor_zz(); c + sage: nf = D._normalisation_factor_zz(); nf 15.7496... sage: from collections import defaultdict @@ -223,13 +227,13 @@ def _normalisation_factor_zz(self, tau=None, prec=None): sage: v.set_immutable() sage: while v not in counter: add_samples(1000) - sage: while abs(m*f(v)*1.0/c/counter[v] - 1.0) >= 0.1: add_samples(1000) + sage: while abs(m*f(v)*1.0/nf/counter[v] - 1.0) >= 0.1: add_samples(1000) sage: v = vector(ZZ, n, (-1, 2, 3)) sage: v.set_immutable() sage: while v not in counter: add_samples(1000) - sage: while abs(m*f(v)*1.0/c/counter[v] - 1.0) >= 0.2: add_samples(1000) # long time + sage: while abs(m*f(v)*1.0/nf/counter[v] - 1.0) >= 0.2: add_samples(1000) # long time sage: D = DiscreteGaussianDistributionLatticeSampler(ZZ^8, 0.5) sage: D._normalisation_factor_zz(tau=3) @@ -251,9 +255,6 @@ def _normalisation_factor_zz(self, tau=None, prec=None): Traceback (most recent call last): ... NotImplementedError: Basis must be a square matrix for now. - - sage: c = vector([3, 7, 1]) - sage: D = DiscreteGaussianDistributionLatticeSampler(M, 3, c = c) """ # If σ > 1: @@ -262,21 +263,42 @@ def _normalisation_factor_zz(self, tau=None, prec=None): # If σ ≤ 1: # The formula in docstring converges quickly since it has -1 / σ^2 in # the exponent - def f(x): + def f_or_hat(x): # Fun fact: If you remove this R() and delay the call to return, # It might give an error due to precision error. For example, # RR(1 + 100 * exp(-5.0 * pi^2)) == 0 + if sigma > 1: return R(exp(-pi**2 * (2 * sigma**2) * x)) + return R(exp(-x / (2 * sigma**2))) + if not self.is_spherical: + # NOTE: This is only a poor approximation placeholder. + # It should be easy to implement, since the Fourier transform + # is essentially the same, but I can't figure out how to + # tweak the `.qfrep` call below correctly. TODO. + from warnings import warn + warn("Note: `_normalisation_factor_zz` has not been properly "\ + "implemented for non-spherical distributions.") + import itertools + from sage.functions.log import log + basis = self.B.LLL() + base = vector(ZZ, [v.round() for v in basis.solve_left(self.c)]) + BOUND = max(1, (self._RR(log(10**4, self.n)).ceil() - 1) // 2) + if BOUND > 10: + BOUND = 10 + coords = itertools.product(range(-BOUND, BOUND + 1), repeat=self.n) + return sum(self.f((vector(u) + base) * self.B) for u in coords) + if self.B.nrows() != self.B.ncols(): raise NotImplementedError("Basis must be a square matrix for now.") - if not self._c_in_lattice: + if self.is_spherical and not self._c_in_lattice: raise NotImplementedError("Lattice must contain 0 for now.") - n = self.B.nrows() + if self.B.base_ring() not in (ZZ, QQ): + raise NotImplementedError("Lattice must be integral for now.") sigma = self._sigma prec = DiscreteGaussianDistributionLatticeSampler.compute_precision( @@ -285,19 +307,19 @@ def f(x): R = RealField(prec=prec) if sigma > 1: det = self.B.det() - norm_factor = (sigma * sqrt(2 * pi))**n / det + norm_factor = (sigma * sqrt(2 * pi))**self.n / det else: det = 1 norm_factor = 1 # qfrep computes theta series of a quadratic form, which is *half* the # generating function of number of vectors with given norm (and no 0) - Q = self.B * self.B.T + Q = self.Q if tau is not None: freq = Q.__pari__().qfrep(tau * sigma, 0) res = R(1) for x, fq in enumerate(freq): - res += 2 * ZZ(fq) * f((x + 1) / det**n) + res += 2 * ZZ(fq) * f_or_hat((x + 1) / det**self.n) return R(norm_factor * res) res = R(1) @@ -306,12 +328,47 @@ def f(x): while True: bound += 1 cnt = ZZ(Q.__pari__().qfrep(bound, 0)[bound - 1]) - inc = 2 * cnt * f(bound / det**n) + inc = 2 * cnt * f_or_hat(bound / det**self.n) if cnt > 0 and res == res + inc: return R(norm_factor * res) res += inc - def __init__(self, B, sigma=1, c=None, precision=None): + @cached_method + def _maximal_r(self): + r""" + This function computes the largest value `r > 0` such that `Σ - r²BBᵀ` + is positive definite. + + This is equivalent to finding `λ₁(Σ / Q) = 1 / λₙ(Q / Σ)`, which is done + via the Power iteration method. + """ + # TODO: Write doctest + if self.is_spherical: + raise RuntimeError("You have encountered a bug. File it! :)") + + Q = self.Q.change_ring(self._RR) / self._sigma.change_ring(self._RR) + v = Q[0].change_ring(self._RR) + cnt = 0 + while cnt < 10000: + nv = (Q * v).normalized() + if (nv - v).norm() < 1e-12: + break + v = nv + cnt += 1 + res = (v[0] / (Q * v)[0]).sqrt() + return res + + def _randomise(self, v): + r""" + Randomly round to the latice coset `\ZZ + v` with Gaussian parameter `r` + + REFERENCES: + + - [Pei2010]_, Section 4.1 + """ + return vector(ZZ, [DGI(self.r, c=vi)() for vi in v]) + + def __init__(self, B, sigma=1, c=None, r=None, precision=None): r""" Construct a discrete Gaussian sampler over the lattice `Λ(B)` with parameter ``sigma`` and center `c`. @@ -326,11 +383,18 @@ def __init__(self, B, sigma=1, c=None, precision=None): - ``sigma`` -- Gaussian parameter, one of the following: - - a real number `σ > 0`, - - a positive definite matrix `Σ`, or - - any matrix-like ``B``, equivalent to ``Σ = BBᵀ`` + - a real number `σ > 0` (spherical), + - a positive definite matrix `Σ` (non-spherical), or + - any matrix-like ``S``, equivalent to ``Σ = SSᵀ`` + + - ``c`` -- (default: None) center `c`, any vector in `\ZZ^n` is + supported, but `c ∈ Λ(B)` is faster. + + - ``r`` -- (default: None) rounding parameter `r` as defined in + [Pei2010]_. Ignored for spherical Gaussian parameter. If not provided, + set to be the maximal possible such that Σ - r²BBᵀ is positive + definite. - - ``c`` -- center `c`, any vector in `\ZZ^n` is supported, but `c ∈ Λ(B)` is faster. - ``precision`` -- bit precision `≥ 53`. EXAMPLES:: @@ -339,7 +403,7 @@ def __init__(self, B, sigma=1, c=None, precision=None): sage: n = 2; sigma = 3.0 sage: D = DiscreteGaussianDistributionLatticeSampler(ZZ^n, sigma) sage: f = D.f - sage: c = D._normalisation_factor_zz(); c + sage: nf = D._normalisation_factor_zz(); nf 56.5486677646... sage: from collections import defaultdict @@ -354,17 +418,43 @@ def __init__(self, B, sigma=1, c=None, precision=None): sage: v = vector(ZZ, n, (-3, -3)) sage: v.set_immutable() sage: while v not in counter: add_samples(1000) - sage: while abs(m*f(v)*1.0/c/counter[v] - 1.0) >= 0.1: add_samples(1000) + sage: while abs(m*f(v)*1.0/nf/counter[v] - 1.0) >= 0.1: add_samples(1000) + sage: counter = defaultdict(Integer) sage: v = vector(ZZ, n, (0, 0)) sage: v.set_immutable() sage: while v not in counter: add_samples(1000) - sage: while abs(m*f(v)*1.0/c/counter[v] - 1.0) >= 0.1: add_samples(1000) + sage: while abs(m*f(v)*1.0/nf/counter[v] - 1.0) >= 0.1: add_samples(1000) + + The sampler supports non-spherical covariance in the form of a Gram + matrix. + + sage: n = 3 + sage: Sigma = Matrix(ZZ, [[5, -2, 4], [-2, 10, -5], [4, -5, 5]]) + sage: c = vector(ZZ, [7, 2, 5]) + sage: D = DiscreteGaussianDistributionLatticeSampler(ZZ^n, Sigma, c) + sage: nf = D._normalisation_factor_zz(); nf # This has not been properly implemented + 63.76927... + sage: while v not in counter: add_samples(1000) + sage: while abs(m*f(v)*1.0/nf/counter[v] - 1.0) >= 0.1: add_samples(1000) + + The non-spherical sampler supports offline computation to speed up + sampling. This will be useful when changing the center `c` is supported. + The difference is more significant for larger matrices. For 128x128 the + author of this sentence see a 4x speedup (86s -> 20s). + + sage: D.offline_samples = [] + sage: T = 2**12 + sage: L = [D() for _ in range(T)] # 560ms + sage: D.add_offline_samples(T) # 150ms + sage: L = [D() for _ in range(T)] # 370ms + + We can also initialise with matrix-like objects: sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler sage: qf = QuadraticForm(matrix(3, [2, 1, 1, 1, 2, 1, 1, 1, 2])) sage: D = DiscreteGaussianDistributionLatticeSampler(qf, 3.0); D - Discrete Gaussian sampler with σ = 3.000000, c=(0, 0, 0) over lattice with basis + Discrete Gaussian sampler with Gaussian parameter σ = 3.00000000000000, c=(0, 0, 0) over lattice with basis [2 1 1] [1 2 1] @@ -379,14 +469,18 @@ def __init__(self, B, sigma=1, c=None, precision=None): self.is_spherical = True try: self._sigma = self._RR(sigma) - except TypeError as e: - print("error:", e) + except TypeError: self._sigma = matrix(self._RR, sigma) + # Will it be "annoying" if a matrix Sigma has different behaviour + # sometimes? There should be a parameter in the consrtuctor if self._sigma == self._sigma[0, 0]: self._sigma = self._RR(self._sigma[0, 0]) else: + if not self._sigma.is_positive_definite(): + raise RuntimeError(f"Sigma(={self._sigma}) is not positive definite") self.is_spherical = False + # TODO: Support taking a basis for the covariance try: B = matrix(B) except (TypeError, ValueError): @@ -397,8 +491,11 @@ def __init__(self, B, sigma=1, c=None, precision=None): except AttributeError: pass + self.n = B.ncols() self.B = B + self.Q = B * B.T self._G = B.gram_schmidt()[0] + self._c_in_lattice = False try: c = vector(ZZ, B.ncols(), c) @@ -406,23 +503,20 @@ def __init__(self, B, sigma=1, c=None, precision=None): try: c = vector(QQ, B.ncols(), c) except TypeError: - c = vector(RR, B.ncols(), c) + c = vector(self._RR, B.ncols(), c) self._c = c - self.f = lambda x: exp(-(vector(ZZ, B.ncols(), x) - c).norm() ** 2 / (2 * self._sigma ** 2)) - if self.is_spherical: # deal with trivial case first, it is common - if self._G == 1 and self._c == 0: + if self._G == 1 and self.c == 0: self._c_in_lattice = True - D = DiscreteGaussianDistributionIntegerSampler(sigma=sigma) + D = DGI(sigma=sigma) self.D = tuple([D for _ in range(self.B.nrows())]) self.VS = FreeModule(ZZ, B.nrows()) return else: - self._c_in_lattice = False try: w = B.solve_left(c) if w in ZZ ** B.nrows(): @@ -430,14 +524,37 @@ def __init__(self, B, sigma=1, c=None, precision=None): D = [] for i in range(self.B.nrows()): sigma_ = self._sigma / self._G[i].norm() - D.append(DiscreteGaussianDistributionIntegerSampler(sigma=sigma_)) + D.append(DGI(sigma=sigma_)) self.D = tuple(D) self.VS = FreeModule(ZZ, B.nrows()) except ValueError: pass else: - # TODO: Precompute basis of sqrt(sigma), change _str_ - raise NotImplementedError + # Variables Sigma2 and r are from [Pei2010]_ + # TODO: B is implicitly assumed to be full-rank for the + # non-spherical case. Remove this assumption :) + + # Offline samples of B⁻¹D₁ + self.offline_samples = [] + self.B_inv = B.inverse() + self.sigma_inv = self.sigma.inverse() + + if r is None: + # Compute the maximal r such that (Sigma - r^2 * Q) > 0 + r = self._maximal_r() * 0.9999 + r = self._RR(r) + + Sigma2 = self._sigma - r**2 * self.Q + try: + self.r = r + verbose(f"Computing Cholesky decomposition of a {Sigma2.dimensions()} matrix") + self.B2 = Sigma2.cholesky().T + self.B2_B_inv = self.B2 * self.B_inv + except ValueError: + raise ValueError("Σ₂ is not positive definite. Is your "\ + f"r(={r}) too large? It should be at most "\ + f"{self._maximal_r()}") + def __call__(self): r""" @@ -458,20 +575,42 @@ def __call__(self): sage: norm(mean_L.n() - D.c) < 0.25 # long time True """ - if self._c_in_lattice: + if not self.is_spherical: + v = self._call_non_spherical() + elif self._c_in_lattice: v = self._call_in_lattice() else: v = self._call() v.set_immutable() return v + @property + def f(self): + r""" + Returns closure that computes the Gaussian `\rho_{\Lambda, c, \Sigma}`. + """ + def g(x): + try: + x = vector(ZZ, self.n, x) + except TypeError: + try: + x = vector(QQ, self.n, x) + except TypeError: + x = vector(self._RR, self.n, x) + x -= self.c + if self.is_spherical: + return exp(-x.norm() ** 2 / (2 * self.sigma**2)) + return exp(-x * self.sigma_inv * x / 2) + + return g + @property def sigma(self): r""" Gaussian parameter `σ`. - Samples from this sampler will have expected norm `\sqrt{n}σ` where `n` - is the dimension of the lattice. + If σ is a real number, samples from this sampler will have expected norm + `\sqrt{n}σ` where `n` is the dimension of the lattice. EXAMPLES:: @@ -484,7 +623,8 @@ def sigma(self): @property def c(self): - r"""Center `c`. + r""" + Center `c`. Samples from this sampler will be centered at `c`. @@ -492,7 +632,7 @@ def c(self): sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler sage: D = DiscreteGaussianDistributionLatticeSampler(ZZ^3, 3.0, c=(1,0,0)); D - Discrete Gaussian sampler with σ = 3.000000, c=(1, 0, 0) over lattice with basis + Discrete Gaussian sampler with Gaussian parameter σ = 3.00000000000000, c=(1, 0, 0) over lattice with basis [1 0 0] [0 1 0] @@ -503,21 +643,54 @@ def c(self): """ return self._c + @c.setter + def c(self, _): + r""" + Modifies center `c` + + EXAMPLES:: + + sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler + sage: D = DiscreteGaussianDistributionLatticeSampler(ZZ^3, 3.0, c=(1,0,0)) + sage: D.c = 5 + Traceback (most recent call last): + ... + NotImplementedError: Modifying c is not yet supported! + """ + # TODO: Isolate code to set `c` here, so that the offline part of + # non-spherical sampling can be effectively utilised + raise NotImplementedError("Modifying c is not yet supported!") + def __repr__(self): r""" EXAMPLES:: sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler sage: D = DiscreteGaussianDistributionLatticeSampler(ZZ^3, 3.0, c=(1,0,0)); D - Discrete Gaussian sampler with σ = 3.000000, c=(1, 0, 0) over lattice with basis + Discrete Gaussian sampler with Gaussian parameter σ = 3.00000000000000, c=(1, 0, 0) over lattice with basis [1 0 0] [0 1 0] [0 0 1] + sage: Sigma = Matrix(ZZ, [[10, -6, 1], [-6, 5, -1], [1, -1, 2]]) + sage: D = DiscreteGaussianDistributionLatticeSampler(ZZ^3, Sigma); D + Discrete Gaussian sampler with Gaussian parameter Σ = + [ 10.0000000000000 -6.00000000000000 1.00000000000000] + [-6.00000000000000 5.00000000000000 -1.00000000000000] + [ 1.00000000000000 -1.00000000000000 2.00000000000000], c=(0, 0, 0) over lattice with basis + + [1 0 0] + [0 1 0] + [0 0 1] """ # beware of unicode character in ascii string ! - return "Discrete Gaussian sampler with σ = %f, c=%s over lattice with basis\n\n%s" % (self._sigma, self._c, self.B) + if self.is_spherical: + sigma_str = f"σ = {self._sigma}" + else: + sigma_str = f"Σ =\n{self._sigma}" + return f"Discrete Gaussian sampler with Gaussian parameter {sigma_str}, c={self.c} over lattice with basis\n\n{self.B}" + def _call_in_lattice(self): r""" @@ -537,7 +710,7 @@ def _call_in_lattice(self): Do not call this method directly, call :func:`DiscreteGaussianDistributionLatticeSampler.__call__` instead. """ w = self.VS([d() for d in self.D], check=False) - return w * self.B + self._c + return w * self.B + self.c def _call(self): """ @@ -547,9 +720,9 @@ def _call(self): sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler sage: D = DiscreteGaussianDistributionLatticeSampler(ZZ^3, 3.0, c=(1/2,0,0)) - sage: L = [D._call() for _ in range(2^12)] # long time - sage: mean_L = sum(L) / len(L) # long time - sage: norm(mean_L.n() - D.c) < 0.25 # long time + sage: L = [D._call() for _ in range(2^12)] + sage: mean_L = sum(L) / len(L) + sage: norm(mean_L.n() - D.c) < 0.25 True .. note:: @@ -566,18 +739,55 @@ def _call(self): c_ = c.dot_product(b_) / b_.dot_product(b_) sigma_ = sigma / b_.norm() assert sigma_ > 0 - z = DiscreteGaussianDistributionIntegerSampler(sigma=sigma_, c=c_, algorithm="uniform+online")() + z = DGI(sigma=sigma_, c=c_, algorithm="uniform+online")() c = c - z * B[i] v = v + z * B[i] return v + + def add_offline_samples(self, cnt=1): + """ + Precompute samples from B⁻¹D₁ to be used in :meth:`_call_non_spherical` + + EXAMPLES:: + + sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler + sage: Sigma = Matrix([[5, -2, 4], [-2, 10, -5], [4, -5, 5]]) + sage: D = DiscreteGaussianDistributionLatticeSampler(ZZ^3, Sigma) + sage: assert not D.is_spherical + sage: D.add_offline_samples(2^12) + sage: L = [D() for _ in range(2^12)] # Takes less time + """ + # Just to document the difference with [Pei2010]_, in the paper (Algo 1) + # he samples from Λ + c, but we instead sample from Λ with distribution + # sampled at c (D_{Λ, c}), but that's the same as c + D_{Λ - c} + # Also, we use row notation instead of column notation. Sorry. + for _ in range(cnt): + coord = [normalvariate(mu=0, sigma=1) for _ in range(self.n)] + self.offline_samples.append(vector(self._RR, coord) * self.B2_B_inv) + def _call_non_spherical(self): """ - Non-spherical sampler + Return a new sample. + + EXAMPLES:: + + sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler + sage: Sigma = Matrix([[5, -2, 4], [-2, 10, -5], [4, -5, 5]]) + sage: D = DiscreteGaussianDistributionLatticeSampler(ZZ^3, Sigma, c=(1/2,0,0)) + sage: L = [D._call_non_spherical() for _ in range(2^12)] + sage: mean_L = sum(L) / len(L) + sage: norm(mean_L.n() - D.c) < 0.25 + True .. note:: Do not call this method directly, call :func:`DiscreteGaussianDistributionLatticeSampler.__call__` instead. """ - # TODO: Implement - raise NotImplementedError + if len(self.offline_samples) == 0: + self.add_offline_samples() + vec = self.c * self.B_inv - self.offline_samples.pop() + return self._randomise(vec) * self.B + + +DGL = DiscreteGaussianDistributionLatticeSampler diff --git a/src/sage/stats/distributions/discrete_gaussian_polynomial.py b/src/sage/stats/distributions/discrete_gaussian_polynomial.py index 0f4e7e59361..d61399b2c06 100644 --- a/src/sage/stats/distributions/discrete_gaussian_polynomial.py +++ b/src/sage/stats/distributions/discrete_gaussian_polynomial.py @@ -139,3 +139,6 @@ def _repr_(self): """ # beware of unicode character in ascii string ! return "Discrete Gaussian sampler for polynomials of degree < %d with σ=%f in each component" % (self.n, self.D.sigma) + + +DGP = DiscreteGaussianDistributionPolynomialSampler From ad02163097bae65dcf7363d46bbb1604e0acbd9f Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Mon, 25 Sep 2023 11:59:13 +0300 Subject: [PATCH 016/278] Add doctest to `._maximal_r()` --- .../discrete_gaussian_lattice.py | 118 ++++++++++-------- 1 file changed, 65 insertions(+), 53 deletions(-) diff --git a/src/sage/stats/distributions/discrete_gaussian_lattice.py b/src/sage/stats/distributions/discrete_gaussian_lattice.py index 32c7d6cbf96..b177886a0b9 100644 --- a/src/sage/stats/distributions/discrete_gaussian_lattice.py +++ b/src/sage/stats/distributions/discrete_gaussian_lattice.py @@ -18,8 +18,8 @@ EXAMPLES:: - sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler - sage: D = DiscreteGaussianDistributionLatticeSampler(ZZ^10, 3.0) + sage: from sage.stats.all import DGL + sage: D = DGL(ZZ^10, 3.0) sage: D(), D(), D() # random ((3, 0, -5, 0, -1, -3, 3, 3, -7, 2), (4, 0, 1, -2, -4, -4, 4, 0, 1, -4), (-3, 0, 4, 5, 0, 1, 3, 2, 0, -1)) sage: a = D() @@ -120,8 +120,8 @@ class DiscreteGaussianDistributionLatticeSampler(SageObject): EXAMPLES:: - sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler - sage: D = DiscreteGaussianDistributionLatticeSampler(ZZ^10, 3.0); D + sage: from sage.stats.all import DGL + sage: D = DGL(ZZ^10, 3.0); D Discrete Gaussian sampler with Gaussian parameter σ = 3.00000000000000, c=(0, 0, 0, 0, 0, 0, 0, 0, 0, 0) over lattice with basis [1 0 0 0 0 0 0 0 0 0] @@ -138,10 +138,10 @@ class DiscreteGaussianDistributionLatticeSampler(SageObject): We plot a histogram:: - sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler + sage: from sage.stats.all import DGL sage: import warnings sage: warnings.simplefilter('ignore', UserWarning) - sage: D = DiscreteGaussianDistributionLatticeSampler(identity_matrix(2), 3.0) + sage: D = DGL(identity_matrix(2), 3.0) sage: S = [D() for _ in range(2^12)] sage: l = [vector(v.list() + [S.count(v)]) for v in set(S)] sage: list_plot3d(l, point_list=True, interpolation='nn') @@ -167,18 +167,18 @@ def compute_precision(precision, sigma): EXAMPLES:: - sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler - sage: DiscreteGaussianDistributionLatticeSampler.compute_precision(100, RR(3)) + sage: from sage.stats.all import DGL + sage: DGL.compute_precision(100, RR(3)) 100 - sage: DiscreteGaussianDistributionLatticeSampler.compute_precision(100, RealField(200)(3)) + sage: DGL.compute_precision(100, RealField(200)(3)) 100 - sage: DiscreteGaussianDistributionLatticeSampler.compute_precision(100, 3) + sage: DGL.compute_precision(100, 3) 100 - sage: DiscreteGaussianDistributionLatticeSampler.compute_precision(None, RR(3)) + sage: DGL.compute_precision(None, RR(3)) 53 - sage: DiscreteGaussianDistributionLatticeSampler.compute_precision(None, RealField(200)(3)) + sage: DGL.compute_precision(None, RealField(200)(3)) 200 - sage: DiscreteGaussianDistributionLatticeSampler.compute_precision(None, 3) + sage: DGL.compute_precision(None, 3) 53 """ @@ -207,9 +207,9 @@ def _normalisation_factor_zz(self, tau=None, prec=None): EXAMPLES:: - sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler + sage: from sage.stats.all import DGL sage: n = 3; sigma = 1.0 - sage: D = DiscreteGaussianDistributionLatticeSampler(ZZ^n, sigma) + sage: D = DGL(ZZ^n, sigma) sage: f = D.f sage: nf = D._normalisation_factor_zz(); nf 15.7496... @@ -235,22 +235,22 @@ def _normalisation_factor_zz(self, tau=None, prec=None): sage: while abs(m*f(v)*1.0/nf/counter[v] - 1.0) >= 0.2: add_samples(1000) # long time - sage: D = DiscreteGaussianDistributionLatticeSampler(ZZ^8, 0.5) + sage: D = DGL(ZZ^8, 0.5) sage: D._normalisation_factor_zz(tau=3) 3.1653... sage: D._normalisation_factor_zz() 6.8249... - sage: D = DiscreteGaussianDistributionLatticeSampler(ZZ^8, 1000) + sage: D = DGL(ZZ^8, 1000) sage: round(D._normalisation_factor_zz(prec=100)) 1558545456544038969634991553 sage: M = Matrix(ZZ, [[1, 3, 0], [-2, 5, 1], [3, -4, 2]]) - sage: D = DiscreteGaussianDistributionLatticeSampler(M, 1.7) + sage: D = DGL(M, 1.7) sage: D._normalisation_factor_zz() # long time 7247.1975... sage: M = Matrix(ZZ, [[1, 3, 0], [-2, 5, 1]]) - sage: D = DiscreteGaussianDistributionLatticeSampler(M, 3) + sage: D = DGL(M, 3) sage: D._normalisation_factor_zz() Traceback (most recent call last): ... @@ -297,11 +297,11 @@ def f_or_hat(x): if self.is_spherical and not self._c_in_lattice: raise NotImplementedError("Lattice must contain 0 for now.") - if self.B.base_ring() not in (ZZ, QQ): + if self.B.base_ring() not in ZZ: raise NotImplementedError("Lattice must be integral for now.") sigma = self._sigma - prec = DiscreteGaussianDistributionLatticeSampler.compute_precision( + prec = DGL.compute_precision( prec, sigma ) R = RealField(prec=prec) @@ -341,6 +341,18 @@ def _maximal_r(self): This is equivalent to finding `λ₁(Σ / Q) = 1 / λₙ(Q / Σ)`, which is done via the Power iteration method. + + EXAMPLES:: + + sage: from sage.stats.all import DGL + sage: n = 3 + sage: Sigma = Matrix(ZZ, [[5, -2, 4], [-2, 10, -5], [4, -5, 5]]) + sage: c = vector(ZZ, [7, 2, 5]) + sage: D = DGL(ZZ^n, Sigma, c) + sage: r = D._maximal_r(); r + 0.58402... + sage: e_vals = (D.sigma - r^2 * D.Q).eigenvalues() + sage: assert all(e_val >= -1e-12 for e_val in e_vals) """ # TODO: Write doctest if self.is_spherical: @@ -399,9 +411,9 @@ def __init__(self, B, sigma=1, c=None, r=None, precision=None): EXAMPLES:: - sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler + sage: from sage.stats.all import DGL sage: n = 2; sigma = 3.0 - sage: D = DiscreteGaussianDistributionLatticeSampler(ZZ^n, sigma) + sage: D = DGL(ZZ^n, sigma) sage: f = D.f sage: nf = D._normalisation_factor_zz(); nf 56.5486677646... @@ -432,7 +444,7 @@ def __init__(self, B, sigma=1, c=None, r=None, precision=None): sage: n = 3 sage: Sigma = Matrix(ZZ, [[5, -2, 4], [-2, 10, -5], [4, -5, 5]]) sage: c = vector(ZZ, [7, 2, 5]) - sage: D = DiscreteGaussianDistributionLatticeSampler(ZZ^n, Sigma, c) + sage: D = DGL(ZZ^n, Sigma, c) sage: nf = D._normalisation_factor_zz(); nf # This has not been properly implemented 63.76927... sage: while v not in counter: add_samples(1000) @@ -451,9 +463,9 @@ def __init__(self, B, sigma=1, c=None, r=None, precision=None): We can also initialise with matrix-like objects: - sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler + sage: from sage.stats.all import DGL sage: qf = QuadraticForm(matrix(3, [2, 1, 1, 1, 2, 1, 1, 1, 2])) - sage: D = DiscreteGaussianDistributionLatticeSampler(qf, 3.0); D + sage: D = DGL(qf, 3.0); D Discrete Gaussian sampler with Gaussian parameter σ = 3.00000000000000, c=(0, 0, 0) over lattice with basis [2 1 1] @@ -462,7 +474,7 @@ def __init__(self, B, sigma=1, c=None, r=None, precision=None): sage: D().parent() is D.c.parent() True """ - precision = DiscreteGaussianDistributionLatticeSampler.compute_precision(precision, sigma) + precision = DGL.compute_precision(precision, sigma) self._RR = RealField(precision) # Check if sigma is a (real) number or a scaled identity matrix @@ -498,12 +510,12 @@ def __init__(self, B, sigma=1, c=None, r=None, precision=None): self._c_in_lattice = False try: - c = vector(ZZ, B.ncols(), c) + c = vector(ZZ, self.n, c) except TypeError: try: - c = vector(QQ, B.ncols(), c) + c = vector(QQ, self.n, c) except TypeError: - c = vector(self._RR, B.ncols(), c) + c = vector(self._RR, self.n, c) self._c = c @@ -562,14 +574,14 @@ def __call__(self): EXAMPLES:: - sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler - sage: D = DiscreteGaussianDistributionLatticeSampler(ZZ^3, 3.0, c=(1,0,0)) + sage: from sage.stats.all import DGL + sage: D = DGL(ZZ^3, 3.0, c=(1,0,0)) sage: L = [D() for _ in range(2^12)] sage: mean_L = sum(L) / len(L) sage: norm(mean_L.n() - D.c) < 0.25 True - sage: D = DiscreteGaussianDistributionLatticeSampler(ZZ^3, 3.0, c=(1/2,0,0)) + sage: D = DGL(ZZ^3, 3.0, c=(1/2,0,0)) sage: L = [D() for _ in range(2^12)] # long time sage: mean_L = sum(L) / len(L) # long time sage: norm(mean_L.n() - D.c) < 0.25 # long time @@ -614,8 +626,8 @@ def sigma(self): EXAMPLES:: - sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler - sage: D = DiscreteGaussianDistributionLatticeSampler(ZZ^3, 3.0, c=(1,0,0)) + sage: from sage.stats.all import DGL + sage: D = DGL(ZZ^3, 3.0, c=(1,0,0)) sage: D.sigma 3.00000000000000 """ @@ -630,8 +642,8 @@ def c(self): EXAMPLES:: - sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler - sage: D = DiscreteGaussianDistributionLatticeSampler(ZZ^3, 3.0, c=(1,0,0)); D + sage: from sage.stats.all import DGL + sage: D = DGL(ZZ^3, 3.0, c=(1,0,0)); D Discrete Gaussian sampler with Gaussian parameter σ = 3.00000000000000, c=(1, 0, 0) over lattice with basis [1 0 0] @@ -650,8 +662,8 @@ def c(self, _): EXAMPLES:: - sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler - sage: D = DiscreteGaussianDistributionLatticeSampler(ZZ^3, 3.0, c=(1,0,0)) + sage: from sage.stats.all import DGL + sage: D = DGL(ZZ^3, 3.0, c=(1,0,0)) sage: D.c = 5 Traceback (most recent call last): ... @@ -665,8 +677,8 @@ def __repr__(self): r""" EXAMPLES:: - sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler - sage: D = DiscreteGaussianDistributionLatticeSampler(ZZ^3, 3.0, c=(1,0,0)); D + sage: from sage.stats.all import DGL + sage: D = DGL(ZZ^3, 3.0, c=(1,0,0)); D Discrete Gaussian sampler with Gaussian parameter σ = 3.00000000000000, c=(1, 0, 0) over lattice with basis [1 0 0] @@ -674,7 +686,7 @@ def __repr__(self): [0 0 1] sage: Sigma = Matrix(ZZ, [[10, -6, 1], [-6, 5, -1], [1, -1, 2]]) - sage: D = DiscreteGaussianDistributionLatticeSampler(ZZ^3, Sigma); D + sage: D = DGL(ZZ^3, Sigma); D Discrete Gaussian sampler with Gaussian parameter Σ = [ 10.0000000000000 -6.00000000000000 1.00000000000000] [-6.00000000000000 5.00000000000000 -1.00000000000000] @@ -698,8 +710,8 @@ def _call_in_lattice(self): EXAMPLES:: - sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler - sage: D = DiscreteGaussianDistributionLatticeSampler(ZZ^3, 3.0, c=(1,0,0)) + sage: from sage.stats.all import DGL + sage: D = DGL(ZZ^3, 3.0, c=(1,0,0)) sage: L = [D._call_in_lattice() for _ in range(2^12)] sage: mean_L = sum(L) / len(L) sage: norm(mean_L.n() - D.c) < 0.25 @@ -707,7 +719,7 @@ def _call_in_lattice(self): .. note:: - Do not call this method directly, call :func:`DiscreteGaussianDistributionLatticeSampler.__call__` instead. + Do not call this method directly, call :func:`DGL.__call__` instead. """ w = self.VS([d() for d in self.D], check=False) return w * self.B + self.c @@ -718,8 +730,8 @@ def _call(self): EXAMPLES:: - sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler - sage: D = DiscreteGaussianDistributionLatticeSampler(ZZ^3, 3.0, c=(1/2,0,0)) + sage: from sage.stats.all import DGL + sage: D = DGL(ZZ^3, 3.0, c=(1/2,0,0)) sage: L = [D._call() for _ in range(2^12)] sage: mean_L = sum(L) / len(L) sage: norm(mean_L.n() - D.c) < 0.25 @@ -727,7 +739,7 @@ def _call(self): .. note:: - Do not call this method directly, call :func:`DiscreteGaussianDistributionLatticeSampler.__call__` instead. + Do not call this method directly, call :func:`DGL.__call__` instead. """ v = 0 c, sigma, B = self._c, self._sigma, self.B @@ -751,9 +763,9 @@ def add_offline_samples(self, cnt=1): EXAMPLES:: - sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler + sage: from sage.stats.all import DGL sage: Sigma = Matrix([[5, -2, 4], [-2, 10, -5], [4, -5, 5]]) - sage: D = DiscreteGaussianDistributionLatticeSampler(ZZ^3, Sigma) + sage: D = DGL(ZZ^3, Sigma) sage: assert not D.is_spherical sage: D.add_offline_samples(2^12) sage: L = [D() for _ in range(2^12)] # Takes less time @@ -772,9 +784,9 @@ def _call_non_spherical(self): EXAMPLES:: - sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler + sage: from sage.stats.all import DGL sage: Sigma = Matrix([[5, -2, 4], [-2, 10, -5], [4, -5, 5]]) - sage: D = DiscreteGaussianDistributionLatticeSampler(ZZ^3, Sigma, c=(1/2,0,0)) + sage: D = DGL(ZZ^3, Sigma, c=(1/2,0,0)) sage: L = [D._call_non_spherical() for _ in range(2^12)] sage: mean_L = sum(L) / len(L) sage: norm(mean_L.n() - D.c) < 0.25 @@ -782,7 +794,7 @@ def _call_non_spherical(self): .. note:: - Do not call this method directly, call :func:`DiscreteGaussianDistributionLatticeSampler.__call__` instead. + Do not call this method directly, call :func:`DGL.__call__` instead. """ if len(self.offline_samples) == 0: self.add_offline_samples() From 8d0147fcb04e0ee774580f451cc0cf01778b2680 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 20 Nov 2023 08:43:10 +0100 Subject: [PATCH 017/278] fix_doctest_hide_option initial --- src/sage/doctest/control.py | 62 ++++++++++++++++++------------- src/sage/features/__init__.py | 55 +++++++++++++++------------ src/sage/features/databases.py | 6 +-- src/sage/features/join_feature.py | 10 +++-- 4 files changed, 79 insertions(+), 54 deletions(-) diff --git a/src/sage/doctest/control.py b/src/sage/doctest/control.py index 633afd5c2e6..ed62ac4ba87 100644 --- a/src/sage/doctest/control.py +++ b/src/sage/doctest/control.py @@ -423,7 +423,7 @@ def __init__(self, options, args): if options.gc: options.timeout *= 2 if options.nthreads == 0: - options.nthreads = int(os.getenv('SAGE_NUM_THREADS_PARALLEL',1)) + options.nthreads = int(os.getenv('SAGE_NUM_THREADS_PARALLEL', 1)) if options.failed and not (args or options.new): # If the user doesn't specify any files then we rerun all failed files. options.all = True @@ -457,15 +457,11 @@ def __init__(self, options, args): options.hide.discard('all') from sage.features.all import all_features feature_names = {f.name for f in all_features() if not f.is_standard()} - from sage.doctest.external import external_software - feature_names.difference_update(external_software) options.hide = options.hide.union(feature_names) if 'optional' in options.hide: options.hide.discard('optional') from sage.features.all import all_features feature_names = {f.name for f in all_features() if f.is_optional()} - from sage.doctest.external import external_software - feature_names.difference_update(external_software) options.hide = options.hide.union(feature_names) options.disabled_optional = set() @@ -1015,7 +1011,7 @@ def expand(): if os.path.isdir(path): for root, dirs, files in os.walk(path): for dir in list(dirs): - if dir[0] == "." or skipdir(os.path.join(root,dir)): + if dir[0] == "." or skipdir(os.path.join(root, dir)): dirs.remove(dir) for file in files: if not skipfile(os.path.join(root, file), @@ -1320,9 +1316,9 @@ def run_val_gdb(self, testing=False): flags = os.getenv("SAGE_MEMCHECK_FLAGS") if flags is None: flags = "--leak-resolution=high --leak-check=full --num-callers=25 " - flags += '''--suppressions="%s" ''' % (os.path.join(SAGE_EXTCODE,"valgrind", "pyalloc.supp")) - flags += '''--suppressions="%s" ''' % (os.path.join(SAGE_EXTCODE,"valgrind", "sage.supp")) - flags += '''--suppressions="%s" ''' % (os.path.join(SAGE_EXTCODE,"valgrind", "sage-additional.supp")) + flags += '''--suppressions="%s" ''' % (os.path.join(SAGE_EXTCODE, "valgrind", "pyalloc.supp")) + flags += '''--suppressions="%s" ''' % (os.path.join(SAGE_EXTCODE, "valgrind", "sage.supp")) + flags += '''--suppressions="%s" ''' % (os.path.join(SAGE_EXTCODE, "valgrind", "sage-additional.supp")) elif opt.massif: toolname = "massif" flags = os.getenv("SAGE_MASSIF_FLAGS", "--depth=6 ") @@ -1337,7 +1333,7 @@ def run_val_gdb(self, testing=False): if opt.omega: toolname = "omega" if "%s" in flags: - flags %= toolname + ".%p" # replace %s with toolname + flags %= toolname + ".%p" # replace %s with toolname cmd += flags + sage_cmd sys.stdout.flush() @@ -1464,6 +1460,25 @@ def run(self): cumulative wall time: ... seconds Features detected... 0 + + Test *Features that have been hidden* message:: + + sage: DC.run() # optional - meataxe + Running doctests with ID ... + Using --optional=sage + Features to be detected: ... + Doctesting 1 file. + sage -t ....py + [4 tests, ... s] + ---------------------------------------------------------------------- + All tests passed! + ---------------------------------------------------------------------- + Total time for all tests: ... seconds + cpu time: ... seconds + cumulative wall time: ... seconds + Features detected... + Features that have been hidden: ...meataxe... + 0 """ opt = self.options L = (opt.gdb, opt.lldb, opt.valgrind, opt.massif, opt.cachegrind, opt.omega) @@ -1491,10 +1506,10 @@ def run(self): pass try: ref = subprocess.check_output(["git", - "--git-dir=" + SAGE_ROOT_GIT, - "describe", - "--always", - "--dirty"]) + "--git-dir=" + SAGE_ROOT_GIT, + "describe", + "--always", + "--dirty"]) ref = ref.decode('utf-8') self.log("Git ref: " + ref, end="") except subprocess.CalledProcessError: @@ -1512,12 +1527,11 @@ def run(self): pass else: f = available_software._features[i] - if f.is_present(): - f.hide() - self.options.hidden_features.add(f) - for g in f.joined_features(): - if g.name in self.options.optional: - self.options.optional.discard(g.name) + f.hide() + self.options.hidden_features.add(f) + for g in f.joined_features(): + if g.name in self.options.optional: + self.options.optional.discard(g.name) for o in self.options.disabled_optional: try: @@ -1528,8 +1542,6 @@ def run(self): available_software._seen[i] = -1 self.log("Features to be detected: " + ','.join(available_software.detectable())) - if self.options.hidden_features: - self.log("Hidden features: " + ','.join([f.name for f in self.options.hidden_features])) if self.options.probe: self.log("Features to be probed: " + ('all' if self.options.probe is True else ','.join(self.options.probe))) @@ -1539,11 +1551,11 @@ def run(self): self.sort_sources() self.run_doctests() - for f in self.options.hidden_features: - f.unhide() - self.log("Features detected for doctesting: " + ','.join(available_software.seen())) + if self.options.hidden_features: + features_hidden = [f.name for f in self.options.hidden_features if f.unhide()] + self.log("Features that have been hidden: " + ','.join(features_hidden)) self.cleanup() return self.reporter.error_status diff --git a/src/sage/features/__init__.py b/src/sage/features/__init__.py index cabd63a1a24..b592447e7ce 100644 --- a/src/sage/features/__init__.py +++ b/src/sage/features/__init__.py @@ -158,6 +158,12 @@ def __init__(self, name, spkg=None, url=None, description=None, type='optional') self._hidden = False self._type = type + # For multiprocessing of doctests, the data self._num_hidings should be + # shared among subprocesses. Thus we use the Value class from the + # multiprocessing module (cf. self._seen of class AvailableSoftware) + from multiprocessing import Value + self._num_hidings = Value('i', 0) + try: from sage.misc.package import spkg_type except ImportError: # may have been surgically removed in a downstream distribution @@ -205,8 +211,6 @@ def is_present(self): sage: TestFeature("other").is_present() FeatureTestResult('other', True) """ - if self._hidden: - return FeatureTestResult(self, False, reason="Feature `{name}` is hidden.".format(name=self.name)) # We do not use @cached_method here because we wish to use # Feature early in the build system of sagelib. if self._cache_is_present is None: @@ -214,6 +218,14 @@ def is_present(self): if not isinstance(res, FeatureTestResult): res = FeatureTestResult(self, res) self._cache_is_present = res + + if self._hidden: + if self._num_hidings.value > 0: + self._num_hidings.value += 1 + elif self._cache_is_present: + self._num_hidings.value = 1 + return FeatureTestResult(self, False, reason="Feature `{name}` is hidden.".format(name=self.name)) + return self._cache_is_present def _is_present(self): @@ -386,7 +398,8 @@ def hide(self): Feature `benzene` is hidden. Use method `unhide` to make it available again. - sage: Benzene().unhide() + sage: Benzene().unhide() # optional - benzene, needs sage.graphs + 1 sage: len(list(graphs.fusenes(2))) # optional - benzene, needs sage.graphs 1 """ @@ -394,29 +407,25 @@ def hide(self): def unhide(self): r""" - Revert what :meth:`hide` does. + Revert what :meth:`hide` does. It returns the number of events + a present feature has been hidden. EXAMPLES: - Polycyclic is a standard GAP package since 4.10 (see :trac:`26856`). The - following test just fails if it is hidden. Thus, in the second - invocation no optional tag is needed:: - - sage: from sage.features.gap import GapPackage - sage: Polycyclic = GapPackage("polycyclic", spkg="gap_packages") - sage: Polycyclic.hide() - sage: libgap(AbelianGroup(3, [0,3,4], names="abc")) # needs sage.libs.gap - Traceback (most recent call last): - ... - FeatureNotPresentError: gap_package_polycyclic is not available. - Feature `gap_package_polycyclic` is hidden. - Use method `unhide` to make it available again. - - sage: Polycyclic.unhide() - sage: libgap(AbelianGroup(3, [0,3,4], names="abc")) # needs sage.libs.gap - Pcp-group with orders [ 0, 3, 4 ] + sage: from sage.features.sagemath import sage__plot + sage: sage__plot().hide() + sage: sage__plot().is_present() + FeatureTestResult('sage.plot', False) + sage: sage__plot().unhide() # needs sage.plot + 1 + sage: sage__plot().is_present() # needs sage.plot + FeatureTestResult('sage.plot', True) """ + num_hidings = self._num_hidings.value + self._num_hidings.value = 0 self._hidden = False + return int(num_hidings) + class FeatureNotPresentError(RuntimeError): r""" @@ -801,7 +810,7 @@ class StaticFile(FileFeature): To install no_such_file...you can try to run...sage -i some_spkg... Further installation instructions might be available at http://rand.om. """ - def __init__(self, name, filename, search_path=None, **kwds): + def __init__(self, name, filename, search_path=None, type='optional', **kwds): r""" TESTS:: @@ -809,7 +818,7 @@ def __init__(self, name, filename, search_path=None, **kwds): sage: StaticFile(name="null", filename="null", search_path=("/dev",)) Feature('null') """ - Feature.__init__(self, name, **kwds) + Feature.__init__(self, name, type=type, **kwds) self.filename = filename if search_path is None: self.search_path = [SAGE_SHARE] diff --git a/src/sage/features/databases.py b/src/sage/features/databases.py index f9b297b1e30..1c7c62e80f1 100644 --- a/src/sage/features/databases.py +++ b/src/sage/features/databases.py @@ -72,12 +72,12 @@ class DatabaseCremona(StaticFile): EXAMPLES:: sage: from sage.features.databases import DatabaseCremona - sage: DatabaseCremona('cremona_mini').is_present() + sage: DatabaseCremona('cremona_mini', type='sandard').is_present() FeatureTestResult('database_cremona_mini_ellcurve', True) sage: DatabaseCremona().is_present() # optional - database_cremona_ellcurve FeatureTestResult('database_cremona_ellcurve', True) """ - def __init__(self, name="cremona", spkg="database_cremona_ellcurve"): + def __init__(self, name="cremona", spkg="database_cremona_ellcurve", type='optional'): r""" TESTS:: @@ -198,7 +198,7 @@ def __init__(self, name='polytopes_db', dirname='Full3D'): def all_features(): return [DatabaseConwayPolynomials(), - DatabaseCremona(), DatabaseCremona('cremona_mini'), + DatabaseCremona(), DatabaseCremona('cremona_mini', type='standard'), DatabaseJones(), DatabaseKnotInfo(), DatabaseCubicHecke(), diff --git a/src/sage/features/join_feature.py b/src/sage/features/join_feature.py index 6360eec1576..3f950c7dd48 100644 --- a/src/sage/features/join_feature.py +++ b/src/sage/features/join_feature.py @@ -152,7 +152,8 @@ def hide(self): def unhide(self): r""" - Revert what :meth:`hide` does. + Revert what :meth:`hide` does. It returns the number of events + a present feature has been hidden. EXAMPLES:: @@ -165,11 +166,14 @@ def unhide(self): FeatureTestResult('sage.groups.perm_gps.permgroup', False) sage: f.unhide() + 4 sage: f.is_present() # optional sage.groups FeatureTestResult('sage.groups', True) sage: f._features[0].is_present() # optional sage.groups FeatureTestResult('sage.groups.perm_gps.permgroup', True) """ + num_hidings = 0 for f in self._features: - f.unhide() - super().unhide() + num_hidings += f.unhide() + num_hidings += super().unhide() + return num_hidings From a65ad969c2ef17aa16d915b061c3a22020fb3574 Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Sat, 25 Nov 2023 23:03:29 -0500 Subject: [PATCH 018/278] build/pkgs/importlib_metadata: skip with python >= 3.11 --- .../pkgs/importlib_metadata/spkg-configure.m4 | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/build/pkgs/importlib_metadata/spkg-configure.m4 b/build/pkgs/importlib_metadata/spkg-configure.m4 index 0554e522252..e096a0182a5 100644 --- a/build/pkgs/importlib_metadata/spkg-configure.m4 +++ b/build/pkgs/importlib_metadata/spkg-configure.m4 @@ -1,3 +1,24 @@ SAGE_SPKG_CONFIGURE([importlib_metadata], [ SAGE_PYTHON_PACKAGE_CHECK([importlib_metadata]) +],[ + # Three of our python packages are backport packages providing + # python-3.11 features (see coding_in_python.rst): + # + # * importlib_metadata + # * importlib_resources + # * typing_extensions + # + # These packages are therefore not needed with >=python-3.11. Here + # we test for a python minor version component greater than or equal + # to 11, and mark this package as "not required" if we succeed. + AC_MSG_CHECKING([for >=python-3.11]) + + # Keep in mind that False (~ zero) in python is success in the shell + AS_IF(["${PYTHON_FOR_VENV}" -c "import sys; sys.exit(sys.version_info.minor < 11)"],[ + AC_MSG_RESULT([yes]) + sage_require_importlib_metadata="no" + ],[ + AC_MSG_RESULT([no]) + ]) ]) + From 957409a050af313b40b1005b510af49a7e89d3cc Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Sat, 25 Nov 2023 23:03:49 -0500 Subject: [PATCH 019/278] build/pkgs/importlib_resources: skip with python >= 3.11 --- .../importlib_resources/spkg-configure.m4 | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/build/pkgs/importlib_resources/spkg-configure.m4 b/build/pkgs/importlib_resources/spkg-configure.m4 index 50df55b4643..1416ec5c200 100644 --- a/build/pkgs/importlib_resources/spkg-configure.m4 +++ b/build/pkgs/importlib_resources/spkg-configure.m4 @@ -1,3 +1,23 @@ SAGE_SPKG_CONFIGURE([importlib_resources], [ SAGE_PYTHON_PACKAGE_CHECK([importlib_resources]) +],[ + # Three of our python packages are backport packages providing + # python-3.11 features (see coding_in_python.rst): + # + # * importlib_metadata + # * importlib_resources + # * typing_extensions + # + # These packages are therefore not needed with >=python-3.11. Here + # we test for a python minor version component greater than or equal + # to 11, and mark this package as "not required" if we succeed. + AC_MSG_CHECKING([for >=python-3.11]) + + # Keep in mind that False (~ zero) in python is success in the shell + AS_IF(["${PYTHON_FOR_VENV}" -c "import sys; sys.exit(sys.version_info.minor < 11)"],[ + AC_MSG_RESULT([yes]) + sage_require_importlib_resources="no" + ],[ + AC_MSG_RESULT([no]) + ]) ]) From fdd900b2d6e99fecb1143fd821db396e74b3aa3d Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Sat, 25 Nov 2023 23:04:00 -0500 Subject: [PATCH 020/278] build/pkgs/typing_extensions: skip with python >= 3.11 --- .../pkgs/typing_extensions/spkg-configure.m4 | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/build/pkgs/typing_extensions/spkg-configure.m4 b/build/pkgs/typing_extensions/spkg-configure.m4 index cbb75fec955..fec3271a44b 100644 --- a/build/pkgs/typing_extensions/spkg-configure.m4 +++ b/build/pkgs/typing_extensions/spkg-configure.m4 @@ -1,3 +1,24 @@ SAGE_SPKG_CONFIGURE([typing_extensions],[ SAGE_PYTHON_PACKAGE_CHECK([typing_extensions]) +],[ + # Three of our python packages are backport packages providing + # python-3.11 features (see coding_in_python.rst): + # + # * importlib_metadata + # * importlib_resources + # * typing_extensions + # + # These packages are therefore not needed with >=python-3.11. Here + # we test for a python minor version component greater than or equal + # to 11, and mark this package as "not required" if we succeed. + AC_MSG_CHECKING([for >=python-3.11]) + + # Keep in mind that False (~ zero) in python is success in the shell + AS_IF(["${PYTHON_FOR_VENV}" -c "import sys; sys.exit(sys.version_info.minor < 11)"],[ + AC_MSG_RESULT([yes]) + sage_require_typing_extensions="no" + ],[ + AC_MSG_RESULT([no]) + ]) ]) + From a6adbcf0103b45ddfd58ebc4048d9fceeb7b6f5c Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Sun, 26 Nov 2023 00:06:18 -0500 Subject: [PATCH 021/278] build/pkgs/importlib_metadata/install-requires.txt: skip if python >= 3.11 --- build/pkgs/importlib_metadata/install-requires.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/pkgs/importlib_metadata/install-requires.txt b/build/pkgs/importlib_metadata/install-requires.txt index 7a0ebd24888..e09fe706732 100644 --- a/build/pkgs/importlib_metadata/install-requires.txt +++ b/build/pkgs/importlib_metadata/install-requires.txt @@ -1,3 +1,3 @@ # According to https://pypi.org/project/importlib-metadata/, # 4.13 provides the features of Python 3.11 importlib.metadata -importlib_metadata >=4.13 +importlib_metadata >=4.13; python_version<'3.11' From 24842239cd74c34151dd14b21687ef5161e6214b Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Sun, 26 Nov 2023 00:07:28 -0500 Subject: [PATCH 022/278] build/pkgs/importlib_resources/install-requires.txt: skip if python >= 3.11 --- build/pkgs/importlib_resources/install-requires.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/pkgs/importlib_resources/install-requires.txt b/build/pkgs/importlib_resources/install-requires.txt index 632e716f5a0..aef80842362 100644 --- a/build/pkgs/importlib_resources/install-requires.txt +++ b/build/pkgs/importlib_resources/install-requires.txt @@ -1,3 +1,3 @@ # According to https://pypi.org/project/importlib-resources/, # version 5.7 provides the features of Python 3.11 importlib.resources -importlib_resources >= 5.7 +importlib_resources >= 5.7; python_version<'3.11' From a3d015df083d68d8f9284e1f90a3fa2c2df6e301 Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Sun, 26 Nov 2023 00:07:40 -0500 Subject: [PATCH 023/278] build/pkgs/typing_extensions/install-requires.txt: skip if python >= 3.11 --- build/pkgs/typing_extensions/install-requires.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/pkgs/typing_extensions/install-requires.txt b/build/pkgs/typing_extensions/install-requires.txt index 22c3dd116b6..e1a912109ed 100644 --- a/build/pkgs/typing_extensions/install-requires.txt +++ b/build/pkgs/typing_extensions/install-requires.txt @@ -1,3 +1,3 @@ # According to https://github.com/python/typing_extensions/blob/main/CHANGELOG.md, # version 4.4.0 adds another Python 3.11 typing backport -typing_extensions >= 4.4.0 +typing_extensions >= 4.4.0; python_version<'3.11' From 5c3c332ecba1390047ac1c56b57a312a4a06c73d Mon Sep 17 00:00:00 2001 From: Dima Pasechnik Date: Sun, 26 Nov 2023 14:19:55 +0000 Subject: [PATCH 024/278] spkg-configure.m4 for all the meaningful Python packages --- build/pkgs/admcycles/spkg-configure.m4 | 1 + build/pkgs/anyio/spkg-configure.m4 | 1 + build/pkgs/arrow/spkg-configure.m4 | 1 + build/pkgs/async_lru/spkg-configure.m4 | 1 + build/pkgs/auditwheel_or_delocate/spkg-configure.m4 | 1 + build/pkgs/biopython/spkg-configure.m4 | 1 + build/pkgs/comm/spkg-configure.m4 | 1 + build/pkgs/cvxpy/spkg-configure.m4 | 1 + build/pkgs/cylp/spkg-configure.m4 | 1 + build/pkgs/database_cubic_hecke/spkg-configure.m4 | 1 + build/pkgs/database_knotinfo/spkg-configure.m4 | 1 + build/pkgs/deprecation/spkg-configure.m4 | 1 + build/pkgs/dot2tex/spkg-configure.m4 | 1 + build/pkgs/ecos_python/spkg-configure.m4 | 1 + build/pkgs/fqdn/spkg-configure.m4 | 1 + build/pkgs/gap_jupyter/spkg-configure.m4 | 1 + build/pkgs/gitpython/spkg-configure.m4 | 1 + build/pkgs/isoduration/spkg-configure.m4 | 1 + build/pkgs/json5/spkg-configure.m4 | 1 + build/pkgs/jsonpointer/spkg-configure.m4 | 1 + build/pkgs/jsonschema_specifications/spkg-configure.m4 | 1 + build/pkgs/jupymake/spkg-configure.m4 | 1 + build/pkgs/jupyter_events/spkg-configure.m4 | 1 + build/pkgs/jupyter_lsp/spkg-configure.m4 | 1 + build/pkgs/jupyter_server/spkg-configure.m4 | 1 + build/pkgs/jupyter_server_terminals/spkg-configure.m4 | 1 + build/pkgs/jupyterlab/spkg-configure.m4 | 1 + build/pkgs/jupyterlab_mathjax2/spkg-configure.m4 | 1 + build/pkgs/jupyterlab_server/spkg-configure.m4 | 1 + build/pkgs/mathics/spkg-configure.m4 | 1 + build/pkgs/mathics_scanner/spkg-configure.m4 | 1 + build/pkgs/nibabel/spkg-configure.m4 | 1 + build/pkgs/notebook_shim/spkg-configure.m4 | 1 + build/pkgs/notedown/spkg-configure.m4 | 1 + build/pkgs/ore_algebra/spkg-configure.m4 | 1 + build/pkgs/osqp_python/spkg-configure.m4 | 1 + build/pkgs/overrides/spkg-configure.m4 | 1 + build/pkgs/p_group_cohomology/spkg-configure.m4 | 1 + build/pkgs/palettable/spkg-configure.m4 | 1 + build/pkgs/pandoc_attributes/spkg-configure.m4 | 1 + build/pkgs/pari_jupyter/spkg-configure.m4 | 1 + build/pkgs/phitigra/spkg-configure.m4 | 1 + build/pkgs/pint/spkg-configure.m4 | 1 + build/pkgs/psutil/spkg-configure.m4 | 1 + build/pkgs/pybtex/spkg-configure.m4 | 1 + build/pkgs/pycosat/spkg-configure.m4 | 1 + build/pkgs/pycryptosat/spkg-configure.m4 | 1 + build/pkgs/pycygwin/spkg-configure.m4 | 1 + build/pkgs/pygraphviz/spkg-configure.m4 | 1 + build/pkgs/pynormaliz/spkg-configure.m4 | 1 + build/pkgs/pyppeteer/spkg-configure.m4 | 1 + build/pkgs/pyproject_api/spkg-configure.m4 | 1 + build/pkgs/pyscipopt/spkg-configure.m4 | 1 + build/pkgs/pysingular/spkg-configure.m4 | 1 + build/pkgs/pytest/spkg-configure.m4 | 1 + build/pkgs/pytest_mock/spkg-configure.m4 | 1 + build/pkgs/pytest_xdist/spkg-configure.m4 | 1 + build/pkgs/python_build/spkg-configure.m4 | 1 + build/pkgs/python_igraph/spkg-configure.m4 | 1 + build/pkgs/python_json_logger/spkg-configure.m4 | 1 + build/pkgs/pytz_deprecation_shim/spkg-configure.m4 | 1 + build/pkgs/pyx/spkg-configure.m4 | 1 + build/pkgs/pyyaml/spkg-configure.m4 | 1 + build/pkgs/qdldl_python/spkg-configure.m4 | 1 + build/pkgs/referencing/spkg-configure.m4 | 1 + build/pkgs/rfc3339_validator/spkg-configure.m4 | 1 + build/pkgs/rfc3986_validator/spkg-configure.m4 | 1 + build/pkgs/rst2ipynb/spkg-configure.m4 | 1 + build/pkgs/sagelib/spkg-configure.m4 | 1 + build/pkgs/sagenb_export/spkg-configure.m4 | 1 + build/pkgs/sagetex/spkg-configure.m4 | 1 + build/pkgs/scs/spkg-configure.m4 | 1 + build/pkgs/singular_jupyter/spkg-configure.m4 | 1 + build/pkgs/slabbe/spkg-configure.m4 | 1 + build/pkgs/snappy/spkg-configure.m4 | 1 + build/pkgs/sniffio/spkg-configure.m4 | 1 + build/pkgs/soupsieve/spkg-configure.m4 | 1 + build/pkgs/sqlalchemy/spkg-configure.m4 | 1 + build/pkgs/surface_dynamics/spkg-configure.m4 | 1 + build/pkgs/types_python_dateutil/spkg-configure.m4 | 1 + build/pkgs/uri_template/spkg-configure.m4 | 1 + build/pkgs/webcolors/spkg-configure.m4 | 1 + build/pkgs/websocket_client/spkg-configure.m4 | 1 + 83 files changed, 83 insertions(+) create mode 100644 build/pkgs/admcycles/spkg-configure.m4 create mode 100644 build/pkgs/anyio/spkg-configure.m4 create mode 100644 build/pkgs/arrow/spkg-configure.m4 create mode 100644 build/pkgs/async_lru/spkg-configure.m4 create mode 100644 build/pkgs/auditwheel_or_delocate/spkg-configure.m4 create mode 100644 build/pkgs/biopython/spkg-configure.m4 create mode 100644 build/pkgs/comm/spkg-configure.m4 create mode 100644 build/pkgs/cvxpy/spkg-configure.m4 create mode 100644 build/pkgs/cylp/spkg-configure.m4 create mode 100644 build/pkgs/database_cubic_hecke/spkg-configure.m4 create mode 100644 build/pkgs/database_knotinfo/spkg-configure.m4 create mode 100644 build/pkgs/deprecation/spkg-configure.m4 create mode 100644 build/pkgs/dot2tex/spkg-configure.m4 create mode 100644 build/pkgs/ecos_python/spkg-configure.m4 create mode 100644 build/pkgs/fqdn/spkg-configure.m4 create mode 100644 build/pkgs/gap_jupyter/spkg-configure.m4 create mode 100644 build/pkgs/gitpython/spkg-configure.m4 create mode 100644 build/pkgs/isoduration/spkg-configure.m4 create mode 100644 build/pkgs/json5/spkg-configure.m4 create mode 100644 build/pkgs/jsonpointer/spkg-configure.m4 create mode 100644 build/pkgs/jsonschema_specifications/spkg-configure.m4 create mode 100644 build/pkgs/jupymake/spkg-configure.m4 create mode 100644 build/pkgs/jupyter_events/spkg-configure.m4 create mode 100644 build/pkgs/jupyter_lsp/spkg-configure.m4 create mode 100644 build/pkgs/jupyter_server/spkg-configure.m4 create mode 100644 build/pkgs/jupyter_server_terminals/spkg-configure.m4 create mode 100644 build/pkgs/jupyterlab/spkg-configure.m4 create mode 100644 build/pkgs/jupyterlab_mathjax2/spkg-configure.m4 create mode 100644 build/pkgs/jupyterlab_server/spkg-configure.m4 create mode 100644 build/pkgs/mathics/spkg-configure.m4 create mode 100644 build/pkgs/mathics_scanner/spkg-configure.m4 create mode 100644 build/pkgs/nibabel/spkg-configure.m4 create mode 100644 build/pkgs/notebook_shim/spkg-configure.m4 create mode 100644 build/pkgs/notedown/spkg-configure.m4 create mode 100644 build/pkgs/ore_algebra/spkg-configure.m4 create mode 100644 build/pkgs/osqp_python/spkg-configure.m4 create mode 100644 build/pkgs/overrides/spkg-configure.m4 create mode 100644 build/pkgs/p_group_cohomology/spkg-configure.m4 create mode 100644 build/pkgs/palettable/spkg-configure.m4 create mode 100644 build/pkgs/pandoc_attributes/spkg-configure.m4 create mode 100644 build/pkgs/pari_jupyter/spkg-configure.m4 create mode 100644 build/pkgs/phitigra/spkg-configure.m4 create mode 100644 build/pkgs/pint/spkg-configure.m4 create mode 100644 build/pkgs/psutil/spkg-configure.m4 create mode 100644 build/pkgs/pybtex/spkg-configure.m4 create mode 100644 build/pkgs/pycosat/spkg-configure.m4 create mode 100644 build/pkgs/pycryptosat/spkg-configure.m4 create mode 100644 build/pkgs/pycygwin/spkg-configure.m4 create mode 100644 build/pkgs/pygraphviz/spkg-configure.m4 create mode 100644 build/pkgs/pynormaliz/spkg-configure.m4 create mode 100644 build/pkgs/pyppeteer/spkg-configure.m4 create mode 100644 build/pkgs/pyproject_api/spkg-configure.m4 create mode 100644 build/pkgs/pyscipopt/spkg-configure.m4 create mode 100644 build/pkgs/pysingular/spkg-configure.m4 create mode 100644 build/pkgs/pytest/spkg-configure.m4 create mode 100644 build/pkgs/pytest_mock/spkg-configure.m4 create mode 100644 build/pkgs/pytest_xdist/spkg-configure.m4 create mode 100644 build/pkgs/python_build/spkg-configure.m4 create mode 100644 build/pkgs/python_igraph/spkg-configure.m4 create mode 100644 build/pkgs/python_json_logger/spkg-configure.m4 create mode 100644 build/pkgs/pytz_deprecation_shim/spkg-configure.m4 create mode 100644 build/pkgs/pyx/spkg-configure.m4 create mode 100644 build/pkgs/pyyaml/spkg-configure.m4 create mode 100644 build/pkgs/qdldl_python/spkg-configure.m4 create mode 100644 build/pkgs/referencing/spkg-configure.m4 create mode 100644 build/pkgs/rfc3339_validator/spkg-configure.m4 create mode 100644 build/pkgs/rfc3986_validator/spkg-configure.m4 create mode 100644 build/pkgs/rst2ipynb/spkg-configure.m4 create mode 100644 build/pkgs/sagelib/spkg-configure.m4 create mode 100644 build/pkgs/sagenb_export/spkg-configure.m4 create mode 100644 build/pkgs/sagetex/spkg-configure.m4 create mode 100644 build/pkgs/scs/spkg-configure.m4 create mode 100644 build/pkgs/singular_jupyter/spkg-configure.m4 create mode 100644 build/pkgs/slabbe/spkg-configure.m4 create mode 100644 build/pkgs/snappy/spkg-configure.m4 create mode 100644 build/pkgs/sniffio/spkg-configure.m4 create mode 100644 build/pkgs/soupsieve/spkg-configure.m4 create mode 100644 build/pkgs/sqlalchemy/spkg-configure.m4 create mode 100644 build/pkgs/surface_dynamics/spkg-configure.m4 create mode 100644 build/pkgs/types_python_dateutil/spkg-configure.m4 create mode 100644 build/pkgs/uri_template/spkg-configure.m4 create mode 100644 build/pkgs/webcolors/spkg-configure.m4 create mode 100644 build/pkgs/websocket_client/spkg-configure.m4 diff --git a/build/pkgs/admcycles/spkg-configure.m4 b/build/pkgs/admcycles/spkg-configure.m4 new file mode 100644 index 00000000000..6f2caf2731e --- /dev/null +++ b/build/pkgs/admcycles/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([admcycles], [SAGE_PYTHON_PACKAGE_CHECK([admcycles])]) diff --git a/build/pkgs/anyio/spkg-configure.m4 b/build/pkgs/anyio/spkg-configure.m4 new file mode 100644 index 00000000000..f740791dcb8 --- /dev/null +++ b/build/pkgs/anyio/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([anyio], [SAGE_PYTHON_PACKAGE_CHECK([anyio])]) diff --git a/build/pkgs/arrow/spkg-configure.m4 b/build/pkgs/arrow/spkg-configure.m4 new file mode 100644 index 00000000000..9b95db5215d --- /dev/null +++ b/build/pkgs/arrow/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([arrow], [SAGE_PYTHON_PACKAGE_CHECK([arrow])]) diff --git a/build/pkgs/async_lru/spkg-configure.m4 b/build/pkgs/async_lru/spkg-configure.m4 new file mode 100644 index 00000000000..064b750987f --- /dev/null +++ b/build/pkgs/async_lru/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([async_lru], [SAGE_PYTHON_PACKAGE_CHECK([async_lru])]) diff --git a/build/pkgs/auditwheel_or_delocate/spkg-configure.m4 b/build/pkgs/auditwheel_or_delocate/spkg-configure.m4 new file mode 100644 index 00000000000..bc070b3d98d --- /dev/null +++ b/build/pkgs/auditwheel_or_delocate/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([auditwheel_or_delocate], [SAGE_PYTHON_PACKAGE_CHECK([auditwheel_or_delocate])]) diff --git a/build/pkgs/biopython/spkg-configure.m4 b/build/pkgs/biopython/spkg-configure.m4 new file mode 100644 index 00000000000..69f8a404ab6 --- /dev/null +++ b/build/pkgs/biopython/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([biopython], [SAGE_PYTHON_PACKAGE_CHECK([biopython])]) diff --git a/build/pkgs/comm/spkg-configure.m4 b/build/pkgs/comm/spkg-configure.m4 new file mode 100644 index 00000000000..aceb6757466 --- /dev/null +++ b/build/pkgs/comm/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([comm], [SAGE_PYTHON_PACKAGE_CHECK([comm])]) diff --git a/build/pkgs/cvxpy/spkg-configure.m4 b/build/pkgs/cvxpy/spkg-configure.m4 new file mode 100644 index 00000000000..a0eedb3195a --- /dev/null +++ b/build/pkgs/cvxpy/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([cvxpy], [SAGE_PYTHON_PACKAGE_CHECK([cvxpy])]) diff --git a/build/pkgs/cylp/spkg-configure.m4 b/build/pkgs/cylp/spkg-configure.m4 new file mode 100644 index 00000000000..e2d9b43cdc3 --- /dev/null +++ b/build/pkgs/cylp/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([cylp], [SAGE_PYTHON_PACKAGE_CHECK([cylp])]) diff --git a/build/pkgs/database_cubic_hecke/spkg-configure.m4 b/build/pkgs/database_cubic_hecke/spkg-configure.m4 new file mode 100644 index 00000000000..d9a5bae6d52 --- /dev/null +++ b/build/pkgs/database_cubic_hecke/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([database_cubic_hecke], [SAGE_PYTHON_PACKAGE_CHECK([database_cubic_hecke])]) diff --git a/build/pkgs/database_knotinfo/spkg-configure.m4 b/build/pkgs/database_knotinfo/spkg-configure.m4 new file mode 100644 index 00000000000..eeda747473e --- /dev/null +++ b/build/pkgs/database_knotinfo/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([database_knotinfo], [SAGE_PYTHON_PACKAGE_CHECK([database_knotinfo])]) diff --git a/build/pkgs/deprecation/spkg-configure.m4 b/build/pkgs/deprecation/spkg-configure.m4 new file mode 100644 index 00000000000..306d7c5cd1e --- /dev/null +++ b/build/pkgs/deprecation/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([deprecation], [SAGE_PYTHON_PACKAGE_CHECK([deprecation])]) diff --git a/build/pkgs/dot2tex/spkg-configure.m4 b/build/pkgs/dot2tex/spkg-configure.m4 new file mode 100644 index 00000000000..ca81f4b34d5 --- /dev/null +++ b/build/pkgs/dot2tex/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([dot2tex], [SAGE_PYTHON_PACKAGE_CHECK([dot2tex])]) diff --git a/build/pkgs/ecos_python/spkg-configure.m4 b/build/pkgs/ecos_python/spkg-configure.m4 new file mode 100644 index 00000000000..85ab65deb65 --- /dev/null +++ b/build/pkgs/ecos_python/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([ecos_python], [SAGE_PYTHON_PACKAGE_CHECK([ecos_python])]) diff --git a/build/pkgs/fqdn/spkg-configure.m4 b/build/pkgs/fqdn/spkg-configure.m4 new file mode 100644 index 00000000000..6c5cc1778dc --- /dev/null +++ b/build/pkgs/fqdn/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([fqdn], [SAGE_PYTHON_PACKAGE_CHECK([fqdn])]) diff --git a/build/pkgs/gap_jupyter/spkg-configure.m4 b/build/pkgs/gap_jupyter/spkg-configure.m4 new file mode 100644 index 00000000000..96ce5e8fa8e --- /dev/null +++ b/build/pkgs/gap_jupyter/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([gap_jupyter], [SAGE_PYTHON_PACKAGE_CHECK([gap_jupyter])]) diff --git a/build/pkgs/gitpython/spkg-configure.m4 b/build/pkgs/gitpython/spkg-configure.m4 new file mode 100644 index 00000000000..382b212ea16 --- /dev/null +++ b/build/pkgs/gitpython/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([gitpython], [SAGE_PYTHON_PACKAGE_CHECK([gitpython])]) diff --git a/build/pkgs/isoduration/spkg-configure.m4 b/build/pkgs/isoduration/spkg-configure.m4 new file mode 100644 index 00000000000..2b42e17f0e8 --- /dev/null +++ b/build/pkgs/isoduration/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([isoduration], [SAGE_PYTHON_PACKAGE_CHECK([isoduration])]) diff --git a/build/pkgs/json5/spkg-configure.m4 b/build/pkgs/json5/spkg-configure.m4 new file mode 100644 index 00000000000..50f9ab8c882 --- /dev/null +++ b/build/pkgs/json5/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([json5], [SAGE_PYTHON_PACKAGE_CHECK([json5])]) diff --git a/build/pkgs/jsonpointer/spkg-configure.m4 b/build/pkgs/jsonpointer/spkg-configure.m4 new file mode 100644 index 00000000000..3d99f290be9 --- /dev/null +++ b/build/pkgs/jsonpointer/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([jsonpointer], [SAGE_PYTHON_PACKAGE_CHECK([jsonpointer])]) diff --git a/build/pkgs/jsonschema_specifications/spkg-configure.m4 b/build/pkgs/jsonschema_specifications/spkg-configure.m4 new file mode 100644 index 00000000000..f6557ca1bbd --- /dev/null +++ b/build/pkgs/jsonschema_specifications/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([jsonschema_specifications], [SAGE_PYTHON_PACKAGE_CHECK([jsonschema_specifications])]) diff --git a/build/pkgs/jupymake/spkg-configure.m4 b/build/pkgs/jupymake/spkg-configure.m4 new file mode 100644 index 00000000000..aca24c94eae --- /dev/null +++ b/build/pkgs/jupymake/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([jupymake], [SAGE_PYTHON_PACKAGE_CHECK([jupymake])]) diff --git a/build/pkgs/jupyter_events/spkg-configure.m4 b/build/pkgs/jupyter_events/spkg-configure.m4 new file mode 100644 index 00000000000..e48d110521f --- /dev/null +++ b/build/pkgs/jupyter_events/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([jupyter_events], [SAGE_PYTHON_PACKAGE_CHECK([jupyter_events])]) diff --git a/build/pkgs/jupyter_lsp/spkg-configure.m4 b/build/pkgs/jupyter_lsp/spkg-configure.m4 new file mode 100644 index 00000000000..ca4ba801296 --- /dev/null +++ b/build/pkgs/jupyter_lsp/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([jupyter_lsp], [SAGE_PYTHON_PACKAGE_CHECK([jupyter_lsp])]) diff --git a/build/pkgs/jupyter_server/spkg-configure.m4 b/build/pkgs/jupyter_server/spkg-configure.m4 new file mode 100644 index 00000000000..5419748c46d --- /dev/null +++ b/build/pkgs/jupyter_server/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([jupyter_server], [SAGE_PYTHON_PACKAGE_CHECK([jupyter_server])]) diff --git a/build/pkgs/jupyter_server_terminals/spkg-configure.m4 b/build/pkgs/jupyter_server_terminals/spkg-configure.m4 new file mode 100644 index 00000000000..1fe9e5ed896 --- /dev/null +++ b/build/pkgs/jupyter_server_terminals/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([jupyter_server_terminals], [SAGE_PYTHON_PACKAGE_CHECK([jupyter_server_terminals])]) diff --git a/build/pkgs/jupyterlab/spkg-configure.m4 b/build/pkgs/jupyterlab/spkg-configure.m4 new file mode 100644 index 00000000000..33865328e22 --- /dev/null +++ b/build/pkgs/jupyterlab/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([jupyterlab], [SAGE_PYTHON_PACKAGE_CHECK([jupyterlab])]) diff --git a/build/pkgs/jupyterlab_mathjax2/spkg-configure.m4 b/build/pkgs/jupyterlab_mathjax2/spkg-configure.m4 new file mode 100644 index 00000000000..5d1b366b376 --- /dev/null +++ b/build/pkgs/jupyterlab_mathjax2/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([jupyterlab_mathjax2], [SAGE_PYTHON_PACKAGE_CHECK([jupyterlab_mathjax2])]) diff --git a/build/pkgs/jupyterlab_server/spkg-configure.m4 b/build/pkgs/jupyterlab_server/spkg-configure.m4 new file mode 100644 index 00000000000..0173ac2c800 --- /dev/null +++ b/build/pkgs/jupyterlab_server/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([jupyterlab_server], [SAGE_PYTHON_PACKAGE_CHECK([jupyterlab_server])]) diff --git a/build/pkgs/mathics/spkg-configure.m4 b/build/pkgs/mathics/spkg-configure.m4 new file mode 100644 index 00000000000..e297793c591 --- /dev/null +++ b/build/pkgs/mathics/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([mathics], [SAGE_PYTHON_PACKAGE_CHECK([mathics])]) diff --git a/build/pkgs/mathics_scanner/spkg-configure.m4 b/build/pkgs/mathics_scanner/spkg-configure.m4 new file mode 100644 index 00000000000..09917819a0f --- /dev/null +++ b/build/pkgs/mathics_scanner/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([mathics_scanner], [SAGE_PYTHON_PACKAGE_CHECK([mathics_scanner])]) diff --git a/build/pkgs/nibabel/spkg-configure.m4 b/build/pkgs/nibabel/spkg-configure.m4 new file mode 100644 index 00000000000..e5c298f9a5f --- /dev/null +++ b/build/pkgs/nibabel/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([nibabel], [SAGE_PYTHON_PACKAGE_CHECK([nibabel])]) diff --git a/build/pkgs/notebook_shim/spkg-configure.m4 b/build/pkgs/notebook_shim/spkg-configure.m4 new file mode 100644 index 00000000000..9131ca968f5 --- /dev/null +++ b/build/pkgs/notebook_shim/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([notebook_shim], [SAGE_PYTHON_PACKAGE_CHECK([notebook_shim])]) diff --git a/build/pkgs/notedown/spkg-configure.m4 b/build/pkgs/notedown/spkg-configure.m4 new file mode 100644 index 00000000000..a45e08710f5 --- /dev/null +++ b/build/pkgs/notedown/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([notedown], [SAGE_PYTHON_PACKAGE_CHECK([notedown])]) diff --git a/build/pkgs/ore_algebra/spkg-configure.m4 b/build/pkgs/ore_algebra/spkg-configure.m4 new file mode 100644 index 00000000000..4f230702942 --- /dev/null +++ b/build/pkgs/ore_algebra/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([ore_algebra], [SAGE_PYTHON_PACKAGE_CHECK([ore_algebra])]) diff --git a/build/pkgs/osqp_python/spkg-configure.m4 b/build/pkgs/osqp_python/spkg-configure.m4 new file mode 100644 index 00000000000..ce4821c39da --- /dev/null +++ b/build/pkgs/osqp_python/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([osqp_python], [SAGE_PYTHON_PACKAGE_CHECK([osqp_python])]) diff --git a/build/pkgs/overrides/spkg-configure.m4 b/build/pkgs/overrides/spkg-configure.m4 new file mode 100644 index 00000000000..52a1e2fcd3c --- /dev/null +++ b/build/pkgs/overrides/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([overrides], [SAGE_PYTHON_PACKAGE_CHECK([overrides])]) diff --git a/build/pkgs/p_group_cohomology/spkg-configure.m4 b/build/pkgs/p_group_cohomology/spkg-configure.m4 new file mode 100644 index 00000000000..3934f8d0751 --- /dev/null +++ b/build/pkgs/p_group_cohomology/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([p_group_cohomology], [SAGE_PYTHON_PACKAGE_CHECK([p_group_cohomology])]) diff --git a/build/pkgs/palettable/spkg-configure.m4 b/build/pkgs/palettable/spkg-configure.m4 new file mode 100644 index 00000000000..76d3715e9fd --- /dev/null +++ b/build/pkgs/palettable/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([palettable], [SAGE_PYTHON_PACKAGE_CHECK([palettable])]) diff --git a/build/pkgs/pandoc_attributes/spkg-configure.m4 b/build/pkgs/pandoc_attributes/spkg-configure.m4 new file mode 100644 index 00000000000..57aba6b68ba --- /dev/null +++ b/build/pkgs/pandoc_attributes/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([pandoc_attributes], [SAGE_PYTHON_PACKAGE_CHECK([pandoc_attributes])]) diff --git a/build/pkgs/pari_jupyter/spkg-configure.m4 b/build/pkgs/pari_jupyter/spkg-configure.m4 new file mode 100644 index 00000000000..7e7f7ecbb0e --- /dev/null +++ b/build/pkgs/pari_jupyter/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([pari_jupyter], [SAGE_PYTHON_PACKAGE_CHECK([pari_jupyter])]) diff --git a/build/pkgs/phitigra/spkg-configure.m4 b/build/pkgs/phitigra/spkg-configure.m4 new file mode 100644 index 00000000000..06fc3367f18 --- /dev/null +++ b/build/pkgs/phitigra/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([phitigra], [SAGE_PYTHON_PACKAGE_CHECK([phitigra])]) diff --git a/build/pkgs/pint/spkg-configure.m4 b/build/pkgs/pint/spkg-configure.m4 new file mode 100644 index 00000000000..bfa78b5f327 --- /dev/null +++ b/build/pkgs/pint/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([pint], [SAGE_PYTHON_PACKAGE_CHECK([pint])]) diff --git a/build/pkgs/psutil/spkg-configure.m4 b/build/pkgs/psutil/spkg-configure.m4 new file mode 100644 index 00000000000..ce714751c5f --- /dev/null +++ b/build/pkgs/psutil/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([psutil], [SAGE_PYTHON_PACKAGE_CHECK([psutil])]) diff --git a/build/pkgs/pybtex/spkg-configure.m4 b/build/pkgs/pybtex/spkg-configure.m4 new file mode 100644 index 00000000000..77e95f59f47 --- /dev/null +++ b/build/pkgs/pybtex/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([pybtex], [SAGE_PYTHON_PACKAGE_CHECK([pybtex])]) diff --git a/build/pkgs/pycosat/spkg-configure.m4 b/build/pkgs/pycosat/spkg-configure.m4 new file mode 100644 index 00000000000..1d4969fa697 --- /dev/null +++ b/build/pkgs/pycosat/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([pycosat], [SAGE_PYTHON_PACKAGE_CHECK([pycosat])]) diff --git a/build/pkgs/pycryptosat/spkg-configure.m4 b/build/pkgs/pycryptosat/spkg-configure.m4 new file mode 100644 index 00000000000..542bb5676ef --- /dev/null +++ b/build/pkgs/pycryptosat/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([pycryptosat], [SAGE_PYTHON_PACKAGE_CHECK([pycryptosat])]) diff --git a/build/pkgs/pycygwin/spkg-configure.m4 b/build/pkgs/pycygwin/spkg-configure.m4 new file mode 100644 index 00000000000..7876a693d7b --- /dev/null +++ b/build/pkgs/pycygwin/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([pycygwin], [SAGE_PYTHON_PACKAGE_CHECK([pycygwin])]) diff --git a/build/pkgs/pygraphviz/spkg-configure.m4 b/build/pkgs/pygraphviz/spkg-configure.m4 new file mode 100644 index 00000000000..7ae8d558c95 --- /dev/null +++ b/build/pkgs/pygraphviz/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([pygraphviz], [SAGE_PYTHON_PACKAGE_CHECK([pygraphviz])]) diff --git a/build/pkgs/pynormaliz/spkg-configure.m4 b/build/pkgs/pynormaliz/spkg-configure.m4 new file mode 100644 index 00000000000..7bfc840f137 --- /dev/null +++ b/build/pkgs/pynormaliz/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([pynormaliz], [SAGE_PYTHON_PACKAGE_CHECK([pynormaliz])]) diff --git a/build/pkgs/pyppeteer/spkg-configure.m4 b/build/pkgs/pyppeteer/spkg-configure.m4 new file mode 100644 index 00000000000..371c5ba346a --- /dev/null +++ b/build/pkgs/pyppeteer/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([pyppeteer], [SAGE_PYTHON_PACKAGE_CHECK([pyppeteer])]) diff --git a/build/pkgs/pyproject_api/spkg-configure.m4 b/build/pkgs/pyproject_api/spkg-configure.m4 new file mode 100644 index 00000000000..19951851d6b --- /dev/null +++ b/build/pkgs/pyproject_api/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([pyproject_api], [SAGE_PYTHON_PACKAGE_CHECK([pyproject_api])]) diff --git a/build/pkgs/pyscipopt/spkg-configure.m4 b/build/pkgs/pyscipopt/spkg-configure.m4 new file mode 100644 index 00000000000..4e1a264fe3e --- /dev/null +++ b/build/pkgs/pyscipopt/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([pyscipopt], [SAGE_PYTHON_PACKAGE_CHECK([pyscipopt])]) diff --git a/build/pkgs/pysingular/spkg-configure.m4 b/build/pkgs/pysingular/spkg-configure.m4 new file mode 100644 index 00000000000..30ef02f02f7 --- /dev/null +++ b/build/pkgs/pysingular/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([pysingular], [SAGE_PYTHON_PACKAGE_CHECK([pysingular])]) diff --git a/build/pkgs/pytest/spkg-configure.m4 b/build/pkgs/pytest/spkg-configure.m4 new file mode 100644 index 00000000000..4d91d44fc27 --- /dev/null +++ b/build/pkgs/pytest/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([pytest], [SAGE_PYTHON_PACKAGE_CHECK([pytest])]) diff --git a/build/pkgs/pytest_mock/spkg-configure.m4 b/build/pkgs/pytest_mock/spkg-configure.m4 new file mode 100644 index 00000000000..da82e85e76a --- /dev/null +++ b/build/pkgs/pytest_mock/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([pytest_mock], [SAGE_PYTHON_PACKAGE_CHECK([pytest_mock])]) diff --git a/build/pkgs/pytest_xdist/spkg-configure.m4 b/build/pkgs/pytest_xdist/spkg-configure.m4 new file mode 100644 index 00000000000..62150d6edff --- /dev/null +++ b/build/pkgs/pytest_xdist/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([pytest_xdist], [SAGE_PYTHON_PACKAGE_CHECK([pytest_xdist])]) diff --git a/build/pkgs/python_build/spkg-configure.m4 b/build/pkgs/python_build/spkg-configure.m4 new file mode 100644 index 00000000000..6d4543116f1 --- /dev/null +++ b/build/pkgs/python_build/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([python_build], [SAGE_PYTHON_PACKAGE_CHECK([python_build])]) diff --git a/build/pkgs/python_igraph/spkg-configure.m4 b/build/pkgs/python_igraph/spkg-configure.m4 new file mode 100644 index 00000000000..392ceb0a8f1 --- /dev/null +++ b/build/pkgs/python_igraph/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([python_igraph], [SAGE_PYTHON_PACKAGE_CHECK([python_igraph])]) diff --git a/build/pkgs/python_json_logger/spkg-configure.m4 b/build/pkgs/python_json_logger/spkg-configure.m4 new file mode 100644 index 00000000000..23245405de8 --- /dev/null +++ b/build/pkgs/python_json_logger/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([python_json_logger], [SAGE_PYTHON_PACKAGE_CHECK([python_json_logger])]) diff --git a/build/pkgs/pytz_deprecation_shim/spkg-configure.m4 b/build/pkgs/pytz_deprecation_shim/spkg-configure.m4 new file mode 100644 index 00000000000..89e68bb45c4 --- /dev/null +++ b/build/pkgs/pytz_deprecation_shim/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([pytz_deprecation_shim], [SAGE_PYTHON_PACKAGE_CHECK([pytz_deprecation_shim])]) diff --git a/build/pkgs/pyx/spkg-configure.m4 b/build/pkgs/pyx/spkg-configure.m4 new file mode 100644 index 00000000000..91199e8574b --- /dev/null +++ b/build/pkgs/pyx/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([pyx], [SAGE_PYTHON_PACKAGE_CHECK([pyx])]) diff --git a/build/pkgs/pyyaml/spkg-configure.m4 b/build/pkgs/pyyaml/spkg-configure.m4 new file mode 100644 index 00000000000..05d245261c8 --- /dev/null +++ b/build/pkgs/pyyaml/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([pyyaml], [SAGE_PYTHON_PACKAGE_CHECK([pyyaml])]) diff --git a/build/pkgs/qdldl_python/spkg-configure.m4 b/build/pkgs/qdldl_python/spkg-configure.m4 new file mode 100644 index 00000000000..a0408be6143 --- /dev/null +++ b/build/pkgs/qdldl_python/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([qdldl_python], [SAGE_PYTHON_PACKAGE_CHECK([qdldl_python])]) diff --git a/build/pkgs/referencing/spkg-configure.m4 b/build/pkgs/referencing/spkg-configure.m4 new file mode 100644 index 00000000000..6bf88a31d8f --- /dev/null +++ b/build/pkgs/referencing/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([referencing], [SAGE_PYTHON_PACKAGE_CHECK([referencing])]) diff --git a/build/pkgs/rfc3339_validator/spkg-configure.m4 b/build/pkgs/rfc3339_validator/spkg-configure.m4 new file mode 100644 index 00000000000..0746c1bb785 --- /dev/null +++ b/build/pkgs/rfc3339_validator/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([rfc3339_validator], [SAGE_PYTHON_PACKAGE_CHECK([rfc3339_validator])]) diff --git a/build/pkgs/rfc3986_validator/spkg-configure.m4 b/build/pkgs/rfc3986_validator/spkg-configure.m4 new file mode 100644 index 00000000000..e6c5e896a46 --- /dev/null +++ b/build/pkgs/rfc3986_validator/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([rfc3986_validator], [SAGE_PYTHON_PACKAGE_CHECK([rfc3986_validator])]) diff --git a/build/pkgs/rst2ipynb/spkg-configure.m4 b/build/pkgs/rst2ipynb/spkg-configure.m4 new file mode 100644 index 00000000000..48b6c7a04a7 --- /dev/null +++ b/build/pkgs/rst2ipynb/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([rst2ipynb], [SAGE_PYTHON_PACKAGE_CHECK([rst2ipynb])]) diff --git a/build/pkgs/sagelib/spkg-configure.m4 b/build/pkgs/sagelib/spkg-configure.m4 new file mode 100644 index 00000000000..7c833a729dc --- /dev/null +++ b/build/pkgs/sagelib/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([sagelib], [SAGE_PYTHON_PACKAGE_CHECK([sagelib])]) diff --git a/build/pkgs/sagenb_export/spkg-configure.m4 b/build/pkgs/sagenb_export/spkg-configure.m4 new file mode 100644 index 00000000000..b09db05b663 --- /dev/null +++ b/build/pkgs/sagenb_export/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([sagenb_export], [SAGE_PYTHON_PACKAGE_CHECK([sagenb_export])]) diff --git a/build/pkgs/sagetex/spkg-configure.m4 b/build/pkgs/sagetex/spkg-configure.m4 new file mode 100644 index 00000000000..13d44a7ef7e --- /dev/null +++ b/build/pkgs/sagetex/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([sagetex], [SAGE_PYTHON_PACKAGE_CHECK([sagetex])]) diff --git a/build/pkgs/scs/spkg-configure.m4 b/build/pkgs/scs/spkg-configure.m4 new file mode 100644 index 00000000000..21cf4812045 --- /dev/null +++ b/build/pkgs/scs/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([scs], [SAGE_PYTHON_PACKAGE_CHECK([scs])]) diff --git a/build/pkgs/singular_jupyter/spkg-configure.m4 b/build/pkgs/singular_jupyter/spkg-configure.m4 new file mode 100644 index 00000000000..4b0124dad10 --- /dev/null +++ b/build/pkgs/singular_jupyter/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([singular_jupyter], [SAGE_PYTHON_PACKAGE_CHECK([singular_jupyter])]) diff --git a/build/pkgs/slabbe/spkg-configure.m4 b/build/pkgs/slabbe/spkg-configure.m4 new file mode 100644 index 00000000000..3d5663df620 --- /dev/null +++ b/build/pkgs/slabbe/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([slabbe], [SAGE_PYTHON_PACKAGE_CHECK([slabbe])]) diff --git a/build/pkgs/snappy/spkg-configure.m4 b/build/pkgs/snappy/spkg-configure.m4 new file mode 100644 index 00000000000..4ffb99a0045 --- /dev/null +++ b/build/pkgs/snappy/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([snappy], [SAGE_PYTHON_PACKAGE_CHECK([snappy])]) diff --git a/build/pkgs/sniffio/spkg-configure.m4 b/build/pkgs/sniffio/spkg-configure.m4 new file mode 100644 index 00000000000..c24914e3780 --- /dev/null +++ b/build/pkgs/sniffio/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([sniffio], [SAGE_PYTHON_PACKAGE_CHECK([sniffio])]) diff --git a/build/pkgs/soupsieve/spkg-configure.m4 b/build/pkgs/soupsieve/spkg-configure.m4 new file mode 100644 index 00000000000..5d7588f7974 --- /dev/null +++ b/build/pkgs/soupsieve/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([soupsieve], [SAGE_PYTHON_PACKAGE_CHECK([soupsieve])]) diff --git a/build/pkgs/sqlalchemy/spkg-configure.m4 b/build/pkgs/sqlalchemy/spkg-configure.m4 new file mode 100644 index 00000000000..dba8bc35444 --- /dev/null +++ b/build/pkgs/sqlalchemy/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([sqlalchemy], [SAGE_PYTHON_PACKAGE_CHECK([sqlalchemy])]) diff --git a/build/pkgs/surface_dynamics/spkg-configure.m4 b/build/pkgs/surface_dynamics/spkg-configure.m4 new file mode 100644 index 00000000000..0426cbfe45d --- /dev/null +++ b/build/pkgs/surface_dynamics/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([surface_dynamics], [SAGE_PYTHON_PACKAGE_CHECK([surface_dynamics])]) diff --git a/build/pkgs/types_python_dateutil/spkg-configure.m4 b/build/pkgs/types_python_dateutil/spkg-configure.m4 new file mode 100644 index 00000000000..d4a0940919d --- /dev/null +++ b/build/pkgs/types_python_dateutil/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([types_python_dateutil], [SAGE_PYTHON_PACKAGE_CHECK([types_python_dateutil])]) diff --git a/build/pkgs/uri_template/spkg-configure.m4 b/build/pkgs/uri_template/spkg-configure.m4 new file mode 100644 index 00000000000..35335475c2f --- /dev/null +++ b/build/pkgs/uri_template/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([uri_template], [SAGE_PYTHON_PACKAGE_CHECK([uri_template])]) diff --git a/build/pkgs/webcolors/spkg-configure.m4 b/build/pkgs/webcolors/spkg-configure.m4 new file mode 100644 index 00000000000..6bfc58db199 --- /dev/null +++ b/build/pkgs/webcolors/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([webcolors], [SAGE_PYTHON_PACKAGE_CHECK([webcolors])]) diff --git a/build/pkgs/websocket_client/spkg-configure.m4 b/build/pkgs/websocket_client/spkg-configure.m4 new file mode 100644 index 00000000000..67712d904b6 --- /dev/null +++ b/build/pkgs/websocket_client/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([websocket_client], [SAGE_PYTHON_PACKAGE_CHECK([websocket_client])]) From f242f3c460cec630bab882427afaed56621a9db3 Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Tue, 19 Dec 2023 16:29:08 +0000 Subject: [PATCH 025/278] Refactor code and isolate precomputation --- .../discrete_gaussian_lattice.py | 140 ++++++++++++------ 1 file changed, 92 insertions(+), 48 deletions(-) diff --git a/src/sage/stats/distributions/discrete_gaussian_lattice.py b/src/sage/stats/distributions/discrete_gaussian_lattice.py index fabcc799e39..a06690fad4c 100644 --- a/src/sage/stats/distributions/discrete_gaussian_lattice.py +++ b/src/sage/stats/distributions/discrete_gaussian_lattice.py @@ -236,7 +236,7 @@ def _normalisation_factor_zz(self, tau=None, prec=None): sage: while v not in counter: ....: add_samples(1000) - sage: while abs(m*f(v)*1.0/nf/counter[v] - 1.0) >= 0.2: # long time, needs sage.symbolic + sage: while abs(m*f(v)*1.0/nf/counter[v] - 1.0) >= 0.2: # long time, needs sage.symbolic ....: add_samples(1000) sage: D = DGL(ZZ^8, 0.5) @@ -278,10 +278,10 @@ def f_or_hat(x): return R(exp(-x / (2 * sigma**2))) if not self.is_spherical: - # NOTE: This is only a poor approximation placeholder. + # TODO: This is only a poor approximation placeholder. # It should be easy to implement, since the Fourier transform # is essentially the same, but I can't figure out how to - # tweak the `.qfrep` call below correctly. TODO. + # tweak the `.qfrep` call below correctly. from warnings import warn warn("Note: `_normalisation_factor_zz` has not been properly "\ "implemented for non-spherical distributions.") @@ -301,7 +301,7 @@ def f_or_hat(x): if self.is_spherical and not self._c_in_lattice: raise NotImplementedError("Lattice must contain 0 for now.") - if self.B.base_ring() not in ZZ: + if self.B.base_ring() != ZZ: raise NotImplementedError("Lattice must be integral for now.") sigma = self._sigma @@ -384,14 +384,14 @@ def _randomise(self, v): """ return vector(ZZ, [DGI(self.r, c=vi)() for vi in v]) - def __init__(self, B, sigma=1, c=None, r=None, precision=None): + def __init__(self, B, sigma=1, c=0, r=None, precision=None, sigma_basis=False): r""" Construct a discrete Gaussian sampler over the lattice `Λ(B)` with parameter ``sigma`` and center `c`. INPUT: - - ``B`` -- a basis for the lattice, one of the following: + - ``B`` -- a (row) basis for the lattice, one of the following: - an integer matrix, - an object with a ``matrix()`` method, e.g. ``ZZ^n``, or @@ -401,9 +401,10 @@ def __init__(self, B, sigma=1, c=None, r=None, precision=None): - a real number `σ > 0` (spherical), - a positive definite matrix `Σ` (non-spherical), or - - any matrix-like ``S``, equivalent to ``Σ = SSᵀ`` + - any matrix-like ``S``, equivalent to ``Σ = SSᵀ``, when + ``sigma_basis`` is set - - ``c`` -- (default: None) center `c`, any vector in `\ZZ^n` is + - ``c`` -- (default: 0) center `c`, any vector in `\ZZ^n` is supported, but `c ∈ Λ(B)` is faster. - ``r`` -- (default: None) rounding parameter `r` as defined in @@ -413,6 +414,9 @@ def __init__(self, B, sigma=1, c=None, r=None, precision=None): - ``precision`` -- bit precision `≥ 53`. + - ``sigma_basis`` -- (default: False) When set, ``sigma`` is treated as + a basis, i.e. the covariance matrix is computed by ``Σ = SSᵀ``. + EXAMPLES:: sage: from sage.stats.all import DGL @@ -459,8 +463,8 @@ def __init__(self, B, sigma=1, c=None, r=None, precision=None): The non-spherical sampler supports offline computation to speed up sampling. This will be useful when changing the center `c` is supported. - The difference is more significant for larger matrices. For 128x128 the - author of this sentence see a 4x speedup (86s -> 20s). + The difference is more significant for larger matrices. For 128x128 we + observe a 4x speedup (86s -> 20s). sage: D.offline_samples = [] sage: T = 2**12 @@ -487,15 +491,15 @@ def __init__(self, B, sigma=1, c=None, r=None, precision=None): # Check if sigma is a (real) number or a scaled identity matrix self.is_spherical = True try: - self._sigma = self._RR(sigma) + self.sigma = self._RR(sigma) except TypeError: - self._sigma = matrix(self._RR, sigma) + self.sigma = matrix(self._RR, sigma) # Will it be "annoying" if a matrix Sigma has different behaviour # sometimes? There should be a parameter in the consrtuctor - if self._sigma == self._sigma[0, 0]: - self._sigma = self._RR(self._sigma[0, 0]) + if self.sigma == self.sigma[0, 0]: + self.sigma = self._RR(self.sigma[0, 0]) else: - if not self._sigma.is_positive_definite(): + if not self.sigma.is_positive_definite(): raise RuntimeError(f"Sigma(={self._sigma}) is not positive definite") self.is_spherical = False @@ -516,36 +520,36 @@ def __init__(self, B, sigma=1, c=None, r=None, precision=None): self._G = B.gram_schmidt()[0] self._c_in_lattice = False - try: - c = vector(ZZ, self.n, c) - except TypeError: - try: - c = vector(QQ, self.n, c) - except TypeError: - c = vector(self._RR, self.n, c) + self.D = None + self.VS = None + self._c_mul_B_inv = None + self.r = r - self._c = c + self.c = c + def _precompute_data(self): + r""" + Precomputes basis data. + """ if self.is_spherical: # deal with trivial case first, it is common if self._G == 1 and self.c == 0: self._c_in_lattice = True - D = DGI(sigma=sigma) + D = DGI(sigma=self.sigma) self.D = tuple([D for _ in range(self.B.nrows())]) - self.VS = FreeModule(ZZ, B.nrows()) - return + self.VS = FreeModule(ZZ, self.B.nrows()) else: try: - w = B.solve_left(c) - if w in ZZ ** B.nrows(): + w = self.B.solve_left(self.c) + if w in ZZ ** self.B.nrows(): self._c_in_lattice = True D = [] for i in range(self.B.nrows()): - sigma_ = self._sigma / self._G[i].norm() + sigma_ = self.sigma / self._G[i].norm() D.append(DGI(sigma=sigma_)) self.D = tuple(D) - self.VS = FreeModule(ZZ, B.nrows()) + self.VS = FreeModule(ZZ, self.B.nrows()) except ValueError: pass else: @@ -555,26 +559,27 @@ def __init__(self, B, sigma=1, c=None, r=None, precision=None): # Offline samples of B⁻¹D₁ self.offline_samples = [] - self.B_inv = B.inverse() + self.B_inv = self.B.inverse() self.sigma_inv = self.sigma.inverse() + self._c_mul_B_inv = self.c * self.B_inv - if r is None: + if self.r is None: # Compute the maximal r such that (Sigma - r^2 * Q) > 0 - r = self._maximal_r() * 0.9999 - r = self._RR(r) + self.r = self._maximal_r() * 0.9999 + self.r = self._RR(self.r) - Sigma2 = self._sigma - r**2 * self.Q + Sigma2 = self._sigma - self.r**2 * self.Q try: - self.r = r verbose(f"Computing Cholesky decomposition of a {Sigma2.dimensions()} matrix") self.B2 = Sigma2.cholesky().T self.B2_B_inv = self.B2 * self.B_inv except ValueError: raise ValueError("Σ₂ is not positive definite. Is your "\ - f"r(={r}) too large? It should be at most "\ + f"r(={self.r}) too large? It should be at most "\ f"{self._maximal_r()}") + def __call__(self): r""" Return a new sample. @@ -640,6 +645,25 @@ def sigma(self): """ return self._sigma + @sigma.setter + def sigma(self, sigma): + r""" + Modifies center `σ`. + + EXAMPLES:: + + sage: from sage.stats.all import DGL + sage: D = DGL(ZZ^3, 3.0, c=(1,0,0)) + sage: D.c = (2, 0, 0) + sage: D + Discrete Gaussian sampler with Gaussian parameter σ = 3.00000000000000, c=(2, 0, 0) over lattice with basis + + [1 0 0] + [0 1 0] + [0 0 1] + """ + self._sigma = sigma + @property def c(self): r""" @@ -663,7 +687,7 @@ def c(self): return self._c @c.setter - def c(self, _): + def c(self, c): r""" Modifies center `c` @@ -671,14 +695,35 @@ def c(self, _): sage: from sage.stats.all import DGL sage: D = DGL(ZZ^3, 3.0, c=(1,0,0)) - sage: D.c = 5 - Traceback (most recent call last): - ... - NotImplementedError: Modifying c is not yet supported! + sage: D.c = (2, 0, 0) + sage: D + Discrete Gaussian sampler with Gaussian parameter σ = 3.00000000000000, c=(2, 0, 0) over lattice with basis + + [1 0 0] + [0 1 0] + [0 0 1] """ - # TODO: Isolate code to set `c` here, so that the offline part of - # non-spherical sampling can be effectively utilised - raise NotImplementedError("Modifying c is not yet supported!") + if c is None: + self._c = None + return + + if c == 0: + c = vector(ZZ, self.n) + else: + try: + c = vector(ZZ, self.n, c) + except TypeError: + try: + c = vector(QQ, self.n, c) + except TypeError: + try: + c = vector(self._RR, self.n, c) + except TypeError: + c = vector(self._RR, self.n) + + self._c = c + self._precompute_data() + def __repr__(self): r""" @@ -703,7 +748,6 @@ def __repr__(self): [0 1 0] [0 0 1] """ - # beware of unicode character in ascii string ! if self.is_spherical: sigma_str = f"σ = {self._sigma}" else: @@ -749,7 +793,7 @@ def _call(self): Do not call this method directly, call :func:`DGL.__call__` instead. """ v = 0 - c, sigma, B = self._c, self._sigma, self.B + c, sigma, B = self.c, self._sigma, self.B m = self.B.nrows() @@ -805,7 +849,7 @@ def _call_non_spherical(self): """ if len(self.offline_samples) == 0: self.add_offline_samples() - vec = self.c * self.B_inv - self.offline_samples.pop() + vec = self._c_mul_B_inv - self.offline_samples.pop() return self._randomise(vec) * self.B From fe7fb8d32a753ff78073fe4e49c2f7c1d2c06932 Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Tue, 19 Dec 2023 17:23:43 +0000 Subject: [PATCH 026/278] lint: Fix pycodestyle --- .../discrete_gaussian_lattice.py | 29 ++++++++++++------- .../discrete_gaussian_polynomial.py | 4 +-- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/sage/stats/distributions/discrete_gaussian_lattice.py b/src/sage/stats/distributions/discrete_gaussian_lattice.py index a06690fad4c..e5f7853a410 100644 --- a/src/sage/stats/distributions/discrete_gaussian_lattice.py +++ b/src/sage/stats/distributions/discrete_gaussian_lattice.py @@ -26,7 +26,7 @@ sage: a.parent() Ambient free module of rank 10 over the principal ideal domain Integer Ring """ -#****************************************************************************** +# ****************************************************************************** # # DGS - Discrete Gaussian Samplers # @@ -56,7 +56,7 @@ # The views and conclusions contained in the software and documentation are # those of the authors and should not be interpreted as representing official # policies, either expressed or implied, of the FreeBSD Project. -#*****************************************************************************/ +# *****************************************************************************/ from sage.functions.log import exp from sage.rings.real_mpfr import RealField @@ -283,7 +283,7 @@ def f_or_hat(x): # is essentially the same, but I can't figure out how to # tweak the `.qfrep` call below correctly. from warnings import warn - warn("Note: `_normalisation_factor_zz` has not been properly "\ + warn("Note: `_normalisation_factor_zz` has not been properly " "implemented for non-spherical distributions.") import itertools from sage.functions.log import log @@ -415,7 +415,7 @@ def __init__(self, B, sigma=1, c=0, r=None, precision=None, sigma_basis=False): - ``precision`` -- bit precision `≥ 53`. - ``sigma_basis`` -- (default: False) When set, ``sigma`` is treated as - a basis, i.e. the covariance matrix is computed by ``Σ = SSᵀ``. + a (row) basis, i.e. the covariance matrix is computed by ``Σ = SSᵀ``. EXAMPLES:: @@ -461,6 +461,16 @@ def __init__(self, B, sigma=1, c=0, r=None, precision=None, sigma_basis=False): sage: while v not in counter: add_samples(1000) sage: while abs(m*f(v)*1.0/nf/counter[v] - 1.0) >= 0.1: add_samples(1000) + The sampler supports passing a basis for the covariance. + + sage: n = 3 + sage: S = Matrix(ZZ, [[2, 0, 0], [-1, 3, 0], [2, -1, 1]]) + sage: D = DGL(ZZ^n, S, sigma_basis=True) + sage: D.sigma + [ 4.00000000000000 -2.00000000000000 4.00000000000000] + [-2.00000000000000 10.0000000000000 -5.00000000000000] + [ 4.00000000000000 -5.00000000000000 6.00000000000000] + The non-spherical sampler supports offline computation to speed up sampling. This will be useful when changing the center `c` is supported. The difference is more significant for larger matrices. For 128x128 we @@ -499,6 +509,8 @@ def __init__(self, B, sigma=1, c=0, r=None, precision=None, sigma_basis=False): if self.sigma == self.sigma[0, 0]: self.sigma = self._RR(self.sigma[0, 0]) else: + if sigma_basis: + self.sigma = self.sigma * self.sigma.T if not self.sigma.is_positive_definite(): raise RuntimeError(f"Sigma(={self._sigma}) is not positive definite") self.is_spherical = False @@ -574,12 +586,10 @@ def _precompute_data(self): self.B2 = Sigma2.cholesky().T self.B2_B_inv = self.B2 * self.B_inv except ValueError: - raise ValueError("Σ₂ is not positive definite. Is your "\ - f"r(={self.r}) too large? It should be at most "\ + raise ValueError("Σ₂ is not positive definite. Is your " + f"r(={self.r}) too large? It should be at most " f"{self._maximal_r()}") - - def __call__(self): r""" Return a new sample. @@ -724,7 +734,6 @@ def c(self, c): self._c = c self._precompute_data() - def __repr__(self): r""" EXAMPLES:: @@ -754,7 +763,6 @@ def __repr__(self): sigma_str = f"Σ =\n{self._sigma}" return f"Discrete Gaussian sampler with Gaussian parameter {sigma_str}, c={self.c} over lattice with basis\n\n{self.B}" - def _call_in_lattice(self): r""" Return a new sample assuming `c ∈ Λ(B)`. @@ -807,7 +815,6 @@ def _call(self): v = v + z * B[i] return v - def add_offline_samples(self, cnt=1): """ Precompute samples from B⁻¹D₁ to be used in :meth:`_call_non_spherical` diff --git a/src/sage/stats/distributions/discrete_gaussian_polynomial.py b/src/sage/stats/distributions/discrete_gaussian_polynomial.py index 63ab7084feb..b2d18da06e1 100644 --- a/src/sage/stats/distributions/discrete_gaussian_polynomial.py +++ b/src/sage/stats/distributions/discrete_gaussian_polynomial.py @@ -23,7 +23,7 @@ (24.0, 24.0) """ -#****************************************************************************** +# ****************************************************************************** # # DGS - Discrete Gaussian Samplers # @@ -53,7 +53,7 @@ # The views and conclusions contained in the software and documentation are # those of the authors and should not be interpreted as representing official # policies, either expressed or implied, of the FreeBSD Project. -#*****************************************************************************/ +# *****************************************************************************/ from sage.rings.real_mpfr import RR from sage.rings.integer_ring import ZZ From 9bd54b3013de82b1bd156ad0df1d0e81ab66bd98 Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Tue, 19 Dec 2023 17:32:46 +0000 Subject: [PATCH 027/278] lint: Fix relint imports --- .../discrete_gaussian_lattice.py | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/sage/stats/distributions/discrete_gaussian_lattice.py b/src/sage/stats/distributions/discrete_gaussian_lattice.py index 5e553851021..705f9390b09 100644 --- a/src/sage/stats/distributions/discrete_gaussian_lattice.py +++ b/src/sage/stats/distributions/discrete_gaussian_lattice.py @@ -18,7 +18,7 @@ EXAMPLES:: - sage: from sage.stats.all import DGL + sage: from sage.stats.distributions.discrete_gaussian_lattice import DGL sage: D = DGL(ZZ^10, 3.0) sage: D(), D(), D() # random ((3, 0, -5, 0, -1, -3, 3, 3, -7, 2), (4, 0, 1, -2, -4, -4, 4, 0, 1, -4), (-3, 0, 4, 5, 0, 1, 3, 2, 0, -1)) @@ -120,7 +120,7 @@ class DiscreteGaussianDistributionLatticeSampler(SageObject): EXAMPLES:: - sage: from sage.stats.all import DGL + sage: from sage.stats.distributions.discrete_gaussian_lattice import DGL sage: D = DGL(ZZ^10, 3.0); D Discrete Gaussian sampler with Gaussian parameter σ = 3.00000000000000, c=(0, 0, 0, 0, 0, 0, 0, 0, 0, 0) over lattice with basis @@ -138,7 +138,7 @@ class DiscreteGaussianDistributionLatticeSampler(SageObject): We plot a histogram:: - sage: from sage.stats.all import DGL + sage: from sage.stats.distributions.discrete_gaussian_lattice import DGL sage: import warnings sage: warnings.simplefilter('ignore', UserWarning) sage: D = DGL(identity_matrix(2), 3.0) @@ -167,7 +167,7 @@ def compute_precision(precision, sigma): EXAMPLES:: - sage: from sage.stats.all import DGL + sage: from sage.stats.distributions.discrete_gaussian_lattice import DGL sage: DGL.compute_precision(100, RR(3)) 100 sage: DGL.compute_precision(100, RealField(200)(3)) @@ -208,7 +208,7 @@ def _normalisation_factor_zz(self, tau=None, prec=None): EXAMPLES:: - sage: from sage.stats.all import DGL + sage: from sage.stats.distributions.discrete_gaussian_lattice import DGL sage: n = 3; sigma = 1.0 sage: D = DGL(ZZ^n, sigma) sage: f = D.f @@ -349,7 +349,7 @@ def _maximal_r(self): EXAMPLES:: - sage: from sage.stats.all import DGL + sage: from sage.stats.distributions.discrete_gaussian_lattice import DGL sage: n = 3 sage: Sigma = Matrix(ZZ, [[5, -2, 4], [-2, 10, -5], [4, -5, 5]]) sage: c = vector(ZZ, [7, 2, 5]) @@ -420,7 +420,7 @@ def __init__(self, B, sigma=1, c=0, r=None, precision=None, sigma_basis=False): EXAMPLES:: - sage: from sage.stats.all import DGL + sage: from sage.stats.distributions.discrete_gaussian_lattice import DGL sage: n = 2; sigma = 3.0 sage: D = DGL(ZZ^n, sigma) sage: f = D.f @@ -485,7 +485,7 @@ def __init__(self, B, sigma=1, c=0, r=None, precision=None, sigma_basis=False): We can also initialise with matrix-like objects: - sage: from sage.stats.all import DGL + sage: from sage.stats.distributions.discrete_gaussian_lattice import DGL sage: qf = QuadraticForm(matrix(3, [2, 1, 1, 1, 2, 1, 1, 1, 2])) sage: D = DGL(qf, 3.0); D # needs sage.symbolic Discrete Gaussian sampler with Gaussian parameter σ = 3.00000000000000, c=(0, 0, 0) over lattice with basis @@ -597,7 +597,7 @@ def __call__(self): EXAMPLES:: - sage: from sage.stats.all import DGL + sage: from sage.stats.distributions.discrete_gaussian_lattice import DGL sage: D = DGL(ZZ^3, 3.0, c=(1,0,0)) sage: L = [D() for _ in range(2^12)] sage: mean_L = sum(L) / len(L) @@ -649,7 +649,7 @@ def sigma(self): EXAMPLES:: - sage: from sage.stats.all import DGL + sage: from sage.stats.distributions.discrete_gaussian_lattice import DGL sage: D = DGL(ZZ^3, 3.0, c=(1,0,0)) sage: D.sigma 3.00000000000000 @@ -663,7 +663,7 @@ def sigma(self, sigma): EXAMPLES:: - sage: from sage.stats.all import DGL + sage: from sage.stats.distributions.discrete_gaussian_lattice import DGL sage: D = DGL(ZZ^3, 3.0, c=(1,0,0)) sage: D.c = (2, 0, 0) sage: D @@ -684,7 +684,7 @@ def c(self): EXAMPLES:: - sage: from sage.stats.all import DGL + sage: from sage.stats.distributions.discrete_gaussian_lattice import DGL sage: D = DGL(ZZ^3, 3.0, c=(1,0,0)); D Discrete Gaussian sampler with Gaussian parameter σ = 3.00000000000000, c=(1, 0, 0) over lattice with basis @@ -704,7 +704,7 @@ def c(self, c): EXAMPLES:: - sage: from sage.stats.all import DGL + sage: from sage.stats.distributions.discrete_gaussian_lattice import DGL sage: D = DGL(ZZ^3, 3.0, c=(1,0,0)) sage: D.c = (2, 0, 0) sage: D @@ -739,7 +739,7 @@ def __repr__(self): r""" EXAMPLES:: - sage: from sage.stats.all import DGL + sage: from sage.stats.distributions.discrete_gaussian_lattice import DGL sage: D = DGL(ZZ^3, 3.0, c=(1,0,0)); D Discrete Gaussian sampler with Gaussian parameter σ = 3.00000000000000, c=(1, 0, 0) over lattice with basis @@ -770,7 +770,7 @@ def _call_in_lattice(self): EXAMPLES:: - sage: from sage.stats.all import DGL + sage: from sage.stats.distributions.discrete_gaussian_lattice import DGL sage: D = DGL(ZZ^3, 3.0, c=(1,0,0)) sage: L = [D._call_in_lattice() for _ in range(2^12)] sage: mean_L = sum(L) / len(L) @@ -790,7 +790,7 @@ def _call(self): EXAMPLES:: - sage: from sage.stats.all import DGL + sage: from sage.stats.distributions.discrete_gaussian_lattice import DGL sage: D = DGL(ZZ^3, 3.0, c=(1/2,0,0)) sage: L = [D._call() for _ in range(2^12)] sage: mean_L = sum(L) / len(L) @@ -822,7 +822,7 @@ def add_offline_samples(self, cnt=1): EXAMPLES:: - sage: from sage.stats.all import DGL + sage: from sage.stats.distributions.discrete_gaussian_lattice import DGL sage: Sigma = Matrix([[5, -2, 4], [-2, 10, -5], [4, -5, 5]]) sage: D = DGL(ZZ^3, Sigma) sage: assert not D.is_spherical @@ -843,7 +843,7 @@ def _call_non_spherical(self): EXAMPLES:: - sage: from sage.stats.all import DGL + sage: from sage.stats.distributions.discrete_gaussian_lattice import DGL sage: Sigma = Matrix([[5, -2, 4], [-2, 10, -5], [4, -5, 5]]) sage: D = DGL(ZZ^3, Sigma, c=(1/2,0,0)) sage: L = [D._call_non_spherical() for _ in range(2^12)] From 2000fc88764a3f99b8f7e83316ba6746a2ab4006 Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Wed, 20 Dec 2023 10:25:51 +0000 Subject: [PATCH 028/278] Fix coverage --- .../discrete_gaussian_lattice.py | 51 ++++++++++++++----- 1 file changed, 38 insertions(+), 13 deletions(-) diff --git a/src/sage/stats/distributions/discrete_gaussian_lattice.py b/src/sage/stats/distributions/discrete_gaussian_lattice.py index 705f9390b09..24fd0075cbe 100644 --- a/src/sage/stats/distributions/discrete_gaussian_lattice.py +++ b/src/sage/stats/distributions/discrete_gaussian_lattice.py @@ -260,6 +260,18 @@ def _normalisation_factor_zz(self, tau=None, prec=None): Traceback (most recent call last): ... NotImplementedError: Basis must be a square matrix for now. + + sage: D = DGL(ZZ^3, c=(1/2, 0, 0)) + sage: D._normalisation_factor_zz() + Traceback (most recent call last): + ... + NotImplementedError: Lattice must contain 0 for now. + + sage: D = DGL(Matrix(3, 3, 1/2)) + sage: D._normalisation_factor_zz() + Traceback (most recent call last): + ... + NotImplementedError: Lattice must be integral for now. """ # If σ > 1: @@ -359,7 +371,6 @@ def _maximal_r(self): sage: e_vals = (D.sigma - r^2 * D.Q).eigenvalues() sage: assert all(e_val >= -1e-12 for e_val in e_vals) """ - # TODO: Write doctest if self.is_spherical: raise RuntimeError("You have encountered a bug. File it! :)") @@ -450,6 +461,15 @@ def __init__(self, B, sigma=1, c=0, r=None, precision=None, sigma_basis=False): sage: while abs(m*f(v)*1.0/nf/counter[v] - 1.0) >= 0.1: # needs sage.symbolic ....: add_samples(1000) + Spherical covariance are automatically handled. + + sage: DGL(ZZ^3, sigma=Matrix(3, 3, 2)) + Discrete Gaussian sampler with Gaussian parameter σ = 2.00000000000000, c=(0, 0, 0) over lattice with basis + + [1 0 0] + [0 1 0] + [0 0 1] + The sampler supports non-spherical covariance in the form of a Gram matrix. @@ -462,6 +482,14 @@ def __init__(self, B, sigma=1, c=0, r=None, precision=None, sigma_basis=False): sage: while v not in counter: add_samples(1000) sage: while abs(m*f(v)*1.0/nf/counter[v] - 1.0) >= 0.1: add_samples(1000) + If the covariance provided is not positive definite, an error is thrown. + + sage: Sigma = Matrix(ZZ, [[0, 1], [1, 0]]) + sage: DGL(ZZ^2, Sigma) + ... + RuntimeError: Sigma(=[0.000000000000000 1.00000000000000] + [ 1.00000000000000 0.000000000000000]) is not positive definite + The sampler supports passing a basis for the covariance. sage: n = 3 @@ -553,18 +581,15 @@ def _precompute_data(self): self.VS = FreeModule(ZZ, self.B.nrows()) else: - try: - w = self.B.solve_left(self.c) - if w in ZZ ** self.B.nrows(): - self._c_in_lattice = True - D = [] - for i in range(self.B.nrows()): - sigma_ = self.sigma / self._G[i].norm() - D.append(DGI(sigma=sigma_)) - self.D = tuple(D) - self.VS = FreeModule(ZZ, self.B.nrows()) - except ValueError: - pass + w = self.B.solve_left(self.c) + if w in ZZ ** self.B.nrows(): + self._c_in_lattice = True + D = [] + for i in range(self.B.nrows()): + sigma_ = self.sigma / self._G[i].norm() + D.append(DGI(sigma=sigma_)) + self.D = tuple(D) + self.VS = FreeModule(ZZ, self.B.nrows()) else: # Variables Sigma2 and r are from [Pei2010]_ # TODO: B is implicitly assumed to be full-rank for the From 4d2c72cfd7df0db97eaa59a02d5bfaf358142b16 Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Wed, 20 Dec 2023 16:19:58 +0000 Subject: [PATCH 029/278] Fix doctest Why are there so many bugs :/ --- src/sage/stats/distributions/discrete_gaussian_lattice.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sage/stats/distributions/discrete_gaussian_lattice.py b/src/sage/stats/distributions/discrete_gaussian_lattice.py index 24fd0075cbe..c69b3ba1561 100644 --- a/src/sage/stats/distributions/discrete_gaussian_lattice.py +++ b/src/sage/stats/distributions/discrete_gaussian_lattice.py @@ -237,7 +237,7 @@ def _normalisation_factor_zz(self, tau=None, prec=None): sage: while v not in counter: ....: add_samples(1000) - sage: while abs(m*f(v)*1.0/nf/counter[v] - 1.0) >= 0.2: # long time, needs sage.symbolic + sage: while abs(m*f(v)*1.0/nf/counter[v] - 1.0) >= 0.2: # long time, needs sage.symbolic ....: add_samples(1000) sage: D = DGL(ZZ^8, 0.5) @@ -486,6 +486,7 @@ def __init__(self, B, sigma=1, c=0, r=None, precision=None, sigma_basis=False): sage: Sigma = Matrix(ZZ, [[0, 1], [1, 0]]) sage: DGL(ZZ^2, Sigma) + Traceback (most recent call last): ... RuntimeError: Sigma(=[0.000000000000000 1.00000000000000] [ 1.00000000000000 0.000000000000000]) is not positive definite From 6663315b7b06974cd7c2f67dd4d5a061a10e784f Mon Sep 17 00:00:00 2001 From: Lorenz Panny Date: Tue, 19 Dec 2023 20:32:57 +0100 Subject: [PATCH 030/278] helper method to concatenate vectors --- src/sage/modules/free_module_element.pyx | 31 ++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/sage/modules/free_module_element.pyx b/src/sage/modules/free_module_element.pyx index 3492dbf2eb1..ea330cc76f5 100644 --- a/src/sage/modules/free_module_element.pyx +++ b/src/sage/modules/free_module_element.pyx @@ -4148,6 +4148,37 @@ cdef class FreeModuleElement(Vector): # abstract base class nintegrate=nintegral + def concatenate(self, other): + r""" + Return the result of concatenating this vector with another + vector over the same ring. + + EXAMPLES:: + + sage: v = vector([1, 2, 3]) + sage: w = vector([4, 5]) + sage: v.concatenate(w) + (1, 2, 3, 4, 5) + sage: v.parent() + Ambient free module of rank 3 over the principal ideal domain Integer Ring + sage: w.parent() + Ambient free module of rank 2 over the principal ideal domain Integer Ring + sage: v.concatenate(w).parent() + Ambient free module of rank 5 over the principal ideal domain Integer Ring + + The method fails when the vectors aren't defined over the same ring:: + + sage: w2 = vector(QQ, [4, 5]) + sage: v.concatenate(w2) + Traceback (most recent call last): + ... + ValueError: can only concatenate vectors over the same base ring + """ + R = self.parent().base_ring() + if other.parent().base_ring() != R: + raise ValueError('can only concatenate vectors over the same base ring') + return vector(R, list(self) + list(other)) + ############################################# # Generic dense element ############################################# From 4b5f2a54475c72d82651aef1e90a06025dbd74aa Mon Sep 17 00:00:00 2001 From: Lorenz Panny Date: Thu, 28 Dec 2023 23:06:26 +0100 Subject: [PATCH 031/278] add another type check following reviewer comments --- src/sage/modules/free_module_element.pyx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/sage/modules/free_module_element.pyx b/src/sage/modules/free_module_element.pyx index ea330cc76f5..cfb8d32b834 100644 --- a/src/sage/modules/free_module_element.pyx +++ b/src/sage/modules/free_module_element.pyx @@ -4166,14 +4166,22 @@ cdef class FreeModuleElement(Vector): # abstract base class sage: v.concatenate(w).parent() Ambient free module of rank 5 over the principal ideal domain Integer Ring - The method fails when the vectors aren't defined over the same ring:: + The method fails when the inputs aren't both vectors, or the vectors + aren't defined over the same ring:: + sage: w2 = polygen(QQ)^4 + 5 + sage: v.concatenate(w2) + Traceback (most recent call last): + ... + TypeError: can only concatenate two vectors sage: w2 = vector(QQ, [4, 5]) sage: v.concatenate(w2) Traceback (most recent call last): ... ValueError: can only concatenate vectors over the same base ring """ + if not isinstance(other, FreeModuleElement): + raise TypeError('can only concatenate two vectors') R = self.parent().base_ring() if other.parent().base_ring() != R: raise ValueError('can only concatenate vectors over the same base ring') From 4d1f385d1670daae69e0b7d1e08d546aaff8acd6 Mon Sep 17 00:00:00 2001 From: Lorenz Panny Date: Sat, 13 Jan 2024 15:23:34 +0100 Subject: [PATCH 032/278] support arbitrary iterables per reviewer comments --- src/sage/modules/free_module_element.pyx | 82 ++++++++++++++++++------ 1 file changed, 62 insertions(+), 20 deletions(-) diff --git a/src/sage/modules/free_module_element.pyx b/src/sage/modules/free_module_element.pyx index cfb8d32b834..28da5ed21a7 100644 --- a/src/sage/modules/free_module_element.pyx +++ b/src/sage/modules/free_module_element.pyx @@ -4148,10 +4148,15 @@ cdef class FreeModuleElement(Vector): # abstract base class nintegrate=nintegral - def concatenate(self, other): + def concatenate(self, other, *, ring=None): r""" - Return the result of concatenating this vector with another - vector over the same ring. + Return the result of concatenating this vector with a sequence + of elements given by another iterable. + + If the optional keyword argument ``ring`` is passed, this method + will return a vector over the specified ring (or fail). If no + base ring is given, the base ring is determined automatically by + the :func:`vector` constructor. EXAMPLES:: @@ -4166,26 +4171,63 @@ cdef class FreeModuleElement(Vector): # abstract base class sage: v.concatenate(w).parent() Ambient free module of rank 5 over the principal ideal domain Integer Ring - The method fails when the inputs aren't both vectors, or the vectors - aren't defined over the same ring:: + Forcing a base ring is possible using the ``ring`` argument:: - sage: w2 = polygen(QQ)^4 + 5 + sage: v.concatenate(w, ring=QQ) + (1, 2, 3, 4, 5) + sage: v.concatenate(w, ring=QQ).parent() + Vector space of dimension 5 over Rational Field + + :: + + sage: v.concatenate(w, ring=Zmod(3)) + (1, 2, 0, 1, 2) + + The method accepts arbitrary iterables of elements which can + be coerced to a common base ring:: + + sage: v.concatenate(range(4,8)) + (1, 2, 3, 4, 5, 6, 7) + sage: v.concatenate(range(4,8)).parent() + Ambient free module of rank 7 over the principal ideal domain Integer Ring + + :: + + sage: w2 = [4, QQbar(-5).sqrt()] sage: v.concatenate(w2) - Traceback (most recent call last): - ... - TypeError: can only concatenate two vectors - sage: w2 = vector(QQ, [4, 5]) + (1, 2, 3, 4, 2.236...*I) + sage: v.concatenate(w2).parent() + Vector space of dimension 5 over Algebraic Field + sage: w2 = vector(w2) sage: v.concatenate(w2) - Traceback (most recent call last): - ... - ValueError: can only concatenate vectors over the same base ring - """ - if not isinstance(other, FreeModuleElement): - raise TypeError('can only concatenate two vectors') - R = self.parent().base_ring() - if other.parent().base_ring() != R: - raise ValueError('can only concatenate vectors over the same base ring') - return vector(R, list(self) + list(other)) + (1, 2, 3, 4, 2.236...*I) + sage: v.concatenate(w2).parent() + Vector space of dimension 5 over Algebraic Field + + :: + + sage: w2 = polygen(QQ)^4 + 5 + sage: v.concatenate(w2) + (1, 2, 3, 5, 0, 0, 0, 1) + sage: v.concatenate(w2).parent() + Vector space of dimension 8 over Rational Field + sage: v.concatenate(w2, ring=ZZ) + (1, 2, 3, 5, 0, 0, 0, 1) + sage: v.concatenate(w2, ring=ZZ).parent() + Ambient free module of rank 8 over the principal ideal domain Integer Ring + + :: + + sage: v.concatenate(GF(9).gens()) + (1, 2, 0, z2) + sage: v.concatenate(GF(9).gens()).parent() + Vector space of dimension 4 over Finite Field in z2 of size 3^2 + """ + from itertools import chain + coeffs = chain(self, other) + if ring is not None: + return vector(ring, coeffs) + return vector(coeffs) ############################################# # Generic dense element From ec3fb7ae0fdb5997b4377f1c834fc9e2f7955690 Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Mon, 15 Jan 2024 23:07:09 +0000 Subject: [PATCH 033/278] Remove @property usage + styles --- src/sage/stats/all.py | 2 +- src/sage/stats/distributions/__init__.py | 3 - src/sage/stats/distributions/all.py | 6 + .../discrete_gaussian_integer.pyx | 1 - .../discrete_gaussian_lattice.py | 184 ++++++++---------- .../discrete_gaussian_polynomial.py | 3 - 6 files changed, 90 insertions(+), 109 deletions(-) delete mode 100644 src/sage/stats/distributions/__init__.py diff --git a/src/sage/stats/all.py b/src/sage/stats/all.py index 3a437b6b6a3..e0f0b6e874e 100644 --- a/src/sage/stats/all.py +++ b/src/sage/stats/all.py @@ -2,7 +2,7 @@ from .r import ttest from .basic_stats import (mean, mode, std, variance, median, moving_average) from .hmm import all as hmm -from .distributions import (DGI, DGL, DGP) +from .distributions.all import * # We lazy_import the following modules since they import numpy which # slows down sage startup diff --git a/src/sage/stats/distributions/__init__.py b/src/sage/stats/distributions/__init__.py deleted file mode 100644 index 5e9426ef1c0..00000000000 --- a/src/sage/stats/distributions/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .discrete_gaussian_integer import DGI -from .discrete_gaussian_lattice import DGL -from .discrete_gaussian_polynomial import DGP diff --git a/src/sage/stats/distributions/all.py b/src/sage/stats/distributions/all.py index e69de29bb2d..d37a8563ec6 100644 --- a/src/sage/stats/distributions/all.py +++ b/src/sage/stats/distributions/all.py @@ -0,0 +1,6 @@ +# We lazy_import the following modules since they import numpy which +# slows down sage startup +from sage.misc.lazy_import import lazy_import +lazy_import("sage.stats.distributions.discrete_gaussian_integer", ["DiscreteGaussianDistributionIntegerSampler"]) +lazy_import("sage.stats.distributions.discrete_gaussian_lattice", ["DiscreteGaussianDistributionLatticeSampler"]) +lazy_import("sage.stats.distributions.discrete_gaussian_polynomial", ["DiscreteGaussianDistributionPolynomialSampler"]) diff --git a/src/sage/stats/distributions/discrete_gaussian_integer.pyx b/src/sage/stats/distributions/discrete_gaussian_integer.pyx index c221cee7003..dcec2bc6d58 100644 --- a/src/sage/stats/distributions/discrete_gaussian_integer.pyx +++ b/src/sage/stats/distributions/discrete_gaussian_integer.pyx @@ -496,4 +496,3 @@ cdef class DiscreteGaussianDistributionIntegerSampler(SageObject): return f"Discrete Gaussian sampler over the Integers with sigma = {self.sigma:.6f} and c = {self.c:.6f}" -DGI = DiscreteGaussianDistributionIntegerSampler diff --git a/src/sage/stats/distributions/discrete_gaussian_lattice.py b/src/sage/stats/distributions/discrete_gaussian_lattice.py index c69b3ba1561..4dd3223856d 100644 --- a/src/sage/stats/distributions/discrete_gaussian_lattice.py +++ b/src/sage/stats/distributions/discrete_gaussian_lattice.py @@ -18,7 +18,7 @@ EXAMPLES:: - sage: from sage.stats.distributions.discrete_gaussian_lattice import DGL + sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler as DGL sage: D = DGL(ZZ^10, 3.0) sage: D(), D(), D() # random ((3, 0, -5, 0, -1, -3, 3, 3, -7, 2), (4, 0, 1, -2, -4, -4, 4, 0, 1, -4), (-3, 0, 4, 5, 0, 1, 3, 2, 0, -1)) @@ -62,7 +62,7 @@ from sage.rings.real_mpfr import RealField from sage.rings.integer_ring import ZZ from sage.rings.rational_field import QQ -from sage.stats.distributions.discrete_gaussian_integer import DGI +from sage.stats.distributions.discrete_gaussian_integer import DiscreteGaussianDistributionIntegerSampler as DGI from sage.structure.sage_object import SageObject from sage.misc.cachefunc import cached_method from sage.misc.functional import sqrt @@ -120,7 +120,7 @@ class DiscreteGaussianDistributionLatticeSampler(SageObject): EXAMPLES:: - sage: from sage.stats.distributions.discrete_gaussian_lattice import DGL + sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler as DGL sage: D = DGL(ZZ^10, 3.0); D Discrete Gaussian sampler with Gaussian parameter σ = 3.00000000000000, c=(0, 0, 0, 0, 0, 0, 0, 0, 0, 0) over lattice with basis @@ -138,7 +138,7 @@ class DiscreteGaussianDistributionLatticeSampler(SageObject): We plot a histogram:: - sage: from sage.stats.distributions.discrete_gaussian_lattice import DGL + sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler as DGL sage: import warnings sage: warnings.simplefilter('ignore', UserWarning) sage: D = DGL(identity_matrix(2), 3.0) @@ -167,7 +167,7 @@ def compute_precision(precision, sigma): EXAMPLES:: - sage: from sage.stats.distributions.discrete_gaussian_lattice import DGL + sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler as DGL sage: DGL.compute_precision(100, RR(3)) 100 sage: DGL.compute_precision(100, RealField(200)(3)) @@ -208,7 +208,7 @@ def _normalisation_factor_zz(self, tau=None, prec=None): EXAMPLES:: - sage: from sage.stats.distributions.discrete_gaussian_lattice import DGL + sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler as DGL sage: n = 3; sigma = 1.0 sage: D = DGL(ZZ^n, sigma) sage: f = D.f @@ -301,7 +301,7 @@ def f_or_hat(x): import itertools from sage.functions.log import log basis = self.B.LLL() - base = vector(ZZ, [v.round() for v in basis.solve_left(self.c)]) + base = vector(ZZ, [v.round() for v in basis.solve_left(self._c)]) BOUND = max(1, (self._RR(log(10**4, self.n)).ceil() - 1) // 2) if BOUND > 10: BOUND = 10 @@ -318,7 +318,7 @@ def f_or_hat(x): raise NotImplementedError("Lattice must be integral for now.") sigma = self._sigma - prec = DGL.compute_precision( + prec = DiscreteGaussianDistributionLatticeSampler.compute_precision( prec, sigma ) R = RealField(prec=prec) @@ -361,14 +361,14 @@ def _maximal_r(self): EXAMPLES:: - sage: from sage.stats.distributions.discrete_gaussian_lattice import DGL + sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler as DGL sage: n = 3 sage: Sigma = Matrix(ZZ, [[5, -2, 4], [-2, 10, -5], [4, -5, 5]]) sage: c = vector(ZZ, [7, 2, 5]) sage: D = DGL(ZZ^n, Sigma, c) sage: r = D._maximal_r(); r 0.58402... - sage: e_vals = (D.sigma - r^2 * D.Q).eigenvalues() + sage: e_vals = (D.sigma() - r^2 * D.Q).eigenvalues() sage: assert all(e_val >= -1e-12 for e_val in e_vals) """ if self.is_spherical: @@ -431,7 +431,7 @@ def __init__(self, B, sigma=1, c=0, r=None, precision=None, sigma_basis=False): EXAMPLES:: - sage: from sage.stats.distributions.discrete_gaussian_lattice import DGL + sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler as DGL sage: n = 2; sigma = 3.0 sage: D = DGL(ZZ^n, sigma) sage: f = D.f @@ -461,7 +461,7 @@ def __init__(self, B, sigma=1, c=0, r=None, precision=None, sigma_basis=False): sage: while abs(m*f(v)*1.0/nf/counter[v] - 1.0) >= 0.1: # needs sage.symbolic ....: add_samples(1000) - Spherical covariance are automatically handled. + Spherical covariance are automatically handled:: sage: DGL(ZZ^3, sigma=Matrix(3, 3, 2)) Discrete Gaussian sampler with Gaussian parameter σ = 2.00000000000000, c=(0, 0, 0) over lattice with basis @@ -471,7 +471,7 @@ def __init__(self, B, sigma=1, c=0, r=None, precision=None, sigma_basis=False): [0 0 1] The sampler supports non-spherical covariance in the form of a Gram - matrix. + matrix:: sage: n = 3 sage: Sigma = Matrix(ZZ, [[5, -2, 4], [-2, 10, -5], [4, -5, 5]]) @@ -482,7 +482,7 @@ def __init__(self, B, sigma=1, c=0, r=None, precision=None, sigma_basis=False): sage: while v not in counter: add_samples(1000) sage: while abs(m*f(v)*1.0/nf/counter[v] - 1.0) >= 0.1: add_samples(1000) - If the covariance provided is not positive definite, an error is thrown. + If the covariance provided is not positive definite, an error is thrown:: sage: Sigma = Matrix(ZZ, [[0, 1], [1, 0]]) sage: DGL(ZZ^2, Sigma) @@ -491,12 +491,12 @@ def __init__(self, B, sigma=1, c=0, r=None, precision=None, sigma_basis=False): RuntimeError: Sigma(=[0.000000000000000 1.00000000000000] [ 1.00000000000000 0.000000000000000]) is not positive definite - The sampler supports passing a basis for the covariance. + The sampler supports passing a basis for the covariance:: sage: n = 3 sage: S = Matrix(ZZ, [[2, 0, 0], [-1, 3, 0], [2, -1, 1]]) sage: D = DGL(ZZ^n, S, sigma_basis=True) - sage: D.sigma + sage: D.sigma() [ 4.00000000000000 -2.00000000000000 4.00000000000000] [-2.00000000000000 10.0000000000000 -5.00000000000000] [ 4.00000000000000 -5.00000000000000 6.00000000000000] @@ -504,7 +504,7 @@ def __init__(self, B, sigma=1, c=0, r=None, precision=None, sigma_basis=False): The non-spherical sampler supports offline computation to speed up sampling. This will be useful when changing the center `c` is supported. The difference is more significant for larger matrices. For 128x128 we - observe a 4x speedup (86s -> 20s). + observe a 4x speedup (86s -> 20s):: sage: D.offline_samples = [] sage: T = 2**12 @@ -512,36 +512,36 @@ def __init__(self, B, sigma=1, c=0, r=None, precision=None, sigma_basis=False): sage: D.add_offline_samples(T) # 150ms sage: L = [D() for _ in range(T)] # 370ms - We can also initialise with matrix-like objects: + We can also initialise with matrix-like objects:: - sage: from sage.stats.distributions.discrete_gaussian_lattice import DGL - sage: qf = QuadraticForm(matrix(3, [2, 1, 1, 1, 2, 1, 1, 1, 2])) + sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler as DGL + sage: qf = matrix(3, [2, 1, 1, 1, 2, 1, 1, 1, 2]) sage: D = DGL(qf, 3.0); D # needs sage.symbolic Discrete Gaussian sampler with Gaussian parameter σ = 3.00000000000000, c=(0, 0, 0) over lattice with basis [2 1 1] [1 2 1] [1 1 2] - sage: D().parent() is D.c.parent() # needs sage.symbolic + sage: D().parent() is D.c().parent() # needs sage.symbolic True """ - precision = DGL.compute_precision(precision, sigma) + precision = DiscreteGaussianDistributionLatticeSampler.compute_precision(precision, sigma) self._RR = RealField(precision) # Check if sigma is a (real) number or a scaled identity matrix self.is_spherical = True try: - self.sigma = self._RR(sigma) + self._sigma = self._RR(sigma) except TypeError: - self.sigma = matrix(self._RR, sigma) + self._sigma = matrix(self._RR, sigma) # Will it be "annoying" if a matrix Sigma has different behaviour # sometimes? There should be a parameter in the consrtuctor - if self.sigma == self.sigma[0, 0]: - self.sigma = self._RR(self.sigma[0, 0]) + if self._sigma == self._sigma[0, 0]: + self._sigma = self._RR(self._sigma[0, 0]) else: if sigma_basis: - self.sigma = self.sigma * self.sigma.T - if not self.sigma.is_positive_definite(): + self._sigma = self._sigma * self._sigma.T + if not self._sigma.is_positive_definite(): raise RuntimeError(f"Sigma(={self._sigma}) is not positive definite") self.is_spherical = False @@ -567,27 +567,38 @@ def __init__(self, B, sigma=1, c=0, r=None, precision=None, sigma_basis=False): self._c_mul_B_inv = None self.r = r - self.c = c + self.set_c(c) def _precompute_data(self): r""" - Precomputes basis data. + Precompute basis data. Do not call this method directly. + + EXAMPLES:: + + sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler as DGL + sage: D = DGL(ZZ^3, 3.0, c=(1,0,0)) + sage: D.set_c((2, 0, 0)) + sage: D + + .. note:: + + Do not call this method directly, it is called automatically from :func:`DiscreteGaussianDistributionLatticeSampler.__init__`. """ if self.is_spherical: # deal with trivial case first, it is common - if self._G == 1 and self.c == 0: + if self._G == 1 and self._c == 0: self._c_in_lattice = True - D = DGI(sigma=self.sigma) + D = DGI(sigma=self._sigma) self.D = tuple([D for _ in range(self.B.nrows())]) self.VS = FreeModule(ZZ, self.B.nrows()) else: - w = self.B.solve_left(self.c) + w = self.B.solve_left(self._c) if w in ZZ ** self.B.nrows(): self._c_in_lattice = True D = [] for i in range(self.B.nrows()): - sigma_ = self.sigma / self._G[i].norm() + sigma_ = self._sigma / self._G[i].norm() D.append(DGI(sigma=sigma_)) self.D = tuple(D) self.VS = FreeModule(ZZ, self.B.nrows()) @@ -599,8 +610,8 @@ def _precompute_data(self): # Offline samples of B⁻¹D₁ self.offline_samples = [] self.B_inv = self.B.inverse() - self.sigma_inv = self.sigma.inverse() - self._c_mul_B_inv = self.c * self.B_inv + self.sigma_inv = self._sigma.inverse() + self._c_mul_B_inv = self._c * self.B_inv if self.r is None: # Compute the maximal r such that (Sigma - r^2 * Q) > 0 @@ -623,17 +634,17 @@ def __call__(self): EXAMPLES:: - sage: from sage.stats.distributions.discrete_gaussian_lattice import DGL + sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler as DGL sage: D = DGL(ZZ^3, 3.0, c=(1,0,0)) sage: L = [D() for _ in range(2^12)] sage: mean_L = sum(L) / len(L) - sage: norm(mean_L.n() - D.c) < 0.25 + sage: norm(mean_L.n() - D.c()) < 0.25 True sage: D = DGL(ZZ^3, 3.0, c=(1/2,0,0)) sage: L = [D() for _ in range(2^12)] # long time sage: mean_L = sum(L) / len(L) # long time - sage: norm(mean_L.n() - D.c) < 0.25 # long time + sage: norm(mean_L.n() - D.c()) < 0.25 # long time True """ if not self.is_spherical: @@ -645,27 +656,22 @@ def __call__(self): v.set_immutable() return v - @property - def f(self): + def f(self, x): r""" - Returns closure that computes the Gaussian `\rho_{\Lambda, c, \Sigma}`. + Compute the Gaussian `\rho_{\Lambda, c, \Sigma}`. """ - def g(x): + try: + x = vector(ZZ, self.n, x) + except TypeError: try: - x = vector(ZZ, self.n, x) + x = vector(QQ, self.n, x) except TypeError: - try: - x = vector(QQ, self.n, x) - except TypeError: - x = vector(self._RR, self.n, x) - x -= self.c - if self.is_spherical: - return exp(-x.norm() ** 2 / (2 * self.sigma**2)) - return exp(-x * self.sigma_inv * x / 2) - - return g + x = vector(self._RR, self.n, x) + x -= self._c + if self.is_spherical: + return exp(-x.norm() ** 2 / (2 * self._sigma**2)) + return exp(-x * self.sigma_inv * x / 2) - @property def sigma(self): r""" Gaussian parameter `σ`. @@ -675,33 +681,13 @@ def sigma(self): EXAMPLES:: - sage: from sage.stats.distributions.discrete_gaussian_lattice import DGL + sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler as DGL sage: D = DGL(ZZ^3, 3.0, c=(1,0,0)) - sage: D.sigma + sage: D.sigma() 3.00000000000000 """ return self._sigma - @sigma.setter - def sigma(self, sigma): - r""" - Modifies center `σ`. - - EXAMPLES:: - - sage: from sage.stats.distributions.discrete_gaussian_lattice import DGL - sage: D = DGL(ZZ^3, 3.0, c=(1,0,0)) - sage: D.c = (2, 0, 0) - sage: D - Discrete Gaussian sampler with Gaussian parameter σ = 3.00000000000000, c=(2, 0, 0) over lattice with basis - - [1 0 0] - [0 1 0] - [0 0 1] - """ - self._sigma = sigma - - @property def c(self): r""" Center `c`. @@ -710,7 +696,7 @@ def c(self): EXAMPLES:: - sage: from sage.stats.distributions.discrete_gaussian_lattice import DGL + sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler as DGL sage: D = DGL(ZZ^3, 3.0, c=(1,0,0)); D Discrete Gaussian sampler with Gaussian parameter σ = 3.00000000000000, c=(1, 0, 0) over lattice with basis @@ -718,21 +704,20 @@ def c(self): [0 1 0] [0 0 1] - sage: D.c + sage: D.c() (1, 0, 0) """ return self._c - @c.setter - def c(self, c): + def set_c(self, c): r""" - Modifies center `c` + Modifies center `c`. EXAMPLES:: - sage: from sage.stats.distributions.discrete_gaussian_lattice import DGL + sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler as DGL sage: D = DGL(ZZ^3, 3.0, c=(1,0,0)) - sage: D.c = (2, 0, 0) + sage: D.set_c((2, 0, 0)) sage: D Discrete Gaussian sampler with Gaussian parameter σ = 3.00000000000000, c=(2, 0, 0) over lattice with basis @@ -765,7 +750,7 @@ def __repr__(self): r""" EXAMPLES:: - sage: from sage.stats.distributions.discrete_gaussian_lattice import DGL + sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler as DGL sage: D = DGL(ZZ^3, 3.0, c=(1,0,0)); D Discrete Gaussian sampler with Gaussian parameter σ = 3.00000000000000, c=(1, 0, 0) over lattice with basis @@ -788,7 +773,7 @@ def __repr__(self): sigma_str = f"σ = {self._sigma}" else: sigma_str = f"Σ =\n{self._sigma}" - return f"Discrete Gaussian sampler with Gaussian parameter {sigma_str}, c={self.c} over lattice with basis\n\n{self.B}" + return f"Discrete Gaussian sampler with Gaussian parameter {sigma_str}, c={self._c} over lattice with basis\n\n{self.B}" def _call_in_lattice(self): r""" @@ -796,19 +781,19 @@ def _call_in_lattice(self): EXAMPLES:: - sage: from sage.stats.distributions.discrete_gaussian_lattice import DGL + sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler as DGL sage: D = DGL(ZZ^3, 3.0, c=(1,0,0)) sage: L = [D._call_in_lattice() for _ in range(2^12)] sage: mean_L = sum(L) / len(L) - sage: norm(mean_L.n() - D.c) < 0.25 + sage: norm(mean_L.n() - D.c()) < 0.25 True .. note:: - Do not call this method directly, call :func:`DGL.__call__` instead. + Do not call this method directly, call :func:`DiscreteGaussianDistributionLatticeSampler.__call__` instead. """ w = self.VS([d() for d in self.D], check=False) - return w * self.B + self.c + return w * self.B + self._c def _call(self): """ @@ -816,19 +801,19 @@ def _call(self): EXAMPLES:: - sage: from sage.stats.distributions.discrete_gaussian_lattice import DGL + sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler as DGL sage: D = DGL(ZZ^3, 3.0, c=(1/2,0,0)) sage: L = [D._call() for _ in range(2^12)] sage: mean_L = sum(L) / len(L) - sage: norm(mean_L.n() - D.c) < 0.25 + sage: norm(mean_L.n() - D.c()) < 0.25 True .. note:: - Do not call this method directly, call :func:`DGL.__call__` instead. + Do not call this method directly, call :func:`DiscreteGaussianDistributionLatticeSampler.__call__` instead. """ v = 0 - c, sigma, B = self.c, self._sigma, self.B + c, sigma, B = self._c, self._sigma, self.B m = self.B.nrows() @@ -848,7 +833,7 @@ def add_offline_samples(self, cnt=1): EXAMPLES:: - sage: from sage.stats.distributions.discrete_gaussian_lattice import DGL + sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler as DGL sage: Sigma = Matrix([[5, -2, 4], [-2, 10, -5], [4, -5, 5]]) sage: D = DGL(ZZ^3, Sigma) sage: assert not D.is_spherical @@ -869,22 +854,19 @@ def _call_non_spherical(self): EXAMPLES:: - sage: from sage.stats.distributions.discrete_gaussian_lattice import DGL + sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler as DGL sage: Sigma = Matrix([[5, -2, 4], [-2, 10, -5], [4, -5, 5]]) sage: D = DGL(ZZ^3, Sigma, c=(1/2,0,0)) sage: L = [D._call_non_spherical() for _ in range(2^12)] sage: mean_L = sum(L) / len(L) - sage: norm(mean_L.n() - D.c) < 0.25 + sage: norm(mean_L.n() - D.c()) < 0.25 True .. note:: - Do not call this method directly, call :func:`DGL.__call__` instead. + Do not call this method directly, call :func:`DiscreteGaussianDistributionLatticeSampler.__call__` instead. """ if len(self.offline_samples) == 0: self.add_offline_samples() vec = self._c_mul_B_inv - self.offline_samples.pop() return self._randomise(vec) * self.B - - -DGL = DiscreteGaussianDistributionLatticeSampler diff --git a/src/sage/stats/distributions/discrete_gaussian_polynomial.py b/src/sage/stats/distributions/discrete_gaussian_polynomial.py index b2d18da06e1..63c8f5b800a 100644 --- a/src/sage/stats/distributions/discrete_gaussian_polynomial.py +++ b/src/sage/stats/distributions/discrete_gaussian_polynomial.py @@ -140,6 +140,3 @@ def _repr_(self): """ # beware of unicode character in ascii string ! return "Discrete Gaussian sampler for polynomials of degree < %d with σ=%f in each component" % (self.n, self.D.sigma) - - -DGP = DiscreteGaussianDistributionPolynomialSampler From d318a49f532cd5a43f22bb3105f61e5c6ada3862 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 17 Jan 2024 22:38:14 -0800 Subject: [PATCH 034/278] src/sage_docbuild/conf.py: Show preparsed doctests (and Jupyter cells) using inline tabs --- src/sage_docbuild/conf.py | 101 ++++++++++++++++++++++++++++---------- 1 file changed, 76 insertions(+), 25 deletions(-) diff --git a/src/sage_docbuild/conf.py b/src/sage_docbuild/conf.py index 6b84e772dd1..8d7d9e470da 100644 --- a/src/sage_docbuild/conf.py +++ b/src/sage_docbuild/conf.py @@ -230,7 +230,7 @@ def sphinx_plot(graphics, **kwds): # console lexers. 'ipycon' is the IPython console, which is what we want # for most code blocks: anything with "sage:" prompts. For other IPython, # like blocks which might appear in a notebook cell, use 'ipython'. -highlighting.lexers['ipycon'] = IPythonConsoleLexer(in1_regex=r'sage: ', in2_regex=r'[.][.][.][.]: ') +highlighting.lexers['ipycon'] = IPythonConsoleLexer(in1_regex=r'(sage:|>>>)', in2_regex=r'([.][.][.][.]:|[.][.][.])') highlighting.lexers['ipython'] = IPyLexer() highlight_language = 'ipycon' @@ -790,6 +790,9 @@ class will be properly documented inside its surrounding class. from jupyter_sphinx.ast import JupyterCellNode, CellInputNode +from docutils.nodes import container as Container, label as Label, literal_block as LiteralBlock, Text +from sphinx_inline_tabs._impl import TabContainer +from sage.repl.preparse import preparse class SagecodeTransform(SphinxTransform): """ @@ -828,29 +831,78 @@ def apply(self): if self.app.builder.tags.has('html') or self.app.builder.tags.has('inventory'): for node in self.document.traverse(nodes.literal_block): if node.get('language') is None and node.astext().startswith('sage:'): - source = node.rawsource - lines = [] - for line in source.splitlines(): + parent = node.parent + index = parent.index(node) + parent.remove(node) + # Tab for Sage code + container = TabContainer("", type="tab", new_set=False) + textnodes = [Text('Sage')] + label = Label("", "", *textnodes) + container += label + content = Container("", is_div=True, classes=["tab-content"]) + content += node + container += content + parent.insert(index, container) + # Tab for preparsed version + container = TabContainer("", type="tab", new_set=False) + textnodes = [Text('Python')] + label = Label("", "", *textnodes) + container += label + content = Container("", is_div=True, classes=["tab-content"]) + example_lines = [] + preparsed_lines = ['>>> from sage.all import *'] + for line in node.rawsource.splitlines() + ['']: # one extra to process last example newline = line.lstrip() - if newline.startswith('sage: ') or newline.startswith('....: '): - lines.append(newline[6:]) - cell_node = JupyterCellNode( - execute=False, - hide_code=True, - hide_output=True, - emphasize_lines=[], - raises=False, - stderr=True, - code_below=False, - classes=["jupyter_cell"]) - cell_input = CellInputNode(classes=['cell_input','live-doc']) - cell_input += nodes.literal_block( - text='\n'.join(lines), - linenos=False, - linenostart=1) - cell_node += cell_input - - node.parent.insert(node.parent.index(node) + 1, cell_node) + if newline.startswith('....: '): + example_lines.append(newline[6:]) + else: + if example_lines: + preparsed_example = preparse('\n'.join(example_lines)) + prompt = '>>> ' + for preparsed_line in preparsed_example.splitlines(): + preparsed_lines.append(prompt + preparsed_line) + prompt = '... ' + example_lines = [] + if newline.startswith('sage: '): + example_lines.append(newline[6:]) + else: + preparsed_lines.append(line) + preparsed = '\n'.join(preparsed_lines) + preparsed_node = LiteralBlock(preparsed, preparsed, language='ipycon') + content += preparsed_node + container += content + parent.insert(index + 1, container) + if os.environ.get('SAGE_LIVE_DOC', 'no') == 'yes': + # Tab for Jupyter-sphinx cell + source = node.rawsource + lines = [] + for line in source.splitlines(): + newline = line.lstrip() + if newline.startswith('sage: ') or newline.startswith('....: '): + lines.append(newline[6:]) + cell_node = JupyterCellNode( + execute=False, + hide_code=False, + hide_output=True, + emphasize_lines=[], + raises=False, + stderr=True, + code_below=False, + classes=["jupyter_cell"]) + cell_input = CellInputNode(classes=['cell_input','live-doc']) + cell_input += nodes.literal_block( + text='\n'.join(lines), + linenos=False, + linenostart=1) + cell_node += cell_input + container = TabContainer("", type="tab", new_set=False) + textnodes = [Text('Sage (live)')] + label = Label("", "", *textnodes) + container += label + content = Container("", is_div=True, classes=["tab-content"]) + content += cell_node + container += content + parent.insert(index + 1, container) # This replaces the setup() in sage.misc.sagedoc_conf def setup(app): @@ -863,8 +915,7 @@ def setup(app): app.connect('autodoc-process-docstring', skip_TESTS_block) app.connect('autodoc-skip-member', skip_member) app.add_transform(SagemathTransform) - if os.environ.get('SAGE_LIVE_DOC', 'no') == 'yes': - app.add_transform(SagecodeTransform) + app.add_transform(SagecodeTransform) # When building the standard docs, app.srcdir is set to SAGE_DOC_SRC + # 'LANGUAGE/DOCNAME'. From 10acc0f34c328d89bd255ab97018bb9c19c400e8 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 17 Jan 2024 23:22:45 -0800 Subject: [PATCH 035/278] src/sage_docbuild/conf.py: Include plain Python prompts in copybutton config --- 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 8d7d9e470da..b998e02b6b9 100644 --- a/src/sage_docbuild/conf.py +++ b/src/sage_docbuild/conf.py @@ -305,7 +305,7 @@ def set_intersphinx_mappings(app, config): multidocs_is_master = True # https://sphinx-copybutton.readthedocs.io/en/latest/use.html -copybutton_prompt_text = r"sage: |[.][.][.][.]: |\$ " +copybutton_prompt_text = r"sage: |[.][.][.][.]: |>>> |[.][.][.] |\$ " copybutton_prompt_is_regexp = True copybutton_exclude = '.linenos, .c1' # exclude single comments (in particular, # optional!) copybutton_only_copy_prompt_lines = True From 8974bcb94e5686547f7ca2160004cc70f65ef390 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 18 Jan 2024 09:52:52 -0800 Subject: [PATCH 036/278] src/sage_docbuild/conf.py: Conditionalize on environment variable SAGE_PREPARSED_DOC (default yes) --- src/sage_docbuild/conf.py | 74 +++++++++++++++++++++------------------ 1 file changed, 39 insertions(+), 35 deletions(-) diff --git a/src/sage_docbuild/conf.py b/src/sage_docbuild/conf.py index b998e02b6b9..a4bc5955e90 100644 --- a/src/sage_docbuild/conf.py +++ b/src/sage_docbuild/conf.py @@ -39,6 +39,9 @@ # General configuration # --------------------- +SAGE_LIVE_DOC = os.environ.get('SAGE_LIVE_DOC', 'no') +SAGE_PREPARSED_DOC = os.environ.get('SAGE_PREPARSED_DOC', 'yes') + # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ @@ -57,7 +60,7 @@ jupyter_execute_default_kernel = 'sagemath' -if os.environ.get('SAGE_LIVE_DOC', 'no') == 'yes': +if SAGE_LIVE_DOC == 'yes': SAGE_JUPYTER_SERVER = os.environ.get('SAGE_JUPYTER_SERVER', 'binder') if SAGE_JUPYTER_SERVER.startswith('binder'): # format: "binder" or @@ -789,11 +792,6 @@ class will be properly documented inside its surrounding class. return skip -from jupyter_sphinx.ast import JupyterCellNode, CellInputNode -from docutils.nodes import container as Container, label as Label, literal_block as LiteralBlock, Text -from sphinx_inline_tabs._impl import TabContainer -from sage.repl.preparse import preparse - class SagecodeTransform(SphinxTransform): """ Transform a code block to a live code block enabled by jupyter-sphinx. @@ -831,6 +829,8 @@ def apply(self): if self.app.builder.tags.has('html') or self.app.builder.tags.has('inventory'): for node in self.document.traverse(nodes.literal_block): if node.get('language') is None and node.astext().startswith('sage:'): + from docutils.nodes import container as Container, label as Label, literal_block as LiteralBlock, Text + from sphinx_inline_tabs._impl import TabContainer parent = node.parent index = parent.index(node) parent.remove(node) @@ -843,37 +843,40 @@ def apply(self): content += node container += content parent.insert(index, container) - # Tab for preparsed version - container = TabContainer("", type="tab", new_set=False) - textnodes = [Text('Python')] - label = Label("", "", *textnodes) - container += label - content = Container("", is_div=True, classes=["tab-content"]) - example_lines = [] - preparsed_lines = ['>>> from sage.all import *'] - for line in node.rawsource.splitlines() + ['']: # one extra to process last example - newline = line.lstrip() - if newline.startswith('....: '): - example_lines.append(newline[6:]) - else: - if example_lines: - preparsed_example = preparse('\n'.join(example_lines)) - prompt = '>>> ' - for preparsed_line in preparsed_example.splitlines(): - preparsed_lines.append(prompt + preparsed_line) - prompt = '... ' - example_lines = [] - if newline.startswith('sage: '): + if SAGE_PREPARSED_DOC == 'yes': + # Tab for preparsed version + from sage.repl.preparse import preparse + container = TabContainer("", type="tab", new_set=False) + textnodes = [Text('Python')] + label = Label("", "", *textnodes) + container += label + content = Container("", is_div=True, classes=["tab-content"]) + example_lines = [] + preparsed_lines = ['>>> from sage.all import *'] + for line in node.rawsource.splitlines() + ['']: # one extra to process last example + newline = line.lstrip() + if newline.startswith('....: '): example_lines.append(newline[6:]) else: - preparsed_lines.append(line) - preparsed = '\n'.join(preparsed_lines) - preparsed_node = LiteralBlock(preparsed, preparsed, language='ipycon') - content += preparsed_node - container += content - parent.insert(index + 1, container) - if os.environ.get('SAGE_LIVE_DOC', 'no') == 'yes': + if example_lines: + preparsed_example = preparse('\n'.join(example_lines)) + prompt = '>>> ' + for preparsed_line in preparsed_example.splitlines(): + preparsed_lines.append(prompt + preparsed_line) + prompt = '... ' + example_lines = [] + if newline.startswith('sage: '): + example_lines.append(newline[6:]) + else: + preparsed_lines.append(line) + preparsed = '\n'.join(preparsed_lines) + preparsed_node = LiteralBlock(preparsed, preparsed, language='ipycon') + content += preparsed_node + container += content + parent.insert(index + 1, container) + if SAGE_LIVE_DOC == 'yes': # Tab for Jupyter-sphinx cell + from jupyter_sphinx.ast import JupyterCellNode, CellInputNode source = node.rawsource lines = [] for line in source.splitlines(): @@ -915,7 +918,8 @@ def setup(app): app.connect('autodoc-process-docstring', skip_TESTS_block) app.connect('autodoc-skip-member', skip_member) app.add_transform(SagemathTransform) - app.add_transform(SagecodeTransform) + if SAGE_LIVE_DOC == 'yes' or SAGE_PREPARSED_DOC == 'yes': + app.add_transform(SagecodeTransform) # When building the standard docs, app.srcdir is set to SAGE_DOC_SRC + # 'LANGUAGE/DOCNAME'. From 6412ce4a5e3eada85555d76d231a886f6e8c83ec Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 18 Jan 2024 10:20:41 -0800 Subject: [PATCH 037/278] sage --docbuild: Add option --no-preparsed-examples --- src/sage_docbuild/__main__.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/sage_docbuild/__main__.py b/src/sage_docbuild/__main__.py index 0d15808a69c..15eaf6c5e19 100644 --- a/src/sage_docbuild/__main__.py +++ b/src/sage_docbuild/__main__.py @@ -288,6 +288,9 @@ def setup_parser(): standard.add_argument("--no-plot", dest="no_plot", action="store_true", help="do not include graphics auto-generated using the '.. plot' markup") + standard.add_argument("--no-preparsed-examples", dest="no_preparsed_examples", + action="store_true", + help="do not show preparsed versions of EXAMPLES blocks") standard.add_argument("--include-tests-blocks", dest="skip_tests", default=True, action="store_false", help="include TESTS blocks in the reference manual") @@ -477,6 +480,8 @@ def excepthook(*exc_info): build_options.ALLSPHINXOPTS += "-n " if args.no_plot: os.environ['SAGE_SKIP_PLOT_DIRECTIVE'] = 'yes' + if args.no_preparsed_examples: + os.environ['SAGE_PREPARSED_DOC'] = 'no' if args.live_doc: os.environ['SAGE_LIVE_DOC'] = 'yes' if args.skip_tests: From 161a7e761b740a50f1c47cdca8098594b12706ee Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 18 Jan 2024 10:28:13 -0800 Subject: [PATCH 038/278] src/doc/en/installation/source.rst (SAGE_DOCBUILD_OPTS): Mention --no-preparsed-examples --- src/doc/en/installation/source.rst | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/doc/en/installation/source.rst b/src/doc/en/installation/source.rst index 26454e4d6d3..fbe0d15ac2f 100644 --- a/src/doc/en/installation/source.rst +++ b/src/doc/en/installation/source.rst @@ -940,11 +940,19 @@ Environment variables controlling the documentation build The value of this variable is passed as an argument to ``sage --docbuild all html`` or ``sage --docbuild all pdf`` when - you run ``make``, ``make doc``, or ``make doc-pdf``. For example, you can - add ``--no-plot`` to this variable to avoid building the graphics coming from - the ``.. PLOT`` directive within the documentation, or you can add - ``--include-tests-blocks`` to include all "TESTS" blocks in the reference - manual. Run ``sage --docbuild help`` to see the full list of options. + you run ``make``, ``make doc``, or ``make doc-pdf``. For example: + + - add ``--no-plot`` to this variable to avoid building the graphics coming from + the ``.. PLOT`` directive within the documentation, + + - add ``--no-preparsed-examples`` to only show the original Sage code of + "EXAMPLES" blocks, suppressing the tab with the preparsed, plain Python + version, or + + - add ``--include-tests-blocks`` to include all "TESTS" blocks in the reference + manual. + + Run ``sage --docbuild help`` to see the full list of options. .. envvar:: SAGE_SPKG_INSTALL_DOCS From 0034cc9b5d3ecb816b731560d96ba9580e3da91d Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 18 Jan 2024 12:17:14 -0800 Subject: [PATCH 039/278] .github/workflows/[doc-]build[-pdf].yml: Do not fail when there are no blockers --- .github/workflows/build.yml | 4 ++-- .github/workflows/doc-build-pdf.yml | 4 ++-- .github/workflows/doc-build.yml | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 56f231d9578..9129874ab55 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -35,13 +35,13 @@ jobs: uses: actions/checkout@v4 - name: Merge CI fixes from sagemath/sage run: | - .ci/merge-fixes.sh + mkdir -p upstream + .ci/merge-fixes.sh 2>&1 | tee upstream/ci_fixes.log env: GH_TOKEN: ${{ github.token }} SAGE_CI_FIXES_FROM_REPOSITORIES: ${{ vars.SAGE_CI_FIXES_FROM_REPOSITORIES }} - name: Store CI fixes in upstream artifact run: | - mkdir -p upstream if git format-patch --stdout test_base > ci_fixes.patch; then cp ci_fixes.patch upstream/ fi diff --git a/.github/workflows/doc-build-pdf.yml b/.github/workflows/doc-build-pdf.yml index 7ae675d9e64..6e6e8776062 100644 --- a/.github/workflows/doc-build-pdf.yml +++ b/.github/workflows/doc-build-pdf.yml @@ -29,13 +29,13 @@ jobs: uses: actions/checkout@v4 - name: Merge CI fixes from sagemath/sage run: | - .ci/merge-fixes.sh + mkdir -p upstream + .ci/merge-fixes.sh 2>&1 | tee upstream/ci_fixes.log env: GH_TOKEN: ${{ github.token }} SAGE_CI_FIXES_FROM_REPOSITORIES: ${{ vars.SAGE_CI_FIXES_FROM_REPOSITORIES }} - name: Store CI fixes in upstream artifact run: | - mkdir -p upstream if git format-patch --stdout test_base > ci_fixes.patch; then cp ci_fixes.patch upstream/ fi diff --git a/.github/workflows/doc-build.yml b/.github/workflows/doc-build.yml index 9d82909ef5f..21e19e3859a 100644 --- a/.github/workflows/doc-build.yml +++ b/.github/workflows/doc-build.yml @@ -24,13 +24,13 @@ jobs: uses: actions/checkout@v4 - name: Merge CI fixes from sagemath/sage run: | - .ci/merge-fixes.sh + mkdir -p upstream + .ci/merge-fixes.sh 2>&1 | tee upstream/ci_fixes.log env: GH_TOKEN: ${{ github.token }} SAGE_CI_FIXES_FROM_REPOSITORIES: ${{ vars.SAGE_CI_FIXES_FROM_REPOSITORIES }} - name: Store CI fixes in upstream artifact run: | - mkdir -p upstream if git format-patch --stdout test_base > ci_fixes.patch; then cp ci_fixes.patch upstream/ fi From d8b0a129ffec9f07f47bc84f3375685f9daa26f7 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 18 Jan 2024 12:18:29 -0800 Subject: [PATCH 040/278] .ci/merge-fixes.sh: Cosmetic change --- .ci/merge-fixes.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.ci/merge-fixes.sh b/.ci/merge-fixes.sh index 73b4c665caf..13350018221 100755 --- a/.ci/merge-fixes.sh +++ b/.ci/merge-fixes.sh @@ -42,6 +42,7 @@ for REPO in ${SAGE_CI_FIXES_FROM_REPOSITORIES:-sagemath/sage}; do # Considered alternative: Use https://github.com/$REPO/pull/$a.diff, # which squashes everything into one diff without commit metadata. PULL_URL="https://github.com/$REPO/pull/$a" + PULL_SHORT="$REPO#$a" PULL_FILE="$REPO_FILE-$a" PATH=build/bin:$PATH build/bin/sage-download-file --quiet "$PULL_URL.patch" $PULL_FILE.patch date -u +"%Y-%m-%dT%H:%M:%SZ" > $PULL_FILE.date # Record the date, for future reference @@ -67,7 +68,7 @@ for REPO in ${SAGE_CI_FIXES_FROM_REPOSITORIES:-sagemath/sage}; do git am --signoff --show-current-patch=diff echo "--------------------------------------------------------------------8<-----------------------------" echo "::endgroup::" - echo "Failure applying $PULL_URL as a patch, resetting" + echo "Failure applying $PULL_SHORT as a patch, resetting" git am --signoff --abort fi done From 84e7c89909c60659b226f6b50d2591fbc53d07b0 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 18 Jan 2024 20:20:50 -0800 Subject: [PATCH 041/278] Activate Thebe when "Sage (live)" tab is clicked --- src/doc/common/static/jupyter-sphinx-furo.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/doc/common/static/jupyter-sphinx-furo.js b/src/doc/common/static/jupyter-sphinx-furo.js index 5194ff470fc..01839aa4e99 100644 --- a/src/doc/common/static/jupyter-sphinx-furo.js +++ b/src/doc/common/static/jupyter-sphinx-furo.js @@ -112,3 +112,16 @@ thebelab.on("status", function (evt, data) { kernel.requestExecute({code: "%display latex"}); } }); + +// Activate Thebe when "Sage (live)" tab is clicked +document.querySelectorAll('input[class="tab-input"]').forEach((elem) => { + elem.addEventListener("click", function(event) { + if (elem.nextElementSibling) { + if (elem.nextElementSibling.nextElementSibling) { + if (elem.nextElementSibling.nextElementSibling.querySelector('div[class="thebelab-code"]')) { + initThebelab(); + } + } + } + }); +}); From dfaf9bb27f1a0598fd117696a5ae4d6ed736f837 Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Sun, 21 Jan 2024 00:22:39 +0100 Subject: [PATCH 042/278] Fix random polynomial bias --- src/sage/rings/polynomial/polynomial_ring.py | 76 +++++++++++++++----- 1 file changed, 60 insertions(+), 16 deletions(-) diff --git a/src/sage/rings/polynomial/polynomial_ring.py b/src/sage/rings/polynomial/polynomial_ring.py index 858df388caf..752a5a3e9f5 100644 --- a/src/sage/rings/polynomial/polynomial_ring.py +++ b/src/sage/rings/polynomial/polynomial_ring.py @@ -1360,7 +1360,7 @@ def ngens(self): """ return 1 - def random_element(self, degree=(-1,2), *args, **kwds): + def random_element(self, degree=(-1,2), monic=False, *args, **kwds): r""" Return a random polynomial of given degree or with given degree bounds. @@ -1386,8 +1386,8 @@ def random_element(self, degree=(-1,2), *args, **kwds): sage: R.random_element(6).degree() 6 - If a tuple of two integers is given for the ``degree`` argument, a degree - is first uniformly chosen, then a polynomial of that degree is given:: + If a tuple of two integers is given for the ``degree`` argument, a polynomial is chosen + uniformly among all polynomials with degree between them:: sage: R.random_element(degree=(0, 8)).degree() in range(0, 9) True @@ -1439,6 +1439,9 @@ def random_element(self, degree=(-1,2), *args, **kwds): if degree[0] <= -2: raise ValueError("degree should be an integer greater or equal than -1") + if monic and degree[0] == -1: + raise ValueError("degree cannot include -1 when monic is set") + # If the coefficient range only contains 0, then # * if the degree range includes -1, return the zero polynomial, # * otherwise raise a value error @@ -1448,24 +1451,65 @@ def random_element(self, degree=(-1,2), *args, **kwds): else: raise ValueError("No polynomial of degree >= 0 has all coefficients zero") - # Pick a random degree - d = randint(degree[0], degree[1]) + if degree == (-1, -1): + return 0 + + if degree[0] == -1: + allow_zero = True + degree = (0, degree[1]) + else: + allow_zero = False + + while True: + # Pick random coefficients + coefs = [] + nonzero = False + + leading = degree[1] - degree[0] + 1 + if monic: + leading -= 1 + coefs.append(1) + + for _ in range(leading): + c = R.random_element(*args, **kwds) + coefs.append(c) + if c != 0: + nonzero = True + + if not (allow_zero or nonzero): + continue + + for _ in range(degree[0]): + c = R.random_element(*args, **kwds) + coefs.append(c) + + coefs.reverse() + return self(coefs) + + def random_monic_element(self, degree=(0, 2), *args, **kwargs): + r""" + Return a random monic polynomial of given degree or with given degree bounds. - # If degree is -1, return the 0 polynomial - if d == -1: - return self.zero() + Calls :meth:`random_element` with ``monic=True``. - # If degree is 0, return a random constant term - if d == 0: - return self(R._random_nonzero_element(*args, **kwds)) + INPUT: - # Pick random coefficients - p = self([R.random_element(*args, **kwds) for _ in range(d)]) + - ``degree`` - optional integer for fixing the degree + or a tuple of minimum and maximum degrees. By default set to + ``(0,2)``. Must not include -1. - # Add non-zero leading coefficient - p += R._random_nonzero_element(*args, **kwds) * self.gen() ** d + - ``*args, **kwds`` - Passed on to the ``random_element`` method for + the base ring - return p + EXAMPLES:: + + sage: R. = GF(13)[] + sage: R.random_element() # random + x^2 + 11*x + 5 + sage: all(R.random_monic_element().is_monic() for _ in range(100)) + True + """ + return self.random_element(degree=degree, monic=True, *args, **kwargs) def _monics_degree(self, of_degree): """ From d67cf847d3d46f8f4bd1e56c810bd42bc9cc6e69 Mon Sep 17 00:00:00 2001 From: grhkm21 <83517584+grhkm21@users.noreply.github.com> Date: Sun, 21 Jan 2024 00:58:57 +0100 Subject: [PATCH 043/278] Allow `degree=-1` together with `monic=True` It is more useful in practice --- src/sage/rings/polynomial/polynomial_ring.py | 26 +++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/src/sage/rings/polynomial/polynomial_ring.py b/src/sage/rings/polynomial/polynomial_ring.py index 752a5a3e9f5..2cbbe28d071 100644 --- a/src/sage/rings/polynomial/polynomial_ring.py +++ b/src/sage/rings/polynomial/polynomial_ring.py @@ -1370,6 +1370,10 @@ def random_element(self, degree=(-1,2), monic=False, *args, **kwds): or a tuple of minimum and maximum degrees. By default set to ``(-1,2)``. + - ``monic`` - optional boolean to indicate whether the sample + polynomial should be monic or not. If degree includes -1, it is still + possible for the algorithm to return ``0``, which is not monic. + - ``*args, **kwds`` - Passed on to the ``random_element`` method for the base ring @@ -1401,6 +1405,16 @@ def random_element(self, degree=(-1,2), monic=False, *args, **kwds): sage: while R.random_element(degree=(-1,2), x=-1, y=1) != R.zero(): ....: pass + It is possible to sample a monic polynomial:: + + sage: R.random_element(5, monic=True).is_monic() + True + + Note that if the degree range includes `-1`, then the resulting polynomial might not be monic:: + + sage: all(R.random_element(degree=(-1, 1), monic=True).is_monic() for _ in range(10^3)) + False + TESTS:: sage: R.random_element(degree=[5]) @@ -1439,8 +1453,9 @@ def random_element(self, degree=(-1,2), monic=False, *args, **kwds): if degree[0] <= -2: raise ValueError("degree should be an integer greater or equal than -1") - if monic and degree[0] == -1: - raise ValueError("degree cannot include -1 when monic is set") + # Actually, it's probably more useful to let the user check this. + # if monic and degree[0] == -1: + # raise ValueError("degree cannot include -1 when monic is set") # If the coefficient range only contains 0, then # * if the degree range includes -1, return the zero polynomial, @@ -1496,18 +1511,21 @@ def random_monic_element(self, degree=(0, 2), *args, **kwargs): - ``degree`` - optional integer for fixing the degree or a tuple of minimum and maximum degrees. By default set to - ``(0,2)``. Must not include -1. + ``(0,2)``. If the bounds include ``-1``, it is still possible for the + algorithm to return ``0``, which is not monic. - ``*args, **kwds`` - Passed on to the ``random_element`` method for the base ring EXAMPLES:: - sage: R. = GF(13)[] + sage: R. = GF(3)[] sage: R.random_element() # random x^2 + 11*x + 5 sage: all(R.random_monic_element().is_monic() for _ in range(100)) True + sage: all(R.random_monic_element(degree=(-1,1)).is_monic() for _ in range(100)) + True """ return self.random_element(degree=degree, monic=True, *args, **kwargs) From e4817865bcaeeeddaa04caec236d5ab0e398d24b Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Sun, 21 Jan 2024 01:05:10 +0100 Subject: [PATCH 044/278] Fix buggy sampling --- src/sage/rings/polynomial/polynomial_ring.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/sage/rings/polynomial/polynomial_ring.py b/src/sage/rings/polynomial/polynomial_ring.py index 2cbbe28d071..98756d26af3 100644 --- a/src/sage/rings/polynomial/polynomial_ring.py +++ b/src/sage/rings/polynomial/polynomial_ring.py @@ -1480,15 +1480,11 @@ def random_element(self, degree=(-1,2), monic=False, *args, **kwds): coefs = [] nonzero = False - leading = degree[1] - degree[0] + 1 - if monic: - leading -= 1 - coefs.append(1) - - for _ in range(leading): + for _ in range(degree[1] - degree[0] + 1): c = R.random_element(*args, **kwds) coefs.append(c) - if c != 0: + if c != 0 and not nonzero: + coefs[-1] = 1 nonzero = True if not (allow_zero or nonzero): From d9d20187c75dc9f75776c24ae33c6621d74eee70 Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Mon, 22 Jan 2024 01:46:47 +0000 Subject: [PATCH 045/278] =?UTF-8?q?Please=20the=20lint=20god=20=E2=9C=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/sage/stats/distributions/discrete_gaussian_integer.pyx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/sage/stats/distributions/discrete_gaussian_integer.pyx b/src/sage/stats/distributions/discrete_gaussian_integer.pyx index dcec2bc6d58..6d50074102b 100644 --- a/src/sage/stats/distributions/discrete_gaussian_integer.pyx +++ b/src/sage/stats/distributions/discrete_gaussian_integer.pyx @@ -494,5 +494,3 @@ cdef class DiscreteGaussianDistributionIntegerSampler(SageObject): 'Discrete Gaussian sampler over the Integers with sigma = 3.000000 and c = 2.000000' """ return f"Discrete Gaussian sampler over the Integers with sigma = {self.sigma:.6f} and c = {self.c:.6f}" - - From e35b0182ad19ba75d83d4206e1b39fbf669c0ca7 Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Wed, 24 Jan 2024 02:01:08 +0000 Subject: [PATCH 046/278] =?UTF-8?q?Added=20catalog=20=F0=9F=93=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/sage/stats/all.py | 1 + src/sage/stats/distributions/catalog.py | 33 +++++++++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 src/sage/stats/distributions/catalog.py diff --git a/src/sage/stats/all.py b/src/sage/stats/all.py index e0f0b6e874e..f8b42231671 100644 --- a/src/sage/stats/all.py +++ b/src/sage/stats/all.py @@ -1,3 +1,4 @@ +import sage.stats.distributions.catalog as distributions from .r import ttest from .basic_stats import (mean, mode, std, variance, median, moving_average) diff --git a/src/sage/stats/distributions/catalog.py b/src/sage/stats/distributions/catalog.py new file mode 100644 index 00000000000..66c753ccff4 --- /dev/null +++ b/src/sage/stats/distributions/catalog.py @@ -0,0 +1,33 @@ +r""" +Index of distributions + +This catalogue includes the samplers for statistical distributions listed below. + +Let ```` indicate pressing the :kbd:`Tab` key. So begin by typing +``algebras.`` to the see the currently implemented named algebras. + +- :class:`distributions.discrete_gaussian_integer.DiscreteGaussianDistributionIntegerSampler + ` +- :class:`distributions.discrete_gaussian_lattice.DiscreteGaussianDistributionLatticeSampler + ` +- :class:`distributions.discrete_gaussian_polynomial.DiscreteGaussianDistributionPolynomialSampler + ` + +To import these names into the global namespace, use:: + + sage: from sage.stats.channels_catalog import * + +""" +#***************************************************************************** +# Copyright (C) 2024 Gareth Ma +# +# Distributed under the terms of the GNU General Public License (GPL), +# version 2 or later (at your preference). +# +# http://www.gnu.org/licenses/ +#***************************************************************************** + +from sage.misc.lazy_import import lazy_import as _lazy_import +_lazy_import("sage.stats.distributions.discrete_gaussian_integer", ["DiscreteGaussianDistributionIntegerSampler"]) +_lazy_import("sage.stats.distributions.discrete_gaussian_lattice", ["DiscreteGaussianDistributionLatticeSampler"]) +_lazy_import("sage.stats.distributions.discrete_gaussian_polynomial", ["DiscreteGaussianDistributionPolynomialSampler"]) From f4b72cdab0ddcb4eb62be5a3230a16797bdce966 Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Wed, 24 Jan 2024 03:03:57 +0000 Subject: [PATCH 047/278] =?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/rings/polynomial/polynomial_ring.py | 59 ++++++++++++-------- 1 file changed, 35 insertions(+), 24 deletions(-) diff --git a/src/sage/rings/polynomial/polynomial_ring.py b/src/sage/rings/polynomial/polynomial_ring.py index 98756d26af3..06116324f03 100644 --- a/src/sage/rings/polynomial/polynomial_ring.py +++ b/src/sage/rings/polynomial/polynomial_ring.py @@ -1360,7 +1360,7 @@ def ngens(self): """ return 1 - def random_element(self, degree=(-1,2), monic=False, *args, **kwds): + def random_element(self, degree=(-1, 2), monic_or_zero=False, *args, **kwds): r""" Return a random polynomial of given degree or with given degree bounds. @@ -1370,9 +1370,9 @@ def random_element(self, degree=(-1,2), monic=False, *args, **kwds): or a tuple of minimum and maximum degrees. By default set to ``(-1,2)``. - - ``monic`` - optional boolean to indicate whether the sample - polynomial should be monic or not. If degree includes -1, it is still - possible for the algorithm to return ``0``, which is not monic. + - ``monic_or_zero`` - optional boolean to indicate whether the sampled + polynomial should be monic or zero, or not. If ``degree`` includes + -1, the algorithm output range will also include zero. - ``*args, **kwds`` - Passed on to the ``random_element`` method for the base ring @@ -1380,7 +1380,7 @@ def random_element(self, degree=(-1,2), monic=False, *args, **kwds): EXAMPLES:: sage: R. = ZZ[] - sage: f = R.random_element(10, 5, 10) + sage: f = R.random_element(10, x=5, y=10) sage: f.degree() 10 sage: f.parent() is R @@ -1405,15 +1405,20 @@ def random_element(self, degree=(-1,2), monic=False, *args, **kwds): sage: while R.random_element(degree=(-1,2), x=-1, y=1) != R.zero(): ....: pass - It is possible to sample a monic polynomial:: + It is possible to sample a monic polynomial, but it is preferable to + use :meth:`random_monic_element`:: - sage: R.random_element(5, monic=True).is_monic() + sage: R.random_element(degree=(0, 5), monic_or_zero=True).is_monic() + True + sage: R.random_monic_element(degree=(0, 5)).is_monic() True Note that if the degree range includes `-1`, then the resulting polynomial might not be monic:: - sage: all(R.random_element(degree=(-1, 1), monic=True).is_monic() for _ in range(10^3)) + sage: all(R.random_element(degree=(-1, 1), monic_or_zero=True).is_monic() for _ in range(10^3)) False + sage: all(R.random_element(degree=(0, 1), monic_or_zero=True).is_monic() for _ in range(10^3)) + True TESTS:: @@ -1435,6 +1440,8 @@ def random_element(self, degree=(-1,2), monic=False, *args, **kwds): ....: P = R.random_element(degree=d) ....: assert P.degree() == d, "problem with {} which has not degree {}".format(P,d) + In :issue:`37118`, negative degrees no longer error. + sage: R.random_element(degree=-2) Traceback (most recent call last): ... @@ -1451,7 +1458,9 @@ def random_element(self, degree=(-1,2), monic=False, *args, **kwds): degree = (degree,degree) if degree[0] <= -2: - raise ValueError("degree should be an integer greater or equal than -1") + # This error has been removed in issue #37118. + # raise ValueError("degree should be an integer greater or equal than -1") + degree = (-1, degree[1]) # Actually, it's probably more useful to let the user check this. # if monic and degree[0] == -1: @@ -1467,7 +1476,7 @@ def random_element(self, degree=(-1,2), monic=False, *args, **kwds): raise ValueError("No polynomial of degree >= 0 has all coefficients zero") if degree == (-1, -1): - return 0 + return self.zero() if degree[0] == -1: allow_zero = True @@ -1477,38 +1486,38 @@ def random_element(self, degree=(-1,2), monic=False, *args, **kwds): while True: # Pick random coefficients - coefs = [] + coefs = [None] * (degree[1] + 1) nonzero = False - for _ in range(degree[1] - degree[0] + 1): - c = R.random_element(*args, **kwds) - coefs.append(c) - if c != 0 and not nonzero: - coefs[-1] = 1 + for i in range(degree[1] - degree[0] + 1): + coefs[i] = R.random_element(*args, **kwds) + if monic_or_zero and not nonzero and not coefs[i].is_zero(): + coefs[i] = R.one() nonzero = True if not (allow_zero or nonzero): continue - for _ in range(degree[0]): - c = R.random_element(*args, **kwds) - coefs.append(c) + for i in range(degree[1] - degree[0] + 1, degree[1] + 1): + coefs[i] = R.random_element(*args, **kwds) coefs.reverse() return self(coefs) - def random_monic_element(self, degree=(0, 2), *args, **kwargs): + def random_monic_element(self, degree=(0, 2), zero=False, *args, **kwargs): r""" Return a random monic polynomial of given degree or with given degree bounds. - Calls :meth:`random_element` with ``monic=True``. + Calls :meth:`random_element` with ``monic_or_zero=True``. INPUT: - ``degree`` - optional integer for fixing the degree or a tuple of minimum and maximum degrees. By default set to - ``(0,2)``. If the bounds include ``-1``, it is still possible for the - algorithm to return ``0``, which is not monic. + ``(0, 2)``. + + - ``zero`` - boolean, if set the algorithm may return `0`, even though + it is not a monic polynomial. By default set to ``False``. - ``*args, **kwds`` - Passed on to the ``random_element`` method for the base ring @@ -1523,7 +1532,9 @@ def random_monic_element(self, degree=(0, 2), *args, **kwargs): sage: all(R.random_monic_element(degree=(-1,1)).is_monic() for _ in range(100)) True """ - return self.random_element(degree=degree, monic=True, *args, **kwargs) + if not zero and degree[0] < 0: + degree = (0, degree[1]) + return self.random_element(degree=degree, monic_or_zero=True, *args, **kwargs) def _monics_degree(self, of_degree): """ From 388849cafe102c12ff5bddb211f52627ea1f71d8 Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Wed, 24 Jan 2024 14:15:46 +0000 Subject: [PATCH 048/278] =?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/rings/polynomial/polynomial_ring.py | 178 +++++++++++++++---- 1 file changed, 139 insertions(+), 39 deletions(-) diff --git a/src/sage/rings/polynomial/polynomial_ring.py b/src/sage/rings/polynomial/polynomial_ring.py index 06116324f03..c8a2ebd161e 100644 --- a/src/sage/rings/polynomial/polynomial_ring.py +++ b/src/sage/rings/polynomial/polynomial_ring.py @@ -1360,9 +1360,9 @@ def ngens(self): """ return 1 - def random_element(self, degree=(-1, 2), monic_or_zero=False, *args, **kwds): + def random_element(self, degree=(-1, 2), monic=False, _allow_zero=False, *args, **kwds): r""" - Return a random polynomial of given degree or with given degree bounds. + Return a random polynomial of given degree (bounds). INPUT: @@ -1370,17 +1370,31 @@ def random_element(self, degree=(-1, 2), monic_or_zero=False, *args, **kwds): or a tuple of minimum and maximum degrees. By default set to ``(-1,2)``. - - ``monic_or_zero`` - optional boolean to indicate whether the sampled - polynomial should be monic or zero, or not. If ``degree`` includes - -1, the algorithm output range will also include zero. + - ``monic`` - optional boolean to indicate whether the sampled + polynomial should be monic, or not. If this is set, `0` is not a + possible output of the method. If monic and `0` are both needed, + one should use :meth:`random_monic_or_zero_element`. + + - ``_allow_zero`` - optional boolean to indicate whether zero should be + included. If ``monic`` is not set, or the degree bound does not + include `-1`, then this argument does nothing. Otherwise, the + algorithm returns either monic or `0` polynomials (with uniform + probability over the range of output). This is for internal use, users + should use :meth:`random_monic_or_zero_element`. - ``*args, **kwds`` - Passed on to the ``random_element`` method for - the base ring + the base ring. + + .. SEEALSO:: + + :meth:`random_monic_element`, :meth:`random_monic_or_zero_element` EXAMPLES:: sage: R. = ZZ[] sage: f = R.random_element(10, x=5, y=10) + sage: f # random + 5*x^10 + 6*x^9 + 5*x^8 + 8*x^7 + 8*x^6 + 5*x^5 + 7*x^4 + 8*x^3 + 6*x^2 + 9*x + 6 sage: f.degree() 10 sage: f.parent() is R @@ -1406,18 +1420,18 @@ def random_element(self, degree=(-1, 2), monic_or_zero=False, *args, **kwds): ....: pass It is possible to sample a monic polynomial, but it is preferable to - use :meth:`random_monic_element`:: + use :meth:`random_monic_element` or :meth:`random_monic_or_zero_element`:: - sage: R.random_element(degree=(0, 5), monic_or_zero=True).is_monic() + sage: R.random_element(degree=(-1, 5), monic=True).is_monic() True - sage: R.random_monic_element(degree=(0, 5)).is_monic() + sage: R.random_monic_element(degree=(-1, 5)).is_monic() True - Note that if the degree range includes `-1`, then the resulting polynomial might not be monic:: + Note that if the degree range includes `-1` and ``monic`` is set, it is silently ignored:: - sage: all(R.random_element(degree=(-1, 1), monic_or_zero=True).is_monic() for _ in range(10^3)) - False - sage: all(R.random_element(degree=(0, 1), monic_or_zero=True).is_monic() for _ in range(10^3)) + sage: all(R.random_element(degree=(-1, 1), monic=True).is_monic() for _ in range(10^3)) + True + sage: all(R.random_element(degree=(0, 1), monic=True).is_monic() for _ in range(10^3)) True TESTS:: @@ -1440,12 +1454,21 @@ def random_element(self, degree=(-1, 2), monic_or_zero=False, *args, **kwds): ....: P = R.random_element(degree=d) ....: assert P.degree() == d, "problem with {} which has not degree {}".format(P,d) - In :issue:`37118`, negative degrees no longer error. + In :issue:`37118`, ranges including integers below `-1` no longer error:: - sage: R.random_element(degree=-2) - Traceback (most recent call last): - ... - ValueError: degree should be an integer greater or equal than -1 + sage: R.random_element(degree=(-2, 3)) # random + 1 + + :: + + sage: 0 in [R.random_element(degree=(-1, 2), monic=True) for _ in range(500)] + False + sage: 0 in [R.random_element(degree=(-1, 2), monic=True, _allow_zero=True) for _ in range(500)] + True + sage: 0 in [R.random_monic_element(degree=(-1, 2)) for _ in range(500)] + False + sage: 0 in [R.random_monic_or_zero_element(degree=(-1, 2)) for _ in range(500)] + True """ R = self.base_ring() @@ -1462,10 +1485,6 @@ def random_element(self, degree=(-1, 2), monic_or_zero=False, *args, **kwds): # raise ValueError("degree should be an integer greater or equal than -1") degree = (-1, degree[1]) - # Actually, it's probably more useful to let the user check this. - # if monic and degree[0] == -1: - # raise ValueError("degree cannot include -1 when monic is set") - # If the coefficient range only contains 0, then # * if the degree range includes -1, return the zero polynomial, # * otherwise raise a value error @@ -1478,35 +1497,54 @@ def random_element(self, degree=(-1, 2), monic_or_zero=False, *args, **kwds): if degree == (-1, -1): return self.zero() + # If `monic` is set and `_allow_zero` is not, zero should be ignored if degree[0] == -1: - allow_zero = True degree = (0, degree[1]) + has_zero = not monic or _allow_zero else: - allow_zero = False + has_zero = False while True: # Pick random coefficients coefs = [None] * (degree[1] + 1) nonzero = False - for i in range(degree[1] - degree[0] + 1): - coefs[i] = R.random_element(*args, **kwds) - if monic_or_zero and not nonzero and not coefs[i].is_zero(): - coefs[i] = R.one() - nonzero = True + # Pick leading coefficients, if `monic` is set it's handle here. + if monic: + for i in range(degree[1] - degree[0] + 1): + coefs[i] = R.random_element(*args, **kwds) + if not nonzero and not coefs[i].is_zero(): + coefs[i] = R.one() + nonzero = True - if not (allow_zero or nonzero): - continue + if not nonzero and not _allow_zero: + continue + else: + # Fast path + for i in range(degree[1] - degree[0] + 1): + coefs[i] = R.random_element(*args, **kwds) + nonzero |= not coefs[i].is_zero() + if not nonzero and not has_zero: + continue + + # Now we pick the remaining coefficients. Zeros still should be + # tracked to handle `has_zero`. for i in range(degree[1] - degree[0] + 1, degree[1] + 1): coefs[i] = R.random_element(*args, **kwds) + nonzero |= not coefs[i].is_zero() + + # If we don't want zero (not has_zero), but coefs is zero (not + # nonzero), then reject + if not has_zero and not nonzero: + continue coefs.reverse() return self(coefs) - def random_monic_element(self, degree=(0, 2), zero=False, *args, **kwargs): + def random_monic_element(self, degree=(0, 2), *args, **kwargs): r""" - Return a random monic polynomial of given degree or with given degree bounds. + Return a random monic polynomial of given degree (bounds). Calls :meth:`random_element` with ``monic_or_zero=True``. @@ -1520,21 +1558,83 @@ def random_monic_element(self, degree=(0, 2), zero=False, *args, **kwargs): it is not a monic polynomial. By default set to ``False``. - ``*args, **kwds`` - Passed on to the ``random_element`` method for - the base ring + the base ring. + + .. SEEALSO:: + + :meth:`random_element`, :meth:`random_monic_or_zero_element` EXAMPLES:: sage: R. = GF(3)[] - sage: R.random_element() # random - x^2 + 11*x + 5 + sage: R.random_monic_element() # random + x^2 + x + 1 sage: all(R.random_monic_element().is_monic() for _ in range(100)) True sage: all(R.random_monic_element(degree=(-1,1)).is_monic() for _ in range(100)) True + + TESTS: + + There are 7 monic polynomials of degree not exceeding 2 over GF(2). We + apply the chi-square test:: + + sage: from collections import Counter + sage: from scipy.stats import chisquare + sage: N = 10^5 + sage: R. = GF(2)[] + sage: cnts = Counter(R.random_monic_element() for _ in range(N)) + sage: chisquare(list(cnts.values()), [N / 7] * 7).pvalue < 0.1 + False + """ + return self.random_element(degree=degree, monic=True, *args, **kwargs) + + def random_monic_or_zero_element(self, degree=(-1, 2), *args, **kwargs): + r""" + Return a random polynomial that is either monic or zero of given degree (bounds). + + INPUT: + + - ``degree`` - optional integer for fixing the degree + or a tuple of minimum and maximum degrees. By default set to + ``(0, 2)``. + + - ``zero`` - boolean, if set the algorithm may return `0`, even though + it is not a monic polynomial. By default set to ``False``. + + - ``*args, **kwds`` - Passed on to the ``random_element`` method for + the base ring. + + .. SEEALSO:: + + :meth:`random_element`, :meth:`random_monic_or_zero_element` + + EXAMPLES:: + + sage: R. = GF(2)[] + sage: R.random_monic_or_zero_element() # random + x^2 + x + 1 + sage: all(R.random_monic_or_zero_element().is_monic() for _ in range(500)) + False + sage: 0 in [R.random_monic_or_zero_element().is_monic() for _ in range(500)] + True + sage: all(R.random_monic_element(degree=(-1,1)).is_monic() for _ in range(500)) + True + + TESTS: + + There are 8 monic or zero polynomials of degree not exceeding 2 over + GF(2). We apply the chi-square test:: + + sage: from collections import Counter + sage: from scipy.stats import chisquare + sage: N = 10^5 + sage: R. = GF(2)[] + sage: cnts = Counter(R.random_monic_or_zero_element() for _ in range(N)) + sage: chisquare(list(cnts.values()), [N / 8] * 8).pvalue < 0.1 + False """ - if not zero and degree[0] < 0: - degree = (0, degree[1]) - return self.random_element(degree=degree, monic_or_zero=True, *args, **kwargs) + return self.random_element(degree=degree, monic=True, _allow_zero=True, *args, **kwargs) def _monics_degree(self, of_degree): """ From efac3ca246cb9d8ec9a36abb51798b9f6ca9aaca Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Thu, 25 Jan 2024 16:48:52 +0000 Subject: [PATCH 049/278] =?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 This commit removes all `monic_or_zero` methods. --- src/sage/rings/polynomial/polynomial_ring.py | 87 ++++---------------- 1 file changed, 14 insertions(+), 73 deletions(-) diff --git a/src/sage/rings/polynomial/polynomial_ring.py b/src/sage/rings/polynomial/polynomial_ring.py index c8a2ebd161e..241b1dfe2de 100644 --- a/src/sage/rings/polynomial/polynomial_ring.py +++ b/src/sage/rings/polynomial/polynomial_ring.py @@ -1360,34 +1360,25 @@ def ngens(self): """ return 1 - def random_element(self, degree=(-1, 2), monic=False, _allow_zero=False, *args, **kwds): + def random_element(self, degree=(-1, 2), monic=False, *args, **kwds): r""" Return a random polynomial of given degree (bounds). INPUT: - - ``degree`` - optional integer for fixing the degree - or a tuple of minimum and maximum degrees. By default set to - ``(-1,2)``. + - ``degree`` - (default: ``(-1, 2)``) integer for fixing the degree or + a tuple of minimum and maximum degrees. - ``monic`` - optional boolean to indicate whether the sampled polynomial should be monic, or not. If this is set, `0` is not a - possible output of the method. If monic and `0` are both needed, - one should use :meth:`random_monic_or_zero_element`. - - - ``_allow_zero`` - optional boolean to indicate whether zero should be - included. If ``monic`` is not set, or the degree bound does not - include `-1`, then this argument does nothing. Otherwise, the - algorithm returns either monic or `0` polynomials (with uniform - probability over the range of output). This is for internal use, users - should use :meth:`random_monic_or_zero_element`. + possible output of the method. - ``*args, **kwds`` - Passed on to the ``random_element`` method for the base ring. .. SEEALSO:: - :meth:`random_monic_element`, :meth:`random_monic_or_zero_element` + :meth:`random_monic_element` EXAMPLES:: @@ -1420,7 +1411,7 @@ def random_element(self, degree=(-1, 2), monic=False, _allow_zero=False, *args, ....: pass It is possible to sample a monic polynomial, but it is preferable to - use :meth:`random_monic_element` or :meth:`random_monic_or_zero_element`:: + use :meth:`random_monic_element`:: sage: R.random_element(degree=(-1, 5), monic=True).is_monic() True @@ -1456,19 +1447,15 @@ def random_element(self, degree=(-1, 2), monic=False, _allow_zero=False, *args, In :issue:`37118`, ranges including integers below `-1` no longer error:: - sage: R.random_element(degree=(-2, 3)) # random + sage: R.random_element(degree=(-2, 3)) # random 1 :: sage: 0 in [R.random_element(degree=(-1, 2), monic=True) for _ in range(500)] False - sage: 0 in [R.random_element(degree=(-1, 2), monic=True, _allow_zero=True) for _ in range(500)] - True sage: 0 in [R.random_monic_element(degree=(-1, 2)) for _ in range(500)] False - sage: 0 in [R.random_monic_or_zero_element(degree=(-1, 2)) for _ in range(500)] - True """ R = self.base_ring() @@ -1497,10 +1484,10 @@ def random_element(self, degree=(-1, 2), monic=False, _allow_zero=False, *args, if degree == (-1, -1): return self.zero() - # If `monic` is set and `_allow_zero` is not, zero should be ignored + # If `monic` is set, zero should be ignored if degree[0] == -1: degree = (0, degree[1]) - has_zero = not monic or _allow_zero + has_zero = not monic else: has_zero = False @@ -1517,7 +1504,8 @@ def random_element(self, degree=(-1, 2), monic=False, _allow_zero=False, *args, coefs[i] = R.one() nonzero = True - if not nonzero and not _allow_zero: + # Leading terms must be nonzero + if not nonzero: continue else: # Fast path @@ -1546,7 +1534,7 @@ def random_monic_element(self, degree=(0, 2), *args, **kwargs): r""" Return a random monic polynomial of given degree (bounds). - Calls :meth:`random_element` with ``monic_or_zero=True``. + Calls :meth:`random_element` with ``monic=True``. INPUT: @@ -1562,7 +1550,7 @@ def random_monic_element(self, degree=(0, 2), *args, **kwargs): .. SEEALSO:: - :meth:`random_element`, :meth:`random_monic_or_zero_element` + :meth:`random_element` EXAMPLES:: @@ -1571,7 +1559,7 @@ def random_monic_element(self, degree=(0, 2), *args, **kwargs): x^2 + x + 1 sage: all(R.random_monic_element().is_monic() for _ in range(100)) True - sage: all(R.random_monic_element(degree=(-1,1)).is_monic() for _ in range(100)) + sage: all(R.random_monic_element(degree=(-1, 1)).is_monic() for _ in range(100)) True TESTS: @@ -1589,53 +1577,6 @@ def random_monic_element(self, degree=(0, 2), *args, **kwargs): """ return self.random_element(degree=degree, monic=True, *args, **kwargs) - def random_monic_or_zero_element(self, degree=(-1, 2), *args, **kwargs): - r""" - Return a random polynomial that is either monic or zero of given degree (bounds). - - INPUT: - - - ``degree`` - optional integer for fixing the degree - or a tuple of minimum and maximum degrees. By default set to - ``(0, 2)``. - - - ``zero`` - boolean, if set the algorithm may return `0`, even though - it is not a monic polynomial. By default set to ``False``. - - - ``*args, **kwds`` - Passed on to the ``random_element`` method for - the base ring. - - .. SEEALSO:: - - :meth:`random_element`, :meth:`random_monic_or_zero_element` - - EXAMPLES:: - - sage: R. = GF(2)[] - sage: R.random_monic_or_zero_element() # random - x^2 + x + 1 - sage: all(R.random_monic_or_zero_element().is_monic() for _ in range(500)) - False - sage: 0 in [R.random_monic_or_zero_element().is_monic() for _ in range(500)] - True - sage: all(R.random_monic_element(degree=(-1,1)).is_monic() for _ in range(500)) - True - - TESTS: - - There are 8 monic or zero polynomials of degree not exceeding 2 over - GF(2). We apply the chi-square test:: - - sage: from collections import Counter - sage: from scipy.stats import chisquare - sage: N = 10^5 - sage: R. = GF(2)[] - sage: cnts = Counter(R.random_monic_or_zero_element() for _ in range(N)) - sage: chisquare(list(cnts.values()), [N / 8] * 8).pvalue < 0.1 - False - """ - return self.random_element(degree=degree, monic=True, _allow_zero=True, *args, **kwargs) - def _monics_degree(self, of_degree): """ Refer to monics() for full documentation. From 2495622e4197264bc422aac5cc4701e33202633b Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Thu, 25 Jan 2024 16:49:49 +0000 Subject: [PATCH 050/278] fix docstring fullstops --- src/sage/rings/polynomial/polynomial_ring.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/sage/rings/polynomial/polynomial_ring.py b/src/sage/rings/polynomial/polynomial_ring.py index 241b1dfe2de..b1e522648d5 100644 --- a/src/sage/rings/polynomial/polynomial_ring.py +++ b/src/sage/rings/polynomial/polynomial_ring.py @@ -1367,14 +1367,14 @@ def random_element(self, degree=(-1, 2), monic=False, *args, **kwds): INPUT: - ``degree`` - (default: ``(-1, 2)``) integer for fixing the degree or - a tuple of minimum and maximum degrees. + a tuple of minimum and maximum degrees - ``monic`` - optional boolean to indicate whether the sampled polynomial should be monic, or not. If this is set, `0` is not a - possible output of the method. + possible output of the method - ``*args, **kwds`` - Passed on to the ``random_element`` method for - the base ring. + the base ring .. SEEALSO:: @@ -1540,13 +1540,13 @@ def random_monic_element(self, degree=(0, 2), *args, **kwargs): - ``degree`` - optional integer for fixing the degree or a tuple of minimum and maximum degrees. By default set to - ``(0, 2)``. + ``(0, 2)`` - ``zero`` - boolean, if set the algorithm may return `0`, even though - it is not a monic polynomial. By default set to ``False``. + it is not a monic polynomial. By default set to ``False`` - ``*args, **kwds`` - Passed on to the ``random_element`` method for - the base ring. + the base ring .. SEEALSO:: From 7d4ece61ca001501263073f500c63b99d14e8876 Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Thu, 25 Jan 2024 16:50:49 +0000 Subject: [PATCH 051/278] fix styling --- src/sage/rings/polynomial/polynomial_ring.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/polynomial/polynomial_ring.py b/src/sage/rings/polynomial/polynomial_ring.py index b1e522648d5..b9ef769cab8 100644 --- a/src/sage/rings/polynomial/polynomial_ring.py +++ b/src/sage/rings/polynomial/polynomial_ring.py @@ -1384,7 +1384,7 @@ def random_element(self, degree=(-1, 2), monic=False, *args, **kwds): sage: R. = ZZ[] sage: f = R.random_element(10, x=5, y=10) - sage: f # random + sage: f # random 5*x^10 + 6*x^9 + 5*x^8 + 8*x^7 + 8*x^6 + 5*x^5 + 7*x^4 + 8*x^3 + 6*x^2 + 9*x + 6 sage: f.degree() 10 From a879ac52685f4e07b9009b341df4421d9282522c Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Tue, 30 Jan 2024 15:42:09 +0000 Subject: [PATCH 052/278] =?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/rings/polynomial/polynomial_ring.py | 69 ++++++++++++-------- 1 file changed, 40 insertions(+), 29 deletions(-) diff --git a/src/sage/rings/polynomial/polynomial_ring.py b/src/sage/rings/polynomial/polynomial_ring.py index b9ef769cab8..f74674fb3b7 100644 --- a/src/sage/rings/polynomial/polynomial_ring.py +++ b/src/sage/rings/polynomial/polynomial_ring.py @@ -1370,11 +1370,10 @@ def random_element(self, degree=(-1, 2), monic=False, *args, **kwds): a tuple of minimum and maximum degrees - ``monic`` - optional boolean to indicate whether the sampled - polynomial should be monic, or not. If this is set, `0` is not a - possible output of the method + polynomial should be monic - - ``*args, **kwds`` - Passed on to the ``random_element`` method for - the base ring + - ``*args, **kwds`` - additional keyword parameters passed on to the + ``random_element`` method for the base ring .. SEEALSO:: @@ -1418,7 +1417,8 @@ def random_element(self, degree=(-1, 2), monic=False, *args, **kwds): sage: R.random_monic_element(degree=(-1, 5)).is_monic() True - Note that if the degree range includes `-1` and ``monic`` is set, it is silently ignored:: + Note that if the degree range includes `-1` and ``monic`` is set, it is + silently ignored, as `0` is not a monic polynomial:: sage: all(R.random_element(degree=(-1, 1), monic=True).is_monic() for _ in range(10^3)) True @@ -1441,14 +1441,15 @@ def random_element(self, degree=(-1, 2), monic=False, *args, **kwds): sage: R = PolynomialRing(GF(2), 'z') sage: for _ in range(100): - ....: d = randint(-1,20) + ....: d = randint(-1, 20) ....: P = R.random_element(degree=d) - ....: assert P.degree() == d, "problem with {} which has not degree {}".format(P,d) + ....: assert P.degree() == d - In :issue:`37118`, ranges including integers below `-1` no longer error:: + In :issue:`37118`, ranges including integers below `-1` no longer raise + an error:: sage: R.random_element(degree=(-2, 3)) # random - 1 + z^3 + z^2 + 1 :: @@ -1456,6 +1457,18 @@ def random_element(self, degree=(-1, 2), monic=False, *args, **kwds): False sage: 0 in [R.random_monic_element(degree=(-1, 2)) for _ in range(500)] False + + Testing error handling:: + + sage: R.random_element(degree=-5) + Traceback (most recent call last): + ... + ValueError: degree (=-5) must be at least -1 + + sage: R.random_element(degree=(-3, -2)) + Traceback (most recent call last): + ... + ValueError: maximum degree (=-2) must be at least -1 """ R = self.base_ring() @@ -1464,8 +1477,12 @@ def random_element(self, degree=(-1, 2), monic=False, *args, **kwds): raise ValueError("degree argument must be an integer or a tuple of 2 integers (min_degree, max_degree)") if degree[0] > degree[1]: raise ValueError("minimum degree must be less or equal than maximum degree") + if degree[1] < -1: + raise ValueError(f"maximum degree (={degree[1]}) must be at least -1") else: - degree = (degree,degree) + if degree < -1: + raise ValueError(f"degree (={degree}) must be at least -1") + degree = (degree, degree) if degree[0] <= -2: # This error has been removed in issue #37118. @@ -1481,53 +1498,47 @@ def random_element(self, degree=(-1, 2), monic=False, *args, **kwds): else: raise ValueError("No polynomial of degree >= 0 has all coefficients zero") + degree_lb = degree[0] if degree == (-1, -1): return self.zero() # If `monic` is set, zero should be ignored - if degree[0] == -1: + if degree[0] == -1 and monic: degree = (0, degree[1]) - has_zero = not monic - else: - has_zero = False while True: # Pick random coefficients + end = degree[1] coefs = [None] * (degree[1] + 1) nonzero = False # Pick leading coefficients, if `monic` is set it's handle here. if monic: for i in range(degree[1] - degree[0] + 1): - coefs[i] = R.random_element(*args, **kwds) - if not nonzero and not coefs[i].is_zero(): - coefs[i] = R.one() + coefs[end - i] = R.random_element(*args, **kwds) + if not nonzero and not coefs[end - i].is_zero(): + coefs[end - i] = R.one() nonzero = True - - # Leading terms must be nonzero - if not nonzero: - continue else: # Fast path for i in range(degree[1] - degree[0] + 1): - coefs[i] = R.random_element(*args, **kwds) - nonzero |= not coefs[i].is_zero() + coefs[end - i] = R.random_element(*args, **kwds) + nonzero |= not coefs[end - i].is_zero() - if not nonzero and not has_zero: - continue + # Leading terms must be nonzero + if not nonzero: + continue # Now we pick the remaining coefficients. Zeros still should be # tracked to handle `has_zero`. for i in range(degree[1] - degree[0] + 1, degree[1] + 1): - coefs[i] = R.random_element(*args, **kwds) - nonzero |= not coefs[i].is_zero() + coefs[end - i] = R.random_element(*args, **kwds) # If we don't want zero (not has_zero), but coefs is zero (not # nonzero), then reject - if not has_zero and not nonzero: + if degree_lb == -1 and not nonzero: continue - coefs.reverse() return self(coefs) def random_monic_element(self, degree=(0, 2), *args, **kwargs): From 0e864844dad0b97d3b4a34a6cacaa12e2c233d31 Mon Sep 17 00:00:00 2001 From: Giorgos Mousa Date: Wed, 31 Jan 2024 20:23:14 +0200 Subject: [PATCH 053/278] Translate "A Tour of Sage" into Greek --- src/doc/el/a_tour_of_sage/conf.py | 40 +++++++ src/doc/el/a_tour_of_sage/eigen_plot.png | Bin 0 -> 18520 bytes src/doc/el/a_tour_of_sage/index.rst | 145 +++++++++++++++++++++++ src/doc/el/a_tour_of_sage/sin_plot.png | Bin 0 -> 30132 bytes src/doc/en/website/root_index.html | 10 ++ 5 files changed, 195 insertions(+) create mode 100644 src/doc/el/a_tour_of_sage/conf.py create mode 100644 src/doc/el/a_tour_of_sage/eigen_plot.png create mode 100644 src/doc/el/a_tour_of_sage/index.rst create mode 100644 src/doc/el/a_tour_of_sage/sin_plot.png diff --git a/src/doc/el/a_tour_of_sage/conf.py b/src/doc/el/a_tour_of_sage/conf.py new file mode 100644 index 00000000000..f451fdf5ab8 --- /dev/null +++ b/src/doc/el/a_tour_of_sage/conf.py @@ -0,0 +1,40 @@ +# nodoctest +# Numerical Sage documentation build configuration file, created by +# sphinx-quickstart on Sat Dec 6 11:08:04 2008. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# The contents of this file are pickled, so don't put values in the namespace +# that aren't pickleable (module imports are okay, they're removed automatically). +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +from sage_docbuild.conf import release +from sage_docbuild.conf import * # NOQA + +# Add any paths that contain custom static files (such as style sheets), +# relative to this directory to html_static_path. They are copied after the +# builtin static files, so a file named "default.css" will overwrite the +# builtin "default.css". html_common_static_path imported from sage_docbuild.conf +# contains common paths. +html_static_path = [] + html_common_static_path + +# General information about the project. +project = 'Περιήγηση στο Sage' +name = 'a_tour_of_sage' +language = 'el' + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +html_title = project + ' v' + release + +# Output file base name for HTML help builder. +htmlhelp_basename = name + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, document class [howto/manual]). +latex_documents = [ + ('index', name + '.tex', project, + 'The Sage Development Team', 'manual'), +] diff --git a/src/doc/el/a_tour_of_sage/eigen_plot.png b/src/doc/el/a_tour_of_sage/eigen_plot.png new file mode 100644 index 0000000000000000000000000000000000000000..925264764f198f9f7f4e9d753d684eb180e1d048 GIT binary patch literal 18520 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 Rd}#*+lUIFQ@X$Eu{{Y)ke@y@Y literal 0 HcmV?d00001 diff --git a/src/doc/el/a_tour_of_sage/index.rst b/src/doc/el/a_tour_of_sage/index.rst new file mode 100644 index 00000000000..6811b7265e5 --- /dev/null +++ b/src/doc/el/a_tour_of_sage/index.rst @@ -0,0 +1,145 @@ +.. _a-tour-of-sage: + +===================== +Καλώς ήρθατε στο Sage +===================== + +Αυτή είναι μία σύντομη περιήγηση στο Sage και στην χρήση του ως αριθμομηχανή. + +Η γραμμή εντολών στο Sage εκκινεί με το μήνυμα προτροπής "``sage:``". Για +πειραματισμό με τα ακόλουθα παραδείγματα, αρκεί να εισαγάγετε το μέρος μετά το +μήνυμα προτροπής. + +:: + + sage: 3 + 5 + 8 + +Εάν χρησιμοποιείτε το Sage σε σημειωματάριο Jupyter, τότε -- παρομοίως -- +τοποθετείστε τα πάντα έπειτα του μηνύματος προτροπής εντός ενός κελιού +εισαγωγής, και πατήστε :kbd:`Shift-Enter` για να λάβετε την αντίστοιχη έξοδο. + +Το σύμβολο εκθέτη σημαίνει «ύψωση σε δύναμη». + +:: + + sage: 57.1^100 + 4.60904368661396e175 + +Υπολογίζουμε τον αντίστροφο ενός :math:`2 \times 2` πίνακα στο Sage. + +:: + + sage: matrix([[1, 2], [3, 4]])^(-1) + [ -2 1] + [ 3/2 -1/2] + +Εδώ υπολογίζουμε το ολοκλήρωμα μίας απλής συνάρτησης. + +:: + + sage: x = var('x') # δημιουργίας συμβολικής μεταβλητής + sage: integrate(sqrt(x) * sqrt(1 + x), x) + 1/4*((x + 1)^(3/2)/x^(3/2) + sqrt(x + 1)/sqrt(x))/((x + 1)^2/x^2 - 2*(x + 1)/x + 1) + - 1/8*log(sqrt(x + 1)/sqrt(x) + 1) + 1/8*log(sqrt(x + 1)/sqrt(x) - 1) + +Εδώ το Sage καλείται να λύσει μία δευτεροβάθμια εξίσωση. Το σύμβολο ``==`` +αντιπροσωπεύει την ισότητα στο Sage. + +:: + + sage: a = var('a') + sage: S = solve(x^2 + x == a, x); S + [x == -1/2*sqrt(4*a + 1) - 1/2, x == 1/2*sqrt(4*a + 1) - 1/2] + +Το αποτέλεσμα είναι μία λίστα από ισότητες. + +.. link + +:: + + sage: S[0].rhs() # δεξί μέρος της εξίσωσης (rhs = right hand side) + -1/2*sqrt(4*a + 1) - 1/2 + +Το Sage μπορεί να παραγάγει γραφήματα για διάφορες συναρτήσεις. + +:: + + sage: show(plot(sin(x) + sin(1.6*x), 0, 40)) + +.. image:: sin_plot.* + + +Το Sage είναι μία πολύ ισχυρή αριθμομηχανή. Για να το δείτε αυτό, δημιουργείστε +έναν :math:`500 \times 500` πίνακα με τυχαίους αριθμούς. + +:: + + sage: m = random_matrix(RDF, 500) + +Το Sage χρειάζεται ένα δευτερόλεπτο για τον υπολογισμό και την γραφική +παρουσίαση των ιδιοτιμών του πίνακα. + +.. link + +:: + + sage: e = m.eigenvalues() # περίπου 1 δευτερόλεπτο + sage: w = [(i, abs(e[i])) for i in range(len(e))] + sage: show(points(w)) + +.. image:: eigen_plot.* + + +Το Sage μπορεί να διαχειριστεί τεράστιους αριθμούς, ακόμα και με εκατομμύρια ή +δισεκατομμύρια ψηφία. + +:: + + sage: factorial(100) + 93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000 + +:: + + sage: n = factorial(1000000) # περίπου 1 δευτερόλεπτο + sage: len(n.digits()) + 5565709 + +Εδώ υπολογίζουμε τουλάχιστον 100 ψηφία του αριθμού :math:`\pi`. + +:: + + sage: N(pi, digits=100) + 3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117068 + +Εδώ το Sage παραγοντοποιεί ένα πολυώνυμο δύο μεταβλητών. + +:: + + sage: R. = QQ[] + sage: F = factor(x^99 + y^99) + sage: F + (x + y) * (x^2 - x*y + y^2) * (x^6 - x^3*y^3 + y^6) * + (x^10 - x^9*y + x^8*y^2 - x^7*y^3 + x^6*y^4 - x^5*y^5 + + x^4*y^6 - x^3*y^7 + x^2*y^8 - x*y^9 + y^10) * + (x^20 + x^19*y - x^17*y^3 - x^16*y^4 + x^14*y^6 + x^13*y^7 - + x^11*y^9 - x^10*y^10 - x^9*y^11 + x^7*y^13 + x^6*y^14 - + x^4*y^16 - x^3*y^17 + x*y^19 + y^20) * (x^60 + x^57*y^3 - + x^51*y^9 - x^48*y^12 + x^42*y^18 + x^39*y^21 - x^33*y^27 - + x^30*y^30 - x^27*y^33 + x^21*y^39 + x^18*y^42 - x^12*y^48 - + x^9*y^51 + x^3*y^57 + y^60) + sage: F.expand() + x^99 + y^99 + +Το Sage χρειάζεται λιγότερο από 1 δευτερόλεπτο για να υπολογίσει τους τρόπους +με τους οποίους ο αριθμός 100 εκατομμύρια μπορεί να γραφεί ως άθροισμα θετικών +ακεραίων. + +:: + + sage: z = Partitions(10^8).cardinality() # περίπου .1 δευτερόλεπτα + sage: z + 1760517045946249141360373894679135204009... + +Το Sage είναι το πιό προηγμένο λογισμικό ανοιχτού κώδικα για μαθηματικά στον +κόσμο. diff --git a/src/doc/el/a_tour_of_sage/sin_plot.png b/src/doc/el/a_tour_of_sage/sin_plot.png new file mode 100644 index 0000000000000000000000000000000000000000..ef4e87c69c170a7cd7302c21b87ab66df5d8c18c GIT binary patch literal 30132 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 Documentation
Tutorial
Thematic Tutorials
+
+
Greek
+
Hungarian
@@ -135,6 +140,11 @@

Sage Documentation

Tutorial
Thematic Tutorials
+
+
Greek
+
Hungarian
From f0f9d5a8be4bf5d3ba4dc9bd89340c306214ac7c Mon Sep 17 00:00:00 2001 From: jtcc2 <93992456+jtcc2@users.noreply.github.com> Date: Thu, 18 Jan 2024 15:24:20 +0100 Subject: [PATCH 054/278] Added QuaternionAlgebra.is_definite(), Quaternion Ideal reduced_basis(), and is_equivalent certificate --- .../algebras/quatalg/quaternion_algebra.py | 147 +++++++++++++++++- 1 file changed, 140 insertions(+), 7 deletions(-) diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index f45399ae288..5e86bf8bc3f 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -918,6 +918,22 @@ def invariants(self): """ return self._a, self._b + def is_definite(self) -> bool: + r""" + Return ``True`` if the quaternion algebra is definite. + + EXAMPLES:: + sage: B = QuaternionAlgebra(-1, -11) + sage: B.is_definite() + True + + sage: B = QuaternionAlgebra(-2, 5) + sage: B.is_definite() + False + """ + a, b = self.invariants() + return a < 0 and b < 0 + def __eq__(self, other): """ Compare self and other. @@ -2462,6 +2478,39 @@ def basis_matrix(self): C, d = B._clear_denom() return C.hermite_form() / d + def reduced_basis(self): + r""" + Let `I` = ``self`` be a quaternion ideal in a positive definite quaternion algebra. This function returns an LLL reduced basis of I + + OUTPUT: + + - A tuple of four elements in I forming an LLL reduced basis of I as a lattice + + EXAMPLES: + sage: B = BrandtModule(2,37); I = B.right_ideals()[0] + sage: I + Fractional ideal (2 + 2*i + 2*j + 2*k, 4*i + 108*k, 4*j + 44*k, 148*k) + sage: I.reduced_basis() + (2 + 2*i + 2*j + 2*k, 4, -2 - 2*i - 14*j + 14*k, -16*i + 12*k) + sage: l = I.reduced_basis() + sage: assert all(l[i].reduced_norm() <= l[i+1].reduced_norm() for i in range(len(l) - 1)) + + sage: B = QuaternionAlgebra(next_prime(2**50)) + sage: O = B.maximal_order() + sage: i,j,k = B.gens() + sage: alpha = 1/2 - 1/2*i + 3/2*j - 7/2*k + sage: I = O*alpha + O*3089622859 + sage: I.reduced_basis()[0] + 1/2*i + j + 5/2*k + """ + if not self.quadratic_form().is_positive_definite(): + raise TypeError(f'The quaternion algebra must be definite') + + U = self.gram_matrix().LLL_gram().transpose() + reduced_basis = tuple(sum(c * g for c, g in zip(row, self.basis())) for row in U) + + return reduced_basis + def theta_series_vector(self, B): r""" Return theta series coefficients of ``self``, as a vector @@ -2830,19 +2879,26 @@ def multiply_by_conjugate(self, J): R = self.quaternion_algebra() return R.ideal(basis, check=False) - def is_equivalent(self, J, B=10) -> bool: - """ - Return ``True`` if ``self`` and ``J`` are equivalent as right ideals. + def is_equivalent(self, J, B=10, certificate=False, side=None): + r""" + Return ``True`` if ``self`` and ``J`` are equivalent as ideals. + Tests equivalence as right ideals by default. Requires + quaternion algebra to be definite. INPUT: - ``J`` -- a fractional quaternion ideal with same order as ``self`` - ``B`` -- a bound to compute and compare theta series before - doing the full equivalence test + doing the full equivalence test - OUTPUT: bool + - ``certificate`` -- if ``True`` returns an element α such that + αJ=I or Jα=I for right and left ideals respectively + + - ``side`` -- If ``'left'`` performs left equivalence test. If ``'right' + ``or ``None`` performs right-ideal equivalence test + OUTPUT: bool or (bool, α) if ``certificate`` is ``True`` EXAMPLES:: sage: R = BrandtModule(3,5).right_ideals(); len(R) @@ -2855,17 +2911,80 @@ def is_equivalent(self, J, B=10) -> bool: sage: S = OO.right_ideal([3*a for a in R[0].basis()]) sage: R[0].is_equivalent(S) True + + sage: B = QuaternionAlgebra(101) + sage: i,j,k = B.gens() + sage: I = B.maximal_order().unit_ideal() + sage: J = I*(i + 2*k) + sage: I == J + False + sage: I.is_equivalent(J, side='left', certificate=True) + (True, 1/810*i + 1/405*k) + """ + if side == 'left': + return self.is_left_equivalent(J, B, certificate) + # If None, assume right ideals, for backwards compatibility + return self.is_right_equivalent(J, B, certificate) + + def is_left_equivalent(self, J, B=10, certificate=False): + r""" + Return ``True`` if ``self`` and ``J`` are equivalent as left ideals. + Requires quaternion algebra to be definite. + + INPUT: + + - ``J`` -- a fractional quaternion left-ideal with same order as ``self`` + + - ``B`` -- a bound to compute and compare theta series before + doing the full equivalence test + + - ``certificate`` -- if ``True`` returns an element α such that Jα=I + + OUTPUT: bool or (bool, α) if ``certificate`` is ``True`` + """ + if certificate: + is_equiv, cert = self.conjugate().is_right_equivalent(J.conjugate(), B, True) + if is_equiv: + return True, cert.conjugate() + return False, None + return self.conjugate().is_right_equivalent(J.conjugate(), B, False) + + def is_right_equivalent(self, J, B=10, certificate=False): + r""" + Return ``True`` if ``self`` and ``J`` are equivalent as right ideals. + Requires quaternion algebra to be definite. + + INPUT: + + - ``J`` -- a fractional quaternion right-ideal with same order as ``self`` + + - ``B`` -- a bound to compute and compare theta series before + doing the full equivalence test + + - ``certificate`` -- if ``True`` returns an element α such that αJ=I + + OUTPUT: bool or (bool, α) if ``certificate`` is ``True`` """ # shorthand: let I be self if not isinstance(self, QuaternionFractionalIdeal_rational): + if certificate: + return False, None return False - + if self.right_order() != J.right_order(): raise ValueError("self and J must be right ideals") + + A = self.quaternion_algebra() + if not A.is_definite(): + raise NotImplementedError('equivalence of ideals are not' + 'implemented for indefinite' + 'quaternion algebras') # Just test theta series first. If the theta series are # different, the ideals are definitely not equivalent. if B > 0 and self.theta_series_vector(B) != J.theta_series_vector(B): + if certificate: + return False, None return False # The theta series are the same, so perhaps the ideals are @@ -2878,7 +2997,21 @@ def is_equivalent(self, J, B=10) -> bool: # 2. Determine if there is alpha in K such # that N(alpha) = N(I)*N(J) as explained by Pizer. c = IJbar.theta_series_vector(2)[1] - return c != 0 + if c == 0: + if certificate: + return False, None + return False + + if not certificate: + return True + + # Use Pair's qfminim with flag = 1 + # Note the quadratic form is scaled by 4, so we must rescale after solving it + Q = IJbar.quadratic_form() + _,v = Q.__pari__().qfminim(None, None, 1) + alpha = sum(ZZ(c)*g for c,g in zip(v, IJbar.basis())) + alpha = alpha * (1/J.norm()) + return True, alpha def __contains__(self, x): """ From 180618ea45285d4e3c95593c123344810d880e36 Mon Sep 17 00:00:00 2001 From: jtcc2 <93992456+jtcc2@users.noreply.github.com> Date: Fri, 19 Jan 2024 09:10:21 +0100 Subject: [PATCH 055/278] Improved definiteness check for reduced_basis --- src/sage/algebras/quatalg/quaternion_algebra.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index 5e86bf8bc3f..78546f48252 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -2503,8 +2503,9 @@ def reduced_basis(self): sage: I.reduced_basis()[0] 1/2*i + j + 5/2*k """ - if not self.quadratic_form().is_positive_definite(): - raise TypeError(f'The quaternion algebra must be definite') + if not self.quaternion_algebra().is_definite(): + if not self.quadratic_form().is_positive_definite(): + raise TypeError(f'The quaternion algebra must be definite') U = self.gram_matrix().LLL_gram().transpose() reduced_basis = tuple(sum(c * g for c, g in zip(row, self.basis())) for row in U) From fc7636459d75b5a0eaf5a3335f96f11f4d115093 Mon Sep 17 00:00:00 2001 From: jtcc2 <93992456+jtcc2@users.noreply.github.com> Date: Fri, 19 Jan 2024 10:01:21 +0100 Subject: [PATCH 056/278] reduced_basis must have full rank ideal --- src/sage/algebras/quatalg/quaternion_algebra.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index 78546f48252..34758d25f4e 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -2480,7 +2480,8 @@ def basis_matrix(self): def reduced_basis(self): r""" - Let `I` = ``self`` be a quaternion ideal in a positive definite quaternion algebra. This function returns an LLL reduced basis of I + Let `I` = ``self`` be a rank 4 quaternion ideal in a positive definite quaternion algebra. + This function returns an LLL reduced basis of I OUTPUT: @@ -2503,6 +2504,9 @@ def reduced_basis(self): sage: I.reduced_basis()[0] 1/2*i + j + 5/2*k """ + if len(self.basis()) < 4: + raise ValueError("basis must have rank 4") + if not self.quaternion_algebra().is_definite(): if not self.quadratic_form().is_positive_definite(): raise TypeError(f'The quaternion algebra must be definite') From f48b3db25e24b125bf49e899f883d351d1785d03 Mon Sep 17 00:00:00 2001 From: jtcc2 <93992456+jtcc2@users.noreply.github.com> Date: Fri, 19 Jan 2024 11:35:28 +0100 Subject: [PATCH 057/278] Removed whitespace --- src/sage/algebras/quatalg/quaternion_algebra.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index 34758d25f4e..5cdf44b26be 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -2899,7 +2899,7 @@ def is_equivalent(self, J, B=10, certificate=False, side=None): - ``certificate`` -- if ``True`` returns an element α such that αJ=I or Jα=I for right and left ideals respectively - + - ``side`` -- If ``'left'`` performs left equivalence test. If ``'right' ``or ``None`` performs right-ideal equivalence test @@ -2924,7 +2924,7 @@ def is_equivalent(self, J, B=10, certificate=False, side=None): sage: I == J False sage: I.is_equivalent(J, side='left', certificate=True) - (True, 1/810*i + 1/405*k) + (True, 1/810*i + 1/405*k) """ if side == 'left': return self.is_left_equivalent(J, B, certificate) @@ -2946,7 +2946,7 @@ def is_left_equivalent(self, J, B=10, certificate=False): - ``certificate`` -- if ``True`` returns an element α such that Jα=I OUTPUT: bool or (bool, α) if ``certificate`` is ``True`` - """ + """ if certificate: is_equiv, cert = self.conjugate().is_right_equivalent(J.conjugate(), B, True) if is_equiv: @@ -2970,7 +2970,6 @@ def is_right_equivalent(self, J, B=10, certificate=False): OUTPUT: bool or (bool, α) if ``certificate`` is ``True`` """ - # shorthand: let I be self if not isinstance(self, QuaternionFractionalIdeal_rational): if certificate: return False, None @@ -3006,10 +3005,10 @@ def is_right_equivalent(self, J, B=10, certificate=False): if certificate: return False, None return False - + if not certificate: return True - + # Use Pair's qfminim with flag = 1 # Note the quadratic form is scaled by 4, so we must rescale after solving it Q = IJbar.quadratic_form() From e234f7ae70f87e1b0704c6e74ac91e615004cbba Mon Sep 17 00:00:00 2001 From: jtcc2 <93992456+jtcc2@users.noreply.github.com> Date: Fri, 19 Jan 2024 11:53:12 +0100 Subject: [PATCH 058/278] Added .is_principal --- .../algebras/quatalg/quaternion_algebra.py | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index 5cdf44b26be..37db754e6dc 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -3017,6 +3017,48 @@ def is_right_equivalent(self, J, B=10, certificate=False): alpha = alpha * (1/J.norm()) return True, alpha + def is_principal(self, certificate=False): + r""" + Return ``True`` if ``self`` is principal as a full rank quaternion ideal. + Requires definite quaternion algebra. Independent of whether ``self`` is + left or right ideal. + + INPUT: + + - ``certificate`` -- if ``True`` returns a generator α s.t. I = alpha O + where O is the right order. + + OUTPUT: bool or (bool, α) if ``certificate`` is ``True`` + + EXAMPLES:: + sage: B. = QuaternionAlgebra(419) + sage: O = B.quaternion_order([1/2 + 3/2*j, 1/6*i + 2/3*j + 1/2*k, 3*j, k]) + sage: alpha = B.random_element() + sage: I = alpha * O + sage: is_principal(I, True) + (True, 1/2 + 140*i + j - k) + """ + Q = self.quadratic_form() + + A = self.quaternion_algebra() + if not A.is_definite(): + raise NotImplementedError('is_principal ideal not' + 'implemented for indefinite' + 'quaternion algebras') + + if len(self.basis()) < 4: + raise ValueError("basis must have rank 4") + + c = self.theta_series_vector(2)[1] + if not certificate: + return c != 0 + if certificate and c == 0: + return False, None + + _,v = Q.__pari__().qfminim(None, None, 1) + alpha = sum(ZZ(c)*g for c,g in zip(v, self.basis())) + return True, alpha + def __contains__(self, x): """ Return whether x is in self. From 93728169612dda3611ac91c172968946931d913b Mon Sep 17 00:00:00 2001 From: jtcc2 <93992456+jtcc2@users.noreply.github.com> Date: Fri, 19 Jan 2024 12:02:59 +0100 Subject: [PATCH 059/278] Fixed codestyle --- .../algebras/quatalg/quaternion_algebra.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index 37db754e6dc..e7392ccc44c 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -2895,13 +2895,13 @@ def is_equivalent(self, J, B=10, certificate=False, side=None): - ``J`` -- a fractional quaternion ideal with same order as ``self`` - ``B`` -- a bound to compute and compare theta series before - doing the full equivalence test + doing the full equivalence test - - ``certificate`` -- if ``True`` returns an element α such that - αJ=I or Jα=I for right and left ideals respectively + - ``certificate`` -- if ``True`` returns an element α such that + αJ=I or Jα=I for right and left ideals respectively - - ``side`` -- If ``'left'`` performs left equivalence test. If ``'right' - ``or ``None`` performs right-ideal equivalence test + - ``side`` -- If ``'left'`` performs left equivalence test. If ``'right' + ``or ``None`` performs right-ideal equivalence test OUTPUT: bool or (bool, α) if ``certificate`` is ``True`` EXAMPLES:: @@ -2941,7 +2941,7 @@ def is_left_equivalent(self, J, B=10, certificate=False): - ``J`` -- a fractional quaternion left-ideal with same order as ``self`` - ``B`` -- a bound to compute and compare theta series before - doing the full equivalence test + doing the full equivalence test - ``certificate`` -- if ``True`` returns an element α such that Jα=I @@ -2964,7 +2964,7 @@ def is_right_equivalent(self, J, B=10, certificate=False): - ``J`` -- a fractional quaternion right-ideal with same order as ``self`` - ``B`` -- a bound to compute and compare theta series before - doing the full equivalence test + doing the full equivalence test - ``certificate`` -- if ``True`` returns an element α such that αJ=I @@ -2974,10 +2974,10 @@ def is_right_equivalent(self, J, B=10, certificate=False): if certificate: return False, None return False - + if self.right_order() != J.right_order(): raise ValueError("self and J must be right ideals") - + A = self.quaternion_algebra() if not A.is_definite(): raise NotImplementedError('equivalence of ideals are not' From 1a21d10cf10f75245829df11ec074794ed517b2c Mon Sep 17 00:00:00 2001 From: jtcc2 <93992456+jtcc2@users.noreply.github.com> Date: Fri, 19 Jan 2024 13:28:52 +0100 Subject: [PATCH 060/278] Fixed codestyle --- 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 e7392ccc44c..2b7ee144598 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -3026,7 +3026,7 @@ def is_principal(self, certificate=False): INPUT: - ``certificate`` -- if ``True`` returns a generator α s.t. I = alpha O - where O is the right order. + where O is the right order. OUTPUT: bool or (bool, α) if ``certificate`` is ``True`` From e49fc7e1c5efb9fcc0adbfc9de34a3482d8b4848 Mon Sep 17 00:00:00 2001 From: jtcc2 <93992456+jtcc2@users.noreply.github.com> Date: Fri, 19 Jan 2024 14:50:32 +0100 Subject: [PATCH 061/278] fix --- .../algebras/quatalg/quaternion_algebra.py | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index 2b7ee144598..2318a0b1fc7 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -3038,26 +3038,26 @@ def is_principal(self, certificate=False): sage: is_principal(I, True) (True, 1/2 + 140*i + j - k) """ - Q = self.quadratic_form() - - A = self.quaternion_algebra() - if not A.is_definite(): - raise NotImplementedError('is_principal ideal not' - 'implemented for indefinite' - 'quaternion algebras') - - if len(self.basis()) < 4: - raise ValueError("basis must have rank 4") - - c = self.theta_series_vector(2)[1] - if not certificate: - return c != 0 - if certificate and c == 0: - return False, None - - _,v = Q.__pari__().qfminim(None, None, 1) - alpha = sum(ZZ(c)*g for c,g in zip(v, self.basis())) - return True, alpha + Q = self.quadratic_form() + + A = self.quaternion_algebra() + if not A.is_definite(): + raise NotImplementedError('is_principal ideal not' + 'implemented for indefinite' + 'quaternion algebras') + + if len(self.basis()) < 4: + raise ValueError("basis must have rank 4") + + c = self.theta_series_vector(2)[1] + if not certificate: + return c != 0 + if certificate and c == 0: + return False, None + + _,v = Q.__pari__().qfminim(None, None, 1) + alpha = sum(ZZ(c)*g for c,g in zip(v, self.basis())) + return True, alpha def __contains__(self, x): """ From 98fdb58f82cd713e7968c8892109b34c927ec89f Mon Sep 17 00:00:00 2001 From: jtcc2 <93992456+jtcc2@users.noreply.github.com> Date: Fri, 19 Jan 2024 16:10:09 +0100 Subject: [PATCH 062/278] fix --- .../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 2318a0b1fc7..0863bba3f94 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -923,6 +923,7 @@ def is_definite(self) -> bool: Return ``True`` if the quaternion algebra is definite. EXAMPLES:: + sage: B = QuaternionAlgebra(-1, -11) sage: B.is_definite() True @@ -2487,7 +2488,8 @@ def reduced_basis(self): - A tuple of four elements in I forming an LLL reduced basis of I as a lattice - EXAMPLES: + EXAMPLES:: + sage: B = BrandtModule(2,37); I = B.right_ideals()[0] sage: I Fractional ideal (2 + 2*i + 2*j + 2*k, 4*i + 108*k, 4*j + 44*k, 148*k) @@ -2904,6 +2906,7 @@ def is_equivalent(self, J, B=10, certificate=False, side=None): ``or ``None`` performs right-ideal equivalence test OUTPUT: bool or (bool, α) if ``certificate`` is ``True`` + EXAMPLES:: sage: R = BrandtModule(3,5).right_ideals(); len(R) @@ -3031,12 +3034,13 @@ def is_principal(self, certificate=False): OUTPUT: bool or (bool, α) if ``certificate`` is ``True`` EXAMPLES:: - sage: B. = QuaternionAlgebra(419) - sage: O = B.quaternion_order([1/2 + 3/2*j, 1/6*i + 2/3*j + 1/2*k, 3*j, k]) - sage: alpha = B.random_element() - sage: I = alpha * O - sage: is_principal(I, True) - (True, 1/2 + 140*i + j - k) + + sage: B. = QuaternionAlgebra(419) + sage: O = B.quaternion_order([1/2 + 3/2*j, 1/6*i + 2/3*j + 1/2*k, 3*j, k]) + sage: alpha = B.random_element() + sage: I = alpha * O + sage: is_principal(I, True) + (True, 1/2 + 140*i + j - k) """ Q = self.quadratic_form() From c17a8e34d2513f2e3e6eafc8760c1204cf49d7ba Mon Sep 17 00:00:00 2001 From: jtcc2 <93992456+jtcc2@users.noreply.github.com> Date: Mon, 22 Jan 2024 17:35:27 +0000 Subject: [PATCH 063/278] Fixed doctest --- src/sage/algebras/quatalg/quaternion_algebra.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index 0863bba3f94..f4c1cd5f244 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -3037,10 +3037,10 @@ def is_principal(self, certificate=False): sage: B. = QuaternionAlgebra(419) sage: O = B.quaternion_order([1/2 + 3/2*j, 1/6*i + 2/3*j + 1/2*k, 3*j, k]) - sage: alpha = B.random_element() + sage: alpha = -1 - 59*i + 1/2*j + 154/9*k sage: I = alpha * O - sage: is_principal(I, True) - (True, 1/2 + 140*i + j - k) + sage: I.is_principal(True) + (True, 1 + 59*i - 1/2*j - 154/9*k) """ Q = self.quadratic_form() From a08850e00413525e63e53fed65b17217a89fa3aa Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Mon, 29 Jan 2024 11:42:59 +0100 Subject: [PATCH 064/278] Small modifications - Removed greek letter alpha in docstrings in hopes of this fixing infinite build loops - Updated `.is_definite()` to fit PR #37173 (up to the reference to Voight's book) - Other small modifications of docstrings, comments and error warnings - Slightly cleaned up code with respect to intermediately defined variables --- .../algebras/quatalg/quaternion_algebra.py | 127 +++++++++--------- 1 file changed, 62 insertions(+), 65 deletions(-) diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index f4c1cd5f244..5296729cda4 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -918,20 +918,26 @@ def invariants(self): """ return self._a, self._b - def is_definite(self) -> bool: - r""" - Return ``True`` if the quaternion algebra is definite. + def is_definite(self): + """ + Checks whether the quaternion algebra ``self`` is definite, i.e. whether it ramifies at the + unique Archimedean place of its base field QQ. This is the case if and only if both + invariants of ``self`` are negative. EXAMPLES:: - sage: B = QuaternionAlgebra(-1, -11) - sage: B.is_definite() + sage: QuaternionAlgebra(QQ,-5,-2).is_definite() True - - sage: B = QuaternionAlgebra(-2, 5) - sage: B.is_definite() + sage: QuaternionAlgebra(1).is_definite() False + + sage: QuaternionAlgebra(RR(2.),1).is_definite() + Traceback (most recent call last): + ... + ValueError: base field must be rational numbers """ + if not is_RationalField(self.base_ring()): + raise ValueError("base field must be rational numbers") a, b = self.invariants() return a < 0 and b < 0 @@ -2481,8 +2487,8 @@ def basis_matrix(self): def reduced_basis(self): r""" - Let `I` = ``self`` be a rank 4 quaternion ideal in a positive definite quaternion algebra. - This function returns an LLL reduced basis of I + Let `I` = ``self`` be a rank 4 quaternion ideal in a definite quaternion algebra. + This function returns an LLL reduced basis of I. OUTPUT: @@ -2511,12 +2517,10 @@ def reduced_basis(self): if not self.quaternion_algebra().is_definite(): if not self.quadratic_form().is_positive_definite(): - raise TypeError(f'The quaternion algebra must be definite') + raise TypeError("The quaternion algebra must be definite") U = self.gram_matrix().LLL_gram().transpose() - reduced_basis = tuple(sum(c * g for c, g in zip(row, self.basis())) for row in U) - - return reduced_basis + return tuple(sum(c * g for c, g in zip(row, self.basis())) for row in U) def theta_series_vector(self, B): r""" @@ -2888,8 +2892,8 @@ def multiply_by_conjugate(self, J): def is_equivalent(self, J, B=10, certificate=False, side=None): r""" - Return ``True`` if ``self`` and ``J`` are equivalent as ideals. - Tests equivalence as right ideals by default. Requires + Checks whether ``self`` and ``J`` are equivalent as ideals. + Tests equivalence as right ideals by default. Requires the underlying quaternion algebra to be definite. INPUT: @@ -2899,13 +2903,13 @@ def is_equivalent(self, J, B=10, certificate=False, side=None): - ``B`` -- a bound to compute and compare theta series before doing the full equivalence test - - ``certificate`` -- if ``True`` returns an element α such that - αJ=I or Jα=I for right and left ideals respectively + - ``certificate`` -- if ``True`` returns an element alpha such that + alpha*J = I or J*alpha = I for right and left ideals respectively - ``side`` -- If ``'left'`` performs left equivalence test. If ``'right' - ``or ``None`` performs right-ideal equivalence test + ``or ``None`` performs right ideal equivalence test - OUTPUT: bool or (bool, α) if ``certificate`` is ``True`` + OUTPUT: bool, or (bool, alpha) if ``certificate`` is ``True`` EXAMPLES:: @@ -2936,19 +2940,19 @@ def is_equivalent(self, J, B=10, certificate=False, side=None): def is_left_equivalent(self, J, B=10, certificate=False): r""" - Return ``True`` if ``self`` and ``J`` are equivalent as left ideals. - Requires quaternion algebra to be definite. + Checks whether ``self`` and ``J`` are equivalent as left ideals. + Requires the underlying quaternion algebra to be definite. INPUT: - - ``J`` -- a fractional quaternion left-ideal with same order as ``self`` + - ``J`` -- a fractional quaternion left ideal with same order as ``self`` - ``B`` -- a bound to compute and compare theta series before doing the full equivalence test - - ``certificate`` -- if ``True`` returns an element α such that Jα=I + - ``certificate`` -- if ``True`` returns an element alpha such that J*alpha=I - OUTPUT: bool or (bool, α) if ``certificate`` is ``True`` + OUTPUT: bool, or (bool, alpha) if ``certificate`` is ``True`` """ if certificate: is_equiv, cert = self.conjugate().is_right_equivalent(J.conjugate(), B, True) @@ -2959,50 +2963,47 @@ def is_left_equivalent(self, J, B=10, certificate=False): def is_right_equivalent(self, J, B=10, certificate=False): r""" - Return ``True`` if ``self`` and ``J`` are equivalent as right ideals. - Requires quaternion algebra to be definite. + Checks whether ``self`` and ``J`` are equivalent as right ideals. + Requires the underlying quaternion algebra to be definite. INPUT: - - ``J`` -- a fractional quaternion right-ideal with same order as ``self`` + - ``J`` -- a fractional quaternion right ideal with same order as ``self`` - ``B`` -- a bound to compute and compare theta series before doing the full equivalence test - - ``certificate`` -- if ``True`` returns an element α such that αJ=I + - ``certificate`` -- if ``True`` returns an element alpha such that alpha*J=I - OUTPUT: bool or (bool, α) if ``certificate`` is ``True`` + OUTPUT: bool, or (bool, alpha) if ``certificate`` is ``True`` """ - if not isinstance(self, QuaternionFractionalIdeal_rational): + if not isinstance(J, QuaternionFractionalIdeal_rational): if certificate: return False, None return False if self.right_order() != J.right_order(): - raise ValueError("self and J must be right ideals") + raise ValueError("self and J must be right ideals over the same order") - A = self.quaternion_algebra() - if not A.is_definite(): - raise NotImplementedError('equivalence of ideals are not' - 'implemented for indefinite' - 'quaternion algebras') + if not self.quaternion_algebra().is_definite(): + raise NotImplementedError('equivalence test of ideals not implemented' + 'for indefinite quaternion algebras') - # Just test theta series first. If the theta series are - # different, the ideals are definitely not equivalent. + # Just test theta series first; if the theta series are + # different, the ideals are definitely not equivalent if B > 0 and self.theta_series_vector(B) != J.theta_series_vector(B): if certificate: return False, None return False # The theta series are the same, so perhaps the ideals are - # equivalent. We use Prop 1.18 of [Pizer, 1980] to decide. + # equivalent. We use Prop 1.18 of [Piz1980]_ to decide. # 1. Compute I * Jbar - # see Prop. 1.17 in Pizer. Note that we use IJbar instead of - # JbarI since we work with right ideals + # Note that we use I*Jbar instead of Jbar*I since we work with right ideals IJbar = self.multiply_by_conjugate(J) - # 2. Determine if there is alpha in K such - # that N(alpha) = N(I)*N(J) as explained by Pizer. + # 2. Determine if there is alpha in K such that + # N(alpha) = N(I)*N(J) as explained by Pizer c = IJbar.theta_series_vector(2)[1] if c == 0: if certificate: @@ -3012,26 +3013,25 @@ def is_right_equivalent(self, J, B=10, certificate=False): if not certificate: return True - # Use Pair's qfminim with flag = 1 - # Note the quadratic form is scaled by 4, so we must rescale after solving it - Q = IJbar.quadratic_form() - _,v = Q.__pari__().qfminim(None, None, 1) + # Use Pari's qfminim with flag = 1 to find alpha as above + _,v = IJbar.quadratic_form().__pari__().qfminim(None, None, 1) alpha = sum(ZZ(c)*g for c,g in zip(v, IJbar.basis())) - alpha = alpha * (1/J.norm()) - return True, alpha + + # As explained by Pizer, we now only need to rescale alpha by 1/N(J) + return True, alpha * (1/J.norm()) def is_principal(self, certificate=False): r""" - Return ``True`` if ``self`` is principal as a full rank quaternion ideal. - Requires definite quaternion algebra. Independent of whether ``self`` is - left or right ideal. + Checks whether ``self`` is principal as a full rank quaternion ideal. + Requires the underlying quaternion algebra to be definite. + Independent of whether ``self`` is a left or a right ideal. INPUT: - - ``certificate`` -- if ``True`` returns a generator α s.t. I = alpha O - where O is the right order. + - ``certificate`` -- if ``True`` returns a generator alpha s.t. I = alpha*O + where O is the right order of I. - OUTPUT: bool or (bool, α) if ``certificate`` is ``True`` + OUTPUT: bool, or (bool, alpha) if ``certificate`` is ``True`` EXAMPLES:: @@ -3042,12 +3042,9 @@ def is_principal(self, certificate=False): sage: I.is_principal(True) (True, 1 + 59*i - 1/2*j - 154/9*k) """ - Q = self.quadratic_form() - - A = self.quaternion_algebra() - if not A.is_definite(): - raise NotImplementedError('is_principal ideal not' - 'implemented for indefinite' + if not self.quaternion_algebra().is_definite(): + raise NotImplementedError('principality test not' + 'implemented in indefinite' 'quaternion algebras') if len(self.basis()) < 4: @@ -3059,9 +3056,9 @@ def is_principal(self, certificate=False): if certificate and c == 0: return False, None - _,v = Q.__pari__().qfminim(None, None, 1) - alpha = sum(ZZ(c)*g for c,g in zip(v, self.basis())) - return True, alpha + # Use Pari's qfminim with flag = 1 to find alpha with N(alpha) = N(I) + _,v = self.quadratic_form().__pari__().qfminim(None, None, 1) + return True, sum(ZZ(c)*g for c,g in zip(v, self.basis())) def __contains__(self, x): """ From 7cddcffcaf587b74306721c87e0e5f7bc70a9425 Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Fri, 2 Feb 2024 18:59:08 +0100 Subject: [PATCH 065/278] Implemented first batch of reviewer feedback - Added deprecation warning for `.is_equivalent` - Removed unnecessary call of `.is_positive_definite()` in `.reduced_basis()` - Reduced `.is_right_equivalent` to `.is_principal` with suitable rescaling - Fixed some error warnings - Modified docstrings, moved examples to non-deprecated methods and randomized test of `.is_principal` --- .../algebras/quatalg/quaternion_algebra.py | 108 ++++++++++-------- 1 file changed, 60 insertions(+), 48 deletions(-) diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index 5296729cda4..8369fd9155d 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -2516,8 +2516,7 @@ def reduced_basis(self): raise ValueError("basis must have rank 4") if not self.quaternion_algebra().is_definite(): - if not self.quadratic_form().is_positive_definite(): - raise TypeError("The quaternion algebra must be definite") + raise TypeError("The quaternion algebra must be definite") U = self.gram_matrix().LLL_gram().transpose() return tuple(sum(c * g for c, g in zip(row, self.basis())) for row in U) @@ -2894,7 +2893,7 @@ def is_equivalent(self, J, B=10, certificate=False, side=None): r""" Checks whether ``self`` and ``J`` are equivalent as ideals. Tests equivalence as right ideals by default. Requires the underlying - quaternion algebra to be definite. + rational quaternion algebra to be definite. INPUT: @@ -2916,23 +2915,21 @@ def is_equivalent(self, J, B=10, certificate=False, side=None): sage: R = BrandtModule(3,5).right_ideals(); len(R) 2 sage: R[0].is_equivalent(R[1]) + doctest:...: DeprecationWarning: is_equivalent is deprecated, + please use is_left_equivalent or is_right_equivalent + accordingly instead + See https://github.com/sagemath/sage/issues/37100 for details. False - sage: R[0].is_equivalent(R[0]) - True + sage: OO = R[0].left_order() sage: S = OO.right_ideal([3*a for a in R[0].basis()]) sage: R[0].is_equivalent(S) + doctest:...: DeprecationWarning: ... True - - sage: B = QuaternionAlgebra(101) - sage: i,j,k = B.gens() - sage: I = B.maximal_order().unit_ideal() - sage: J = I*(i + 2*k) - sage: I == J - False - sage: I.is_equivalent(J, side='left', certificate=True) - (True, 1/810*i + 1/405*k) """ + from sage.misc.superseded import deprecation + deprecation(37100, 'is_equivalent is deprecated, please use is_left_equivalent' + ' or is_right_equivalent accordingly instead') if side == 'left': return self.is_left_equivalent(J, B, certificate) # If None, assume right ideals, for backwards compatibility @@ -2941,7 +2938,7 @@ def is_equivalent(self, J, B=10, certificate=False, side=None): def is_left_equivalent(self, J, B=10, certificate=False): r""" Checks whether ``self`` and ``J`` are equivalent as left ideals. - Requires the underlying quaternion algebra to be definite. + Requires the underlying rational quaternion algebra to be definite. INPUT: @@ -2964,7 +2961,7 @@ def is_left_equivalent(self, J, B=10, certificate=False): def is_right_equivalent(self, J, B=10, certificate=False): r""" Checks whether ``self`` and ``J`` are equivalent as right ideals. - Requires the underlying quaternion algebra to be definite. + Requires the underlying rational quaternion algebra to be definite. INPUT: @@ -2976,18 +2973,44 @@ def is_right_equivalent(self, J, B=10, certificate=False): - ``certificate`` -- if ``True`` returns an element alpha such that alpha*J=I OUTPUT: bool, or (bool, alpha) if ``certificate`` is ``True`` + + EXAMPLES:: + + sage: R = BrandtModule(3,5).right_ideals(); len(R) + 2 + sage: R[0].is_right_equivalent(R[1]) + False + + sage: R[0].is_right_equivalent(R[0]) + True + sage: OO = R[0].left_order() + sage: S = OO.right_ideal([3*a for a in R[0].basis()]) + sage: R[0].is_right_equivalent(S, certificate=True) + (True, -1/3) + sage: -1/3*S == R[0] + True + + sage: B = QuaternionAlgebra(101) + sage: i,j,k = B.gens() + sage: I = B.maximal_order().unit_ideal() + sage: beta = B.random_element() # random + sage: J = beta*I + sage: bool, alpha = I.is_right_equivalent(J, certificate=True) + sage: bool + True + sage: alpha*J == I + True """ if not isinstance(J, QuaternionFractionalIdeal_rational): - if certificate: - return False, None - return False + raise TypeError('J must be a fractional ideal' + ' in a rational quaternion algebra') if self.right_order() != J.right_order(): - raise ValueError("self and J must be right ideals over the same order") + raise ValueError('self and J must be right ideals over the same order') if not self.quaternion_algebra().is_definite(): raise NotImplementedError('equivalence test of ideals not implemented' - 'for indefinite quaternion algebras') + ' for indefinite quaternion algebras') # Just test theta series first; if the theta series are # different, the ideals are definitely not equivalent @@ -2996,29 +3019,16 @@ def is_right_equivalent(self, J, B=10, certificate=False): return False, None return False - # The theta series are the same, so perhaps the ideals are - # equivalent. We use Prop 1.18 of [Piz1980]_ to decide. + # The theta series are the same, so perhaps the ideals are equivalent + # We adapt Prop 1.18 of [Piz1980]_ to right ideals to decide: # 1. Compute I * Jbar - # Note that we use I*Jbar instead of Jbar*I since we work with right ideals IJbar = self.multiply_by_conjugate(J) - # 2. Determine if there is alpha in K such that - # N(alpha) = N(I)*N(J) as explained by Pizer - c = IJbar.theta_series_vector(2)[1] - if c == 0: - if certificate: - return False, None - return False - - if not certificate: - return True - - # Use Pari's qfminim with flag = 1 to find alpha as above - _,v = IJbar.quadratic_form().__pari__().qfminim(None, None, 1) - alpha = sum(ZZ(c)*g for c,g in zip(v, IJbar.basis())) - - # As explained by Pizer, we now only need to rescale alpha by 1/N(J) - return True, alpha * (1/J.norm()) + # 2. Determine if there is alpha in I * Jbar with N(alpha) = N(I)*N(J) + # Equivalently, we can simply call the principality test on IJbar, + # but we rescale by 1/N(J) to make sure this test directly gives back + # the correct alpha if a certificate is requested + return (1/J.norm()*IJbar).is_principal(certificate) def is_principal(self, certificate=False): r""" @@ -3037,15 +3047,17 @@ def is_principal(self, certificate=False): sage: B. = QuaternionAlgebra(419) sage: O = B.quaternion_order([1/2 + 3/2*j, 1/6*i + 2/3*j + 1/2*k, 3*j, k]) - sage: alpha = -1 - 59*i + 1/2*j + 154/9*k - sage: I = alpha * O - sage: I.is_principal(True) - (True, 1 + 59*i - 1/2*j - 154/9*k) + sage: beta = O.random_element() # random + sage: I = O*beta + sage: bool, alpha = I.is_principal(True) + sage: bool + True + sage: I == O*alpha + True """ if not self.quaternion_algebra().is_definite(): - raise NotImplementedError('principality test not' - 'implemented in indefinite' - 'quaternion algebras') + raise NotImplementedError('principality test not implemented in' + ' indefinite quaternion algebras') if len(self.basis()) < 4: raise ValueError("basis must have rank 4") From d55b29a289eff3b542ba1b6e96d8d8128b459d4d Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Fri, 2 Feb 2024 19:07:53 +0100 Subject: [PATCH 066/278] Fix lint issues --- src/sage/algebras/quatalg/quaternion_algebra.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index 8369fd9155d..9c2a05eda9e 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -3019,14 +3019,14 @@ def is_right_equivalent(self, J, B=10, certificate=False): return False, None return False - # The theta series are the same, so perhaps the ideals are equivalent + # The theta series are the same, so perhaps the ideals are equivalent # We adapt Prop 1.18 of [Piz1980]_ to right ideals to decide: # 1. Compute I * Jbar IJbar = self.multiply_by_conjugate(J) # 2. Determine if there is alpha in I * Jbar with N(alpha) = N(I)*N(J) # Equivalently, we can simply call the principality test on IJbar, - # but we rescale by 1/N(J) to make sure this test directly gives back + # but we rescale by 1/N(J) to make sure this test directly gives back # the correct alpha if a certificate is requested return (1/J.norm()*IJbar).is_principal(certificate) From 039d80e41107ae3108bb5720b52b5da7f7d9bd65 Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Fri, 2 Feb 2024 22:31:47 +0100 Subject: [PATCH 067/278] (Attempt to) Fix deprecation failure --- src/sage/algebras/quatalg/quaternion_algebra.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index 9c2a05eda9e..ef07506d815 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -2914,17 +2914,13 @@ def is_equivalent(self, J, B=10, certificate=False, side=None): sage: R = BrandtModule(3,5).right_ideals(); len(R) 2 - sage: R[0].is_equivalent(R[1]) - doctest:...: DeprecationWarning: is_equivalent is deprecated, - please use is_left_equivalent or is_right_equivalent - accordingly instead - See https://github.com/sagemath/sage/issues/37100 for details. - False - sage: OO = R[0].left_order() sage: S = OO.right_ideal([3*a for a in R[0].basis()]) sage: R[0].is_equivalent(S) - doctest:...: DeprecationWarning: ... + doctest:...: DeprecationWarning: is_equivalent is deprecated, + please use is_left_equivalent or is_right_equivalent + accordingly instead + See https://github.com/sagemath/sage/issues/37100 for details. True """ from sage.misc.superseded import deprecation From 099b8c93bc303282e0c268a9fe6ff11e359a205b Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Sat, 3 Feb 2024 01:21:01 +0100 Subject: [PATCH 068/278] Replace is_equivalent in BrandtModule --- src/sage/modular/quatalg/brandt.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sage/modular/quatalg/brandt.py b/src/sage/modular/quatalg/brandt.py index 1c74d953f6d..9ae81181279 100644 --- a/src/sage/modular/quatalg/brandt.py +++ b/src/sage/modular/quatalg/brandt.py @@ -93,7 +93,7 @@ if there exists an element `\alpha \in I \overline{J}` such `N(\alpha)=N(I)N(J)`. -``is_equivalent(I,J)`` returns true if `I` and `J` are equivalent. This +``is_right_equivalent(I,J)`` returns true if `I` and `J` are equivalent. This method first compares the theta series of `I` and `J`. If they are the same, it computes the theta series of the lattice `I\overline(J)`. It returns true if the `n^{th}` coefficient of this series is nonzero @@ -1193,7 +1193,7 @@ def _compute_hecke_matrix_directly(self, n, B=None, sparse=False): T[r, v[0]] += 1 else: for i in v: - if C[i].is_equivalent(J, 0): + if C[i].is_right_equivalent(J, 0): T[r, i] += 1 break return T @@ -1325,7 +1325,7 @@ def right_ideals(self, B=None): sage: B = BrandtModule(1009) sage: Is = B.right_ideals() sage: n = len(Is) - sage: prod(not Is[i].is_equivalent(Is[j]) for i in range(n) for j in range(i)) + sage: prod(not Is[i].is_right_equivalent(Is[j]) for i in range(n) for j in range(i)) 1 """ p = self._smallest_good_prime() @@ -1353,7 +1353,7 @@ def right_ideals(self, B=None): J_theta = tuple(J.theta_series_vector(B)) if J_theta in ideals_theta: for K in ideals_theta[J_theta]: - if J.is_equivalent(K, 0): + if J.is_right_equivalent(K, 0): is_new = False break if is_new: From 094a40f5119c11805f9fb102b09c2ec694d62b42 Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Sat, 3 Feb 2024 02:11:49 +0100 Subject: [PATCH 069/278] Fully integrated #34976 - Modified definiteness check in `.minimal_element()` and `.isomorphism_to`; also added check of the base field in the latter method - Reduced search for a generator in `.is_principal()` to a call to `.minimal_element()` --- src/sage/algebras/quatalg/quaternion_algebra.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index 823bfa02261..a03b2e12208 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -2261,7 +2261,9 @@ def isomorphism_to(self, other, *, conjugator=False): if other.quaternion_algebra() != Q: raise TypeError('not an order in the same quaternion algebra') - if not self.quadratic_form().is_positive_definite(): + if not is_RationalField(Q.base_ring()): + raise NotImplementedError('only implemented for orders in a rational quaternion algebra') + if not Q.is_definite(): raise NotImplementedError('only implemented for definite quaternion orders') if not (self.discriminant() == Q.discriminant() == other.discriminant()): raise NotImplementedError('only implemented for maximal orders') @@ -2779,10 +2781,9 @@ def minimal_element(self): sage: el.reduced_norm() 282 """ - qf = self.quadratic_form() - if not qf.is_positive_definite(): + if not self.quaternion_algebra().is_definite(): raise ValueError('quaternion algebra must be definite') - pariqf = qf.__pari__() + pariqf = self.quadratic_form().__pari__() _,v = pariqf.qfminim(None, None, 1) return sum(ZZ(c)*g for c,g in zip(v, self.basis())) @@ -3260,9 +3261,9 @@ def is_principal(self, certificate=False): if certificate and c == 0: return False, None - # Use Pari's qfminim with flag = 1 to find alpha with N(alpha) = N(I) - _,v = self.quadratic_form().__pari__().qfminim(None, None, 1) - return True, sum(ZZ(c)*g for c,g in zip(v, self.basis())) + # From this point on we know that self is principal, so it suffices to + # find an element of minimal norm in self; see [Piz1980]_, Corollary 1.20. + return True, self.minimal_element() def __contains__(self, x): """ From abb108b6b4616c5d9f695da2b1d53c37bb0b789c Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Sat, 3 Feb 2024 02:53:11 +0100 Subject: [PATCH 070/278] Added check for fractional ideal basis length, removed "local" checks --- src/sage/algebras/quatalg/quaternion_algebra.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index a03b2e12208..3b63c2a98b6 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -2331,6 +2331,8 @@ def __init__(self, Q, basis, left_order=None, right_order=None, check=True): raise TypeError("basis must be a list or tuple") basis = tuple([Q(v) for v in (QQ**4).span([Q(v).coefficient_tuple() for v in basis], ZZ).basis()]) + if len(basis) != 4: + raise ValueError("fractional ideal must have rank 4") self.__left_order = left_order self.__right_order = right_order Ideal_fractional.__init__(self, Q, basis) @@ -2657,7 +2659,7 @@ def basis_matrix(self): def reduced_basis(self): r""" - Let `I` = ``self`` be a rank 4 quaternion ideal in a definite quaternion algebra. + Let `I` = ``self`` be a fractional ideal in a (rational) definite quaternion algebra. This function returns an LLL reduced basis of I. OUTPUT: @@ -2682,9 +2684,6 @@ def reduced_basis(self): sage: I.reduced_basis()[0] 1/2*i + j + 5/2*k """ - if len(self.basis()) < 4: - raise ValueError("basis must have rank 4") - if not self.quaternion_algebra().is_definite(): raise TypeError("The quaternion algebra must be definite") @@ -3252,9 +3251,6 @@ def is_principal(self, certificate=False): raise NotImplementedError('principality test not implemented in' ' indefinite quaternion algebras') - if len(self.basis()) < 4: - raise ValueError("basis must have rank 4") - c = self.theta_series_vector(2)[1] if not certificate: return c != 0 From 57fd4ab7a9ee868188484b9c23c83dbef4f569fd Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 6 Feb 2024 21:49:56 -0800 Subject: [PATCH 071/278] src/sage_docbuild/conf.py: Do not merge inline tabs of adjacent literal blocks --- src/sage_docbuild/conf.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/sage_docbuild/conf.py b/src/sage_docbuild/conf.py index a4bc5955e90..d1416efc8ba 100644 --- a/src/sage_docbuild/conf.py +++ b/src/sage_docbuild/conf.py @@ -833,6 +833,10 @@ def apply(self): from sphinx_inline_tabs._impl import TabContainer parent = node.parent index = parent.index(node) + if isinstance(node.previous_sibling(), TabContainer): + # Make sure not to merge inline tabs for adjacent literal blocks + parent.insert(index, Text('')) + index += 1 parent.remove(node) # Tab for Sage code container = TabContainer("", type="tab", new_set=False) From b9be7ab71db261f14575b7e94fd348ce40999a7b Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Thu, 8 Feb 2024 12:18:36 +0000 Subject: [PATCH 072/278] sage.groups.generic: Fix incorrect identity test --- src/sage/groups/generic.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/sage/groups/generic.py b/src/sage/groups/generic.py index c97028d419a..bc9abe9cc75 100644 --- a/src/sage/groups/generic.py +++ b/src/sage/groups/generic.py @@ -119,7 +119,6 @@ from sage.arith.misc import integer_ceil, integer_floor, xlcm from sage.arith.srange import xsrange -from sage.misc.functional import log from sage.misc.misc_c import prod import sage.rings.integer_ring as integer_ring import sage.rings.integer @@ -171,9 +170,11 @@ def multiple(a, n, operation='*', identity=None, inverse=None, op=None): sage: E = EllipticCurve('389a1') sage: P = E(-1,1) sage: multiple(P, 10, '+') - (645656132358737542773209599489/22817025904944891235367494656 : 525532176124281192881231818644174845702936831/3446581505217248068297884384990762467229696 : 1) + (645656132358737542773209599489/22817025904944891235367494656 : + 525532176124281192881231818644174845702936831/3446581505217248068297884384990762467229696 : 1) sage: multiple(P, -10, '+') - (645656132358737542773209599489/22817025904944891235367494656 : -528978757629498440949529703029165608170166527/3446581505217248068297884384990762467229696 : 1) + (645656132358737542773209599489/22817025904944891235367494656 : + -528978757629498440949529703029165608170166527/3446581505217248068297884384990762467229696 : 1) """ from operator import inv, mul, neg, add @@ -332,11 +333,11 @@ def __init__(self, P, n, P0=None, indexed=False, operation='+', op=None): P0 = P.parent()(0) self.op = add else: - self.op = op if P0 is None: raise ValueError("P0 must be supplied when operation is neither addition nor multiplication") if op is None: raise ValueError("op() must both be supplied when operation is neither addition nor multiplication") + self.op = op self.P = copy(P) self.Q = copy(P0) @@ -458,6 +459,7 @@ def bsgs(a, b, bounds, operation='*', identity=None, inverse=None, op=None): inverse = inv op = mul elif operation in addition_names: + # Should this be replaced with .zero()? With an extra AttributeError handler? identity = a.parent()(0) inverse = neg op = add @@ -469,7 +471,7 @@ def bsgs(a, b, bounds, operation='*', identity=None, inverse=None, op=None): if lb < 0 or ub < lb: raise ValueError("bsgs() requires 0<=lb<=ub") - if a.is_zero() and not b.is_zero(): + if a == identity and b != identity: raise ValueError("no solution in bsgs()") ran = 1 + ub - lb # the length of the interval @@ -1410,6 +1412,7 @@ def order_from_bounds(P, bounds, d=None, operation='+', return order_from_multiple(P, m, operation=operation, check=False) + def has_order(P, n, operation='+'): r""" Generic function to test if a group element `P` has order @@ -1497,8 +1500,8 @@ def _rec(Q, fn): fl = fn[::2] fr = fn[1::2] - l = prod(p**k for p,k in fl) - r = prod(p**k for p,k in fr) + l = prod(p**k for p, k in fl) + r = prod(p**k for p, k in fr) L, R = mult(Q, r), mult(Q, l) return _rec(L, fl) and _rec(R, fr) From 969c37d6913f019685f76ffe9b244b02fa652363 Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Thu, 8 Feb 2024 12:33:51 +0000 Subject: [PATCH 073/278] =?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/rings/polynomial/polynomial_ring.py | 61 -------------------- 1 file changed, 61 deletions(-) diff --git a/src/sage/rings/polynomial/polynomial_ring.py b/src/sage/rings/polynomial/polynomial_ring.py index f74674fb3b7..55dab10d8dd 100644 --- a/src/sage/rings/polynomial/polynomial_ring.py +++ b/src/sage/rings/polynomial/polynomial_ring.py @@ -1375,10 +1375,6 @@ def random_element(self, degree=(-1, 2), monic=False, *args, **kwds): - ``*args, **kwds`` - additional keyword parameters passed on to the ``random_element`` method for the base ring - .. SEEALSO:: - - :meth:`random_monic_element` - EXAMPLES:: sage: R. = ZZ[] @@ -1409,14 +1405,6 @@ def random_element(self, degree=(-1, 2), monic=False, *args, **kwds): sage: while R.random_element(degree=(-1,2), x=-1, y=1) != R.zero(): ....: pass - It is possible to sample a monic polynomial, but it is preferable to - use :meth:`random_monic_element`:: - - sage: R.random_element(degree=(-1, 5), monic=True).is_monic() - True - sage: R.random_monic_element(degree=(-1, 5)).is_monic() - True - Note that if the degree range includes `-1` and ``monic`` is set, it is silently ignored, as `0` is not a monic polynomial:: @@ -1455,8 +1443,6 @@ def random_element(self, degree=(-1, 2), monic=False, *args, **kwds): sage: 0 in [R.random_element(degree=(-1, 2), monic=True) for _ in range(500)] False - sage: 0 in [R.random_monic_element(degree=(-1, 2)) for _ in range(500)] - False Testing error handling:: @@ -1541,53 +1527,6 @@ def random_element(self, degree=(-1, 2), monic=False, *args, **kwds): return self(coefs) - def random_monic_element(self, degree=(0, 2), *args, **kwargs): - r""" - Return a random monic polynomial of given degree (bounds). - - Calls :meth:`random_element` with ``monic=True``. - - INPUT: - - - ``degree`` - optional integer for fixing the degree - or a tuple of minimum and maximum degrees. By default set to - ``(0, 2)`` - - - ``zero`` - boolean, if set the algorithm may return `0`, even though - it is not a monic polynomial. By default set to ``False`` - - - ``*args, **kwds`` - Passed on to the ``random_element`` method for - the base ring - - .. SEEALSO:: - - :meth:`random_element` - - EXAMPLES:: - - sage: R. = GF(3)[] - sage: R.random_monic_element() # random - x^2 + x + 1 - sage: all(R.random_monic_element().is_monic() for _ in range(100)) - True - sage: all(R.random_monic_element(degree=(-1, 1)).is_monic() for _ in range(100)) - True - - TESTS: - - There are 7 monic polynomials of degree not exceeding 2 over GF(2). We - apply the chi-square test:: - - sage: from collections import Counter - sage: from scipy.stats import chisquare - sage: N = 10^5 - sage: R. = GF(2)[] - sage: cnts = Counter(R.random_monic_element() for _ in range(N)) - sage: chisquare(list(cnts.values()), [N / 7] * 7).pvalue < 0.1 - False - """ - return self.random_element(degree=degree, monic=True, *args, **kwargs) - def _monics_degree(self, of_degree): """ Refer to monics() for full documentation. From d38462f84712f3c40e876422994571ceb2886866 Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Thu, 8 Feb 2024 13:22:38 +0000 Subject: [PATCH 074/278] sage.groups.generic: Allow generic operation in `order_from_multiple` --- src/sage/groups/generic.py | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/sage/groups/generic.py b/src/sage/groups/generic.py index bc9abe9cc75..5eabca47723 100644 --- a/src/sage/groups/generic.py +++ b/src/sage/groups/generic.py @@ -481,7 +481,7 @@ def bsgs(a, b, bounds, operation='*', identity=None, inverse=None, op=None): if ran < 30: # use simple search for small ranges d = c -# for i,d in multiples(a,ran,c,indexed=True,operation=operation): + # for i,d in multiples(a,ran,c,indexed=True,operation=operation): for i0 in range(ran): i = lb + i0 if identity == d: # identity == b^(-1)*a^i, so return i @@ -1189,7 +1189,7 @@ def linear_relation(P, Q, operation='+', identity=None, inverse=None, op=None): def order_from_multiple(P, m, plist=None, factorization=None, check=True, - operation='+'): + operation='+', identity=None, inverse=None, op=None): r""" Generic function to find order of a group element given a multiple of its order. @@ -1259,14 +1259,23 @@ def order_from_multiple(P, m, plist=None, factorization=None, check=True, elif operation in addition_names: identity = P.parent()(0) else: - raise ValueError("unknown group operation") + if identity is None or inverse is None or op is None: + raise ValueError("identity, inverse and operation must all be specified") + + def _multiple(A, B): + return multiple(A, + B, + operation=operation, + identity=identity, + inverse=inverse, + op=op) if P == identity: return Z.one() M = Z(m) if check: - assert multiple(P, M, operation=operation) == identity + assert _multiple(P, M) == identity if factorization: F = factorization @@ -1295,7 +1304,7 @@ def _order_from_multiple_helper(Q, L, S): p, e = L[0] e0 = 0 while (Q != identity) and (e0 < e - 1): - Q = multiple(Q, p, operation=operation) + Q = _multiple(Q, p) e0 += 1 if Q != identity: e0 += 1 @@ -1314,12 +1323,8 @@ def _order_from_multiple_helper(Q, L, S): L2 = L[k:] # recursive calls o1 = _order_from_multiple_helper( - multiple(Q, prod([p**e for p, e in L2]), operation), - L1, - sum_left) - o2 = _order_from_multiple_helper(multiple(Q, o1, operation), - L2, - S - sum_left) + _multiple(Q, prod([p**e for p, e in L2])), L1, sum_left) + o2 = _order_from_multiple_helper(_multiple(Q, o1), L2, S - sum_left) return o1 * o2 return _order_from_multiple_helper(P, F, sage.functions.log.log(float(M))) From bbb06493647652541eda158e8d63839a31a2b7e3 Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Thu, 8 Feb 2024 13:51:09 +0000 Subject: [PATCH 075/278] =?UTF-8?q?Docs=20=F0=9F=94=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/sage/groups/generic.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/sage/groups/generic.py b/src/sage/groups/generic.py index 5eabca47723..52d00429115 100644 --- a/src/sage/groups/generic.py +++ b/src/sage/groups/generic.py @@ -1194,6 +1194,8 @@ def order_from_multiple(P, m, plist=None, factorization=None, check=True, Generic function to find order of a group element given a multiple of its order. + See :meth:`bsgs` for full explanation of the inputs. + INPUT: - ``P`` -- a Sage object which is a group element; @@ -1203,9 +1205,12 @@ def order_from_multiple(P, m, plist=None, factorization=None, check=True, really is a multiple of the order; - ``factorization`` -- the factorization of ``m``, or ``None`` in which case this function will need to factor ``m``; - - ``plist`` -- a list of the prime factors of ``m``, or ``None`` - kept for compatibility only, + - ``plist`` -- a list of the prime factors of ``m``, or ``None``. Kept for compatibility only, prefer the use of ``factorization``; - - ``operation`` -- string: ``'+'`` (default) or ``'*'``. + - ``operation`` -- string: ``'+'`` (default), ``'*'`` or ``None``; + - ``identity`` -- the identity element of the group; + - ``inverse()`` -- function of 1 argument ``x``, returning inverse of ``x``; + - ``op()`` - function of 2 arguments ``x``, ``y`` returning ``x*y`` in the group. .. note:: From ccfdd938dfb330eda81716a1bdca020983ba7ab0 Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Fri, 9 Feb 2024 16:46:22 +0000 Subject: [PATCH 076/278] =?UTF-8?q?refactor=20`.random=5Felement`=20?= =?UTF-8?q?=F0=9F=93=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/sage/rings/polynomial/polynomial_ring.py | 61 +++++++++----------- 1 file changed, 27 insertions(+), 34 deletions(-) diff --git a/src/sage/rings/polynomial/polynomial_ring.py b/src/sage/rings/polynomial/polynomial_ring.py index 55dab10d8dd..a56689ae0b2 100644 --- a/src/sage/rings/polynomial/polynomial_ring.py +++ b/src/sage/rings/polynomial/polynomial_ring.py @@ -1318,7 +1318,7 @@ def monomial(self, exponent): sage: R.monomial(m.degree()) == m True """ - return self({exponent:self.base_ring().one()}) + return self({exponent: self.base_ring().one()}) def krull_dimension(self): """ @@ -1369,7 +1369,7 @@ def random_element(self, degree=(-1, 2), monic=False, *args, **kwds): - ``degree`` - (default: ``(-1, 2)``) integer for fixing the degree or a tuple of minimum and maximum degrees - - ``monic`` - optional boolean to indicate whether the sampled + - ``monic`` - boolean (optional); indicate whether the sampled polynomial should be monic - ``*args, **kwds`` - additional keyword parameters passed on to the @@ -1379,8 +1379,6 @@ def random_element(self, degree=(-1, 2), monic=False, *args, **kwds): sage: R. = ZZ[] sage: f = R.random_element(10, x=5, y=10) - sage: f # random - 5*x^10 + 6*x^9 + 5*x^8 + 8*x^7 + 8*x^6 + 5*x^5 + 7*x^4 + 8*x^3 + 6*x^2 + 9*x + 6 sage: f.degree() 10 sage: f.parent() is R @@ -1391,13 +1389,14 @@ def random_element(self, degree=(-1, 2), monic=False, *args, **kwds): 6 If a tuple of two integers is given for the ``degree`` argument, a polynomial is chosen - uniformly among all polynomials with degree between them:: + among all polynomials with degree between them. If the base ring is uniform, so is this + method:: - sage: R.random_element(degree=(0, 8)).degree() in range(0, 9) + sage: R.random_element(degree=(0, 4)).degree() in range(0, 5) True - sage: found = [False]*9 + sage: found = [False]*5 sage: while not all(found): - ....: found[R.random_element(degree=(0, 8)).degree()] = True + ....: found[R.random_element(degree=(0, 4)).degree()] = True Note that the zero polynomial has degree `-1`, so if you want to consider it set the minimum degree to `-1`:: @@ -1405,8 +1404,8 @@ def random_element(self, degree=(-1, 2), monic=False, *args, **kwds): sage: while R.random_element(degree=(-1,2), x=-1, y=1) != R.zero(): ....: pass - Note that if the degree range includes `-1` and ``monic`` is set, it is - silently ignored, as `0` is not a monic polynomial:: + Monic polynomials are chosen among all monic polynomials with degree between the given + ``degree`` argument:: sage: all(R.random_element(degree=(-1, 1), monic=True).is_monic() for _ in range(10^3)) True @@ -1471,8 +1470,6 @@ def random_element(self, degree=(-1, 2), monic=False, *args, **kwds): degree = (degree, degree) if degree[0] <= -2: - # This error has been removed in issue #37118. - # raise ValueError("degree should be an integer greater or equal than -1") degree = (-1, degree[1]) # If the coefficient range only contains 0, then @@ -1484,20 +1481,26 @@ def random_element(self, degree=(-1, 2), monic=False, *args, **kwds): else: raise ValueError("No polynomial of degree >= 0 has all coefficients zero") - degree_lb = degree[0] if degree == (-1, -1): return self.zero() # If `monic` is set, zero should be ignored if degree[0] == -1 and monic: + if degree[1] == -1: + raise ValueError("the maximum degree of monic polynomials needs to be at least 0") + if degree[1] == 0: + return self.one() degree = (0, degree[1]) - while True: - # Pick random coefficients - end = degree[1] - coefs = [None] * (degree[1] + 1) - nonzero = False + # Pick random coefficients + end = degree[1] + if degree[0] == -1: + return self([R.random_element(*args, **kwds) for _ in range(end + 1)]) + nonzero = False + coefs = [None] * (end + 1) + + while not nonzero: # Pick leading coefficients, if `monic` is set it's handle here. if monic: for i in range(degree[1] - degree[0] + 1): @@ -1511,21 +1514,11 @@ def random_element(self, degree=(-1, 2), monic=False, *args, **kwds): coefs[end - i] = R.random_element(*args, **kwds) nonzero |= not coefs[end - i].is_zero() - # Leading terms must be nonzero - if not nonzero: - continue - - # Now we pick the remaining coefficients. Zeros still should be - # tracked to handle `has_zero`. - for i in range(degree[1] - degree[0] + 1, degree[1] + 1): - coefs[end - i] = R.random_element(*args, **kwds) - - # If we don't want zero (not has_zero), but coefs is zero (not - # nonzero), then reject - if degree_lb == -1 and not nonzero: - continue + # Now we pick the remaining coefficients. + for i in range(degree[1] - degree[0] + 1, degree[1] + 1): + coefs[end - i] = R.random_element(*args, **kwds) - return self(coefs) + return self(coefs) def _monics_degree(self, of_degree): """ @@ -2471,8 +2464,8 @@ def lagrange_polynomial(self, points, algorithm="divided_difference", previous_r # P += (F[i] * prod) # return P - # using Neville's method for recursively generating the - # Lagrange interpolation polynomial +# using Neville's method for recursively generating the +# Lagrange interpolation polynomial elif algorithm == "neville": if previous_row is None: previous_row = [] From 1b09de02ba1aa25703f1e7e10a74236566467127 Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Fri, 9 Feb 2024 16:51:33 +0000 Subject: [PATCH 077/278] fix input docs format --- src/sage/rings/polynomial/polynomial_ring.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/sage/rings/polynomial/polynomial_ring.py b/src/sage/rings/polynomial/polynomial_ring.py index a56689ae0b2..b5044d6fd0d 100644 --- a/src/sage/rings/polynomial/polynomial_ring.py +++ b/src/sage/rings/polynomial/polynomial_ring.py @@ -1366,13 +1366,13 @@ def random_element(self, degree=(-1, 2), monic=False, *args, **kwds): INPUT: - - ``degree`` - (default: ``(-1, 2)``) integer for fixing the degree or + - ``degree`` -- (default: ``(-1, 2)``) integer for fixing the degree or a tuple of minimum and maximum degrees - - ``monic`` - boolean (optional); indicate whether the sampled + - ``monic`` -- boolean (optional); indicate whether the sampled polynomial should be monic - - ``*args, **kwds`` - additional keyword parameters passed on to the + - ``*args, **kwds`` -- additional keyword parameters passed on to the ``random_element`` method for the base ring EXAMPLES:: @@ -1638,11 +1638,11 @@ def polynomials( self, of_degree=None, max_degree=None ): INPUT: Pass exactly one of: - - ``max_degree`` - an int; the iterator will generate + - ``max_degree`` -- an int; the iterator will generate all polynomials which have degree less than or equal to ``max_degree`` - - ``of_degree`` - an int; the iterator will generate + - ``of_degree`` -- an int; the iterator will generate all polynomials which have degree ``of_degree`` OUTPUT: an iterator @@ -1704,11 +1704,11 @@ def monics( self, of_degree=None, max_degree=None ): INPUT: Pass exactly one of: - - ``max_degree`` - an int; the iterator will generate + - ``max_degree`` -- an int; the iterator will generate all monic polynomials which have degree less than or equal to ``max_degree`` - - ``of_degree`` - an int; the iterator will generate + - ``of_degree`` -- an int; the iterator will generate all monic polynomials which have degree ``of_degree`` @@ -1785,7 +1785,7 @@ def quotient_by_principal_ideal(self, f, names=None, **kwds): INPUT: - - ``f`` - either a polynomial in ``self``, or a principal + - ``f`` -- either a polynomial in ``self``, or a principal ideal of ``self``. - further named arguments that are passed to the quotient constructor. From 4e1378fd049fc1bbe480709e09ffe426a2a00214 Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Fri, 9 Feb 2024 16:59:23 +0000 Subject: [PATCH 078/278] I don't know what the code is trying to do here --- src/sage/rings/tests.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sage/rings/tests.py b/src/sage/rings/tests.py index 3eed7e1aa31..289c002d543 100644 --- a/src/sage/rings/tests.py +++ b/src/sage/rings/tests.py @@ -474,9 +474,9 @@ def test_karatsuba_multiplication(base_ring, maxdeg1, maxdeg2, R = PolynomialRing(base_ring, 'x') if verbose: print("test_karatsuba_multiplication: ring={}, threshold={}".format(R, 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) + for _ in range(numtests): + f = R.random_element(randint(0, maxdeg1), False, *base_ring_random_elt_args) + g = R.random_element(randint(0, maxdeg2), False, *base_ring_random_elt_args) if verbose: print(" ({})*({})".format(f, g)) if ref_mul(f, g) - f._mul_karatsuba(g, threshold) != 0: From 97ad0a5e9e44922bd46d49d999b03ffa82228138 Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Fri, 9 Feb 2024 17:05:11 +0000 Subject: [PATCH 079/278] fix remaining doctests --- src/sage/crypto/lattice.py | 24 ++++++++++++------------ src/sage/crypto/lwe.py | 4 ++-- src/sage/misc/randstate.pyx | 28 ++++++++++++++-------------- 3 files changed, 28 insertions(+), 28 deletions(-) diff --git a/src/sage/crypto/lattice.py b/src/sage/crypto/lattice.py index ce6c63f66f3..513730ff89f 100644 --- a/src/sage/crypto/lattice.py +++ b/src/sage/crypto/lattice.py @@ -112,10 +112,10 @@ def gen_lattice(type='modular', n=4, m=8, q=11, seed=None, [ 0 11 0 0 0 0 0 0] [ 0 0 11 0 0 0 0 0] [ 0 0 0 11 0 0 0 0] - [-2 -3 -3 4 1 0 0 0] - [ 4 -2 -3 -3 0 1 0 0] - [-3 4 -2 -3 0 0 1 0] - [-3 -3 4 -2 0 0 0 1] + [-3 -3 -2 4 1 0 0 0] + [ 4 -3 -3 -2 0 1 0 0] + [-2 4 -3 -3 0 0 1 0] + [-3 -2 4 -3 0 0 0 1] Ideal bases also work with polynomials:: @@ -125,10 +125,10 @@ def gen_lattice(type='modular', n=4, m=8, q=11, seed=None, [ 0 11 0 0 0 0 0 0] [ 0 0 11 0 0 0 0 0] [ 0 0 0 11 0 0 0 0] - [ 1 4 -3 3 1 0 0 0] - [ 3 1 4 -3 0 1 0 0] - [-3 3 1 4 0 0 1 0] - [ 4 -3 3 1 0 0 0 1] + [-3 4 1 4 1 0 0 0] + [ 4 -3 4 1 0 1 0 0] + [ 1 4 -3 4 0 0 1 0] + [ 4 1 4 -3 0 0 0 1] Cyclotomic bases with n=2^k are SWIFFT bases:: @@ -137,10 +137,10 @@ def gen_lattice(type='modular', n=4, m=8, q=11, seed=None, [ 0 11 0 0 0 0 0 0] [ 0 0 11 0 0 0 0 0] [ 0 0 0 11 0 0 0 0] - [-2 -3 -3 4 1 0 0 0] - [-4 -2 -3 -3 0 1 0 0] - [ 3 -4 -2 -3 0 0 1 0] - [ 3 3 -4 -2 0 0 0 1] + [-3 -3 -2 4 1 0 0 0] + [-4 -3 -3 -2 0 1 0 0] + [ 2 -4 -3 -3 0 0 1 0] + [ 3 2 -4 -3 0 0 0 1] Dual modular bases are related to Regev's famous public-key encryption [Reg2005]_:: diff --git a/src/sage/crypto/lwe.py b/src/sage/crypto/lwe.py index 25bb2a3fb47..40281916b19 100644 --- a/src/sage/crypto/lwe.py +++ b/src/sage/crypto/lwe.py @@ -670,7 +670,7 @@ def __init__(self, ringlwe): sage: lwe = RingLWEConverter(RingLWE(16, 257, D, secret_dist='uniform')) sage: set_random_seed(1337) sage: lwe() - ((32, 216, 3, 125, 58, 197, 171, 43), ...) + ((171, 197, 58, 125, 3, 216, 32, 130), ...) """ self.ringlwe = ringlwe self._i = 0 @@ -686,7 +686,7 @@ def __call__(self): sage: lwe = RingLWEConverter(RingLWE(16, 257, D, secret_dist='uniform')) sage: set_random_seed(1337) sage: lwe() - ((32, 216, 3, 125, 58, 197, 171, 43), ...) + ((171, 197, 58, 125, 3, 216, 32, 130), ...) """ R_q = self.ringlwe.R_q diff --git a/src/sage/misc/randstate.pyx b/src/sage/misc/randstate.pyx index 5c883c928b4..a4b23fb97b2 100644 --- a/src/sage/misc/randstate.pyx +++ b/src/sage/misc/randstate.pyx @@ -56,22 +56,22 @@ results of these random number generators reproducible. :: sage: set_random_seed(0) sage: print(rtest()) - (303, -0.266166246380421, 1/6, (1,2), [ 0, 1, 1, 0, 0 ], 265625921, 79302, 0.2450652680687958) + (303, -0.266166246380421, 1/2*x^2 - 1/95*x - 1/2, (1,3), [ 1, 0, 0, 1, 1 ], 265625921, 5842, 0.9661911734708414) sage: set_random_seed(1) sage: print(rtest()) - (978, 0.0557699430711638, -1/8*x^2 - 1/2*x + 1/2, (1,2,3), [ 1, 0, 0, 0, 1 ], 807447831, 23865, 0.6170498912488264) + (978, 0.0557699430711638, -3*x^2 - 1/12, (1,2), [ 0, 0, 1, 1, 0 ], 807447831, 29982, 0.8335077654199736) sage: set_random_seed(2) sage: print(rtest()) - (207, -0.0141049486533456, 0, (1,3)(4,5), [ 1, 0, 1, 1, 1 ], 1642898426, 16190, 0.9343331114872127) + (207, -0.0141049486533456, 4*x^2 + 1/2, (1,2)(4,5), [ 1, 0, 0, 1, 1 ], 1642898426, 41662, 0.19982565117278328) sage: set_random_seed(0) sage: print(rtest()) - (303, -0.266166246380421, 1/6, (1,2), [ 0, 1, 1, 0, 0 ], 265625921, 79302, 0.2450652680687958) + (303, -0.266166246380421, 1/2*x^2 - 1/95*x - 1/2, (1,3), [ 1, 0, 0, 1, 1 ], 265625921, 5842, 0.9661911734708414) sage: set_random_seed(1) sage: print(rtest()) - (978, 0.0557699430711638, -1/8*x^2 - 1/2*x + 1/2, (1,2,3), [ 1, 0, 0, 0, 1 ], 807447831, 23865, 0.6170498912488264) + (978, 0.0557699430711638, -3*x^2 - 1/12, (1,2), [ 0, 0, 1, 1, 0 ], 807447831, 29982, 0.8335077654199736) sage: set_random_seed(2) sage: print(rtest()) - (207, -0.0141049486533456, 0, (1,3)(4,5), [ 1, 0, 1, 1, 1 ], 1642898426, 16190, 0.9343331114872127) + (207, -0.0141049486533456, 4*x^2 + 1/2, (1,2)(4,5), [ 1, 0, 0, 1, 1 ], 1642898426, 41662, 0.19982565117278328) Once we've set the random number seed, we can check what seed was used. (This is not the current random number state; it does not change when @@ -81,7 +81,7 @@ random numbers are generated.) :: sage: initial_seed() 12345 sage: print(rtest()) - (720, -0.612180244315804, 0, (1,3), [ 1, 0, 1, 1, 0 ], 1911581957, 65175, 0.8043027951758298) + (720, -0.612180244315804, x^2 - x, (1,2,3), [ 1, 0, 0, 0, 1 ], 1911581957, 27093, 0.9205331599518184) sage: initial_seed() 12345 @@ -216,9 +216,9 @@ that you get without intervening ``with seed``. :: sage: set_random_seed(0) sage: r1 = rtest(); print(r1) - (303, -0.266166246380421, 1/6, (1,2), [ 0, 1, 1, 0, 0 ], 265625921, 79302, 0.2450652680687958) + (303, -0.266166246380421, 1/2*x^2 - 1/95*x - 1/2, (1,3), [ 1, 0, 0, 1, 1 ], 265625921, 5842, 0.9661911734708414) sage: r2 = rtest(); print(r2) - (443, 0.185001351421963, -2, (1,3), [ 0, 0, 1, 1, 0 ], 53231108, 8171, 0.28363811590618193) + (105, 0.642309615982449, -x^2 - x - 6, (1,3)(4,5), [ 1, 0, 0, 0, 1 ], 53231108, 77132, 0.001767155077382232) We get slightly different results with an intervening ``with seed``. :: @@ -226,9 +226,9 @@ We get slightly different results with an intervening ``with seed``. :: sage: r1 == rtest() True sage: with seed(1): rtest() - (978, 0.0557699430711638, -1/8*x^2 - 1/2*x + 1/2, (1,2,3), [ 1, 0, 0, 0, 1 ], 807447831, 23865, 0.6170498912488264) + (978, 0.0557699430711638, -3*x^2 - 1/12, (1,2), [ 0, 0, 1, 1, 0 ], 807447831, 29982, 0.8335077654199736) sage: r2m = rtest(); r2m - (443, 0.185001351421963, -2, (1,3), [ 0, 0, 1, 1, 0 ], 53231108, 51295, 0.28363811590618193) + (105, 0.642309615982449, -x^2 - x - 6, (1,3)(4,5), [ 1, 0, 0, 0, 1 ], 53231108, 40267, 0.001767155077382232) sage: r2m == r2 False @@ -245,8 +245,8 @@ case, as we see in this example:: sage: with seed(1): ....: print(rtest()) ....: print(rtest()) - (978, 0.0557699430711638, -1/8*x^2 - 1/2*x + 1/2, (1,2,3), [ 1, 0, 0, 0, 1 ], 807447831, 23865, 0.6170498912488264) - (181, 0.607995392046754, -x + 1/2, (2,3)(4,5), [ 1, 0, 0, 1, 1 ], 1010791326, 9693, 0.5691716786307407) + (978, 0.0557699430711638, -3*x^2 - 1/12, (1,2), [ 0, 0, 1, 1, 0 ], 807447831, 29982, 0.8335077654199736) + (138, -0.0404945051288503, 2*x - 24, (1,2,3), [ 1, 1, 0, 1, 1 ], 1010791326, 91360, 0.0033332230808060803) sage: r2m == rtest() True @@ -258,7 +258,7 @@ NTL random numbers were generated inside the ``with seed``. True sage: with seed(1): ....: rtest() - (978, 0.0557699430711638, -1/8*x^2 - 1/2*x + 1/2, (1,2,3), [ 1, 0, 0, 0, 1 ], 807447831, 23865, 0.6170498912488264) + (978, 0.0557699430711638, -3*x^2 - 1/12, (1,2), [ 0, 0, 1, 1, 0 ], 807447831, 29982, 0.8335077654199736) sage: r2m == rtest() True From d5215d4985fe6b23099511ff52d5b85ebe893726 Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Sat, 10 Feb 2024 17:28:48 +0000 Subject: [PATCH 080/278] =?UTF-8?q?fix=20docs=20wording=20+=20uniformity?= =?UTF-8?q?=20test=20=F0=9F=93=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/sage/rings/polynomial/polynomial_ring.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/sage/rings/polynomial/polynomial_ring.py b/src/sage/rings/polynomial/polynomial_ring.py index b5044d6fd0d..7d9c456b1c2 100644 --- a/src/sage/rings/polynomial/polynomial_ring.py +++ b/src/sage/rings/polynomial/polynomial_ring.py @@ -1389,8 +1389,8 @@ def random_element(self, degree=(-1, 2), monic=False, *args, **kwds): 6 If a tuple of two integers is given for the ``degree`` argument, a polynomial is chosen - among all polynomials with degree between them. If the base ring is uniform, so is this - method:: + among all polynomials with degree between them. If the base ring is uniform, then this is + also sampled uniformly:: sage: R.random_element(degree=(0, 4)).degree() in range(0, 5) True @@ -1454,6 +1454,16 @@ def random_element(self, degree=(-1, 2), monic=False, *args, **kwds): Traceback (most recent call last): ... ValueError: maximum degree (=-2) must be at least -1 + + Testing uniformity:: + + sage: from collections import Counter + sage: R = GF(3)["x"] + sage: samples = [R.random_element(degree=(-1, 2)) for _ in range(27 * 1000)] # long time + sage: assert all(750 <= f <= 1250 for f in Counter(samples).values()) + + sage: samples = [R.random_element(degree=(-1, 2), monic=True) for _ in range(13 * 1000)] # long time + sage: assert all(750 <= f <= 1250 for f in Counter(samples).values()) """ R = self.base_ring() From 786d7f0065bf507dc22cd21c740737e4bd513bc9 Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Sat, 10 Feb 2024 17:33:02 +0000 Subject: [PATCH 081/278] Fix doctests As I noted in the discussion under :issue:`37118`, it seems that the output of `sage -t` and the REPL differ, so I am not sure if this commit is correct. --- src/sage/rings/tests.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/sage/rings/tests.py b/src/sage/rings/tests.py index 289c002d543..95896889970 100644 --- a/src/sage/rings/tests.py +++ b/src/sage/rings/tests.py @@ -428,16 +428,16 @@ def test_karatsuba_multiplication(base_ring, maxdeg1, maxdeg2, sage: from sage.rings.tests import test_karatsuba_multiplication sage: test_karatsuba_multiplication(ZZ, 6, 5, verbose=True, seed=42) test_karatsuba_multiplication: ring=Univariate Polynomial Ring in x over Integer Ring, threshold=2 - (2*x^6 - x^5 - x^4 - 3*x^3 + 4*x^2 + 4*x + 1)*(4*x^4 + x^3 - 2*x^2 - 20*x + 3) - (16*x^2)*(-41*x + 1) - (x^6 + 2*x^5 + 8*x^4 - x^3 + x^2 + x)*(-x^2 - 4*x + 3) - (-x^3 - x - 8)*(-1) - (x - 1)*(-x^5 + 3*x^4 - x^3 + 2*x + 1) - (x^3 + x^2 + x + 1)*(4*x^3 + 76*x^2 - x - 1) - (x^6 - 5*x^4 - x^3 + 6*x^2 + 1)*(5*x^2 - x + 4) - (3*x - 2)*(x - 1) - (21)*(14*x^5 - x^2 + 4*x + 1) - (12*x^5 - 12*x^2 + 2*x + 1)*(26*x^4 + x^3 + 1) + (x^6 + 4*x^5 + 4*x^4 - 3*x^3 - x^2 - x)*(2*x^4 + 3*x^3 - 20*x^2 - 2*x + 1) + (4*x^5 + 16*x^2 + x - 41)*(x^2 + x - 1) + (8*x^2 + 2*x + 1)*(3) + (-4*x - 1)*(-8*x^2 - x) + (-x^6 - x^3 - x^2 + x + 1)*(2*x^3 - x + 3) + (-x^2 + x + 1)*(x^4 + x^3 - x^2 - x + 76) + (4*x^3 + x^2 + 6)*(-x^2 - 5*x) + (x + 4)*(-x + 5) + (-2*x)*(3*x^2 - x) + (x^6 + 21*x^5 + x^4 + 4*x^3 - x^2)*(14*x^4 + x^3 + 2*x^2 - 12*x) Test Karatsuba multiplication of polynomials of small degree over some common rings:: From 683513bc88e8e40bb36d63072c4eba55c499caea Mon Sep 17 00:00:00 2001 From: RuchitJagodara Date: Sun, 11 Feb 2024 01:10:28 +0530 Subject: [PATCH 082/278] Fixed the problem with direct making a permutation from a permutation group element --- src/sage/combinat/permutation.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/sage/combinat/permutation.py b/src/sage/combinat/permutation.py index 92e187287fd..0bdbacedf66 100644 --- a/src/sage/combinat/permutation.py +++ b/src/sage/combinat/permutation.py @@ -7092,7 +7092,17 @@ def _element_constructor_(self, x, check=True): [1, 4, 5, 2, 3, 6] sage: Permutations(6)(x) # known bug [1, 4, 5, 2, 3, 6] + + check if :trac:`37284` is fixed:: + + sage: S5 = SymmetricGroup(5) + sage: P5 = Permutations(5) + sage: p = S5.list()[3] + sage: P5(p) + [4, 5, 1, 2, 3] """ + if not isinstance(x, Permutation): + x = Permutation(x) if len(x) < self.n: x = list(x) + list(range(len(x) + 1, self.n + 1)) return self.element_class(self, x, check=check) From 90971f0b1256015d5692fd6bddfdca4edfe30f6f Mon Sep 17 00:00:00 2001 From: RuchitJagodara Date: Sun, 11 Feb 2024 01:48:16 +0530 Subject: [PATCH 083/278] Made the changes efficient --- 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 0bdbacedf66..fd82d0ab75d 100644 --- a/src/sage/combinat/permutation.py +++ b/src/sage/combinat/permutation.py @@ -7102,7 +7102,7 @@ def _element_constructor_(self, x, check=True): [4, 5, 1, 2, 3] """ if not isinstance(x, Permutation): - x = Permutation(x) + x = x.domain() if len(x) < self.n: x = list(x) + list(range(len(x) + 1, self.n + 1)) return self.element_class(self, x, check=check) From 0fae8c5d87321f8de337e5d9e6724b2b44f5e2a1 Mon Sep 17 00:00:00 2001 From: grhkm21 <83517584+grhkm21@users.noreply.github.com> Date: Sun, 11 Feb 2024 04:36:50 +0000 Subject: [PATCH 084/278] Update src/sage/groups/generic.py Co-authored-by: Lorenz Panny <84067835+yyyyx4@users.noreply.github.com> --- src/sage/groups/generic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/groups/generic.py b/src/sage/groups/generic.py index 52d00429115..24c4a1b0431 100644 --- a/src/sage/groups/generic.py +++ b/src/sage/groups/generic.py @@ -481,7 +481,7 @@ def bsgs(a, b, bounds, operation='*', identity=None, inverse=None, op=None): if ran < 30: # use simple search for small ranges d = c - # for i,d in multiples(a,ran,c,indexed=True,operation=operation): + # for i,d in multiples(a,ran,c,indexed=True,operation=operation): for i0 in range(ran): i = lb + i0 if identity == d: # identity == b^(-1)*a^i, so return i From cb1a79b176867d379200f3bba474671c1f2ab064 Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Sun, 11 Feb 2024 05:47:55 +0000 Subject: [PATCH 085/278] use `.zero()` instead of (0) --- src/sage/groups/generic.py | 16 ++++++++-------- src/sage/schemes/curves/point.py | 2 +- src/sage/schemes/generic/homset.py | 8 +++++++- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/sage/groups/generic.py b/src/sage/groups/generic.py index 24c4a1b0431..24fd776ddc8 100644 --- a/src/sage/groups/generic.py +++ b/src/sage/groups/generic.py @@ -183,7 +183,7 @@ def multiple(a, n, operation='*', identity=None, inverse=None, op=None): inverse = inv op = mul elif operation in addition_names: - identity = a.parent()(0) + identity = a.parent().zero() inverse = neg op = add else: @@ -330,7 +330,7 @@ def __init__(self, P, n, P0=None, indexed=False, operation='+', op=None): self.op = mul elif operation in addition_names: if P0 is None: - P0 = P.parent()(0) + P0 = P.parent().zero() self.op = add else: if P0 is None: @@ -443,7 +443,7 @@ def bsgs(a, b, bounds, operation='*', identity=None, inverse=None, op=None): This will return a multiple of the order of P:: - sage: bsgs(P, P.parent()(0), Hasse_bounds(F.order()), operation='+') # needs sage.rings.finite_rings sage.schemes + sage: bsgs(P, P.parent().zero(), Hasse_bounds(F.order()), operation='+') # needs sage.rings.finite_rings sage.schemes 69327408 AUTHOR: @@ -460,7 +460,7 @@ def bsgs(a, b, bounds, operation='*', identity=None, inverse=None, op=None): op = mul elif operation in addition_names: # Should this be replaced with .zero()? With an extra AttributeError handler? - identity = a.parent()(0) + identity = a.parent().zero() inverse = neg op = add else: @@ -1010,7 +1010,7 @@ def discrete_log_lambda(a, base, bounds, operation='*', identity=None, inverse=N This will return a multiple of the order of P:: - sage: discrete_log_lambda(P.parent()(0), P, Hasse_bounds(F.order()), # needs sage.rings.finite_rings sage.schemes + sage: discrete_log_lambda(P.parent().zero(), P, Hasse_bounds(F.order()), # needs sage.rings.finite_rings sage.schemes ....: operation='+') 69327408 @@ -1262,7 +1262,7 @@ def order_from_multiple(P, m, plist=None, factorization=None, check=True, if operation in multiplication_names: identity = P.parent().one() elif operation in addition_names: - identity = P.parent()(0) + identity = P.parent().zero() else: if identity is None or inverse is None or op is None: raise ValueError("identity, inverse and operation must all be specified") @@ -1401,7 +1401,7 @@ def order_from_bounds(P, bounds, d=None, operation='+', identity = P.parent().one() elif operation in addition_names: op = add - identity = P.parent()(0) + identity = P.parent().zero() else: if op is None: raise ValueError("operation and identity must be specified") @@ -1581,7 +1581,7 @@ def merge_points(P1, P2, operation='+', identity = g1.parent().one() elif operation in addition_names: op = add - identity = g1.parent()(0) + identity = g1.parent().zero() else: if op is None: raise ValueError("operation and identity must be specified") diff --git a/src/sage/schemes/curves/point.py b/src/sage/schemes/curves/point.py index dcc78724497..373dc6ce19f 100644 --- a/src/sage/schemes/curves/point.py +++ b/src/sage/schemes/curves/point.py @@ -451,7 +451,7 @@ class IntegralAffineCurvePoint_finite_field(IntegralAffineCurvePoint): class IntegralAffinePlaneCurvePoint(IntegralAffineCurvePoint, AffinePlaneCurvePoint_field): """ - Point of an integral affine plane curve over a finite field. + Point of an integral affine plane curve. """ pass diff --git a/src/sage/schemes/generic/homset.py b/src/sage/schemes/generic/homset.py index a9a0f0735df..54b89416125 100644 --- a/src/sage/schemes/generic/homset.py +++ b/src/sage/schemes/generic/homset.py @@ -656,7 +656,7 @@ def _element_constructor_(self, *v, **kwds): """ if len(v) == 1: v = v[0] - return self.codomain()._point(self, v, **kwds) + return self.extended_codomain()._point(self, v, **kwds) def extended_codomain(self): """ @@ -693,6 +693,12 @@ def extended_codomain(self): self._extended_codomain = X return X + def zero(self): + """ + Return the identity of the codomain with extended base, if necessary. + """ + return self.extended_codomain().zero() + def _repr_(self): """ Return a string representation of ``self``. From 778cad83bcdb270654b3fbfc8c0589c78744bcc7 Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Sun, 11 Feb 2024 05:53:01 +0000 Subject: [PATCH 086/278] =?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/groups/generic.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/sage/groups/generic.py b/src/sage/groups/generic.py index 24fd776ddc8..716a04d3931 100644 --- a/src/sage/groups/generic.py +++ b/src/sage/groups/generic.py @@ -341,7 +341,8 @@ def __init__(self, P, n, P0=None, indexed=False, operation='+', op=None): self.P = copy(P) self.Q = copy(P0) - assert self.P is not None and self.Q is not None + if self.P is None or self.Q is None: + raise ValueError("P and Q must not be None") self.i = 0 self.bound = n self.indexed = indexed @@ -1279,8 +1280,8 @@ def _multiple(A, B): return Z.one() M = Z(m) - if check: - assert _multiple(P, M) == identity + if check and _multiple(P, M) != identity: + raise ValueError(f"The order of P(={P}) does not divide {M}") if factorization: F = factorization @@ -1587,8 +1588,8 @@ def merge_points(P1, P2, operation='+', raise ValueError("operation and identity must be specified") if check: - assert multiple(g1, n1, operation=operation) == identity - assert multiple(g2, n2, operation=operation) == identity + if multiple(g1, n1, operation=operation) != identity or multiple(g2, n2, operation=operation) != identity: + raise ValueError("the orders provided do not divide the orders of the points provided") # trivial cases if n1.divides(n2): From 7e1f3fc0353ce16299dd1c012547507dba3823b5 Mon Sep 17 00:00:00 2001 From: RuchitJagodara Date: Sun, 11 Feb 2024 14:57:45 +0530 Subject: [PATCH 087/278] Added try except block to cover all cases --- src/sage/combinat/permutation.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/sage/combinat/permutation.py b/src/sage/combinat/permutation.py index fd82d0ab75d..ec1fe0dd406 100644 --- a/src/sage/combinat/permutation.py +++ b/src/sage/combinat/permutation.py @@ -7101,8 +7101,11 @@ def _element_constructor_(self, x, check=True): sage: P5(p) [4, 5, 1, 2, 3] """ - if not isinstance(x, Permutation): - x = x.domain() + try: + if not isinstance(x, Permutation): + x = x.domain() + except AttributeError: + pass if len(x) < self.n: x = list(x) + list(range(len(x) + 1, self.n + 1)) return self.element_class(self, x, check=check) From e9ce5c112f345a57c2af0731635315796aec5b0d Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Sun, 11 Feb 2024 11:46:45 +0000 Subject: [PATCH 088/278] fix weird failing doctest... --- src/sage/schemes/projective/projective_morphism.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/schemes/projective/projective_morphism.py b/src/sage/schemes/projective/projective_morphism.py index 991da14db75..e30478aad8f 100644 --- a/src/sage/schemes/projective/projective_morphism.py +++ b/src/sage/schemes/projective/projective_morphism.py @@ -372,7 +372,7 @@ def __call__(self, x, check=True): sage: F. = GF(4) sage: P = T(F)(1, a) sage: h(P) # needs sage.libs.singular - (a : a) + (1 : 1) sage: h(P).domain() Spectrum of Finite Field in a of size 2^2 sage: h.change_ring(F)(P) From 5d3a177a716f6b5d1029f84262873a9ca6d910e0 Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Mon, 12 Feb 2024 01:34:00 +0000 Subject: [PATCH 089/278] =?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/stats/all.py | 1 - .../discrete_gaussian_lattice.py | 217 +++++++++--------- 2 files changed, 112 insertions(+), 106 deletions(-) diff --git a/src/sage/stats/all.py b/src/sage/stats/all.py index f8b42231671..3b4c8c4ff52 100644 --- a/src/sage/stats/all.py +++ b/src/sage/stats/all.py @@ -3,7 +3,6 @@ from .r import ttest from .basic_stats import (mean, mode, std, variance, median, moving_average) from .hmm import all as hmm -from .distributions.all import * # We lazy_import the following modules since they import numpy which # slows down sage startup diff --git a/src/sage/stats/distributions/discrete_gaussian_lattice.py b/src/sage/stats/distributions/discrete_gaussian_lattice.py index 4dd3223856d..fa9a1f81bc5 100644 --- a/src/sage/stats/distributions/discrete_gaussian_lattice.py +++ b/src/sage/stats/distributions/discrete_gaussian_lattice.py @@ -1,14 +1,14 @@ # -*- coding: utf-8 -*- -r""" -Discrete Gaussian Samplers over Lattices +r""" Discrete Gaussian Samplers over Lattices This file implements oracles which return samples from a lattice following a -discrete Gaussian distribution. That is, if `σ` is big enough relative to the -provided basis, then vectors are returned with a probability proportional to -`\exp(-|x-c|_2^2/(2σ^2))`. More precisely lattice vectors in `x ∈ Λ` are -returned with probability: +discrete Gaussian distribution. That is, if `\sigma` is big enough relative to +the provided basis, then vectors are returned with a probability proportional +to `\exp(-|x - c|_2^2 / (2\sigma^2))`. More precisely lattice vectors in `x \in +\Lambda` are returned with probability: - `\exp(-|x-c|_2^2/(2σ²))/(∑_{x ∈ Λ} \exp(-|x|_2^2/(2σ²)))` + `\exp(-|x - c|_2^2 / (2\sigma^2)) / (\Sigma_{x \in \Lambda} \exp(-|x|_2^2 / + (2\sigma^2)))` AUTHORS: @@ -18,8 +18,7 @@ EXAMPLES:: - sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler as DGL - sage: D = DGL(ZZ^10, 3.0) + sage: D = distributions.DiscreteGaussianDistributionLatticeSampler(ZZ^10, 3.0) sage: D(), D(), D() # random ((3, 0, -5, 0, -1, -3, 3, 3, -7, 2), (4, 0, 1, -2, -4, -4, 4, 0, 1, -4), (-3, 0, 4, 5, 0, 1, 3, 2, 0, -1)) sage: a = D() @@ -62,7 +61,7 @@ from sage.rings.real_mpfr import RealField from sage.rings.integer_ring import ZZ from sage.rings.rational_field import QQ -from sage.stats.distributions.discrete_gaussian_integer import DiscreteGaussianDistributionIntegerSampler as DGI +from sage.stats.distributions.discrete_gaussian_integer import DiscreteGaussianDistributionIntegerSampler from sage.structure.sage_object import SageObject from sage.misc.cachefunc import cached_method from sage.misc.functional import sqrt @@ -95,9 +94,9 @@ def _iter_vectors(n, lower, upper, step=None): """ if step is None: if ZZ(lower) >= ZZ(upper): - raise ValueError("Expected lower < upper, but got %d >= %d" % (lower, upper)) + raise ValueError("expected lower < upper, but got %d >= %d" % (lower, upper)) if ZZ(n) <= 0: - raise ValueError("Expected n>0 but got %d <= 0" % n) + raise ValueError("expected n>0 but got %d <= 0" % n) step = n assert step > 0 @@ -120,8 +119,7 @@ class DiscreteGaussianDistributionLatticeSampler(SageObject): EXAMPLES:: - sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler as DGL - sage: D = DGL(ZZ^10, 3.0); D + sage: D = distributions.DiscreteGaussianDistributionLatticeSampler(ZZ^10, 3.0); D Discrete Gaussian sampler with Gaussian parameter σ = 3.00000000000000, c=(0, 0, 0, 0, 0, 0, 0, 0, 0, 0) over lattice with basis [1 0 0 0 0 0 0 0 0 0] @@ -138,10 +136,9 @@ class DiscreteGaussianDistributionLatticeSampler(SageObject): We plot a histogram:: - sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler as DGL sage: import warnings sage: warnings.simplefilter('ignore', UserWarning) - sage: D = DGL(identity_matrix(2), 3.0) + sage: D = distributions.DiscreteGaussianDistributionLatticeSampler(identity_matrix(2), 3.0) sage: S = [D() for _ in range(2^12)] sage: l = [vector(v.list() + [S.count(v)]) for v in set(S)] sage: list_plot3d(l, point_list=True, interpolation='nn') # needs sage.plot @@ -167,7 +164,7 @@ def compute_precision(precision, sigma): EXAMPLES:: - sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler as DGL + sage: DGL = distributions.DiscreteGaussianDistributionLatticeSampler sage: DGL.compute_precision(100, RR(3)) 100 sage: DGL.compute_precision(100, RealField(200)(3)) @@ -192,27 +189,26 @@ def compute_precision(precision, sigma): def _normalisation_factor_zz(self, tau=None, prec=None): r""" - This function returns an approximation of `∑_{x ∈ B} - \exp(-|x|_2^2/(2σ²))`, i.e. the normalisation factor such that the sum + This function returns an approximation of `\Sigma_{x \in B} + \exp(-|x|_2^2 / (2\sigma²))`, i.e. the normalisation factor such that the sum over all probabilities is 1 for `B`, via Poisson summation. INPUT: - - ``tau`` -- (default: ``None``) all vectors `v` with `|v|_2^2 ≤ τ·σ` - are enumerated; if none is provided, enumerate vectors with - increasing norm until the sum converges to given precision. For high - dimension lattice, this is recommended. + - ``tau`` -- (default: ``None``) all vectors `v` with `|v|_2^2 \leq + \tau \sigma` are enumerated; if none is provided, enumerate vectors + with increasing norm until the sum converges to given precision. For + high dimension lattice, this is recommended. - - ``prec`` -- (default: ``None``) Passed to :meth:`compute_precision` + - ``prec`` -- (default: ``None``) passed to :meth:`compute_precision` EXAMPLES:: - sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler as DGL sage: n = 3; sigma = 1.0 - sage: D = DGL(ZZ^n, sigma) + sage: D = distributions.DiscreteGaussianDistributionLatticeSampler(ZZ^n, sigma) sage: f = D.f - sage: nf = D._normalisation_factor_zz(); nf # needs sage.symbolic + sage: nf = D._normalisation_factor_zz(); nf 15.7496... sage: from collections import defaultdict @@ -229,7 +225,7 @@ def _normalisation_factor_zz(self, tau=None, prec=None): sage: while v not in counter: ....: add_samples(1000) - sage: while abs(m*f(v)*1.0/nf/counter[v] - 1.0) >= 0.1: # needs sage.symbolic + sage: while abs(m*f(v)*1.0/nf/counter[v] - 1.0) >= 0.1: ....: add_samples(1000) sage: v = vector(ZZ, n, (-1, 2, 3)) @@ -237,9 +233,10 @@ def _normalisation_factor_zz(self, tau=None, prec=None): sage: while v not in counter: ....: add_samples(1000) - sage: while abs(m*f(v)*1.0/nf/counter[v] - 1.0) >= 0.2: # long time, needs sage.symbolic + sage: while abs(m*f(v)*1.0/nf/counter[v] - 1.0) >= 0.2: # long time ....: add_samples(1000) + sage: DGL = distributions.DiscreteGaussianDistributionLatticeSampler sage: D = DGL(ZZ^8, 0.5) sage: D._normalisation_factor_zz(tau=3) 3.1653... @@ -259,19 +256,19 @@ def _normalisation_factor_zz(self, tau=None, prec=None): sage: D._normalisation_factor_zz() Traceback (most recent call last): ... - NotImplementedError: Basis must be a square matrix for now. + NotImplementedError: basis must be a square matrix for now sage: D = DGL(ZZ^3, c=(1/2, 0, 0)) sage: D._normalisation_factor_zz() Traceback (most recent call last): ... - NotImplementedError: Lattice must contain 0 for now. + NotImplementedError: lattice must contain 0 for now sage: D = DGL(Matrix(3, 3, 1/2)) sage: D._normalisation_factor_zz() Traceback (most recent call last): ... - NotImplementedError: Lattice must be integral for now. + NotImplementedError: lattice must be integral for now """ # If σ > 1: @@ -309,13 +306,13 @@ def f_or_hat(x): return sum(self.f((vector(u) + base) * self.B) for u in coords) if self.B.nrows() != self.B.ncols(): - raise NotImplementedError("Basis must be a square matrix for now.") + raise NotImplementedError("basis must be a square matrix for now") if self.is_spherical and not self._c_in_lattice: - raise NotImplementedError("Lattice must contain 0 for now.") + raise NotImplementedError("lattice must contain 0 for now") if self.B.base_ring() != ZZ: - raise NotImplementedError("Lattice must be integral for now.") + raise NotImplementedError("lattice must be integral for now") sigma = self._sigma prec = DiscreteGaussianDistributionLatticeSampler.compute_precision( @@ -356,23 +353,21 @@ def _maximal_r(self): This function computes the largest value `r > 0` such that `Σ - r²BBᵀ` is positive definite. - This is equivalent to finding `λ₁(Σ / Q) = 1 / λₙ(Q / Σ)`, which is done - via the Power iteration method. + This is equivalent to finding `\lambda_1(\Sigma / Q) = 1 / \lambda_n(Q + / \Sigma)`, which is done via the Power iteration method. EXAMPLES:: - sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler as DGL sage: n = 3 sage: Sigma = Matrix(ZZ, [[5, -2, 4], [-2, 10, -5], [4, -5, 5]]) sage: c = vector(ZZ, [7, 2, 5]) - sage: D = DGL(ZZ^n, Sigma, c) + sage: D = distributions.DiscreteGaussianDistributionLatticeSampler(ZZ^n, Sigma, c) sage: r = D._maximal_r(); r 0.58402... sage: e_vals = (D.sigma() - r^2 * D.Q).eigenvalues() sage: assert all(e_val >= -1e-12 for e_val in e_vals) """ - if self.is_spherical: - raise RuntimeError("You have encountered a bug. File it! :)") + assert not self.is_spherical Q = self.Q.change_ring(self._RR) / self._sigma.change_ring(self._RR) v = Q[0].change_ring(self._RR) @@ -388,17 +383,25 @@ def _maximal_r(self): def _randomise(self, v): r""" - Randomly round to the latice coset `\ZZ + v` with Gaussian parameter `r` + Randomly round to the latice coset `\ZZ + v` with Gaussian parameter + `r`. Used at :meth:`_call_non_spherical`. REFERENCES: - [Pei2010]_, Section 4.1 + + EXAMPLES:: + + sage: Sigma = Matrix(ZZ, [[5, -2, 4], [-2, 10, -5], [4, -5, 5]]) + sage: D = distributions.DiscreteGaussianDistributionLatticeSampler(ZZ^3, Sigma) + sage: all(D._randomise([0, 0, 0]).norm() <= 16 for _ in range(100)) + True """ - return vector(ZZ, [DGI(self.r, c=vi)() for vi in v]) + return vector(ZZ, [DiscreteGaussianDistributionIntegerSampler(self.r, c=vi)() for vi in v]) def __init__(self, B, sigma=1, c=0, r=None, precision=None, sigma_basis=False): r""" - Construct a discrete Gaussian sampler over the lattice `Λ(B)` + Construct a discrete Gaussian sampler over the lattice `\LAmbda(B)` with parameter ``sigma`` and center `c`. INPUT: @@ -406,34 +409,31 @@ def __init__(self, B, sigma=1, c=0, r=None, precision=None, sigma_basis=False): - ``B`` -- a (row) basis for the lattice, one of the following: - an integer matrix, - - an object with a ``matrix()`` method, e.g. ``ZZ^n``, or - - an object where ``matrix(B)`` succeeds, e.g. a list of vectors. + - an object with a ``.matrix()`` method, e.g. ``ZZ^n``, or + - an object where ``matrix(B)`` succeeds, e.g. a list of vectors - ``sigma`` -- Gaussian parameter, one of the following: - - a real number `σ > 0` (spherical), - - a positive definite matrix `Σ` (non-spherical), or - - any matrix-like ``S``, equivalent to ``Σ = SSᵀ``, when + - a real number `\sigma > 0` (spherical), + - a positive definite matrix `\Sigma` (non-spherical), or + - any matrix-like ``S``, equivalent to `\Sigma = SS^T`, when ``sigma_basis`` is set - ``c`` -- (default: 0) center `c`, any vector in `\ZZ^n` is - supported, but `c ∈ Λ(B)` is faster. - - - ``r`` -- (default: None) rounding parameter `r` as defined in - [Pei2010]_. Ignored for spherical Gaussian parameter. If not provided, - set to be the maximal possible such that Σ - r²BBᵀ is positive - definite. - - - ``precision`` -- bit precision `≥ 53`. + supported, but `c \in \Lambda(B)` is faster - - ``sigma_basis`` -- (default: False) When set, ``sigma`` is treated as - a (row) basis, i.e. the covariance matrix is computed by ``Σ = SSᵀ``. + - ``r`` -- (default: ``None``) rounding parameter `r` as defined in + [Pei2010]_; ignored for spherical Gaussian parameter; if not provided, + set to be the maximal possible such that `\Sigma - rBB^T` is positive + definite + - ``precision`` -- bit precision `\geq 53`. + - ``sigma_basis`` -- (default: ``False``) When set, ``sigma`` is treated as + a (row) basis, i.e. the covariance matrix is computed by `\Sigma = SS^T` EXAMPLES:: - sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler as DGL sage: n = 2; sigma = 3.0 - sage: D = DGL(ZZ^n, sigma) + sage: D = distributions.DiscreteGaussianDistributionLatticeSampler(ZZ^n, sigma) sage: f = D.f sage: nf = D._normalisation_factor_zz(); nf # needs sage.symbolic 56.5486677646... @@ -463,7 +463,7 @@ def __init__(self, B, sigma=1, c=0, r=None, precision=None, sigma_basis=False): Spherical covariance are automatically handled:: - sage: DGL(ZZ^3, sigma=Matrix(3, 3, 2)) + sage: distributions.DiscreteGaussianDistributionLatticeSampler(ZZ^3, sigma=Matrix(3, 3, 2)) Discrete Gaussian sampler with Gaussian parameter σ = 2.00000000000000, c=(0, 0, 0) over lattice with basis [1 0 0] @@ -476,7 +476,7 @@ def __init__(self, B, sigma=1, c=0, r=None, precision=None, sigma_basis=False): sage: n = 3 sage: Sigma = Matrix(ZZ, [[5, -2, 4], [-2, 10, -5], [4, -5, 5]]) sage: c = vector(ZZ, [7, 2, 5]) - sage: D = DGL(ZZ^n, Sigma, c) + sage: D = distributions.DiscreteGaussianDistributionLatticeSampler(ZZ^n, Sigma, c) sage: nf = D._normalisation_factor_zz(); nf # This has not been properly implemented 63.76927... sage: while v not in counter: add_samples(1000) @@ -485,7 +485,7 @@ def __init__(self, B, sigma=1, c=0, r=None, precision=None, sigma_basis=False): If the covariance provided is not positive definite, an error is thrown:: sage: Sigma = Matrix(ZZ, [[0, 1], [1, 0]]) - sage: DGL(ZZ^2, Sigma) + sage: distributions.DiscreteGaussianDistributionLatticeSampler(ZZ^2, Sigma) Traceback (most recent call last): ... RuntimeError: Sigma(=[0.000000000000000 1.00000000000000] @@ -495,7 +495,7 @@ def __init__(self, B, sigma=1, c=0, r=None, precision=None, sigma_basis=False): sage: n = 3 sage: S = Matrix(ZZ, [[2, 0, 0], [-1, 3, 0], [2, -1, 1]]) - sage: D = DGL(ZZ^n, S, sigma_basis=True) + sage: D = distributions.DiscreteGaussianDistributionLatticeSampler(ZZ^n, S, sigma_basis=True) sage: D.sigma() [ 4.00000000000000 -2.00000000000000 4.00000000000000] [-2.00000000000000 10.0000000000000 -5.00000000000000] @@ -514,15 +514,14 @@ def __init__(self, B, sigma=1, c=0, r=None, precision=None, sigma_basis=False): We can also initialise with matrix-like objects:: - sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler as DGL sage: qf = matrix(3, [2, 1, 1, 1, 2, 1, 1, 1, 2]) - sage: D = DGL(qf, 3.0); D # needs sage.symbolic + sage: D = distributions.DiscreteGaussianDistributionLatticeSampler(qf, 3.0); D Discrete Gaussian sampler with Gaussian parameter σ = 3.00000000000000, c=(0, 0, 0) over lattice with basis [2 1 1] [1 2 1] [1 1 2] - sage: D().parent() is D.c().parent() # needs sage.symbolic + sage: D().parent() is D.c().parent() True """ precision = DiscreteGaussianDistributionLatticeSampler.compute_precision(precision, sigma) @@ -575,20 +574,25 @@ def _precompute_data(self): EXAMPLES:: - sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler as DGL - sage: D = DGL(ZZ^3, 3.0, c=(1,0,0)) + sage: D = distributions.DiscreteGaussianDistributionLatticeSampler(ZZ^3, 3.0, c=(1,0,0)) sage: D.set_c((2, 0, 0)) sage: D + Discrete Gaussian sampler with Gaussian parameter σ = 3.00000000000000, c=(2, 0, 0) over lattice with basis + + [1 0 0] + [0 1 0] + [0 0 1] - .. note:: + .. note: - Do not call this method directly, it is called automatically from :func:`DiscreteGaussianDistributionLatticeSampler.__init__`. + Do not call this method directly, it is called automatically from + :func:`DiscreteGaussianDistributionLatticeSampler.__init__`. """ if self.is_spherical: # deal with trivial case first, it is common if self._G == 1 and self._c == 0: self._c_in_lattice = True - D = DGI(sigma=self._sigma) + D = DiscreteGaussianDistributionIntegerSampler(sigma=self._sigma) self.D = tuple([D for _ in range(self.B.nrows())]) self.VS = FreeModule(ZZ, self.B.nrows()) @@ -599,7 +603,7 @@ def _precompute_data(self): D = [] for i in range(self.B.nrows()): sigma_ = self._sigma / self._G[i].norm() - D.append(DGI(sigma=sigma_)) + D.append(DiscreteGaussianDistributionIntegerSampler(sigma=sigma_)) self.D = tuple(D) self.VS = FreeModule(ZZ, self.B.nrows()) else: @@ -634,16 +638,15 @@ def __call__(self): EXAMPLES:: - sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler as DGL - sage: D = DGL(ZZ^3, 3.0, c=(1,0,0)) + sage: D = distributions.DiscreteGaussianDistributionLatticeSampler(ZZ^3, 3.0, c=(1,0,0)) sage: L = [D() for _ in range(2^12)] sage: mean_L = sum(L) / len(L) sage: norm(mean_L.n() - D.c()) < 0.25 True - sage: D = DGL(ZZ^3, 3.0, c=(1/2,0,0)) - sage: L = [D() for _ in range(2^12)] # long time - sage: mean_L = sum(L) / len(L) # long time + sage: D = distributions.DiscreteGaussianDistributionLatticeSampler(ZZ^3, 3.0, c=(1/2,0,0)) + sage: L = [D() for _ in range(2^12)] # long time + sage: mean_L = sum(L) / len(L) # long time sage: norm(mean_L.n() - D.c()) < 0.25 # long time True """ @@ -659,6 +662,15 @@ def __call__(self): def f(self, x): r""" Compute the Gaussian `\rho_{\Lambda, c, \Sigma}`. + + EXAMPLES:: + + sage: Sigma = Matrix(ZZ, [[5, -2, 4], [-2, 10, -5], [4, -5, 5]]) + sage: D = distributions.DiscreteGaussianDistributionLatticeSampler(ZZ^3, Sigma) + sage: D.f([1, 0, 1]) + 0.802518797962478 + sage: D.f([1, 0, 3]) + 0.00562800641440405 """ try: x = vector(ZZ, self.n, x) @@ -674,15 +686,14 @@ def f(self, x): def sigma(self): r""" - Gaussian parameter `σ`. + Gaussian parameter `\sigma`. - If σ is a real number, samples from this sampler will have expected norm - `\sqrt{n}σ` where `n` is the dimension of the lattice. + If `\sigma` is a real number, samples from this sampler will have expected norm + `\sqrt{n}\sigma` where `n` is the dimension of the lattice. EXAMPLES:: - sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler as DGL - sage: D = DGL(ZZ^3, 3.0, c=(1,0,0)) + sage: D = distributions.DiscreteGaussianDistributionLatticeSampler(ZZ^3, 3.0, c=(1,0,0)) sage: D.sigma() 3.00000000000000 """ @@ -696,8 +707,7 @@ def c(self): EXAMPLES:: - sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler as DGL - sage: D = DGL(ZZ^3, 3.0, c=(1,0,0)); D + sage: D = distributions.DiscreteGaussianDistributionLatticeSampler(ZZ^3, 3.0, c=(1,0,0)); D Discrete Gaussian sampler with Gaussian parameter σ = 3.00000000000000, c=(1, 0, 0) over lattice with basis [1 0 0] @@ -715,8 +725,7 @@ def set_c(self, c): EXAMPLES:: - sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler as DGL - sage: D = DGL(ZZ^3, 3.0, c=(1,0,0)) + sage: D = distributions.DiscreteGaussianDistributionLatticeSampler(ZZ^3, 3.0, c=(1,0,0)) sage: D.set_c((2, 0, 0)) sage: D Discrete Gaussian sampler with Gaussian parameter σ = 3.00000000000000, c=(2, 0, 0) over lattice with basis @@ -750,8 +759,7 @@ def __repr__(self): r""" EXAMPLES:: - sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler as DGL - sage: D = DGL(ZZ^3, 3.0, c=(1,0,0)); D + sage: D = distributions.DiscreteGaussianDistributionLatticeSampler(ZZ^3, 3.0, c=(1,0,0)); D Discrete Gaussian sampler with Gaussian parameter σ = 3.00000000000000, c=(1, 0, 0) over lattice with basis [1 0 0] @@ -759,7 +767,7 @@ def __repr__(self): [0 0 1] sage: Sigma = Matrix(ZZ, [[10, -6, 1], [-6, 5, -1], [1, -1, 2]]) - sage: D = DGL(ZZ^3, Sigma); D + sage: D = distributions.DiscreteGaussianDistributionLatticeSampler(ZZ^3, Sigma); D Discrete Gaussian sampler with Gaussian parameter Σ = [ 10.0000000000000 -6.00000000000000 1.00000000000000] [-6.00000000000000 5.00000000000000 -1.00000000000000] @@ -777,12 +785,11 @@ def __repr__(self): def _call_in_lattice(self): r""" - Return a new sample assuming `c ∈ Λ(B)`. + Return a new sample assuming `c \in \Lambda(B)`. EXAMPLES:: - sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler as DGL - sage: D = DGL(ZZ^3, 3.0, c=(1,0,0)) + sage: D = distributions.DiscreteGaussianDistributionLatticeSampler(ZZ^3, 3.0, c=(1,0,0)) sage: L = [D._call_in_lattice() for _ in range(2^12)] sage: mean_L = sum(L) / len(L) sage: norm(mean_L.n() - D.c()) < 0.25 @@ -790,7 +797,8 @@ def _call_in_lattice(self): .. note:: - Do not call this method directly, call :func:`DiscreteGaussianDistributionLatticeSampler.__call__` instead. + Do not call this method directly, call + :func:`DiscreteGaussianDistributionLatticeSampler.__call__` instead. """ w = self.VS([d() for d in self.D], check=False) return w * self.B + self._c @@ -801,8 +809,7 @@ def _call(self): EXAMPLES:: - sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler as DGL - sage: D = DGL(ZZ^3, 3.0, c=(1/2,0,0)) + sage: D = distributions.DiscreteGaussianDistributionLatticeSampler(ZZ^3, 3.0, c=(1/2,0,0)) sage: L = [D._call() for _ in range(2^12)] sage: mean_L = sum(L) / len(L) sage: norm(mean_L.n() - D.c()) < 0.25 @@ -810,7 +817,8 @@ def _call(self): .. note:: - Do not call this method directly, call :func:`DiscreteGaussianDistributionLatticeSampler.__call__` instead. + Do not call this method directly, call + :func:`DiscreteGaussianDistributionLatticeSampler.__call__` instead. """ v = 0 c, sigma, B = self._c, self._sigma, self.B @@ -822,20 +830,19 @@ def _call(self): c_ = c.dot_product(b_) / b_.dot_product(b_) sigma_ = sigma / b_.norm() assert sigma_ > 0 - z = DGI(sigma=sigma_, c=c_, algorithm="uniform+online")() + z = DiscreteGaussianDistributionIntegerSampler(sigma=sigma_, c=c_, algorithm="uniform+online")() c = c - z * B[i] v = v + z * B[i] return v def add_offline_samples(self, cnt=1): """ - Precompute samples from B⁻¹D₁ to be used in :meth:`_call_non_spherical` + Precompute samples from `B^{-1}D_1` to be used in :meth:`_call_non_spherical` EXAMPLES:: - sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler as DGL sage: Sigma = Matrix([[5, -2, 4], [-2, 10, -5], [4, -5, 5]]) - sage: D = DGL(ZZ^3, Sigma) + sage: D = distributions.DiscreteGaussianDistributionLatticeSampler(ZZ^3, Sigma) sage: assert not D.is_spherical sage: D.add_offline_samples(2^12) sage: L = [D() for _ in range(2^12)] # Takes less time @@ -854,9 +861,8 @@ def _call_non_spherical(self): EXAMPLES:: - sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler as DGL sage: Sigma = Matrix([[5, -2, 4], [-2, 10, -5], [4, -5, 5]]) - sage: D = DGL(ZZ^3, Sigma, c=(1/2,0,0)) + sage: D = distributions.DiscreteGaussianDistributionLatticeSampler(ZZ^3, Sigma, c=(1/2,0,0)) sage: L = [D._call_non_spherical() for _ in range(2^12)] sage: mean_L = sum(L) / len(L) sage: norm(mean_L.n() - D.c()) < 0.25 @@ -864,7 +870,8 @@ def _call_non_spherical(self): .. note:: - Do not call this method directly, call :func:`DiscreteGaussianDistributionLatticeSampler.__call__` instead. + Do not call this method directly, call + :func:`DiscreteGaussianDistributionLatticeSampler.__call__` instead. """ if len(self.offline_samples) == 0: self.add_offline_samples() From 729a0dda2f65e724802d2e333caef4a6cb3319eb Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Mon, 12 Feb 2024 07:52:45 +0000 Subject: [PATCH 090/278] =?UTF-8?q?Incorrect=20docstring=20formatting=20?= =?UTF-8?q?=F0=9F=93=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Let's just say I was slightly sleep deprived when I made the previous commits. ... --- .../discrete_gaussian_lattice.py | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/sage/stats/distributions/discrete_gaussian_lattice.py b/src/sage/stats/distributions/discrete_gaussian_lattice.py index fa9a1f81bc5..ff158c845e1 100644 --- a/src/sage/stats/distributions/discrete_gaussian_lattice.py +++ b/src/sage/stats/distributions/discrete_gaussian_lattice.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- -r""" Discrete Gaussian Samplers over Lattices +r""" +Discrete Gaussian Samplers over Lattices This file implements oracles which return samples from a lattice following a discrete Gaussian distribution. That is, if `\sigma` is big enough relative to @@ -7,8 +8,10 @@ to `\exp(-|x - c|_2^2 / (2\sigma^2))`. More precisely lattice vectors in `x \in \Lambda` are returned with probability: - `\exp(-|x - c|_2^2 / (2\sigma^2)) / (\Sigma_{x \in \Lambda} \exp(-|x|_2^2 / - (2\sigma^2)))` +.. MATH:: + + `\frac{\exp(-|x - c|_2^2 / (2\sigma^2))}{\sum_{x \in \Lambda} \exp(-|x|_2^2 / + (2\sigma^2))}` AUTHORS: @@ -189,8 +192,8 @@ def compute_precision(precision, sigma): def _normalisation_factor_zz(self, tau=None, prec=None): r""" - This function returns an approximation of `\Sigma_{x \in B} - \exp(-|x|_2^2 / (2\sigma²))`, i.e. the normalisation factor such that the sum + This function returns an approximation of `\sum_{x \in B} + \exp(-|x|_2^2 / (2\sigma^2))`, i.e. the normalization factor such that the sum over all probabilities is 1 for `B`, via Poisson summation. @@ -270,7 +273,6 @@ def _normalisation_factor_zz(self, tau=None, prec=None): ... NotImplementedError: lattice must be integral for now """ - # If σ > 1: # We use the Fourier transform g(t) of f(x) = exp(-k^2 / 2σ^2), but # taking the norm of vector t^2 as input, and with norm_factor factored. @@ -350,7 +352,7 @@ def f_or_hat(x): @cached_method def _maximal_r(self): r""" - This function computes the largest value `r > 0` such that `Σ - r²BBᵀ` + This function computes the largest value `r > 0` such that `\Sigma - r^2BB^@` is positive definite. This is equivalent to finding `\lambda_1(\Sigma / Q) = 1 / \lambda_n(Q @@ -401,7 +403,7 @@ def _randomise(self, v): def __init__(self, B, sigma=1, c=0, r=None, precision=None, sigma_basis=False): r""" - Construct a discrete Gaussian sampler over the lattice `\LAmbda(B)` + Construct a discrete Gaussian sampler over the lattice `\Lambda(B)` with parameter ``sigma`` and center `c`. INPUT: @@ -583,7 +585,7 @@ def _precompute_data(self): [0 1 0] [0 0 1] - .. note: + .. NOTE:: Do not call this method directly, it is called automatically from :func:`DiscreteGaussianDistributionLatticeSampler.__init__`. @@ -795,7 +797,7 @@ def _call_in_lattice(self): sage: norm(mean_L.n() - D.c()) < 0.25 True - .. note:: + .. NOTE:: Do not call this method directly, call :func:`DiscreteGaussianDistributionLatticeSampler.__call__` instead. @@ -815,7 +817,7 @@ def _call(self): sage: norm(mean_L.n() - D.c()) < 0.25 True - .. note:: + .. NOTE:: Do not call this method directly, call :func:`DiscreteGaussianDistributionLatticeSampler.__call__` instead. @@ -837,7 +839,7 @@ def _call(self): def add_offline_samples(self, cnt=1): """ - Precompute samples from `B^{-1}D_1` to be used in :meth:`_call_non_spherical` + Precompute samples from `B^{-1}D_1` to be used in :meth:`_call_non_spherical`. EXAMPLES:: @@ -868,7 +870,7 @@ def _call_non_spherical(self): sage: norm(mean_L.n() - D.c()) < 0.25 True - .. note:: + .. NOTE:: Do not call this method directly, call :func:`DiscreteGaussianDistributionLatticeSampler.__call__` instead. From 1901a69a2da36951dc20b7021d9d78d464ec2064 Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Mon, 12 Feb 2024 07:57:01 +0000 Subject: [PATCH 091/278] catalog.py fix --- src/sage/stats/distributions/catalog.py | 11 ++++++----- .../stats/distributions/discrete_gaussian_lattice.py | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/sage/stats/distributions/catalog.py b/src/sage/stats/distributions/catalog.py index 66c753ccff4..5f252494e13 100644 --- a/src/sage/stats/distributions/catalog.py +++ b/src/sage/stats/distributions/catalog.py @@ -15,7 +15,7 @@ To import these names into the global namespace, use:: - sage: from sage.stats.channels_catalog import * + sage: from sage.stats.distributions.catalog import * """ #***************************************************************************** @@ -27,7 +27,8 @@ # http://www.gnu.org/licenses/ #***************************************************************************** -from sage.misc.lazy_import import lazy_import as _lazy_import -_lazy_import("sage.stats.distributions.discrete_gaussian_integer", ["DiscreteGaussianDistributionIntegerSampler"]) -_lazy_import("sage.stats.distributions.discrete_gaussian_lattice", ["DiscreteGaussianDistributionLatticeSampler"]) -_lazy_import("sage.stats.distributions.discrete_gaussian_polynomial", ["DiscreteGaussianDistributionPolynomialSampler"]) +from sage.misc.lazy_import import lazy_import +lazy_import("sage.stats.distributions.discrete_gaussian_integer", ["DiscreteGaussianDistributionIntegerSampler"]) +lazy_import("sage.stats.distributions.discrete_gaussian_lattice", ["DiscreteGaussianDistributionLatticeSampler"]) +lazy_import("sage.stats.distributions.discrete_gaussian_polynomial", ["DiscreteGaussianDistributionPolynomialSampler"]) +del lazy_import diff --git a/src/sage/stats/distributions/discrete_gaussian_lattice.py b/src/sage/stats/distributions/discrete_gaussian_lattice.py index ff158c845e1..17d1a2f2bc7 100644 --- a/src/sage/stats/distributions/discrete_gaussian_lattice.py +++ b/src/sage/stats/distributions/discrete_gaussian_lattice.py @@ -10,7 +10,7 @@ .. MATH:: - `\frac{\exp(-|x - c|_2^2 / (2\sigma^2))}{\sum_{x \in \Lambda} \exp(-|x|_2^2 / + `\frac{\exp(-|x - c|_2^2 / (2\sigma^2))}{\sum_{v \in \Lambda} \exp(-|v|_2^2 / (2\sigma^2))}` AUTHORS: From b7b05c65bcc035f70e30ef17baaf5bf9c92426b5 Mon Sep 17 00:00:00 2001 From: Ruchit Jagodara Date: Mon, 12 Feb 2024 19:33:53 +0530 Subject: [PATCH 092/278] Check for specific type while x is not in correct form Co-authored-by: Travis Scrimshaw --- src/sage/combinat/permutation.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/sage/combinat/permutation.py b/src/sage/combinat/permutation.py index ec1fe0dd406..fb2ec9afc6b 100644 --- a/src/sage/combinat/permutation.py +++ b/src/sage/combinat/permutation.py @@ -7101,11 +7101,8 @@ def _element_constructor_(self, x, check=True): sage: P5(p) [4, 5, 1, 2, 3] """ - try: - if not isinstance(x, Permutation): - x = x.domain() - except AttributeError: - pass + if isinstance(x, PermutationGroupElement): + x = x.domain() if len(x) < self.n: x = list(x) + list(range(len(x) + 1, self.n + 1)) return self.element_class(self, x, check=check) From f444d326540464fa9908fcd73fd3ab16511c5410 Mon Sep 17 00:00:00 2001 From: Ruchit Jagodara Date: Mon, 12 Feb 2024 19:34:36 +0530 Subject: [PATCH 093/278] Corrected the test documentation Co-authored-by: grhkm21 <83517584+grhkm21@users.noreply.github.com> --- 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 fb2ec9afc6b..1e213f2d5ad 100644 --- a/src/sage/combinat/permutation.py +++ b/src/sage/combinat/permutation.py @@ -7093,7 +7093,7 @@ def _element_constructor_(self, x, check=True): sage: Permutations(6)(x) # known bug [1, 4, 5, 2, 3, 6] - check if :trac:`37284` is fixed:: + Ensure that :issue:`37284` is fixed:: sage: S5 = SymmetricGroup(5) sage: P5 = Permutations(5) From 6beda49e4e289ba65ebff0394b87385a800b44cb Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Tue, 13 Feb 2024 10:18:24 +0000 Subject: [PATCH 094/278] long time doctests --- src/sage/rings/polynomial/polynomial_ring.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sage/rings/polynomial/polynomial_ring.py b/src/sage/rings/polynomial/polynomial_ring.py index 7d9c456b1c2..7e7f6c98c9e 100644 --- a/src/sage/rings/polynomial/polynomial_ring.py +++ b/src/sage/rings/polynomial/polynomial_ring.py @@ -1459,11 +1459,11 @@ def random_element(self, degree=(-1, 2), monic=False, *args, **kwds): sage: from collections import Counter sage: R = GF(3)["x"] - sage: samples = [R.random_element(degree=(-1, 2)) for _ in range(27 * 1000)] # long time - sage: assert all(750 <= f <= 1250 for f in Counter(samples).values()) + sage: samples = [R.random_element(degree=(-1, 2)) for _ in range(27000)] # long time + sage: assert all(750 <= f <= 1250 for f in Counter(samples).values()) # long time - sage: samples = [R.random_element(degree=(-1, 2), monic=True) for _ in range(13 * 1000)] # long time - sage: assert all(750 <= f <= 1250 for f in Counter(samples).values()) + sage: samples = [R.random_element(degree=(-1, 2), monic=True) for _ in range(13000)] # long time + sage: assert all(750 <= f <= 1250 for f in Counter(samples).values()) # long time """ R = self.base_ring() From e6ef89957e568c2e00ce1f0ce5010a5816f046e1 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 4 Feb 2024 18:53:44 -0800 Subject: [PATCH 095/278] build/pkgs/ntl: Update to 11.5.1 --- build/pkgs/ntl/checksums.ini | 7 ++++--- build/pkgs/ntl/package-version.txt | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/build/pkgs/ntl/checksums.ini b/build/pkgs/ntl/checksums.ini index d5ea9216c02..ca472fff0a4 100644 --- a/build/pkgs/ntl/checksums.ini +++ b/build/pkgs/ntl/checksums.ini @@ -1,4 +1,5 @@ tarball=ntl-VERSION.tar.gz -sha1=f4c7dc1fd448b499ef98549e8702b320ba6a7830 -md5=536b72b7ba5b0075fb137137c00e5773 -cksum=1481984929 +sha1=a55050ca07fb42c6f9e9a479b6f80be6f1f77886 +md5=abd887865df30c02609210a86cb953b1 +cksum=322384454 +upstream_url=https://libntl.org/ntl-VERSION.tar.gz diff --git a/build/pkgs/ntl/package-version.txt b/build/pkgs/ntl/package-version.txt index 72f04fb9d60..326ba189b47 100644 --- a/build/pkgs/ntl/package-version.txt +++ b/build/pkgs/ntl/package-version.txt @@ -1 +1 @@ -11.4.3 +11.5.1 From fbedec1d355a2fdb5fc950810bc9774a820b90a1 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 4 Feb 2024 19:11:17 -0800 Subject: [PATCH 096/278] build/pkgs/ntl/checksums.ini: Use src.fedoraproject.org --- build/pkgs/ntl/checksums.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/pkgs/ntl/checksums.ini b/build/pkgs/ntl/checksums.ini index ca472fff0a4..57769694456 100644 --- a/build/pkgs/ntl/checksums.ini +++ b/build/pkgs/ntl/checksums.ini @@ -2,4 +2,4 @@ tarball=ntl-VERSION.tar.gz sha1=a55050ca07fb42c6f9e9a479b6f80be6f1f77886 md5=abd887865df30c02609210a86cb953b1 cksum=322384454 -upstream_url=https://libntl.org/ntl-VERSION.tar.gz +upstream_url=https://src.fedoraproject.org/repo/pkgs/ntl/ntl-11.5.1.tar.gz/sha512/cf1f642b8a0f9cdc6dda888e07183817dc67ff494e56a852053aeb15b3d2a0e61fbc05824779c5d1f20b8115fba6f97266acf7e0b0b527c25df5989c86d5928f/ntl-11.5.1.tar.gz From 10df873efa3159724690fe4fcabb0802d58f56ee Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 25 Dec 2023 17:34:15 -0800 Subject: [PATCH 097/278] tox.ini [DOCKER_BUILDKIT=1]: Do not attempt to retrieve a failed container from buildkit --- tox.ini | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tox.ini b/tox.ini index 3673833724c..ae2db7923b7 100644 --- a/tox.ini +++ b/tox.ini @@ -756,7 +756,7 @@ commands = docker: BUILD_IMAGE=$DOCKER_PUSH_REPOSITORY$BUILD_IMAGE_STEM-$docker_target; \ docker: BUILD_TAG={env:DOCKER_TAG:$(git describe --dirty --always)}; \ docker: TAG_ARGS=$(for tag in $BUILD_TAG {env:EXTRA_DOCKER_TAGS:}; do echo --tag $BUILD_IMAGE:$tag; done); \ - docker: DOCKER_BUILDKIT={env:DOCKER_BUILDKIT:0} \ + docker: DOCKER_BUILDKIT={env:DOCKER_BUILDKIT:0}; \ docker: docker build . -f {envdir}/Dockerfile \ docker: --target $docker_target \ docker: $TAG_ARGS \ @@ -769,12 +769,16 @@ commands = docker: --build-arg TARGETS_OPTIONAL="{env:TARGETS_OPTIONAL:ptest}" \ docker: {env:EXTRA_DOCKER_BUILD_ARGS:}; status=$?; \ docker: if [ $status != 0 ]; then \ - docker: BUILD_TAG="$BUILD_TAG-failed"; docker commit $(docker ps -l -q) $BUILD_IMAGE:$BUILD_TAG; PUSH_TAGS=$BUILD_IMAGE:$BUILD_TAG; \ + docker: if [ $DOCKER_BUILDKIT = 0 ]; then \ + docker: BUILD_TAG="$BUILD_TAG-failed"; docker commit $(docker ps -l -q) $BUILD_IMAGE:$BUILD_TAG; PUSH_TAGS=$BUILD_IMAGE:$BUILD_TAG; \ + docker: else \ + docker: unset BUILD_TAG; unset PUSH_TAGS; echo "DOCKER_BUILDKIT=1, so we cannot commit and tag the failed image"; \ + docker: fi; \ docker: else \ docker: PUSH_TAGS=$(echo $BUILD_IMAGE:$BUILD_TAG; for tag in {env:EXTRA_DOCKER_TAGS:}; do echo "$BUILD_IMAGE:$tag"; done); \ docker: fi; \ docker: echo $BUILD_IMAGE:$BUILD_TAG >> {envdir}/Dockertags; \ - docker: if [ x"{env:DOCKER_PUSH_REPOSITORY:}" != x ]; then \ + docker: if [ x"{env:DOCKER_PUSH_REPOSITORY:}" != x -a x"$PUSH_TAGS" != x ]; then \ docker: echo Pushing $PUSH_TAGS; \ docker: for tag in $PUSH_TAGS; do \ docker: docker push $tag || echo "(ignoring errors)"; \ From 8cd1147daa373dac8346bd96189a4d40522e046f Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 26 Dec 2023 11:14:17 -0800 Subject: [PATCH 098/278] tox.ini (docker): Move copying of logs from container here from .github/workflows/docker.yml --- .github/workflows/docker.yml | 4 +--- tox.ini | 12 +++++++++++- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 1ee938339b3..7dd0aa5ab17 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -248,9 +248,7 @@ jobs: - name: Copy logs from the Docker image or build container run: | mkdir -p "artifacts/$LOGS_ARTIFACT_NAME" - cp -r .tox/$TOX_ENV/Dockerfile .tox/$TOX_ENV/log "artifacts/$LOGS_ARTIFACT_NAME" - if [ -f .tox/$TOX_ENV/Dockertags ]; then CONTAINERS=$(docker create $(tail -1 .tox/$TOX_ENV/Dockertags) /bin/bash || true); fi - if [ -n "$CONTAINERS" ]; then for CONTAINER in $CONTAINERS; do for ARTIFACT in /sage/logs; do docker cp $CONTAINER:$ARTIFACT artifacts/$LOGS_ARTIFACT_NAME && HAVE_LOG=1; done; if [ -n "$HAVE_LOG" ]; then break; fi; done; fi + cp -r .tox/$TOX_ENV/* "artifacts/$LOGS_ARTIFACT_NAME" if: always() - uses: actions/upload-artifact@v3 with: diff --git a/tox.ini b/tox.ini index ae2db7923b7..2c7bd3c9ffa 100644 --- a/tox.ini +++ b/tox.ini @@ -768,9 +768,10 @@ commands = docker: --build-arg TARGETS="{posargs:build}" \ docker: --build-arg TARGETS_OPTIONAL="{env:TARGETS_OPTIONAL:ptest}" \ docker: {env:EXTRA_DOCKER_BUILD_ARGS:}; status=$?; \ + docker: unset CONTAINER; \ docker: if [ $status != 0 ]; then \ docker: if [ $DOCKER_BUILDKIT = 0 ]; then \ - docker: BUILD_TAG="$BUILD_TAG-failed"; docker commit $(docker ps -l -q) $BUILD_IMAGE:$BUILD_TAG; PUSH_TAGS=$BUILD_IMAGE:$BUILD_TAG; \ + docker: BUILD_TAG="$BUILD_TAG-failed"; CONTAINER=$(docker ps -l -q); docker commit $CONTAINER $BUILD_IMAGE:$BUILD_TAG; PUSH_TAGS=$BUILD_IMAGE:$BUILD_TAG; \ docker: else \ docker: unset BUILD_TAG; unset PUSH_TAGS; echo "DOCKER_BUILDKIT=1, so we cannot commit and tag the failed image"; \ docker: fi; \ @@ -784,6 +785,15 @@ commands = docker: docker push $tag || echo "(ignoring errors)"; \ docker: done; \ docker: fi; \ + docker: if [ x"$BUILD_TAG" != x ]; then \ + docker: echo "Copying logs from the container to {envdir}/sage/"; \ + docker: docker run $BUILD_IMAGE:$BUILD_TAG bash -c " \ + docker: tar -c --ignore-failed-read -f - \ + docker: /sage/logs /sage/{prefix,venv}/var/lib/sage/installed \ + docker: ~/.sage/timings2.json /sage/pkgs/*/.tox/*/.sage/timings2.json \ + docker: /sage/pkgs/*/.tox/*/logs 2> /dev/null" \ + docker: | (cd {envdir} && tar xf -); \ + docker: fi; \ docker: if [ $status != 0 ]; then exit $status; fi; \ docker: done' # #28728: gap fails its test suite. From 5537a8271d0dff757be5e7210b63d6cf7b6c59bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20K=C3=B6ppe?= Date: Tue, 26 Dec 2023 22:30:50 +0000 Subject: [PATCH 099/278] tox.ini (docker): Only append to Dockertags if BUILD_TAG is set --- tox.ini | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 2c7bd3c9ffa..96cb2eb1173 100644 --- a/tox.ini +++ b/tox.ini @@ -778,7 +778,9 @@ commands = docker: else \ docker: PUSH_TAGS=$(echo $BUILD_IMAGE:$BUILD_TAG; for tag in {env:EXTRA_DOCKER_TAGS:}; do echo "$BUILD_IMAGE:$tag"; done); \ docker: fi; \ - docker: echo $BUILD_IMAGE:$BUILD_TAG >> {envdir}/Dockertags; \ + docker: if [ -n "$BUILD_TAG" ]; then \ + docker: echo $BUILD_IMAGE:$BUILD_TAG >> {envdir}/Dockertags; \ + docker: fi; \ docker: if [ x"{env:DOCKER_PUSH_REPOSITORY:}" != x -a x"$PUSH_TAGS" != x ]; then \ docker: echo Pushing $PUSH_TAGS; \ docker: for tag in $PUSH_TAGS; do \ From b4e6ac7cf9cf5ec5ff8bc7a174335eb084b045f4 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 26 Dec 2023 14:31:44 -0800 Subject: [PATCH 100/278] .github/workflows/docker.yml: Show Docker images as annotations, show instructions --- .github/workflows/docker.yml | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 7dd0aa5ab17..4b330c0020e 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -260,9 +260,32 @@ jobs: run: | .github/workflows/scan-logs.sh "artifacts/$LOGS_ARTIFACT_NAME" if: always() - - name: List docker images + - name: List Docker images run: | - if [ -f .tox/$TOX_ENV/Dockertags ]; then - cat .tox/$TOX_ENV/Dockertags + if [ -n "$DOCKER_PUSH_REPOSITORY" -a -f .tox/$TOX_ENV/Dockertags ]; then + echo "::notice title=Docker images pushed::$(echo $(cat .tox/$TOX_ENV/Dockertags))" + echo + echo "To pull an image and enter the container, type:" + echo + for TAG in $(cat .tox/$TOX_ENV/Dockertags); do + echo " \$ docker run -it $TAG bash" + done + echo + echo "To use an image as the base for an incremental build, type:" + echo + TOX_ENV_SANS_INCREMENTAL=${TOX_ENV/-incremental/} + DOCKER_IMAGE=${TOX_ENV_SANS_INCREMENTAL#docker-} + for TAG in $(cat .tox/$TOX_ENV/Dockertags); do + echo -n " \$" + if [ "$DOCKER_PUSH_REPOSITORY" != "ghcr.io/sagemath/sage/" ]; then + echo -n " FROM_DOCKER_REPOSITORY=$DOCKER_PUSH_REPOSITORY" + fi + eval DOCKER_TARGET=\${TAG#*$DOCKER_IMAGE-} + DOCKER_TARGET=${DOCKER_TARGET%:*} + if [ "$DOCKER_TARGET" != "with-targets" ]; then + echo -n " FROM_DOCKER_TARGET=$DOCKER_TARGET" + fi + echo " FROM_DOCKER_TAG=${TAG#*:} tox -e $TOX_ENV_SANS_INCREMENTAL-incremental" + done fi if: always() From eba5a061a1bed60b611281b1d9d48451e18fe3af Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 26 Dec 2023 14:49:26 -0800 Subject: [PATCH 101/278] build/pkgs/info: Update to 7.0.3 --- build/pkgs/info/checksums.ini | 6 +++--- build/pkgs/info/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/info/checksums.ini b/build/pkgs/info/checksums.ini index c01f8b97505..cf6afe0684c 100644 --- a/build/pkgs/info/checksums.ini +++ b/build/pkgs/info/checksums.ini @@ -1,5 +1,5 @@ tarball=texinfo-VERSION.tar.xz -sha1=ce3776715e655400485381b8ae94e34c5632e729 -md5=a91b404e30561a5df803e6eb3a53be71 -cksum=3632265516 +sha1=356a623b88401d7c993408f33450c8104aad9df8 +md5=37bf94fd255729a14d4ea3dda119f81a +cksum=1448415744 upstream_url=https://ftp.gnu.org/gnu/texinfo/texinfo-VERSION.tar.xz diff --git a/build/pkgs/info/package-version.txt b/build/pkgs/info/package-version.txt index 21afad37646..a50da181e9b 100644 --- a/build/pkgs/info/package-version.txt +++ b/build/pkgs/info/package-version.txt @@ -1 +1 @@ -6.8 +7.0.3 From a19a34857a5aefd92280cbc571cf1b31a82edc91 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 26 Dec 2023 18:33:51 -0800 Subject: [PATCH 102/278] .github/workflows/docker.yml: Improve wording of Docker instructions --- .github/workflows/docker.yml | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 4b330c0020e..f77328707b0 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -263,19 +263,24 @@ jobs: - name: List Docker images run: | if [ -n "$DOCKER_PUSH_REPOSITORY" -a -f .tox/$TOX_ENV/Dockertags ]; then - echo "::notice title=Docker images pushed::$(echo $(cat .tox/$TOX_ENV/Dockertags))" + set -- $(cat .tox/$TOX_ENV/Dockertags) + case $# in + 1) images="image"; one_image="the image";; + *) images="images"; one_image="one of the images";; + esac + echo "::notice title=Docker $images pushed::Pushed $images $(echo $(cat .tox/$TOX_ENV/Dockertags))" echo - echo "To pull an image and enter the container, type:" + echo "To pull $one_image and enter the container, type:" echo - for TAG in $(cat .tox/$TOX_ENV/Dockertags); do + for TAG in $*; do echo " \$ docker run -it $TAG bash" done echo - echo "To use an image as the base for an incremental build, type:" + echo "To use $one_image as the base for an incremental build, type:" echo TOX_ENV_SANS_INCREMENTAL=${TOX_ENV/-incremental/} DOCKER_IMAGE=${TOX_ENV_SANS_INCREMENTAL#docker-} - for TAG in $(cat .tox/$TOX_ENV/Dockertags); do + for TAG in $*; do echo -n " \$" if [ "$DOCKER_PUSH_REPOSITORY" != "ghcr.io/sagemath/sage/" ]; then echo -n " FROM_DOCKER_REPOSITORY=$DOCKER_PUSH_REPOSITORY" From 4826a15a8d177e3d624f024b9947d1423d31a263 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 26 Dec 2023 18:51:15 -0800 Subject: [PATCH 103/278] tox.ini (docker): Record successful pushes in Dockertags.pushed --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 96cb2eb1173..6a364fdd019 100644 --- a/tox.ini +++ b/tox.ini @@ -784,7 +784,7 @@ commands = docker: if [ x"{env:DOCKER_PUSH_REPOSITORY:}" != x -a x"$PUSH_TAGS" != x ]; then \ docker: echo Pushing $PUSH_TAGS; \ docker: for tag in $PUSH_TAGS; do \ - docker: docker push $tag || echo "(ignoring errors)"; \ + docker: if docker push $tag; then echo $tag >> {envdir}/Dockertags.pushed; else echo "(ignoring errors)"; fi; \ docker: done; \ docker: fi; \ docker: if [ x"$BUILD_TAG" != x ]; then \ From bf28d2080e0697d72bbd40e554019c6ed96c5db1 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 26 Dec 2023 19:03:18 -0800 Subject: [PATCH 104/278] .github/workflows/docker.yml: Use Dockertags.pushed --- .github/workflows/docker.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index f77328707b0..81be0a8506b 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -262,13 +262,13 @@ jobs: if: always() - name: List Docker images run: | - if [ -n "$DOCKER_PUSH_REPOSITORY" -a -f .tox/$TOX_ENV/Dockertags ]; then - set -- $(cat .tox/$TOX_ENV/Dockertags) + if [ -n "$DOCKER_PUSH_REPOSITORY" -a -f .tox/$TOX_ENV/Dockertags.pushed ]; then + set -- $(cat .tox/$TOX_ENV/Dockertags.pushed) case $# in 1) images="image"; one_image="the image";; *) images="images"; one_image="one of the images";; esac - echo "::notice title=Docker $images pushed::Pushed $images $(echo $(cat .tox/$TOX_ENV/Dockertags))" + echo "::notice title=Docker $images pushed::Pushed $images $*)" echo echo "To pull $one_image and enter the container, type:" echo @@ -292,5 +292,7 @@ jobs: fi echo " FROM_DOCKER_TAG=${TAG#*:} tox -e $TOX_ENV_SANS_INCREMENTAL-incremental" done + else + echo "No Docker images pushed" fi if: always() From e2992439d8afbf1ecac0b7424723ba7b96a7ffc0 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 26 Dec 2023 19:22:08 -0800 Subject: [PATCH 105/278] .github/workflows/docker.yml: Add instructions for the case that no Docker image is pushed --- .github/workflows/docker.yml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 81be0a8506b..2a95b682095 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -292,7 +292,14 @@ jobs: fi echo " FROM_DOCKER_TAG=${TAG#*:} tox -e $TOX_ENV_SANS_INCREMENTAL-incremental" done + elif [ -n "$DOCKER_PUSH_REPOSITORY" -a -f .tox/$TOX_ENV/Dockertags ]; then + echo "Unable to push Docker images to $DOCKER_PUSH_REPOSITORY." + echo "This is normal in a pull request to sagemath/sage or to another user's repository." + echo + echo "If you need Docker images, " + echo " - either run this GitHub Actions workflow in your repository fork" + echo " - or use the method described in https://doc.sagemath.org/html/en/developer/portability_testing.html#automatic-docker-based-build-testing-using-tox else - echo "No Docker images pushed" + echo "No Docker images created." fi - if: always() + if: always() && ${{ inputs.docker_push_repository }} From 755fd97dc7b073f28dcb97d8757a185b0de882dc Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 26 Dec 2023 21:41:46 -0800 Subject: [PATCH 106/278] .github/workflows/docker.yml: Fix up --- .github/workflows/docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 2a95b682095..9ea69233574 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -298,7 +298,7 @@ jobs: echo echo "If you need Docker images, " echo " - either run this GitHub Actions workflow in your repository fork" - echo " - or use the method described in https://doc.sagemath.org/html/en/developer/portability_testing.html#automatic-docker-based-build-testing-using-tox + echo " - or use the method described in https://doc.sagemath.org/html/en/developer/portability_testing.html#automatic-docker-based-build-testing-using-tox" else echo "No Docker images created." fi From 358909a319bca66b68efc906f926d05062972f26 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 27 Dec 2023 18:03:52 -0800 Subject: [PATCH 107/278] tox.ini (docker), build/bin/write-dockerfile.sh [DOCKER_BUILDKIT=1]: Save error status in a file --- build/bin/write-dockerfile.sh | 28 ++++++++++++++++++++-------- tox.ini | 32 +++++++++++++++++++------------- 2 files changed, 39 insertions(+), 21 deletions(-) diff --git a/build/bin/write-dockerfile.sh b/build/bin/write-dockerfile.sh index 2d0de123665..3c62d6082e4 100755 --- a/build/bin/write-dockerfile.sh +++ b/build/bin/write-dockerfile.sh @@ -214,6 +214,18 @@ EOF ;; esac esac + +case ${DOCKER_BUILDKIT-0} in + 1) + # With buildkit we cannot retrieve failed builds. + # So we do not allow the main step of a build stage to fail. + # Instead we record the exit code in the file STATUS. + THEN_SAVE_STATUS='; echo $? > STATUS' + # ... and at the beginning of the next build stage, + # we check the status and exit with an error status. + CHECK_STATUS_THEN='STATUS=$(cat STATUS 2>/dev/null); case "$STATUS" in ""|0) ;; *) exit $STATUS;; esac; ' +esac + cat < /dev/null" \ + docker: | (cd {envdir} && tar xf -); \ + docker: if [ -f {envdir}/sage/STATUS ]; then status=$(cat {envdir}/sage/STATUS); fi; \ + docker: fi; \ + docker: unset PUSH_TAGS; \ + docker: if [ -n "$BUILD_TAG" ]; then \ + docker: if [ $status != 0 ]; then \ + docker: BUILD_TAG="${BUILD_TAG%-failed}-failed"; PUSH_TAGS=$BUILD_IMAGE:$BUILD_TAG; \ + docker: else \ + docker: PUSH_TAGS=$(echo $BUILD_IMAGE:$BUILD_TAG; for tag in {env:EXTRA_DOCKER_TAGS:}; do echo "$BUILD_IMAGE:$tag"; done); \ + docker: fi; \ docker: echo $BUILD_IMAGE:$BUILD_TAG >> {envdir}/Dockertags; \ docker: fi; \ docker: if [ x"{env:DOCKER_PUSH_REPOSITORY:}" != x -a x"$PUSH_TAGS" != x ]; then \ @@ -787,15 +802,6 @@ commands = docker: if docker push $tag; then echo $tag >> {envdir}/Dockertags.pushed; else echo "(ignoring errors)"; fi; \ docker: done; \ docker: fi; \ - docker: if [ x"$BUILD_TAG" != x ]; then \ - docker: echo "Copying logs from the container to {envdir}/sage/"; \ - docker: docker run $BUILD_IMAGE:$BUILD_TAG bash -c " \ - docker: tar -c --ignore-failed-read -f - \ - docker: /sage/logs /sage/{prefix,venv}/var/lib/sage/installed \ - docker: ~/.sage/timings2.json /sage/pkgs/*/.tox/*/.sage/timings2.json \ - docker: /sage/pkgs/*/.tox/*/logs 2> /dev/null" \ - docker: | (cd {envdir} && tar xf -); \ - docker: fi; \ docker: if [ $status != 0 ]; then exit $status; fi; \ docker: done' # #28728: gap fails its test suite. From 415ecad8d459a95413afe4b24696ab429ef85910 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 27 Dec 2023 18:23:58 -0800 Subject: [PATCH 108/278] tox.ini (docker): Make DOCKER_BUILDKIT=1 the default --- tox.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index 075c09f2d53..4dacdea4d5d 100644 --- a/tox.ini +++ b/tox.ini @@ -143,7 +143,7 @@ passenv = docker: EXTRA_DOCKER_BUILD_ARGS docker: EXTRA_DOCKER_TAGS docker: DOCKER_TAG - # Use DOCKER_BUILDKIT=1 for new version - for which unfortunately we cannot save failed builds as an image + # Use DOCKER_BUILDKIT=0 for legacy builder docker: DOCKER_BUILDKIT docker: BUILDKIT_INLINE_CACHE # Set for example to "with-system-packages configured with-targets-pre with-targets" @@ -756,7 +756,7 @@ commands = docker: BUILD_IMAGE=$DOCKER_PUSH_REPOSITORY$BUILD_IMAGE_STEM-$docker_target; \ docker: BUILD_TAG={env:DOCKER_TAG:$(git describe --dirty --always)}; \ docker: TAG_ARGS=$(for tag in $BUILD_TAG {env:EXTRA_DOCKER_TAGS:}; do echo --tag $BUILD_IMAGE:$tag; done); \ - docker: DOCKER_BUILDKIT={env:DOCKER_BUILDKIT:0}; \ + docker: DOCKER_BUILDKIT={env:DOCKER_BUILDKIT:1}; \ docker: docker build . -f {envdir}/Dockerfile \ docker: --target $docker_target \ docker: $TAG_ARGS \ From 78f9f2e84e64e1cb0e4f7ee2d112e158a6425401 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 27 Dec 2023 23:01:29 -0800 Subject: [PATCH 109/278] .github/workflows/docker.yml: Don't put tox venv in LOGS_ARTIFACT --- .github/workflows/docker.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 9ea69233574..18910ca50a6 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -249,6 +249,7 @@ jobs: run: | mkdir -p "artifacts/$LOGS_ARTIFACT_NAME" cp -r .tox/$TOX_ENV/* "artifacts/$LOGS_ARTIFACT_NAME" + rm -rf "artifacts/$LOGS_ARTIFACT_NAME"/{bin,lib,pyvenv.cfg} if: always() - uses: actions/upload-artifact@v3 with: From b9805bbb5eeb96276f001f5ad11baf0b0b4d19aa Mon Sep 17 00:00:00 2001 From: Omegaconstant Date: Wed, 14 Feb 2024 12:23:09 +0530 Subject: [PATCH 110/278] Modified the doc to display the HTML nicely --- .../sets/media/recursively-enumerated-set.pdf | Bin 0 -> 22396 bytes .../sets/media/recursively-enumerated-set.png | Bin 0 -> 27161 bytes src/sage/sets/recursively_enumerated_set.pyx | 45 ++---------------- 3 files changed, 4 insertions(+), 41 deletions(-) create mode 100644 src/doc/en/reference/sets/media/recursively-enumerated-set.pdf create mode 100644 src/doc/en/reference/sets/media/recursively-enumerated-set.png diff --git a/src/doc/en/reference/sets/media/recursively-enumerated-set.pdf b/src/doc/en/reference/sets/media/recursively-enumerated-set.pdf new file mode 100644 index 0000000000000000000000000000000000000000..0c14b3febc842e68b18a71e0ff5f3ea4daf03254 GIT binary patch literal 22396 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%0Af?L+sv6P+tui)R+b=#Y-}v|21Yg3VH#|ZcCI~p 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 literal 0 HcmV?d00001 diff --git a/src/doc/en/reference/sets/media/recursively-enumerated-set.png b/src/doc/en/reference/sets/media/recursively-enumerated-set.png new file mode 100644 index 0000000000000000000000000000000000000000..54864b24b6fdddce40e5aab67704f859e9bfd6ad GIT binary patch literal 27161 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 literal 0 HcmV?d00001 diff --git a/src/sage/sets/recursively_enumerated_set.pyx b/src/sage/sets/recursively_enumerated_set.pyx index 66720d924e5..27ef65c4e6a 100644 --- a/src/sage/sets/recursively_enumerated_set.pyx +++ b/src/sage/sets/recursively_enumerated_set.pyx @@ -9,7 +9,7 @@ sets that are described by some ``seeds`` and a successor function graded, forest) or not. The elements of a set having a symmetric, graded or forest structure can be enumerated uniquely without keeping all of them in memory. Many kinds of iterators are provided in this module: depth first -search, breadth first search or elements of given depth. +search, breadth first search and elements of given depth. See :wikipedia:`Recursively_enumerable_set`. @@ -20,8 +20,6 @@ AUTHORS: - Sébastien Labbé, April 2014, at Sage Days 57, Cernay-la-ville -EXAMPLES: - No hypothesis on the structure ------------------------------ @@ -173,43 +171,8 @@ Only two things are necessary to define a set using a :class:`RecursivelyEnumeratedSet` object (the other classes being very similar): -.. MATH:: - - \begin{picture}(-300,0)(600,0) - % Root - \put(0,0){\circle*{7}} - \put(0,10){\makebox(0,10){``\ ''}} - % First Children - \put(-150,-60){\makebox(0,10){``a''}} - \put(0,-60){\makebox(0,10){``b''}} - \put(150,-60){\makebox(0,10){``c''}} - \multiput(-150,-70)(150,0){3}{\circle*{7}} - % Second children - \put(-200,-130){\makebox(0,10){``aa''}} - \put(-150,-130){\makebox(0,10){``ab''}} - \put(-100,-130){\makebox(0,10){``ac''}} - \put(-50,-130){\makebox(0,10){``ba''}} - \put(0,-130){\makebox(0,10){``bb''}} - \put(50,-130){\makebox(0,10){``bc''}} - \put(100,-130){\makebox(0,10){``ca''}} - \put(150,-130){\makebox(0,10){``cb''}} - \put(200,-130){\makebox(0,10){``cc''}} - \multiput(-200,-140)(50,0){9}{\circle*{7}} - % Legend - \put(100,-5){\makebox(0,10)[l]{1) An initial element}} - \put(-250,-5){\makebox(0,10)[l]{2) A function of an element enumerating}} - \put(-235,-20){\makebox(0,10)[l]{its children (if any)}} - % Arrows - \thicklines - \put(0,-10){\vector(0,-1){30}} - \put(-15,-5){\vector(-2,-1){110}} - \put(15,-5){\vector(2,-1){110}} - \multiput(-150,-80)(150,0){3}{\vector(0,-1){30}} - \multiput(-160,-80)(150,0){3}{\vector(-1,-1){30}} - \multiput(-140,-80)(150,0){3}{\vector(1,-1){30}} - \put(90,0){\vector(-1,0){70}} - \put(-215,-30){\vector(1,-1){40}} - \end{picture} +.. figure:: ../../media/recursively-enumerated-set.png + :scale: 67 % For the previous example, the two necessary pieces of information are: @@ -256,7 +219,7 @@ This example was provided by Florent Hivert. Here is a little more involved example. We want to iterate through all permutations of a given set `S`. One solution is to take elements of `S` one -by one an insert them at every positions. So a node of the generating tree +by one and insert them at every positions. So a node of the generating tree contains two pieces of information: - the list ``lst`` of already inserted element; From 7e22d869744338adade880b9a29d4e263f91eb39 Mon Sep 17 00:00:00 2001 From: Omegaconstant Date: Wed, 14 Feb 2024 12:44:39 +0530 Subject: [PATCH 111/278] Corrected a spelling error --- src/sage/sets/recursively_enumerated_set.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/sets/recursively_enumerated_set.pyx b/src/sage/sets/recursively_enumerated_set.pyx index 27ef65c4e6a..d865914b2e9 100644 --- a/src/sage/sets/recursively_enumerated_set.pyx +++ b/src/sage/sets/recursively_enumerated_set.pyx @@ -219,7 +219,7 @@ This example was provided by Florent Hivert. Here is a little more involved example. We want to iterate through all permutations of a given set `S`. One solution is to take elements of `S` one -by one and insert them at every positions. So a node of the generating tree +by one and insert them at every position. So a node of the generating tree contains two pieces of information: - the list ``lst`` of already inserted element; From d39eedc29961cbb153881958a8d3d852d752f05d Mon Sep 17 00:00:00 2001 From: Omegaconstant Date: Wed, 14 Feb 2024 13:11:30 +0530 Subject: [PATCH 112/278] Corrected some typos --- src/sage/sets/recursively_enumerated_set.pyx | 36 ++++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/sage/sets/recursively_enumerated_set.pyx b/src/sage/sets/recursively_enumerated_set.pyx index d865914b2e9..5ecb32bdb1a 100644 --- a/src/sage/sets/recursively_enumerated_set.pyx +++ b/src/sage/sets/recursively_enumerated_set.pyx @@ -13,7 +13,7 @@ search, breadth first search and elements of given depth. See :wikipedia:`Recursively_enumerable_set`. -See documentation of :func:`RecursivelyEnumeratedSet` below for the +See the documentation of :func:`RecursivelyEnumeratedSet` below for the description of the inputs. AUTHORS: @@ -25,7 +25,7 @@ No hypothesis on the structure What we mean by "no hypothesis" is that the set is not known to be a forest, symmetric, or graded. However, it may have other -structure, like not containing an oriented cycle, that does not +structures, such as not containing an oriented cycle, that do not help with the enumeration. In this example, the seed is 0 and the successor function is either ``+2`` @@ -139,7 +139,7 @@ Forest structure ---------------- The set of words over the alphabet `\{a,b\}` can be generated from the -empty word by appending letter `a` or `b` as a successor function. This set +empty word by appending the letter `a` or `b` as a successor function. This set has a forest structure:: sage: seeds = [''] @@ -182,7 +182,7 @@ For the previous example, the two necessary pieces of information are: lambda x: [x + letter for letter in ['a', 'b', 'c'] -This would actually describe an **infinite** set, as such rules describes +This would actually describe an **infinite** set, as such rules describe "all words" on 3 letters. Hence, it is a good idea to replace the function by:: lambda x: [x + letter for letter in ['a', 'b', 'c']] if len(x) < 2 else [] @@ -283,7 +283,7 @@ def RecursivelyEnumeratedSet(seeds, successors, structure=None, A set `S` is called recursively enumerable if there is an algorithm that enumerates the members of `S`. We consider here the recursively - enumerated set that are described by some ``seeds`` and a successor + enumerated sets that are described by some ``seeds`` and a successor function ``successors``. Let `U` be a set and ``successors`` `:U \to 2^U` be a successor function @@ -369,7 +369,7 @@ def RecursivelyEnumeratedSet(seeds, successors, structure=None, .. WARNING:: - If you do not set the good structure, you might obtain bad results, + If you do not set a good structure, you might obtain bad results, like elements generated twice:: sage: f = lambda a: [a-1,a+1] @@ -744,8 +744,8 @@ cdef class RecursivelyEnumeratedSet_generic(Parent): r""" Iterate over the elements of ``self`` of given depth. - An element of depth `n` can be obtained applying `n` times the - successor function to a seed. + An element of depth `n` can be obtained by applying the + successor function `n` times to a seed. INPUT: @@ -810,7 +810,7 @@ cdef class RecursivelyEnumeratedSet_generic(Parent): r""" Iterate on the elements of ``self`` (breadth first). - This code remembers every elements generated and uses python + This code remembers every element generated and uses python queues. It is 3 times slower than the other one. See :wikipedia:`Breadth-first_search`. @@ -839,7 +839,7 @@ cdef class RecursivelyEnumeratedSet_generic(Parent): r""" Iterate on the elements of ``self`` (in no particular order). - This code remembers every elements generated. + This code remembers every element generated. TESTS: @@ -868,7 +868,7 @@ cdef class RecursivelyEnumeratedSet_generic(Parent): r""" Iterate on the elements of ``self`` (depth first). - This code remembers every elements generated. + This code remembers every element generated. The elements are traversed right-to-left, so the last element returned by the successor function is visited first. @@ -1515,7 +1515,7 @@ def search_forest_iterator(roots, children, algorithm='depth'): [0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 1, 1], [1, 0, 0], [1, 0, 1], [1, 1, 0], [1, 1, 1]] - This allows for iterating trough trees of infinite depth:: + This allows for iterating through trees of infinite depth:: sage: it = search_forest_iterator([[]], lambda l: [l+[0], l+[1]], algorithm='breadth') sage: [ next(it) for i in range(16) ] @@ -1537,7 +1537,7 @@ def search_forest_iterator(roots, children, algorithm='depth'): [0, 1, 2], [0, 2, 1], [1, 0, 2], [1, 2, 0], [2, 0, 1], [2, 1, 0]] """ # Little trick: the same implementation handles both depth and - # breadth first search. Setting position to -1 makes a depth search + # breadth first search. Setting the position to -1 makes a depth search # (you ask the children for the last node you met). Setting # position on 0 makes a breadth search (enumerate all the # descendants of a node before going on to the next father) @@ -1883,8 +1883,8 @@ class RecursivelyEnumeratedSet_forest(Parent): def _elements_of_depth_iterator_rec(self, depth=0): r""" Return an iterator over the elements of ``self`` of given depth. - An element of depth `n` can be obtained applying `n` times the - children function from a root. This function is not affected + An element of depth `n` can be obtained by applying the + children function `n` times from the root. This function is not affected by post processing. EXAMPLES:: @@ -1914,8 +1914,8 @@ class RecursivelyEnumeratedSet_forest(Parent): def elements_of_depth_iterator(self, depth=0): r""" Return an iterator over the elements of ``self`` of given depth. - An element of depth `n` can be obtained applying `n` times the - children function from a root. + An element of depth `n` can be obtained by applying the + children function `n` times from the root. EXAMPLES:: @@ -1973,7 +1973,7 @@ class RecursivelyEnumeratedSet_forest(Parent): depth first search and breadth first search failed. The following example enumerates all ordered pairs of nonnegative integers, starting from an infinite set of roots, where each - roots has an infinite number of children:: + root has an infinite number of children:: sage: from sage.sets.recursively_enumerated_set import RecursivelyEnumeratedSet_forest sage: S = RecursivelyEnumeratedSet_forest(Family(NN, lambda x : (x, 0)), From 9697fac04916a98cfbde6eadcfe9080f9c618ebf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Wed, 14 Feb 2024 09:49:58 +0100 Subject: [PATCH 113/278] some details in modules (ruff and pep) --- src/sage/modules/filtered_vector_space.py | 2 +- src/sage/modules/fp_graded/free_element.py | 2 +- src/sage/modules/fp_graded/morphism.py | 4 +- src/sage/modules/free_module.py | 142 ++++++++++++--------- src/sage/modules/matrix_morphism.py | 17 +-- src/sage/modules/vector_space_morphism.py | 11 +- 6 files changed, 98 insertions(+), 80 deletions(-) diff --git a/src/sage/modules/filtered_vector_space.py b/src/sage/modules/filtered_vector_space.py index d6a1d6237a6..68bbff12c8d 100644 --- a/src/sage/modules/filtered_vector_space.py +++ b/src/sage/modules/filtered_vector_space.py @@ -806,7 +806,7 @@ def _repr_field_name(self): return 'RR' from sage.categories.finite_fields import FiniteFields if self.base_ring() in FiniteFields(): - return 'GF({0})'.format(len(self.base_ring())) + return 'GF({})'.format(len(self.base_ring())) else: raise NotImplementedError() diff --git a/src/sage/modules/fp_graded/free_element.py b/src/sage/modules/fp_graded/free_element.py index 232a7956d0e..8dc11e7764a 100755 --- a/src/sage/modules/fp_graded/free_element.py +++ b/src/sage/modules/fp_graded/free_element.py @@ -193,7 +193,7 @@ def _lmul_(self, a): Sq(1,1,1)*x0 + Sq(1,1,1)*y0 + Sq(5,1)*z3, Sq(3,2)*z3] """ - return self.parent()((a * c for c in self.dense_coefficient_list())) + return self.parent()(a * c for c in self.dense_coefficient_list()) @cached_method def vector_presentation(self): diff --git a/src/sage/modules/fp_graded/morphism.py b/src/sage/modules/fp_graded/morphism.py index 47f74fd49e5..92c2753bddf 100755 --- a/src/sage/modules/fp_graded/morphism.py +++ b/src/sage/modules/fp_graded/morphism.py @@ -1747,7 +1747,7 @@ def _resolve_kernel(self, top_dim=None, verbose=False): if not kernel_n: continue - generator_degrees = tuple((x.degree() for x in F_.generators())) + generator_degrees = tuple(x.degree() for x in F_.generators()) if j.is_zero(): # The map j is not onto in degree `n` of the kernel. @@ -1875,7 +1875,7 @@ def _resolve_image(self, top_dim=None, verbose=False): if image_n.dimension() == 0: continue - generator_degrees = tuple((x.degree() for x in F_.generators())) + generator_degrees = tuple(x.degree() for x in F_.generators()) if j.is_zero(): # The map j is not onto in degree `n` of the image. new_generator_degrees = image_n.rank() * (n,) diff --git a/src/sage/modules/free_module.py b/src/sage/modules/free_module.py index 7fe7ff4f02d..a751a0ee2fe 100644 --- a/src/sage/modules/free_module.py +++ b/src/sage/modules/free_module.py @@ -266,16 +266,16 @@ def create_object(self, version, key): from sage.modules.free_quadratic_module import FreeQuadraticModule return FreeQuadraticModule(base_ring, rank, inner_product_matrix=inner_product_matrix, sparse=sparse) - if not isinstance(sparse,bool): + if not isinstance(sparse, bool): raise TypeError("Argument sparse (= %s) must be True or False" % sparse) - if not (hasattr(base_ring,'is_commutative') and base_ring.is_commutative()): + if not (hasattr(base_ring, 'is_commutative') and base_ring.is_commutative()): warn("You are constructing a free module\n" "over a noncommutative ring. Sage does not have a concept\n" "of left/right and both sided modules, so be careful.\n" "It's also not guaranteed that all multiplications are\n" "done from the right side.") - #raise TypeError, "The base_ring must be a commutative ring." + # raise TypeError("the base_ring must be a commutative ring") if not sparse and isinstance(base_ring, sage.rings.abc.RealDoubleField): return RealDoubleVectorSpace_class(rank) @@ -293,7 +293,7 @@ def create_object(self, version, key): return FreeModule_ambient_pid(base_ring, rank, sparse=sparse) if (isinstance(base_ring, sage.rings.abc.Order) - and base_ring.is_maximal() and base_ring.class_number() == 1): + and base_ring.is_maximal() and base_ring.class_number() == 1): return FreeModule_ambient_pid(base_ring, rank, sparse=sparse) if isinstance(base_ring, IntegralDomain) or base_ring in IntegralDomains(): @@ -304,6 +304,7 @@ def create_object(self, version, key): FreeModuleFactory_with_standard_basis = FreeModuleFactory("FreeModule") + def FreeModule(base_ring, rank_or_basis_keys=None, sparse=False, inner_product_matrix=None, *, with_basis='standard', rank=None, basis_keys=None, **args): r""" @@ -537,7 +538,7 @@ def FreeModule(base_ring, rank_or_basis_keys=None, sparse=False, inner_product_m elif with_basis == 'standard': if rank is not None: return FreeModuleFactory_with_standard_basis(base_ring, rank, sparse, - inner_product_matrix, **args) + inner_product_matrix, **args) else: if inner_product_matrix is not None: raise NotImplementedError @@ -546,6 +547,7 @@ def FreeModule(base_ring, rank_or_basis_keys=None, sparse=False, inner_product_m else: raise NotImplementedError + def VectorSpace(K, dimension_or_basis_keys=None, sparse=False, inner_product_matrix=None, *, with_basis='standard', dimension=None, basis_keys=None, **args): """ @@ -582,6 +584,7 @@ def VectorSpace(K, dimension_or_basis_keys=None, sparse=False, inner_product_mat with_basis=with_basis, rank=dimension, basis_keys=basis_keys, **args) + def span(gens, base_ring=None, check=True, already_echelonized=False): r""" Return the span of the vectors in ``gens`` using scalars from ``base_ring``. @@ -772,6 +775,7 @@ def span(gens, base_ring=None, check=True, already_echelonized=False): return M.span(gens=gens, base_ring=base_ring, check=check, already_echelonized=already_echelonized) + def basis_seq(V, vecs): """ This converts a list vecs of vectors in V to a Sequence of @@ -2001,8 +2005,9 @@ def construction(self): (VectorFunctor, Multivariate Polynomial Ring in x0, x1, x2 over Rational Field) """ from sage.categories.pushout import VectorFunctor - if hasattr(self,'_inner_product_matrix'): - return VectorFunctor(self.rank(), self.is_sparse(),self.inner_product_matrix()), self.base_ring() + if hasattr(self, '_inner_product_matrix'): + return VectorFunctor(self.rank(), self.is_sparse(), + self.inner_product_matrix()), self.base_ring() return VectorFunctor(self.rank(), self.is_sparse()), self.base_ring() # FIXME: what's the level of generality of FreeModuleHomspace? @@ -2168,10 +2173,9 @@ def _element_constructor_(self, x, coerce=True, copy=True, check=True): sage: N((0,0,0,1), check=False) in N True """ - if (isinstance(x, (int, sage.rings.integer.Integer)) and - x == 0): + if (isinstance(x, (int, sage.rings.integer.Integer)) and x == 0): return self.zero_vector() - elif isinstance(x, free_module_element.FreeModuleElement): + if isinstance(x, free_module_element.FreeModuleElement): if x.parent() is self: if copy: return x.__copy__() @@ -2325,7 +2329,7 @@ def is_submodule(self, other): pass from sage.modules.quotient_module import FreeModule_ambient_field_quotient if isinstance(other, FreeModule_ambient_field_quotient): - #if the relations agree we continue with the covers. + # if the relations agree we continue with the covers. if isinstance(self, FreeModule_ambient_field_quotient): if other.relations() != self.relations(): return False @@ -2437,23 +2441,24 @@ def __iter__(self): # Aleksei Udovenko, adapted by Lorenz Panny to order by 1-norm # primarily and by max-norm secondarily. def aux(length, norm, max_): - if not 0 <= norm <= length*max_: + if not 0 <= norm <= length * max_: return # there are no such vectors if norm == max_ == 0: - yield (0,)*length + yield (0,) * length return for pos in range(length): - for lnorm in range(norm-max_+1): + for lnorm in range(norm - max_ + 1): for lmax in range(max_): for left in aux(pos, lnorm, lmax): - for rmax in range(max_+1): - for right in aux(length-1-pos, norm-max_-lnorm, rmax): + for rmax in range(max_ + 1): + for right in aux(length - 1 - pos, + norm - max_ - lnorm, rmax): for mid in (+max_, -max_): yield left + (mid,) + right n = len(G) for norm in itertools.count(0): - mm = (norm + n-1) // n - for max_ in range(mm, norm+1): + mm = (norm + n - 1) // n + for max_ in range(mm, norm + 1): for vec in aux(n, norm, max_): yield self.linear_combination_of_basis(vec) assert False # should loop forever @@ -2619,8 +2624,9 @@ def basis_matrix(self, ring=None): A = self.__basis_matrix except AttributeError: MAT = sage.matrix.matrix_space.MatrixSpace(self.coordinate_ring(), - len(self.basis()), self.degree(), - sparse=self.is_sparse()) + len(self.basis()), + self.degree(), + sparse=self.is_sparse()) if self.is_ambient(): A = MAT.identity_matrix() else: @@ -3017,10 +3023,10 @@ def gram_matrix(self): else: if self._gram_matrix is None: B = self.basis_matrix() - self._gram_matrix = B*B.transpose() + self._gram_matrix = B * B.transpose() return self._gram_matrix - def has_user_basis(self): + def has_user_basis(self) -> bool: """ Return ``True`` if the basis of this free module is specified by the user, as opposed to being the default echelon @@ -3504,7 +3510,7 @@ def scale(self, other): return self.zero_submodule() if other == 1 or other == -1: return self - return self.span([v*other for v in self.basis()]) + return self.span([v * other for v in self.basis()]) def __radd__(self, other): """ @@ -4120,12 +4126,12 @@ def span_of_basis(self, basis, base_ring=None, check=True, already_echelonized=F M = self.change_ring(base_ring) except TypeError: raise ValueError("Argument base_ring (= %s) is not compatible " % base_ring + - "with the base ring (= %s)." % self.base_ring()) + "with the base ring (= %s)." % self.base_ring()) try: return M.span_of_basis(basis) except TypeError: raise ValueError("Argument gens (= %s) is not compatible " % basis + - "with base_ring (= %s)." % base_ring) + "with base_ring (= %s)." % base_ring) def submodule_with_basis(self, basis, check=True, already_echelonized=False): r""" @@ -4754,7 +4760,7 @@ def subspaces(self, dim): b = self.basis_matrix() from sage.matrix.echelon_matrix import reduced_echelon_matrix_iterator for m in reduced_echelon_matrix_iterator(self.base_ring(), dim, self.dimension(), self.is_sparse(), copy=False): - yield self.subspace((m*b).rows()) + yield self.subspace((m * b).rows()) def subspace_with_basis(self, gens, check=True, already_echelonized=False): """ @@ -5353,7 +5359,9 @@ def __init__(self, base_ring, rank, sparse=False, coordinate_ring=None, category True """ FreeModule_generic.__init__(self, base_ring, rank=rank, - degree=rank, sparse=sparse, coordinate_ring=coordinate_ring, category=category) + degree=rank, sparse=sparse, + coordinate_ring=coordinate_ring, + category=category) def __hash__(self): """ @@ -5398,14 +5406,14 @@ def _coerce_map_from_(self, M): return None if isinstance(M, FreeModule_ambient): if (self.base_ring().has_coerce_map_from(M.base_ring()) and - self.rank() == M.rank()): + self.rank() == M.rank()): # We could return M.hom(self.basis(), self), but the # complexity of this is quadratic in space and time, # since it constructs a matrix. return True elif isinstance(M, Submodule_free_ambient): if (self.base_ring().has_coerce_map_from(M.base_ring()) and - self.rank() == M.degree()): + self.rank() == M.degree()): return True return super()._coerce_map_from_(M) @@ -5543,7 +5551,7 @@ def _echelon_matrix_richcmp(self, other, op): if isinstance(other, FreeModule_ambient): if (isinstance(other, FreeModule_ambient_field_quotient) or isinstance(self, FreeModule_ambient_field_quotient)): - return richcmp(self,other,op) + return richcmp(self, other, op) lx = self.rank() rx = other.rank() @@ -5558,10 +5566,10 @@ def _echelon_matrix_richcmp(self, other, op): if self._inner_product_is_dot_product() and other._inner_product_is_dot_product(): return rich_to_bool(op, 0) else: - #this only affects free_quadratic_modules + # this only affects free_quadratic_modules lx = self.inner_product_matrix() rx = other.inner_product_matrix() - return richcmp(lx,rx,op) + return richcmp(lx, rx, op) try: if lx.is_subring(rx): @@ -6233,9 +6241,11 @@ def __init__(self, base_ring, rank, sparse=False, coordinate_ring=None, category """ FreeModule_ambient_domain.__init__(self, base_ring=base_ring, - rank=rank, sparse=sparse, coordinate_ring=coordinate_ring, category=category) + rank=rank, sparse=sparse, + coordinate_ring=coordinate_ring, + category=category) - def _repr_(self): + def _repr_(self) -> str: """ The printing representation of self. @@ -6401,18 +6411,18 @@ def _element_constructor_(self, e, *args, **kwds): class RealDoubleVectorSpace_class(FreeModule_ambient_field): - def __init__(self,n): - FreeModule_ambient_field.__init__(self,sage.rings.real_double.RDF,n) + def __init__(self, n): + FreeModule_ambient_field.__init__(self, sage.rings.real_double.RDF, n) - def coordinates(self,v): + def coordinates(self, v): return v class ComplexDoubleVectorSpace_class(FreeModule_ambient_field): - def __init__(self,n): - FreeModule_ambient_field.__init__(self,sage.rings.complex_double.CDF,n) + def __init__(self, n): + FreeModule_ambient_field.__init__(self, sage.rings.complex_double.CDF, n) - def coordinates(self,v): + def coordinates(self, v): return v @@ -6471,8 +6481,9 @@ class FreeModule_submodule_with_basis_pid(FreeModule_generic_pid): [ 4 5 6] """ def __init__(self, ambient, basis, check=True, - echelonize=False, echelonized_basis=None, already_echelonized=False, - category=None): + echelonize=False, echelonized_basis=None, + already_echelonized=False, + category=None): r""" See :class:`FreeModule_submodule_with_basis_pid` for documentation. @@ -6730,11 +6741,11 @@ def _echelonized_basis(self, ambient, basis): MAT = sage.matrix.matrix_space.MatrixSpace( ambient.base_ring(), len(basis), ambient.degree(), sparse=ambient.is_sparse()) if d != 1: - basis = [x*d for x in basis] + basis = [x * d for x in basis] A = MAT(basis) E = A.echelon_form() if d != 1: - E = E.matrix_over_field()*(~d) # divide out denominator + E = E.matrix_over_field() * (~d) # divide out denominator r = E.rank() if r < E.nrows(): E = E.matrix_from_rows(range(r)) @@ -6765,7 +6776,7 @@ def _denominator(self, B): d = B[0].denominator() from sage.arith.functions import lcm for x in B[1:]: - d = lcm(d,x.denominator()) + d = lcm(d, x.denominator()) return d def _repr_(self): @@ -7093,10 +7104,11 @@ def user_to_echelon_matrix(self): if self.base_ring().is_field(): self.__user_to_echelon_matrix = self._user_to_rref_matrix() else: - rows = sum([self.echelon_coordinates(b,check=False) for b in self.basis()], []) + rows = sum([self.echelon_coordinates(b, check=False) + for b in self.basis()], []) M = sage.matrix.matrix_space.MatrixSpace(self.base_ring().fraction_field(), - self.dimension(), - sparse=self.is_sparse()) + self.dimension(), + sparse=self.is_sparse()) self.__user_to_echelon_matrix = M(rows) return self.__user_to_echelon_matrix @@ -7602,8 +7614,9 @@ def __init__(self, ambient, gens, check=True, already_echelonized=False, [0 3 6] """ FreeModule_submodule_with_basis_pid.__init__(self, ambient, basis=gens, - echelonize=True, already_echelonized=already_echelonized, - category=category) + echelonize=True, + already_echelonized=already_echelonized, + category=category) def _repr_(self): """ @@ -7742,8 +7755,9 @@ class FreeModule_submodule_with_basis_field(FreeModule_generic_field, FreeModule sage: TestSuite(W).run() """ def __init__(self, ambient, basis, check=True, - echelonize=False, echelonized_basis=None, already_echelonized=False, - category=None): + echelonize=False, echelonized_basis=None, + already_echelonized=False, + category=None): """ Create a vector space with given basis. @@ -7830,12 +7844,12 @@ def _repr_(self): """ if self.is_sparse(): return "Sparse vector space of degree %s and dimension %s over %s\n" % ( - self.degree(), self.dimension(), self.base_field()) + \ - "User basis matrix:\n%r" % self.basis_matrix() - else: - return "Vector space of degree %s and dimension %s over %s\n" % ( - self.degree(), self.dimension(), self.base_field()) + \ - "User basis matrix:\n%r" % self.basis_matrix() + self.degree(), self.dimension(), self.base_field()) + \ + "User basis matrix:\n%r" % self.basis_matrix() + + return "Vector space of degree %s and dimension %s over %s\n" % ( + self.degree(), self.dimension(), self.base_field()) + \ + "User basis matrix:\n%r" % self.basis_matrix() def _denominator(self, B): """ @@ -7957,11 +7971,13 @@ def __init__(self, ambient, gens, check=True, already_echelonized=False, categor """ if is_FreeModule(gens): gens = gens.gens() - FreeModule_submodule_with_basis_field.__init__(self, ambient, basis=gens, check=check, - echelonize=not already_echelonized, already_echelonized=already_echelonized, - category=category) + FreeModule_submodule_with_basis_field.__init__(self, ambient, + basis=gens, check=check, + echelonize=not already_echelonized, + already_echelonized=already_echelonized, + category=category) - def _repr_(self): + def _repr_(self) -> str: """ The default printing representation of self. @@ -8255,7 +8271,7 @@ def element_class(R, is_sparse): @richcmp_method -class EchelonMatrixKey(): +class EchelonMatrixKey: r""" A total ordering on free modules for sorting. diff --git a/src/sage/modules/matrix_morphism.py b/src/sage/modules/matrix_morphism.py index 868da6f88a5..39ccb8bbb91 100644 --- a/src/sage/modules/matrix_morphism.py +++ b/src/sage/modules/matrix_morphism.py @@ -71,6 +71,7 @@ def is_MatrixMorphism(x): """ return isinstance(x, MatrixMorphism_abstract) + class MatrixMorphism_abstract(sage.categories.morphism.Morphism): def __init__(self, parent, side='left'): """ @@ -188,7 +189,7 @@ def _call_(self, x): if parent(x) is not self.domain(): x = self.domain()(x) except TypeError: - raise TypeError("%s must be coercible into %s" % (x,self.domain())) + raise TypeError("%s must be coercible into %s" % (x, self.domain())) if self.domain().is_ambient(): x = x.element() else: @@ -860,12 +861,12 @@ def decomposition(self, *args, **kwds): ] """ if not self.is_endomorphism(): - raise ArithmeticError("Matrix morphism must be an endomorphism.") + raise ArithmeticError("matrix morphism must be an endomorphism") D = self.domain() if self.side() == "left": - E = self.matrix().decomposition(*args,**kwds) + E = self.matrix().decomposition(*args, **kwds) else: - E = self.matrix().transpose().decomposition(*args,**kwds) + E = self.matrix().transpose().decomposition(*args, **kwds) if D.is_ambient(): return Sequence([D.submodule(V, check=False) for V, _ in E], cr=True, check=False) @@ -899,7 +900,7 @@ def det(self): 2 """ if not self.is_endomorphism(): - raise ArithmeticError("Matrix morphism must be an endomorphism.") + raise ArithmeticError("matrix morphism must be an endomorphism") return self.matrix().determinant() def fcp(self, var='x'): @@ -1635,7 +1636,7 @@ def __init__(self, parent, A, copy_matrix=True, side='left'): if A.nrows() != parent.domain().rank(): raise ArithmeticError("number of rows of matrix (={}) must equal rank of domain (={})".format(A.nrows(), parent.domain().rank())) if A.ncols() != parent.codomain().rank(): - raise ArithmeticError("number of columns of matrix (={}) must equal rank of codomain (={})".format(A.ncols(), parent.codomain().rank())) + raise ArithmeticError("number of columns of matrix (={}) must equal rank of codomain (={})".format(A.ncols(), parent.codomain().rank())) if side == "right": if A.nrows() != parent.codomain().rank(): raise ArithmeticError("number of rows of matrix (={}) must equal rank of codomain (={})".format(A.nrows(), parent.domain().rank())) @@ -1697,7 +1698,7 @@ def matrix(self, side=None): ValueError: side must be 'left' or 'right', not junk """ if side not in ['left', 'right', None]: - raise ValueError("side must be 'left' or 'right', not {0}".format(side)) + raise ValueError("side must be 'left' or 'right', not {}".format(side)) if side == self.side() or side is None: return self._matrix return self._matrix.transpose() @@ -1794,7 +1795,7 @@ def _repr_(self): sage: phi._repr_() 'Free module morphism defined by the matrix\n[3 0]\n[0 2]\nDomain: Ambient free module of rank 2 over the principal ideal domain Integer Ring\nCodomain: Ambient free module of rank 2 over the principal ideal domain Integer Ring' """ - rep = "Morphism defined by the matrix\n{0}".format(self.matrix()) + rep = "Morphism defined by the matrix\n{}".format(self.matrix()) if self._side == 'right': rep += " acting by multiplication on the left" return rep diff --git a/src/sage/modules/vector_space_morphism.py b/src/sage/modules/vector_space_morphism.py index 1102d15968e..6585da0b047 100644 --- a/src/sage/modules/vector_space_morphism.py +++ b/src/sage/modules/vector_space_morphism.py @@ -705,9 +705,9 @@ def linear_transformation(arg0, arg1=None, arg2=None, side='left'): Vector_callable_symbolic_dense = () if side not in ['left', 'right']: - raise ValueError("side must be 'left' or 'right', not {0}".format(side)) + raise ValueError("side must be 'left' or 'right', not {}".format(side)) if not (is_Matrix(arg0) or is_VectorSpace(arg0)): - raise TypeError('first argument must be a matrix or a vector space, not {0}'.format(arg0)) + raise TypeError('first argument must be a matrix or a vector space, not {}'.format(arg0)) if is_Matrix(arg0): R = arg0.base_ring() if not R.is_field(): @@ -763,7 +763,7 @@ def linear_transformation(arg0, arg1=None, arg2=None, side='left'): msg = 'symbolic function must be linear in all the inputs:\n' + e.args[0] raise ValueError(msg) # have matrix with respect to standard bases, now consider user bases - images = [v*arg2 for v in D.basis()] + images = [v * arg2 for v in D.basis()] try: arg2 = matrix([C.coordinates(C(a)) for a in images]) except (ArithmeticError, TypeError) as e: @@ -779,7 +779,8 @@ def linear_transformation(arg0, arg1=None, arg2=None, side='left'): # __init__ will check matrix sizes versus domain/codomain dimensions return H(arg2) -def is_VectorSpaceMorphism(x): + +def is_VectorSpaceMorphism(x) -> bool: r""" Returns ``True`` if ``x`` is a vector space morphism (a linear transformation). @@ -859,7 +860,7 @@ def __init__(self, homspace, A, side="left"): """ if not vector_space_homspace.is_VectorSpaceHomspace(homspace): - raise TypeError('homspace must be a vector space hom space, not {0}'.format(homspace)) + raise TypeError('homspace must be a vector space hom space, not {}'.format(homspace)) if isinstance(A, matrix_morphism.MatrixMorphism): A = A.matrix() if not is_Matrix(A): From 1ceae66ba2eb81767ec93df511b05f3c44fc7b88 Mon Sep 17 00:00:00 2001 From: Sebastian Spindler Date: Wed, 14 Feb 2024 12:37:23 +0100 Subject: [PATCH 114/278] Address issue #37337 This is not a fix for the issue - it just adapts the error warning to be more ambiguous, as suggested in https://github.com/sagemath/sage/pull/34976#issuecomment-1937007360 --- src/sage/algebras/quatalg/quaternion_algebra.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index 03165a770b4..2cb09c0b327 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -2130,6 +2130,9 @@ def isomorphism_to(self, other, *, conjugator=False): This method is currently only implemented for maximal orders in definite quaternion orders over `\QQ`. For a general algorithm, see [KV2010]_ (Problem ``IsConjugate``). + Currently not working for all maximal orders, see + https://github.com/sagemath/sage/issues/37337 + for more details. EXAMPLES:: @@ -2246,7 +2249,7 @@ def isomorphism_to(self, other, *, conjugator=False): sage: O1.isomorphism_to(O2) Traceback (most recent call last): ... - ValueError: quaternion orders not isomorphic + NotImplementedError: isomorphism_to was not able to recognize the given orders as isomorphic ALGORITHM: @@ -2272,7 +2275,7 @@ def isomorphism_to(self, other, *, conjugator=False): I = N * self * other gamma = I.minimal_element() if self*gamma != I: - raise ValueError('quaternion orders not isomorphic') + raise NotImplementedError('isomorphism_to was not able to recognize the given orders as isomorphic') assert gamma*other == I if conjugator: From 43e48f7eba72da929e9ef7a4daa524c718f34c5a Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Wed, 14 Feb 2024 20:34:14 +0000 Subject: [PATCH 115/278] `sage.schemes.generic`: fix docs --- src/sage/schemes/generic/glue.py | 41 ++++++++++++++++++++++++---- src/sage/schemes/generic/homset.py | 28 +++++++++---------- src/sage/schemes/generic/morphism.py | 14 +++++----- src/sage/schemes/generic/point.py | 25 ----------------- src/sage/schemes/generic/scheme.py | 6 ++-- 5 files changed, 60 insertions(+), 54 deletions(-) diff --git a/src/sage/schemes/generic/glue.py b/src/sage/schemes/generic/glue.py index 76bd9a1ab9e..8778800f8c2 100644 --- a/src/sage/schemes/generic/glue.py +++ b/src/sage/schemes/generic/glue.py @@ -16,19 +16,35 @@ class GluedScheme(scheme.Scheme): INPUT: - - ``f`` - open immersion from a scheme U to a scheme - X + - ``f`` - open immersion from a scheme `U` to a scheme + `X` - - ``g`` - open immersion from U to a scheme Y + - ``g`` - open immersion from `U` to a scheme `Y` - OUTPUT: The scheme obtained by gluing X and Y along the open set - U. + OUTPUT: The scheme obtained by gluing `X` and `Y` along the open set + `U`. .. note:: Checking that `f` and `g` are open immersions is not implemented. + + EXAMPLES:: + + sage: R. = QQ[] + sage: S. = R.quotient(x * y - 1) + sage: Rx = QQ["x"] + sage: Ry = QQ["y"] + sage: phi_x = Rx.hom([xbar]) + sage: phi_y = Ry.hom([ybar]) + sage: Sx = Schemes()(phi_x) + sage: Sy = Schemes()(phi_y) + sage: Sx.glue_along_domains(Sy) + Scheme obtained by gluing X and Y along U, where + X: Spectrum of Univariate Polynomial Ring in x over Rational Field + Y: Spectrum of Univariate Polynomial Ring in y over Rational Field + U: Spectrum of Quotient of Multivariate Polynomial Ring in x, y over Rational Field by the ideal (x*y - 1) """ def __init__(self, f, g, check=True): if check: @@ -42,6 +58,21 @@ def __init__(self, f, g, check=True): self.__g = g def gluing_maps(self): + r""" + Return the gluing maps of this glued scheme, i.e. the maps `f` and `g`. + + EXAMPLES:: + sage: S. = R.quotient(x * y - 1) + sage: Rx = QQ["x"] + sage: Ry = QQ["y"] + sage: phi_x = Rx.hom([xbar]) + sage: phi_y = Ry.hom([ybar]) + sage: Sx = Schemes()(phi_x) + sage: Sy = Schemes()(phi_y) + sage: Sxy = Sx.glue_along_domains(Sy) + sage: Sxy.gluing_maps() == (Sx, Sy) + True + """ return self.__f, self.__g def _repr_(self): diff --git a/src/sage/schemes/generic/homset.py b/src/sage/schemes/generic/homset.py index a9a0f0735df..2b36411d3f2 100644 --- a/src/sage/schemes/generic/homset.py +++ b/src/sage/schemes/generic/homset.py @@ -2,13 +2,13 @@ Set of homomorphisms between two schemes For schemes `X` and `Y`, this module implements the set of morphisms -`Hom(X,Y)`. This is done by :class:`SchemeHomset_generic`. +`\mathrm{Hom}(X,Y)`. This is done by :class:`SchemeHomset_generic`. -As a special case, the Hom-sets can also represent the points of a -scheme. Recall that the `K`-rational points of a scheme `X` over `k` -can be identified with the set of morphisms `Spec(K) \to X`. In Sage -the rational points are implemented by such scheme morphisms. This is -done by :class:`SchemeHomset_points` and its subclasses. +As a special case, the Hom-sets can also represent the points of a scheme. +Recall that the `K`-rational points of a scheme `X` over `k` can be identified +with the set of morphisms `\mathrm{Spec}(K) \to X`. In Sage the rational points +are implemented by such scheme morphisms. This is done by +:class:`SchemeHomset_points` and its subclasses. .. note:: @@ -407,12 +407,12 @@ def _element_constructor_(self, x, check=True): # ******************************************************************* class SchemeHomset_points(SchemeHomset_generic): - """ + r""" Set of rational points of the scheme. - Recall that the `K`-rational points of a scheme `X` over `k` can - be identified with the set of morphisms `Spec(K) \to X`. In Sage, - the rational points are implemented by such scheme morphisms. + Recall that the `K`-rational points of a scheme `X` over `k` can be + identified with the set of morphisms `\mathrm{Spec}(K) \to X`. In Sage, the + rational points are implemented by such scheme morphisms. If a scheme has a finite number of points, then the homset is supposed to implement the Python iterator interface. See @@ -659,13 +659,13 @@ def _element_constructor_(self, *v, **kwds): return self.codomain()._point(self, v, **kwds) def extended_codomain(self): - """ + r""" Return the codomain with extended base, if necessary. OUTPUT: The codomain scheme, with its base ring extended to the - codomain. That is, the codomain is of the form `Spec(R)` and + codomain. That is, the codomain is of the form `\mathrm{Spec}(R)` and the base ring of the domain is extended to `R`. EXAMPLES:: @@ -710,8 +710,8 @@ def _repr_(self): return 'Set of rational points of '+str(self.extended_codomain()) def value_ring(self): - """ - Return `R` for a point Hom-set `X(Spec(R))`. + r""" + Return `R` for a point Hom-set `X(\mathrm{Spec}(R))`. OUTPUT: diff --git a/src/sage/schemes/generic/morphism.py b/src/sage/schemes/generic/morphism.py index 4405268fed6..3e4f310d3c8 100644 --- a/src/sage/schemes/generic/morphism.py +++ b/src/sage/schemes/generic/morphism.py @@ -28,12 +28,12 @@ new Hom-set class does not use ``MyScheme._morphism`` then you do not have to provide it. -Note that points on schemes are morphisms `Spec(K)\to X`, too. But we -typically use a different notation, so they are implemented in a -different derived class. For this, you should implement a method +Note that points on schemes are morphisms `\mathrm{Spec}(K)\to X`, too. But we +typically use a different notation, so they are implemented in a different +derived class. For this, you should implement a method -* ``MyScheme._point(*args, **kwds)`` returning a point, that is, - a morphism `Spec(K)\to X`. Your point class should derive from +* ``MyScheme._point(*args, **kwds)`` returning a point, that is, a morphism + `\mathrm{Spec}(K)\to X`. Your point class should derive from :class:`SchemeMorphism_point`. Optionally, you can also provide a special Hom-set for the points, for @@ -1790,11 +1790,11 @@ def __init__(self, X): ############################################################################ class SchemeMorphism_point(SchemeMorphism): - """ + r""" Base class for rational points on schemes. Recall that the `K`-rational points of a scheme `X` over `k` can - be identified with the set of morphisms `Spec(K) \to X`. In Sage, + be identified with the set of morphisms `\mathrm{Spec}(K) \to X`. In Sage, the rational points are implemented by such scheme morphisms. EXAMPLES:: diff --git a/src/sage/schemes/generic/point.py b/src/sage/schemes/generic/point.py index c65963e3eaa..e85d8218f0d 100644 --- a/src/sage/schemes/generic/point.py +++ b/src/sage/schemes/generic/point.py @@ -227,28 +227,3 @@ def _richcmp_(self, other, op): False """ return richcmp(self.__P, other.__P, op) - -######################################################## -# Points on a scheme defined by a morphism -######################################################## - -def is_SchemeRationalPoint(x): - return isinstance(x, SchemeRationalPoint) - -class SchemeRationalPoint(SchemePoint): - def __init__(self, f): - """ - INPUT: - - - - ``f`` - a morphism of schemes - """ - SchemePoint.__init__(self, f.codomain(), parent=f.parent()) - self.__f = f - - def _repr_(self): - return "Point on %s defined by the morphism %s" % (self.scheme(), - self.morphism()) - - def morphism(self): - return self.__f diff --git a/src/sage/schemes/generic/scheme.py b/src/sage/schemes/generic/scheme.py index 25a67a2c4c4..dc98baed5c7 100644 --- a/src/sage/schemes/generic/scheme.py +++ b/src/sage/schemes/generic/scheme.py @@ -77,7 +77,7 @@ class Scheme(Parent): sage: ProjectiveSpace(4, QQ).category() Category of schemes over Rational Field - There is a special and unique `Spec(\ZZ)` that is the default base + There is a special and unique `\mathrm{Spec}(\ZZ)` that is the default base scheme:: sage: Spec(ZZ).base_scheme() is Spec(QQ).base_scheme() @@ -267,7 +267,7 @@ def __call__(self, *args): @cached_method def point_homset(self, S=None): - """ + r""" Return the set of S-valued points of this scheme. INPUT: @@ -276,7 +276,7 @@ def point_homset(self, S=None): OUTPUT: - The set of morphisms `Spec(S)\to X`. + The set of morphisms `\mathrm{Spec}(S) \to X`. EXAMPLES:: From bacf72895e259398481a075ce0be223d23866846 Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Thu, 15 Feb 2024 08:46:59 +0000 Subject: [PATCH 116/278] =?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/schemes/generic/glue.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/sage/schemes/generic/glue.py b/src/sage/schemes/generic/glue.py index 8778800f8c2..38a17c0d78b 100644 --- a/src/sage/schemes/generic/glue.py +++ b/src/sage/schemes/generic/glue.py @@ -16,16 +16,16 @@ class GluedScheme(scheme.Scheme): INPUT: - - ``f`` - open immersion from a scheme `U` to a scheme + - ``f`` -- open immersion from a scheme `U` to a scheme `X` - - ``g`` - open immersion from `U` to a scheme `Y` + - ``g`` -- open immersion from `U` to a scheme `Y` OUTPUT: The scheme obtained by gluing `X` and `Y` along the open set `U`. - .. note:: + .. NOTE:: Checking that `f` and `g` are open immersions is not implemented. @@ -62,6 +62,7 @@ def gluing_maps(self): Return the gluing maps of this glued scheme, i.e. the maps `f` and `g`. EXAMPLES:: + sage: S. = R.quotient(x * y - 1) sage: Rx = QQ["x"] sage: Ry = QQ["y"] From b272b1c4dc6c7df379685a40744ffba7c9b5f2b8 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 8 Feb 2024 18:57:43 -0800 Subject: [PATCH 117/278] src/doc/Makefile: Reduce latex's verbosity for PDF build --- src/doc/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/doc/Makefile b/src/doc/Makefile index 912eb5cb0e9..2d433b3727c 100644 --- a/src/doc/Makefile +++ b/src/doc/Makefile @@ -69,7 +69,7 @@ doc-html: doc-html-reference doc-html-other # Matches doc-pdf--developer, doc-pdf--reference-manifolds etc. doc-pdf--%: - sage --docbuild $(subst -,/,$(subst doc-pdf--,,$@)) pdf $(SAGE_DOCBUILD_OPTS) + LATEXOPTS="--file-line-error --interaction=batchmode" sage --docbuild $(subst -,/,$(subst doc-pdf--,,$@)) pdf $(SAGE_DOCBUILD_OPTS) # reference manual, pdf doc-pdf-reference: doc-inventory-reference From 9ced7837d2c884d1fe0d5a1407c79c3ea007b6e6 Mon Sep 17 00:00:00 2001 From: Ruchit Jagodara Date: Thu, 15 Feb 2024 21:47:45 +0530 Subject: [PATCH 118/278] Use _from_permutation_group_element function instead of updating x Co-authored-by: Travis Scrimshaw --- 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 1e213f2d5ad..665faba8985 100644 --- a/src/sage/combinat/permutation.py +++ b/src/sage/combinat/permutation.py @@ -7102,7 +7102,7 @@ def _element_constructor_(self, x, check=True): [4, 5, 1, 2, 3] """ if isinstance(x, PermutationGroupElement): - x = x.domain() + return self. _from_permutation_group_element(x) if len(x) < self.n: x = list(x) + list(range(len(x) + 1, self.n + 1)) return self.element_class(self, x, check=check) From 7328410da91d8001e338c959ac812bf8e81e6451 Mon Sep 17 00:00:00 2001 From: RuchitJagodara Date: Thu, 15 Feb 2024 21:51:52 +0530 Subject: [PATCH 119/278] Updated the test case --- src/sage/combinat/permutation.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sage/combinat/permutation.py b/src/sage/combinat/permutation.py index 665faba8985..748f3197177 100644 --- a/src/sage/combinat/permutation.py +++ b/src/sage/combinat/permutation.py @@ -7095,11 +7095,11 @@ def _element_constructor_(self, x, check=True): Ensure that :issue:`37284` is fixed:: - sage: S5 = SymmetricGroup(5) + sage: PG = PermutationGroup(SymmetricGroup(5).gens()) sage: P5 = Permutations(5) - sage: p = S5.list()[3] - sage: P5(p) - [4, 5, 1, 2, 3] + sage: p = PG.list()[0] + sage: s = P5(p); s + [1, 2, 3, 4, 5] """ if isinstance(x, PermutationGroupElement): return self. _from_permutation_group_element(x) From 561725608fbfd327fb7877268d08aa515325b70e Mon Sep 17 00:00:00 2001 From: jtcc2 <93992456+jtcc2@users.noreply.github.com> Date: Thu, 15 Feb 2024 19:17:37 +0000 Subject: [PATCH 120/278] Fixed isomorphism_to bug --- .../algebras/quatalg/quaternion_algebra.py | 74 ++++++++++++++----- 1 file changed, 57 insertions(+), 17 deletions(-) diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index 2cb09c0b327..82c5c81b8ee 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -2114,16 +2114,20 @@ def ternary_quadratic_form(self, include_basis=False): else: return Q - def isomorphism_to(self, other, *, conjugator=False): + def isomorphism_to(self, other, *, conjugator=False, B=10): r""" Compute an isomorphism from this quaternion order `O` to another order `O'` in the same quaternion algebra. - If the optional keyword argument ``conjugator`` is set - to ``True``, this method returns a single quaternion - `\gamma \in O \cap O'` of minimal norm such that - `O' = \gamma^{-1} O \gamma`, - rather than the ring isomorphism it defines. + INPUT: + + - ``conjugator`` -- bool (default: False), if True this + method returns a single quaternion `\gamma \in O \cap O'` + of minimal norm such that `O' = \gamma^{-1} O \gamma`, + rather than the ring isomorphism it defines. + + - ``B`` -- postive integer, bound on theta series + coefficients to rule out non isomorphic orders. .. NOTE:: @@ -2257,7 +2261,24 @@ def isomorphism_to(self, other, *, conjugator=False): where `N = [O : O cap O']` using :meth:`QuaternionFractionalIdeal_rational.minimal_element()`. An isomorphism is given by conjugation by such an element. - """ + Works providing reduced norm of conjugation element is not + a ramified prime times a square. To cover cases where it is + we repeat the check for orders conjugated by i, j, and k. + """ + + # Method to find isomorphism, which does not work when O2 is + # O1 conjugated by an alpha such that nrd(alpha) is a + # ramified prime times a square + def attempt_isomorphism(self, other): + N = self.intersection(other).free_module().index_in(self.free_module()) + I = N * self * other + gamma = I.miminal_element() + if self*gamma != I: + return False, None + if gamma*other != I: + return False, None + return True, gamma + if not isinstance(other, QuaternionOrder): raise TypeError('not a quaternion order') Q = self.quaternion_algebra() @@ -2271,18 +2292,37 @@ def isomorphism_to(self, other, *, conjugator=False): if not (self.discriminant() == Q.discriminant() == other.discriminant()): raise NotImplementedError('only implemented for maximal orders') - N = self.intersection(other).free_module().index_in(self.free_module()) - I = N * self * other - gamma = I.minimal_element() - if self*gamma != I: - raise NotImplementedError('isomorphism_to was not able to recognize the given orders as isomorphic') - assert gamma*other == I + # First try a theta series check, up to bound B + if self.unit_ideal().theta_series_vector(B) != other.unit_ideal().theta_series_vector(B): + raise ValueError('quaternion orders not isomorphic') + + # Want to iterate over elements alpha where the square-free part of nrd(alpha) divides prod(Q.ramified_primes()), + # and each time try attempt_isomorphism with the order conjugated by alpha. + # But in general finding all such elements alpha is hard, + # so we just try 1, i, j, k first. + for alpha in [1] + Q.gens(): + other_conj = other + if alpha != 1: + other_conj = Q.quaternion_order((alpha.inverse() * other * alpha).basis()) + found, gamma = attempt_isomorphism(self, other_conj) + if found: + if conjugator: + return gamma + else: + ims = [~gamma * gen * gamma for gen in Q.gens()] + return self.hom(ims, other, check=False) + + # We can tell if 1, i, j, k cover all the alpha we need to test, + # by checking if we have additional ramified primes which are not the square-free parts of nrd(i), nrd(j) or nrd(k) + a, b = -Q.invariants()[0], -Q.invariants()[1] + square_free_invariants = [a.squarefree_part(), b.squarefree_part(), (a*b).squarefree_part()] + is_result_guaranteed = len([a for a in Q.ramified_primes() if a not in square_free_invariants]) == 0 - if conjugator: - return gamma + if is_result_guaranteed: + raise ValueError('quaternion orders not isomorphic') - ims = [~gamma * gen * gamma for gen in Q.gens()] - return self.hom(ims, other, check=False) + # Otherwise, there might be other unknown alpha's giving isomorphism. If so we can't find them. + raise NotImplementedError("isomorphism_to was not able to recognize the given orders as isomorphic") class QuaternionFractionalIdeal(Ideal_fractional): From cf070c96d812074119f36e56c823f4abeacdf67a Mon Sep 17 00:00:00 2001 From: jtcc2 <93992456+jtcc2@users.noreply.github.com> Date: Thu, 15 Feb 2024 19:32:13 +0000 Subject: [PATCH 121/278] Fixed lint --- 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 82c5c81b8ee..3902b1094ce 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -2265,7 +2265,7 @@ def isomorphism_to(self, other, *, conjugator=False, B=10): a ramified prime times a square. To cover cases where it is we repeat the check for orders conjugated by i, j, and k. """ - + # Method to find isomorphism, which does not work when O2 is # O1 conjugated by an alpha such that nrd(alpha) is a # ramified prime times a square From 57d933ad8e868c04f868da18983a2513905318db Mon Sep 17 00:00:00 2001 From: jtcc2 <93992456+jtcc2@users.noreply.github.com> Date: Thu, 15 Feb 2024 23:06:30 +0000 Subject: [PATCH 122/278] Added tests --- .../algebras/quatalg/quaternion_algebra.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index 3902b1094ce..3f0a80908f8 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -2134,9 +2134,6 @@ def isomorphism_to(self, other, *, conjugator=False, B=10): This method is currently only implemented for maximal orders in definite quaternion orders over `\QQ`. For a general algorithm, see [KV2010]_ (Problem ``IsConjugate``). - Currently not working for all maximal orders, see - https://github.com/sagemath/sage/issues/37337 - for more details. EXAMPLES:: @@ -2238,18 +2235,18 @@ def isomorphism_to(self, other, *, conjugator=False, B=10): :: sage: Quat. = QuaternionAlgebra(-1,-11) - sage: O1 = Quat.quaternion_order([1, i, j, k]) - sage: O2 = Quat.quaternion_order([1,-i, j,-k]) + sage: O1 = Quat.quaternion_order([1, i, (i+j)/2, (1+k)/2]) + sage: O2 = Quat.quaternion_order([1, (2+i+k)/4, (-11*i+2*j+k)/4, (-5*i+k)/3]) sage: O1.isomorphism_to(O2) Traceback (most recent call last): ... - NotImplementedError: only implemented for maximal orders + ValueError: quaternion orders not isomorphic :: - sage: Quat. = QuaternionAlgebra(-1,-11) - sage: O1 = Quat.quaternion_order([1, i, (i+j)/2, (1+k)/2]) - sage: O2 = Quat.quaternion_order([1, (2+i+k)/4, (-11*i+2*j+k)/4, (-5*i+k)/3]) + sage: Quat. = QuaternionAlgebra(-5, -17) + sage: O1 = Quat.quaternion_order([1, i, j, 1/2 + 1/2*i + 1/2*j + 1/2*k]) + sage: O2 = Quat.quaternion_order([1/2 + 1/2*i + 1/6*j + 13/6*k, i, 1/3*j + 4/3*k, 3*k]) sage: O1.isomorphism_to(O2) Traceback (most recent call last): ... @@ -2272,7 +2269,7 @@ def isomorphism_to(self, other, *, conjugator=False, B=10): def attempt_isomorphism(self, other): N = self.intersection(other).free_module().index_in(self.free_module()) I = N * self * other - gamma = I.miminal_element() + gamma = I.minimal_element() if self*gamma != I: return False, None if gamma*other != I: @@ -2300,7 +2297,7 @@ def attempt_isomorphism(self, other): # and each time try attempt_isomorphism with the order conjugated by alpha. # But in general finding all such elements alpha is hard, # so we just try 1, i, j, k first. - for alpha in [1] + Q.gens(): + for alpha in [1] + list(Q.gens()): other_conj = other if alpha != 1: other_conj = Q.quaternion_order((alpha.inverse() * other * alpha).basis()) From b7778fda40e6b8501ef8137421b86410cbe421ab Mon Sep 17 00:00:00 2001 From: jtcc2 <93992456+jtcc2@users.noreply.github.com> Date: Thu, 15 Feb 2024 23:31:51 +0000 Subject: [PATCH 123/278] Added tests --- src/sage/algebras/quatalg/quaternion_algebra.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index 3f0a80908f8..378bec04201 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -2232,6 +2232,16 @@ def isomorphism_to(self, other, *, conjugator=False, B=10): ... NotImplementedError: only implemented for definite quaternion orders + :: + + sage: Quat. = QuaternionAlgebra(-1,-11) + sage: O1 = Quat.quaternion_order([1, i, j, k]) + sage: O2 = Quat.quaternion_order([1,-i, j,-k]) + sage: O1.isomorphism_to(O2) + Traceback (most recent call last): + ... + NotImplementedError: only implemented for maximal orders + :: sage: Quat. = QuaternionAlgebra(-1,-11) From 62437bbcd34c3314606e61a8b072f3e7eb2f6b43 Mon Sep 17 00:00:00 2001 From: Omegaconstant Date: Fri, 16 Feb 2024 18:52:07 +0530 Subject: [PATCH 124/278] Fixed the documentation --- src/sage/sets/recursively_enumerated_set.pyx | 33 +++++++++++++++----- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/src/sage/sets/recursively_enumerated_set.pyx b/src/sage/sets/recursively_enumerated_set.pyx index 5ecb32bdb1a..c38cd85ef85 100644 --- a/src/sage/sets/recursively_enumerated_set.pyx +++ b/src/sage/sets/recursively_enumerated_set.pyx @@ -25,7 +25,7 @@ No hypothesis on the structure What we mean by "no hypothesis" is that the set is not known to be a forest, symmetric, or graded. However, it may have other -structures, such as not containing an oriented cycle, that do not +structure, such as not containing an oriented cycle, that does not help with the enumeration. In this example, the seed is 0 and the successor function is either ``+2`` @@ -171,8 +171,25 @@ Only two things are necessary to define a set using a :class:`RecursivelyEnumeratedSet` object (the other classes being very similar): -.. figure:: ../../media/recursively-enumerated-set.png - :scale: 67 % +.. MATH:: + + \begin{array}{rcl} + & ^{``}\hspace{0.5em}^{"} \\ + \swarrow & \downarrow & \searrow \\[5pt] + ^{``}a^{"} \hspace{3.125em} & ^{``}b^{"} & \hspace{3.125em}^{``}c^{"} \\ + \begin{array}{rcl} + \swarrow & \downarrow & \searrow \\[5pt] + ^{``}aa^{"}\: & ^{``}ab^{"} & \:^{``}ac^{"} \\ + \end{array} & + \begin{array}{rcl} + \swarrow & \downarrow & \searrow \\[5pt] + ^{``}ba^{"}\: & ^{``}bb^{"} & \:^{``}bc^{"} \\ + \end{array} & + \begin{array}{rcl} + \swarrow & \downarrow & \searrow \\[5pt] + ^{``}ca^{"}\: & ^{``}cb^{"} & \:^{``}cc^{"} \\ + \end{array} + \end{array} For the previous example, the two necessary pieces of information are: @@ -369,7 +386,7 @@ def RecursivelyEnumeratedSet(seeds, successors, structure=None, .. WARNING:: - If you do not set a good structure, you might obtain bad results, + If you do not set a valid structure, you might obtain bad results, like elements generated twice:: sage: f = lambda a: [a-1,a+1] @@ -1537,9 +1554,9 @@ def search_forest_iterator(roots, children, algorithm='depth'): [0, 1, 2], [0, 2, 1], [1, 0, 2], [1, 2, 0], [2, 0, 1], [2, 1, 0]] """ # Little trick: the same implementation handles both depth and - # breadth first search. Setting the position to -1 makes a depth search + # breadth first search. Setting position to -1 initiates a depth search # (you ask the children for the last node you met). Setting - # position on 0 makes a breadth search (enumerate all the + # position on 0 initiates a breadth search (enumerate all the # descendants of a node before going on to the next father) if algorithm == 'depth': position = -1 @@ -1884,7 +1901,7 @@ class RecursivelyEnumeratedSet_forest(Parent): r""" Return an iterator over the elements of ``self`` of given depth. An element of depth `n` can be obtained by applying the - children function `n` times from the root. This function is not affected + children function `n` times from a root. This function is not affected by post processing. EXAMPLES:: @@ -1915,7 +1932,7 @@ class RecursivelyEnumeratedSet_forest(Parent): r""" Return an iterator over the elements of ``self`` of given depth. An element of depth `n` can be obtained by applying the - children function `n` times from the root. + children function `n` times from a root. EXAMPLES:: From 5014db8c04ca6441ee3d1710db930d0761101d03 Mon Sep 17 00:00:00 2001 From: Omegaconstant Date: Fri, 16 Feb 2024 19:00:10 +0530 Subject: [PATCH 125/278] Removed the unnecessary sets/media folder --- .../sets/media/recursively-enumerated-set.pdf | Bin 22396 -> 0 bytes .../sets/media/recursively-enumerated-set.png | Bin 27161 -> 0 bytes 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/doc/en/reference/sets/media/recursively-enumerated-set.pdf delete mode 100644 src/doc/en/reference/sets/media/recursively-enumerated-set.png diff --git a/src/doc/en/reference/sets/media/recursively-enumerated-set.pdf b/src/doc/en/reference/sets/media/recursively-enumerated-set.pdf deleted file mode 100644 index 0c14b3febc842e68b18a71e0ff5f3ea4daf03254..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22396 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%0Af?L+sv6P+tui)R+b=#Y-}v|21Yg3VH#|ZcCI~p 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 diff --git a/src/doc/en/reference/sets/media/recursively-enumerated-set.png b/src/doc/en/reference/sets/media/recursively-enumerated-set.png deleted file mode 100644 index 54864b24b6fdddce40e5aab67704f859e9bfd6ad..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 27161 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 From e5407aff98cee74c99a1c1935576ba7fdfa49ed2 Mon Sep 17 00:00:00 2001 From: jtcc2 <93992456+jtcc2@users.noreply.github.com> Date: Fri, 16 Feb 2024 22:16:14 +0000 Subject: [PATCH 126/278] Small correction --- src/sage/algebras/quatalg/quaternion_algebra.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index 378bec04201..d4d686749d9 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -2200,6 +2200,15 @@ def isomorphism_to(self, other, *, conjugator=False, B=10): sage: {iso(g * h) == iso(g) * iso(h) for g in els for h in els} {True} + Test edge case:: + + sage: Quat. = QuaternionAlgebra(419) + sage: O = Quat.quaternion_order([1/2 + 3/2*j + k, 1/18*i + 25/9*j + 5/6*k, 3*j + 2*k, 3*k]) + sage: Oconj = j.inverse() * O * j + sage: Oconj = Quat.quaternion_order(Oconj.basis()) + sage: O.isomorphism_to(Oconj, conjugator=True) + -j + Test error cases:: sage: Quat. = QuaternionAlgebra(-1,-11) @@ -2273,7 +2282,7 @@ def isomorphism_to(self, other, *, conjugator=False, B=10): we repeat the check for orders conjugated by i, j, and k. """ - # Method to find isomorphism, which does not work when O2 is + # Method to find isomorphism, which might not work when O2 is # O1 conjugated by an alpha such that nrd(alpha) is a # ramified prime times a square def attempt_isomorphism(self, other): @@ -2310,9 +2319,10 @@ def attempt_isomorphism(self, other): for alpha in [1] + list(Q.gens()): other_conj = other if alpha != 1: - other_conj = Q.quaternion_order((alpha.inverse() * other * alpha).basis()) + other_conj = Q.quaternion_order((alpha * other * alpha.inverse()).basis()) found, gamma = attempt_isomorphism(self, other_conj) if found: + gamma = gamma * alpha if conjugator: return gamma else: From ba1f5d78ad688760aa12e882aa26336a7085ccd4 Mon Sep 17 00:00:00 2001 From: Omegaconstant Date: Sat, 17 Feb 2024 09:28:52 +0530 Subject: [PATCH 127/278] Modified the Forest structure to display HTML nicely --- src/sage/sets/recursively_enumerated_set.pyx | 30 ++++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/sage/sets/recursively_enumerated_set.pyx b/src/sage/sets/recursively_enumerated_set.pyx index c38cd85ef85..08ab505a4e7 100644 --- a/src/sage/sets/recursively_enumerated_set.pyx +++ b/src/sage/sets/recursively_enumerated_set.pyx @@ -173,21 +173,21 @@ classes being very similar): .. MATH:: - \begin{array}{rcl} - & ^{``}\hspace{0.5em}^{"} \\ - \swarrow & \downarrow & \searrow \\[5pt] - ^{``}a^{"} \hspace{3.125em} & ^{``}b^{"} & \hspace{3.125em}^{``}c^{"} \\ - \begin{array}{rcl} - \swarrow & \downarrow & \searrow \\[5pt] - ^{``}aa^{"}\: & ^{``}ab^{"} & \:^{``}ac^{"} \\ - \end{array} & - \begin{array}{rcl} - \swarrow & \downarrow & \searrow \\[5pt] - ^{``}ba^{"}\: & ^{``}bb^{"} & \:^{``}bc^{"} \\ - \end{array} & - \begin{array}{rcl} - \swarrow & \downarrow & \searrow \\[5pt] - ^{``}ca^{"}\: & ^{``}cb^{"} & \:^{``}cc^{"} \\ + \begin{array}{ccc} + & ``\," \\ + \hfil\swarrow & \downarrow & \searrow\hfil\\ + ``a" & ``b" & ``c" \\ + \begin{array}{ccc} + \swarrow & \downarrow & \searrow \\ + ``aa" & ``ab" & ``ac" \\ + \end{array} & + \begin{array}{ccc} + \swarrow & \downarrow & \searrow \\ + ``ba" & ``bb" & ``bc" \\ + \end{array} & + \begin{array}{ccc} + \swarrow & \downarrow & \searrow \\ + ``ca" & ``cb" & ``cc" \\ \end{array} \end{array} From f8d9d70d8623ad91f5ecab561f48c537f1ec37d1 Mon Sep 17 00:00:00 2001 From: RuchitJagodara Date: Sun, 18 Feb 2024 15:19:45 +0530 Subject: [PATCH 128/278] Improve the quality of test case --- src/sage/combinat/permutation.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/sage/combinat/permutation.py b/src/sage/combinat/permutation.py index 748f3197177..12de6e95165 100644 --- a/src/sage/combinat/permutation.py +++ b/src/sage/combinat/permutation.py @@ -7097,9 +7097,10 @@ def _element_constructor_(self, x, check=True): sage: PG = PermutationGroup(SymmetricGroup(5).gens()) sage: P5 = Permutations(5) - sage: p = PG.list()[0] - sage: s = P5(p); s - [1, 2, 3, 4, 5] + sage: p = PG.an_element() + sage: x = PG([4,3,5,1,2]) + sage: s = P5(x); s + [4, 3, 5, 1, 2] """ if isinstance(x, PermutationGroupElement): return self. _from_permutation_group_element(x) From 0d2a9445f0479b6ae00751edd9d3095d583c5f4c Mon Sep 17 00:00:00 2001 From: RuchitJagodara Date: Sun, 18 Feb 2024 15:20:46 +0530 Subject: [PATCH 129/278] Added a line in test case to ensure that conversion was successfull --- src/sage/combinat/permutation.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/sage/combinat/permutation.py b/src/sage/combinat/permutation.py index 12de6e95165..fb67129e3d8 100644 --- a/src/sage/combinat/permutation.py +++ b/src/sage/combinat/permutation.py @@ -7101,6 +7101,8 @@ def _element_constructor_(self, x, check=True): sage: x = PG([4,3,5,1,2]) sage: s = P5(x); s [4, 3, 5, 1, 2] + sage: s.parent() + Standard permutations of 5 """ if isinstance(x, PermutationGroupElement): return self. _from_permutation_group_element(x) From 191993a82a0f24a7c44fbbdace09f4cccdb217db Mon Sep 17 00:00:00 2001 From: Omegaconstant Date: Sun, 18 Feb 2024 22:45:50 +0530 Subject: [PATCH 130/278] Removed some trailing spaces --- src/sage/sets/recursively_enumerated_set.pyx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/sets/recursively_enumerated_set.pyx b/src/sage/sets/recursively_enumerated_set.pyx index 08ab505a4e7..e40bf792f43 100644 --- a/src/sage/sets/recursively_enumerated_set.pyx +++ b/src/sage/sets/recursively_enumerated_set.pyx @@ -180,11 +180,11 @@ classes being very similar): \begin{array}{ccc} \swarrow & \downarrow & \searrow \\ ``aa" & ``ab" & ``ac" \\ - \end{array} & + \end{array} & \begin{array}{ccc} \swarrow & \downarrow & \searrow \\ ``ba" & ``bb" & ``bc" \\ - \end{array} & + \end{array} & \begin{array}{ccc} \swarrow & \downarrow & \searrow \\ ``ca" & ``cb" & ``cc" \\ From daf93c0f31277815f3926316bd1f13cd3e75a8a5 Mon Sep 17 00:00:00 2001 From: Giacomo Pope <44242839+GiacomoPope@users.noreply.github.com> Date: Thu, 22 Feb 2024 10:06:41 +0000 Subject: [PATCH 131/278] Update element_base.pyx --- src/sage/rings/finite_rings/element_base.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/finite_rings/element_base.pyx b/src/sage/rings/finite_rings/element_base.pyx index 25e7d65adad..684511abbd1 100755 --- a/src/sage/rings/finite_rings/element_base.pyx +++ b/src/sage/rings/finite_rings/element_base.pyx @@ -143,7 +143,7 @@ cdef class FiniteRingElement(CommutativeRingElement): raise ValueError("unknown algorithm") def to_bytes(self, byteorder="big"): - """ + r""" Return an array of bytes representing an integer. Internally relies on the python ``int.to_bytes()`` method. From b5af144361cb1e32e33da91f20df027731820a13 Mon Sep 17 00:00:00 2001 From: Giacomo Pope <44242839+GiacomoPope@users.noreply.github.com> Date: Thu, 22 Feb 2024 10:07:10 +0000 Subject: [PATCH 132/278] Update finite_field_base.pyx --- src/sage/rings/finite_rings/finite_field_base.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/finite_rings/finite_field_base.pyx b/src/sage/rings/finite_rings/finite_field_base.pyx index df1d49eb45f..8988718da18 100644 --- a/src/sage/rings/finite_rings/finite_field_base.pyx +++ b/src/sage/rings/finite_rings/finite_field_base.pyx @@ -2118,7 +2118,7 @@ cdef class FiniteField(Field): for col in B.columns()] def from_bytes(self, input_bytes, byteorder="big"): - """ + r""" Return the integer represented by the given array of bytes. Internally relies on the python ``int.from_bytes()`` method. From f6bb7c99a2ad44cd72eabe6563fd24fdfc1040a6 Mon Sep 17 00:00:00 2001 From: Giacomo Pope <44242839+GiacomoPope@users.noreply.github.com> Date: Thu, 22 Feb 2024 10:07:25 +0000 Subject: [PATCH 133/278] Update src/sage/rings/integer_ring.pyx Co-authored-by: Travis Scrimshaw --- 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 cc7ebced0a9..80a23287c36 100644 --- a/src/sage/rings/integer_ring.pyx +++ b/src/sage/rings/integer_ring.pyx @@ -1575,7 +1575,7 @@ cdef class IntegerRing_class(PrincipalIdealDomain): return pAdicValuation(self, p) def from_bytes(self, input_bytes, byteorder="big", is_signed=False): - """ + r""" Return the integer represented by the given array of bytes. Internally relies on the python ``int.from_bytes()`` method. From 0ea04b1c8af93190529e6707fd80bc9b452baaf3 Mon Sep 17 00:00:00 2001 From: grhkm21 <83517584+grhkm21@users.noreply.github.com> Date: Thu, 22 Feb 2024 21:20:29 +0000 Subject: [PATCH 134/278] Small wording change --- src/sage/rings/polynomial/polynomial_ring.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/rings/polynomial/polynomial_ring.py b/src/sage/rings/polynomial/polynomial_ring.py index 4c30b6b283f..08045542168 100644 --- a/src/sage/rings/polynomial/polynomial_ring.py +++ b/src/sage/rings/polynomial/polynomial_ring.py @@ -1391,8 +1391,8 @@ def random_element(self, degree=(-1, 2), monic=False, *args, **kwds): 6 If a tuple of two integers is given for the ``degree`` argument, a polynomial is chosen - among all polynomials with degree between them. If the base ring is uniform, then this is - also sampled uniformly:: + among all polynomials with degree between them. If the base ring can be sampled uniformly, + then this method also samples uniformly:: sage: R.random_element(degree=(0, 4)).degree() in range(0, 5) True From a0d5d4ab4cc66b92a3c4ed2a3ff6e5ea3ee0402d Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Fri, 23 Feb 2024 05:33:57 +0000 Subject: [PATCH 135/278] wrap some lines fix some indentation --- src/sage/rings/polynomial/polynomial_ring.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/sage/rings/polynomial/polynomial_ring.py b/src/sage/rings/polynomial/polynomial_ring.py index 08045542168..890b47c6be4 100644 --- a/src/sage/rings/polynomial/polynomial_ring.py +++ b/src/sage/rings/polynomial/polynomial_ring.py @@ -1390,9 +1390,10 @@ def random_element(self, degree=(-1, 2), monic=False, *args, **kwds): sage: R.random_element(6).degree() 6 - If a tuple of two integers is given for the ``degree`` argument, a polynomial is chosen - among all polynomials with degree between them. If the base ring can be sampled uniformly, - then this method also samples uniformly:: + If a tuple of two integers is given for the ``degree`` argument, a + polynomial is chosen among all polynomials with degree between them. If + the base ring can be sampled uniformly, then this method also samples + uniformly:: sage: R.random_element(degree=(0, 4)).degree() in range(0, 5) True @@ -1406,8 +1407,8 @@ def random_element(self, degree=(-1, 2), monic=False, *args, **kwds): sage: while R.random_element(degree=(-1,2), x=-1, y=1) != R.zero(): ....: pass - Monic polynomials are chosen among all monic polynomials with degree between the given - ``degree`` argument:: + Monic polynomials are chosen among all monic polynomials with degree + between the given ``degree`` argument:: sage: all(R.random_element(degree=(-1, 1), monic=True).is_monic() for _ in range(10^3)) True @@ -2477,8 +2478,8 @@ def lagrange_polynomial(self, points, algorithm="divided_difference", previous_r # P += (F[i] * prod) # return P -# using Neville's method for recursively generating the -# Lagrange interpolation polynomial + # using Neville's method for recursively generating the + # Lagrange interpolation polynomial elif algorithm == "neville": if previous_row is None: previous_row = [] From 9d1d0a1272570ba09244e61c520c24bc83130f7c Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Fri, 23 Feb 2024 14:52:32 +0000 Subject: [PATCH 136/278] wrap at 80 --- src/sage/rings/polynomial/polynomial_ring.py | 49 ++++++++++++-------- 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/src/sage/rings/polynomial/polynomial_ring.py b/src/sage/rings/polynomial/polynomial_ring.py index 890b47c6be4..9b2985d85af 100644 --- a/src/sage/rings/polynomial/polynomial_ring.py +++ b/src/sage/rings/polynomial/polynomial_ring.py @@ -281,7 +281,6 @@ def __init__(self, base_ring, name=None, sparse=False, implementation=None, sage: GF(7)['x']['y'].is_finite() False - """ # We trust that, if category is given, it is useful and does not need to be joined # with the default category @@ -1626,8 +1625,8 @@ def karatsuba_threshold(self): def set_karatsuba_threshold(self, Karatsuba_threshold): """ - Changes the default threshold for this ring in the method :meth:`_mul_karatsuba` - to fall back to the schoolbook algorithm. + Changes the default threshold for this ring in the method + :meth:`_mul_karatsuba` to fall back to the schoolbook algorithm. .. warning:: @@ -1948,7 +1947,8 @@ def __init__(self, base_ring, name="x", sparse=False, implementation=None, @cached_method(key=lambda self, d, q, sign, lead: (d, q, sign, tuple([x if isinstance(x, (tuple, list)) else (x, 0) for x in lead]) if isinstance(lead, (tuple, list)) else ((lead, 0)))) def weil_polynomials(self, d, q, sign=1, lead=1): r""" - Return all integer polynomials whose complex roots all have a specified absolute value. + Return all integer polynomials whose complex roots all have a specified + absolute value. Such polynomials `f` satisfy a functional equation @@ -1956,29 +1956,34 @@ def weil_polynomials(self, d, q, sign=1, lead=1): T^d f(q/T) = s q^{d/2} f(T) - where `d` is the degree of `f`, `s` is a sign and `q^{1/2}` is the absolute value - of the roots of `f`. + where `d` is the degree of `f`, `s` is a sign and `q^{1/2}` is the + absolute value of the roots of `f`. INPUT: - ``d`` -- integer, the degree of the polynomials - - ``q`` -- integer, the square of the complex absolute value of the roots + - ``q`` -- integer, the square of the complex absolute value of the + roots - - ``sign`` -- integer (default `1`), the sign `s` of the functional equation + - ``sign`` -- integer (default `1`), the sign `s` of the functional + equation - - ``lead`` -- integer, list of integers or list of pairs of integers (default `1`), - constraints on the leading few coefficients of the generated polynomials. - If pairs `(a, b)` of integers are given, they are treated as a constraint - of the form `\equiv a \pmod{b}`; the moduli must be in decreasing order by - divisibility, and the modulus of the leading coefficient must be 0. + - ``lead`` -- integer, list of integers or list of pairs of integers + (default `1`), constraints on the leading few coefficients of the + generated polynomials. If pairs `(a, b)` of integers are given, they + are treated as a constraint of the form `\equiv a \pmod{b}`; the + moduli must be in decreasing order by divisibility, and the modulus + of the leading coefficient must be 0. .. SEEALSO:: - More documentation and additional options are available using the iterator + More documentation and additional options are available using the + iterator :class:`sage.rings.polynomial.weil.weil_polynomials.WeilPolynomials` - directly. In addition, polynomials have a method :meth:`is_weil_polynomial` to - test whether or not the given polynomial is a Weil polynomial. + directly. In addition, polynomials have a method + :meth:`is_weil_polynomial` to test whether or not the given + polynomial is a Weil polynomial. EXAMPLES:: @@ -2013,7 +2018,8 @@ def weil_polynomials(self, d, q, sign=1, lead=1): TESTS: - We check that products of Weil polynomials are also listed as Weil polynomials:: + We check that products of Weil polynomials are also listed as Weil + polynomials:: sage: all((f * g) in R.weil_polynomials(6, q) for q in [3, 4] # needs sage.libs.flint ....: for f in R.weil_polynomials(2, q) for g in R.weil_polynomials(4, q)) @@ -2028,13 +2034,15 @@ def weil_polynomials(self, d, q, sign=1, lead=1): ....: for j in range(1, (3+i)//2 + 1)) ....: for i in range(4)]) for f in simples] - Check that every polynomial in this list has 3 real roots between `-2 \sqrt{3}` and `2 \sqrt{3}`:: + Check that every polynomial in this list has 3 real roots between `-2 + \sqrt{3}` and `2 \sqrt{3}`:: sage: roots = [f.roots(RR, multiplicities=False) for f in reals] # needs sage.libs.flint sage: all(len(L) == 3 and all(x^2 <= 12 for x in L) for L in roots) # needs sage.libs.flint True - Finally, check that the original polynomials are reconstructed as CM polynomials:: + Finally, check that the original polynomials are reconstructed as CM + polynomials:: sage: all(f == T^3*r(T + 3/T) for (f, r) in zip(simples, reals)) # needs sage.libs.flint True @@ -2190,7 +2198,8 @@ def _element_class(): def _ideal_class_(self, n=0): """ - Returns the class representing ideals in univariate polynomial rings over fields. + Returns the class representing ideals in univariate polynomial rings + over fields. EXAMPLES:: From e753e35eff70a54b4be9f77a889585ee9b92fb9f Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Sat, 24 Feb 2024 03:43:33 +0000 Subject: [PATCH 137/278] =?UTF-8?q?=F0=9F=8E=89=20finally=20positive=20rev?= =?UTF-8?q?iew=20=F0=9F=8E=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/sage/stats/distributions/discrete_gaussian_lattice.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sage/stats/distributions/discrete_gaussian_lattice.py b/src/sage/stats/distributions/discrete_gaussian_lattice.py index 17d1a2f2bc7..6667b2fb5cf 100644 --- a/src/sage/stats/distributions/discrete_gaussian_lattice.py +++ b/src/sage/stats/distributions/discrete_gaussian_lattice.py @@ -10,8 +10,8 @@ .. MATH:: - `\frac{\exp(-|x - c|_2^2 / (2\sigma^2))}{\sum_{v \in \Lambda} \exp(-|v|_2^2 / - (2\sigma^2))}` + \frac{\exp(-|x - c|_2^2 / (2\sigma^2))}{\sum_{v \in \Lambda} \exp(-|v|_2^2 / + (2\sigma^2))}. AUTHORS: @@ -352,7 +352,7 @@ def f_or_hat(x): @cached_method def _maximal_r(self): r""" - This function computes the largest value `r > 0` such that `\Sigma - r^2BB^@` + This function computes the largest value `r > 0` such that `\Sigma - r^2BB^T` is positive definite. This is equivalent to finding `\lambda_1(\Sigma / Q) = 1 / \lambda_n(Q From a1d62386d5ec65495d13e75bbe0e4f3984089a7e Mon Sep 17 00:00:00 2001 From: miguelmarco Date: Sat, 24 Feb 2024 12:23:13 +0100 Subject: [PATCH 138/278] Update src/sage/algebras/commutative_dga.py Co-authored-by: Travis Scrimshaw --- src/sage/algebras/commutative_dga.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/algebras/commutative_dga.py b/src/sage/algebras/commutative_dga.py index ef10db191ca..67a09882016 100644 --- a/src/sage/algebras/commutative_dga.py +++ b/src/sage/algebras/commutative_dga.py @@ -1572,7 +1572,7 @@ def __call__(self, *values, **kwargs): INPUT: - ``values`` -- (optional) either the values in which the variables - will be evaluated or a dictionary. + will be evaluated or a dictionary OUTPUT: From 6ffd806dab9602147501a47c9b44bb5badca7e16 Mon Sep 17 00:00:00 2001 From: miguelmarco Date: Sat, 24 Feb 2024 12:23:26 +0100 Subject: [PATCH 139/278] Update src/sage/algebras/commutative_dga.py Co-authored-by: Travis Scrimshaw --- src/sage/algebras/commutative_dga.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/sage/algebras/commutative_dga.py b/src/sage/algebras/commutative_dga.py index 67a09882016..f60328d08c9 100644 --- a/src/sage/algebras/commutative_dga.py +++ b/src/sage/algebras/commutative_dga.py @@ -1612,7 +1612,6 @@ def __call__(self, *values, **kwargs): sage: f = x*y - 5*y*z + 7*x*y^2*z^3*t sage: f({x:1}, t=x,y=z) 7*y^2*z^3*t - 5*y*z + y - """ gens = self.parent().gens() images = list(gens) From 24ef310e364eff58d22765cccf54a6625c9f9248 Mon Sep 17 00:00:00 2001 From: miguelmarco Date: Sat, 24 Feb 2024 12:23:35 +0100 Subject: [PATCH 140/278] Update src/sage/algebras/commutative_dga.py Co-authored-by: Travis Scrimshaw --- src/sage/algebras/commutative_dga.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/algebras/commutative_dga.py b/src/sage/algebras/commutative_dga.py index f60328d08c9..c2ef84f079e 100644 --- a/src/sage/algebras/commutative_dga.py +++ b/src/sage/algebras/commutative_dga.py @@ -1631,7 +1631,7 @@ def __call__(self, *values, **kwargs): images = list(gens) for (i, g) in enumerate(gens): gstr = str(g) - if gstr in kwargs.keys(): + if gstr in kwargs: images[i] = kwargs[gstr] res = 0 for (m, c) in self.dict().items(): From 5d5602fa7766fef7de9cca0fc189079c98ea8c64 Mon Sep 17 00:00:00 2001 From: DavidAyotte Date: Sat, 24 Feb 2024 11:07:03 -0500 Subject: [PATCH 141/278] src/sage/modular/quasimodform/element.py: add a definition of the weight --- src/sage/modular/quasimodform/element.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/sage/modular/quasimodform/element.py b/src/sage/modular/quasimodform/element.py index c85f8e6f932..e1d5896119c 100644 --- a/src/sage/modular/quasimodform/element.py +++ b/src/sage/modular/quasimodform/element.py @@ -31,16 +31,20 @@ class QuasiModularFormsElement(ModuleElement): r""" - A quasimodular forms ring element. Such an element is describbed by SageMath - as a polynomial + A quasimodular forms ring element. Such an element is describbed by + SageMath as a polynomial .. MATH:: - f_0 + f_1 E_2 + f_2 E_2^2 + \cdots + f_m E_2^m + F = f_0 + f_1 E_2 + f_2 E_2^2 + \cdots + f_m E_2^m where each `f_i` a graded modular form element (see :class:`~sage.modular.modform.element.GradedModularFormElement`) + For an integer `k`, we say that `F` is homogeneous of weight `k` if + it lies in an homogeneous component of degree `k` of the graded ring + of quasimodular forms. + EXAMPLES:: sage: QM = QuasiModularForms(1) From fe0ec5df4a9134713098898beaff2f9543492d0b Mon Sep 17 00:00:00 2001 From: DavidAyotte Date: Sat, 24 Feb 2024 11:07:51 -0500 Subject: [PATCH 142/278] src/sage/modular/quasimodform/element.py: minor fix --- src/sage/modular/quasimodform/element.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/modular/quasimodform/element.py b/src/sage/modular/quasimodform/element.py index e1d5896119c..3b41f19437c 100644 --- a/src/sage/modular/quasimodform/element.py +++ b/src/sage/modular/quasimodform/element.py @@ -301,7 +301,7 @@ def __bool__(self): def depth(self): r""" - Return the depth of the given quasimodular form. + Return the depth of this quasimodular form. Note that the quasimodular form must be homogeneous of weight `k`. Recall that the *depth* is the integer `p` such that From 4df388b037f4a2590d31f5235af4af16944c6293 Mon Sep 17 00:00:00 2001 From: DavidAyotte Date: Sat, 24 Feb 2024 11:34:59 -0500 Subject: [PATCH 143/278] src/sage/modular/quasimodform/ring.py: use incremental powers of E2 --- src/sage/modular/quasimodform/ring.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/sage/modular/quasimodform/ring.py b/src/sage/modular/quasimodform/ring.py index d5cf90c429e..53fcea616bd 100644 --- a/src/sage/modular/quasimodform/ring.py +++ b/src/sage/modular/quasimodform/ring.py @@ -817,9 +817,11 @@ def basis_of_weight(self, weight): basis = [] E2 = self.weight_2_eisenstein_series() M = self.__modular_forms_subring + E2_pow = self.one() for j in range(weight//2): - basis += [f*E2**j for f + basis += [f*E2_pow for f in M.modular_forms_of_weight(weight - 2*j).basis()] + E2_pow *= E2 if not weight%2: - basis.append(E2**(Integer(weight/2))) + basis.append(E2_pow) return basis From de98279e9fb04d7b7d7a52310c00077b890fcde6 Mon Sep 17 00:00:00 2001 From: Omegaconstant Date: Sun, 25 Feb 2024 13:03:51 +0530 Subject: [PATCH 144/278] Added Examples tag before the examples --- src/sage/sets/recursively_enumerated_set.pyx | 30 +++++++++++--------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/src/sage/sets/recursively_enumerated_set.pyx b/src/sage/sets/recursively_enumerated_set.pyx index e40bf792f43..c6913e9ae23 100644 --- a/src/sage/sets/recursively_enumerated_set.pyx +++ b/src/sage/sets/recursively_enumerated_set.pyx @@ -28,6 +28,8 @@ to be a forest, symmetric, or graded. However, it may have other structure, such as not containing an oriented cycle, that does not help with the enumeration. +EXAMPLES: + In this example, the seed is 0 and the successor function is either ``+2`` or ``+3``. This is the set of non negative linear combinations of 2 and 3:: @@ -53,7 +55,9 @@ Symmetric structure The origin ``(0, 0)`` as seed and the upper, lower, left and right lattice point as successor function. This function is symmetric since `p` is a -successor of `q` if and only if `q` is a successor or `p`:: +successor of `q` if and only if `q` is a successor or `p`: + +EXAMPLES:: sage: succ = lambda a: [(a[0]-1,a[1]), (a[0],a[1]-1), (a[0]+1,a[1]), (a[0],a[1]+1)] sage: seeds = [(0,0)] @@ -88,7 +92,9 @@ Graded structure ---------------- Identity permutation as seed and ``permutohedron_succ`` as successor -function:: +function: + +EXAMPLES:: sage: succ = attrcall("permutohedron_succ") sage: seed = [Permutation([1..5])] @@ -138,6 +144,8 @@ Graded components (set of elements of the same depth):: Forest structure ---------------- +EXAMPLES: + The set of words over the alphabet `\{a,b\}` can be generated from the empty word by appending the letter `a` or `b` as a successor function. This set has a forest structure:: @@ -160,10 +168,7 @@ Breadth first search iterator:: sage: [next(it) for _ in range(6)] ['', 'a', 'b', 'aa', 'ab', 'ba'] -Example: Forest structure -------------------------- - -This example was provided by Florent Hivert. +The following example of Forest structure was provided by Florent Hivert. How to define a set using those classes? @@ -229,10 +234,7 @@ or:: sage: S.list() ['', 'a', 'aa', 'ab', 'ac', 'b', 'ba', 'bb', 'bc', 'c', 'ca', 'cb', 'cc'] -Example: Forest structure 2 ---------------------------- - -This example was provided by Florent Hivert. +The following example of Forest structure was provided by Florent Hivert. Here is a little more involved example. We want to iterate through all permutations of a given set `S`. One solution is to take elements of `S` one @@ -386,7 +388,7 @@ def RecursivelyEnumeratedSet(seeds, successors, structure=None, .. WARNING:: - If you do not set a valid structure, you might obtain bad results, + If you do not set a good structure, you might obtain bad results, like elements generated twice:: sage: f = lambda a: [a-1,a+1] @@ -1554,9 +1556,9 @@ def search_forest_iterator(roots, children, algorithm='depth'): [0, 1, 2], [0, 2, 1], [1, 0, 2], [1, 2, 0], [2, 0, 1], [2, 1, 0]] """ # Little trick: the same implementation handles both depth and - # breadth first search. Setting position to -1 initiates a depth search + # breadth first search. Setting position to -1 results in a depth search # (you ask the children for the last node you met). Setting - # position on 0 initiates a breadth search (enumerate all the + # position on 0 results in a breadth search (enumerate all the # descendants of a node before going on to the next father) if algorithm == 'depth': position = -1 @@ -1690,6 +1692,8 @@ class RecursivelyEnumeratedSet_forest(Parent): recover the corresponding integers, and discard tuples finishing by zero. + EXAMPLES: + A first approach is to pass the ``roots`` and ``children`` functions as arguments to :meth:`RecursivelyEnumeratedSet_forest.__init__`:: From c4a60bde89393e24404c3c631d5e226529d440e5 Mon Sep 17 00:00:00 2001 From: RuchitJagodara Date: Sun, 25 Feb 2024 23:09:42 +0530 Subject: [PATCH 145/278] Make test more robust for the function _element_constructor_ --- src/sage/combinat/permutation.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/sage/combinat/permutation.py b/src/sage/combinat/permutation.py index fb67129e3d8..e26bde5e0cc 100644 --- a/src/sage/combinat/permutation.py +++ b/src/sage/combinat/permutation.py @@ -7095,14 +7095,11 @@ def _element_constructor_(self, x, check=True): Ensure that :issue:`37284` is fixed:: - sage: PG = PermutationGroup(SymmetricGroup(5).gens()) - sage: P5 = Permutations(5) + sage: PG = PermutationGroup([[(1,2,3),(5,6)],[(7,8)]]) + sage: P5 = Permutations(8) sage: p = PG.an_element() - sage: x = PG([4,3,5,1,2]) - sage: s = P5(x); s - [4, 3, 5, 1, 2] - sage: s.parent() - Standard permutations of 5 + sage: P5(p).parent() + Standard permutations of 8 """ if isinstance(x, PermutationGroupElement): return self. _from_permutation_group_element(x) From cdcdb8fed589303863cf9d8fa0e338eb2cc8a6e9 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 12 Feb 2024 18:05:28 -0800 Subject: [PATCH 146/278] README.md: Move all mentions of release tarballs to the installation guide --- README.md | 40 ++++++++------------------ src/doc/en/developer/doctesting.rst | 2 +- src/doc/en/installation/source.rst | 44 +++++++++++++++++++++++++++-- 3 files changed, 55 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 422582292af..89c55ea5a24 100644 --- a/README.md +++ b/README.md @@ -131,9 +131,8 @@ in the Installation Guide. - On personal computers, any subdirectory of your :envvar:`HOME` directory should do. - - For example, you could use `SAGE_ROOT=~/sage/sage-x.y`, which we - will use as the running example below, where `x.y` is the - current Sage version. + - For example, you could use `SAGE_ROOT=~/sage/sage`, which we + will use as the running example below. - You need at least 10 GB of free disk space. @@ -151,31 +150,19 @@ in the Installation Guide. capitalization when changing into :envvar:`SAGE_ROOT` can lead to build errors for dependencies requiring exact capitalization in path names. -2. Download/unpack or clone the sources. +2. Clone the sources. - - Go to https://www.sagemath.org/download-source.html, select a mirror, - and download the file :file:`sage-x.y.tar.gz`. + - Create the directory where `SAGE_ROOT` should be established: - This compressed archive file contains the source code for Sage and - the source for all programs on which Sage depends. + $ mkdir -p ~/sage + $ cd ~/sage - - After downloading the source tarball `sage-x.y.tar.gz` into - `~/sage/`: - - $ cd ~/sage/ - $ tar xf sage-x.y.tar.gz # adapt x.y; takes a while - - This creates the subdirectory `sage-x.y`. Now change into it: - - $ cd sage-x.y/ # adapt x.y - - - [Git] Alternatively, and required for Sage development, clone the Sage - git repository: + - Clone the Sage git repository: $ ORIG=https://github.com/sagemath/sage.git - $ git clone -c core.symlinks=true --branch develop --tags $ORIG + $ git clone -c core.symlinks=true --origin upstream --branch develop --tags $ORIG - This will create the directory `sage`. (See the section + This will create the subdirectory `~/sage/sage`. (See the section [Setting up git](https://doc.sagemath.org/html/en/developer/git_setup.html) and the following sections in the Sage Developer's Guide for more information.) @@ -190,11 +177,8 @@ in the Installation Guide. build will not work if Windows line endings rather than UNIX line endings are used. - Therefore it is crucial that you unpack the source tree from the - WSL `bash` using the WSL `tar` utility and not using other - Windows tools (including mingw). Likewise, when using `git`, it - is recommended (but not necessary) to use the WSL version of - `git`. + Therefore it is recommended (but not necessary) to use the + WSL version of `git`. 3. [Linux, WSL] Install the required minimal build prerequisites. @@ -473,7 +457,7 @@ Directory Layout Simplified directory layout (only essential files/directories): ``` -SAGE_ROOT Root directory (sage-x.y in Sage tarball) +SAGE_ROOT Root directory (create by git clone) ├── build │ └── pkgs Every package is a subdirectory here │ ├── 4ti2/ diff --git a/src/doc/en/developer/doctesting.rst b/src/doc/en/developer/doctesting.rst index f7c54bbbeec..e6324640ca1 100644 --- a/src/doc/en/developer/doctesting.rst +++ b/src/doc/en/developer/doctesting.rst @@ -26,7 +26,7 @@ this syntax: .. CODE-BLOCK:: text - /path/to/sage-x.y.z/sage -t [--long] /path/to/sage-x.y.z/path/to/module.py[x] + /path/to/sage_root/sage -t [--long] /path/to/sage_root/path/to/module.py[x] where ``--long`` is an optional argument (see :ref:`section-options` for more options). The version of ``sage`` used must match the version diff --git a/src/doc/en/installation/source.rst b/src/doc/en/installation/source.rst index 26454e4d6d3..06f07544928 100644 --- a/src/doc/en/installation/source.rst +++ b/src/doc/en/installation/source.rst @@ -387,9 +387,49 @@ Installation steps #. Follow the procedure in the file `README.md `_ in ``SAGE_ROOT``. +#. If you need wish to prepare for having to build Sage in an environment + without sufficient Internet connectivity: + + - After running ``configure``, you can use ``make download`` to force + downloading packages before building. After this, the packages + are in the subdirectory ``upstream``. + + - Alternatively, instead of cloning the git repository, you + can download a self-contained release tarball for any + stable release from the Sage project's + `GitHub Releases `_. + Use the file named ``sage-x.y.tar.gz`` (1.25 GB as of Sage 10.2) + in the Release Assets, which contains a prepopulated subdirectory + ``upstream``. + + After downloading the source tarball ```sage-x.y.tar.gz``` into + a directory ```~/sage/```:: + + $ cd ~/sage/ + $ tar xf sage-x.y.tar.gz # adapt x.y; takes a while + + This creates the subdirectory `````sage-x.y```. Now change into it:: + + $ cd sage-x.y/ # adapt x.y + + .. note:: + + On Windows, it is crucial that you unpack the source tree from the + WSL `bash` using the WSL `tar` utility and not using other + Windows tools (including mingw). + + This is because the Sage source tree contains symbolic links, and the + build will not work if Windows line endings rather than UNIX + line endings are used. + + - The Sage mirrors also provide such self-contained tarballs + for all `stable releases `_ + and additionally for all `development releases + `_. + #. Additional remarks: You do not need to be logged in as root, since no files are - changed outside of the :file:`sage-x.y` directory. + changed outside of the :file:`SAGE_ROOT` directory. In fact, **it is inadvisable to build Sage as root**, as the root account should only be used when absolutely necessary and mistyped commands can have serious consequences if you are logged in as root. @@ -504,7 +544,7 @@ Installation steps - Make a symbolic link from :file:`/usr/local/bin/sage` (or another directory in your :envvar:`PATH`) to :file:`$SAGE_ROOT/sage`:: - $ ln -s /path/to/sage-x.y/sage /usr/local/bin/sage + $ ln -s /path/to/sage_root/sage /usr/local/bin/sage Now simply typing ``sage`` from any directory should be sufficient to run Sage. From 78663e5a1b6ca35c7e7d7c9b7372f334795755b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20K=C3=B6ppe?= Date: Tue, 20 Feb 2024 13:49:52 -0800 Subject: [PATCH 147/278] Apply suggestions from code review Co-authored-by: John H. Palmieri --- src/doc/en/installation/source.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/doc/en/installation/source.rst b/src/doc/en/installation/source.rst index 06f07544928..d84ad7e2f12 100644 --- a/src/doc/en/installation/source.rst +++ b/src/doc/en/installation/source.rst @@ -387,7 +387,7 @@ Installation steps #. Follow the procedure in the file `README.md `_ in ``SAGE_ROOT``. -#. If you need wish to prepare for having to build Sage in an environment +#. If you wish to prepare for having to build Sage in an environment without sufficient Internet connectivity: - After running ``configure``, you can use ``make download`` to force @@ -402,13 +402,13 @@ Installation steps in the Release Assets, which contains a prepopulated subdirectory ``upstream``. - After downloading the source tarball ```sage-x.y.tar.gz``` into - a directory ```~/sage/```:: + After downloading the source tarball ``sage-x.y.tar.gz`` into + a directory ``~/sage/``:: $ cd ~/sage/ $ tar xf sage-x.y.tar.gz # adapt x.y; takes a while - This creates the subdirectory `````sage-x.y```. Now change into it:: + This creates the subdirectory ``sage-x.y``. Now change into it:: $ cd sage-x.y/ # adapt x.y From 940d1d3b495ec379615501e18082f147eeb0ab5c Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 21 Feb 2024 09:21:34 -0800 Subject: [PATCH 148/278] README.md: Use 'git clone' with '--filter blob:none' --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 89c55ea5a24..85964e879f9 100644 --- a/README.md +++ b/README.md @@ -159,8 +159,9 @@ in the Installation Guide. - Clone the Sage git repository: - $ ORIG=https://github.com/sagemath/sage.git - $ git clone -c core.symlinks=true --origin upstream --branch develop --tags $ORIG + $ git clone -c core.symlinks=true --filter blob:none \ + --origin upstream --branch develop --tags \ + https://github.com/sagemath/sage.git This will create the subdirectory `~/sage/sage`. (See the section [Setting up git](https://doc.sagemath.org/html/en/developer/git_setup.html) From 50b47cdee241d03fa58705d68d3dc684d46ec375 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 21 Feb 2024 09:22:54 -0800 Subject: [PATCH 149/278] src/doc/en/developer/walkthrough.rst: Small edit --- src/doc/en/developer/walkthrough.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/doc/en/developer/walkthrough.rst b/src/doc/en/developer/walkthrough.rst index 6e1b686989a..1faf7c271f2 100644 --- a/src/doc/en/developer/walkthrough.rst +++ b/src/doc/en/developer/walkthrough.rst @@ -61,8 +61,8 @@ Obtaining the Sage source code ============================== Obviously one needs the Sage source code to develop. You can use your -local installation of Sage, or (to start from scratch) download it -from our Sage repository on GitHub:: +local installation of Sage (if you installed Sage from source), or +(to start from scratch) download it from our Sage repository on GitHub:: [alice@localhost ~]$ git clone --origin upstream https://github.com/sagemath/sage.git Cloning into 'sage'... From b1684d2d6ddc4f2b7d5566c8492a9b7c403e5ed2 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 23 Feb 2024 19:27:07 -0800 Subject: [PATCH 150/278] README.md: Start with installing git if not installed already --- README.md | 97 ++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 61 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index 85964e879f9..0e743841c41 100644 --- a/README.md +++ b/README.md @@ -150,7 +150,22 @@ in the Installation Guide. capitalization when changing into :envvar:`SAGE_ROOT` can lead to build errors for dependencies requiring exact capitalization in path names. -2. Clone the sources. +2. Clone the sources with `git`: + + - To check that `git` is available, open a terminal and enter + the following command at the shell prompt (`$`): + + $ git --version + git version 2.42.0 + + The exact version does not matter, but if this command gives an error, + install `git` using your package manager, using one of these commands: + + $ sudo pacman -S git # on Arch Linux + $ sudo apt-get update && apt-get install git # on Debian/Ubuntu + $ sudo yum install git # on Fedora/Redhat/CentOS + $ sudo zypper install git # on openSUSE + $ sudo xbps-install git # on Void Linux - Create the directory where `SAGE_ROOT` should be established: @@ -163,16 +178,18 @@ in the Installation Guide. --origin upstream --branch develop --tags \ https://github.com/sagemath/sage.git + This command obtains the most recent development release. + Replace `--branch develop` by `--branch master` to select + the most recent stable release instead. + This will create the subdirectory `~/sage/sage`. (See the section [Setting up git](https://doc.sagemath.org/html/en/developer/git_setup.html) and the following sections in the Sage Developer's Guide for more information.) - Change into it and pick the branch you need, typically - the latest development branch: + - Change into the created subdirectory: $ cd sage - $ git checkout develop - [Windows] The Sage source tree contains symbolic links, and the build will not work if Windows line endings rather than UNIX @@ -181,7 +198,16 @@ in the Installation Guide. Therefore it is recommended (but not necessary) to use the WSL version of `git`. -3. [Linux, WSL] Install the required minimal build prerequisites. +3. Install system packages. + + Either refer for this to the [section on installation from + source](https://doc.sagemath.org/html/en/installation/source.html) in the + Sage Installation Manual for compilations of system packages + that you can install. When done, skip to step 7 (bootstrapping). + + Alternatively, follow the more fine-grained approach below. + +4. [Linux, WSL] Install the required minimal build prerequisites: - Compilers: `gcc`, `gfortran`, `g++` (GCC versions from 8.4.0 to 13.x and recent versions of Clang (LLVM) are supported). @@ -190,12 +216,12 @@ in the Installation Guide. for a discussion of suitable compilers. - Build tools: GNU `make`, GNU `m4`, `perl` (including - ``ExtUtils::MakeMaker``), `ranlib`, `git`, `tar`, `bc`. + `ExtUtils::MakeMaker`), `ranlib`, `git`, `tar`, `bc`. See [build/pkgs/_prereq/SPKG.rst](build/pkgs/_prereq/SPKG.rst) for more details. - Python 3.4 or later, or Python 2.7, a full installation including - `urllib`; but ideally version 3.9.x, 3.10.x, or 3.11.x, which + `urllib`; but ideally version 3.9.x, 3.10.x, 3.11.x, 3.12.x, which will avoid having to build Sage's own copy of Python 3. See [build/pkgs/python3/SPKG.rst](build/pkgs/python3/SPKG.rst) for more details. @@ -214,22 +240,25 @@ in the Installation Guide. [void.txt](build/pkgs/_prereq/distros/void.txt), or visit https://doc.sagemath.org/html/en/reference/spkg/_prereq.html#spkg-prereq -4. [Git] If you plan to do Sage development or otherwise work with ticket branches - and not only releases, install the bootstrapping prerequisites. See the - files in the folder +5. Optional: It is recommended that you have both LaTeX and + the ImageMagick tools (e.g. the "convert" command) installed + since some plotting functionality benefits from them. + +6. [Development] If you plan to do Sage development or otherwise work with + ticket branches and not only releases, install the bootstrapping + prerequisites. See the files in the folder [build/pkgs/_bootstrap/distros](build/pkgs/_bootstrap/distros), or visit https://doc.sagemath.org/html/en/reference/spkg/_bootstrap.html#spkg-bootstrap -5. [Git] If you cloned the Sage repository using `git`, bootstrap the - source tree using the following command: +7. Bootstrap the source tree using the following command: $ make configure - (If the bootstrapping prerequisites are not installed, this command will - download a package providing pre-built bootstrap output instead.) + (If the bootstrapping prerequisites are not installed, this command + will download a package providing pre-built bootstrap output instead.) -6. Sanitize the build environment. Use the command +8. Sanitize the build environment. Use the command $ env @@ -261,7 +290,7 @@ in the Installation Guide. can also add it to your shell profile so that it gets run automatically in all future sessions.) -7. Optionally, decide on the installation prefix (`SAGE_LOCAL`): +9. Optionally, decide on the installation prefix (`SAGE_LOCAL`): - Traditionally, and by default, Sage is installed into the subdirectory hierarchy rooted at `SAGE_ROOT/local/`. @@ -279,15 +308,11 @@ in the Installation Guide. installs (`make install` is a no-op). Therefore the installation hierarchy must be writable by the user. - - See the installation manual for options if you want to + - See the Sage Installation Manual for options if you want to install into shared locations such as `/usr/local/`. Do not attempt to build Sage as `root`. -8. Optional: It is recommended that you have both LaTeX and - the ImageMagick tools (e.g. the "convert" command) installed - since some plotting functionality benefits from them. - -9. Optionally, review the configuration options, which includes +10. Optionally, review the configuration options, which includes many optional packages: $ ./configure --help @@ -305,7 +330,7 @@ in the Installation Guide. a great speedup when switching between different branches, at the expense of disk space use. -10. Optional, but highly recommended: Set some environment variables to +11. Optional, but highly recommended: Set some environment variables to customize the build. For example, the `MAKE` environment variable controls whether to @@ -327,7 +352,7 @@ in the Installation Guide. building Sage, see [the installation guide](https://doc.sagemath.org/html/en/installation/source.html#environment-variables). -11. Type `./configure`, followed by any options that you wish to use. +12. Type `./configure`, followed by any options that you wish to use. For example, to build Sage with `gf2x` package supplied by Sage, use `./configure --with-system-gf2x=no`. @@ -347,60 +372,60 @@ in the Installation Guide. available; only the most recent releases of your distribution will have all of these recommended packages. -12. Optional: If you choose to install the additional system packages, +13. Optional: If you choose to install the additional system packages, a re-run of `./configure` will test whether the versions installed are usable for Sage; if they are, this will reduce the compilation time and disk space needed by Sage. The usage of packages may be adjusted by `./configure` parameters (check again the output of `./configure --help`). -13. Type `make`. That's it! Everything is automatic and +14. Type `make`. That's it! Everything is automatic and non-interactive. If you followed the above instructions, in particular regarding the installation of system packages recommended by the output of - `./configure` (step 10), and regarding the parallel build (step 9), + `./configure` (step 11), and regarding the parallel build (step 10), building Sage takes less than one hour on a modern computer. (Otherwise, it can take much longer.) The build should work fine on all fully supported platforms. If it does not, we want to know! -14. Type `./sage` to try it out. In Sage, try for example `2 + 2`, +15. Type `./sage` to try it out. In Sage, try for example `2 + 2`, `plot(x^2)`, `plot3d(lambda x, y: x*y, (-1, 1), (-1, 1))` to test a simple computation and plotting in 2D and 3D. Type Ctrl+D or `quit` to quit Sage. -15. Optional: Type `make ptestlong` to test all examples in the documentation +16. Optional: Type `make ptestlong` to test all examples in the documentation (over 200,000 lines of input!) -- this takes from 10 minutes to several hours. Don't get too disturbed if there are 2 to 3 failures, but always feel free to email the section of `logs/ptestlong.log` that contains errors to the [sage-support mailing list](https://groups.google.com/group/sage-support). If there are numerous failures, there was a serious problem with your build. -16. The HTML version of the [documentation](https://doc.sagemath.org/html/en/index.html) +17. The HTML version of the [documentation](https://doc.sagemath.org/html/en/index.html) is built during the compilation process of Sage and resides in the directory `local/share/doc/sage/html/`. You may want to bookmark it in your browser. -17. Optional: If you want to build the PDF version of the documentation, +18. Optional: If you want to build the PDF version of the documentation, run `make doc-pdf` (this requires LaTeX to be installed). -18. Optional: Install optional packages of interest to you: +19. Optional: Install optional packages of interest to you: get a list by typing `./sage --optional` or by visiting the [packages documentation page](https://doc.sagemath.org/html/en/reference/spkg/). -19. Optional: Create a symlink to the installed `sage` script in a - directory in your `PATH`, for example ``/usr/local``. This will +20. Optional: Create a symlink to the installed `sage` script in a + directory in your `PATH`, for example `/usr/local`. This will allow you to start Sage by typing `sage` from anywhere rather than having to either type the full path or navigate to the Sage directory and type `./sage`. This can be done by running: $ sudo ln -s $(./sage -sh -c 'ls $SAGE_ROOT/venv/bin/sage') /usr/local/bin -20. Optional: Set up SageMath as a Jupyter kernel in an existing Jupyter notebook +21. Optional: Set up SageMath as a Jupyter kernel in an existing Jupyter notebook or JupyterLab installation, as described in [section "Launching SageMath"](https://doc.sagemath.org/html/en/installation/launching.html) - in the installation manual. + in the Sage Installation Manual. Alternative Installation using PyPI --------------- From 3341cdbceaebc6ec0160d847d74fc5c3fe024565 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 23 Feb 2024 19:31:50 -0800 Subject: [PATCH 151/278] src/doc/en/developer/git_setup.rst: Link to the SPKG page for the full list of install commands --- src/doc/en/developer/git_setup.rst | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/doc/en/developer/git_setup.rst b/src/doc/en/developer/git_setup.rst index f95b0d653da..bcd19a916a6 100644 --- a/src/doc/en/developer/git_setup.rst +++ b/src/doc/en/developer/git_setup.rst @@ -12,11 +12,9 @@ Installing Git Depending on your platform, use the following to install Git: -Debian / Ubuntu - Run ``sudo apt-get install git-core`` - -Fedora - Run ``sudo yum install git-core`` +Linux + See :ref:`spkg_git` for the installation command on your + Linux distribution. Windows (WSL) We strongly recommend to install the package using the Linux From 4feff64cad07f4efc17ff2fe0c384ad17609e068 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 13 Jan 2024 10:38:28 -0800 Subject: [PATCH 152/278] CODE_OF_CONDUCT.md: Do not send people to sage-flame --- 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 0e64fa46a1f..eca34dc7e4c 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -37,6 +37,4 @@ Sage can more effectively collaborate with others if they follow this code. 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. It is also possible to move heated -discussions to https://groups.google.com/g/sage-flame. - +consider the issue and explore resolutions. From 97286da199ea4cdd3858327f20926f9a90136e62 Mon Sep 17 00:00:00 2001 From: DavidAyotte Date: Sun, 25 Feb 2024 21:24:53 -0500 Subject: [PATCH 153/278] src/sage/modular/quasimodform/element.py: fix typo --- src/sage/modular/quasimodform/element.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/modular/quasimodform/element.py b/src/sage/modular/quasimodform/element.py index 3b41f19437c..f6543af92f2 100644 --- a/src/sage/modular/quasimodform/element.py +++ b/src/sage/modular/quasimodform/element.py @@ -31,7 +31,7 @@ class QuasiModularFormsElement(ModuleElement): r""" - A quasimodular forms ring element. Such an element is describbed by + A quasimodular forms ring element. Such an element is described by SageMath as a polynomial .. MATH:: From 4d248d9285030e8631bd532977f019b35a4ae4af Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Mon, 26 Feb 2024 04:28:51 +0000 Subject: [PATCH 154/278] =?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/schemes/generic/glue.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sage/schemes/generic/glue.py b/src/sage/schemes/generic/glue.py index 38a17c0d78b..e70aafa0507 100644 --- a/src/sage/schemes/generic/glue.py +++ b/src/sage/schemes/generic/glue.py @@ -63,6 +63,7 @@ def gluing_maps(self): EXAMPLES:: + sage: R. = QQ[] sage: S. = R.quotient(x * y - 1) sage: Rx = QQ["x"] sage: Ry = QQ["y"] From 6bc3152598a045a10c969dd5e768c2b12c0f064f Mon Sep 17 00:00:00 2001 From: Ruchit Jagodara Date: Mon, 26 Feb 2024 18:08:16 +0530 Subject: [PATCH 155/278] Correct variable names in test Co-authored-by: Travis Scrimshaw --- src/sage/combinat/permutation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/combinat/permutation.py b/src/sage/combinat/permutation.py index e26bde5e0cc..0bd2f33549c 100644 --- a/src/sage/combinat/permutation.py +++ b/src/sage/combinat/permutation.py @@ -7096,9 +7096,9 @@ def _element_constructor_(self, x, check=True): Ensure that :issue:`37284` is fixed:: sage: PG = PermutationGroup([[(1,2,3),(5,6)],[(7,8)]]) - sage: P5 = Permutations(8) + sage: P8 = Permutations(8) sage: p = PG.an_element() - sage: P5(p).parent() + sage: P8(p).parent() Standard permutations of 8 """ if isinstance(x, PermutationGroupElement): From 54747b616a71a70cd8206636c06fc458c0661e16 Mon Sep 17 00:00:00 2001 From: RuchitJagodara Date: Mon, 26 Feb 2024 18:15:14 +0530 Subject: [PATCH 156/278] Make tests more robust --- src/sage/combinat/permutation.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/sage/combinat/permutation.py b/src/sage/combinat/permutation.py index 0bd2f33549c..df41747f5b2 100644 --- a/src/sage/combinat/permutation.py +++ b/src/sage/combinat/permutation.py @@ -7098,7 +7098,9 @@ def _element_constructor_(self, x, check=True): sage: PG = PermutationGroup([[(1,2,3),(5,6)],[(7,8)]]) sage: P8 = Permutations(8) sage: p = PG.an_element() - sage: P8(p).parent() + sage: q = P8(p); q + [2, 3, 1, 4, 6, 5, 8, 7] + sage: q.parent() Standard permutations of 8 """ if isinstance(x, PermutationGroupElement): From a1d23b5bd089cfeb1d3ab77e8943926c0baed4f9 Mon Sep 17 00:00:00 2001 From: Miguel Marco Date: Mon, 26 Feb 2024 19:32:19 +0100 Subject: [PATCH 157/278] Reviewer's comments --- src/sage/algebras/commutative_dga.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/sage/algebras/commutative_dga.py b/src/sage/algebras/commutative_dga.py index ef10db191ca..c2ef84f079e 100644 --- a/src/sage/algebras/commutative_dga.py +++ b/src/sage/algebras/commutative_dga.py @@ -1572,7 +1572,7 @@ def __call__(self, *values, **kwargs): INPUT: - ``values`` -- (optional) either the values in which the variables - will be evaluated or a dictionary. + will be evaluated or a dictionary OUTPUT: @@ -1612,7 +1612,6 @@ def __call__(self, *values, **kwargs): sage: f = x*y - 5*y*z + 7*x*y^2*z^3*t sage: f({x:1}, t=x,y=z) 7*y^2*z^3*t - 5*y*z + y - """ gens = self.parent().gens() images = list(gens) @@ -1632,7 +1631,7 @@ def __call__(self, *values, **kwargs): images = list(gens) for (i, g) in enumerate(gens): gstr = str(g) - if gstr in kwargs.keys(): + if gstr in kwargs: images[i] = kwargs[gstr] res = 0 for (m, c) in self.dict().items(): From 01e19d56996e6a82090fe9d32ca22392b499d719 Mon Sep 17 00:00:00 2001 From: Giorgos Mousa Date: Thu, 29 Feb 2024 12:27:05 +0200 Subject: [PATCH 158/278] Suggestions by saraedum --- src/doc/el/a_tour_of_sage/index.rst | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/doc/el/a_tour_of_sage/index.rst b/src/doc/el/a_tour_of_sage/index.rst index 6811b7265e5..8ac2414fcf8 100644 --- a/src/doc/el/a_tour_of_sage/index.rst +++ b/src/doc/el/a_tour_of_sage/index.rst @@ -1,5 +1,3 @@ -.. _a-tour-of-sage: - ===================== Καλώς ήρθατε στο Sage ===================== @@ -141,5 +139,5 @@ sage: z 1760517045946249141360373894679135204009... -Το Sage είναι το πιό προηγμένο λογισμικό ανοιχτού κώδικα για μαθηματικά στον +Το Sage είναι το πιο προηγμένο λογισμικό ανοιχτού κώδικα για μαθηματικά στον κόσμο. From 1f5d1d720d5c66d132e39261674a409509f1061f Mon Sep 17 00:00:00 2001 From: Omegaconstant Date: Fri, 1 Mar 2024 01:14:30 +0530 Subject: [PATCH 159/278] Corrected the positions of example tags --- src/sage/sets/recursively_enumerated_set.pyx | 22 ++++++++------------ 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/src/sage/sets/recursively_enumerated_set.pyx b/src/sage/sets/recursively_enumerated_set.pyx index c6913e9ae23..047338518de 100644 --- a/src/sage/sets/recursively_enumerated_set.pyx +++ b/src/sage/sets/recursively_enumerated_set.pyx @@ -20,6 +20,8 @@ AUTHORS: - Sébastien Labbé, April 2014, at Sage Days 57, Cernay-la-ville +EXAMPLES: + No hypothesis on the structure ------------------------------ @@ -28,8 +30,6 @@ to be a forest, symmetric, or graded. However, it may have other structure, such as not containing an oriented cycle, that does not help with the enumeration. -EXAMPLES: - In this example, the seed is 0 and the successor function is either ``+2`` or ``+3``. This is the set of non negative linear combinations of 2 and 3:: @@ -55,9 +55,7 @@ Symmetric structure The origin ``(0, 0)`` as seed and the upper, lower, left and right lattice point as successor function. This function is symmetric since `p` is a -successor of `q` if and only if `q` is a successor or `p`: - -EXAMPLES:: +successor of `q` if and only if `q` is a successor or `p`:: sage: succ = lambda a: [(a[0]-1,a[1]), (a[0],a[1]-1), (a[0]+1,a[1]), (a[0],a[1]+1)] sage: seeds = [(0,0)] @@ -92,9 +90,7 @@ Graded structure ---------------- Identity permutation as seed and ``permutohedron_succ`` as successor -function: - -EXAMPLES:: +function:: sage: succ = attrcall("permutohedron_succ") sage: seed = [Permutation([1..5])] @@ -144,7 +140,7 @@ Graded components (set of elements of the same depth):: Forest structure ---------------- -EXAMPLES: +.. RUBRIC:: Forest structure [Ex 1] The set of words over the alphabet `\{a,b\}` can be generated from the empty word by appending the letter `a` or `b` as a successor function. This set @@ -168,7 +164,7 @@ Breadth first search iterator:: sage: [next(it) for _ in range(6)] ['', 'a', 'b', 'aa', 'ab', 'ba'] -The following example of Forest structure was provided by Florent Hivert. +This example was provided by Florent Hivert. How to define a set using those classes? @@ -234,7 +230,9 @@ or:: sage: S.list() ['', 'a', 'aa', 'ab', 'ac', 'b', 'ba', 'bb', 'bc', 'c', 'ca', 'cb', 'cc'] -The following example of Forest structure was provided by Florent Hivert. +.. RUBRIC:: Forest structure [Ex 2] + +This example was provided by Florent Hivert. Here is a little more involved example. We want to iterate through all permutations of a given set `S`. One solution is to take elements of `S` one @@ -1692,8 +1690,6 @@ class RecursivelyEnumeratedSet_forest(Parent): recover the corresponding integers, and discard tuples finishing by zero. - EXAMPLES: - A first approach is to pass the ``roots`` and ``children`` functions as arguments to :meth:`RecursivelyEnumeratedSet_forest.__init__`:: From 8166de5a34bd67eb0003b8a155b79da1845273da Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 12 Feb 2024 23:48:58 -0800 Subject: [PATCH 160/278] build/pkgs: Mark math packages with a 'math' tag --- build/pkgs/4ti2/math | 0 build/pkgs/_sagemath/math | 0 build/pkgs/admcycles/math | 0 build/pkgs/barvinok/math | 0 build/pkgs/benzene/math | 0 build/pkgs/bliss/math | 0 build/pkgs/brial/math | 0 build/pkgs/buckygen/math | 0 build/pkgs/cbc/math | 0 build/pkgs/cddlib/math | 0 build/pkgs/clarabel/math | 0 build/pkgs/cliquer/math | 0 build/pkgs/cocoalib/math | 0 build/pkgs/combinatorial_designs/math | 0 build/pkgs/conway_polynomials/math | 0 build/pkgs/coxeter3/math | 0 build/pkgs/cryptominisat/math | 0 build/pkgs/csdp/math | 0 build/pkgs/cunningham_tables/math | 0 build/pkgs/cvxopt/math | 0 build/pkgs/cvxpy/math | 0 build/pkgs/cylp/math | 0 build/pkgs/cypari/math | 0 build/pkgs/deformation/math | 0 build/pkgs/dsdp/math | 0 build/pkgs/e_antic/math | 0 build/pkgs/eclib/math | 0 build/pkgs/ecm/math | 0 build/pkgs/ecos_python/math | 0 build/pkgs/elliptic_curves/math | 0 build/pkgs/fflas_ffpack/math | 0 build/pkgs/flint/math | 0 build/pkgs/fplll/math | 0 build/pkgs/fricas/math | 0 build/pkgs/gap/math | 0 build/pkgs/gf2x/math | 0 build/pkgs/gfan/math | 0 build/pkgs/giac/math | 0 build/pkgs/givaro/math | 0 build/pkgs/glpk/math | 0 build/pkgs/gmp/math | 0 build/pkgs/gmpy2/math | 0 build/pkgs/gp2c/math | 0 build/pkgs/graphs/math | 0 build/pkgs/gsl/math | 0 build/pkgs/igraph/math | 0 build/pkgs/iml/math | 0 build/pkgs/lcalc/math | 0 build/pkgs/libbraiding/math | 0 build/pkgs/libhomfly/math | 0 build/pkgs/libsemigroups/math | 0 build/pkgs/linbox/math | 0 build/pkgs/lrcalc/math | 0 build/pkgs/lrcalc_python/math | 0 build/pkgs/lrslib/math | 0 build/pkgs/m4ri/math | 0 build/pkgs/m4rie/math | 0 build/pkgs/maxima/math | 0 build/pkgs/mpc/math | 0 build/pkgs/mpfi/math | 0 build/pkgs/mpfr/math | 0 build/pkgs/mpmath/math | 0 build/pkgs/nauty/math | 0 build/pkgs/networkx/math | 0 build/pkgs/ntl/math | 0 build/pkgs/numpy/math | 0 build/pkgs/openblas/math | 0 build/pkgs/palp/math | 0 build/pkgs/pari/math | 0 build/pkgs/pari_elldata/math | 0 build/pkgs/pari_galdata/math | 0 build/pkgs/pari_galpol/math | 0 build/pkgs/pari_nftables/math | 0 build/pkgs/pari_seadata/math | 0 build/pkgs/pari_seadata_small/math | 0 build/pkgs/planarity/math | 0 build/pkgs/polytopes_db/math | 0 build/pkgs/ppl/math | 0 build/pkgs/pplpy/math | 0 build/pkgs/primecount/math | 0 build/pkgs/primecountpy/math | 0 build/pkgs/primesieve/math | 0 build/pkgs/pycosat/math | 0 build/pkgs/pyscipopt/math | 0 build/pkgs/python_igraph/math | 0 build/pkgs/qhull/math | 0 build/pkgs/r/math | 0 build/pkgs/rpy2/math | 0 build/pkgs/rubiks/math | 0 build/pkgs/rw/math | 0 build/pkgs/scipy/math | 0 build/pkgs/singular/math | 0 build/pkgs/soplex/math | 0 build/pkgs/suitesparse/math | 0 build/pkgs/symengine/math | 0 build/pkgs/symengine_py/math | 0 build/pkgs/symmetrica/math | 0 build/pkgs/sympow/math | 0 build/pkgs/sympy/math | 0 99 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 build/pkgs/4ti2/math create mode 100644 build/pkgs/_sagemath/math create mode 100644 build/pkgs/admcycles/math create mode 100644 build/pkgs/barvinok/math create mode 100644 build/pkgs/benzene/math create mode 100644 build/pkgs/bliss/math create mode 100644 build/pkgs/brial/math create mode 100644 build/pkgs/buckygen/math create mode 100644 build/pkgs/cbc/math create mode 100644 build/pkgs/cddlib/math create mode 100644 build/pkgs/clarabel/math create mode 100644 build/pkgs/cliquer/math create mode 100644 build/pkgs/cocoalib/math create mode 100644 build/pkgs/combinatorial_designs/math create mode 100644 build/pkgs/conway_polynomials/math create mode 100644 build/pkgs/coxeter3/math create mode 100644 build/pkgs/cryptominisat/math create mode 100644 build/pkgs/csdp/math create mode 100644 build/pkgs/cunningham_tables/math create mode 100644 build/pkgs/cvxopt/math create mode 100644 build/pkgs/cvxpy/math create mode 100644 build/pkgs/cylp/math create mode 100644 build/pkgs/cypari/math create mode 100644 build/pkgs/deformation/math create mode 100644 build/pkgs/dsdp/math create mode 100644 build/pkgs/e_antic/math create mode 100644 build/pkgs/eclib/math create mode 100644 build/pkgs/ecm/math create mode 100644 build/pkgs/ecos_python/math create mode 100644 build/pkgs/elliptic_curves/math create mode 100644 build/pkgs/fflas_ffpack/math create mode 100644 build/pkgs/flint/math create mode 100644 build/pkgs/fplll/math create mode 100644 build/pkgs/fricas/math create mode 100644 build/pkgs/gap/math create mode 100644 build/pkgs/gf2x/math create mode 100644 build/pkgs/gfan/math create mode 100644 build/pkgs/giac/math create mode 100644 build/pkgs/givaro/math create mode 100644 build/pkgs/glpk/math create mode 100644 build/pkgs/gmp/math create mode 100644 build/pkgs/gmpy2/math create mode 100644 build/pkgs/gp2c/math create mode 100644 build/pkgs/graphs/math create mode 100644 build/pkgs/gsl/math create mode 100644 build/pkgs/igraph/math create mode 100644 build/pkgs/iml/math create mode 100644 build/pkgs/lcalc/math create mode 100644 build/pkgs/libbraiding/math create mode 100644 build/pkgs/libhomfly/math create mode 100644 build/pkgs/libsemigroups/math create mode 100644 build/pkgs/linbox/math create mode 100644 build/pkgs/lrcalc/math create mode 100644 build/pkgs/lrcalc_python/math create mode 100644 build/pkgs/lrslib/math create mode 100644 build/pkgs/m4ri/math create mode 100644 build/pkgs/m4rie/math create mode 100644 build/pkgs/maxima/math create mode 100644 build/pkgs/mpc/math create mode 100644 build/pkgs/mpfi/math create mode 100644 build/pkgs/mpfr/math create mode 100644 build/pkgs/mpmath/math create mode 100644 build/pkgs/nauty/math create mode 100644 build/pkgs/networkx/math create mode 100644 build/pkgs/ntl/math create mode 100644 build/pkgs/numpy/math create mode 100644 build/pkgs/openblas/math create mode 100644 build/pkgs/palp/math create mode 100644 build/pkgs/pari/math create mode 100644 build/pkgs/pari_elldata/math create mode 100644 build/pkgs/pari_galdata/math create mode 100644 build/pkgs/pari_galpol/math create mode 100644 build/pkgs/pari_nftables/math create mode 100644 build/pkgs/pari_seadata/math create mode 100644 build/pkgs/pari_seadata_small/math create mode 100644 build/pkgs/planarity/math create mode 100644 build/pkgs/polytopes_db/math create mode 100644 build/pkgs/ppl/math create mode 100644 build/pkgs/pplpy/math create mode 100644 build/pkgs/primecount/math create mode 100644 build/pkgs/primecountpy/math create mode 100644 build/pkgs/primesieve/math create mode 100644 build/pkgs/pycosat/math create mode 100644 build/pkgs/pyscipopt/math create mode 100644 build/pkgs/python_igraph/math create mode 100644 build/pkgs/qhull/math create mode 100644 build/pkgs/r/math create mode 100644 build/pkgs/rpy2/math create mode 100644 build/pkgs/rubiks/math create mode 100644 build/pkgs/rw/math create mode 100644 build/pkgs/scipy/math create mode 100644 build/pkgs/singular/math create mode 100644 build/pkgs/soplex/math create mode 100644 build/pkgs/suitesparse/math create mode 100644 build/pkgs/symengine/math create mode 100644 build/pkgs/symengine_py/math create mode 100644 build/pkgs/symmetrica/math create mode 100644 build/pkgs/sympow/math create mode 100644 build/pkgs/sympy/math diff --git a/build/pkgs/4ti2/math b/build/pkgs/4ti2/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/_sagemath/math b/build/pkgs/_sagemath/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/admcycles/math b/build/pkgs/admcycles/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/barvinok/math b/build/pkgs/barvinok/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/benzene/math b/build/pkgs/benzene/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/bliss/math b/build/pkgs/bliss/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/brial/math b/build/pkgs/brial/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/buckygen/math b/build/pkgs/buckygen/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/cbc/math b/build/pkgs/cbc/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/cddlib/math b/build/pkgs/cddlib/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/clarabel/math b/build/pkgs/clarabel/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/cliquer/math b/build/pkgs/cliquer/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/cocoalib/math b/build/pkgs/cocoalib/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/combinatorial_designs/math b/build/pkgs/combinatorial_designs/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/conway_polynomials/math b/build/pkgs/conway_polynomials/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/coxeter3/math b/build/pkgs/coxeter3/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/cryptominisat/math b/build/pkgs/cryptominisat/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/csdp/math b/build/pkgs/csdp/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/cunningham_tables/math b/build/pkgs/cunningham_tables/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/cvxopt/math b/build/pkgs/cvxopt/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/cvxpy/math b/build/pkgs/cvxpy/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/cylp/math b/build/pkgs/cylp/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/cypari/math b/build/pkgs/cypari/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/deformation/math b/build/pkgs/deformation/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/dsdp/math b/build/pkgs/dsdp/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/e_antic/math b/build/pkgs/e_antic/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/eclib/math b/build/pkgs/eclib/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/ecm/math b/build/pkgs/ecm/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/ecos_python/math b/build/pkgs/ecos_python/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/elliptic_curves/math b/build/pkgs/elliptic_curves/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/fflas_ffpack/math b/build/pkgs/fflas_ffpack/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/flint/math b/build/pkgs/flint/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/fplll/math b/build/pkgs/fplll/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/fricas/math b/build/pkgs/fricas/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/gap/math b/build/pkgs/gap/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/gf2x/math b/build/pkgs/gf2x/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/gfan/math b/build/pkgs/gfan/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/giac/math b/build/pkgs/giac/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/givaro/math b/build/pkgs/givaro/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/glpk/math b/build/pkgs/glpk/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/gmp/math b/build/pkgs/gmp/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/gmpy2/math b/build/pkgs/gmpy2/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/gp2c/math b/build/pkgs/gp2c/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/graphs/math b/build/pkgs/graphs/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/gsl/math b/build/pkgs/gsl/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/igraph/math b/build/pkgs/igraph/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/iml/math b/build/pkgs/iml/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/lcalc/math b/build/pkgs/lcalc/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/libbraiding/math b/build/pkgs/libbraiding/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/libhomfly/math b/build/pkgs/libhomfly/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/libsemigroups/math b/build/pkgs/libsemigroups/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/linbox/math b/build/pkgs/linbox/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/lrcalc/math b/build/pkgs/lrcalc/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/lrcalc_python/math b/build/pkgs/lrcalc_python/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/lrslib/math b/build/pkgs/lrslib/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/m4ri/math b/build/pkgs/m4ri/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/m4rie/math b/build/pkgs/m4rie/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/maxima/math b/build/pkgs/maxima/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/mpc/math b/build/pkgs/mpc/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/mpfi/math b/build/pkgs/mpfi/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/mpfr/math b/build/pkgs/mpfr/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/mpmath/math b/build/pkgs/mpmath/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/nauty/math b/build/pkgs/nauty/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/networkx/math b/build/pkgs/networkx/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/ntl/math b/build/pkgs/ntl/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/numpy/math b/build/pkgs/numpy/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/openblas/math b/build/pkgs/openblas/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/palp/math b/build/pkgs/palp/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/pari/math b/build/pkgs/pari/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/pari_elldata/math b/build/pkgs/pari_elldata/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/pari_galdata/math b/build/pkgs/pari_galdata/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/pari_galpol/math b/build/pkgs/pari_galpol/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/pari_nftables/math b/build/pkgs/pari_nftables/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/pari_seadata/math b/build/pkgs/pari_seadata/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/pari_seadata_small/math b/build/pkgs/pari_seadata_small/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/planarity/math b/build/pkgs/planarity/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/polytopes_db/math b/build/pkgs/polytopes_db/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/ppl/math b/build/pkgs/ppl/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/pplpy/math b/build/pkgs/pplpy/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/primecount/math b/build/pkgs/primecount/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/primecountpy/math b/build/pkgs/primecountpy/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/primesieve/math b/build/pkgs/primesieve/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/pycosat/math b/build/pkgs/pycosat/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/pyscipopt/math b/build/pkgs/pyscipopt/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/python_igraph/math b/build/pkgs/python_igraph/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/qhull/math b/build/pkgs/qhull/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/r/math b/build/pkgs/r/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/rpy2/math b/build/pkgs/rpy2/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/rubiks/math b/build/pkgs/rubiks/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/rw/math b/build/pkgs/rw/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/scipy/math b/build/pkgs/scipy/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/singular/math b/build/pkgs/singular/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/soplex/math b/build/pkgs/soplex/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/suitesparse/math b/build/pkgs/suitesparse/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/symengine/math b/build/pkgs/symengine/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/symengine_py/math b/build/pkgs/symengine_py/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/symmetrica/math b/build/pkgs/symmetrica/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/sympow/math b/build/pkgs/sympow/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/sympy/math b/build/pkgs/sympy/math new file mode 100644 index 00000000000..e69de29bb2d From 8bd8645e5552880f108c7fa312b6c07ed95c8078 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 13 Feb 2024 00:21:21 -0800 Subject: [PATCH 161/278] src/doc/bootstrap: Separate section for standard math packages --- src/doc/bootstrap | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/doc/bootstrap b/src/doc/bootstrap index 35ad8b20879..8b4262dd156 100755 --- a/src/doc/bootstrap +++ b/src/doc/bootstrap @@ -97,12 +97,25 @@ The Sage distribution includes most programs and libraries on which Sage depends. It installs them automatically if it does not find equivalent system packages. +Mathematics +~~~~~~~~~~~ + +EOF +for PKG_BASE in $(sage-package list --has-file SPKG.rst --has-file math :standard: | grep -v '^sagemath_'); do + echo "* :ref:\`spkg_$PKG_BASE\`" +done >> "$OUTPUT_INDEX" +cat >> "$OUTPUT_INDEX" <> "$OUTPUT_INDEX" cat >> "$OUTPUT_INDEX" < Date: Tue, 13 Feb 2024 00:39:14 -0800 Subject: [PATCH 162/278] build/pkgs: Mark more math packages with a 'math' tag --- build/pkgs/{_sagemath => database_cremona_ellcurve}/math | 0 build/pkgs/database_cubic_hecke/math | 0 build/pkgs/database_jones_numfield/math | 0 build/pkgs/database_knotinfo/math | 0 build/pkgs/database_kohel/math | 0 build/pkgs/database_mutation_class/math | 0 build/pkgs/database_odlyzko_zeta/math | 0 build/pkgs/database_stein_watkins/math | 0 build/pkgs/database_stein_watkins_mini/math | 0 build/pkgs/database_symbolic_data/math | 0 build/pkgs/gap3/math | 0 build/pkgs/gap_jupyter/math | 0 build/pkgs/gap_packages/math | 0 build/pkgs/glucose/math | 0 build/pkgs/isl/math | 0 build/pkgs/jupymake/math | 0 build/pkgs/kenzo/math | 0 build/pkgs/kissat/math | 0 build/pkgs/latte_int/math | 0 build/pkgs/libnauty/math | 0 build/pkgs/lidia/math | 0 build/pkgs/mathics/math | 0 build/pkgs/mathics_scanner/math | 0 build/pkgs/mcqd/math | 0 build/pkgs/meataxe/math | 0 build/pkgs/mpfrcx/math | 0 build/pkgs/msolve/math | 0 build/pkgs/normaliz/math | 0 build/pkgs/ore_algebra/math | 0 build/pkgs/osqp_python/math | 0 build/pkgs/p_group_cohomology/math | 0 build/pkgs/papilo/math | 0 build/pkgs/pari_jupyter/math | 0 build/pkgs/phitigra/math | 0 build/pkgs/plantri/math | 0 build/pkgs/polytopes_db_4d/math | 0 build/pkgs/pycryptosat/math | 0 build/pkgs/pynormaliz/math | 0 build/pkgs/pysingular/math | 0 build/pkgs/qdldl_python/math | 0 build/pkgs/qepcad/math | 0 build/pkgs/saclib/math | 0 build/pkgs/sage_flatsurf/math | 0 build/pkgs/sage_numerical_backends_coin/math | 0 build/pkgs/sage_numerical_backends_cplex/math | 0 build/pkgs/sage_numerical_backends_gurobi/math | 0 build/pkgs/scip/math | 0 build/pkgs/scip_sdp/math | 0 build/pkgs/scs/math | 0 build/pkgs/singular_jupyter/math | 0 build/pkgs/sirocco/math | 0 build/pkgs/slabbe/math | 0 build/pkgs/snappy/math | 0 build/pkgs/surf/math | 0 build/pkgs/surface_dynamics/math | 0 build/pkgs/tdlib/math | 0 build/pkgs/tides/math | 0 build/pkgs/topcom/math | 0 58 files changed, 0 insertions(+), 0 deletions(-) rename build/pkgs/{_sagemath => database_cremona_ellcurve}/math (100%) create mode 100644 build/pkgs/database_cubic_hecke/math create mode 100644 build/pkgs/database_jones_numfield/math create mode 100644 build/pkgs/database_knotinfo/math create mode 100644 build/pkgs/database_kohel/math create mode 100644 build/pkgs/database_mutation_class/math create mode 100644 build/pkgs/database_odlyzko_zeta/math create mode 100644 build/pkgs/database_stein_watkins/math create mode 100644 build/pkgs/database_stein_watkins_mini/math create mode 100644 build/pkgs/database_symbolic_data/math create mode 100644 build/pkgs/gap3/math create mode 100644 build/pkgs/gap_jupyter/math create mode 100644 build/pkgs/gap_packages/math create mode 100644 build/pkgs/glucose/math create mode 100644 build/pkgs/isl/math create mode 100644 build/pkgs/jupymake/math create mode 100644 build/pkgs/kenzo/math create mode 100644 build/pkgs/kissat/math create mode 100644 build/pkgs/latte_int/math create mode 100644 build/pkgs/libnauty/math create mode 100644 build/pkgs/lidia/math create mode 100644 build/pkgs/mathics/math create mode 100644 build/pkgs/mathics_scanner/math create mode 100644 build/pkgs/mcqd/math create mode 100644 build/pkgs/meataxe/math create mode 100644 build/pkgs/mpfrcx/math create mode 100644 build/pkgs/msolve/math create mode 100644 build/pkgs/normaliz/math create mode 100644 build/pkgs/ore_algebra/math create mode 100644 build/pkgs/osqp_python/math create mode 100644 build/pkgs/p_group_cohomology/math create mode 100644 build/pkgs/papilo/math create mode 100644 build/pkgs/pari_jupyter/math create mode 100644 build/pkgs/phitigra/math create mode 100644 build/pkgs/plantri/math create mode 100644 build/pkgs/polytopes_db_4d/math create mode 100644 build/pkgs/pycryptosat/math create mode 100644 build/pkgs/pynormaliz/math create mode 100644 build/pkgs/pysingular/math create mode 100644 build/pkgs/qdldl_python/math create mode 100644 build/pkgs/qepcad/math create mode 100644 build/pkgs/saclib/math create mode 100644 build/pkgs/sage_flatsurf/math create mode 100644 build/pkgs/sage_numerical_backends_coin/math create mode 100644 build/pkgs/sage_numerical_backends_cplex/math create mode 100644 build/pkgs/sage_numerical_backends_gurobi/math create mode 100644 build/pkgs/scip/math create mode 100644 build/pkgs/scip_sdp/math create mode 100644 build/pkgs/scs/math create mode 100644 build/pkgs/singular_jupyter/math create mode 100644 build/pkgs/sirocco/math create mode 100644 build/pkgs/slabbe/math create mode 100644 build/pkgs/snappy/math create mode 100644 build/pkgs/surf/math create mode 100644 build/pkgs/surface_dynamics/math create mode 100644 build/pkgs/tdlib/math create mode 100644 build/pkgs/tides/math create mode 100644 build/pkgs/topcom/math diff --git a/build/pkgs/_sagemath/math b/build/pkgs/database_cremona_ellcurve/math similarity index 100% rename from build/pkgs/_sagemath/math rename to build/pkgs/database_cremona_ellcurve/math diff --git a/build/pkgs/database_cubic_hecke/math b/build/pkgs/database_cubic_hecke/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/database_jones_numfield/math b/build/pkgs/database_jones_numfield/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/database_knotinfo/math b/build/pkgs/database_knotinfo/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/database_kohel/math b/build/pkgs/database_kohel/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/database_mutation_class/math b/build/pkgs/database_mutation_class/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/database_odlyzko_zeta/math b/build/pkgs/database_odlyzko_zeta/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/database_stein_watkins/math b/build/pkgs/database_stein_watkins/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/database_stein_watkins_mini/math b/build/pkgs/database_stein_watkins_mini/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/database_symbolic_data/math b/build/pkgs/database_symbolic_data/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/gap3/math b/build/pkgs/gap3/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/gap_jupyter/math b/build/pkgs/gap_jupyter/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/gap_packages/math b/build/pkgs/gap_packages/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/glucose/math b/build/pkgs/glucose/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/isl/math b/build/pkgs/isl/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/jupymake/math b/build/pkgs/jupymake/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/kenzo/math b/build/pkgs/kenzo/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/kissat/math b/build/pkgs/kissat/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/latte_int/math b/build/pkgs/latte_int/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/libnauty/math b/build/pkgs/libnauty/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/lidia/math b/build/pkgs/lidia/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/mathics/math b/build/pkgs/mathics/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/mathics_scanner/math b/build/pkgs/mathics_scanner/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/mcqd/math b/build/pkgs/mcqd/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/meataxe/math b/build/pkgs/meataxe/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/mpfrcx/math b/build/pkgs/mpfrcx/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/msolve/math b/build/pkgs/msolve/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/normaliz/math b/build/pkgs/normaliz/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/ore_algebra/math b/build/pkgs/ore_algebra/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/osqp_python/math b/build/pkgs/osqp_python/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/p_group_cohomology/math b/build/pkgs/p_group_cohomology/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/papilo/math b/build/pkgs/papilo/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/pari_jupyter/math b/build/pkgs/pari_jupyter/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/phitigra/math b/build/pkgs/phitigra/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/plantri/math b/build/pkgs/plantri/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/polytopes_db_4d/math b/build/pkgs/polytopes_db_4d/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/pycryptosat/math b/build/pkgs/pycryptosat/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/pynormaliz/math b/build/pkgs/pynormaliz/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/pysingular/math b/build/pkgs/pysingular/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/qdldl_python/math b/build/pkgs/qdldl_python/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/qepcad/math b/build/pkgs/qepcad/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/saclib/math b/build/pkgs/saclib/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/sage_flatsurf/math b/build/pkgs/sage_flatsurf/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/sage_numerical_backends_coin/math b/build/pkgs/sage_numerical_backends_coin/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/sage_numerical_backends_cplex/math b/build/pkgs/sage_numerical_backends_cplex/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/sage_numerical_backends_gurobi/math b/build/pkgs/sage_numerical_backends_gurobi/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/scip/math b/build/pkgs/scip/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/scip_sdp/math b/build/pkgs/scip_sdp/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/scs/math b/build/pkgs/scs/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/singular_jupyter/math b/build/pkgs/singular_jupyter/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/sirocco/math b/build/pkgs/sirocco/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/slabbe/math b/build/pkgs/slabbe/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/snappy/math b/build/pkgs/snappy/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/surf/math b/build/pkgs/surf/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/surface_dynamics/math b/build/pkgs/surface_dynamics/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/tdlib/math b/build/pkgs/tdlib/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/tides/math b/build/pkgs/tides/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/topcom/math b/build/pkgs/topcom/math new file mode 100644 index 00000000000..e69de29bb2d From 38e5e8af17e6c06e70e0ee70ebbf6dedf9e3fdf1 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 13 Feb 2024 00:39:26 -0800 Subject: [PATCH 163/278] src/doc/bootstrap: Separate section for optional math packages --- src/doc/bootstrap | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/doc/bootstrap b/src/doc/bootstrap index 8b4262dd156..71e13bb6eab 100755 --- a/src/doc/bootstrap +++ b/src/doc/bootstrap @@ -122,12 +122,25 @@ Optional Packages For additional functionality, you can install some of the following optional packages. +Mathematics +~~~~~~~~~~~ + +EOF +for PKG_BASE in $(sage-package list --has-file SPKG.rst --has-file math :optional: | grep -v '^sagemath_'); do + echo "* :ref:\`spkg_$PKG_BASE\`" +done >> "$OUTPUT_INDEX" +cat >> "$OUTPUT_INDEX" <> "$OUTPUT_INDEX" cat >> "$OUTPUT_INDEX" < Date: Tue, 13 Feb 2024 09:42:03 -0800 Subject: [PATCH 164/278] build/pkgs: Mark even more math packages with a 'math' tag --- build/pkgs/awali/math | 0 build/pkgs/frobby/math | 0 build/pkgs/gambit/math | 0 build/pkgs/lie/math | 0 build/pkgs/modular_decomposition/math | 0 build/pkgs/polylib/math | 0 build/pkgs/r_jupyter/math | 0 7 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 build/pkgs/awali/math create mode 100644 build/pkgs/frobby/math create mode 100644 build/pkgs/gambit/math create mode 100644 build/pkgs/lie/math create mode 100644 build/pkgs/modular_decomposition/math create mode 100644 build/pkgs/polylib/math create mode 100644 build/pkgs/r_jupyter/math diff --git a/build/pkgs/awali/math b/build/pkgs/awali/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/frobby/math b/build/pkgs/frobby/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/gambit/math b/build/pkgs/gambit/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/lie/math b/build/pkgs/lie/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/modular_decomposition/math b/build/pkgs/modular_decomposition/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/polylib/math b/build/pkgs/polylib/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/r_jupyter/math b/build/pkgs/r_jupyter/math new file mode 100644 index 00000000000..e69de29bb2d From 32e31a3d43442a426d72657b837140e79df6bb5b Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 13 Feb 2024 09:42:21 -0800 Subject: [PATCH 165/278] src/doc/bootstrap: Separate section for experimental math packages --- src/doc/bootstrap | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/doc/bootstrap b/src/doc/bootstrap index 71e13bb6eab..1c57e750723 100755 --- a/src/doc/bootstrap +++ b/src/doc/bootstrap @@ -196,13 +196,27 @@ Some packages that provide additional functionality are marked as "experimental". Developers are needed in order to improve the integration of these packages into the Sage distribution. +Mathematics +~~~~~~~~~~~ + EOF -for PKG_BASE in $(sage-package list --has-file SPKG.rst :experimental: | grep -v '^sagemath_'); do +for PKG_BASE in $(sage-package list --has-file SPKG.rst --has-file math :experimental: | grep -v '^sagemath_'); do echo "* :ref:\`spkg_$PKG_BASE\`" done >> "$OUTPUT_INDEX" cat >> "$OUTPUT_INDEX" <> "$OUTPUT_INDEX" + +cat >> "$OUTPUT_INDEX" < Date: Tue, 13 Feb 2024 09:49:26 -0800 Subject: [PATCH 166/278] build/pkgs: Mark yet more math packages with a 'math' tag --- build/pkgs/fpylll/math | 0 build/pkgs/polymake/math | 0 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 build/pkgs/fpylll/math create mode 100644 build/pkgs/polymake/math diff --git a/build/pkgs/fpylll/math b/build/pkgs/fpylll/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/polymake/math b/build/pkgs/polymake/math new file mode 100644 index 00000000000..e69de29bb2d From 9a52ea48f33df2e39fc0667e4beb14563f30cdf9 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 13 Feb 2024 09:49:49 -0800 Subject: [PATCH 167/278] build/pkgs: Fix some SPKG.rst titles --- build/pkgs/imagemagick/SPKG.rst | 2 +- build/pkgs/modular_resolution/SPKG.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build/pkgs/imagemagick/SPKG.rst b/build/pkgs/imagemagick/SPKG.rst index 9bd89270e5b..241b2981833 100644 --- a/build/pkgs/imagemagick/SPKG.rst +++ b/build/pkgs/imagemagick/SPKG.rst @@ -1,4 +1,4 @@ -ImageMagick: A collection of tools and libraries for many image file formats +imagemagick: A collection of tools and libraries for many image file formats ============================================================================ Description diff --git a/build/pkgs/modular_resolution/SPKG.rst b/build/pkgs/modular_resolution/SPKG.rst index 22c3f90f0fe..9de3398c83a 100644 --- a/build/pkgs/modular_resolution/SPKG.rst +++ b/build/pkgs/modular_resolution/SPKG.rst @@ -1,4 +1,4 @@ -p_group_cohomology: Modular cohomology rings of finite groups +modular_resolution: Modular cohomology rings of finite groups ============================================================= Description From 1d12b8a794222c82cfe2262e6021d83ad6b8705a Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 17 Feb 2024 19:20:33 -0800 Subject: [PATCH 168/278] build/pkgs: Mark some packages with tag 'front-end'; src/doc/bootstrap: Show separately --- build/pkgs/dot2tex/front-end | 0 build/pkgs/graphviz/front-end | 0 build/pkgs/ipympl/front-end | 0 build/pkgs/ipython/front-end | 0 build/pkgs/ipywidgets/front-end | 0 build/pkgs/jmol/front-end | 0 build/pkgs/jupyter_jsmol/front-end | 0 build/pkgs/jupyterlab/front-end | 0 build/pkgs/jupyterlab_widgets/front-end | 0 build/pkgs/matplotlib/front-end | 0 build/pkgs/matplotlib_inline/front-end | 0 build/pkgs/nbconvert/front-end | 0 build/pkgs/notebook/front-end | 0 build/pkgs/pygraphviz/front-end | 0 build/pkgs/rst2ipynb/front-end | 0 build/pkgs/sage_sws2rst/front-end | 0 build/pkgs/sagenb_export/front-end | 0 build/pkgs/sagetex/front-end | 0 build/pkgs/tachyon/front-end | 0 build/pkgs/texlive/front-end | 0 build/pkgs/threejs/front-end | 0 src/doc/bootstrap | 34 +++++++++++++++++++------ 22 files changed, 26 insertions(+), 8 deletions(-) create mode 100644 build/pkgs/dot2tex/front-end create mode 100644 build/pkgs/graphviz/front-end create mode 100644 build/pkgs/ipympl/front-end create mode 100644 build/pkgs/ipython/front-end create mode 100644 build/pkgs/ipywidgets/front-end create mode 100644 build/pkgs/jmol/front-end create mode 100644 build/pkgs/jupyter_jsmol/front-end create mode 100644 build/pkgs/jupyterlab/front-end create mode 100644 build/pkgs/jupyterlab_widgets/front-end create mode 100644 build/pkgs/matplotlib/front-end create mode 100644 build/pkgs/matplotlib_inline/front-end create mode 100644 build/pkgs/nbconvert/front-end create mode 100644 build/pkgs/notebook/front-end create mode 100644 build/pkgs/pygraphviz/front-end create mode 100644 build/pkgs/rst2ipynb/front-end create mode 100644 build/pkgs/sage_sws2rst/front-end create mode 100644 build/pkgs/sagenb_export/front-end create mode 100644 build/pkgs/sagetex/front-end create mode 100644 build/pkgs/tachyon/front-end create mode 100644 build/pkgs/texlive/front-end create mode 100644 build/pkgs/threejs/front-end diff --git a/build/pkgs/dot2tex/front-end b/build/pkgs/dot2tex/front-end new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/graphviz/front-end b/build/pkgs/graphviz/front-end new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/ipympl/front-end b/build/pkgs/ipympl/front-end new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/ipython/front-end b/build/pkgs/ipython/front-end new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/ipywidgets/front-end b/build/pkgs/ipywidgets/front-end new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/jmol/front-end b/build/pkgs/jmol/front-end new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/jupyter_jsmol/front-end b/build/pkgs/jupyter_jsmol/front-end new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/jupyterlab/front-end b/build/pkgs/jupyterlab/front-end new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/jupyterlab_widgets/front-end b/build/pkgs/jupyterlab_widgets/front-end new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/matplotlib/front-end b/build/pkgs/matplotlib/front-end new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/matplotlib_inline/front-end b/build/pkgs/matplotlib_inline/front-end new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/nbconvert/front-end b/build/pkgs/nbconvert/front-end new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/notebook/front-end b/build/pkgs/notebook/front-end new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/pygraphviz/front-end b/build/pkgs/pygraphviz/front-end new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/rst2ipynb/front-end b/build/pkgs/rst2ipynb/front-end new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/sage_sws2rst/front-end b/build/pkgs/sage_sws2rst/front-end new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/sagenb_export/front-end b/build/pkgs/sagenb_export/front-end new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/sagetex/front-end b/build/pkgs/sagetex/front-end new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/tachyon/front-end b/build/pkgs/tachyon/front-end new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/texlive/front-end b/build/pkgs/texlive/front-end new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/threejs/front-end b/build/pkgs/threejs/front-end new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/doc/bootstrap b/src/doc/bootstrap index 1c57e750723..6aa0e98c0ec 100755 --- a/src/doc/bootstrap +++ b/src/doc/bootstrap @@ -106,11 +106,20 @@ for PKG_BASE in $(sage-package list --has-file SPKG.rst --has-file math :standar done >> "$OUTPUT_INDEX" cat >> "$OUTPUT_INDEX" <> "$OUTPUT_INDEX" +cat >> "$OUTPUT_INDEX" <> "$OUTPUT_INDEX" cat >> "$OUTPUT_INDEX" <> "$OUTPUT_INDEX" cat >> "$OUTPUT_INDEX" <> "$OUTPUT_INDEX" +cat >> "$OUTPUT_INDEX" <> "$OUTPUT_INDEX" cat >> "$OUTPUT_INDEX" <> "$OUTPUT_INDEX" cat >> "$OUTPUT_INDEX" < Date: Sat, 17 Feb 2024 19:21:35 -0800 Subject: [PATCH 169/278] build/pkgs/modular_resolution: Add tag 'math' --- build/pkgs/modular_resolution/math | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 build/pkgs/modular_resolution/math diff --git a/build/pkgs/modular_resolution/math b/build/pkgs/modular_resolution/math new file mode 100644 index 00000000000..e69de29bb2d From 62cef34140ba56a0d5ff7d9e23568a9b32b04bb1 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 17 Feb 2024 19:22:36 -0800 Subject: [PATCH 170/278] build/pkgs: Mark some more packages with tag 'front-end' --- build/pkgs/pandoc/front-end | 0 build/pkgs/pdf2svg/front-end | 0 src/doc/bootstrap | 4 ++-- 3 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 build/pkgs/pandoc/front-end create mode 100644 build/pkgs/pdf2svg/front-end diff --git a/build/pkgs/pandoc/front-end b/build/pkgs/pandoc/front-end new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/pdf2svg/front-end b/build/pkgs/pdf2svg/front-end new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/doc/bootstrap b/src/doc/bootstrap index 6aa0e98c0ec..b1cf956dea3 100755 --- a/src/doc/bootstrap +++ b/src/doc/bootstrap @@ -106,8 +106,8 @@ for PKG_BASE in $(sage-package list --has-file SPKG.rst --has-file math :standar done >> "$OUTPUT_INDEX" cat >> "$OUTPUT_INDEX" < Date: Sun, 18 Feb 2024 10:01:40 -0800 Subject: [PATCH 171/278] src/doc/en/developer/packaging.rst: Document the new tags --- src/doc/bootstrap | 1 + src/doc/en/developer/packaging.rst | 37 ++++++++++++++++++++---------- 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/src/doc/bootstrap b/src/doc/bootstrap index b1cf956dea3..df73fd83a39 100755 --- a/src/doc/bootstrap +++ b/src/doc/bootstrap @@ -86,6 +86,7 @@ if [ "${BOOTSTRAP_QUIET}" = "no" ]; then fi OUTPUT_INDEX="$OUTPUT_DIR"/index.rst cat > "$OUTPUT_INDEX" <`. + +- Place an empty file named ``math`` in the package directory to make the + package appear in the "Mathematics" subsections of the index of standard, + optional, and experimental packages. + +- Place an empty file name ``front-end`` in the package directory to make + the package appear in the "Front-end, graphics, document preparation" + subsections. + +- Packages without these tags appear in the "Other dependencies" subsections. + +We use the following tags in our continuous integration scripts to filter out packages that we cannot or should not test automatically. +- You can mark a package as "huge" by placing an empty file named + ``huge`` in the package directory. For example, the package + ``polytopes_db_4d`` is a large database whose compressed tarball has a + size of 9 GB. + +- For some other packages, we have placed an empty file named + ``has_nonfree_dependencies`` in the package directory. This is to + indicate that Sage with this package installed cannot be + redistributed, and also that the package can only be installed after + installing some other, non-free package. + .. _section-trees: From 69bc527e3e5c7b16616a8a769b089a13e4f44bce Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 24 Feb 2024 15:27:23 -0800 Subject: [PATCH 172/278] src/doc/bootstrap: Fix subsection heading --- src/doc/bootstrap | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/doc/bootstrap b/src/doc/bootstrap index df73fd83a39..32a31430554 100755 --- a/src/doc/bootstrap +++ b/src/doc/bootstrap @@ -225,8 +225,8 @@ done >> "$OUTPUT_INDEX" cat >> "$OUTPUT_INDEX" < Date: Sat, 24 Feb 2024 18:30:15 -0800 Subject: [PATCH 173/278] build/pkgs/pdf2svg/SPKG.rst: Fix title format --- build/pkgs/pdf2svg/SPKG.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build/pkgs/pdf2svg/SPKG.rst b/build/pkgs/pdf2svg/SPKG.rst index 2a87b276be0..a9dc895e8a6 100644 --- a/build/pkgs/pdf2svg/SPKG.rst +++ b/build/pkgs/pdf2svg/SPKG.rst @@ -1,5 +1,5 @@ -pdf2svg - PDF to SVG convertor -============================== +pdf2svg: PDF to SVG convertor +============================= Description ----------- From 0f7aec6ba79678e995d0511b016b89281272eed2 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 27 Feb 2024 15:31:31 -0800 Subject: [PATCH 174/278] src/doc/en/developer: Replace outdated link to https://www.sagemath.org/links-components.html by ref to spkg chapter --- src/doc/en/developer/coding_basics.rst | 10 +++------- src/doc/en/developer/coding_in_other.rst | 10 +++------- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/src/doc/en/developer/coding_basics.rst b/src/doc/en/developer/coding_basics.rst index 764d3781d33..7471a9d4022 100644 --- a/src/doc/en/developer/coding_basics.rst +++ b/src/doc/en/developer/coding_basics.rst @@ -7,10 +7,10 @@ General Conventions =================== -There are many ways to contribute to Sage including sharing scripts -and Sage worksheets that implement new functionality using Sage, +There are many ways to contribute to Sage, including sharing scripts +and Jupyter notebooks that implement new functionality using Sage, improving to the Sage library, or to working on the many underlying -libraries distributed with Sage [1]_. +libraries distributed with Sage, see :ref:`spkg`. This guide focuses on editing the Sage library itself. Sage is not just about gathering together functionality. It is about @@ -20,10 +20,6 @@ mathematically. In the design of Sage, the semantics of objects, the definitions, etc., are informed by how the corresponding objects are used in everyday mathematics. -.. [1] - See https://www.sagemath.org/links-components.html for a full list - of packages shipped with every copy of Sage - To meet the goal of making Sage easy to read, maintain, and improve, all Python/Cython code that is included with Sage should adhere to the style conventions discussed in this chapter. diff --git a/src/doc/en/developer/coding_in_other.rst b/src/doc/en/developer/coding_in_other.rst index 6c378097796..54ff5d3b772 100644 --- a/src/doc/en/developer/coding_in_other.rst +++ b/src/doc/en/developer/coding_in_other.rst @@ -8,9 +8,9 @@ When writing code for Sage, use Python for the basic structure and interface. For speed, efficiency, or convenience, you can implement parts of the code using any of the following languages: :ref:`Cython `, C/C++, Fortran 95, GAP, Common Lisp, Singular, and -PARI/GP. You can also use all C/C++ libraries included with Sage -[SageComponents]_. And if you are okay with your code depending on -optional Sage packages, you can use Octave, or even Magma, +PARI/GP. You can also use all C/C++ libraries included with Sage, +see :ref:`spkg`. And if you are okay with your code depending on +external programs, you can use Octave, or even Magma, Mathematica, or Maple. In this chapter, we discuss interfaces between Sage and :ref:`PARI @@ -753,7 +753,3 @@ These are only excerpts from ``octave.py``; check that file for more definitions and examples. Look at other files in the directory ``SAGE_ROOT/src/sage/interfaces/`` for examples of interfaces to other software packages. - - -.. [SageComponents] See http://www.sagemath.org/links-components.html - for a list From 3abd66fce2e634b28accbeaac4d0c5c1081221dc Mon Sep 17 00:00:00 2001 From: Omegaconstant Date: Sun, 3 Mar 2024 19:55:41 +0530 Subject: [PATCH 175/278] Added .. RUBRIC:: for example headings --- src/sage/sets/recursively_enumerated_set.pyx | 26 ++++++++------------ 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/src/sage/sets/recursively_enumerated_set.pyx b/src/sage/sets/recursively_enumerated_set.pyx index 047338518de..b4c61ae49ad 100644 --- a/src/sage/sets/recursively_enumerated_set.pyx +++ b/src/sage/sets/recursively_enumerated_set.pyx @@ -22,8 +22,7 @@ AUTHORS: EXAMPLES: -No hypothesis on the structure ------------------------------- +.. RUBRIC:: No hypothesis on the structure What we mean by "no hypothesis" is that the set is not known to be a forest, symmetric, or graded. However, it may have other @@ -50,8 +49,7 @@ Depth first search:: sage: [next(it) for _ in range(10)] [0, 3, 6, 9, 12, 15, 18, 21, 24, 27] -Symmetric structure -------------------- +.. RUBRIC:: Symmetric structure The origin ``(0, 0)`` as seed and the upper, lower, left and right lattice point as successor function. This function is symmetric since `p` is a @@ -86,8 +84,7 @@ Levels (elements of given depth):: sage: sorted(C.graded_component(2)) [(-2, 0), (-1, -1), (-1, 1), (0, -2), (0, 2), (1, -1), (1, 1), (2, 0)] -Graded structure ----------------- +.. RUBRIC:: Graded structure Identity permutation as seed and ``permutohedron_succ`` as successor function:: @@ -137,10 +134,7 @@ Graded components (set of elements of the same depth):: sage: sorted(R.graded_component(10)) [[5, 4, 3, 2, 1]] -Forest structure ----------------- - -.. RUBRIC:: Forest structure [Ex 1] +.. RUBRIC:: Forest structure (Example 1) The set of words over the alphabet `\{a,b\}` can be generated from the empty word by appending the letter `a` or `b` as a successor function. This set @@ -175,20 +169,20 @@ classes being very similar): .. MATH:: \begin{array}{ccc} - & ``\," \\ + & \emptyset \\ \hfil\swarrow & \downarrow & \searrow\hfil\\ - ``a" & ``b" & ``c" \\ + a & b & c \\ \begin{array}{ccc} \swarrow & \downarrow & \searrow \\ - ``aa" & ``ab" & ``ac" \\ + aa & ab & ac \\ \end{array} & \begin{array}{ccc} \swarrow & \downarrow & \searrow \\ - ``ba" & ``bb" & ``bc" \\ + ba & bb & bc \\ \end{array} & \begin{array}{ccc} \swarrow & \downarrow & \searrow \\ - ``ca" & ``cb" & ``cc" \\ + ca & cb & cc \\ \end{array} \end{array} @@ -230,7 +224,7 @@ or:: sage: S.list() ['', 'a', 'aa', 'ab', 'ac', 'b', 'ba', 'bb', 'bc', 'c', 'ca', 'cb', 'cc'] -.. RUBRIC:: Forest structure [Ex 2] +.. RUBRIC:: Forest structure (Example 2) This example was provided by Florent Hivert. From 162c152663fa38bebe932b73e1ba05387064e248 Mon Sep 17 00:00:00 2001 From: Omegaconstant Date: Mon, 4 Mar 2024 22:43:55 +0530 Subject: [PATCH 176/278] Changed 'Recursively enumerated set' to 'Recursively Enumerated Sets' --- src/sage/sets/recursively_enumerated_set.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/sets/recursively_enumerated_set.pyx b/src/sage/sets/recursively_enumerated_set.pyx index b4c61ae49ad..fa5c20293da 100644 --- a/src/sage/sets/recursively_enumerated_set.pyx +++ b/src/sage/sets/recursively_enumerated_set.pyx @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- r""" -Recursively enumerated set +Recursively Enumerated Sets A set `S` is called recursively enumerable if there is an algorithm that enumerates the members of `S`. We consider here the recursively enumerated From 4124f605237c5b17e820774e3d0a8928485d21e8 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 13 Feb 2024 12:13:15 -0800 Subject: [PATCH 177/278] build/pkgs/appdirs: Change to wheel package, reformat title in SPKG.rst --- build/pkgs/appdirs/SPKG.rst | 6 +++--- build/pkgs/appdirs/checksums.ini | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/build/pkgs/appdirs/SPKG.rst b/build/pkgs/appdirs/SPKG.rst index 6018e327f90..c38ff9b99a4 100644 --- a/build/pkgs/appdirs/SPKG.rst +++ b/build/pkgs/appdirs/SPKG.rst @@ -1,10 +1,10 @@ -appdirs: A small Python module for determining appropriate platform-specific dirs, e.g. a "user data dir". -========================================================================================================== +appdirs: Small Python module for determining appropriate platform-specific dirs, e.g. a "user data dir" +======================================================================================================= Description ----------- -A small Python module for determining appropriate platform-specific dirs, e.g. a "user data dir". +Small Python module for determining appropriate platform-specific dirs, e.g. a "user data dir" License ------- diff --git a/build/pkgs/appdirs/checksums.ini b/build/pkgs/appdirs/checksums.ini index 7b26025094c..3e83a68f3a0 100644 --- a/build/pkgs/appdirs/checksums.ini +++ b/build/pkgs/appdirs/checksums.ini @@ -1,5 +1,5 @@ -tarball=appdirs-VERSION.tar.gz -sha1=1fa04e44b1084338cb7b21e9cf44fce5efb81840 -md5=d6bca12613174185dd9abc8a29f4f012 -cksum=1191718163 -upstream_url=https://pypi.io/packages/source/a/appdirs/appdirs-VERSION.tar.gz +tarball=appdirs-VERSION-py2.py3-none-any.whl +sha1=fc74022712122436427f8282a47bfa430ec2db56 +md5=1d638a5913a8d8ed8e7c7d789ece149c +cksum=3594208532 +upstream_url=https://pypi.io/packages/py2.py3/a/appdirs/appdirs-VERSION-py2.py3-none-any.whl From 10253b222931b6eeb6574a19901945506b679b4d Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 13 Feb 2024 12:19:04 -0800 Subject: [PATCH 178/278] build/pkgs/json5: Reformat title in SPKG.rst --- build/pkgs/colorama/SPKG.rst | 6 +++--- build/pkgs/json5/SPKG.rst | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/build/pkgs/colorama/SPKG.rst b/build/pkgs/colorama/SPKG.rst index 3335092e4c7..1045fb9e424 100644 --- a/build/pkgs/colorama/SPKG.rst +++ b/build/pkgs/colorama/SPKG.rst @@ -1,10 +1,10 @@ -colorama: Cross-platform colored terminal text. -=============================================== +colorama: Cross-platform colored terminal text +============================================== Description ----------- -Cross-platform colored terminal text. +Cross-platform colored terminal text License ------- diff --git a/build/pkgs/json5/SPKG.rst b/build/pkgs/json5/SPKG.rst index 5f85e534785..19bb7036bfa 100644 --- a/build/pkgs/json5/SPKG.rst +++ b/build/pkgs/json5/SPKG.rst @@ -1,10 +1,10 @@ -json5: A Python implementation of the JSON5 data format. -======================================================== +json5: Python implementation of the JSON5 data format +===================================================== Description ----------- -A Python implementation of the JSON5 data format. +Python implementation of the JSON5 data format License ------- From d4a9154ec16ee04c4b024627285a187c4c5ccc2d Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 13 Feb 2024 12:21:18 -0800 Subject: [PATCH 179/278] build/pkgs/jupyterlab_server: Reformat title in SPKG.rst --- build/pkgs/jupyterlab_server/SPKG.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build/pkgs/jupyterlab_server/SPKG.rst b/build/pkgs/jupyterlab_server/SPKG.rst index aca71f627ec..450a6f70ef2 100644 --- a/build/pkgs/jupyterlab_server/SPKG.rst +++ b/build/pkgs/jupyterlab_server/SPKG.rst @@ -1,10 +1,10 @@ -jupyterlab_server: A set of server components for JupyterLab and JupyterLab like applications. -============================================================================================== +jupyterlab_server: Set of server components for JupyterLab and JupyterLab like applications +=========================================================================================== Description ----------- -A set of server components for JupyterLab and JupyterLab like applications. +Set of server components for JupyterLab and JupyterLab like applications License ------- From 93a11619143b0ab99077f6169fe239953d0321ff Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 13 Feb 2024 12:23:59 -0800 Subject: [PATCH 180/278] build/pkgs/kiwisolver: Regenerate title in SPKG.rst --- build/pkgs/kiwisolver/SPKG.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build/pkgs/kiwisolver/SPKG.rst b/build/pkgs/kiwisolver/SPKG.rst index f461bf01f33..9c3ad4350f7 100644 --- a/build/pkgs/kiwisolver/SPKG.rst +++ b/build/pkgs/kiwisolver/SPKG.rst @@ -1,5 +1,5 @@ -kiwisolver: An implementation of the Cassowary constraint solving algorithm -=========================================================================== +kiwisolver: Fast implementation of the Cassowary constraint solver +================================================================== Description ----------- From c32ce91863a5ff17b3fc5bebd960a27a943f5688 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 13 Feb 2024 12:25:28 -0800 Subject: [PATCH 181/278] build/pkgs/nbclient: Reformat title in SPKG.rst --- build/pkgs/nbclient/SPKG.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build/pkgs/nbclient/SPKG.rst b/build/pkgs/nbclient/SPKG.rst index ea6a94cc0aa..53e69bcdc05 100644 --- a/build/pkgs/nbclient/SPKG.rst +++ b/build/pkgs/nbclient/SPKG.rst @@ -1,10 +1,10 @@ -nbclient: A client library for executing notebooks. Formerly nbconvert's ExecutePreprocessor. -============================================================================================= +nbclient: Client library for executing notebooks. Formerly nbconvert's ExecutePreprocessor +========================================================================================== Description ----------- -A client library for executing notebooks. Formerly nbconvert's ExecutePreprocessor. +Client library for executing notebooks. Formerly nbconvert's ExecutePreprocessor License ------- From 94a7329cc93d54dee3bb7ecd7685cae98b1b1389 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 13 Feb 2024 12:27:17 -0800 Subject: [PATCH 182/278] build/pkgs/mistune: Regenerate title in SPKG.rst --- build/pkgs/mistune/SPKG.rst | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/build/pkgs/mistune/SPKG.rst b/build/pkgs/mistune/SPKG.rst index 7d08e818625..f9c18ff407b 100644 --- a/build/pkgs/mistune/SPKG.rst +++ b/build/pkgs/mistune/SPKG.rst @@ -1,19 +1,18 @@ -mistune: A markdown parser in pure Python -========================================= +mistune: Sane and fast Markdown parser with useful plugins and renderers +======================================================================== Description ----------- -The fastest markdown parser in pure Python +Sane and fast Markdown parser with useful plugins and renderers License ------- -BSD License - +BSD-3-Clause Upstream Contact ---------------- -Home Page: https://github.com/lepture/mistune +https://pypi.org/project/mistune/ From 28ca1dd66e902339c2884078ddce671accf58927 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 13 Feb 2024 12:28:05 -0800 Subject: [PATCH 183/278] build/pkgs/overrides: Reformat title in SPKG.rst --- build/pkgs/overrides/SPKG.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build/pkgs/overrides/SPKG.rst b/build/pkgs/overrides/SPKG.rst index bc6dcdbfdd6..61cc5173cf2 100644 --- a/build/pkgs/overrides/SPKG.rst +++ b/build/pkgs/overrides/SPKG.rst @@ -1,10 +1,10 @@ -overrides: A decorator to automatically detect mismatch when overriding a method. -================================================================================= +overrides: Decorator to automatically detect mismatch when overriding a method +============================================================================== Description ----------- -A decorator to automatically detect mismatch when overriding a method. +Decorator to automatically detect mismatch when overriding a method License ------- From b39c7a2904f82c7edd2d8b00eaa23015ae1a29bf Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 13 Feb 2024 12:29:10 -0800 Subject: [PATCH 184/278] build/pkgs/pathspec: Reformat title in SPKG.rst --- build/pkgs/pathspec/SPKG.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build/pkgs/pathspec/SPKG.rst b/build/pkgs/pathspec/SPKG.rst index 4d1ee72eba3..12d2b49b79a 100644 --- a/build/pkgs/pathspec/SPKG.rst +++ b/build/pkgs/pathspec/SPKG.rst @@ -1,10 +1,10 @@ -pathspec: Utility library for gitignore style pattern matching of file paths. -============================================================================= +pathspec: Utility library for gitignore style pattern matching of file paths +============================================================================ Description ----------- -Utility library for gitignore style pattern matching of file paths. +Utility library for gitignore style pattern matching of file paths License ------- From a81c83c2f03dce35e9667d94081492ecd9d58aca Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 13 Feb 2024 12:30:03 -0800 Subject: [PATCH 185/278] build/pkgs/platformdirs: Reformat title in SPKG.rst --- build/pkgs/platformdirs/SPKG.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build/pkgs/platformdirs/SPKG.rst b/build/pkgs/platformdirs/SPKG.rst index ef40ff09e0c..80f9641d5f6 100644 --- a/build/pkgs/platformdirs/SPKG.rst +++ b/build/pkgs/platformdirs/SPKG.rst @@ -1,10 +1,10 @@ -platformdirs: A small Python module for determining appropriate platform-specific dirs, e.g. a "user data dir". -=============================================================================================================== +platformdirs: Small Python package for determining appropriate platform-specific dirs, e.g. a "user data dir" +============================================================================================================= Description ----------- -A small Python module for determining appropriate platform-specific dirs, e.g. a "user data dir". +Small Python package for determining appropriate platform-specific dirs, e.g. a "user data dir" License ------- From b4fa76d2b5dd1ed815edbaec976786c28491f0cb Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 13 Feb 2024 12:31:14 -0800 Subject: [PATCH 186/278] build/pkgs/pluggy: Reformat title in SPKG.rst --- build/pkgs/pluggy/SPKG.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build/pkgs/pluggy/SPKG.rst b/build/pkgs/pluggy/SPKG.rst index b15967007ed..13f4f3ba3c4 100644 --- a/build/pkgs/pluggy/SPKG.rst +++ b/build/pkgs/pluggy/SPKG.rst @@ -1,10 +1,10 @@ -pluggy: plugin and hook calling mechanisms for python +pluggy: Plugin and hook calling mechanisms for python ===================================================== Description ----------- -plugin and hook calling mechanisms for python +Plugin and hook calling mechanisms for python License ------- From 1ae73653db54a531e95f0569c41a87d45a6aaa70 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 13 Feb 2024 12:32:05 -0800 Subject: [PATCH 187/278] build/pkgs/py: Reformat title in SPKG.rst --- build/pkgs/py/SPKG.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build/pkgs/py/SPKG.rst b/build/pkgs/py/SPKG.rst index bf0409acda7..4e8657e33f2 100644 --- a/build/pkgs/py/SPKG.rst +++ b/build/pkgs/py/SPKG.rst @@ -1,10 +1,10 @@ -py: library with cross-python path, ini-parsing, io, code, log facilities +py: Library with cross-python path, ini-parsing, io, code, log facilities ========================================================================= Description ----------- -library with cross-python path, ini-parsing, io, code, log facilities +Library with cross-python path, ini-parsing, io, code, log facilities License ------- From 0e6c0c3185579f51b5c4614d19d886c4c59db650 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 13 Feb 2024 12:34:05 -0800 Subject: [PATCH 188/278] build/pkgs/python_json_logger: Reformat title in SPKG.rst --- build/pkgs/python_json_logger/SPKG.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build/pkgs/python_json_logger/SPKG.rst b/build/pkgs/python_json_logger/SPKG.rst index 8e25e0f8387..9cd588b3023 100644 --- a/build/pkgs/python_json_logger/SPKG.rst +++ b/build/pkgs/python_json_logger/SPKG.rst @@ -1,10 +1,10 @@ -python_json_logger: A python library adding a json log formatter -================================================================ +python_json_logger: Python library adding a json log formatter +============================================================== Description ----------- -A python library adding a json log formatter +Python library adding a json log formatter License ------- From 9d5e38297e4ecb389141c3ea82d796087e24c008 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 13 Feb 2024 12:35:52 -0800 Subject: [PATCH 189/278] build/pkgs/soupsieve: Reformat title in SPKG.rst --- build/pkgs/soupsieve/SPKG.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build/pkgs/soupsieve/SPKG.rst b/build/pkgs/soupsieve/SPKG.rst index 401519ceec9..09be828d5b5 100644 --- a/build/pkgs/soupsieve/SPKG.rst +++ b/build/pkgs/soupsieve/SPKG.rst @@ -1,10 +1,10 @@ -soupsieve: A modern CSS selector implementation for Beautiful Soup. -=================================================================== +soupsieve: Modern CSS selector implementation for Beautiful Soup +================================================================ Description ----------- -A modern CSS selector implementation for Beautiful Soup. +Modern CSS selector implementation for Beautiful Soup License ------- From 689ad3ef582559b4046237749acd3ed4e16bf282 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 13 Feb 2024 12:36:56 -0800 Subject: [PATCH 190/278] build/pkgs/sphinx_basic_ng: Reformat title in SPKG.rst --- build/pkgs/sphinx_basic_ng/SPKG.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build/pkgs/sphinx_basic_ng/SPKG.rst b/build/pkgs/sphinx_basic_ng/SPKG.rst index c137da5c63b..a09b406026f 100644 --- a/build/pkgs/sphinx_basic_ng/SPKG.rst +++ b/build/pkgs/sphinx_basic_ng/SPKG.rst @@ -1,10 +1,10 @@ -sphinx_basic_ng: A modern skeleton for Sphinx themes. -===================================================== +sphinx_basic_ng: Modern skeleton for Sphinx themes +================================================== Description ----------- -A modern skeleton for Sphinx themes. +Modern skeleton for Sphinx themes License ------- From f54f6478ad2aaf497690a447af2f7fffef4f5b99 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 13 Feb 2024 12:37:45 -0800 Subject: [PATCH 191/278] build/pkgs/sphinx_copybutton: Reformat title in SPKG.rst --- build/pkgs/sphinx_copybutton/SPKG.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build/pkgs/sphinx_copybutton/SPKG.rst b/build/pkgs/sphinx_copybutton/SPKG.rst index 2a4d04e7bbf..eefeb83b8b6 100644 --- a/build/pkgs/sphinx_copybutton/SPKG.rst +++ b/build/pkgs/sphinx_copybutton/SPKG.rst @@ -1,10 +1,10 @@ -sphinx_copybutton: Add a copy button to each of your code cells. -================================================================ +sphinx_copybutton: Add a copy button to each of your code cells +=============================================================== Description ----------- -Add a copy button to each of your code cells. +Add a copy button to each of your code cells License ------- From 05814e5576873e6fc1ea78f0cd389bb6ddc2dc2c Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 13 Feb 2024 12:38:31 -0800 Subject: [PATCH 192/278] build/pkgs/sphinx_inline_tabs: Reformat title in SPKG.rst --- build/pkgs/sphinx_inline_tabs/SPKG.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build/pkgs/sphinx_inline_tabs/SPKG.rst b/build/pkgs/sphinx_inline_tabs/SPKG.rst index 1360f35d925..1d7164453e6 100644 --- a/build/pkgs/sphinx_inline_tabs/SPKG.rst +++ b/build/pkgs/sphinx_inline_tabs/SPKG.rst @@ -1,10 +1,10 @@ -sphinx_inline_tabs: Add inline tabbed content to your Sphinx documentation. -=========================================================================== +sphinx_inline_tabs: Add inline tabbed content to your Sphinx documentation +========================================================================== Description ----------- -Add inline tabbed content to your Sphinx documentation. +Add inline tabbed content to your Sphinx documentation License ------- From b14ae7b98416baf2569c6ef0b8d6b4e05393b9b5 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 13 Feb 2024 12:41:13 -0800 Subject: [PATCH 193/278] build/pkgs/trove_classifiers: Reformat title in SPKG.rst --- build/pkgs/trove_classifiers/SPKG.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build/pkgs/trove_classifiers/SPKG.rst b/build/pkgs/trove_classifiers/SPKG.rst index 4e1e7d08746..c0f7ef6ae05 100644 --- a/build/pkgs/trove_classifiers/SPKG.rst +++ b/build/pkgs/trove_classifiers/SPKG.rst @@ -1,10 +1,10 @@ -trove_classifiers: Canonical source for classifiers on PyPI (pypi.org). -======================================================================= +trove_classifiers: Canonical source for classifiers on PyPI (pypi.org) +====================================================================== Description ----------- -Canonical source for classifiers on PyPI (pypi.org). +Canonical source for classifiers on PyPI (pypi.org) License ------- From 10ec71583eeaeb7902310e1fc4c6ab2403cdbe79 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 13 Feb 2024 12:42:44 -0800 Subject: [PATCH 194/278] build/pkgs/urllib3: Reformat title in SPKG.rst --- build/pkgs/urllib3/SPKG.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build/pkgs/urllib3/SPKG.rst b/build/pkgs/urllib3/SPKG.rst index 966295796c4..f40dc17c993 100644 --- a/build/pkgs/urllib3/SPKG.rst +++ b/build/pkgs/urllib3/SPKG.rst @@ -1,10 +1,10 @@ -urllib3: HTTP library with thread-safe connection pooling, file post, and more. -=============================================================================== +urllib3: HTTP library with thread-safe connection pooling, file post, and more +============================================================================== Description ----------- -HTTP library with thread-safe connection pooling, file post, and more. +HTTP library with thread-safe connection pooling, file post, and more License ------- From 9d476b6de1da6fd69c17d541b4115310e78c4b3d Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 13 Feb 2024 12:43:46 -0800 Subject: [PATCH 195/278] build/pkgs/webcolors: Reformat title in SPKG.rst --- build/pkgs/webcolors/SPKG.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build/pkgs/webcolors/SPKG.rst b/build/pkgs/webcolors/SPKG.rst index 760b53943d2..043b971415e 100644 --- a/build/pkgs/webcolors/SPKG.rst +++ b/build/pkgs/webcolors/SPKG.rst @@ -1,10 +1,10 @@ -webcolors: A library for working with the color formats defined by HTML and CSS. -================================================================================ +webcolors: Library for working with the color formats defined by HTML and CSS +============================================================================= Description ----------- -A library for working with the color formats defined by HTML and CSS. +Library for working with the color formats defined by HTML and CSS License ------- From 46e0e61fe3f3a4c94f912393a8343f7349c04a30 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 13 Feb 2024 12:44:48 -0800 Subject: [PATCH 196/278] build/pkgs/cvxpy: Reformat title in SPKG.rst --- build/pkgs/cvxpy/SPKG.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build/pkgs/cvxpy/SPKG.rst b/build/pkgs/cvxpy/SPKG.rst index 55998a0d419..021f33f8540 100644 --- a/build/pkgs/cvxpy/SPKG.rst +++ b/build/pkgs/cvxpy/SPKG.rst @@ -1,10 +1,10 @@ -cvxpy: A domain-specific language for modeling convex optimization problems in Python. -====================================================================================== +cvxpy: Domain-specific language for modeling convex optimization problems in Python +=================================================================================== Description ----------- -A domain-specific language for modeling convex optimization problems in Python. +Domain-specific language for modeling convex optimization problems in Python License ------- From 8a53d5880686b131363bb307d3acc1f8a54648a4 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 13 Feb 2024 12:45:39 -0800 Subject: [PATCH 197/278] build/pkgs/cylp: Reformat title in SPKG.rst --- build/pkgs/cylp/SPKG.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build/pkgs/cylp/SPKG.rst b/build/pkgs/cylp/SPKG.rst index 10b8192e39c..41891d2ecca 100644 --- a/build/pkgs/cylp/SPKG.rst +++ b/build/pkgs/cylp/SPKG.rst @@ -1,10 +1,10 @@ -cylp: A Python interface for CLP, CBC, and CGL -============================================== +cylp: Python interface for CLP, CBC, and CGL +============================================ Description ----------- -A Python interface for CLP, CBC, and CGL +Python interface for CLP, CBC, and CGL License ------- From deab597f166323d69fbc73519de4d0c636465d9d Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 13 Feb 2024 12:47:07 -0800 Subject: [PATCH 198/278] build/pkgs/mathics: Reformat title in SPKG.rst --- build/pkgs/mathics/SPKG.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build/pkgs/mathics/SPKG.rst b/build/pkgs/mathics/SPKG.rst index 09f9e87aea0..9ec090e0446 100644 --- a/build/pkgs/mathics/SPKG.rst +++ b/build/pkgs/mathics/SPKG.rst @@ -1,10 +1,10 @@ -mathics: A general-purpose computer algebra system -================================================== +mathics: General-purpose computer algebra system +================================================ Description ----------- -A general-purpose computer algebra system. +General-purpose computer algebra system License ------- From a11317823e0d2e8b17821387ee20c075593deb4d Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 13 Feb 2024 12:48:15 -0800 Subject: [PATCH 199/278] build/pkgs/mathics_scanner: Reformat title in SPKG.rst --- build/pkgs/mathics_scanner/SPKG.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build/pkgs/mathics_scanner/SPKG.rst b/build/pkgs/mathics_scanner/SPKG.rst index 873cbc97304..f5b5fb463d8 100644 --- a/build/pkgs/mathics_scanner/SPKG.rst +++ b/build/pkgs/mathics_scanner/SPKG.rst @@ -1,10 +1,10 @@ -mathics_scanner: Character Tables and Tokenizer for Mathics and the Wolfram Language. -===================================================================================== +mathics_scanner: Character Tables and Tokenizer for Mathics and the Wolfram Language +==================================================================================== Description ----------- -Character Tables and Tokenizer for Mathics and the Wolfram Language. +Character Tables and Tokenizer for Mathics and the Wolfram Language License ------- From 9165386f6753946847b817a98bd90a441ad0b936 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 13 Feb 2024 12:49:45 -0800 Subject: [PATCH 200/278] build/pkgs/pari_jupyter: Reformat title in SPKG.rst --- build/pkgs/pari_jupyter/SPKG.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build/pkgs/pari_jupyter/SPKG.rst b/build/pkgs/pari_jupyter/SPKG.rst index e4b26710359..87a9ab9cf71 100644 --- a/build/pkgs/pari_jupyter/SPKG.rst +++ b/build/pkgs/pari_jupyter/SPKG.rst @@ -1,10 +1,10 @@ -pari_jupyter: A Jupyter kernel for PARI/GP -========================================== +pari_jupyter: Jupyter kernel for PARI/GP +======================================== Description ----------- -A Jupyter kernel for PARI/GP +Jupyter kernel for PARI/GP License ------- From f8610670510ff7f4b34c3e05e5825cb87c66d04b Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 13 Feb 2024 12:50:26 -0800 Subject: [PATCH 201/278] build/pkgs/phitigra: Reformat title in SPKG.rst --- build/pkgs/phitigra/SPKG.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build/pkgs/phitigra/SPKG.rst b/build/pkgs/phitigra/SPKG.rst index 253c9fee441..1f0d522f9c3 100644 --- a/build/pkgs/phitigra/SPKG.rst +++ b/build/pkgs/phitigra/SPKG.rst @@ -1,10 +1,10 @@ -phitigra: A graph editor for SageMath/Jupyter -============================================= +phitigra: Graph editor for SageMath/Jupyter +=========================================== Description ----------- -A graph editor for SageMath/Jupyter +Graph editor for SageMath/Jupyter License ------- From 998de5f954c8653f4ed1a6fc8e1bae859ebfbb35 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 13 Feb 2024 12:53:44 -0800 Subject: [PATCH 202/278] build/pkgs/sage_flatsurf: Regenerate title in SPKG.rst --- build/pkgs/sage_flatsurf/SPKG.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build/pkgs/sage_flatsurf/SPKG.rst b/build/pkgs/sage_flatsurf/SPKG.rst index 51e815d8a90..82c4d1c0562 100644 --- a/build/pkgs/sage_flatsurf/SPKG.rst +++ b/build/pkgs/sage_flatsurf/SPKG.rst @@ -1,5 +1,5 @@ -sage_flatsurf: computation with flat surfaces -============================================= +sage_flatsurf: Flat surfaces in SageMath +======================================== Description ----------- From 4dd708c0b5070f338a8b65467d39884b73ce6d11 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 13 Feb 2024 12:54:45 -0800 Subject: [PATCH 203/278] sage --package create: Attempt to bring SPKG.rst title to a common style --- build/sage_bootstrap/creator.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/build/sage_bootstrap/creator.py b/build/sage_bootstrap/creator.py index a9a0384e129..a7455b2ba26 100644 --- a/build/sage_bootstrap/creator.py +++ b/build/sage_bootstrap/creator.py @@ -63,6 +63,21 @@ def set_description(self, description, license, upstream_contact): Write the ``SPKG.rst`` file """ with open(os.path.join(self.path, 'SPKG.rst'), 'w+') as f: + # Attempt to bring title to a common style + if description.startswith(self.package_name + ':'): + description = description[len(self.package_name + ':'):] + if description.startswith(self.package_name + ' is'): + description = description[len(self.package_name + ' is'):] + description = description.strip() + if not description.endswith('etc.'): + description = description.rstrip('.') + if description.startswith('A ') or description.startswith('a '): + description = description[2:].strip() + if description.startswith('An ') or description.startswith('an '): + description = description[3:].strip() + if description: + description = description[0].upper() + description[1:] + def heading(title, char='-'): return '{0}\n{1}\n\n'.format(title, char * len(title)) if description: From f4d499a62925318dc9de2d369ac2f15dcc2f0cae Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 13 Feb 2024 13:05:32 -0800 Subject: [PATCH 204/278] build/pkgs/appdirs/spkg-install.in: Remove (changed to wheel package) --- build/pkgs/appdirs/spkg-install.in | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 build/pkgs/appdirs/spkg-install.in diff --git a/build/pkgs/appdirs/spkg-install.in b/build/pkgs/appdirs/spkg-install.in deleted file mode 100644 index 37ac1a53437..00000000000 --- a/build/pkgs/appdirs/spkg-install.in +++ /dev/null @@ -1,2 +0,0 @@ -cd src -sdh_pip_install . From 515f41d4300c3ff6d21131eb56071ae4838bd789 Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Wed, 22 Nov 2023 18:21:41 +0900 Subject: [PATCH 205/278] Implementation of Specht modules in the tableaux basis and their simple modules. --- src/sage/combinat/all.py | 3 + src/sage/combinat/specht_module.py | 679 ++++++++++++++++--- src/sage/combinat/symmetric_group_algebra.py | 25 +- src/sage/combinat/tabloid.py | 317 +++++++++ src/sage/modules/with_basis/subquotient.py | 4 +- 5 files changed, 927 insertions(+), 101 deletions(-) create mode 100644 src/sage/combinat/tabloid.py diff --git a/src/sage/combinat/all.py b/src/sage/combinat/all.py index 79690ff7305..5bbe35c740a 100644 --- a/src/sage/combinat/all.py +++ b/src/sage/combinat/all.py @@ -171,6 +171,9 @@ lazy_import('sage.combinat.super_tableau', ["StandardSuperTableau", "SemistandardSuperTableau", "StandardSuperTableaux", "SemistandardSuperTableaux"]) +# Tabloids +lazy_import('sage.combinat.tabloid', "Tabloids") + # Words from .words.all import * diff --git a/src/sage/combinat/specht_module.py b/src/sage/combinat/specht_module.py index 62842b11a30..2ddc88a1f3f 100644 --- a/src/sage/combinat/specht_module.py +++ b/src/sage/combinat/specht_module.py @@ -18,15 +18,135 @@ # **************************************************************************** from sage.misc.cachefunc import cached_method +from sage.misc.lazy_attribute import lazy_attribute from sage.combinat.diagram import Diagram +from sage.combinat.partition import _Partitions +from sage.combinat.free_module import CombinatorialFreeModule from sage.sets.family import Family from sage.matrix.constructor import matrix from sage.rings.rational_field import QQ -from sage.modules.with_basis.subquotient import SubmoduleWithBasis +from sage.modules.with_basis.subquotient import SubmoduleWithBasis, QuotientModuleWithBasis from sage.categories.modules_with_basis import ModulesWithBasis -class SpechtModule(SubmoduleWithBasis): +class SymmetricGroupRepresentation(): + """ + Mixin class for symmetric group (algebra) representations. + """ + def __init__(self, SGA): + """ + Initialize ``self``. + + EXAMPLES:: + + sage: SM = Partition([3,1,1]).specht_module(GF(3)) + sage: TestSuite(SM).run() + """ + self._SGA = SGA + + def representation_matrix(self, elt): + r""" + Return the matrix corresponding to the left action of the symmetric + group (algebra) element ``elt`` on ``self``. + + .. SEEALSO:: + + :class:`~sage.combinat.symmetric_group_representations.SpechtRepresentation` + + EXAMPLES:: + + sage: SM = Partition([3,1,1]).specht_module(QQ) + sage: SM.representation_matrix(Permutation([2,1,3,5,4])) + [-1 0 0 0 0 0] + [ 0 0 0 -1 0 0] + [ 1 0 0 -1 1 0] + [ 0 -1 0 0 0 0] + [ 1 -1 1 0 0 0] + [ 0 -1 0 1 0 -1] + + sage: SGA = SymmetricGroupAlgebra(QQ, 5) + sage: SM = SGA.specht_module([(0,0), (0,1), (0,2), (1,0), (2,0)]) + sage: SM.representation_matrix(Permutation([2,1,3,5,4])) + [-1 0 0 1 -1 0] + [ 0 0 1 0 -1 1] + [ 0 1 0 -1 0 1] + [ 0 0 0 0 -1 0] + [ 0 0 0 -1 0 0] + [ 0 0 0 0 0 -1] + sage: SGA = SymmetricGroupAlgebra(QQ, 5) + sage: SM.representation_matrix(SGA([3,1,5,2,4])) + [ 0 -1 0 1 0 -1] + [ 0 0 0 0 0 -1] + [ 0 0 0 -1 0 0] + [ 0 0 -1 0 1 -1] + [ 1 0 0 -1 1 0] + [ 0 0 0 0 1 0] + """ + return matrix(self.base_ring(), [(elt * b).to_vector() for b in self.basis()]) + + @cached_method + def frobenius_image(self): + r""" + Return the Frobenius image of ``self``. + + The Frobenius map is defined as the map to symmetric functions + + .. MATH:: + + F(\chi) = \frac{1}{n!} \sum_{w \in S_n} \chi(w) p_{\rho(w)}, + + where `\chi` is the character of the `S_n`-module ``self``, + `p_{\lambda}` is the powersum symmetric function basis element + indexed by `\lambda`, and `\rho(w)` is partition of the cycle type + of `w`. Specifically, this map takes irreducible representations + indexed by `\lambda` to the Schur function `s_{\lambda}`. + + EXAMPLES:: + + sage: s = SymmetricFunctions(QQ).s() + sage: SM = Partition([2,2,1]).specht_module(QQ) + sage: s(SM.frobenius_image()) + s[2, 2, 1] + sage: SM = Partition([4,1]).specht_module(CyclotomicField(5)) + sage: s(SM.frobenius_image()) + s[4, 1] + + We verify the regular representation:: + + sage: from sage.combinat.diagram import Diagram + sage: D = Diagram([(0,0), (1,1), (2,2), (3,3), (4,4)]) + sage: F = s(D.specht_module(QQ).frobenius_image()); F + s[1, 1, 1, 1, 1] + 4*s[2, 1, 1, 1] + 5*s[2, 2, 1] + + 6*s[3, 1, 1] + 5*s[3, 2] + 4*s[4, 1] + s[5] + sage: F == sum(StandardTableaux(la).cardinality() * s[la] + ....: for la in Partitions(5)) + True + sage: all(s[la] == s(la.specht_module(QQ).frobenius_image()) + ....: for n in range(1, 5) for la in Partitions(n)) + True + + sage: D = Diagram([(0,0), (1,1), (1,2), (2,3), (2,4)]) + sage: SM = D.specht_module(QQ) + sage: s(SM.frobenius_image()) + s[2, 2, 1] + s[3, 1, 1] + 2*s[3, 2] + 2*s[4, 1] + s[5] + + An example using the tabloid module:: + + sage: SGA = SymmetricGroupAlgebra(QQ, 5) + sage: TM = SGA.tabloid_module([2, 2, 1]) + sage: s(TM.frobenius_image()) + s[2, 2, 1] + s[3, 1, 1] + 2*s[3, 2] + 2*s[4, 1] + s[5] + """ + from sage.combinat.sf.sf import SymmetricFunctions + BR = self.base_ring() + p = SymmetricFunctions(BR).p() + G = self._SGA.group() + CCR = [(elt, elt.cycle_type()) for elt in G.conjugacy_classes_representatives()] + return p.sum(self.representation_matrix(elt).trace() / la.centralizer_size() * p[la] + for elt, la in CCR) + + +class SpechtModule(SubmoduleWithBasis, SymmetricGroupRepresentation): r""" A Specht module. @@ -84,13 +204,13 @@ class SpechtModule(SubmoduleWithBasis): sage: S5 = SGA.group() sage: v = SM.an_element(); v - 2*B[0] + 2*B[1] + 3*B[2] + 2*S[0] + 2*S[1] + 3*S[2] sage: S5([2,1,5,3,4]) * v - 3*B[0] + 2*B[1] + 2*B[2] + 3*S[0] + 2*S[1] + 2*S[2] sage: x = SGA.an_element(); x [1, 2, 3, 4, 5] + 2*[1, 2, 3, 5, 4] + 3*[1, 2, 4, 3, 5] + [5, 1, 2, 3, 4] sage: x * v - 15*B[0] + 14*B[1] + 16*B[2] - 7*B[5] + 2*B[6] + 2*B[7] + 15*S[0] + 14*S[1] + 16*S[2] - 7*S[5] + 2*S[6] + 2*S[7] .. SEEALSO:: @@ -120,6 +240,9 @@ def __classcall_private__(cls, SGA, D): ... ValueError: the domain size (=3) does not match the number of boxes (=2) of the diagram """ + if D in _Partitions: + D = _Partitions(D) + return TabloidModule(SGA, D).specht_module() D = _to_diagram(D) D = Diagram(D) n = len(D) @@ -138,6 +261,7 @@ def __init__(self, SGA, D): sage: SM = SGA.specht_module([(0,0), (1,1), (1,2), (2,1)]) sage: TestSuite(SM).run() """ + SymmetricGroupRepresentation.__init__(self, SGA) self._diagram = D Mod = ModulesWithBasis(SGA.category().base_ring()) span_set = specht_module_spanning_set(D, SGA) @@ -145,7 +269,8 @@ def __init__(self, SGA, D): basis = SGA.echelon_form(span_set, False, order=support_order) basis = Family(basis) SubmoduleWithBasis.__init__(self, basis, support_order, ambient=SGA, - unitriangular=False, category=Mod.Subobjects()) + unitriangular=False, category=Mod.Subobjects(), + prefix='S') def _repr_(self): r""" @@ -156,6 +281,9 @@ def _repr_(self): sage: SGA = SymmetricGroupAlgebra(QQ, 4) sage: SGA.specht_module([(0,0), (1,1), (1,2), (2,1)]) Specht module of [(0, 0), (1, 1), (1, 2), (2, 1)] over Rational Field + + sage: SGA.specht_module([3, 1]) + Specht module of [3, 1] over Rational Field """ return f"Specht module of {self._diagram} over {self.base_ring()}" @@ -168,13 +296,22 @@ def _latex_(self): sage: SGA = SymmetricGroupAlgebra(QQ, 4) sage: SM = SGA.specht_module([(0,0), (1,1), (1,2), (2,1)]) sage: latex(SM) - S^{{\def\lr#1{\multicolumn{1}{|@{\hspace{.6ex}}c@{\hspace{.6ex}}|}{\raisebox{-.3ex}{$#1$}}} - \raisebox{-.6ex}{$\begin{array}[b]{*{3}{p{0.6ex}}}\cline{1-1} - \lr{\phantom{x}}&&\\\cline{1-1}\cline{2-2}\cline{3-3} - &\lr{\phantom{x}}&\lr{\phantom{x}}\\\cline{2-2}\cline{3-3}\cline{2-2} - &\lr{\phantom{x}}&\\\cline{2-2} - \end{array}$} - }} + S^{{\def\lr#1{\multicolumn{1}{|@{\hspace{.6ex}}c@{\hspace{.6ex}}|}{\raisebox{-.3ex}{$#1$}}} + \raisebox{-.6ex}{$\begin{array}[b]{*{3}{p{0.6ex}}}\cline{1-1} + \lr{\phantom{x}}&&\\\cline{1-1}\cline{2-2}\cline{3-3} + &\lr{\phantom{x}}&\lr{\phantom{x}}\\\cline{2-2}\cline{3-3}\cline{2-2} + &\lr{\phantom{x}}&\\\cline{2-2} + \end{array}$} + }} + + sage: SM = SGA.specht_module([3,1]) + sage: latex(SM) + S^{{\def\lr#1{\multicolumn{1}{|@{\hspace{.6ex}}c@{\hspace{.6ex}}|}{\raisebox{-.3ex}{$#1$}}} + \raisebox{-.6ex}{$\begin{array}[b]{*{3}c}\cline{1-3} + \lr{\phantom{x}}&\lr{\phantom{x}}&\lr{\phantom{x}}\\\cline{1-3} + \lr{\phantom{x}}\\\cline{1-1} + \end{array}$} + }} """ from sage.misc.latex import latex return f"S^{{{latex(self._diagram)}}}" @@ -192,6 +329,12 @@ def _ascii_art_(self): . O O . O . S + + sage: SM = SGA.specht_module([3,1]) + sage: ascii_art(SM) + *** + * + S """ from sage.typeset.ascii_art import ascii_art return ascii_art("S", baseline=0) + ascii_art(self._diagram, baseline=-1) @@ -213,99 +356,153 @@ def _unicode_art_(self): │ │X│ │ └─┴─┴─┘ S + + sage: SM = SGA.specht_module([3,1]) + sage: unicode_art(SM) + ┌┬┬┐ + ├┼┴┘ + └┘ + S """ from sage.typeset.unicode_art import unicode_art return unicode_art("S", baseline=0) + unicode_art(self._diagram, baseline=-1) - def representation_matrix(self, elt): - r""" - Return the matrix corresponding to the left action of the symmetric - group (algebra) element ``elt`` on ``self``. + class Element(SubmoduleWithBasis.Element): + def _acted_upon_(self, x, self_on_left=False): + """ + Return the action of ``x`` on ``self``. - .. SEEALSO:: + INPUT: - :class:`~sage.combinat.symmetric_group_representations.SpechtRepresentation` + - ``x`` -- an element of the base ring or can be converted into + the defining symmetric group algebra + - ``self_on_left`` -- boolean (default: ``False``); which side + ``self`` is on for the action + + EXAMPLES:: + + sage: SGA = SymmetricGroupAlgebra(QQ, 5) + sage: from sage.combinat.diagram import Diagram + sage: D = Diagram([(0,0), (1,1), (2,2), (3,3), (4,4)]) + sage: SM = SGA.specht_module(D) + sage: SGA.an_element() * SM.an_element() + 15*S[0] + 6*S[1] + 9*S[2] + 6*S[3] + 6*S[4] + 2*S[72] + 2*S[96] + 3*S[97] + + sage: SGA = SymmetricGroupAlgebra(QQ, 4) + sage: SM = SGA.specht_module([3,1]) + sage: SGA.an_element() * SM.an_element() + 9*S[[1, 2, 3], [4]] + 17*S[[1, 2, 4], [3]] + 14*S[[1, 3, 4], [2]] + sage: 4 * SM.an_element() + 12*S[[1, 2, 3], [4]] + 8*S[[1, 2, 4], [3]] + 8*S[[1, 3, 4], [2]] + """ + # Check for a scalar first + ret = super()._acted_upon_(x, self_on_left) + if ret is not None: + return ret + # Check if it is in the symmetric group algebra + P = self.parent() + if x in P._SGA or x in P._SGA.group(): + if self_on_left: # it is only a left module + return None + else: + return P.retract(P._SGA(x) * self.lift()) + return None + +class TabloidModule(CombinatorialFreeModule, SymmetricGroupRepresentation): + r""" + The vector space of all tabloids with the natural symmetric group action. + """ + @staticmethod + def __classcall_private__(cls, SGA, shape): + r""" + Normalize input to ensure a unique representation. EXAMPLES:: - sage: SM = Partition([3,1,1]).specht_module(QQ) - sage: SM.representation_matrix(Permutation([2,1,3,5,4])) - [-1 0 0 1 -1 0] - [ 0 0 1 0 -1 1] - [ 0 1 0 -1 0 1] - [ 0 0 0 0 -1 0] - [ 0 0 0 -1 0 0] - [ 0 0 0 0 0 -1] + sage: from sage.combinat.specht_module import TabloidModule sage: SGA = SymmetricGroupAlgebra(QQ, 5) - sage: SM.representation_matrix(SGA([3,1,5,2,4])) - [ 0 -1 0 1 0 -1] - [ 0 0 0 0 0 -1] - [ 0 0 0 -1 0 0] - [ 0 0 -1 0 1 -1] - [ 1 0 0 -1 1 0] - [ 0 0 0 0 1 0] + sage: TM1 = TabloidModule(SGA, [2, 2, 1]) + sage: TM2 = TabloidModule(SGA, Partition([2, 2, 1])) + sage: TM1 is TM2 + True + + sage: TabloidModule(SGA, [3, 2, 1]) + Traceback (most recent call last): + ... + ValueError: the domain size (=5) does not match the number of boxes (=6) of the diagram """ - SGA = self._ambient - return matrix(self.base_ring(), [self.retract(SGA(elt) * b.lift()).to_vector() - for b in self.basis()]) + shape = _Partitions(shape) + if SGA.group().rank() != sum(shape) - 1: + rk = SGA.group().rank() + 1 + n = sum(shape) + raise ValueError(f"the domain size (={rk}) does not match the number of boxes (={n}) of the diagram") + return super().__classcall__(cls, SGA, shape) - @cached_method - def frobenius_image(self): + def __init__(self, SGA, shape): r""" - Return the Frobenius image of ``self``. - - The Frobenius map is defined as the map to symmetric functions + Initialize ``self``. - .. MATH:: + EXAMPLES:: - F(\chi) = \frac{1}{n!} \sum_{w \in S_n} \chi(w) p_{\rho(w)}, + sage: SGA = SymmetricGroupAlgebra(QQ, 5) + sage: TM = SGA.tabloid_module([2,2,1]) + sage: TestSuite(TM).run() + """ + SymmetricGroupRepresentation.__init__(self, SGA) + from sage.combinat.tabloid import Tabloids + self._shape = shape + cat = ModulesWithBasis(SGA.base_ring()).FiniteDimensional() + return CombinatorialFreeModule.__init__(self, SGA.base_ring(), Tabloids(shape), + category=cat, prefix='T', bracket='') - where `\chi` is the character of the `S_n`-module ``self``, - `p_{\lambda}` is the powersum symmetric function basis element - indexed by `\lambda`, and `\rho(w)` is partition of the cycle type - of `w`. Specifically, this map takes irreducible representations - indexed by `\lambda` to the Schur function `s_{\lambda}`. + def _repr_(self): + r""" + Return a string representation of ``self``. EXAMPLES:: - sage: s = SymmetricFunctions(QQ).s() - sage: SM = Partition([2,2,1]).specht_module(QQ) - sage: s(SM.frobenius_image()) - s[2, 2, 1] - sage: SM = Partition([4,1]).specht_module(CyclotomicField(5)) - sage: s(SM.frobenius_image()) - s[4, 1] + sage: SGA = SymmetricGroupAlgebra(QQ, 5) + sage: SGA.tabloid_module([2,2,1]) + Tabloid module of [2, 2, 1] over Rational Field + """ + return f"Tabloid module of {self._shape} over {self.base_ring()}" - We verify the regular representation:: + def specht_module(self): + r""" + Return the Specht submodule of ``self``. - sage: from sage.combinat.diagram import Diagram - sage: D = Diagram([(0,0), (1,1), (2,2), (3,3), (4,4)]) - sage: F = s(D.specht_module(QQ).frobenius_image()); F - s[1, 1, 1, 1, 1] + 4*s[2, 1, 1, 1] + 5*s[2, 2, 1] - + 6*s[3, 1, 1] + 5*s[3, 2] + 4*s[4, 1] + s[5] - sage: F == sum(StandardTableaux(la).cardinality() * s[la] - ....: for la in Partitions(5)) - True - sage: all(s[la] == s(la.specht_module(QQ).frobenius_image()) - ....: for n in range(1, 5) for la in Partitions(n)) - True + EXAMPLES:: - sage: D = Diagram([(0,0), (1,1), (1,2), (2,3), (2,4)]) - sage: SM = D.specht_module(QQ) - sage: s(SM.frobenius_image()) - s[2, 2, 1] + s[3, 1, 1] + 2*s[3, 2] + 2*s[4, 1] + s[5] + sage: SGA = SymmetricGroupAlgebra(QQ, 5) + sage: TM = SGA.tabloid_module([2,2,1]) + sage: TM.specht_module() is SGA.specht_module([2,2,1]) + True """ - from sage.combinat.sf.sf import SymmetricFunctions - BR = self._ambient.base_ring() - p = SymmetricFunctions(BR).p() - G = self._ambient.group() - CCR = [(elt, elt.cycle_type()) for elt in G.conjugacy_classes_representatives()] - return p.sum(self.representation_matrix(elt).trace() / la.centralizer_size() * p[la] - for elt, la in CCR) + return SpechtModuleTableauxBasis(self) - class Element(SubmoduleWithBasis.Element): - def _acted_upon_(self, x, self_on_left=False): - """ + def bilinear_form(self, u, v): + r""" + Return the natural bilinear form of ``self`` applied to ``u`` and ``v``. + + The natural bilinear form is given by defining the tabloid basis + to be orthonormal. + + sage: SGA = SymmetricGroupAlgebra(QQ, 5) + sage: TM = SGA.tabloid_module([2,2,1]) + sage: u = TM.an_element(); u + 2*T[{1, 2}, {3, 4}, {5}] + 2*T[{1, 2}, {3, 5}, {4}] + 3*T[{1, 2}, {4, 5}, {3}] + sage: v = sum(TM.basis()) + sage: TM.bilinear_form(u, v) + 7 + """ + if len(v) < len(u): + u, v = v, u + R = self.base_ring() + return R.sum(c * v[T] for T,c in u) + + class Element(CombinatorialFreeModule.Element): + def _acted_upon_(self, x, self_on_left): + r""" Return the action of ``x`` on ``self``. INPUT: @@ -317,25 +514,318 @@ def _acted_upon_(self, x, self_on_left=False): EXAMPLES:: - sage: SGA = SymmetricGroupAlgebra(QQ, 4) - sage: SM = SGA.specht_module([3,1]) + sage: SGA = SymmetricGroupAlgebra(QQ, 5) + sage: SM = SGA.tabloid_module([2,2,1]) sage: SGA.an_element() * SM.an_element() - 14*B[0] + 18*B[1] + 8*B[2] + 2*T[{1, 5}, {2, 3}, {4}] + 2*T[{1, 5}, {2, 4}, {3}] + 3*T[{1, 5}, {3, 4}, {2}] + + 12*T[{1, 2}, {3, 4}, {5}] + 15*T[{1, 2}, {3, 5}, {4}] + 15*T[{1, 2}, {4, 5}, {3}] sage: 4 * SM.an_element() - 8*B[0] + 8*B[1] + 12*B[2] + 8*T[{1, 2}, {3, 4}, {5}] + 8*T[{1, 2}, {3, 5}, {4}] + 12*T[{1, 2}, {4, 5}, {3}] """ - # Check for a scalar first + # first check for the base action ret = super()._acted_upon_(x, self_on_left) if ret is not None: return ret - # Check if it is in the symmetric group algebra + + if self_on_left: + return None P = self.parent() - if x in P._ambient or x in P._ambient.group(): - if self_on_left: # it is only a left module - return None - else: - return P.retract(P._ambient(x) * self.lift()) - return None + if x in P._SGA: + return P.sum(c * (perm * self) for perm, c in x.monomial_coefficients().items()) + if x in P._SGA.indices(): + return P.element_class(P, {T.symmetric_group_action(x): c for T, c in self._monomial_coefficients.items()}) + + +class SpechtModuleTableauxBasis(SpechtModule): + r""" + A Specht module of a partition in the classical standard + tableau basis. + + This is constructed as a `S_n`-submodule of the :class:`TabloidModule` + (also referred to as the standard module). + """ + def __init__(self, ambient): + r""" + Initialize ``self``. + + EXAMPLES:: + + sage: SGA = SymmetricGroupAlgebra(QQ, 5) + sage: SM = SGA.specht_module([2,2,1]) + sage: TestSuite(SM).run() + """ + self._diagram = ambient._shape + SymmetricGroupRepresentation.__init__(self, ambient._SGA) + + from sage.combinat.symmetric_group_algebra import e + ambient_basis = ambient.basis() + tabloids = ambient_basis.keys() + support_order = list(tabloids) + + G = self._SGA.basis().keys() + BR = self._SGA.base_ring() + + def elt(T): + tab = tabloids.from_tableau(T) + return ambient.sum_of_terms((tab.symmetric_group_action(sigma), sigma.sign()) + for sigma in T.column_stabilizer()) + + basis = Family({T: elt(T) + for T in self._diagram.standard_tableaux()}) + cat = ambient.category().Subobjects() + SubmoduleWithBasis.__init__(self, basis, support_order, ambient=ambient, + unitriangular=False, category=cat, + prefix='S', bracket='') + + @lazy_attribute + def lift(self): + r""" + The lift (embedding) map from ``self`` to the ambient space. + + EXAMPLES:: + + sage: SGA = SymmetricGroupAlgebra(QQ, 5) + sage: SM = SGA.specht_module([3, 1, 1]) + sage: SM.lift + Generic morphism: + From: Specht module of [3, 1, 1] over Rational Field + To: Tabloid module of [3, 1, 1] over Rational Field + """ + return self.module_morphism(self.lift_on_basis, codomain=self.ambient()) + + @lazy_attribute + def retract(self): + r""" + The retract map from the ambient space. + + EXAMPLES:: + + sage: SGA = SymmetricGroupAlgebra(QQ, 5) + sage: X = SGA.tabloid_module([2,2,1]) + sage: Y = X.specht_module() + sage: Y.retract + Generic morphism: + From: Tabloid module of [2, 2, 1] over Rational Field + To: Specht module of [2, 2, 1] over Rational Field + sage: all(Y.retract(u.lift()) == u for u in Y.basis()) + True + + sage: Y.retract(X.zero()) + 0 + """ + B = self.basis() + COB = matrix([b.lift().to_vector() for b in B]).T + P, L, U = COB.LU() + # Since U is upper triangular, the nonzero entriesm must be in the + # upper square portiion of the matrix + n = len(B) + + Uinv = U.matrix_from_rows(range(n)).inverse() + # This is a slight abuse as the codomain should be a module with a different + # S_n action, but we only use it internally, so there isn't any problems + PLinv = (P*L).inverse() + + def retraction(elt): + vec = PLinv * elt.to_vector(order=self._support_order) + if not vec: + return self.zero() + # vec is now in the image of self under U, which is + if max(vec.support()) >= n: + raise ValueError(f"{elt} is not in the image") + return self._from_dict(dict(zip(B.keys(), Uinv * vec[:n]))) + + return self._ambient.module_morphism(function=retraction, codomain=self) + + def bilinear_form(self, u, v): + r""" + Return the natural bilinear form of ``self`` applied to ``u`` and ``v``. + + The natural bilinear form is given by the pullback of the natural + bilinear form on the tabloid module (where the tabloid basis is an + orthonormal basis). + + EXAMPLES:: + + sage: SGA = SymmetricGroupAlgebra(QQ, 5) + sage: SM = SGA.specht_module([2,2,1]) + sage: u = SM.an_element(); u + 3*S[[1, 2], [3, 5], [4]] + 2*S[[1, 3], [2, 5], [4]] + 2*S[[1, 4], [2, 5], [3]] + sage: v = sum(SM.basis()) + sage: SM.bilinear_form(u, v) + 140 + """ + TM = self._ambient + return TM.bilinear_form(u.lift(), v.lift()) + + @cached_method + def gram_matrix(self): + r""" + Return the Gram matrix of the natural bilinear form of ``self``. + + EXAMPLES:: + + sage: SGA = SymmetricGroupAlgebra(QQ, 5) + sage: SM = SGA.specht_module([2,2,1]) + sage: M = SM.gram_matrix(); M + [12 4 -4 -4 4] + [ 4 12 4 4 4] + [-4 4 12 4 4] + [-4 4 4 12 4] + [ 4 4 4 4 12] + sage: M.det() != 0 + True + """ + B = self.basis() + M = matrix([[self.bilinear_form(b, bp) for bp in B] for b in B]) + M.set_immutable() + return M + + def maximal_submodule(self): + """ + Return the maximal submodule of ``self``. + + EXAMPLES:: + + sage: SGA = SymmetricGroupAlgebra(GF(3), 5) + sage: SM = SGA.specht_module([3,2]) + sage: U = SM.maximal_submodule() + sage: U.dimension() + 4 + """ + return MaximalSpechtSubmodule(self) + + def simple_module(self): + r""" + Return the simple (or irreducible) `S_n`-submodule of ``self``. + + EXAMPLES:: + + sage: SGA = SymmetricGroupAlgebra(GF(3), 5) + sage: SM = SGA.specht_module([3,2]) + sage: L = SM.simple_module() + sage: L.dimension() + 1 + """ + return SimpleModule(self) + + +class MaximalSpechtSubmodule(SubmoduleWithBasis, SymmetricGroupRepresentation): + r""" + The maximal submodule `U^{\lambda}` of the Specht module `S^{\lambda}`. + + ALGORITHM: + + We construct `U^{\lambda}` as the intersection `S \cap S^{\perp}`, + where `S^{\perp}` is the orthogonal complement of the Specht module `S` + inside of the tabloid module `T` (with respect to the natural + bilinear form on `T`). + + EXAMPLES:: + + sage: SGA = SymmetricGroupAlgebra(GF(3), 5) + sage: SM = SGA.specht_module([3,2]) + sage: U = SM.maximal_submodule() + sage: u = U.an_element(); u + 2*U[0] + 2*U[1] + sage: [p * u for p in list(SGA.basis())[:4]] + [2*U[0] + 2*U[1], 2*U[2] + 2*U[3], 2*U[0] + 2*U[1], U[0] + 2*U[2]] + sage: sum(SGA.basis()) * u + 0 + """ + def __init__(self, specht_module): + r""" + Initialize ``self``. + + EXAMPLES:: + + sage: SGA = SymmetricGroupAlgebra(GF(3), 5) + sage: SM = SGA.specht_module([3,2]) + sage: U = SM.maximal_submodule() + sage: TestSuite(U).run() + """ + SymmetricGroupRepresentation.__init__(self, specht_module._SGA) + + if specht_module.base_ring().characteristic() == 0: + basis = [] + else: + TM = specht_module._ambient + TV = TM._dense_free_module() + SV = TV.submodule(specht_module.lift.matrix().columns()) + basis = (SV & SV.complement()).basis() + basis = [specht_module.retract(TM.from_vector(b)) for b in basis] + basis = Family(specht_module.echelon_form(basis)) + + unitriangular = all(b.leading_support() == 1 for b in basis) + support_order = list(specht_module.basis().keys()) + cat = specht_module.category().Subobjects() + SubmoduleWithBasis.__init__(self, basis, support_order, ambient=specht_module, + unitriangular=unitriangular, category=cat, + prefix='U') + + def _repr_(self): + r""" + Return a string representation of ``self``. + + EXAMPLES:: + + sage: SGA = SymmetricGroupAlgebra(GF(3), 5) + sage: SM = SGA.specht_module([3,2]) + sage: SM.maximal_submodule() + Maximal submodule of Specht module of [3, 2] over Finite Field of size 3 + """ + return f"Maximal submodule of {self._ambient}" + + Element = SpechtModule.Element + + +class SimpleModule(QuotientModuleWithBasis, SymmetricGroupRepresentation): + r""" + The simgle `S_n`-module associated with a partition `\lambda`. + + The simple module `L^{\lambda}` is the quotient of the Specht module + `S^{\lambda}` by its :class:`maximal submodule ` + `U^{\lambda}`. + + EXAMPLES:: + + sage: SGA = SymmetricGroupAlgebra(GF(3), 5) + sage: SM = SGA.specht_module([3,1,1]) + sage: L = SM.simple_module() + sage: v = L.an_element(); v + 2*L[[[1, 3, 5], [2], [4]]] + 2*L[[[1, 4, 5], [2], [3]]] + sage: SGA.an_element() * v + 2*L[[[1, 2, 4], [3], [5]]] + 2*L[[[1, 3, 5], [2], [4]]] + """ + def __init__(self, specht_module): + """ + Initialize ``self``. + + EXAMPLES:: + + sage: SGA = SymmetricGroupAlgebra(GF(3), 5) + sage: SM = SGA.specht_module([3,1,1]) + sage: L = SM.simple_module() + sage: TestSuite(L).run() + """ + self._diagram = specht_module._diagram + SymmetricGroupRepresentation.__init__(self, specht_module._SGA) + cat = specht_module.category() + QuotientModuleWithBasis.__init__(self, specht_module.maximal_submodule(), cat, prefix='L') + + def _repr_(self): + """ + Return a string representation of ``self``. + + EXAMPLES:: + + sage: SGA = SymmetricGroupAlgebra(GF(3), 5) + sage: SM = SGA.specht_module([3,1,1]) + sage: SM.simple_module() + Simple module of [3, 1, 1] over Finite Field of size 3 + """ + return f"Simple module of {self._diagram} over {self.base_ring()}" + + Element = SpechtModule.Element def _to_diagram(D): @@ -358,7 +848,6 @@ def _to_diagram(D): """ from sage.combinat.integer_vector import IntegerVectors from sage.combinat.skew_partition import SkewPartitions - from sage.combinat.partition import _Partitions if isinstance(D, Diagram): return D if D in _Partitions: diff --git a/src/sage/combinat/symmetric_group_algebra.py b/src/sage/combinat/symmetric_group_algebra.py index 4d96d026fd5..b741914f506 100644 --- a/src/sage/combinat/symmetric_group_algebra.py +++ b/src/sage/combinat/symmetric_group_algebra.py @@ -1572,7 +1572,7 @@ def specht_module(self, D): sage: SGA = SymmetricGroupAlgebra(QQ, 5) sage: SM = SGA.specht_module(Partition([3,1,1])) sage: SM - Specht module of [(0, 0), (0, 1), (0, 2), (1, 0), (2, 0)] over Rational Field + Specht module of [3, 1, 1] over Rational Field sage: s = SymmetricFunctions(QQ).s() sage: s(SM.frobenius_image()) s[3, 1, 1] @@ -1586,6 +1586,23 @@ def specht_module(self, D): from sage.combinat.specht_module import SpechtModule return SpechtModule(self, D) + def tabloid_module(self, D): + """ + Return the module of tabloids with the natural action of ``self``. + + EXAMPLES:: + + sage: SGA = SymmetricGroupAlgebra(QQ, 5) + sage: TM = SGA.tabloid_module(Partition([3,1,1])) + sage: TM + Tabloid module of [3, 1, 1] over Rational Field + sage: s = SymmetricFunctions(QQ).s() + sage: s(TM.frobenius_image()) + s[3, 1, 1] + s[3, 2] + 2*s[4, 1] + s[5] + """ + from sage.combinat.specht_module import TabloidModule + return TabloidModule(self, D) + def specht_module_dimension(self, D): r""" Return the dimension of the Specht module of ``self`` indexed by ``D``. @@ -2591,10 +2608,10 @@ def e(tableau, star=0): one = QQ.one() P = Permutation - rd = dict((P(h), one) for h in rs) + rd = {P(h): one for h in rs} sym = QSn._from_dict(rd) - cd = dict((P(v), v.sign() * one) for v in cs) + cd = {P(v): QQ(v.sign()) for v in cs} antisym = QSn._from_dict(cd) res = QSn.right_action_product(antisym, sym) @@ -2604,7 +2621,7 @@ def e(tableau, star=0): # being [1] rather than [] (which seems to have its origins in # permutation group code). # TODO: Fix this. - if len(tableau) == 0: + if not tableau: res = QSn.one() e_cache[t] = res diff --git a/src/sage/combinat/tabloid.py b/src/sage/combinat/tabloid.py new file mode 100644 index 00000000000..ed41c8e6db9 --- /dev/null +++ b/src/sage/combinat/tabloid.py @@ -0,0 +1,317 @@ +r""" +Tabloids + +AUTHORS: + +- Sacha Goldman, Travis Scrimshaw (2023): initial version +""" + +from sage.combinat.partition import Partition +from sage.groups.perm_gps.permgroup_named import SymmetricGroup +from sage.arith.misc import multinomial +from sage.categories.finite_enumerated_sets import FiniteEnumeratedSets +from sage.structure.list_clone import ClonableArray +from sage.structure.unique_representation import UniqueRepresentation +from sage.structure.parent import Parent +from sage.typeset.ascii_art import ascii_art +from sage.rings.integer_ring import ZZ + +class Tabloid(ClonableArray): + r""" + A tabloid. + + A tabloid is an ordered set partition of `\{1, 2, \ldots, n\}` such that + the sizes of the sets are weakly decreasing (that is, they form an + integer partition). + """ + def __init__(self, parent, tableaux, check=True): + """ + Initialize ``self``. + + EXAMPLES:: + + sage: T = Tabloids([5, 3, 1]) + sage: A = T([[1,2,3,9,7], [4,5,8], [6]]) + sage: TestSuite(A).run() + """ + super().__init__(parent, map(frozenset, tableaux), check=check) + + def check(self): + """ + Check that ``self`` is a valid tabloid. + + EXAMPLES:: + + sage: T = Tabloids([3, 2, 1]) + sage: A = T([[1,2,3], [4,5], [6]]) + sage: A.check() + sage: T([[1,2,3,4], [5], [6]]) # indirect doctest + Traceback (most recent call last): + ... + ValueError: incorrect shape + sage: T([[1,2,3], [4,'a'], [6]]) # indirect doctest + Traceback (most recent call last): + ... + ValueError: invalid entry in the row frozenset({...}) + sage: T([[1,2,3], [4,3], [6]]) # indirect doctest + Traceback (most recent call last): + ... + ValueError: invalid entry in the row frozenset({3, 4}) + """ + la = self.parent()._shape + if [len(row) for row in self] != la: + raise ValueError("incorrect shape") + X = set(range(1, sum(la)+1)) + for row in self: + ell = len(X) + X -= row + if len(X) != ell - len(row): + raise ValueError(f"invalid entry in the row {row}") + assert not X + + def _repr_(self): + """ + Return a string representation of ``self``. + + EXAMPLES:: + + sage: T = Tabloids([5, 3, 1]) + sage: T([[1,2,3,9,7], [4,5,8], [6]]) + [{1, 2, 3, 7, 9}, {4, 5, 8}, {6}] + """ + ret = "[" + ret += ", ".join("{" + ", ".join(str(val) for val in sorted(row)) + "}" for row in self) + return ret + "]" + + def _ascii_art_(self): + """ + Return a string representation of ``self``. + + EXAMPLES:: + + sage: T = Tabloids([5, 3, 1]) + sage: A = T([[1,2,3,9,7], [4,5,8], [6]]) + sage: ascii_art(A) + {1, 2, 3, 7, 9} + {4, 5, 8} + {6} + """ + ret = "\n".join("{" + ", ".join(str(val) for val in sorted(row)) + "}" for row in self) + return ascii_art(ret) + + def _latex_(self): + r""" + Return a LaTeX representation of ``self`` as a Young diagram. + + EXAMPLES:: + + sage: T = Tabloids([5, 3, 1]) + sage: A = T([[1,2,3,9,7], [4,5,8], [6]]) + sage: latex(A) + {\def\lr#1{\multicolumn{1}{@{\hspace{.6ex}}c@{\hspace{.6ex}}}{\raisebox{-.3ex}{$#1$}}} + \raisebox{-.6ex}{$\begin{array}[b]{*{5}c}\cline{1-5} + \lr{1}&\lr{2}&\lr{3}&\lr{7}&\lr{9}\\\cline{1-5} + \lr{4}&\lr{5}&\lr{8}\\\cline{1-3} + \lr{6}\\\cline{1-1} + \end{array}$} + } + """ + if not self: + return "{\\emptyset}" + from sage.combinat.output import tex_from_array + A = list(map(sorted, self)) + ret = str(tex_from_array(A)) + return ret.replace("|", "") + + def symmetric_group_action(self, permutation): + """ + Return the left action of ``permutation`` on ``self``. + + EXAMPLES:: + + sage: T = Tabloids([3, 2, 1]) + sage: A = T([[1,2,6], [3,5], [4]]); ascii_art(A) + {1, 2, 6} + {3, 5} + {4} + sage: sigma = Permutation([1,6,2,3,5,4]) + sage: ascii_art(A.symmetric_group_action(sigma)) + {1, 4, 6} + {2, 5} + {3} + sage: sigma = SymmetricGroup(6)([(1,2),(3,4,5)]) + sage: ascii_art(A.symmetric_group_action(sigma)) + {1, 2, 6} + {3, 4} + {5} + """ + P = self.parent() + S = SymmetricGroup(sum(P._shape)) + permutation = S(permutation) + return P.element_class(P, [[permutation(val) for val in row] for row in self]) + + def _acted_upon_(self, sigma, self_on_left=True): + """ + Return the action of ``sigma`` on ``self``. + + EXAMPLES:: + + sage: T = Tabloids([2, 2, 1]) + sage: A = T([[1,2], [3,5], [4]]); ascii_art(A) + {1, 2} + {3, 5} + {4} + sage: ascii_art(Permutation([1,2,3,5,4]) * A) + {1, 2} + {3, 4} + {5} + sage: sigma = SymmetricGroup(5)([(1,2),(3,4,5)]) + sage: ascii_art(sigma * A) + {1, 2} + {3, 4} + {5} + """ + P = self.parent() + S = SymmetricGroup(sum(P._shape)) + if sigma in S: + return self.symmetric_group_action(sigma) + return super()._acted_upon_(sigma, self_on_left) + + +class Tabloids(UniqueRepresentation, Parent): + """ + Tabloids of a fixed shape. + """ + @staticmethod + def __classcall_private__(cls, partition): + """ + Normalize input to ensure a unique representation. + + EXAMPLES:: + + sage: T1 = Tabloids([2, 1]) + sage: T2 = Tabloids((2, 1)) + sage: T3 = Tabloids(Partition([2, 1])) + sage: T1 is T2 and T2 is T3 + True + """ + partition = Partition(partition) + return super().__classcall__(cls, partition) + + def __init__(self, partition): + """ + Initialize ``self``. + + EXAMPLES:: + + sage: T = Tabloids([2, 1]) + sage: TestSuite(T).run() + + sage: T = Tabloids([]) + sage: TestSuite(T).run() + + sage: T = Tabloids([1]) + sage: TestSuite(T).run() + + sage: T = Tabloids([10]) + sage: TestSuite(T).run() + + sage: T = Tabloids([1, 1, 1, 1]) + sage: TestSuite(T).run() + """ + self._shape = partition + Parent.__init__(self, category=FiniteEnumeratedSets()) + + def __iter__(self): + """ + Iterate over ``self``. + + EXAMPLES:: + + sage: T = Tabloids([2,1]) + sage: list(T) + [[{1, 2}, {3}], [{1, 3}, {2}], [{2, 3}, {1}]] + + sage: T = Tabloids([]) + sage: list(T) + [[]] + sage: T = Tabloids([1]) + sage: list(T) + [[{1}]] + sage: T = Tabloids([3]) + sage: list(T) + [[{1, 2, 3}]] + sage: T = Tabloids([1,1,1]) + sage: list(T) + [[{1}, {2}, {3}], + [{3}, {1}, {2}], + [{2}, {3}, {1}], + [{1}, {3}, {2}], + [{3}, {2}, {1}], + [{2}, {1}, {3}]] + """ + n = sum(self._shape) + # trivial cases + if n == 0: + yield self.element_class(self, [], check=False) + return + if n == 1: + yield self.element_class(self, [[1]], check=False) + return + + ell = len(self._shape) + if ell == n: # single column trivial case + for sigma in SymmetricGroup(n): + yield self.element_class(self, [[val] for val in sigma.tuple()], check=False) + return + if ell == 1: # single row trivial case + yield self.element_class(self, [range(1,n+1)], check=False) + return + + pos = [-1] * n + i = 0 + cur = [[] for _ in range(ell)] + while True: + if i == n: + yield self.element_class(self, cur, check=False) + i -= 1 + cur[pos[i]].pop() + pos[i] += 1 + while pos[i] < ell and self._shape[pos[i]] - len(cur[pos[i]]) == 0: + pos[i] += 1 + if pos[i] == ell: # backtrack + pos[i] = -1 + i -= 1 + if i < 0: + break + cur[pos[i]].pop() + continue + cur[pos[i]].append(i+1) + i += 1 + + def from_tableau(self, T): + """ + Construct a tabloid from the tableau ``T``. + + EXAMPLES:: + + sage: T = Tabloids([3, 1]) + sage: T.from_tableau([[1,3,4], [2]]) + [{1, 3, 4}, {2}] + """ + return self.element_class(self, T) + + def cardinality(self): + """ + Return the cardinality of ``self``. + + EXAMPLES:: + + sage: T = Tabloids([3,2,1]) + sage: T.cardinality() + 60 + """ + return ZZ(multinomial(list(self._shape))) + + Element = Tabloid + diff --git a/src/sage/modules/with_basis/subquotient.py b/src/sage/modules/with_basis/subquotient.py index 220e1e521f3..cadd8bbb7fd 100644 --- a/src/sage/modules/with_basis/subquotient.py +++ b/src/sage/modules/with_basis/subquotient.py @@ -82,7 +82,7 @@ def __classcall_private__(cls, submodule, category=None): category = default_category.or_subcategory(category, join=True) return super().__classcall__(cls, submodule, category) - def __init__(self, submodule, category): + def __init__(self, submodule, category, *args, **opts): r""" Initialize this quotient of a module with basis by a submodule. @@ -107,7 +107,7 @@ def __init__(self, submodule, category): indices = embedding.cokernel_basis_indices() CombinatorialFreeModule.__init__(self, submodule.base_ring(), indices, - category=category) + category=category, *args, **opts) def ambient(self): r""" From 6615954808921015bbeb0110c3bc06e9bd897b0a Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Thu, 23 Nov 2023 15:20:13 +0900 Subject: [PATCH 206/278] Adding Brauer characters, fixing doctest failures, other improvements. --- src/sage/combinat/partition.py | 2 +- src/sage/combinat/permutation.py | 2 +- src/sage/combinat/specht_module.py | 274 +++++++++++++++---- src/sage/combinat/symmetric_group_algebra.py | 29 +- src/sage/combinat/tabloid.py | 25 +- 5 files changed, 262 insertions(+), 70 deletions(-) diff --git a/src/sage/combinat/partition.py b/src/sage/combinat/partition.py index cec67892f4c..88074aef457 100644 --- a/src/sage/combinat/partition.py +++ b/src/sage/combinat/partition.py @@ -5495,7 +5495,7 @@ def specht_module(self, base_ring=None): EXAMPLES:: sage: SM = Partition([2,2,1]).specht_module(QQ); SM - Specht module of [(0, 0), (0, 1), (1, 0), (1, 1), (2, 0)] over Rational Field + Specht module of [2, 2, 1] over Rational Field sage: s = SymmetricFunctions(QQ).s() sage: s(SM.frobenius_image()) # needs sage.modules s[2, 2, 1] diff --git a/src/sage/combinat/permutation.py b/src/sage/combinat/permutation.py index 92e187287fd..7f702977168 100644 --- a/src/sage/combinat/permutation.py +++ b/src/sage/combinat/permutation.py @@ -1263,7 +1263,7 @@ def __mul__(self, rp): sage: p213 * SGA.an_element() 3*[1, 2, 3] + [1, 3, 2] + [2, 1, 3] + 2*[3, 1, 2] sage: p213 * SM.an_element() - 2*B[0] - 4*B[1] + 2*S[[1, 2], [3]] - 4*S[[1, 3], [2]] """ if not isinstance(rp, Permutation) and isinstance(rp, Element): return get_coercion_model().bin_op(self, rp, operator.mul) diff --git a/src/sage/combinat/specht_module.py b/src/sage/combinat/specht_module.py index 2ddc88a1f3f..9e2c35cfe60 100644 --- a/src/sage/combinat/specht_module.py +++ b/src/sage/combinat/specht_module.py @@ -5,6 +5,13 @@ AUTHORS: - Travis Scrimshaw (2023-1-22): initial version +- Travis Scrimshaw (2023-11-23): added simple modules based on code + from Sacha Goldman + +.. TODO:: + + Integrate this with the implementations in + :mod:`sage.modules.with_basis.representation`. """ # **************************************************************************** @@ -26,10 +33,10 @@ from sage.matrix.constructor import matrix from sage.rings.rational_field import QQ from sage.modules.with_basis.subquotient import SubmoduleWithBasis, QuotientModuleWithBasis +from sage.modules.free_module_element import vector from sage.categories.modules_with_basis import ModulesWithBasis - -class SymmetricGroupRepresentation(): +class SymmetricGroupRepresentation: """ Mixin class for symmetric group (algebra) representations. """ @@ -42,48 +49,9 @@ def __init__(self, SGA): sage: SM = Partition([3,1,1]).specht_module(GF(3)) sage: TestSuite(SM).run() """ + self._semigroup = SGA.group() self._SGA = SGA - def representation_matrix(self, elt): - r""" - Return the matrix corresponding to the left action of the symmetric - group (algebra) element ``elt`` on ``self``. - - .. SEEALSO:: - - :class:`~sage.combinat.symmetric_group_representations.SpechtRepresentation` - - EXAMPLES:: - - sage: SM = Partition([3,1,1]).specht_module(QQ) - sage: SM.representation_matrix(Permutation([2,1,3,5,4])) - [-1 0 0 0 0 0] - [ 0 0 0 -1 0 0] - [ 1 0 0 -1 1 0] - [ 0 -1 0 0 0 0] - [ 1 -1 1 0 0 0] - [ 0 -1 0 1 0 -1] - - sage: SGA = SymmetricGroupAlgebra(QQ, 5) - sage: SM = SGA.specht_module([(0,0), (0,1), (0,2), (1,0), (2,0)]) - sage: SM.representation_matrix(Permutation([2,1,3,5,4])) - [-1 0 0 1 -1 0] - [ 0 0 1 0 -1 1] - [ 0 1 0 -1 0 1] - [ 0 0 0 0 -1 0] - [ 0 0 0 -1 0 0] - [ 0 0 0 0 0 -1] - sage: SGA = SymmetricGroupAlgebra(QQ, 5) - sage: SM.representation_matrix(SGA([3,1,5,2,4])) - [ 0 -1 0 1 0 -1] - [ 0 0 0 0 0 -1] - [ 0 0 0 -1 0 0] - [ 0 0 -1 0 1 -1] - [ 1 0 0 -1 1 0] - [ 0 0 0 0 1 0] - """ - return matrix(self.base_ring(), [(elt * b).to_vector() for b in self.basis()]) - @cached_method def frobenius_image(self): r""" @@ -140,11 +108,142 @@ def frobenius_image(self): from sage.combinat.sf.sf import SymmetricFunctions BR = self.base_ring() p = SymmetricFunctions(BR).p() - G = self._SGA.group() + G = self._semigroup CCR = [(elt, elt.cycle_type()) for elt in G.conjugacy_classes_representatives()] return p.sum(self.representation_matrix(elt).trace() / la.centralizer_size() * p[la] for elt, la in CCR) + # TODO: Move these methods up to methods of general representations + + def representation_matrix(self, elt): + r""" + Return the matrix corresponding to the left action of the symmetric + group (algebra) element ``elt`` on ``self``. + + EXAMPLES:: + + sage: SM = Partition([3,1,1]).specht_module(QQ) + sage: SM.representation_matrix(Permutation([2,1,3,5,4])) + [-1 0 0 0 0 0] + [ 0 0 0 -1 0 0] + [ 1 0 0 -1 1 0] + [ 0 -1 0 0 0 0] + [ 1 -1 1 0 0 0] + [ 0 -1 0 1 0 -1] + + sage: SGA = SymmetricGroupAlgebra(QQ, 5) + sage: SM = SGA.specht_module([(0,0), (0,1), (0,2), (1,0), (2,0)]) + sage: SM.representation_matrix(Permutation([2,1,3,5,4])) + [-1 0 0 1 -1 0] + [ 0 0 1 0 -1 1] + [ 0 1 0 -1 0 1] + [ 0 0 0 0 -1 0] + [ 0 0 0 -1 0 0] + [ 0 0 0 0 0 -1] + sage: SGA = SymmetricGroupAlgebra(QQ, 5) + sage: SM.representation_matrix(SGA([3,1,5,2,4])) + [ 0 -1 0 1 0 -1] + [ 0 0 0 0 0 -1] + [ 0 0 0 -1 0 0] + [ 0 0 -1 0 1 -1] + [ 1 0 0 -1 1 0] + [ 0 0 0 0 1 0] + """ + return matrix(self.base_ring(), [(elt * b).to_vector() for b in self.basis()]) + + @cached_method + def character(self): + """ + Return the character of ``self``. + + EXAMPLES:: + + sage: SGA = SymmetricGroupAlgebra(QQ, 5) + sage: SM = SGA.specht_module([3,2]) + sage: SM.character() + (5, 1, 1, -1, 1, -1, 0) + sage: matrix(SGA.specht_module(la).character() for la in Partitions(5)) + [ 1 1 1 1 1 1 1] + [ 4 2 0 1 -1 0 -1] + [ 5 1 1 -1 1 -1 0] + [ 6 0 -2 0 0 0 1] + [ 5 -1 1 -1 -1 1 0] + [ 4 -2 0 1 1 0 -1] + [ 1 -1 1 1 -1 -1 1] + + sage: SGA = SymmetricGroupAlgebra(QQ, SymmetricGroup(5)) + sage: SM = SGA.specht_module([3,2]) + sage: SM.character() + Character of Symmetric group of order 5! as a permutation group + sage: SM.character().values() + [5, 1, 1, -1, 1, -1, 0] + sage: matrix(SGA.specht_module(la).character().values() for la in reversed(Partitions(5))) + [ 1 -1 1 1 -1 -1 1] + [ 4 -2 0 1 1 0 -1] + [ 5 -1 1 -1 -1 1 0] + [ 6 0 -2 0 0 0 1] + [ 5 1 1 -1 1 -1 0] + [ 4 2 0 1 -1 0 -1] + [ 1 1 1 1 1 1 1] + sage: SGA.group().character_table() + [ 1 -1 1 1 -1 -1 1] + [ 4 -2 0 1 1 0 -1] + [ 5 -1 1 -1 -1 1 0] + [ 6 0 -2 0 0 0 1] + [ 5 1 1 -1 1 -1 0] + [ 4 2 0 1 -1 0 -1] + [ 1 1 1 1 1 1 1] + """ + G = self._semigroup + chi = [self.representation_matrix(g).trace() + for g in G.conjugacy_classes_representatives()] + try: + return G.character(chi) + except AttributeError: + return vector(chi, immutable=True) + + @cached_method + def brauer_character(self): + """ + Return the Brauer character of ``self``. + + EXAMPLES:: + + sage: SGA = SymmetricGroupAlgebra(GF(2), 5) + sage: SM = SGA.specht_module([3,2]) + sage: SM.brauer_character() + (5, -1, 0) + sage: SM.simple_module().brauer_character() + (4, -2, -1) + """ + from sage.rings.number_field.number_field import CyclotomicField + from sage.arith.functions import lcm + G = self._semigroup + p = self.base_ring().characteristic() + # We manually compute the order since a Permutation does not implement order() + chi = [] + for g in G.conjugacy_classes_representatives(): + if p.divides(lcm(g.cycle_type())): + # ignore the non-p-regular elements + continue + evals = self.representation_matrix(g).eigenvalues() + K = evals[0].parent() + val = 0 + for la in evals: + if la == K.one(): + val += 1 + continue + o = la.multiplicative_order() + zeta = CyclotomicField(o).gen() + prim = K.zeta(o) + for deg in range(o): + if prim**deg == la: + val += zeta ** deg + break + chi.append(val) + + return vector(chi, immutable=True) + class SpechtModule(SubmoduleWithBasis, SymmetricGroupRepresentation): r""" @@ -543,6 +642,13 @@ class SpechtModuleTableauxBasis(SpechtModule): This is constructed as a `S_n`-submodule of the :class:`TabloidModule` (also referred to as the standard module). + + .. SEEALSO:: + + - :class:`SpechtModule` for the generic diagram implementation + constructed as a left ideal of the group algebra + - :class:`~sage.combinat.symmetric_group_representations.SpechtRepresentation` + for an implementation of the representation by matrices. """ def __init__(self, ambient): r""" @@ -698,6 +804,10 @@ def simple_module(self): r""" Return the simple (or irreducible) `S_n`-submodule of ``self``. + .. SEEALSO:: + + :class:`~sage.combinat.specht_module.SimpleModule` + EXAMPLES:: sage: SGA = SymmetricGroupAlgebra(GF(3), 5) @@ -705,7 +815,14 @@ def simple_module(self): sage: L = SM.simple_module() sage: L.dimension() 1 + + sage: SGA = SymmetricGroupAlgebra(QQ, 5) + sage: SM = SGA.specht_module([3,2]) + sage: SM.simple_module() is SM + True """ + if self.base_ring().characteristic() == 0: + return self return SimpleModule(self) @@ -742,13 +859,22 @@ def __init__(self, specht_module): sage: SM = SGA.specht_module([3,2]) sage: U = SM.maximal_submodule() sage: TestSuite(U).run() + + sage: SM = SGA.specht_module([2,1,1,1]) + sage: SM.maximal_submodule() + Traceback (most recent call last): + ... + NotImplementedError: only implemented for 3-regular partitions """ SymmetricGroupRepresentation.__init__(self, specht_module._SGA) - if specht_module.base_ring().characteristic() == 0: + p = specht_module.base_ring().characteristic() + if p == 0: basis = [] else: TM = specht_module._ambient + if not TM._shape.is_regular(p): + raise NotImplementedError(f"only implemented for {p}-regular partitions") TV = TM._dense_free_module() SV = TV.submodule(specht_module.lift.matrix().columns()) basis = (SV & SV.complement()).basis() @@ -782,7 +908,7 @@ class SimpleModule(QuotientModuleWithBasis, SymmetricGroupRepresentation): r""" The simgle `S_n`-module associated with a partition `\lambda`. - The simple module `L^{\lambda}` is the quotient of the Specht module + The simple module `D^{\lambda}` is the quotient of the Specht module `S^{\lambda}` by its :class:`maximal submodule ` `U^{\lambda}`. @@ -790,30 +916,70 @@ class SimpleModule(QuotientModuleWithBasis, SymmetricGroupRepresentation): sage: SGA = SymmetricGroupAlgebra(GF(3), 5) sage: SM = SGA.specht_module([3,1,1]) - sage: L = SM.simple_module() - sage: v = L.an_element(); v - 2*L[[[1, 3, 5], [2], [4]]] + 2*L[[[1, 4, 5], [2], [3]]] + sage: D = SM.simple_module() + sage: v = D.an_element(); v + 2*D[[[1, 3, 5], [2], [4]]] + 2*D[[[1, 4, 5], [2], [3]]] sage: SGA.an_element() * v - 2*L[[[1, 2, 4], [3], [5]]] + 2*L[[[1, 3, 5], [2], [4]]] + 2*D[[[1, 2, 4], [3], [5]]] + 2*D[[[1, 3, 5], [2], [4]]] + + We give an example on how to construct the decomposition matrix + (the Specht modules are a complete set of irreducible projective + modules) and the Cartan matrix of a symmetric group algebra:: + + sage: SGA = SymmetricGroupAlgebra(GF(3), 4) + sage: BM = matrix(SGA.simple_module(la).brauer_character() + ....: for la in Partitions(4, regular=3)) + sage: SBT = matrix(SGA.specht_module(la).brauer_character() + ....: for la in Partitions(4)) + sage: D = SBT * ~BM; D + [1 0 0 0] + [0 1 0 0] + [1 0 1 0] + [0 0 0 1] + [0 0 1 0] + sage: D.transpose() * D + [2 0 1 0] + [0 1 0 0] + [1 0 2 0] + [0 0 0 1] + + We verify this against the direct computation (up to reindexing the + rows and columns):: + + sage: SGA.cartan_invariants_matrix() # long time + [1 0 0 0] + [0 1 0 0] + [0 0 2 1] + [0 0 1 2] """ def __init__(self, specht_module): - """ + r""" Initialize ``self``. EXAMPLES:: sage: SGA = SymmetricGroupAlgebra(GF(3), 5) sage: SM = SGA.specht_module([3,1,1]) - sage: L = SM.simple_module() - sage: TestSuite(L).run() + sage: D = SM.simple_module() + sage: TestSuite(D).run() + + sage: SGA = SymmetricGroupAlgebra(GF(3), 5) + sage: SM = SGA.specht_module([2,1,1,1]) + sage: SM.simple_module() + Traceback (most recent call last): + ... + ValueError: the partition must be 3-regular """ self._diagram = specht_module._diagram + p = specht_module.base_ring().characteristic() + if not self._diagram.is_regular(p): + raise ValueError(f"the partition must be {p}-regular") SymmetricGroupRepresentation.__init__(self, specht_module._SGA) cat = specht_module.category() - QuotientModuleWithBasis.__init__(self, specht_module.maximal_submodule(), cat, prefix='L') + QuotientModuleWithBasis.__init__(self, specht_module.maximal_submodule(), cat, prefix='D') def _repr_(self): - """ + r""" Return a string representation of ``self``. EXAMPLES:: diff --git a/src/sage/combinat/symmetric_group_algebra.py b/src/sage/combinat/symmetric_group_algebra.py index b741914f506..8fb49d3396c 100644 --- a/src/sage/combinat/symmetric_group_algebra.py +++ b/src/sage/combinat/symmetric_group_algebra.py @@ -1587,9 +1587,13 @@ def specht_module(self, D): return SpechtModule(self, D) def tabloid_module(self, D): - """ + r""" Return the module of tabloids with the natural action of ``self``. + .. SEEALSO:: + + :class:`~sage.combinat.specht_module.TabloidModule` + EXAMPLES:: sage: SGA = SymmetricGroupAlgebra(QQ, 5) @@ -1620,6 +1624,29 @@ def specht_module_dimension(self, D): span_set = specht_module_spanning_set(D, self) return matrix(self.base_ring(), [v.to_vector() for v in span_set]).rank() + def simple_module(self, la): + r""" + Return the simple module of ``self`` indexed by the partition ``la``. + + Over a field of characteristic `0`, this simply returns the Specht + module. + + .. SEEALSO:: + + :class:`sage.combinat.specht_module.SimpleModule` + + EXAMPLES:: + + sage: SGA = SymmetricGroupAlgebra(GF(3), 5) + sage: D = SGA.simple_module(Partition([3,1,1])) + sage: D + Simple module of [3, 1, 1] over Finite Field of size 3 + sage: D.brauer_character() + (6, 0, -2, 0, 1) + """ + from sage.combinat.specht_module import SpechtModule + return SpechtModule(self, la).simple_module() + def simple_module_dimension(self, la): r""" Return the dimension of the simple module of ``self`` indexed by the diff --git a/src/sage/combinat/tabloid.py b/src/sage/combinat/tabloid.py index ed41c8e6db9..72a45f0ebfc 100644 --- a/src/sage/combinat/tabloid.py +++ b/src/sage/combinat/tabloid.py @@ -25,7 +25,7 @@ class Tabloid(ClonableArray): integer partition). """ def __init__(self, parent, tableaux, check=True): - """ + r""" Initialize ``self``. EXAMPLES:: @@ -37,7 +37,7 @@ def __init__(self, parent, tableaux, check=True): super().__init__(parent, map(frozenset, tableaux), check=check) def check(self): - """ + r""" Check that ``self`` is a valid tabloid. EXAMPLES:: @@ -70,7 +70,7 @@ def check(self): assert not X def _repr_(self): - """ + r""" Return a string representation of ``self``. EXAMPLES:: @@ -84,7 +84,7 @@ def _repr_(self): return ret + "]" def _ascii_art_(self): - """ + r""" Return a string representation of ``self``. EXAMPLES:: @@ -124,7 +124,7 @@ def _latex_(self): return ret.replace("|", "") def symmetric_group_action(self, permutation): - """ + r""" Return the left action of ``permutation`` on ``self``. EXAMPLES:: @@ -151,7 +151,7 @@ def symmetric_group_action(self, permutation): return P.element_class(P, [[permutation(val) for val in row] for row in self]) def _acted_upon_(self, sigma, self_on_left=True): - """ + r""" Return the action of ``sigma`` on ``self``. EXAMPLES:: @@ -179,12 +179,12 @@ def _acted_upon_(self, sigma, self_on_left=True): class Tabloids(UniqueRepresentation, Parent): - """ + r""" Tabloids of a fixed shape. """ @staticmethod def __classcall_private__(cls, partition): - """ + r""" Normalize input to ensure a unique representation. EXAMPLES:: @@ -199,7 +199,7 @@ def __classcall_private__(cls, partition): return super().__classcall__(cls, partition) def __init__(self, partition): - """ + r""" Initialize ``self``. EXAMPLES:: @@ -223,7 +223,7 @@ def __init__(self, partition): Parent.__init__(self, category=FiniteEnumeratedSets()) def __iter__(self): - """ + r""" Iterate over ``self``. EXAMPLES:: @@ -290,7 +290,7 @@ def __iter__(self): i += 1 def from_tableau(self, T): - """ + r""" Construct a tabloid from the tableau ``T``. EXAMPLES:: @@ -302,7 +302,7 @@ def from_tableau(self, T): return self.element_class(self, T) def cardinality(self): - """ + r""" Return the cardinality of ``self``. EXAMPLES:: @@ -314,4 +314,3 @@ def cardinality(self): return ZZ(multinomial(list(self._shape))) Element = Tabloid - From 089931a5b0663812bc008885ddf5e0da66dfca2c Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Thu, 23 Nov 2023 15:43:17 +0900 Subject: [PATCH 207/278] Fixing doc issue. --- src/sage/combinat/specht_module.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/sage/combinat/specht_module.py b/src/sage/combinat/specht_module.py index 9e2c35cfe60..ca24856cebc 100644 --- a/src/sage/combinat/specht_module.py +++ b/src/sage/combinat/specht_module.py @@ -586,6 +586,8 @@ def bilinear_form(self, u, v): The natural bilinear form is given by defining the tabloid basis to be orthonormal. + EXAMPLES:: + sage: SGA = SymmetricGroupAlgebra(QQ, 5) sage: TM = SGA.tabloid_module([2,2,1]) sage: u = TM.an_element(); u From deaf9d993cbecbedea47081a364bb9712e46cd42 Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Sat, 30 Dec 2023 13:48:07 +0900 Subject: [PATCH 208/278] Reviewer comments and adding a few more tests. --- src/sage/combinat/specht_module.py | 67 ++++++++++++++++++++++-------- src/sage/combinat/tabloid.py | 4 ++ 2 files changed, 54 insertions(+), 17 deletions(-) diff --git a/src/sage/combinat/specht_module.py b/src/sage/combinat/specht_module.py index ca24856cebc..a7283aa6136 100644 --- a/src/sage/combinat/specht_module.py +++ b/src/sage/combinat/specht_module.py @@ -65,53 +65,53 @@ def frobenius_image(self): where `\chi` is the character of the `S_n`-module ``self``, `p_{\lambda}` is the powersum symmetric function basis element - indexed by `\lambda`, and `\rho(w)` is partition of the cycle type - of `w`. Specifically, this map takes irreducible representations + indexed by `\lambda`, and `\rho(w)` is the cycle type of `w` as a + partition. Specifically, this map takes irreducible representations indexed by `\lambda` to the Schur function `s_{\lambda}`. EXAMPLES:: sage: s = SymmetricFunctions(QQ).s() sage: SM = Partition([2,2,1]).specht_module(QQ) - sage: s(SM.frobenius_image()) + sage: SM.frobenius_image() s[2, 2, 1] sage: SM = Partition([4,1]).specht_module(CyclotomicField(5)) - sage: s(SM.frobenius_image()) + sage: SM.frobenius_image() s[4, 1] We verify the regular representation:: sage: from sage.combinat.diagram import Diagram sage: D = Diagram([(0,0), (1,1), (2,2), (3,3), (4,4)]) - sage: F = s(D.specht_module(QQ).frobenius_image()); F + sage: F = D.specht_module(QQ).frobenius_image(); F s[1, 1, 1, 1, 1] + 4*s[2, 1, 1, 1] + 5*s[2, 2, 1] + 6*s[3, 1, 1] + 5*s[3, 2] + 4*s[4, 1] + s[5] sage: F == sum(StandardTableaux(la).cardinality() * s[la] ....: for la in Partitions(5)) True - sage: all(s[la] == s(la.specht_module(QQ).frobenius_image()) + sage: all(s[la] == la.specht_module(QQ).frobenius_image() ....: for n in range(1, 5) for la in Partitions(n)) True sage: D = Diagram([(0,0), (1,1), (1,2), (2,3), (2,4)]) sage: SM = D.specht_module(QQ) - sage: s(SM.frobenius_image()) + sage: SM.frobenius_image() s[2, 2, 1] + s[3, 1, 1] + 2*s[3, 2] + 2*s[4, 1] + s[5] An example using the tabloid module:: sage: SGA = SymmetricGroupAlgebra(QQ, 5) sage: TM = SGA.tabloid_module([2, 2, 1]) - sage: s(TM.frobenius_image()) + sage: TM.frobenius_image() s[2, 2, 1] + s[3, 1, 1] + 2*s[3, 2] + 2*s[4, 1] + s[5] """ from sage.combinat.sf.sf import SymmetricFunctions - BR = self.base_ring() - p = SymmetricFunctions(BR).p() + p = SymmetricFunctions(QQ).p() + s = SymmetricFunctions(QQ).s() G = self._semigroup CCR = [(elt, elt.cycle_type()) for elt in G.conjugacy_classes_representatives()] - return p.sum(self.representation_matrix(elt).trace() / la.centralizer_size() * p[la] - for elt, la in CCR) + return s(p.sum(QQ(self.representation_matrix(elt).trace()) / la.centralizer_size() * p[la] + for elt, la in CCR)) # TODO: Move these methods up to methods of general representations @@ -493,6 +493,23 @@ def _acted_upon_(self, x, self_on_left=False): 9*S[[1, 2, 3], [4]] + 17*S[[1, 2, 4], [3]] + 14*S[[1, 3, 4], [2]] sage: 4 * SM.an_element() 12*S[[1, 2, 3], [4]] + 8*S[[1, 2, 4], [3]] + 8*S[[1, 3, 4], [2]] + + TESTS:: + + sage: SGA = SymmetricGroupAlgebra(QQ, 4) + sage: SM = SGA.specht_module([3,1]) + sage: SM.an_element() * SGA.an_element() + Traceback (most recent call last): + ... + TypeError: unsupported operand parent(s) for *: + 'Specht module of [3, 1] over Rational Field' + and 'Symmetric group algebra of order 4 over Rational Field' + sage: groups.permutation.Dihedral(3).an_element() * SM.an_element() + Traceback (most recent call last): + ... + TypeError: unsupported operand parent(s) for *: + 'Dihedral group of order 6 as a permutation group' + and 'Specht module of [3, 1] over Rational Field' """ # Check for a scalar first ret = super()._acted_upon_(x, self_on_left) @@ -507,6 +524,7 @@ def _acted_upon_(self, x, self_on_left=False): return P.retract(P._SGA(x) * self.lift()) return None + class TabloidModule(CombinatorialFreeModule, SymmetricGroupRepresentation): r""" The vector space of all tabloids with the natural symmetric group action. @@ -595,6 +613,8 @@ def bilinear_form(self, u, v): sage: v = sum(TM.basis()) sage: TM.bilinear_form(u, v) 7 + sage: TM.bilinear_form(u, TM.zero()) + 0 """ if len(v) < len(u): u, v = v, u @@ -622,6 +642,12 @@ def _acted_upon_(self, x, self_on_left): + 12*T[{1, 2}, {3, 4}, {5}] + 15*T[{1, 2}, {3, 5}, {4}] + 15*T[{1, 2}, {4, 5}, {3}] sage: 4 * SM.an_element() 8*T[{1, 2}, {3, 4}, {5}] + 8*T[{1, 2}, {3, 5}, {4}] + 12*T[{1, 2}, {4, 5}, {3}] + sage: SM.an_element() * SGA.an_element() + Traceback (most recent call last): + ... + TypeError: unsupported operand parent(s) for *: + 'Tabloid module of [2, 2, 1] over Rational Field' + and 'Symmetric group algebra of order 5 over Rational Field' """ # first check for the base action ret = super()._acted_upon_(x, self_on_left) @@ -665,14 +691,10 @@ def __init__(self, ambient): self._diagram = ambient._shape SymmetricGroupRepresentation.__init__(self, ambient._SGA) - from sage.combinat.symmetric_group_algebra import e ambient_basis = ambient.basis() tabloids = ambient_basis.keys() support_order = list(tabloids) - G = self._SGA.basis().keys() - BR = self._SGA.base_ring() - def elt(T): tab = tabloids.from_tableau(T) return ambient.sum_of_terms((tab.symmetric_group_action(sigma), sigma.sign()) @@ -720,6 +742,10 @@ def retract(self): sage: Y.retract(X.zero()) 0 + sage: Y.retract(sum(X.basis())) + Traceback (most recent call last): + ... + ValueError: ... is not in the image """ B = self.basis() COB = matrix([b.lift().to_vector() for b in B]).T @@ -867,12 +893,19 @@ def __init__(self, specht_module): Traceback (most recent call last): ... NotImplementedError: only implemented for 3-regular partitions + + sage: SGA = SymmetricGroupAlgebra(QQ, 5) + sage: SM = SGA.specht_module([3,2]) + sage: U = SM.maximal_submodule() + sage: TestSuite(U).run(skip="_test_cardinality") # skip due to bug for 0 dimensional modules + sage: U.dimension() + 0 """ SymmetricGroupRepresentation.__init__(self, specht_module._SGA) p = specht_module.base_ring().characteristic() if p == 0: - basis = [] + basis = Family([]) else: TM = specht_module._ambient if not TM._shape.is_regular(p): diff --git a/src/sage/combinat/tabloid.py b/src/sage/combinat/tabloid.py index 72a45f0ebfc..976d5ad1e69 100644 --- a/src/sage/combinat/tabloid.py +++ b/src/sage/combinat/tabloid.py @@ -115,6 +115,10 @@ def _latex_(self): \lr{6}\\\cline{1-1} \end{array}$} } + + sage: T = Tabloids([]) + sage: latex(T([])) + {\emptyset} """ if not self: return "{\\emptyset}" From 2c1fd9477bae9e36d12a961e2ff42ff2163dfa3e Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Sat, 30 Dec 2023 14:20:42 +0900 Subject: [PATCH 209/278] Some last details and adding stuff to the documentation. --- src/doc/en/reference/combinat/module_list.rst | 1 + src/sage/combinat/algebraic_combinatorics.py | 5 +++-- src/sage/combinat/catalog_partitions.py | 2 ++ src/sage/combinat/skew_tableau.py | 4 ++-- src/sage/combinat/specht_module.py | 5 +++-- 5 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/doc/en/reference/combinat/module_list.rst b/src/doc/en/reference/combinat/module_list.rst index 771d335d88d..adeedff3a7c 100644 --- a/src/doc/en/reference/combinat/module_list.rst +++ b/src/doc/en/reference/combinat/module_list.rst @@ -362,6 +362,7 @@ Comprehensive Module List sage/combinat/tableau sage/combinat/tableau_residues sage/combinat/tableau_tuple + sage/combinat/tabloid sage/combinat/tamari_lattices sage/combinat/tiling sage/combinat/tools diff --git a/src/sage/combinat/algebraic_combinatorics.py b/src/sage/combinat/algebraic_combinatorics.py index a024d2fa67d..3dd16bf7799 100644 --- a/src/sage/combinat/algebraic_combinatorics.py +++ b/src/sage/combinat/algebraic_combinatorics.py @@ -12,7 +12,8 @@ ---------------------------------------- - :ref:`sage.combinat.catalog_partitions` -- :class:`~sage.combinat.gelfand_tsetlin_patterns.GelfandTsetlinPattern`, :class:`~sage.combinat.gelfand_tsetlin_patterns.GelfandTsetlinPatterns` +- :class:`~sage.combinat.gelfand_tsetlin_patterns.GelfandTsetlinPattern`, + :class:`~sage.combinat.gelfand_tsetlin_patterns.GelfandTsetlinPatterns` - :class:`~sage.combinat.knutson_tao_puzzles.KnutsonTaoPuzzleSolver` Groups and Algebras @@ -39,7 +40,7 @@ - :ref:`sage.combinat.cluster_algebra_quiver.all` - :class:`~sage.combinat.kazhdan_lusztig.KazhdanLusztigPolynomial` - :class:`~sage.combinat.symmetric_group_representations.SymmetricGroupRepresentation` -- :class:`~sage.combinat.specht_module.SpechtModule` +- :ref:`sage.combinat.specht_module` - :ref:`sage.combinat.yang_baxter_graph` - :ref:`sage.combinat.hall_polynomial` - :ref:`sage.combinat.key_polynomial` diff --git a/src/sage/combinat/catalog_partitions.py b/src/sage/combinat/catalog_partitions.py index ca143fa540c..ed1018be436 100644 --- a/src/sage/combinat/catalog_partitions.py +++ b/src/sage/combinat/catalog_partitions.py @@ -8,7 +8,9 @@ - :ref:`sage.combinat.skew_partition` - :ref:`sage.combinat.partition_tuple` - :ref:`sage.combinat.superpartition` +- :ref:`sage.combinat.tableau` - :ref:`sage.combinat.tableau_tuple` +- :ref:`sage.combinat.tabloid` - :ref:`sage.combinat.skew_tableau` - :ref:`sage.combinat.ribbon` - :ref:`sage.combinat.ribbon_tableau` diff --git a/src/sage/combinat/skew_tableau.py b/src/sage/combinat/skew_tableau.py index 3019522d21e..de447b1f556 100644 --- a/src/sage/combinat/skew_tableau.py +++ b/src/sage/combinat/skew_tableau.py @@ -6,11 +6,11 @@ - Mike Hansen: Initial version - Travis Scrimshaw, Arthur Lubovsky (2013-02-11): Factored out ``CombinatorialClass`` -- Trevor K. Karn (2022-08-03): added `backward_slide` +- Trevor K. Karn (2022-08-03): added ``backward_slide`` """ # **************************************************************************** # Copyright (C) 2007 Mike Hansen , -# Copyright (C) 2013 Travis Scrimshaw +# Copyright (C) 2013 Travis Scrimshaw # Copyright (C) 2013 Arthur Lubovsky # # Distributed under the terms of the GNU General Public License (GPL) diff --git a/src/sage/combinat/specht_module.py b/src/sage/combinat/specht_module.py index a7283aa6136..500255cee73 100644 --- a/src/sage/combinat/specht_module.py +++ b/src/sage/combinat/specht_module.py @@ -527,7 +527,8 @@ def _acted_upon_(self, x, self_on_left=False): class TabloidModule(CombinatorialFreeModule, SymmetricGroupRepresentation): r""" - The vector space of all tabloids with the natural symmetric group action. + The vector space of all :class:`~sage.combinat.tabloid.Tabloids` of a + fixed shape with the natural symmetric group action. """ @staticmethod def __classcall_private__(cls, SGA, shape): @@ -897,7 +898,7 @@ def __init__(self, specht_module): sage: SGA = SymmetricGroupAlgebra(QQ, 5) sage: SM = SGA.specht_module([3,2]) sage: U = SM.maximal_submodule() - sage: TestSuite(U).run(skip="_test_cardinality") # skip due to bug for 0 dimensional modules + sage: TestSuite(U).run() sage: U.dimension() 0 """ From 4da2779a11cb94ec061fccc04e61c11f96a3a0cb Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Mon, 19 Feb 2024 10:31:42 +0900 Subject: [PATCH 210/278] Fixing PEP8 spacing. --- src/sage/combinat/specht_module.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/combinat/specht_module.py b/src/sage/combinat/specht_module.py index 500255cee73..e90100c18d2 100644 --- a/src/sage/combinat/specht_module.py +++ b/src/sage/combinat/specht_module.py @@ -620,7 +620,7 @@ def bilinear_form(self, u, v): if len(v) < len(u): u, v = v, u R = self.base_ring() - return R.sum(c * v[T] for T,c in u) + return R.sum(c * v[T] for T, c in u) class Element(CombinatorialFreeModule.Element): def _acted_upon_(self, x, self_on_left): From a7af73070225fd49755e17ace456c2948a2e1269 Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Tue, 20 Feb 2024 11:41:03 +0900 Subject: [PATCH 211/278] Removing Tabloid(s) class; improving class structure; adding functionality. --- src/doc/en/reference/combinat/module_list.rst | 1 - src/sage/combinat/all.py | 3 - src/sage/combinat/catalog_partitions.py | 1 - src/sage/combinat/composition.py | 8 +- src/sage/combinat/partition.py | 20 +- src/sage/combinat/permutation.py | 13 + src/sage/combinat/specht_module.py | 191 +++++++++-- src/sage/combinat/tabloid.py | 320 ------------------ 8 files changed, 203 insertions(+), 354 deletions(-) delete mode 100644 src/sage/combinat/tabloid.py diff --git a/src/doc/en/reference/combinat/module_list.rst b/src/doc/en/reference/combinat/module_list.rst index adeedff3a7c..771d335d88d 100644 --- a/src/doc/en/reference/combinat/module_list.rst +++ b/src/doc/en/reference/combinat/module_list.rst @@ -362,7 +362,6 @@ Comprehensive Module List sage/combinat/tableau sage/combinat/tableau_residues sage/combinat/tableau_tuple - sage/combinat/tabloid sage/combinat/tamari_lattices sage/combinat/tiling sage/combinat/tools diff --git a/src/sage/combinat/all.py b/src/sage/combinat/all.py index 5bbe35c740a..79690ff7305 100644 --- a/src/sage/combinat/all.py +++ b/src/sage/combinat/all.py @@ -171,9 +171,6 @@ lazy_import('sage.combinat.super_tableau', ["StandardSuperTableau", "SemistandardSuperTableau", "StandardSuperTableaux", "SemistandardSuperTableaux"]) -# Tabloids -lazy_import('sage.combinat.tabloid', "Tabloids") - # Words from .words.all import * diff --git a/src/sage/combinat/catalog_partitions.py b/src/sage/combinat/catalog_partitions.py index ed1018be436..302e8f112d6 100644 --- a/src/sage/combinat/catalog_partitions.py +++ b/src/sage/combinat/catalog_partitions.py @@ -10,7 +10,6 @@ - :ref:`sage.combinat.superpartition` - :ref:`sage.combinat.tableau` - :ref:`sage.combinat.tableau_tuple` -- :ref:`sage.combinat.tabloid` - :ref:`sage.combinat.skew_tableau` - :ref:`sage.combinat.ribbon` - :ref:`sage.combinat.ribbon_tableau` diff --git a/src/sage/combinat/composition.py b/src/sage/combinat/composition.py index 8d01e9ac081..5a8536dd300 100644 --- a/src/sage/combinat/composition.py +++ b/src/sage/combinat/composition.py @@ -45,6 +45,9 @@ from sage.combinat.combinatorial_map import combinatorial_map from sage.misc.persist import register_unpickle_override +from sage.misc.lazy_import import lazy_import +lazy_import("sage.combinat.partition", "Partition") + class Composition(CombinatorialElement): r""" @@ -1186,7 +1189,6 @@ def to_partition(self): sage: Composition([]).to_partition() # needs sage.combinat [] """ - from sage.combinat.partition import Partition return Partition(sorted(self, reverse=True)) def to_skew_partition(self, overlap=1): @@ -1754,7 +1756,7 @@ def _element_constructor_(self, lst) -> Composition: sage: P([3,3,1]) # indirect doctest [3, 3, 1] """ - if isinstance(lst, Composition): + if isinstance(lst, (Composition, Partition)): lst = list(lst) elt = self.element_class(self, lst) if elt not in self: @@ -1774,7 +1776,7 @@ def __contains__(self, x) -> bool: sage: [0,0] in Compositions() True """ - if isinstance(x, Composition): + if isinstance(x, (Composition, Partition)): return True elif isinstance(x, list): for i in x: diff --git a/src/sage/combinat/partition.py b/src/sage/combinat/partition.py index 88074aef457..a52596b20ea 100644 --- a/src/sage/combinat/partition.py +++ b/src/sage/combinat/partition.py @@ -5497,7 +5497,7 @@ def specht_module(self, base_ring=None): sage: SM = Partition([2,2,1]).specht_module(QQ); SM Specht module of [2, 2, 1] over Rational Field sage: s = SymmetricFunctions(QQ).s() - sage: s(SM.frobenius_image()) # needs sage.modules + sage: SM.frobenius_image() # needs sage.modules s[2, 2, 1] """ from sage.combinat.specht_module import SpechtModule @@ -5570,6 +5570,24 @@ def simple_module_dimension(self, base_ring=None): from sage.combinat.specht_module import simple_module_rank return simple_module_rank(self, base_ring) + def tabloid_module(self, base_ring=None): + r""" + Return the tabloid module corresponding to ``self``. + + EXAMPLES:: + + sage: TM = Partition([2,2,1]).tabloid_module(QQ); TM + Tabloid module of [2, 2, 1] over Rational Field + sage: TM.frobenius_image() + s[2, 2, 1] + s[3, 1, 1] + 2*s[3, 2] + 2*s[4, 1] + s[5] + """ + from sage.combinat.specht_module import TabloidModule + from sage.combinat.symmetric_group_algebra import SymmetricGroupAlgebra + if base_ring is None: + from sage.rings.rational_field import QQ + base_ring = QQ + R = SymmetricGroupAlgebra(base_ring, sum(self)) + return TabloidModule(R, self) ############## # Partitions # diff --git a/src/sage/combinat/permutation.py b/src/sage/combinat/permutation.py index 7f702977168..7ef23ca65c4 100644 --- a/src/sage/combinat/permutation.py +++ b/src/sage/combinat/permutation.py @@ -7342,6 +7342,19 @@ def cardinality(self): """ return factorial(self.n) + @cached_method + def gens(self): + r""" + Return a set of generators for ``self`` as a group. + + EXAMPLES:: + + sage: P4 = Permutations(4) + sage: P4.gens() + ([2, 1, 3, 4], [1, 3, 2, 4], [1, 2, 4, 3]) + """ + return tuple(self.group_generators()) + def degree(self): """ Return the degree of ``self``. diff --git a/src/sage/combinat/specht_module.py b/src/sage/combinat/specht_module.py index e90100c18d2..4df915324d0 100644 --- a/src/sage/combinat/specht_module.py +++ b/src/sage/combinat/specht_module.py @@ -29,6 +29,7 @@ from sage.combinat.diagram import Diagram from sage.combinat.partition import _Partitions from sage.combinat.free_module import CombinatorialFreeModule +from sage.modules.with_basis.representation import Representation_abstract from sage.sets.family import Family from sage.matrix.constructor import matrix from sage.rings.rational_field import QQ @@ -50,7 +51,19 @@ def __init__(self, SGA): sage: TestSuite(SM).run() """ self._semigroup = SGA.group() - self._SGA = SGA + self._semigroup_algebra = SGA + + def side(self): + r""" + Return the side of the action defining ``self``. + + EXAMPLES:: + + sage: SM = Partition([3,1,1]).specht_module(GF(3)) + sage: SM.side() + 'left' + """ + return "left" @cached_method def frobenius_image(self): @@ -153,7 +166,7 @@ def representation_matrix(self, elt): @cached_method def character(self): - """ + r""" Return the character of ``self``. EXAMPLES:: @@ -204,7 +217,7 @@ def character(self): @cached_method def brauer_character(self): - """ + r""" Return the Brauer character of ``self``. EXAMPLES:: @@ -229,15 +242,18 @@ def brauer_character(self): evals = self.representation_matrix(g).eigenvalues() K = evals[0].parent() val = 0 + orders = {la: la.multiplicative_order() for la in evals if la != K.one()} + zetas = {o: CyclotomicField(o).gen() for o in orders.values()} + prims = {o: K.zeta(o) for o in orders.values()} for la in evals: if la == K.one(): val += 1 continue o = la.multiplicative_order() - zeta = CyclotomicField(o).gen() - prim = K.zeta(o) + zeta = zetas[o] + prim = prims[o] for deg in range(o): - if prim**deg == la: + if prim ** deg == la: val += zeta ** deg break chi.append(val) @@ -245,7 +261,7 @@ def brauer_character(self): return vector(chi, immutable=True) -class SpechtModule(SubmoduleWithBasis, SymmetricGroupRepresentation): +class SpechtModule(SubmoduleWithBasis, SymmetricGroupRepresentation, Representation_abstract): r""" A Specht module. @@ -517,18 +533,37 @@ def _acted_upon_(self, x, self_on_left=False): return ret # Check if it is in the symmetric group algebra P = self.parent() - if x in P._SGA or x in P._SGA.group(): + if x in P._semigroup_algebra or x in P._semigroup_algebra.group(): if self_on_left: # it is only a left module return None else: - return P.retract(P._SGA(x) * self.lift()) + return P.retract(P._semigroup_algebra(x) * self.lift()) return None -class TabloidModule(CombinatorialFreeModule, SymmetricGroupRepresentation): +class TabloidModule(SymmetricGroupRepresentation, Representation_abstract): r""" - The vector space of all :class:`~sage.combinat.tabloid.Tabloids` of a - fixed shape with the natural symmetric group action. + The vector space of all tabloids of a fixed shape with the natural + symmetric group action. + + A *tabloid* is an :class:`OrderedSetPartition` whose underlying set + is `\{1, \ldots, n\}`. The symmetric group acts by permuting the + entries of the set. Hence, this is a representation of the symmetric + group defined over any field. + + EXAMPLES:: + + sage: SGA = SymmetricGroupAlgebra(GF(3), 5) + sage: TM = SGA.tabloid_module([2, 2, 1]) + sage: TM.dimension() + 30 + sage: TM.brauer_character() + (30, 6, 2, 0, 0) + sage: IM = TM.invariant_module() + sage: IM.dimension() + 1 + sage: IM.basis()[0].lift() == sum(TM.basis()) + True """ @staticmethod def __classcall_private__(cls, SGA, shape): @@ -566,12 +601,16 @@ def __init__(self, SGA, shape): sage: TM = SGA.tabloid_module([2,2,1]) sage: TestSuite(TM).run() """ - SymmetricGroupRepresentation.__init__(self, SGA) - from sage.combinat.tabloid import Tabloids + from sage.combinat.set_partition_ordered import OrderedSetPartitions + from sage.groups.perm_gps.permgroup_named import SymmetricGroup self._shape = shape + n = sum(shape) + self._symgp = SymmetricGroup(n) cat = ModulesWithBasis(SGA.base_ring()).FiniteDimensional() - return CombinatorialFreeModule.__init__(self, SGA.base_ring(), Tabloids(shape), - category=cat, prefix='T', bracket='') + tabloids = OrderedSetPartitions(n, shape) + CombinatorialFreeModule.__init__(self, SGA.base_ring(), tabloids, + category=cat, prefix='T', bracket='') + SymmetricGroupRepresentation.__init__(self, SGA) def _repr_(self): r""" @@ -585,6 +624,106 @@ def _repr_(self): """ return f"Tabloid module of {self._shape} over {self.base_ring()}" + def _ascii_art_term(self, T): + r""" + Return an ascii art representation of the term indexed by ``T``. + + EXAMPLES:: + + sage: SGA = SymmetricGroupAlgebra(QQ, 5) + sage: TM = SGA.tabloid_module([2,2,1]) + sage: ascii_art(TM.an_element()) # indirect doctest + 2*T + 2*T + 3*T + {1, 2} {1, 2} {1, 2} + {3, 4} {3, 5} {4, 5} + {5} {4} {3} + """ + # This is basically copied from CombinatorialFreeModule._ascii_art_term + from sage.typeset.ascii_art import AsciiArt, ascii_art + pref = AsciiArt([self.prefix()]) + tab = "\n".join("{" + ", ".join(str(val) for val in sorted(row)) + "}" for row in T) + if not tab: + tab = '-' + r = pref * (AsciiArt([" " * len(pref)]) + ascii_art(tab)) + r._baseline = r._h - 1 + return r + + def _unicode_art_term(self, T): + r""" + Return a unicode art representation of the term indexed by ``T``. + + EXAMPLES:: + + sage: SGA = SymmetricGroupAlgebra(QQ, 5) + sage: TM = SGA.tabloid_module([2,2,1]) + sage: unicode_art(TM.an_element()) # indirect doctest + 2*T + 2*T + 3*T + {1, 2} {1, 2} {1, 2} + {3, 4} {3, 5} {4, 5} + {5} {4} {3} + """ + from sage.typeset.unicode_art import unicode_art + r = unicode_art(repr(self._ascii_art_term(T))) + r._baseline = r._h - 1 + return r + + def _latex_term(self, T): + r""" + Return a latex representation of the term indexed by ``T``. + + EXAMPLES:: + + sage: SGA = SymmetricGroupAlgebra(QQ, 5) + sage: TM = SGA.tabloid_module([2,2,1]) + sage: latex(TM.an_element()) # indirect doctest + 2 T_{{\def\lr#1{\multicolumn{1}{@{\hspace{.6ex}}c@{\hspace{.6ex}}}{\raisebox{-.3ex}{$#1$}}} + \raisebox{-.6ex}{$\begin{array}[b]{*{2}c}\cline{1-2} + \lr{1}&\lr{2}\\\cline{1-2} + \lr{3}&\lr{4}\\\cline{1-2} + \lr{5}\\\cline{1-1} + \end{array}$} + }} + 2 T_{{\def\lr#1{\multicolumn{1}{@{\hspace{.6ex}}c@{\hspace{.6ex}}}{\raisebox{-.3ex}{$#1$}}} + \raisebox{-.6ex}{$\begin{array}[b]{*{2}c}\cline{1-2} + \lr{1}&\lr{2}\\\cline{1-2} + \lr{3}&\lr{5}\\\cline{1-2} + \lr{4}\\\cline{1-1} + \end{array}$} + }} + 3 T_{{\def\lr#1{\multicolumn{1}{@{\hspace{.6ex}}c@{\hspace{.6ex}}}{\raisebox{-.3ex}{$#1$}}} + \raisebox{-.6ex}{$\begin{array}[b]{*{2}c}\cline{1-2} + \lr{1}&\lr{2}\\\cline{1-2} + \lr{4}&\lr{5}\\\cline{1-2} + \lr{3}\\\cline{1-1} + \end{array}$} + }} + """ + if not T: + tab = "\\emptyset" + else: + from sage.combinat.output import tex_from_array + A = list(map(sorted, T)) + tab = str(tex_from_array(A)) + tab = tab.replace("|", "") + return f"{self.prefix()}_{{{tab}}}" + + def _symmetric_group_action(self, osp, g): + r""" + Return the action of the symmetric group element ``g`` on the + ordered set partition ``osp``. + + EXAMPLES:: + + sage: SGA = SymmetricGroupAlgebra(QQ, 5) + sage: TM = SGA.tabloid_module([2,2,1]) + sage: osp = TM._indices([[1,4],[3,5],[2]]) + sage: g = SGA.group().an_element(); g + [5, 1, 2, 3, 4] + sage: TM._symmetric_group_action(osp, g) + [{3, 5}, {2, 4}, {1}] + """ + P = self._indices + g = self._symgp(g) + return P.element_class(P, [[g(val) for val in row] for row in osp], check=False) + def specht_module(self): r""" Return the Specht submodule of ``self``. @@ -658,10 +797,11 @@ def _acted_upon_(self, x, self_on_left): if self_on_left: return None P = self.parent() - if x in P._SGA: + if x in P._semigroup_algebra: return P.sum(c * (perm * self) for perm, c in x.monomial_coefficients().items()) - if x in P._SGA.indices(): - return P.element_class(P, {T.symmetric_group_action(x): c for T, c in self._monomial_coefficients.items()}) + if x in P._semigroup_algebra.indices(): + return P.element_class(P, {P._symmetric_group_action(T, x): c + for T, c in self._monomial_coefficients.items()}) class SpechtModuleTableauxBasis(SpechtModule): @@ -690,15 +830,15 @@ def __init__(self, ambient): sage: TestSuite(SM).run() """ self._diagram = ambient._shape - SymmetricGroupRepresentation.__init__(self, ambient._SGA) + SymmetricGroupRepresentation.__init__(self, ambient._semigroup_algebra) ambient_basis = ambient.basis() tabloids = ambient_basis.keys() support_order = list(tabloids) def elt(T): - tab = tabloids.from_tableau(T) - return ambient.sum_of_terms((tab.symmetric_group_action(sigma), sigma.sign()) + tab = tabloids.element_class(tabloids, list(T), check=False) + return ambient.sum_of_terms((ambient._symmetric_group_action(tab, sigma), sigma.sign()) for sigma in T.column_stabilizer()) basis = Family({T: elt(T) @@ -902,7 +1042,7 @@ def __init__(self, specht_module): sage: U.dimension() 0 """ - SymmetricGroupRepresentation.__init__(self, specht_module._SGA) + SymmetricGroupRepresentation.__init__(self, specht_module._semigroup_algebra) p = specht_module.base_ring().characteristic() if p == 0: @@ -1010,7 +1150,7 @@ def __init__(self, specht_module): p = specht_module.base_ring().characteristic() if not self._diagram.is_regular(p): raise ValueError(f"the partition must be {p}-regular") - SymmetricGroupRepresentation.__init__(self, specht_module._SGA) + SymmetricGroupRepresentation.__init__(self, specht_module._semigroup_algebra) cat = specht_module.category() QuotientModuleWithBasis.__init__(self, specht_module.maximal_submodule(), cat, prefix='D') @@ -1218,7 +1358,8 @@ def bilinear_form(p1, p2): p1, p2 = p2, p1 return sum(c1 * p2.get(T1, 0) for T1, c1 in p1.items() if c1) - gram_matrix = [[bilinear_form(polytabloid(T1), polytabloid(T2)) for T1 in ST] for T2 in ST] + PT = {T: polytabloid(T) for T in ST} + gram_matrix = [[bilinear_form(PT[T1], PT[T2]) for T1 in ST] for T2 in ST] return matrix(base_ring, gram_matrix) diff --git a/src/sage/combinat/tabloid.py b/src/sage/combinat/tabloid.py deleted file mode 100644 index 976d5ad1e69..00000000000 --- a/src/sage/combinat/tabloid.py +++ /dev/null @@ -1,320 +0,0 @@ -r""" -Tabloids - -AUTHORS: - -- Sacha Goldman, Travis Scrimshaw (2023): initial version -""" - -from sage.combinat.partition import Partition -from sage.groups.perm_gps.permgroup_named import SymmetricGroup -from sage.arith.misc import multinomial -from sage.categories.finite_enumerated_sets import FiniteEnumeratedSets -from sage.structure.list_clone import ClonableArray -from sage.structure.unique_representation import UniqueRepresentation -from sage.structure.parent import Parent -from sage.typeset.ascii_art import ascii_art -from sage.rings.integer_ring import ZZ - -class Tabloid(ClonableArray): - r""" - A tabloid. - - A tabloid is an ordered set partition of `\{1, 2, \ldots, n\}` such that - the sizes of the sets are weakly decreasing (that is, they form an - integer partition). - """ - def __init__(self, parent, tableaux, check=True): - r""" - Initialize ``self``. - - EXAMPLES:: - - sage: T = Tabloids([5, 3, 1]) - sage: A = T([[1,2,3,9,7], [4,5,8], [6]]) - sage: TestSuite(A).run() - """ - super().__init__(parent, map(frozenset, tableaux), check=check) - - def check(self): - r""" - Check that ``self`` is a valid tabloid. - - EXAMPLES:: - - sage: T = Tabloids([3, 2, 1]) - sage: A = T([[1,2,3], [4,5], [6]]) - sage: A.check() - sage: T([[1,2,3,4], [5], [6]]) # indirect doctest - Traceback (most recent call last): - ... - ValueError: incorrect shape - sage: T([[1,2,3], [4,'a'], [6]]) # indirect doctest - Traceback (most recent call last): - ... - ValueError: invalid entry in the row frozenset({...}) - sage: T([[1,2,3], [4,3], [6]]) # indirect doctest - Traceback (most recent call last): - ... - ValueError: invalid entry in the row frozenset({3, 4}) - """ - la = self.parent()._shape - if [len(row) for row in self] != la: - raise ValueError("incorrect shape") - X = set(range(1, sum(la)+1)) - for row in self: - ell = len(X) - X -= row - if len(X) != ell - len(row): - raise ValueError(f"invalid entry in the row {row}") - assert not X - - def _repr_(self): - r""" - Return a string representation of ``self``. - - EXAMPLES:: - - sage: T = Tabloids([5, 3, 1]) - sage: T([[1,2,3,9,7], [4,5,8], [6]]) - [{1, 2, 3, 7, 9}, {4, 5, 8}, {6}] - """ - ret = "[" - ret += ", ".join("{" + ", ".join(str(val) for val in sorted(row)) + "}" for row in self) - return ret + "]" - - def _ascii_art_(self): - r""" - Return a string representation of ``self``. - - EXAMPLES:: - - sage: T = Tabloids([5, 3, 1]) - sage: A = T([[1,2,3,9,7], [4,5,8], [6]]) - sage: ascii_art(A) - {1, 2, 3, 7, 9} - {4, 5, 8} - {6} - """ - ret = "\n".join("{" + ", ".join(str(val) for val in sorted(row)) + "}" for row in self) - return ascii_art(ret) - - def _latex_(self): - r""" - Return a LaTeX representation of ``self`` as a Young diagram. - - EXAMPLES:: - - sage: T = Tabloids([5, 3, 1]) - sage: A = T([[1,2,3,9,7], [4,5,8], [6]]) - sage: latex(A) - {\def\lr#1{\multicolumn{1}{@{\hspace{.6ex}}c@{\hspace{.6ex}}}{\raisebox{-.3ex}{$#1$}}} - \raisebox{-.6ex}{$\begin{array}[b]{*{5}c}\cline{1-5} - \lr{1}&\lr{2}&\lr{3}&\lr{7}&\lr{9}\\\cline{1-5} - \lr{4}&\lr{5}&\lr{8}\\\cline{1-3} - \lr{6}\\\cline{1-1} - \end{array}$} - } - - sage: T = Tabloids([]) - sage: latex(T([])) - {\emptyset} - """ - if not self: - return "{\\emptyset}" - from sage.combinat.output import tex_from_array - A = list(map(sorted, self)) - ret = str(tex_from_array(A)) - return ret.replace("|", "") - - def symmetric_group_action(self, permutation): - r""" - Return the left action of ``permutation`` on ``self``. - - EXAMPLES:: - - sage: T = Tabloids([3, 2, 1]) - sage: A = T([[1,2,6], [3,5], [4]]); ascii_art(A) - {1, 2, 6} - {3, 5} - {4} - sage: sigma = Permutation([1,6,2,3,5,4]) - sage: ascii_art(A.symmetric_group_action(sigma)) - {1, 4, 6} - {2, 5} - {3} - sage: sigma = SymmetricGroup(6)([(1,2),(3,4,5)]) - sage: ascii_art(A.symmetric_group_action(sigma)) - {1, 2, 6} - {3, 4} - {5} - """ - P = self.parent() - S = SymmetricGroup(sum(P._shape)) - permutation = S(permutation) - return P.element_class(P, [[permutation(val) for val in row] for row in self]) - - def _acted_upon_(self, sigma, self_on_left=True): - r""" - Return the action of ``sigma`` on ``self``. - - EXAMPLES:: - - sage: T = Tabloids([2, 2, 1]) - sage: A = T([[1,2], [3,5], [4]]); ascii_art(A) - {1, 2} - {3, 5} - {4} - sage: ascii_art(Permutation([1,2,3,5,4]) * A) - {1, 2} - {3, 4} - {5} - sage: sigma = SymmetricGroup(5)([(1,2),(3,4,5)]) - sage: ascii_art(sigma * A) - {1, 2} - {3, 4} - {5} - """ - P = self.parent() - S = SymmetricGroup(sum(P._shape)) - if sigma in S: - return self.symmetric_group_action(sigma) - return super()._acted_upon_(sigma, self_on_left) - - -class Tabloids(UniqueRepresentation, Parent): - r""" - Tabloids of a fixed shape. - """ - @staticmethod - def __classcall_private__(cls, partition): - r""" - Normalize input to ensure a unique representation. - - EXAMPLES:: - - sage: T1 = Tabloids([2, 1]) - sage: T2 = Tabloids((2, 1)) - sage: T3 = Tabloids(Partition([2, 1])) - sage: T1 is T2 and T2 is T3 - True - """ - partition = Partition(partition) - return super().__classcall__(cls, partition) - - def __init__(self, partition): - r""" - Initialize ``self``. - - EXAMPLES:: - - sage: T = Tabloids([2, 1]) - sage: TestSuite(T).run() - - sage: T = Tabloids([]) - sage: TestSuite(T).run() - - sage: T = Tabloids([1]) - sage: TestSuite(T).run() - - sage: T = Tabloids([10]) - sage: TestSuite(T).run() - - sage: T = Tabloids([1, 1, 1, 1]) - sage: TestSuite(T).run() - """ - self._shape = partition - Parent.__init__(self, category=FiniteEnumeratedSets()) - - def __iter__(self): - r""" - Iterate over ``self``. - - EXAMPLES:: - - sage: T = Tabloids([2,1]) - sage: list(T) - [[{1, 2}, {3}], [{1, 3}, {2}], [{2, 3}, {1}]] - - sage: T = Tabloids([]) - sage: list(T) - [[]] - sage: T = Tabloids([1]) - sage: list(T) - [[{1}]] - sage: T = Tabloids([3]) - sage: list(T) - [[{1, 2, 3}]] - sage: T = Tabloids([1,1,1]) - sage: list(T) - [[{1}, {2}, {3}], - [{3}, {1}, {2}], - [{2}, {3}, {1}], - [{1}, {3}, {2}], - [{3}, {2}, {1}], - [{2}, {1}, {3}]] - """ - n = sum(self._shape) - # trivial cases - if n == 0: - yield self.element_class(self, [], check=False) - return - if n == 1: - yield self.element_class(self, [[1]], check=False) - return - - ell = len(self._shape) - if ell == n: # single column trivial case - for sigma in SymmetricGroup(n): - yield self.element_class(self, [[val] for val in sigma.tuple()], check=False) - return - if ell == 1: # single row trivial case - yield self.element_class(self, [range(1,n+1)], check=False) - return - - pos = [-1] * n - i = 0 - cur = [[] for _ in range(ell)] - while True: - if i == n: - yield self.element_class(self, cur, check=False) - i -= 1 - cur[pos[i]].pop() - pos[i] += 1 - while pos[i] < ell and self._shape[pos[i]] - len(cur[pos[i]]) == 0: - pos[i] += 1 - if pos[i] == ell: # backtrack - pos[i] = -1 - i -= 1 - if i < 0: - break - cur[pos[i]].pop() - continue - cur[pos[i]].append(i+1) - i += 1 - - def from_tableau(self, T): - r""" - Construct a tabloid from the tableau ``T``. - - EXAMPLES:: - - sage: T = Tabloids([3, 1]) - sage: T.from_tableau([[1,3,4], [2]]) - [{1, 3, 4}, {2}] - """ - return self.element_class(self, T) - - def cardinality(self): - r""" - Return the cardinality of ``self``. - - EXAMPLES:: - - sage: T = Tabloids([3,2,1]) - sage: T.cardinality() - 60 - """ - return ZZ(multinomial(list(self._shape))) - - Element = Tabloid From af4eb55068ffad8049633e9a92d4ce5e98a168af Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Sat, 2 Mar 2024 04:46:11 +0900 Subject: [PATCH 212/278] Some details from Chapoton's review. --- src/sage/combinat/composition.py | 2 ++ src/sage/combinat/partition.py | 1 - src/sage/combinat/permutation.py | 2 +- src/sage/combinat/symmetric_group_algebra.py | 5 ++--- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/sage/combinat/composition.py b/src/sage/combinat/composition.py index 5a8536dd300..0be0ba09074 100644 --- a/src/sage/combinat/composition.py +++ b/src/sage/combinat/composition.py @@ -1755,6 +1755,8 @@ def _element_constructor_(self, lst) -> Composition: sage: P = Compositions() sage: P([3,3,1]) # indirect doctest [3, 3, 1] + sage: P(Partition([5,2,1])) + [5, 2, 1] """ if isinstance(lst, (Composition, Partition)): lst = list(lst) diff --git a/src/sage/combinat/partition.py b/src/sage/combinat/partition.py index a52596b20ea..108b665d465 100644 --- a/src/sage/combinat/partition.py +++ b/src/sage/combinat/partition.py @@ -5496,7 +5496,6 @@ def specht_module(self, base_ring=None): sage: SM = Partition([2,2,1]).specht_module(QQ); SM Specht module of [2, 2, 1] over Rational Field - sage: s = SymmetricFunctions(QQ).s() sage: SM.frobenius_image() # needs sage.modules s[2, 2, 1] """ diff --git a/src/sage/combinat/permutation.py b/src/sage/combinat/permutation.py index 7ef23ca65c4..5e01de6f175 100644 --- a/src/sage/combinat/permutation.py +++ b/src/sage/combinat/permutation.py @@ -7343,7 +7343,7 @@ def cardinality(self): return factorial(self.n) @cached_method - def gens(self): + def gens(self) -> tuple: r""" Return a set of generators for ``self`` as a group. diff --git a/src/sage/combinat/symmetric_group_algebra.py b/src/sage/combinat/symmetric_group_algebra.py index 8fb49d3396c..d29ff37cd02 100644 --- a/src/sage/combinat/symmetric_group_algebra.py +++ b/src/sage/combinat/symmetric_group_algebra.py @@ -1573,14 +1573,13 @@ def specht_module(self, D): sage: SM = SGA.specht_module(Partition([3,1,1])) sage: SM Specht module of [3, 1, 1] over Rational Field - sage: s = SymmetricFunctions(QQ).s() - sage: s(SM.frobenius_image()) + sage: SM.frobenius_image() s[3, 1, 1] sage: SM = SGA.specht_module([(1,1),(1,3),(2,2),(3,1),(3,2)]) sage: SM Specht module of [(1, 1), (1, 3), (2, 2), (3, 1), (3, 2)] over Rational Field - sage: s(SM.frobenius_image()) + sage: SM.frobenius_image() s[2, 2, 1] + s[3, 1, 1] + s[3, 2] """ from sage.combinat.specht_module import SpechtModule From 2c7f4cd9c1f8cf02d07953aa3a60670117469425 Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Sun, 17 Dec 2023 19:52:32 +0900 Subject: [PATCH 213/278] Implementing the quantum oscillator algebra. --- src/doc/en/reference/algebras/index.rst | 1 + src/doc/en/reference/references/index.rst | 5 + src/sage/algebras/catalog.py | 3 + src/sage/algebras/quantum_oscillator.py | 622 ++++++++++++++++++++++ 4 files changed, 631 insertions(+) create mode 100644 src/sage/algebras/quantum_oscillator.py diff --git a/src/doc/en/reference/algebras/index.rst b/src/doc/en/reference/algebras/index.rst index 90c9a66ab87..6f50276a5ed 100644 --- a/src/doc/en/reference/algebras/index.rst +++ b/src/doc/en/reference/algebras/index.rst @@ -61,6 +61,7 @@ Named associative algebras sage/algebras/quantum_clifford sage/algebras/quantum_groups/quantum_group_gap sage/algebras/quantum_matrix_coordinate_algebra + sage/algebras/quantum_oscillator sage/algebras/quatalg/quaternion_algebra sage/algebras/rational_cherednik_algebra sage/algebras/schur_algebra diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index 09b78514e1c..8a3100a9c57 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -4060,6 +4060,11 @@ REFERENCES: study of the subgroups of the modular group", American Journal of Mathematics 113 (1991), no 6, 1053-1133 +.. [Kuniba2022] Atsuo Kuniba, + *Quantum Groups in Three-Dimensional Integrability*. + Theoretical an Mathematical Phyiscs, Springer. 2022. + :doi:`10.1007/978-981-19-3262-5`. + .. [Kur2008] Chris Kurth, "K Farey package for Sage", http://wayback.archive-it.org/855/20100510123900/http://www.public.iastate.edu/~kurthc/research/index.html diff --git a/src/sage/algebras/catalog.py b/src/sage/algebras/catalog.py index ec48a6debed..afa5db12e8e 100644 --- a/src/sage/algebras/catalog.py +++ b/src/sage/algebras/catalog.py @@ -59,6 +59,8 @@ ` - :class:`algebras.QuantumMatrixCoordinate ` +- :class:`algebras.QuantumOscillator + ` - :class:`algebras.QSym ` - :class:`algebras.Partition ` - :class:`algebras.PlanarPartition ` @@ -128,6 +130,7 @@ lazy_import('sage.combinat.ncsf_qsym.qsym', 'QuasiSymmetricFunctions', 'QSym') lazy_import('sage.combinat.grossman_larson_algebras', 'GrossmanLarsonAlgebra', 'GrossmanLarson') lazy_import('sage.algebras.quantum_clifford', 'QuantumCliffordAlgebra', 'QuantumClifford') +lazy_import('sage.algebras.quantum_oscillator', 'QuantumOscillatorAlgebra', 'QuantumOscillator') lazy_import('sage.algebras.quantum_matrix_coordinate_algebra', 'QuantumMatrixCoordinateAlgebra', 'QuantumMatrixCoordinate') lazy_import('sage.algebras.quantum_matrix_coordinate_algebra', 'QuantumGL') diff --git a/src/sage/algebras/quantum_oscillator.py b/src/sage/algebras/quantum_oscillator.py new file mode 100644 index 00000000000..67b6947c009 --- /dev/null +++ b/src/sage/algebras/quantum_oscillator.py @@ -0,0 +1,622 @@ +r""" +Quantum Oscillator Algebras + +AUTHORS: + +- Travis Scrimshaw (2023-12): initial version +""" + +#***************************************************************************** +# Copyright (C) 2023 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. +# https://www.gnu.org/licenses/ +#***************************************************************************** + +from sage.misc.cachefunc import cached_method +from sage.misc.misc_c import prod +from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing +from sage.rings.integer_ring import ZZ +from sage.categories.algebras import Algebras +from sage.combinat.free_module import CombinatorialFreeModule +from sage.categories.cartesian_product import cartesian_product +from sage.sets.family import Family +from sage.sets.non_negative_integers import NonNegativeIntegers + +class QuantumOscillatorAlgebra(CombinatorialFreeModule): + r""" + The quantum oscillator algebra. + + Let `R` be a commutative algebra and `q \in R` be a unit. + The *quantum oscillator algebra*, or `q`-oscillator algebra, + is the unital associative `R`-algebra with generators `a^+`, + `a^-` and `k^{\pm 1}` satisfying the following relations: + + .. MATH:: + + k a^{\pm} = q^{\pm 1} a^{\pm} k, \qquad + a^- a^+ = 1 - q^2 k^2, \qquad + a^+ a^- = 1 - k^2. + + INPUT: + + - ``q`` -- (optional) the parameter `q` + - ``R`` -- (default: `\QQ(q)`) the base ring that contains ``q`` + + EXAMPLES: + + We construct the algebra and perform some basic computations:: + + sage: O = algebras.QuantumOscillator() + sage: ap, am, k, ki = O.algebra_generators() + sage: q = O.q() + sage: k^-3 * ap * ki * am^2 * k - q^3 * ap * k^3 + q^5*a-*k^-3 - q^3*a-*k^-1 - q^3*a+*k^3 + + We construct representations of the type `A_1` quantum coordinate ring + using the quantum oscillator algebra and verify the quantum determinant:: + + sage: pi = matrix([[am, k], [-q*k, ap]]); pi + [ a- k] + [-q*k a+] + sage: pi[0,0] * pi[1,1] - q * pi[0,1] * pi[1,0] + 1 + + Next, we use this to build representations for type `A_2`:: + + sage: def quantum_det(M): + ....: n = M.nrows() + ....: return sum((-q)**sigma.length() + ....: * prod(M[i,sigma[i]-1] for i in range(n)) + ....: for sigma in Permutations(n)) + sage: def build_repr(wd, gens): + ....: n = gens[0].nrows() + ....: ret = gens[wd[0]-1] + ....: for ind in wd[1:]: + ....: g = gens[ind-1] + ....: temp = [[None]*n for _ in range(n)] + ....: for i in range(n): + ....: for j in range(n): + ....: temp[i][j] = sum(tensor([ret[i,k], g[k,j]]) for k in range(n)) + ....: ret = matrix(temp) + ....: return ret + sage: pi1 = matrix.block_diagonal(pi, matrix.identity(1)); pi1 + [ a- k| 0] + [-q*k a+| 0] + [---------+----] + [ 0 0| 1] + sage: pi2 = matrix.block_diagonal(matrix.identity(1), pi); pi2 + [ 1| 0 0] + [----+---------] + [ 0| a- k] + [ 0|-q*k a+] + sage: quantum_det(pi1) == 1 + True + sage: quantum_det(pi2) == 1 + True + sage: pi12 = build_repr([1,2], [pi1, pi2]); pi12 + [ a- # 1 k # a- k # k] + [-q*k # 1 a+ # a- a+ # k] + [ 0 -q*1 # k 1 # a+] + sage: quantum_det(pi12) + 1 # 1 + sage: pi121 = build_repr([1,2,1], [pi1, pi2]); pi121 + [ a- # 1 # a- - q*k # a- # k a- # 1 # k + k # a- # a+ k # k # 1] + [-q*k # 1 # a- - q*a+ # a- # k -q*k # 1 # k + a+ # a- # a+ a+ # k # 1] + [ q^2*1 # k # k -q*1 # k # a+ 1 # a+ # 1] + sage: quantum_det(pi121) + 1 # 1 # 1 + sage: pi212 = build_repr([2,1,2], [pi1, pi2]); pi212 + [ 1 # a- # 1 1 # k # a- 1 # k # k] + [ -q*a- # k # 1 a- # a+ # a- - q*k # 1 # k a- # a+ # k + k # 1 # a+] + [ q^2*k # k # 1 -q*k # a+ # a- - q*a+ # 1 # k -q*k # a+ # k + a+ # 1 # a+] + sage: quantum_det(pi212) + 1 # 1 # 1 + + REFERENCES: + + - [Kuniba2022]_ Section 3.2 + """ + @staticmethod + def __classcall_private__(cls, q=None, R=None): + r""" + Standardize input to ensure a unique representation. + + TESTS:: + + sage: O1 = algebras.QuantumOscillator() + sage: q = PolynomialRing(ZZ, 'q').fraction_field().gen() + sage: O2 = algebras.QuantumOscillator(q=q) + sage: O3 = algebras.QuantumOscillator(q, q.parent()) + sage: O1 is O2 and O2 is O3 + True + """ + if q is None: + q = PolynomialRing(ZZ, 'q').fraction_field().gen() + if R is None: + R = q.parent() + q = R(q) + + return super().__classcall__(cls, q, R) + + def __init__(self, q, R): + r""" + Initialize ``self``. + + EXAMPLES:: + + sage: O = algebras.QuantumOscillator() + sage: TestSuite(O).run() + """ + self._q = q + self._k_poly = PolynomialRing(R, 'k') + indices = cartesian_product([ZZ, ZZ]) + + cat = Algebras(R).WithBasis() + CombinatorialFreeModule.__init__(self, R, indices, category=cat) + self._assign_names(('ap', 'am', 'k', 'ki')) + + def _repr_(self): + r""" + Return a string representation of ``self``. + + EXAMPLES:: + + sage: algebras.QuantumOscillator() + Quantum oscillator algebra with q=q over + Fraction Field of Univariate Polynomial Ring in q over Integer Ring + """ + return "Quantum oscillator algebra with q={} over {}".format( + self._q, self.base_ring()) + + def _latex_(self): + r""" + Return a latex representation of ``self``. + + EXAMPLES:: + + sage: O = algebras.QuantumOscillator() + sage: latex(O) + \operatorname{Osc}_{q} + """ + return "\\operatorname{Osc}_{%s}" % self._q + + def q(self): + r""" + Return the `q` of ``self``. + + EXAMPLES:: + + sage: O = algebras.QuantumOscillator() + sage: O.q() + q + sage: O = algebras.QuantumOscillator(q=QQ(-5)) + sage: O.q() + -5 + """ + return self._q + + @cached_method + def algebra_generators(self): + r""" + Return the algebra generators of ``self``. + + EXAMPLES:: + + sage: O = algebras.QuantumOscillator() + sage: O.algebra_generators() + Finite family {'am': a-, 'ap': a+, 'k': k, 'ki': k^-1} + """ + d = {'ap': self.monomial((ZZ.one(), ZZ.zero())), + 'am': self.monomial((-ZZ.one(), ZZ.zero())), + 'k': self.monomial((ZZ.zero(), ZZ.one())), + 'ki': self.monomial((ZZ.zero(), -ZZ.one()))} + return Family(d) + + @cached_method + def gens(self): + r""" + Return the generators of ``self``. + + EXAMPLES:: + + sage: O = algebras.QuantumOscillator() + sage: O.gens() + (a+, a-, k, k^-1) + """ + return tuple(self.algebra_generators()) + + @cached_method + def one_basis(self): + r""" + Return the index of the basis element of `1`. + + EXAMPLES:: + + sage: O = algebras.QuantumOscillator() + sage: O.one_basis() + (0, 0) + """ + return (ZZ.zero(), ZZ.zero()) + + def some_elements(self): + r""" + Return some elements of ``self``. + + EXAMPLES:: + + sage: O = algebras.QuantumOscillator() + sage: O.some_elements() + (a+, a-, k, k^-1, 1, a+^3, a-^4, k^2, k^-5, a+*k, + a-^4*k^-3, 1 + 3*k + 2*a+ + a+*k) + """ + ap, am, k, ki = self.gens() + return (ap, am, k, ki, self.one(), + ap**3, am**4, k**2, ki**5, ap*k, am**4*ki**3, + self.an_element()) + + def fock_space_representation(self): + r""" + Return the Fock sapce representation of ``self``. + + .. SEEALSO:: + + :class:`~sage.algebras.quantum_oscillator.FockSpaceRepresentation` + + EXAMPLES:: + + sage: O = algebras.QuantumOscillator() + sage: O.fock_space_representation() + Fock space representation of Quantum oscillator algebra with q=q + over Fraction Field of Univariate Polynomial Ring in q over Integer Ring + """ + return FockSpaceRepresentation(self) + + def _repr_term(self, m): + r""" + Return a string representation of the basis element indexed by ``m``. + + EXAMPLES:: + + sage: O = algebras.QuantumOscillator() + sage: O._repr_term((1, 3)) + 'a+*k^3' + sage: O._repr_term((-1, 1)) + 'a-*k' + sage: O._repr_term((5, 0)) + 'a+^5' + sage: O._repr_term((-4, -2)) + 'a-^4*k^-2' + sage: O._repr_term((0, -4)) + 'k^-4' + sage: O._repr_term((0, 0)) + '1' + + sage: O(5) + 5 + """ + a, k = m + + astr = '' + if a == 1: + astr = 'a+' + elif a > 1: + astr = 'a+^{}'.format(a) + elif a == -1: + astr = 'a-' + elif a < -1: + astr = 'a-^{}'.format(-a) + + kstr = '' + if k == 1: + kstr = 'k' + elif k != 0: + kstr = 'k^{}'.format(k) + + if astr: + if kstr: + return astr + '*' + kstr + return astr + if kstr: + return kstr + return '1' + + def _latex_term(self, m): + r""" + Return a latex representation for the basis element indexed by ``m``. + + EXAMPLES:: + + sage: O = algebras.QuantumOscillator() + sage: O._latex_term((1, 3)) + 'a^+ k^{3}' + sage: O._latex_term((-1, 1)) + 'a^- k' + sage: O._latex_term((5, 0)) + '(a^+)^{5}' + sage: O._latex_term((-4, -2)) + '(a^-)^{4} k^{-2}' + sage: O._latex_term((0, -4)) + 'k^{-4}' + sage: O._latex_term((0, 0)) + '1' + + sage: latex(O(5)) + 5 + """ + a, k = m + + astr = '' + if a == 1: + astr = 'a^+' + elif a > 1: + astr = '(a^+)^{{{}}}'.format(a) + elif a == -1: + astr = 'a^-' + elif a < -1: + astr = '(a^-)^{{{}}}'.format(-a) + + kstr = '' + if k == 1: + kstr = 'k' + elif k != 0: + kstr = 'k^{{{}}}'.format(k) + + if astr: + if kstr: + return astr + ' ' + kstr + return astr + if kstr: + return kstr + return '1' + + @cached_method + def product_on_basis(self, ml, mr): + r""" + Return the product of the basis elements indexed by ``ml`` and ``mr``. + + EXAMPLES:: + + sage: O = algebras.QuantumOscillator() + sage: ap, am, k, ki = O.algebra_generators() + sage: O.product_on_basis((-2, 3), (-4, 5)) + 1/q^12*a-^6*k^8 + sage: O.product_on_basis((2, 3), (4, -5)) + q^12*a+^6*k^-2 + sage: O.product_on_basis((2, 3), (0, -3)) + a+^2 + sage: k^5 * ki^10 + k^-5 + sage: k^10 * ki^5 + k^5 + sage: ap^3 * k^5 + a+^3*k^5 + sage: am^3 * k^5 + a-^3*k^5 + sage: k^5 * ap^3 + q^15*a+^3*k^5 + sage: k^5 * am^3 + 1/q^15*a-^3*k^5 + sage: ki^5 * ap^3 + 1/q^15*a+^3*k^-5 + sage: ki^5 * am^3 + q^15*a-^3*k^-5 + sage: ap * am + 1 - k^2 + sage: am * ap + 1 - q^2*k^2 + + sage: (ap + am + k + ki)^2 + a-^2 + (q+1)*a-*k^-1 + ((q+1)/q)*a-*k + k^-2 + 4 - q^2*k^2 + + ((q+1)/q)*a+*k^-1 + (q+1)*a+*k + a+^2 + + sage: (ap)^3 * (am)^5 + a-^2 + ((-q^4-q^2-1)/q^8)*a-^2*k^2 + ((q^4+q^2+1)/q^14)*a-^2*k^4 - 1/q^18*a-^2*k^6 + sage: (ap)^5 * (am)^3 + a+^2 + ((-q^4-q^2-1)/q^4)*a+^2*k^2 + ((q^4+q^2+1)/q^6)*a+^2*k^4 - 1/q^6*a+^2*k^6 + sage: (am)^3 * (ap)^5 + a+^2 + (-q^10-q^8-q^6)*a+^2*k^2 + (q^18+q^16+q^14)*a+^2*k^4 - q^24*a+^2*k^6 + sage: (am)^5 * (ap)^3 + a-^2 + (-q^6-q^4-q^2)*a-^2*k^2 + (q^10+q^8+q^6)*a-^2*k^4 - q^12*a-^2*k^6 + """ + q = self._q + k = self._k_poly.gen() + al, kl = ml + ar, kr = mr + coeff = q ** (kl * ar) + if (al <= 0 and ar <= 0) or (al >= 0 and ar >= 0): + return self.element_class(self, {(al + ar, kl + kr): coeff}) + # now al and ar have different signs + if al < 0: # a^- * a^+ case + kp = self._k_poly.prod(1 - q**(2*(ar-i)) * k**2 for i in range(min(-al,ar))) + else: # a^+ * a^- case + kp = self._k_poly.prod(1 - q**(2*(ar+i)) * k**2 for i in range(1,min(al,-ar)+1)) + a = al + ar + return self.element_class(self, {(a, kl+kr+i): c * coeff for i, c in enumerate(kp) if c}) + + class Element(CombinatorialFreeModule.Element): + def inverse(self): + r""" + Return the inverse if ``self`` is a basis element. + + EXAMPLES:: + + sage: O = algebras.QuantumOscillator() + sage: ap, am, k, ki = O.algebra_generators() + sage: k.inverse() + k^-1 + sage: ~k^5 + k^-5 + sage: ~ki^2 + k^2 + sage: O.zero().inverse() + Traceback (most recent call last): + ... + ZeroDivisionError + sage: ~ap + Traceback (most recent call last): + ... + NotImplementedError: only implemented for monomials in k + sage: ~(k + ki) + Traceback (most recent call last): + ... + NotImplementedError: only implemented for monomials in k + """ + if not self: + raise ZeroDivisionError + if len(self) != 1 or self.leading_support()[0] != 0: + raise NotImplementedError("only implemented for monomials in k") + + ((a, k), coeff), = list(self._monomial_coefficients.items()) + O = self.parent() + return O.element_class(O, {(a, -k): coeff.inverse_of_unit()}) + + __invert__ = inverse + + +class FockSpaceRepresentation(CombinatorialFreeModule): + """ + The unique Fock space representation of the + :class:`~sage.algebras.quantum_oscillator.QuantumOscillatorAlgebra`. + """ + def __init__(self, O): + """ + Initialize ``self``. + + EXAMPLES:: + + sage: O = algebras.QuantumOscillator() + sage: F = O.fock_space_representation() + sage: TestSuite(F).run() + """ + self._O = O + ind = NonNegativeIntegers() + CombinatorialFreeModule.__init__(self, O.base_ring(), ind, prefix='', bracket=['|', '>'], + latex_bracket=[r'\lvert', r'\rangle']) + + def _test_representation(self, **options): + """ + Test that ``self`` is a representation of the quantum + oscillator algebra. + + EXAMPLES:: + + sage: O = algebras.QuantumOscillator(q=GF(7)(3)) + sage: F = O.fock_space_representation() + sage: F._test_representation() + """ + tester = self._tester(**options) + S = self._O.some_elements() + num_trials = 0 + from itertools import product + for a, b in product(S, repeat=2): + for elt in tester.some_elements(): + num_trials += 1 + if num_trials > tester._max_runs: + return + tester.assertEqual((a*b)*elt, a*(b*elt)) + + def _repr_(self): + """ + Return a string representation of ``self``. + + EXAMPLES:: + + sage: O = algebras.QuantumOscillator(q=GF(5)(2)) + sage: O.fock_space_representation() + Fock space representation of Quantum oscillator algebra + with q=2 over Finite Field of size 5 + """ + return "Fock space representation of {}".format(self._O) + + def _latex_(self): + r""" + Return a latex representation of ``self``. + + EXAMPLES:: + + sage: O = algebras.QuantumOscillator() + sage: F = O.fock_space_representation() + sage: latex(F) + \mathfrak{F}_{q} + """ + return r"\mathfrak{{F}}_{{{}}}".format(self._O._q) + + def vacuum(self): + r""" + Return the vacuum element `|0\rangle` of ``self``. + + EXAMPLES:: + + sage: O = algebras.QuantumOscillator() + sage: F = O.fock_space_representation() + sage: F.vacuum() + |0> + """ + return self.basis()[0] + + def some_elements(self): + """ + Return some elements of ``self``. + + EXAMPLES:: + + sage: O = algebras.QuantumOscillator() + sage: F = O.fock_space_representation() + sage: F.some_elements() + (|0>, |1>, |52>, |0> + 2*|1> + 3*|2> + |42>) + """ + B = self.basis() + return (B[0], B[1], B[52], self.an_element()) + + class Element(CombinatorialFreeModule.Element): + def _acted_upon_(self, scalar, self_on_left=True): + """ + Return the action of ``scalar`` on ``self``. + + EXAMPLES:: + + sage: O = algebras.QuantumOscillator() + sage: ap, am, k, ki = O.gens() + sage: F = O.fock_space_representation() + sage: B = F.basis() + sage: [ap * B[i] for i in range(3)] + [|1>, |2>, |3>] + sage: [am * B[i] for i in range(3)] + [0, (-q^2+1)*|0>, (-q^4+1)*|1>] + sage: [k * B[i] for i in range(3)] + [|0>, q*|1>, q^2*|2>] + sage: [ki * B[i] for i in range(3)] + [|0>, 1/q*|1>, 1/q^2*|2>] + sage: (am)^3 * B[5] + (-q^24+q^18+q^16+q^14-q^10-q^8-q^6+1)*|2> + sage: (7*k^3 + am) * (B[0] + B[1] + B[2]) + (-q^2+8)*|0> + (-q^4+7*q^3+1)*|1> + 7*q^6*|2> + sage: 5 * (B[2] + B[3]) + 5*|2> + 5*|3> + """ + # Check for scalars first + ret = super()._acted_upon_(scalar, self_on_left) + if ret is not None: + return ret + P = self.parent() + if self_on_left or scalar not in P._O: # needs to be a left Osc-action + return None + scalar = P._O(scalar) + q = P._O._q + + ret = [] + for om, oc in scalar: + a, k = om + for fm, fc in self: + if fm < -a: # the result will be 0 + continue + c = q ** (fm*k) + if a < 0: + c *= prod(1 - q**(2*(fm-i)) for i in range(-a)) + if c: + ret.append((fm+a, oc * fc * c)) + return P.sum_of_terms(ret) From 4b5848a5120b9508d68ac594da69d16850341d5f Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Sat, 30 Dec 2023 18:36:33 +0900 Subject: [PATCH 214/278] Allow returning all Casimir elements of a fixed order. --- .../lie_algebras/poincare_birkhoff_witt.py | 4 +-- ...ite_dimensional_lie_algebras_with_basis.py | 36 +++++++++++++------ 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/src/sage/algebras/lie_algebras/poincare_birkhoff_witt.py b/src/sage/algebras/lie_algebras/poincare_birkhoff_witt.py index 38334bfe1f8..e893f7caa87 100644 --- a/src/sage/algebras/lie_algebras/poincare_birkhoff_witt.py +++ b/src/sage/algebras/lie_algebras/poincare_birkhoff_witt.py @@ -496,7 +496,7 @@ def degree_on_basis(self, m): """ return m.length() - def casimir_element(self, order=2): + def casimir_element(self, order=2, *args, **kwds): r""" Return the Casimir element of ``self``. @@ -534,7 +534,7 @@ def casimir_element(self, order=2): from sage.rings.infinity import Infinity if self._g.dimension() == Infinity: raise ValueError("the Lie algebra must be finite dimensional") - return self._g.casimir_element(order=order, UEA=self) + return self._g.casimir_element(order=order, UEA=self, *args, **kwds) class Element(CombinatorialFreeModule.Element): def _act_on_(self, x, self_on_left): diff --git a/src/sage/categories/finite_dimensional_lie_algebras_with_basis.py b/src/sage/categories/finite_dimensional_lie_algebras_with_basis.py index ee3c14948aa..d27f91f9083 100644 --- a/src/sage/categories/finite_dimensional_lie_algebras_with_basis.py +++ b/src/sage/categories/finite_dimensional_lie_algebras_with_basis.py @@ -1745,10 +1745,10 @@ def universal_commutative_algebra(self): R = P[0].parent() return R.quotient(P) - def casimir_element(self, order=2, UEA=None, force_generic=False): + def casimir_element(self, order=2, UEA=None, force_generic=False, basis=False): r""" - Return the Casimir element in the universal enveloping algebra - of ``self``. + Return a Casimir element of order ``order`` in the universal + enveloping algebra of ``self``. A *Casimir element* of order `k` is a distinguished basis element for the center of `U(\mathfrak{g})` of homogeneous degree `k` @@ -1760,11 +1760,13 @@ def casimir_element(self, order=2, UEA=None, force_generic=False): INPUT: - ``order`` -- (default: ``2``) the order of the Casimir element - - ``UEA`` -- (optional) the universal enveloping algebra to - return the result in + - ``UEA`` -- (optional) the universal enveloping algebra + implementation to return the result in - ``force_generic`` -- (default: ``False``) if ``True`` for the quadratic order, then this uses the default algorithm; otherwise this is ignored + - ``basis`` -- (default: ``False``) if ``True``, this returns a + basis of all Casimir elements of order ``order`` as a list ALGORITHM: @@ -1832,6 +1834,13 @@ def casimir_element(self, order=2, UEA=None, force_generic=False): sage: L.casimir_element() 0 + sage: # needs sage.combinat sage.modules + sage: g = LieAlgebra(QQ, cartan_type=['D',2]) + sage: U = g.pbw_basis() + sage: U.casimir_element(2, basis=True) + [2*PBW[alpha[2]]*PBW[-alpha[2]] + 1/2*PBW[alphacheck[2]]^2 - PBW[alphacheck[2]], + 2*PBW[alpha[1]]*PBW[-alpha[1]] + 1/2*PBW[alphacheck[1]]^2 - PBW[alphacheck[1]]] + TESTS:: sage: # needs sage.combinat sage.modules @@ -1856,7 +1865,7 @@ def casimir_element(self, order=2, UEA=None, force_generic=False): B = self.basis() - if order == 2 and not force_generic: + if order == 2 and not force_generic and not basis: # Special case for the quadratic using the Killing form try: K = self.killing_form_matrix().inverse() @@ -1896,11 +1905,10 @@ def casimir_element(self, order=2, UEA=None, force_generic=False): if ker.dimension() == 0: return self.zero() - tens = ker.basis()[0] del eqns # no need to hold onto the matrix - def to_prod(index): - coeff = tens[index] + def to_prod(vec, index): + coeff = vec[index] p = [0] * order base = dim ** (order-1) for i in range(order): @@ -1910,7 +1918,15 @@ def to_prod(index): p.reverse() return coeff * UEA.prod(UEA(B[keys[i]]) for i in p) - return UEA.sum(to_prod(index) for index in tens.support()) + tens = ker.basis() + + if not basis: + vec = tens[0] + return UEA.sum(to_prod(vec, index) for index in vec.support()) + + return [UEA.sum(to_prod(vec, index) for index in vec.support()) + for vec in tens] + class ElementMethods: def adjoint_matrix(self, sparse=False): # In #11111 (more or less) by using matrix of a morphism From daf28478a480f95e416d6af3fc83413628f29acd Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Thu, 4 Jan 2024 23:08:31 +0900 Subject: [PATCH 215/278] Implementing the center of a PBW basis of finite dimensional Lie algebras. --- .../en/reference/algebras/lie_algebras.rst | 1 + src/doc/en/reference/references/index.rst | 5 + src/sage/algebras/lie_algebras/center_uea.py | 732 ++++++++++++++++++ .../lie_algebras/classical_lie_algebra.py | 13 + .../lie_algebras/poincare_birkhoff_witt.py | 51 +- src/sage/algebras/lie_algebras/virasoro.py | 39 + src/sage/categories/lie_algebras.py | 19 + src/sage/categories/magmatic_algebras.py | 4 +- src/sage/monoids/indexed_free_monoid.py | 51 +- src/sage/rings/polynomial/plural.pxd | 2 + src/sage/rings/polynomial/plural.pyx | 42 +- 11 files changed, 952 insertions(+), 7 deletions(-) create mode 100644 src/sage/algebras/lie_algebras/center_uea.py diff --git a/src/doc/en/reference/algebras/lie_algebras.rst b/src/doc/en/reference/algebras/lie_algebras.rst index 457eb90f14e..03f66f4da88 100644 --- a/src/doc/en/reference/algebras/lie_algebras.rst +++ b/src/doc/en/reference/algebras/lie_algebras.rst @@ -7,6 +7,7 @@ Lie Algebras sage/algebras/lie_algebras/abelian sage/algebras/lie_algebras/affine_lie_algebra sage/algebras/lie_algebras/bch + sage/algebras/lie_algebras/center_uea sage/algebras/lie_algebras/classical_lie_algebra sage/algebras/lie_algebras/examples sage/algebras/lie_algebras/free_lie_algebra diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index 09b78514e1c..8b19b3322a6 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -4778,6 +4778,11 @@ REFERENCES: .. [MoPa1994] \P. Morton and P. Patel. The Galois theory of periodic points of polynomial maps. Proc. London Math. Soc., 68 (1994), 225-263. +.. [Motsak2006] Olekasandr Motsak. *Computation of the central elements and + centralizers of sets of elements in non-commutative polynomial + algebras*. PhD Thesis, 2006. + https://kluedo.ub.rptu.de/frontdoor/deliver/index/docId/2308/file/Thesis.pdf + .. [MP2019] \M. Montes, D. Penazzi "Yarara and Coral v1" https://csrc.nist.gov/CSRC/media/Projects/Lightweight-Cryptography/documents/round-1/spec-doc/yarara_and_coral-spec.pdf diff --git a/src/sage/algebras/lie_algebras/center_uea.py b/src/sage/algebras/lie_algebras/center_uea.py new file mode 100644 index 00000000000..c06ccc4f1c3 --- /dev/null +++ b/src/sage/algebras/lie_algebras/center_uea.py @@ -0,0 +1,732 @@ +""" +Center of a Universal Enveloping Algebra + +AUTHORS: + +- Travis Scrimshaw (2024-01-02): Initial version +""" + +#***************************************************************************** +# Copyright (C) 2024 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/ +#***************************************************************************** + +#from sage.structure.unique_representation import UniqueRepresentation +#from sage.structure.parent import Parent +from sage.combinat.free_module import CombinatorialFreeModule +from sage.combinat.integer_lists.invlex import IntegerListsLex +from sage.matrix.constructor import matrix +from sage.monoids.indexed_free_monoid import IndexedFreeAbelianMonoid #, IndexedFreeAbelianMonoidElement +from sage.monoids.indexed_free_monoid import IndexedMonoid +from sage.combinat.root_system.coxeter_group import CoxeterGroup +from sage.combinat.integer_vector_weighted import iterator_fast as intvecwt_iterator +from sage.sets.non_negative_integers import NonNegativeIntegers +from sage.sets.finite_enumerated_set import FiniteEnumeratedSet +from sage.sets.family import Family +from sage.rings.integer_ring import ZZ +from sage.categories.kac_moody_algebras import KacMoodyAlgebras +from sage.categories.finite_dimensional_lie_algebras_with_basis import FiniteDimensionalLieAlgebrasWithBasis +from sage.categories.graded_algebras_with_basis import GradedAlgebrasWithBasis +from sage.categories.fields import Fields +from sage.categories.monoids import Monoids +from sage.categories.enumerated_sets import EnumeratedSets +from sage.misc.cachefunc import cached_method +from sage.misc.lazy_attribute import lazy_attribute +from sage.data_structures.blas_dict import iaxpy +from collections import deque + + +class CenterIndices(IndexedFreeAbelianMonoid): + r""" + Set of basis indices for the center of a universal enveloping algebra. + + This also constructs the lift from the center to the universal enveloping + algebra as part of computing the generators and basis elements. The + basic algorithm is to construct the centeralizer of each filtered + component in increasing order (as each is a finite dimensional vector + space). For more precise details, see [Motsak2006]_. + """ + @staticmethod + def __classcall__(cls, center): + r""" + Normalize input to ensure a unique representation. + + EXAMPLES:: + + sage: from sage.algebras.lie_algebras.center_uea import CenterIndices + sage: g = lie_algebras.pwitt(GF(3), 3) + sage: U = g.pbw_basis() + sage: Z = U.center() + sage: CenterIndices(Z) is CenterIndices(Z) + True + """ + return super(IndexedMonoid, cls).__classcall__(cls, center) + + def __init__(self, center, indices=None): + r""" + Initialize ``self``. + + EXAMPLES:: + + sage: g = lie_algebras.pwitt(GF(5), 5) + sage: U = g.pbw_basis() + sage: Z = U.center() + sage: I = Z.indices() + sage: TestSuite(I).run(max_runs=7) + """ + if indices is None: + indices = NonNegativeIntegers() + category = Monoids() & EnumeratedSets().Infinite() + IndexedFreeAbelianMonoid.__init__(self, indices, prefix='Z', category=category) + + self._center = center + self._envelop_alg = self._center._envelop_alg + self._g = self._envelop_alg._g + # The _lift_map will be a dict with the keys being the degree and the values + # given as dicts with the keys being the leading support. This will be + # used to do the corresponding reductions. + self._lift_map = {0: {self._envelop_alg.one_basis(): self._envelop_alg.one()}} + self._cur_deg = 0 + self._cur_vecs = deque() # we do a lot of deletions in the middle + # The _cur_basis is a mapping from the leading supports to a monoid element of self + self._cur_basis = {self._envelop_alg.one_basis(): self.one()} + self._cur_basis_inv = {self.one(): self._envelop_alg.one_basis()} + self._gen_degrees = {} + self._cur_num_gens = 0 + + def _repr_(self): + r""" + Return a string representation of ``self``. + + EXAMPLES:: + + sage: g = lie_algebras.pwitt(GF(5), 5) + sage: U = g.pbw_basis() + sage: Z = U.center() + sage: Z.indices() + Basis indices of Center of Universal enveloping algebra of + The 5-Witt Lie algebra over Finite Field of size 5 in the Poincare-Birkhoff-Witt basis + """ + return "Basis indices of {}".format(self._center) + + def _latex_(self): + r""" + Return a latex representation of ``self``. + + EXAMPLES:: + + sage: g = lie_algebras.pwitt(GF(5), 5) + sage: U = g.pbw_basis() + sage: Z = U.center() + sage: I = Z.indices() + sage: latex(I) + B\left( Z\left( PBW\left( \mathcal{W}(5)_{\Bold{F}_{5}} \right) \right) \right) + """ + from sage.misc.latex import latex + return r"B\left( {} \right)".format(latex(self._center)) + + def lift_on_basis(self, m): + r""" + Return the image of the basis element indexed by ``m`` in the + universal enveloping algebra. + + EXAMPLES:: + + sage: g = lie_algebras.Heisenberg(QQ, 3) + sage: U = g.pbw_basis() + sage: Z = U.center() + sage: I = Z.indices() + sage: z0 = I.monoid_generators()[0] + sage: I._lift_map + {0: {1: 1}} + sage: I.lift_on_basis(z0) + PBW['z'] + sage: I._lift_map + {0: {1: 1}, 1: {PBW['z']: PBW['z']}} + sage: I.lift_on_basis(z0^3) + PBW['z']^3 + sage: I._lift_map + {0: {1: 1}, 1: {PBW['z']: PBW['z']}} + sage: I._construct_next_degree() + sage: I._construct_next_degree() + sage: I._lift_map + {0: {1: 1}, + 1: {PBW['z']: PBW['z']}, + 2: {PBW['z']^2: PBW['z']^2}, + 3: {PBW['z']^3: PBW['z']^3}} + sage: I.lift_on_basis(z0^3) + PBW['z']^3 + """ + while m not in self._cur_basis_inv: + supp = m.support() + # We might have not computed the correct degree, but we can lift the + # element if we have computed all of the corresponding generators. + if all(i in self._gen_degrees and self._gen_degrees[i] in self._lift_map + for i in supp): + ret = self._envelop_alg.one() + divisors = [mp for mp in self._cur_basis_inv if mp.divides(m) and not mp.is_one()] + while not m.is_one(): + div = max(divisors, key=lambda elt: len(elt)) + ls = self._cur_basis_inv[div] + deg = ls.length() + ret *= self._lift_map[deg][ls] + m = m // div + divisors = [mp for mp in divisors if mp.divides(m)] + return ret + self._construct_next_degree() + ls = self._cur_basis_inv[m] + deg = ls.length() + return self._lift_map[deg][ls] + + def __iter__(self): + r""" + Iterate over ``self`` in degree increasing order. + + EXAMPLES:: + + sage: g = lie_algebras.pwitt(GF(3), 6) + sage: U = g.pbw_basis() + sage: Z = U.center() + sage: I = Z.indices() + sage: it = iter(I) + sage: [next(it) for _ in range(10)] + [1, Z[0], Z[1], Z[2], Z[3], Z[4], Z[5], Z[6], Z[7], Z[0]^2] + """ + yield self.one() # start with the identity + deg = 1 + while True: + while deg not in self._lift_map: + self._construct_next_degree() + for ls in self._lift_map[deg]: + yield self._cur_basis[ls] + deg += 1 + + def some_elements(self): + r""" + Return some elements of ``self``. + + EXAMPLES:: + + sage: g = lie_algebras.pwitt(GF(3), 3) + sage: U = g.pbw_basis() + sage: Z = U.center() + sage: I = Z.indices() + sage: I.some_elements() + [1, Z[0], Z[1], Z[2], Z[0]*Z[1]*Z[2], Z[0]*Z[2]^4, Z[0]^4*Z[1]^3] + """ + it = iter(self) + gens = [next(it) for _ in range(4)] + # We construct it as a set in case we introduce duplicates. + ret = set(gens) + ret.update([self.prod(gens), gens[1] * gens[3]**4, gens[1]**4 * gens[2]**3]) + # Sort the output for uniqueness + ret = sorted(ret, key=lambda m: (self.degree(m), m.to_word_list())) + return ret + + def degree(self, m): + r""" + Return the degre of ``m`` in ``self``. + + EXAMPLES:: + + sage: g = LieAlgebra(QQ, cartan_type=['E', 6]) + sage: U = g.pbw_basis() + sage: Z = U.center() + sage: I = Z.indices() + sage: [I.degree(g) for g in I.monoid_generators()] + [2, 5, 6, 8, 9, 12] + sage: [(elt, I.degree(elt)) for elt in I.some_elements()] + [(1, 0), (Z[0], 2), (Z[0]^2, 4), (Z[1], 5), (Z[0]^3*Z[1], 11), + (Z[0]^10, 20), (Z[0]*Z[1]^4, 22)] + """ + return ZZ.sum(e * self._gen_degrees[i] for i, e in m._monomial.items()) + + def _construct_next_degree(self): + r""" + Construct the next elements of ``self`` for the next (uncomputed) degree. + + EXAMPLES:: + + sage: g = lie_algebras.three_dimensional_by_rank(QQ, 2, 1) + sage: U = g.pbw_basis() + sage: Z = U.center() + sage: I = Z.indices() + sage: I._lift_map + {0: {1: 1}} + sage: I._construct_next_degree() + sage: I._construct_next_degree() + sage: I._construct_next_degree() + sage: I._construct_next_degree() + sage: I._lift_map + {0: {1: 1}, 1: {}, 2: {}, 3: {}, 4: {}} + """ + UEA = self._envelop_alg + gens = UEA.algebra_generators() + monoid = UEA.basis().keys() + self._cur_deg += 1 + + # We first update the lift map with all possible products + # Note that we are using the fact that the elements are central + # so the product order doesn't matter. + # Since we always update this, it is sufficient to compute it + new_red = {} + for i in range(1, self._cur_deg//2+1): + for ls, lelt in self._lift_map[self._cur_deg-i].items(): + for rs, relt in self._lift_map[i].items(): + supp = ls * rs + new_red[supp] = lelt * relt + mon = self._cur_basis[ls] * self._cur_basis[rs] + self._cur_basis[supp] = mon + self._cur_basis_inv[mon] = supp + # TODO: Determine if we need to or benefit from another reduction of the new elements + self._lift_map[self._cur_deg] = new_red + + # Determine the PBW elements of the current degree that are not reduced + # modulo the currently computed center. + for exps in IntegerListsLex(n=self._cur_deg, length=len(gens)): + elt = monoid.element_class(monoid, {k: p for k, p in zip(monoid._indices, exps) if p}) + if elt in new_red: # already has a centeral element with this leading term + continue + # A new basis element to consider + self._cur_vecs.append(UEA.monomial(elt)) + + # Perform the centralization + R = UEA.base_ring() + vecs = list(self._cur_vecs) + for g in gens: + # TODO: We should hold onto previously computed values under the adjoint action + ad = [g * v - v * g for v in vecs] + # Compute the kernel + supp = set() + for v in ad: + supp.update(v._monomial_coefficients) + supp = sorted(supp, key=UEA._monomial_key, reverse=True) + if not supp: # no support for the image, so everything is in the kernel + continue + M = matrix(R, [[v[s] for v in ad] for s in supp]) + ker = M.right_kernel_matrix() + vecs = [self._reduce(UEA.linear_combination((vecs[i], c) for i, c in kv.iteritems())) + for kv in ker.rows()] + + # Lastly, update the appropriate data + if not vecs: # No new central elements, so nothing to do + return + new_gens = {} + for v in vecs: + v = self._reduce(v) # possibly not needed to check this + if not v: + continue + ls = v.trailing_support(key=UEA._monomial_key) + self._cur_vecs.remove(UEA.monomial(ls)) + new_gens[ls] = self._reduce(v) + assert (self._cur_num_gens not in self._gen_degrees + or self._gen_degrees[self._cur_num_gens] == self._cur_deg) + self._gen_degrees[self._cur_num_gens] = self._cur_deg + mon = self.gen(self._cur_num_gens) + self._cur_basis[ls] = mon + self._cur_basis_inv[mon] = ls + self._cur_num_gens += 1 + self._lift_map[self._cur_deg].update(new_gens) + + def _reduce(self, vec): + r""" + Return the UEA vector ``vec`` by the currently computed center. + + EXAMPLES:: + + sage: g = LieAlgebra(QQ, cartan_type=['A', 1]) + sage: U = g.pbw_basis() + sage: Z = U.center() + sage: I = Z.indices() + sage: z0 = I.gen(0) + sage: I._reduce(I.lift_on_basis(z0)) + 0 + sage: max(I._lift_map) + 2 + sage: I._reduce(I.lift_on_basis(z0^2)) + 4*PBW[alpha[1]]^2*PBW[-alpha[1]]^2 + + 2*PBW[alpha[1]]*PBW[alphacheck[1]]^2*PBW[-alpha[1]] + + 1/4*PBW[alphacheck[1]]^4 - PBW[alphacheck[1]]^3 + + PBW[alphacheck[1]]^2 + sage: I._reduce(I.lift_on_basis(z0^2) - I.lift_on_basis(z0)) + 4*PBW[alpha[1]]^2*PBW[-alpha[1]]^2 + + 2*PBW[alpha[1]]*PBW[alphacheck[1]]^2*PBW[-alpha[1]] + + 1/4*PBW[alphacheck[1]]^4 - PBW[alphacheck[1]]^3 + + PBW[alphacheck[1]]^2 + sage: I._construct_next_degree() + sage: I._construct_next_degree() + sage: max(I._lift_map) + 4 + sage: I._reduce(I.lift_on_basis(z0^2) - I.lift_on_basis(z0)) + 0 + """ + # This is replicating what SubmoduleWithBasis does + ret = dict(vec._monomial_coefficients) + for data in self._lift_map.values(): + for m, qv in data.items(): + if m not in ret: + continue + iaxpy(-ret[m] / qv[m], qv._monomial_coefficients, ret) + return self._envelop_alg._from_dict(ret, remove_zeros=False) + + +class SimpleLieCenterIndices(CenterIndices): + r""" + Set of basis indices for the center of a universal enveloping algebra of + a simple Lie algebra. + + For more information, see + :class:`~sage.algebras.lie_algebras.center_uea.CenterIndices`. + """ + def __init__(self, center): + r""" + Initialize ``self``. + + EXAMPLES:: + + sage: g = LieAlgebra(QQ, cartan_type=['E', 6]) + sage: U = g.pbw_basis() + sage: Z = U.center() + sage: I = Z.indices() + sage: TestSuite(I).run() + """ + self._cartan_type = center._envelop_alg._g.cartan_type() + r = self._cartan_type.rank() + super().__init__(center, indices=FiniteEnumeratedSet(range(r))) + W = CoxeterGroup(self._cartan_type) + self._gen_degrees = dict(enumerate(W.degrees())) + + def __iter__(self): + r""" + Iterate over ``self`` in degree increasing order. + + EXAMPLES:: + + sage: g = LieAlgebra(QQ, cartan_type=['E', 6]) + sage: U = g.pbw_basis() + sage: Z = U.center() + sage: I = Z.indices() + sage: it = iter(I) + sage: [next(it) for _ in range(10)] + [1, Z[0], Z[0]^2, Z[1], Z[2], Z[0]^3, Z[0]*Z[1], Z[3], Z[0]*Z[2], Z[0]^4] + """ + deg = 0 + n = len(self._gen_degrees) + wts = sorted(self._gen_degrees.values(), reverse=True) + while True: + total = 0 + for exps in intvecwt_iterator(deg, wts): + yield self.element_class(self, {n-1-i: e for i, e in enumerate(exps) if e}) + deg += 1 + + +class CenterUEA(CombinatorialFreeModule): + r""" + The center of a universal enveloping algebra. + + .. TODO:: + + Generalize this to be the centralizer of any set of the UEA. + + .. TODO:: + + For characteristic `p > 0`, implement the `p`-center of a simple + Lie algebra. See, e.g., + + - Theorem 5.12 of [Motsak2006]_ + - http://www.math.kobe-u.ac.jp/icms2006/icms2006-video/slides/059.pdf + + EXAMPLES:: + + sage: g = LieAlgebra(QQ, cartan_type=['A', 2]) + sage: U = g.pbw_basis() + sage: Z = U.center() + sage: B = Z.basis() + sage: it = iter(B) + sage: center_elts = [next(it) for _ in range(6)]; center_elts + [1, Z[0], Z[1], Z[0]^2, Z[0]*Z[1], Z[1]^2] + sage: elts = [U(v) for v in center_elts] # long time + sage: all(v * g == g * v for g in U.algebra_generators() for v in elts) # long time + True + + sage: g = lie_algebras.Heisenberg(GF(3), 4) + sage: U = g.pbw_basis() + sage: Z = U.center() + sage: B = Z.basis() + sage: it = iter(B) + sage: center_elts = [next(it) for _ in range(12)]; center_elts + [1, Z[0], Z[0]^2, Z[0]^3, Z[1], Z[2], Z[3], Z[4], Z[5], Z[6], Z[7], Z[8]] + sage: elts = [U(v) for v in center_elts]; set(elts) + {1, PBW['p1']^3, PBW['p2']^3, PBW['p3']^3, PBW['p4']^3, PBW['q1']^3, + PBW['q2']^3, PBW['q3']^3, PBW['q4']^3, PBW['z'], PBW['z']^2, PBW['z']^3} + sage: all(v * g == g * v for g in U.algebra_generators() for v in elts) + True + """ + def __init__(self, g, UEA): + r""" + Initialize ``self``. + + EXAMPLES:: + + sage: g = LieAlgebra(ZZ['t'].fraction_field(), cartan_type=['D', 4]) + sage: U = g.pbw_basis() + sage: Z = U.center() + sage: TestSuite(Z).run() + + sage: g = lie_algebras.Heisenberg(GF(3), 4) + sage: U = g.pbw_basis() + sage: Z = U.center() + sage: TestSuite(Z).run() + """ + if g not in FiniteDimensionalLieAlgebrasWithBasis: + raise NotImplementedError("only implemented for finite dimensional Lie algebras with a distinguished basis") + + R = UEA.base_ring() + if R not in Fields(): + raise NotImplementedError("only implemented for the base ring a field") + + self._g = g + self._envelop_alg = UEA + if (self._g in KacMoodyAlgebras + and self._g.cartan_type().is_finite() + and R.characteristic() == 0): + indices = SimpleLieCenterIndices(self) + else: + indices = CenterIndices(self) + category = UEA.category() + base = category.base() + category = GradedAlgebrasWithBasis(base).Commutative() | category.Subobjects() + CombinatorialFreeModule.__init__(self, R, indices, category=category, + prefix='', bracket=False, latex_bracket=False, + sorting_key=self._sorting_key) + self.lift.register_as_coercion() + + def _repr_(self): + r""" + Return a string representation of ``self``. + + EXAMPLES:: + + sage: g = LieAlgebra(QQ, cartan_type=['A',2]) + sage: U = g.pbw_basis() + sage: U.center() + Center of Universal enveloping algebra of Lie algebra of ['A', 2] + in the Chevalley basis in the Poincare-Birkhoff-Witt basis + """ + return "Center of " + repr(self._envelop_alg) + + def _latex_(self): + r""" + Return a latex representation of ``self``. + + EXAMPLES:: + + sage: g = lie_algebras.pwitt(GF(5), 5) + sage: U = g.pbw_basis() + sage: Z = U.center() + sage: latex(Z) + Z\left( PBW\left( \mathcal{W}(5)_{\Bold{F}_{5}} \right) \right) + """ + from sage.misc.latex import latex + return r"Z\left( {} \right)".format(latex(self._envelop_alg)) + + def _sorting_key(self, m): + r""" + Return a key for ``m`` used in sorting elements of ``self``. + + EXAMPLES:: + + sage: g = LieAlgebra(QQ, cartan_type=['A', 2]) + sage: U = g.pbw_basis() + sage: Z = U.center() + sage: z0, z1 = Z.algebra_generators() + sage: z0 * z1 # indirect doctest + Z[0]*Z[1] + sage: z1^2 + z0*z1 + z0^2 # indirect doctest + Z[0]^2 + Z[0]*Z[1] + Z[1]^2 + sage: z1^3 + z0*z1^2 + z0^2*z1 + z0^3 # indirect doctest + Z[0]^3 + Z[0]^2*Z[1] + Z[0]*Z[1]^2 + Z[1]^3 + """ + return (-m.length(), m.to_word_list()) + + @cached_method + def algebra_generators(self): + r""" + Return the algebra generators of ``self``. + + .. WARNING:: + + When the universal enveloping algebra is not known to have + a finite generating set, the generating set will be the basis + of ``self`` in a degree (weakly) increasing order indexed by + `\ZZ_{\geq 0}`. In particular, the `0`-th generator will be + the multiplicative identity `1`. + + EXAMPLES:: + + sage: g = lie_algebras.Heisenberg(QQ, 3) + sage: U = g.pbw_basis() + sage: Z = U.center() + sage: Z.algebra_generators()[0] + 1 + sage: Z.algebra_generators()[1] + Z[0] + + sage: g = LieAlgebra(QQ, cartan_type=['G', 2]) + sage: U = g.pbw_basis() + sage: Z = U.center() + sage: Z.algebra_generators() + Finite family {0: Z[0], 1: Z[1]} + """ + mon_gens = self._indices.monoid_generators() + if mon_gens.cardinality() == float("inf"): + return Family(NonNegativeIntegers(), lambda m: self.monomial(self._indices.unrank(m))) + return Family({i: self.monomial(mon_gens[i]) for i in mon_gens.keys()}) + + @cached_method + def one_basis(self): + r""" + Return the basis index of `1` in ``self``. + + EXAMPLES:: + + sage: g = LieAlgebra(QQ['t'].fraction_field(), cartan_type=['B', 5]) + sage: U = g.pbw_basis() + sage: Z = U.center() + sage: ob = Z.one_basis(); ob + 1 + sage: ob.parent() is Z.indices() + True + """ + return self._indices.one() + + def ambient(self): + r""" + Return the ambient algebra of ``self``. + + EXAMPLES:: + + sage: g = LieAlgebra(GF(5), cartan_type=['A', 2]) + sage: U = g.pbw_basis() + sage: Z = U.center() + sage: Z.ambient() is U + True + """ + return self._envelop_alg + + def product_on_basis(self, left, right): + r""" + Return the product of basis elements indexed by ``left`` and ``right``. + + EXAMPLES:: + + sage: g = LieAlgebra(QQ, cartan_type=['E', 6]) + sage: U = g.pbw_basis() + sage: Z = U.center() + sage: mg = Z.indices().monoid_generators() + sage: Z.product_on_basis(mg[1]*mg[2], mg[0]*mg[1]^3*mg[2]*mg[3]^3) + Z[0]*Z[1]^4*Z[2]^2*Z[3]^3 + """ + return self.monomial(left * right) + + def degree_on_basis(self, m): + r""" + Return the degree of the basis element indexed by ``m`` in ``self``. + + EXAMPLES:: + + sage: g = LieAlgebra(QQ, cartan_type=['E', 6]) + sage: U = g.pbw_basis() + sage: Z = U.center() + sage: I = Z.indices() + sage: it = iter(I) + sage: supports = [next(it) for _ in range(10)]; supports + [1, Z[0], Z[0]^2, Z[1], Z[2], Z[0]^3, Z[0]*Z[1], Z[3], Z[0]*Z[2], Z[0]^4] + sage: [Z.degree_on_basis(m) for m in supports] + [0, 2, 4, 5, 6, 6, 7, 8, 8, 8] + """ + return self._indices.degree(m) + + @lazy_attribute + def lift(self): + r""" + The lift map from ``self`` to the universal enveloping algebra. + + EXAMPLES:: + + sage: g = LieAlgebra(QQ, cartan_type=['A', 1]) + sage: U = g.pbw_basis() + sage: Z = U.center() + sage: gens = Z.algebra_generators() + sage: U(gens[0]^2 + gens[0]) + 4*PBW[alpha[1]]^2*PBW[-alpha[1]]^2 + + 2*PBW[alpha[1]]*PBW[alphacheck[1]]^2*PBW[-alpha[1]] + + 1/4*PBW[alphacheck[1]]^4 - PBW[alphacheck[1]]^3 + - 2*PBW[alpha[1]]*PBW[-alpha[1]] + 1/2*PBW[alphacheck[1]]^2 + + PBW[alphacheck[1]] + sage: U(-1/4*gens[0]) == U.casimir_element() + True + """ + # This is correct if we are using key=self._envelop_alg._monomial_key, + # but we are currently unable to pass such an option. + return self.module_morphism(self._indices.lift_on_basis, codomain=self._envelop_alg, unitriangular='upper') + + def retract(self, elt): + r""" + The retraction map to ``self`` from the universal enveloping algebra. + + .. TODO:: + + Implement a version of this that checks if the leading term of + ``elt`` is divisible by a product of all of the currently known + generators in order to avoid constructing the full centralizer + of larger degrees than needed. + + EXAMPLES:: + + sage: g = lie_algebras.Heisenberg(QQ, 3) + sage: U = g.pbw_basis() + sage: Z = U.center() + sage: z0 = Z.algebra_generators()[1]; z0 + Z[0] + sage: Z.retract(U(z0^2) - U(3*z0)) + Z[0]^2 - 3*Z[0] + + sage: g = LieAlgebra(QQ, cartan_type=['A', 2]) + sage: U = g.pbw_basis() + sage: Z = U.center() + sage: z0, z1 = Z.algebra_generators() + sage: Z.retract(U(z0*z0) - U(z1)) # long time + Z[0]^2 - Z[1] + sage: zc = Z.retract(U.casimir_element()); zc + -1/3*Z[0] + sage: U(zc) == U.casimir_element() + True + """ + # This should work except it needs the monomials of the PBW basis to be + # compariable. However, this does not work for, e.g., Lie algebras + # in the Chevalley basis as ee are unable to pass a key for the + # module morphism. Additionally, the implementation below does more + # operations in-place than the module morphism. + #return self.lift.section() + UEA = self._envelop_alg + elt = UEA(elt) + # We manipulate the dictionary (in place) to avoid creating elements + data = elt.monomial_coefficients(copy=True) + indices = self._indices + ret = {} + while data: + lm = min(data, key=UEA._monomial_key) + while indices._cur_deg < UEA.degree_on_basis(lm): + indices._construct_next_degree() + ind = indices._cur_basis[lm] + other = indices.lift_on_basis(ind).monomial_coefficients(copy=False) + coeff = data[lm] / other[lm] + ret[ind] = coeff + iaxpy(-coeff, other, data) + return self.element_class(self, ret) diff --git a/src/sage/algebras/lie_algebras/classical_lie_algebra.py b/src/sage/algebras/lie_algebras/classical_lie_algebra.py index 11900983313..d76bc084109 100644 --- a/src/sage/algebras/lie_algebras/classical_lie_algebra.py +++ b/src/sage/algebras/lie_algebras/classical_lie_algebra.py @@ -1796,6 +1796,19 @@ def _repr_(self): """ return "Lie algebra of {} in the Chevalley basis".format(self._cartan_type) + def _latex_(self): + r""" + Return a latex representation of ``self``. + + EXAMPLES:: + + sage: g = LieAlgebra(QQ, cartan_type=['A', 2]) + sage: latex(g) + \mathfrak{g}(A_{2})_{\Bold{Q}} + """ + from sage.misc.latex import latex + return r"\mathfrak{{g}}({})_{{{}}}".format(latex(self._cartan_type), latex(self.base_ring())) + def _test_structure_coeffs(self, **options): """ Check the structure coefficients against the GAP implementation. diff --git a/src/sage/algebras/lie_algebras/poincare_birkhoff_witt.py b/src/sage/algebras/lie_algebras/poincare_birkhoff_witt.py index e893f7caa87..b47f1391025 100644 --- a/src/sage/algebras/lie_algebras/poincare_birkhoff_witt.py +++ b/src/sage/algebras/lie_algebras/poincare_birkhoff_witt.py @@ -4,10 +4,11 @@ AUTHORS: - Travis Scrimshaw (2013-11-03): Initial version +- Travis Scrimshaw (2024-01-02): Adding the center """ #***************************************************************************** -# Copyright (C) 2013-2017 Travis Scrimshaw +# Copyright (C) 2013-2024 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 @@ -96,6 +97,14 @@ class PoincareBirkhoffWittBasis(CombinatorialFreeModule): PBW[2]*PBW[3] + PBW[5] sage: G[-2] * G[3] * G[2] PBW[-2]*PBW[2]*PBW[3] + PBW[-2]*PBW[5] + + .. TODO:: + + When the Lie algebra is finite dimensional, set the ordering of the + basis elements, translate the structure coefficients, and work with + fixed-length lists as the exponent vectors. This way we only will + run any nontrivial sorting only once and avoid other potentially + expensive comparisons between keys. """ @staticmethod def __classcall_private__(cls, g, basis_key=None, prefix='PBW', **kwds): @@ -247,6 +256,20 @@ def _repr_(self): """ return "Universal enveloping algebra of {} in the Poincare-Birkhoff-Witt basis".format(self._g) + def _latex_(self): + r""" + Return a latex representation of ``self``. + + EXAMPLES:: + + sage: g = lie_algebras.pwitt(GF(3), 6) + sage: U = g.pbw_basis() + sage: latex(U) + PBW\left( \mathcal{W}(6)_{\Bold{F}_{3}} \right) + """ + from sage.misc.latex import latex + return r"PBW\left( {} \right)".format(latex(self._g)) + def _coerce_map_from_(self, R): """ Return ``True`` if there is a coercion map from ``R`` to ``self``. @@ -536,6 +559,32 @@ def casimir_element(self, order=2, *args, **kwds): raise ValueError("the Lie algebra must be finite dimensional") return self._g.casimir_element(order=order, UEA=self, *args, **kwds) + def center(self): + r""" + Return the center of ``self``. + + .. SEEALSO:: + + :class:`~sage.algebras.lie_algebras.center_uea.CenterUEA` + + EXAMPLES:: + + sage: g = LieAlgebra(QQ, cartan_type=['A', 2]) + sage: U = g.pbw_basis() + sage: U.center() + Center of Universal enveloping algebra of Lie algebra of ['A', 2] + in the Chevalley basis in the Poincare-Birkhoff-Witt basis + + sage: g = lie_algebras.Heisenberg(GF(3), 4) + sage: U = g.pbw_basis() + sage: Z = U.center() + Center of Universal enveloping algebra of Heisenberg algebra of rank 4 + over Finite Field of size 3 in the Poincare-Birkhoff-Witt basis + """ + from sage.algebras.lie_algebras.center_uea import CenterUEA + return CenterUEA(self._g, self) + + class Element(CombinatorialFreeModule.Element): def _act_on_(self, x, self_on_left): """ diff --git a/src/sage/algebras/lie_algebras/virasoro.py b/src/sage/algebras/lie_algebras/virasoro.py index 297ccf184a8..aa11a9ed089 100644 --- a/src/sage/algebras/lie_algebras/virasoro.py +++ b/src/sage/algebras/lie_algebras/virasoro.py @@ -80,6 +80,19 @@ def _repr_(self): """ return "The Lie algebra of regular vector fields over {}".format(self.base_ring()) + def _latex_(self): + r""" + Return a latex representation of ``self``. + + EXAMPLES:: + + sage: g = lie_algebras.regular_vector_fields(QQ) + sage: latex(g) + \mathcal{W}_{\Bold{Q}} + """ + from sage.misc.latex import latex + return r"\mathcal{{W}}_{{{}}}".format(latex(self.base_ring())) + # For compatibility with CombinatorialFreeModuleElement _repr_term = IndexedGenerators._repr_generator _latex_term = IndexedGenerators._latex_generator @@ -217,6 +230,19 @@ def _repr_(self): """ return "The {}-Witt Lie algebra over {}".format(self._p, self.base_ring()) + def _latex_(self): + r""" + Return a latex representation of ``self``. + + EXAMPLES:: + + sage: g = lie_algebras.pwitt(GF(3), 15) + sage: latex(g) + \mathcal{W}(15)_{\Bold{F}_{3}} + """ + from sage.misc.latex import latex + return r"\mathcal{{W}}({})_{{{}}}".format(latex(self._p), latex(self.base_ring())) + # For compatibility with CombinatorialFreeModuleElement _repr_term = IndexedGenerators._repr_generator _latex_term = IndexedGenerators._latex_generator @@ -444,6 +470,19 @@ def _repr_(self): """ return "The Virasoro algebra over {}".format(self.base_ring()) + def _latex_(self): + r""" + Return a latex representation of ``self``. + + EXAMPLES:: + + sage: g = lie_algebras.VirasoroAlgebra(QQ) + sage: latex(g) + \mathcal{V}_{\Bold{Q}} + """ + from sage.misc.latex import latex + return r"\mathcal{{V}}_{{{}}}".format(latex(self.base_ring())) + @cached_method def lie_algebra_generators(self): """ diff --git a/src/sage/categories/lie_algebras.py b/src/sage/categories/lie_algebras.py index 4226c4dba61..38fd5563fb9 100644 --- a/src/sage/categories/lie_algebras.py +++ b/src/sage/categories/lie_algebras.py @@ -346,6 +346,25 @@ def _construct_UEA(self): Multivariate Polynomial Ring in x0, x1, x2 over Rational Field """ + def center_universal_enveloping_algebra(self, UEA=None): + """ + Return the center of the universal enveloping algebra of ``self``. + + EXAMPLES:: + + sage: L = LieAlgebra(QQ, 3, 'x', abelian=True) + sage: L.center_universal_enveloping_algebra() + Center of Universal enveloping algebra of Abelian Lie algebra on 3 generators (x0, x1, x2) + over Rational Field in the Poincare-Birkhoff-Witt basis + sage: PBW = L.PBW_basis() + sage: L.center_universal_enveloping_algebra(PBW) + Center of Universal enveloping algebra of Abelian Lie algebra on 3 generators (x0, x1, x2) + over Rational Field in the Poincare-Birkhoff-Witt basis + """ + if UEA is not None: + return UEA.center() + return self.pbw_basis().center() + @abstract_method(optional=True) def module(self): r""" diff --git a/src/sage/categories/magmatic_algebras.py b/src/sage/categories/magmatic_algebras.py index 9d37608569d..0210838abd3 100644 --- a/src/sage/categories/magmatic_algebras.py +++ b/src/sage/categories/magmatic_algebras.py @@ -221,8 +221,8 @@ def _product_from_product_on_basis_multiply( self, left, right ): """ return self.linear_combination((self.product_on_basis(mon_left, mon_right), coeff_left * coeff_right ) - for (mon_left, coeff_left) in left.monomial_coefficients().items() - for (mon_right, coeff_right) in right.monomial_coefficients().items() ) + for (mon_left, coeff_left) in left.monomial_coefficients(copy=False).items() + for (mon_right, coeff_right) in right.monomial_coefficients(copy=False).items() ) class FiniteDimensional(CategoryWithAxiom_over_base_ring): class ParentMethods: diff --git a/src/sage/monoids/indexed_free_monoid.py b/src/sage/monoids/indexed_free_monoid.py index 58910533a9a..09b35530098 100644 --- a/src/sage/monoids/indexed_free_monoid.py +++ b/src/sage/monoids/indexed_free_monoid.py @@ -355,6 +355,31 @@ def to_word_list(self): """ return [k for k,e in self._sorted_items() for dummy in range(e)] + def is_one(self) -> bool: + """ + Return if ``self`` is the identity element. + + EXAMPLES:: + + sage: F = FreeMonoid(index_set=ZZ) + sage: a,b,c,d,e = [F.gen(i) for i in range(5)] + sage: (b*a*c^3*a).is_one() + False + sage: F.one().is_one() + True + + :: + + sage: F = FreeAbelianMonoid(index_set=ZZ) + sage: a,b,c,d,e = [F.gen(i) for i in range(5)] + sage: (b*c^3*a).is_one() + False + sage: F.one().is_one() + True + """ + return not self._monomial + + class IndexedFreeMonoidElement(IndexedMonoidElement): """ An element of an indexed free abelian monoid. @@ -591,6 +616,29 @@ def __floordiv__(self, elt): d[k] = diff return self.__class__(self.parent(), d) + def divides(self, m) -> bool: + r""" + Return whether ``self`` divides ``m``. + + EXAMPLES:: + + sage: F = FreeAbelianMonoid(index_set=ZZ) + sage: a,b,c,d,e = [F.gen(i) for i in range(5)] + sage: elt = a*b*c^3*d^2 + sage: a.divides(elt) + True + sage: c.divides(elt) + True + sage: (a*b*d^2).divides(elt) + True + sage: (a^4).divides(elt) + False + sage: e.divides(elt) + False + """ + other = m._monomial + return all(k in other and v <= other[k] for k, v in self._monomial.items()) + def __len__(self): """ Return the length of ``self``. @@ -605,8 +653,7 @@ def __len__(self): sage: len(elt) 7 """ - m = self._monomial - return sum(m[gen] for gen in m) + return sum(self._monomial.values()) length = __len__ diff --git a/src/sage/rings/polynomial/plural.pxd b/src/sage/rings/polynomial/plural.pxd index 06b48c737f3..5e3618bd90e 100644 --- a/src/sage/rings/polynomial/plural.pxd +++ b/src/sage/rings/polynomial/plural.pxd @@ -37,6 +37,8 @@ cdef class NCPolynomial_plural(RingElement): cpdef _repr_short_(self) noexcept cdef long _hash_c(self) noexcept cpdef is_constant(self) noexcept + cpdef dict dict(self) noexcept + cpdef dict monomial_coefficients(self, bint copy=*) noexcept # cpdef _homogenize(self, int var) cdef NCPolynomial_plural new_NCP(NCPolynomialRing_plural parent, poly *juice) noexcept diff --git a/src/sage/rings/polynomial/plural.pyx b/src/sage/rings/polynomial/plural.pyx index c04424742d6..0c358f395ff 100644 --- a/src/sage/rings/polynomial/plural.pyx +++ b/src/sage/rings/polynomial/plural.pyx @@ -856,6 +856,19 @@ cdef class NCPolynomialRing_plural(Ring): return new_NCP(self,_p) + def algerbra_generators(self): + r""" + Return the algebra generators of ``self``. + + EXAMPLES:: + + sage: A. = FreeAlgebra(QQ, 3) + sage: P = A.g_algebra(relations={y*x:-x*y}, order = 'lex') + sage: P.algebra_generators() + (x, y, z) + """ + return self.gens() + def ideal(self, *gens, **kwds): """ Create an ideal in this polynomial ring. @@ -2178,7 +2191,7 @@ cdef class NCPolynomial_plural(RingElement): return (self._parent)._base._zero_element - def dict(self): + cpdef dict dict(self) noexcept: """ Return a dictionary representing ``self``. This dictionary is in the same format as the generic MPolynomial: The dictionary @@ -2204,7 +2217,8 @@ cdef class NCPolynomial_plural(RingElement): rChangeCurrRing(r) base = (self._parent)._base p = self._poly - pd = dict() + cdef dict d + cdef dict pd = dict() while p: d = dict() for v from 1 <= v <= r.N: @@ -2217,6 +2231,30 @@ cdef class NCPolynomial_plural(RingElement): p = pNext(p) return pd + cpdef dict monomial_coefficients(self, bint copy=True) noexcept: + """ + Return a dictonary representation of ``self`` with the keys + the exponent vectors and the values the corresponding coefficients. + + INPUT: + + * "copy" -- ignored + + EXAMPLES:: + + sage: A. = FreeAlgebra(GF(389), 3) + sage: R = A.g_algebra(relations={y*x:-x*y + z}, order='lex') + sage: R.inject_variables() + Defining x, z, y + sage: f = (2*x*y^3*z^2 + (7)*x^2 + (3)) + sage: d = f.monomial_coefficients(False); d + {(0, 0, 0): 3, (1, 2, 3): 2, (2, 0, 0): 7} + sage: d.clear() + sage: f.monomial_coefficients() + {(0, 0, 0): 3, (1, 2, 3): 2, (2, 0, 0): 7} + """ + return self.dict() + def _im_gens_(self, codomain, im_gens, base_map=None): """ Return the image of ``self`` in codomain under the map that sends From 352bc77385e167f4f6d6419d40dc278e48918077 Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Fri, 5 Jan 2024 00:15:29 +0900 Subject: [PATCH 216/278] Fixing the linter; updating and adding some doctests. --- src/sage/algebras/lie_algebras/center_uea.py | 39 +++++++++++++++++-- .../lie_algebras/poincare_birkhoff_witt.py | 1 - ...ite_dimensional_lie_algebras_with_basis.py | 1 - 3 files changed, 36 insertions(+), 5 deletions(-) diff --git a/src/sage/algebras/lie_algebras/center_uea.py b/src/sage/algebras/lie_algebras/center_uea.py index c06ccc4f1c3..0709eb637b9 100644 --- a/src/sage/algebras/lie_algebras/center_uea.py +++ b/src/sage/algebras/lie_algebras/center_uea.py @@ -454,6 +454,9 @@ class CenterUEA(CombinatorialFreeModule): sage: all(v * g == g * v for g in U.algebra_generators() for v in elts) # long time True + The Heisenberg Lie algebra `H_4` over a finite field; note the basis + elements `b^p \in Z(U(H_4))` for the basis elements `b \in H_4`:: + sage: g = lie_algebras.Heisenberg(GF(3), 4) sage: U = g.pbw_basis() sage: Z = U.center() @@ -461,9 +464,39 @@ class CenterUEA(CombinatorialFreeModule): sage: it = iter(B) sage: center_elts = [next(it) for _ in range(12)]; center_elts [1, Z[0], Z[0]^2, Z[0]^3, Z[1], Z[2], Z[3], Z[4], Z[5], Z[6], Z[7], Z[8]] - sage: elts = [U(v) for v in center_elts]; set(elts) - {1, PBW['p1']^3, PBW['p2']^3, PBW['p3']^3, PBW['p4']^3, PBW['q1']^3, - PBW['q2']^3, PBW['q3']^3, PBW['q4']^3, PBW['z'], PBW['z']^2, PBW['z']^3} + sage: elts = [U(v) for v in center_elts]; elts + [1, PBW['z'], PBW['z']^2, PBW['z']^3, PBW['p1']^3, PBW['p2']^3, PBW['p3']^3, + PBW['p4']^3, PBW['q1']^3, PBW['q2']^3, PBW['q3']^3, PBW['q4']^3] + sage: all(v * g == g * v for g in U.algebra_generators() for v in elts) + True + + An example with a free 4-step nilpotent Lie algebras on 2 generators:: + + sage: L = LieAlgebra(QQ, 2, step=4); L + Free Nilpotent Lie algebra on 8 generators + (X_1, X_2, X_12, X_112, X_122, X_1112, X_1122, X_1222) over Rational Field + sage: U = L.pbw_basis() + sage: Z = U.center() + sage: it = iter(Z.basis()) + sage: center_elts = [next(it) for _ in range(10)]; center_elts + [1, Z[0], Z[1], Z[2], Z[0]^2, Z[0]*Z[1], Z[0]*Z[2], Z[1]^2, Z[1]*Z[2], Z[2]^2] + sage: elts = [U(v) for v in center_elts]; elts + [1, PBW[(1, 1, 1, 2)], PBW[(1, 1, 2, 2)], PBW[(1, 2, 2, 2)], PBW[(1, 1, 1, 2)]^2, + PBW[(1, 1, 1, 2)]*PBW[(1, 1, 2, 2)], PBW[(1, 1, 1, 2)]*PBW[(1, 2, 2, 2)], + PBW[(1, 1, 2, 2)]^2, PBW[(1, 1, 2, 2)]*PBW[(1, 2, 2, 2)], PBW[(1, 2, 2, 2)]^2] + sage: all(v * g == g * v for g in U.algebra_generators() for v in elts) + True + + Using the Engel Lie algebra:: + + sage: L. = LieAlgebra(QQ, {('X','Y'): {'Z': 1}}, nilpotent=True) + sage: U = L.pbw_basis() + sage: Z = U.center() + sage: it = iter(Z.basis()) + sage: center_elts = [next(it) for _ in range(6)]; center_elts + [1, Z[0], Z[0]^2, Z[0]^3, Z[0]^4, Z[0]^5] + sage: elts = [U(v) for v in center_elts]; elts + [1, PBW['Z'], PBW['Z']^2, PBW['Z']^3, PBW['Z']^4, PBW['Z']^5] sage: all(v * g == g * v for g in U.algebra_generators() for v in elts) True """ diff --git a/src/sage/algebras/lie_algebras/poincare_birkhoff_witt.py b/src/sage/algebras/lie_algebras/poincare_birkhoff_witt.py index b47f1391025..2189e60f905 100644 --- a/src/sage/algebras/lie_algebras/poincare_birkhoff_witt.py +++ b/src/sage/algebras/lie_algebras/poincare_birkhoff_witt.py @@ -584,7 +584,6 @@ def center(self): from sage.algebras.lie_algebras.center_uea import CenterUEA return CenterUEA(self._g, self) - class Element(CombinatorialFreeModule.Element): def _act_on_(self, x, self_on_left): """ diff --git a/src/sage/categories/finite_dimensional_lie_algebras_with_basis.py b/src/sage/categories/finite_dimensional_lie_algebras_with_basis.py index d27f91f9083..7cf48f74904 100644 --- a/src/sage/categories/finite_dimensional_lie_algebras_with_basis.py +++ b/src/sage/categories/finite_dimensional_lie_algebras_with_basis.py @@ -1927,7 +1927,6 @@ def to_prod(vec, index): return [UEA.sum(to_prod(vec, index) for index in vec.support()) for vec in tens] - class ElementMethods: def adjoint_matrix(self, sparse=False): # In #11111 (more or less) by using matrix of a morphism """ From 4f929a73dc8be4cade0f9ad9a1c08fe3d00da9b1 Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Tue, 9 Jan 2024 22:31:43 +0900 Subject: [PATCH 217/278] Fixing some details and doctests. --- src/doc/en/reference/references/index.rst | 6 +++--- src/sage/algebras/lie_algebras/poincare_birkhoff_witt.py | 2 +- src/sage/categories/lie_algebras.py | 2 +- src/sage/rings/polynomial/plural.pyx | 7 ++++--- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index 8b19b3322a6..1241a6fcec3 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -4779,9 +4779,9 @@ REFERENCES: of polynomial maps. Proc. London Math. Soc., 68 (1994), 225-263. .. [Motsak2006] Olekasandr Motsak. *Computation of the central elements and - centralizers of sets of elements in non-commutative polynomial - algebras*. PhD Thesis, 2006. - https://kluedo.ub.rptu.de/frontdoor/deliver/index/docId/2308/file/Thesis.pdf + centralizers of sets of elements in non-commutative polynomial + algebras*. PhD Thesis, 2006. + https://kluedo.ub.rptu.de/frontdoor/deliver/index/docId/2308/file/Thesis.pdf .. [MP2019] \M. Montes, D. Penazzi "Yarara and Coral v1" diff --git a/src/sage/algebras/lie_algebras/poincare_birkhoff_witt.py b/src/sage/algebras/lie_algebras/poincare_birkhoff_witt.py index 2189e60f905..b3f73b49494 100644 --- a/src/sage/algebras/lie_algebras/poincare_birkhoff_witt.py +++ b/src/sage/algebras/lie_algebras/poincare_birkhoff_witt.py @@ -577,7 +577,7 @@ def center(self): sage: g = lie_algebras.Heisenberg(GF(3), 4) sage: U = g.pbw_basis() - sage: Z = U.center() + sage: U.center() Center of Universal enveloping algebra of Heisenberg algebra of rank 4 over Finite Field of size 3 in the Poincare-Birkhoff-Witt basis """ diff --git a/src/sage/categories/lie_algebras.py b/src/sage/categories/lie_algebras.py index 38fd5563fb9..74921ea8f65 100644 --- a/src/sage/categories/lie_algebras.py +++ b/src/sage/categories/lie_algebras.py @@ -356,7 +356,7 @@ def center_universal_enveloping_algebra(self, UEA=None): sage: L.center_universal_enveloping_algebra() Center of Universal enveloping algebra of Abelian Lie algebra on 3 generators (x0, x1, x2) over Rational Field in the Poincare-Birkhoff-Witt basis - sage: PBW = L.PBW_basis() + sage: PBW = L.pbw_basis() sage: L.center_universal_enveloping_algebra(PBW) Center of Universal enveloping algebra of Abelian Lie algebra on 3 generators (x0, x1, x2) over Rational Field in the Poincare-Birkhoff-Witt basis diff --git a/src/sage/rings/polynomial/plural.pyx b/src/sage/rings/polynomial/plural.pyx index 0c358f395ff..d8c103e4481 100644 --- a/src/sage/rings/polynomial/plural.pyx +++ b/src/sage/rings/polynomial/plural.pyx @@ -856,7 +856,7 @@ cdef class NCPolynomialRing_plural(Ring): return new_NCP(self,_p) - def algerbra_generators(self): + def algebra_generators(self): r""" Return the algebra generators of ``self``. @@ -865,9 +865,10 @@ cdef class NCPolynomialRing_plural(Ring): sage: A. = FreeAlgebra(QQ, 3) sage: P = A.g_algebra(relations={y*x:-x*y}, order = 'lex') sage: P.algebra_generators() - (x, y, z) + Family (x, y, z) """ - return self.gens() + from sage.sets.family import Family + return Family(self.gens()) def ideal(self, *gens, **kwds): """ From 4ffbe41242c2e72b9eaa4996e40a8fd260dbb012 Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Wed, 21 Feb 2024 10:52:51 +0900 Subject: [PATCH 218/278] Fixing doctest failures due to changes in the plural ring implementation. --- .../algebras/lie_algebras/lie_algebra_element.pyx | 9 +++++---- .../finite_dimensional_lie_algebras_with_basis.py | 2 +- src/sage/categories/lie_algebras_with_basis.py | 13 +++++++------ src/sage/rings/polynomial/plural.pyx | 6 +++--- 4 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/sage/algebras/lie_algebras/lie_algebra_element.pyx b/src/sage/algebras/lie_algebras/lie_algebra_element.pyx index ca53753153b..9e46da295c4 100644 --- a/src/sage/algebras/lie_algebras/lie_algebra_element.pyx +++ b/src/sage/algebras/lie_algebras/lie_algebra_element.pyx @@ -156,10 +156,6 @@ cdef class LieAlgebraElement(IndexedFreeModuleElement): PBW[-1] + PBW[0] - 3*PBW[1] """ UEA = self._parent.universal_enveloping_algebra() - try: - gen_dict = UEA.algebra_generators() - except (TypeError, AttributeError): - gen_dict = UEA.gens_dict() s = UEA.zero() if not self: return s @@ -167,9 +163,14 @@ cdef class LieAlgebraElement(IndexedFreeModuleElement): # does not match the generators index set of the UEA. if hasattr(self._parent, '_UEA_names_map'): names_map = self._parent._UEA_names_map + gen_dict = UEA.gens_dict() for t, c in self._monomial_coefficients.items(): s += c * gen_dict[names_map[t]] else: + try: + gen_dict = UEA.algebra_generators() + except (TypeError, AttributeError): + gen_dict = UEA.gens_dict() for t, c in self._monomial_coefficients.items(): s += c * gen_dict[t] return s diff --git a/src/sage/categories/finite_dimensional_lie_algebras_with_basis.py b/src/sage/categories/finite_dimensional_lie_algebras_with_basis.py index 7cf48f74904..4e1989e34c1 100644 --- a/src/sage/categories/finite_dimensional_lie_algebras_with_basis.py +++ b/src/sage/categories/finite_dimensional_lie_algebras_with_basis.py @@ -7,7 +7,7 @@ """ # **************************************************************************** -# Copyright (C) 2013-2017 Travis Scrimshaw +# Copyright (C) 2013-2024 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 diff --git a/src/sage/categories/lie_algebras_with_basis.py b/src/sage/categories/lie_algebras_with_basis.py index 6eaebfde844..67570b611b3 100644 --- a/src/sage/categories/lie_algebras_with_basis.py +++ b/src/sage/categories/lie_algebras_with_basis.py @@ -7,13 +7,13 @@ """ #***************************************************************************** -# Copyright (C) 2013-2017 Travis Scrimshaw +# Copyright (C) 2013-2024 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.abstract_method import abstract_method @@ -239,10 +239,6 @@ def lift(self): """ P = self.parent() UEA = P.universal_enveloping_algebra() - try: - gen_dict = UEA.algebra_generators() - except (TypeError, AttributeError): - gen_dict = UEA.gens_dict() s = UEA.zero() if not self: return s @@ -250,9 +246,14 @@ def lift(self): # does not match the generators index set of the UEA. if hasattr(P, '_UEA_names_map'): names_map = P._UEA_names_map + gen_dict = UEA.gens_dict() for t, c in self.monomial_coefficients(copy=False).items(): s += c * gen_dict[names_map[t]] else: + try: + gen_dict = UEA.algebra_generators() + except (TypeError, AttributeError): + gen_dict = UEA.gens_dict() for t, c in self.monomial_coefficients(copy=False).items(): s += c * gen_dict[t] return s diff --git a/src/sage/rings/polynomial/plural.pyx b/src/sage/rings/polynomial/plural.pyx index d8c103e4481..4f12afcf5ee 100644 --- a/src/sage/rings/polynomial/plural.pyx +++ b/src/sage/rings/polynomial/plural.pyx @@ -863,12 +863,12 @@ cdef class NCPolynomialRing_plural(Ring): EXAMPLES:: sage: A. = FreeAlgebra(QQ, 3) - sage: P = A.g_algebra(relations={y*x:-x*y}, order = 'lex') + sage: P = A.g_algebra(relations={y*x:-x*y}, order='lex') sage: P.algebra_generators() - Family (x, y, z) + Finite family {'x': x, 'y': y, 'z': z} """ from sage.sets.family import Family - return Family(self.gens()) + return Family(self.gens_dict()) def ideal(self, *gens, **kwds): """ From 95a8ed34bdb10f7801c3bf471383e8cc00ed9f56 Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Wed, 6 Mar 2024 21:49:38 +0900 Subject: [PATCH 219/278] Having is_submodule not error out for different ambient spaces. --- src/sage/combinat/specht_module.py | 2 +- src/sage/modules/with_basis/subquotient.py | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/sage/combinat/specht_module.py b/src/sage/combinat/specht_module.py index 4df915324d0..127903829f7 100644 --- a/src/sage/combinat/specht_module.py +++ b/src/sage/combinat/specht_module.py @@ -84,7 +84,6 @@ def frobenius_image(self): EXAMPLES:: - sage: s = SymmetricFunctions(QQ).s() sage: SM = Partition([2,2,1]).specht_module(QQ) sage: SM.frobenius_image() s[2, 2, 1] @@ -99,6 +98,7 @@ def frobenius_image(self): sage: F = D.specht_module(QQ).frobenius_image(); F s[1, 1, 1, 1, 1] + 4*s[2, 1, 1, 1] + 5*s[2, 2, 1] + 6*s[3, 1, 1] + 5*s[3, 2] + 4*s[4, 1] + s[5] + sage: s = SymmetricFunctions(QQ).s() sage: F == sum(StandardTableaux(la).cardinality() * s[la] ....: for la in Partitions(5)) True diff --git a/src/sage/modules/with_basis/subquotient.py b/src/sage/modules/with_basis/subquotient.py index cadd8bbb7fd..febd1965961 100644 --- a/src/sage/modules/with_basis/subquotient.py +++ b/src/sage/modules/with_basis/subquotient.py @@ -394,21 +394,19 @@ def is_submodule(self, other): sage: H.is_submodule(G) False - TESTS:: + Different ambient spaces:: sage: X = CombinatorialFreeModule(QQ, range(4)); x = X.basis() sage: F = X.submodule([x[0]-x[1], x[1]-x[2], x[2]-x[3]]) sage: Y = CombinatorialFreeModule(QQ, range(6)); y = Y.basis() sage: G = Y.submodule([y[0]-y[1], y[1]-y[2], y[2]-y[3]]) sage: F.is_submodule(G) - Traceback (most recent call last): - ... - ValueError: other (=...) should be a submodule of the same ambient space + False """ if other is self._ambient: return True if not (isinstance(self, SubmoduleWithBasis) and self.ambient() is other.ambient()): - raise ValueError("other (=%s) should be a submodule of the same ambient space" % other) + return False # different ambient spaces if self not in ModulesWithBasis.FiniteDimensional: raise NotImplementedError("only implemented for finite dimensional submodules") if self.dimension() > other.dimension(): # quick dimension check From 336d40293a5638b833b05ca8451f150672b09d80 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 21 Jan 2024 12:27:06 -0800 Subject: [PATCH 220/278] Makefile: Run 'make reconfigure' less often --- Makefile | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 550039a1df6..a4452951cd2 100644 --- a/Makefile +++ b/Makefile @@ -50,7 +50,16 @@ SPKG_COLLECT_FILES = build/pkgs/*/type build/pkgs/*/package-version.txt build/pk # If configure was run before, rerun it with the old arguments. # Otherwise, run configure with argument $PREREQ_OPTIONS. build/make/Makefile: configure $(SPKG_COLLECT_FILES) $(CONFIG_FILES:%=%.in) - $(MAKE) reconfigure + @if [ -x config.status ]; then \ + case '$?' in \ + *configure*) \ + $(MAKE) reconfigure;; \ + *) \ + ./config.status;; \ + esac; \ + else \ + $(MAKE) reconfigure; \ + fi reconfigure: rm -f config.log From 12a938522160863e991418462048537abac579bb Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 21 Jan 2024 12:29:02 -0800 Subject: [PATCH 221/278] Makefile: Update a comment --- Makefile | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index a4452951cd2..c3fbc733162 100644 --- a/Makefile +++ b/Makefile @@ -47,8 +47,10 @@ CONFIG_FILES = build/make/Makefile src/bin/sage-env-config build/bin/sage-build- # SPKG_COLLECT_FILES contains all files that influence the SAGE_SPKG_COLLECT macro SPKG_COLLECT_FILES = build/pkgs/*/type build/pkgs/*/package-version.txt build/pkgs/*/dependencies build/pkgs/*/requirements.txt build/pkgs/*/checksums.ini build/pkgs/*/spkg-install -# If configure was run before, rerun it with the old arguments. -# Otherwise, run configure with argument $PREREQ_OPTIONS. +# If configure was not run before, complain. +# If configure is newer than the files it generated (we test build/make/Makefile), +# we regenerate config.status by running the "config.status --recheck". +# Either way we regenerate the generated files by calling "config.status". build/make/Makefile: configure $(SPKG_COLLECT_FILES) $(CONFIG_FILES:%=%.in) @if [ -x config.status ]; then \ case '$?' in \ From b1695bd20c382346e97ccc0ae29656fe424cce3e Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 3 Mar 2024 14:30:16 -0800 Subject: [PATCH 222/278] build/pkgs/jupyterlab: Update to 4.1.2 --- build/pkgs/jupyterlab/checksums.ini | 6 +++--- build/pkgs/jupyterlab/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/jupyterlab/checksums.ini b/build/pkgs/jupyterlab/checksums.ini index 8ce07466089..0e0d6e823a2 100644 --- a/build/pkgs/jupyterlab/checksums.ini +++ b/build/pkgs/jupyterlab/checksums.ini @@ -1,5 +1,5 @@ tarball=jupyterlab-VERSION-py3-none-any.whl -sha1=06ca895226e055d4bf92f3971eab23035d9c18c7 -md5=a608fbbd9a4616afcc8b0f2e9e0c76ef -cksum=1688505838 +sha1=9579d2c7cb36db3c40637f12a8caa2d69d9d52e9 +md5=7c4a30c4a322b78ffc3eae53800ad7ef +cksum=3781581462 upstream_url=https://pypi.io/packages/py3/j/jupyterlab/jupyterlab-VERSION-py3-none-any.whl diff --git a/build/pkgs/jupyterlab/package-version.txt b/build/pkgs/jupyterlab/package-version.txt index d13e837c8ec..4d0dcda01c4 100644 --- a/build/pkgs/jupyterlab/package-version.txt +++ b/build/pkgs/jupyterlab/package-version.txt @@ -1 +1 @@ -4.0.6 +4.1.2 From dbba40898077e532c97073dde4bb28aa30e5dcf9 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 3 Mar 2024 14:30:25 -0800 Subject: [PATCH 223/278] build/pkgs/notebook: Update to 7.1.1 --- build/pkgs/notebook/checksums.ini | 6 +++--- build/pkgs/notebook/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/notebook/checksums.ini b/build/pkgs/notebook/checksums.ini index 434646c23c3..1fe102fa1f0 100644 --- a/build/pkgs/notebook/checksums.ini +++ b/build/pkgs/notebook/checksums.ini @@ -1,5 +1,5 @@ tarball=notebook-VERSION-py3-none-any.whl -sha1=cd0c99c8a267ced6a451f712007665df88c60d71 -md5=e61e0d6c55bf3920c013554c6dd071b9 -cksum=2202937268 +sha1=90ec65091058ac541a55cc2417de83c1bcb24985 +md5=bf0fe0b56048113cd8a9e2df7add9fe9 +cksum=1004716816 upstream_url=https://pypi.io/packages/py3/n/notebook/notebook-VERSION-py3-none-any.whl diff --git a/build/pkgs/notebook/package-version.txt b/build/pkgs/notebook/package-version.txt index 024b4b9b53a..21c8c7b46b8 100644 --- a/build/pkgs/notebook/package-version.txt +++ b/build/pkgs/notebook/package-version.txt @@ -1 +1 @@ -7.0.6 +7.1.1 From 085073d28ee599d7218bb670ca792df1d7d75825 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 3 Mar 2024 14:37:20 -0800 Subject: [PATCH 224/278] build/pkgs/httpx: New (jupyterlab 4.1.2 dependency) --- build/pkgs/httpx/SPKG.rst | 16 ++++++++++++++++ build/pkgs/httpx/checksums.ini | 5 +++++ build/pkgs/httpx/dependencies | 4 ++++ build/pkgs/httpx/install-requires.txt | 1 + build/pkgs/httpx/package-version.txt | 1 + build/pkgs/httpx/type | 1 + build/pkgs/jupyterlab/dependencies | 2 +- 7 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 build/pkgs/httpx/SPKG.rst create mode 100644 build/pkgs/httpx/checksums.ini create mode 100644 build/pkgs/httpx/dependencies create mode 100644 build/pkgs/httpx/install-requires.txt create mode 100644 build/pkgs/httpx/package-version.txt create mode 100644 build/pkgs/httpx/type diff --git a/build/pkgs/httpx/SPKG.rst b/build/pkgs/httpx/SPKG.rst new file mode 100644 index 00000000000..205a9045614 --- /dev/null +++ b/build/pkgs/httpx/SPKG.rst @@ -0,0 +1,16 @@ +httpx: The next generation HTTP client. +======================================= + +Description +----------- + +The next generation HTTP client. + +License +------- + +Upstream Contact +---------------- + +https://pypi.org/project/httpx/ + diff --git a/build/pkgs/httpx/checksums.ini b/build/pkgs/httpx/checksums.ini new file mode 100644 index 00000000000..11b2b37b2f2 --- /dev/null +++ b/build/pkgs/httpx/checksums.ini @@ -0,0 +1,5 @@ +tarball=httpx-VERSION-py3-none-any.whl +sha1=01f2a657e43842cb7c8dda30d38860fa741acb7e +md5=9bab916547b4c85999c568d12e04edc4 +cksum=4048488492 +upstream_url=https://pypi.io/packages/py3/h/httpx/httpx-VERSION-py3-none-any.whl diff --git a/build/pkgs/httpx/dependencies b/build/pkgs/httpx/dependencies new file mode 100644 index 00000000000..47296a7bace --- /dev/null +++ b/build/pkgs/httpx/dependencies @@ -0,0 +1,4 @@ + | $(PYTHON_TOOLCHAIN) $(PYTHON) + +---------- +All lines of this file are ignored except the first. diff --git a/build/pkgs/httpx/install-requires.txt b/build/pkgs/httpx/install-requires.txt new file mode 100644 index 00000000000..f7621d172eb --- /dev/null +++ b/build/pkgs/httpx/install-requires.txt @@ -0,0 +1 @@ +httpx diff --git a/build/pkgs/httpx/package-version.txt b/build/pkgs/httpx/package-version.txt new file mode 100644 index 00000000000..1b58cc10180 --- /dev/null +++ b/build/pkgs/httpx/package-version.txt @@ -0,0 +1 @@ +0.27.0 diff --git a/build/pkgs/httpx/type b/build/pkgs/httpx/type new file mode 100644 index 00000000000..a6a7b9cd726 --- /dev/null +++ b/build/pkgs/httpx/type @@ -0,0 +1 @@ +standard diff --git a/build/pkgs/jupyterlab/dependencies b/build/pkgs/jupyterlab/dependencies index 5d2f8f03d6a..e32c10b4fd4 100644 --- a/build/pkgs/jupyterlab/dependencies +++ b/build/pkgs/jupyterlab/dependencies @@ -1,4 +1,4 @@ -async_lru importlib_metadata ipykernel jinja2 jupyter_core jupyter_lsp jupyter_server jupyterlab_server notebook_shim packaging traitlets tornado tomli | $(PYTHON_TOOLCHAIN) $(PYTHON) +async_lru importlib_metadata ipykernel jinja2 jupyter_core jupyter_lsp jupyter_server jupyterlab_server notebook_shim packaging traitlets tornado tomli httpx | $(PYTHON_TOOLCHAIN) $(PYTHON) ---------- All lines of this file are ignored except the first. From f262926197f759d52ee9e2f82218260c043ccd5f Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 3 Mar 2024 15:03:08 -0800 Subject: [PATCH 225/278] build/pkgs/h11: New (httpcore dep) --- build/pkgs/h11/SPKG.rst | 18 ++++++++++++++++++ build/pkgs/h11/checksums.ini | 5 +++++ build/pkgs/h11/dependencies | 4 ++++ build/pkgs/h11/install-requires.txt | 1 + build/pkgs/h11/package-version.txt | 1 + build/pkgs/h11/type | 1 + 6 files changed, 30 insertions(+) create mode 100644 build/pkgs/h11/SPKG.rst create mode 100644 build/pkgs/h11/checksums.ini create mode 100644 build/pkgs/h11/dependencies create mode 100644 build/pkgs/h11/install-requires.txt create mode 100644 build/pkgs/h11/package-version.txt create mode 100644 build/pkgs/h11/type diff --git a/build/pkgs/h11/SPKG.rst b/build/pkgs/h11/SPKG.rst new file mode 100644 index 00000000000..c0690498e36 --- /dev/null +++ b/build/pkgs/h11/SPKG.rst @@ -0,0 +1,18 @@ +h11: A pure-Python, bring-your-own-I/O implementation of HTTP/1.1 +================================================================= + +Description +----------- + +A pure-Python, bring-your-own-I/O implementation of HTTP/1.1 + +License +------- + +MIT + +Upstream Contact +---------------- + +https://pypi.org/project/h11/ + diff --git a/build/pkgs/h11/checksums.ini b/build/pkgs/h11/checksums.ini new file mode 100644 index 00000000000..19d629f0852 --- /dev/null +++ b/build/pkgs/h11/checksums.ini @@ -0,0 +1,5 @@ +tarball=h11-VERSION-py3-none-any.whl +sha1=c502d56dc3288212142a398704a5109749331dd8 +md5=47f19bfed53c8f9278749c532490977e +cksum=462065947 +upstream_url=https://pypi.io/packages/py3/h/h11/h11-VERSION-py3-none-any.whl diff --git a/build/pkgs/h11/dependencies b/build/pkgs/h11/dependencies new file mode 100644 index 00000000000..47296a7bace --- /dev/null +++ b/build/pkgs/h11/dependencies @@ -0,0 +1,4 @@ + | $(PYTHON_TOOLCHAIN) $(PYTHON) + +---------- +All lines of this file are ignored except the first. diff --git a/build/pkgs/h11/install-requires.txt b/build/pkgs/h11/install-requires.txt new file mode 100644 index 00000000000..0d24def7113 --- /dev/null +++ b/build/pkgs/h11/install-requires.txt @@ -0,0 +1 @@ +h11 diff --git a/build/pkgs/h11/package-version.txt b/build/pkgs/h11/package-version.txt new file mode 100644 index 00000000000..a803cc227fe --- /dev/null +++ b/build/pkgs/h11/package-version.txt @@ -0,0 +1 @@ +0.14.0 diff --git a/build/pkgs/h11/type b/build/pkgs/h11/type new file mode 100644 index 00000000000..a6a7b9cd726 --- /dev/null +++ b/build/pkgs/h11/type @@ -0,0 +1 @@ +standard From b190680725bcccd548e5ae27abfbdc4d9d8d7e7e Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 3 Mar 2024 15:03:22 -0800 Subject: [PATCH 226/278] build/pkgs/httpcore: New (httpx dep) --- build/pkgs/httpcore/SPKG.rst | 16 ++++++++++++++++ build/pkgs/httpcore/checksums.ini | 5 +++++ build/pkgs/httpcore/dependencies | 4 ++++ build/pkgs/httpcore/install-requires.txt | 1 + build/pkgs/httpcore/package-version.txt | 1 + build/pkgs/httpcore/type | 1 + build/pkgs/httpx/dependencies | 2 +- 7 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 build/pkgs/httpcore/SPKG.rst create mode 100644 build/pkgs/httpcore/checksums.ini create mode 100644 build/pkgs/httpcore/dependencies create mode 100644 build/pkgs/httpcore/install-requires.txt create mode 100644 build/pkgs/httpcore/package-version.txt create mode 100644 build/pkgs/httpcore/type diff --git a/build/pkgs/httpcore/SPKG.rst b/build/pkgs/httpcore/SPKG.rst new file mode 100644 index 00000000000..fed1a457967 --- /dev/null +++ b/build/pkgs/httpcore/SPKG.rst @@ -0,0 +1,16 @@ +httpcore: A minimal low-level HTTP client. +========================================== + +Description +----------- + +A minimal low-level HTTP client. + +License +------- + +Upstream Contact +---------------- + +https://pypi.org/project/httpcore/ + diff --git a/build/pkgs/httpcore/checksums.ini b/build/pkgs/httpcore/checksums.ini new file mode 100644 index 00000000000..81e08584fbe --- /dev/null +++ b/build/pkgs/httpcore/checksums.ini @@ -0,0 +1,5 @@ +tarball=httpcore-VERSION-py3-none-any.whl +sha1=e83eb30232906df8ac673d6c49c657957dc00ce1 +md5=9f99e1b4ed8ba940bd698e21af54ef6d +cksum=2564019022 +upstream_url=https://pypi.io/packages/py3/h/httpcore/httpcore-VERSION-py3-none-any.whl diff --git a/build/pkgs/httpcore/dependencies b/build/pkgs/httpcore/dependencies new file mode 100644 index 00000000000..36787c2193d --- /dev/null +++ b/build/pkgs/httpcore/dependencies @@ -0,0 +1,4 @@ +h11 | $(PYTHON_TOOLCHAIN) $(PYTHON) + +---------- +All lines of this file are ignored except the first. diff --git a/build/pkgs/httpcore/install-requires.txt b/build/pkgs/httpcore/install-requires.txt new file mode 100644 index 00000000000..3adbfb876d5 --- /dev/null +++ b/build/pkgs/httpcore/install-requires.txt @@ -0,0 +1 @@ +httpcore diff --git a/build/pkgs/httpcore/package-version.txt b/build/pkgs/httpcore/package-version.txt new file mode 100644 index 00000000000..ee90284c27f --- /dev/null +++ b/build/pkgs/httpcore/package-version.txt @@ -0,0 +1 @@ +1.0.4 diff --git a/build/pkgs/httpcore/type b/build/pkgs/httpcore/type new file mode 100644 index 00000000000..a6a7b9cd726 --- /dev/null +++ b/build/pkgs/httpcore/type @@ -0,0 +1 @@ +standard diff --git a/build/pkgs/httpx/dependencies b/build/pkgs/httpx/dependencies index 47296a7bace..8df93e6d599 100644 --- a/build/pkgs/httpx/dependencies +++ b/build/pkgs/httpx/dependencies @@ -1,4 +1,4 @@ - | $(PYTHON_TOOLCHAIN) $(PYTHON) +httpcore | $(PYTHON_TOOLCHAIN) $(PYTHON) ---------- All lines of this file are ignored except the first. From 04b6d8ee30ffb280f68e58eec1d4a67b123a2f07 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 6 Mar 2024 18:53:32 -0800 Subject: [PATCH 227/278] build/pkgs/jupyterlab: Update to 4.1.3 --- build/pkgs/jupyterlab/checksums.ini | 6 +++--- build/pkgs/jupyterlab/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/jupyterlab/checksums.ini b/build/pkgs/jupyterlab/checksums.ini index 0e0d6e823a2..f98b0077323 100644 --- a/build/pkgs/jupyterlab/checksums.ini +++ b/build/pkgs/jupyterlab/checksums.ini @@ -1,5 +1,5 @@ tarball=jupyterlab-VERSION-py3-none-any.whl -sha1=9579d2c7cb36db3c40637f12a8caa2d69d9d52e9 -md5=7c4a30c4a322b78ffc3eae53800ad7ef -cksum=3781581462 +sha1=4efdd879660e719fd49be6ec169272f32a16593e +md5=968a2b0458440a6b018ff2863a66e4af +cksum=2309499829 upstream_url=https://pypi.io/packages/py3/j/jupyterlab/jupyterlab-VERSION-py3-none-any.whl diff --git a/build/pkgs/jupyterlab/package-version.txt b/build/pkgs/jupyterlab/package-version.txt index 4d0dcda01c4..de197cc337f 100644 --- a/build/pkgs/jupyterlab/package-version.txt +++ b/build/pkgs/jupyterlab/package-version.txt @@ -1 +1 @@ -4.1.2 +4.1.3 From 8526c3eeca0753c8f5a1b49646de688ab0b33587 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 7 Mar 2024 19:13:21 -0800 Subject: [PATCH 228/278] Makefile: Fix definition of SPKG_COLLECT_FILES --- Makefile | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/Makefile b/Makefile index c3fbc733162..b5286a7439e 100644 --- a/Makefile +++ b/Makefile @@ -44,23 +44,24 @@ SAGE_ROOT_LOGS = logs # except for build/make/Makefile-auto, which is unused by the build system CONFIG_FILES = build/make/Makefile src/bin/sage-env-config build/bin/sage-build-env-config pkgs/sage-conf/_sage_conf/_conf.py -# SPKG_COLLECT_FILES contains all files that influence the SAGE_SPKG_COLLECT macro -SPKG_COLLECT_FILES = build/pkgs/*/type build/pkgs/*/package-version.txt build/pkgs/*/dependencies build/pkgs/*/requirements.txt build/pkgs/*/checksums.ini build/pkgs/*/spkg-install +# SPKG_COLLECT_FILES contains the files that influence the *runtime* of the +# portions of the 'configure' script generated by the SAGE_SPKG_COLLECT macro +SPKG_COLLECT_FILES = build/pkgs/*/package-version.txt build/pkgs/*/dependencies* # If configure was not run before, complain. # If configure is newer than the files it generated (we test build/make/Makefile), # we regenerate config.status by running the "config.status --recheck". # Either way we regenerate the generated files by calling "config.status". build/make/Makefile: configure $(SPKG_COLLECT_FILES) $(CONFIG_FILES:%=%.in) - @if [ -x config.status ]; then \ - case '$?' in \ - *configure*) \ - $(MAKE) reconfigure;; \ - *) \ - ./config.status;; \ - esac; \ - else \ - $(MAKE) reconfigure; \ + @if [ -x config.status ]; then \ + case '$?' in \ + *configure*|*package-version*|*dependencies*) \ + $(MAKE) reconfigure;; \ + *) \ + ./config.status;; \ + esac; \ + else \ + $(MAKE) reconfigure; \ fi reconfigure: @@ -335,7 +336,14 @@ ptestoptionallong-nodoc: ############################################################################### -configure: bootstrap src/doc/bootstrap configure.ac src/bin/sage-version.sh m4/*.m4 build/pkgs/*/spkg-configure.m4 build/pkgs/*/type build/pkgs/*/install-requires.txt build/pkgs/*/package-version.txt build/pkgs/*/distros/*.txt +# The 'configure' script is just one of the files generated by 'bootstrap'. +# CONFIGURE_DEPENDENCIES is the list of files that influence the generation of 'configure'. +CONFIGURE_DEPENDENCIES=configure.ac src/bin/sage-version.sh m4/*.m4 build/pkgs/*/spkg-configure.m4 \ + build/pkgs/*/type build/pkgs/*/SPKG.rst build/pkgs/*/checksums.ini build/pkgs/*/requirements.txt build/pkgs/*/install-requires.txt build/pkgs/*/package-version.txt build/pkgs/*/spkg-install build/pkgs/*/spkg-install.in +# SPKG_INFO_DEPENDENCIES is the list of files that influence the run of 'sage-spkg-info' and hence +# the generation of the files generated in 'src/doc' by 'src/doc/bootstrap'. +SPKG_INFO_DEPENDENCIES=build/pkgs/*/type build/pkgs/*/SPKG.rst build/pkgs/*/requirements.txt build/pkgs/*/install-requires.txt build/pkgs/*/package-version.txt build/pkgs/*/distros/*.txt +configure: bootstrap src/doc/bootstrap $(CONFIGURE_DEPENDENCIES) $(SPKG_INFO_DEPENDENCIES) ./bootstrap -d install: all From 663c2223b355e508301ce1727d0f611bda092b6e Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 7 Mar 2024 21:41:25 -0800 Subject: [PATCH 229/278] Makefile: Reformat variable settings --- Makefile | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index b5286a7439e..56e11c69f50 100644 --- a/Makefile +++ b/Makefile @@ -338,11 +338,22 @@ ptestoptionallong-nodoc: # The 'configure' script is just one of the files generated by 'bootstrap'. # CONFIGURE_DEPENDENCIES is the list of files that influence the generation of 'configure'. -CONFIGURE_DEPENDENCIES=configure.ac src/bin/sage-version.sh m4/*.m4 build/pkgs/*/spkg-configure.m4 \ - build/pkgs/*/type build/pkgs/*/SPKG.rst build/pkgs/*/checksums.ini build/pkgs/*/requirements.txt build/pkgs/*/install-requires.txt build/pkgs/*/package-version.txt build/pkgs/*/spkg-install build/pkgs/*/spkg-install.in +CONFIGURE_DEPENDENCIES = \ + configure.ac src/bin/sage-version.sh m4/*.m4 \ + build/pkgs/*/spkg-configure.m4 \ + build/pkgs/*/type build/pkgs/*/SPKG.rst \ + build/pkgs/*/checksums.ini build/pkgs/*/requirements.txt \ + build/pkgs/*/install-requires.txt build/pkgs/*/package-version.txt \ + build/pkgs/*/spkg-install build/pkgs/*/spkg-install.in + # SPKG_INFO_DEPENDENCIES is the list of files that influence the run of 'sage-spkg-info' and hence # the generation of the files generated in 'src/doc' by 'src/doc/bootstrap'. -SPKG_INFO_DEPENDENCIES=build/pkgs/*/type build/pkgs/*/SPKG.rst build/pkgs/*/requirements.txt build/pkgs/*/install-requires.txt build/pkgs/*/package-version.txt build/pkgs/*/distros/*.txt +SPKG_INFO_DEPENDENCIES = \ + build/pkgs/*/type build/pkgs/*/SPKG.rst \ + build/pkgs/*/requirements.txt \ + build/pkgs/*/install-requires.txt build/pkgs/*/package-version.txt \ + build/pkgs/*/distros/*.txt + configure: bootstrap src/doc/bootstrap $(CONFIGURE_DEPENDENCIES) $(SPKG_INFO_DEPENDENCIES) ./bootstrap -d From cd652c675fe358390d764dc2fcf8603e120e6b8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Tornar=C3=ADa?= Date: Sat, 9 Mar 2024 11:28:34 -0300 Subject: [PATCH 230/278] Workaround warning in cython 3.0.9 (#37560) A new warning breaks doctests. We filter it out. --- src/sage/misc/cython.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/sage/misc/cython.py b/src/sage/misc/cython.py index 081d0562d05..c36374dbfe9 100644 --- a/src/sage/misc/cython.py +++ b/src/sage/misc/cython.py @@ -236,7 +236,7 @@ def cython(filename, verbose=0, compile_message=False, ... RuntimeError: Error compiling Cython file: ... - ...: 'sage/misc.pxd' not found + ...: 'sage/misc.pxd' not found... """ if not filename.endswith('pyx'): print("Warning: file (={}) should have extension .pyx".format(filename), file=sys.stderr) @@ -382,6 +382,12 @@ def cython(filename, verbose=0, compile_message=False, "Placing it before 'except' or 'noexcept' will be disallowed in a future version of Cython.\n", "", cython_messages, 0, re.MULTILINE) + # workaround for https://github.com/sagemath/sage/issues/37560 + # triggered by Cython 3.0.9 + cython_messages = re.sub( + "^warning: .*noexcept clause is ignored for function returning Python object\n", + "", cython_messages, 0, re.MULTILINE) + sys.stderr.write(cython_messages) sys.stderr.flush() From 7a9ce374f19a283c055901a4f3e2008e908afeab Mon Sep 17 00:00:00 2001 From: Dima Pasechnik Date: Fri, 5 Jan 2024 01:11:23 +0000 Subject: [PATCH 231/278] allowing external ECM to be called gmp-ecm, not ecm This is how it is called in Fedora --- build/pkgs/ecm/spkg-configure.m4 | 5 ++++- pkgs/sage-conf/_sage_conf/_conf.py.in | 2 ++ src/sage/env.py | 1 + src/sage/interfaces/ecm.py | 3 ++- 4 files changed, 9 insertions(+), 2 deletions(-) diff --git a/build/pkgs/ecm/spkg-configure.m4 b/build/pkgs/ecm/spkg-configure.m4 index d456f1d8f2c..5b80e4c4044 100644 --- a/build/pkgs/ecm/spkg-configure.m4 +++ b/build/pkgs/ecm/spkg-configure.m4 @@ -1,5 +1,6 @@ SAGE_SPKG_CONFIGURE([ecm], [ m4_pushdef([SAGE_ECM_MINVER],[7.0.4]) + SAGE_ECM_BIN=ecm SAGE_SPKG_DEPCHECK([gmp], [ AC_CHECK_HEADER(ecm.h, [ AX_ABSOLUTE_HEADER([ecm.h]) @@ -18,8 +19,9 @@ SAGE_SPKG_CONFIGURE([ecm], [ AC_SEARCH_LIBS([ecm_factor], [ecm], [], [sage_spkg_install_ecm=yes]) ]) ]) - AC_PATH_PROG([ECMBIN], [ecm]) + AC_PATH_PROGS([ECMBIN], [ecm gmp-ecm]) if test x$ECMBIN != x; then + SAGE_ECM_BIN=`basename $ECMBIN` ecmbin_version=`echo 121 | $ECMBIN 4 | grep ^GMP | $SED -n -e 's/GMP\-ECM \([[0-9]]*\.[[0-9]]*\.[[0-9]]*\).*/\1/p'` fi @@ -34,4 +36,5 @@ SAGE_SPKG_CONFIGURE([ecm], [ ], [sage_spkg_install_ecm=yes]) ]) m4_popdef([SAGE_ECM_MINVER]) + AC_SUBST(SAGE_ECMBIN, $SAGE_ECM_BIN) ]) diff --git a/pkgs/sage-conf/_sage_conf/_conf.py.in b/pkgs/sage-conf/_sage_conf/_conf.py.in index ccc1c9695fb..54a59fdbacf 100644 --- a/pkgs/sage-conf/_sage_conf/_conf.py.in +++ b/pkgs/sage-conf/_sage_conf/_conf.py.in @@ -32,6 +32,8 @@ ECL_CONFIG = "@SAGE_ECL_CONFIG@".replace('${prefix}', SAGE_LOCAL) SAGE_NAUTY_BINS_PREFIX = "@SAGE_NAUTY_BINS_PREFIX@" +SAGE_ECMBIN = "@SAGE_ECMBIN@" + # Names or paths of the 4ti2 executables FOURTITWO_HILBERT = "@FOURTITWO_HILBERT@" FOURTITWO_MARKOV = "@FOURTITWO_MARKOV@" diff --git a/src/sage/env.py b/src/sage/env.py index da9bad48da9..a6a99ca9303 100644 --- a/src/sage/env.py +++ b/src/sage/env.py @@ -220,6 +220,7 @@ def var(key: str, *fallbacks: Optional[str], force: bool = False) -> Optional[st MAXIMA_FAS = var("MAXIMA_FAS") KENZO_FAS = var("KENZO_FAS") SAGE_NAUTY_BINS_PREFIX = var("SAGE_NAUTY_BINS_PREFIX", "") +SAGE_ECMBIN = var("SAGE_ECMBIN") RUBIKS_BINS_PREFIX = var("RUBIKS_BINS_PREFIX", "") FOURTITWO_HILBERT = var("FOURTITWO_HILBERT") FOURTITWO_MARKOV = var("FOURTITWO_MARKOV") diff --git a/src/sage/interfaces/ecm.py b/src/sage/interfaces/ecm.py index a2c882643c4..ae1379861f2 100644 --- a/src/sage/interfaces/ecm.py +++ b/src/sage/interfaces/ecm.py @@ -55,6 +55,7 @@ from sage.structure.sage_object import SageObject from sage.rings.integer_ring import ZZ +from sage.env import SAGE_ECMBIN class ECM(SageObject): @@ -182,7 +183,7 @@ def __init__(self, B1=10, B2=None, **kwds): self._cmd = self._make_cmd(B1, B2, kwds) def _make_cmd(self, B1, B2, kwds): - ecm = ['ecm'] + ecm = [SAGE_ECMBIN] options = [] for x, v in kwds.items(): if v is False: From 2c53f08ef9253a1f84c737403c58e586d2cf358f Mon Sep 17 00:00:00 2001 From: Dima Pasechnik Date: Fri, 5 Jan 2024 09:20:20 +0000 Subject: [PATCH 232/278] add sage.features.ecm --- src/sage/features/ecm.py | 42 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 src/sage/features/ecm.py diff --git a/src/sage/features/ecm.py b/src/sage/features/ecm.py new file mode 100644 index 00000000000..79a1e77918f --- /dev/null +++ b/src/sage/features/ecm.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +r""" +Feature for testing the presence of ``ecm`` or ``gmp-ecm`` +""" +# **************************************************************************** +# Copyright (C) 2032 Dima Pasechnik +# +# 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 . import Executable +from sage.env import SAGE_ECMBIN + + +class Ecm(Executable): + r""" + A :class:`~sage.features.Feature` describing the presence of :ref:`GMP-ECM `. + + EXAMPLES:: + + sage: from sage.features.ecm import Ecm + sage: Ecm().is_present() + FeatureTestResult('ecm', True) + """ + def __init__(self): + r""" + TESTS:: + + sage: from sage.features.ecm import Ecm + sage: isinstance(Ecm(), Ecm) + True + """ + Executable.__init__(self, name="ecm", executable=SAGE_ECMBIN, + spkg="ecm", type="standard") + + +def all_features(): + return [Ecm()] From afeffaa28b494cb9752868b122a0657ca269e59e Mon Sep 17 00:00:00 2001 From: Dima Pasechnik Date: Fri, 5 Jan 2024 09:56:50 +0000 Subject: [PATCH 233/278] replace SAGE_ECMBIN with ECM --- build/pkgs/ecm/spkg-configure.m4 | 2 +- pkgs/sage-conf/_sage_conf/_conf.py.in | 2 +- src/sage/env.py | 2 +- src/sage/features/ecm.py | 4 ++-- src/sage/interfaces/ecm.py | 4 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/build/pkgs/ecm/spkg-configure.m4 b/build/pkgs/ecm/spkg-configure.m4 index 5b80e4c4044..d5da087c148 100644 --- a/build/pkgs/ecm/spkg-configure.m4 +++ b/build/pkgs/ecm/spkg-configure.m4 @@ -36,5 +36,5 @@ SAGE_SPKG_CONFIGURE([ecm], [ ], [sage_spkg_install_ecm=yes]) ]) m4_popdef([SAGE_ECM_MINVER]) - AC_SUBST(SAGE_ECMBIN, $SAGE_ECM_BIN) + AC_SUBST(ECM, $SAGE_ECM_BIN) ]) diff --git a/pkgs/sage-conf/_sage_conf/_conf.py.in b/pkgs/sage-conf/_sage_conf/_conf.py.in index 54a59fdbacf..b4e1d2e750e 100644 --- a/pkgs/sage-conf/_sage_conf/_conf.py.in +++ b/pkgs/sage-conf/_sage_conf/_conf.py.in @@ -32,7 +32,7 @@ ECL_CONFIG = "@SAGE_ECL_CONFIG@".replace('${prefix}', SAGE_LOCAL) SAGE_NAUTY_BINS_PREFIX = "@SAGE_NAUTY_BINS_PREFIX@" -SAGE_ECMBIN = "@SAGE_ECMBIN@" +ECM = "@ECM@" # Names or paths of the 4ti2 executables FOURTITWO_HILBERT = "@FOURTITWO_HILBERT@" diff --git a/src/sage/env.py b/src/sage/env.py index a6a99ca9303..54ca5431133 100644 --- a/src/sage/env.py +++ b/src/sage/env.py @@ -220,7 +220,7 @@ def var(key: str, *fallbacks: Optional[str], force: bool = False) -> Optional[st MAXIMA_FAS = var("MAXIMA_FAS") KENZO_FAS = var("KENZO_FAS") SAGE_NAUTY_BINS_PREFIX = var("SAGE_NAUTY_BINS_PREFIX", "") -SAGE_ECMBIN = var("SAGE_ECMBIN") +ECM = var("ECM") RUBIKS_BINS_PREFIX = var("RUBIKS_BINS_PREFIX", "") FOURTITWO_HILBERT = var("FOURTITWO_HILBERT") FOURTITWO_MARKOV = var("FOURTITWO_MARKOV") diff --git a/src/sage/features/ecm.py b/src/sage/features/ecm.py index 79a1e77918f..1df11efe067 100644 --- a/src/sage/features/ecm.py +++ b/src/sage/features/ecm.py @@ -13,7 +13,7 @@ # **************************************************************************** from . import Executable -from sage.env import SAGE_ECMBIN +from sage.env import ECM class Ecm(Executable): @@ -34,7 +34,7 @@ def __init__(self): sage: isinstance(Ecm(), Ecm) True """ - Executable.__init__(self, name="ecm", executable=SAGE_ECMBIN, + Executable.__init__(self, name="ecm", executable=ECM, spkg="ecm", type="standard") diff --git a/src/sage/interfaces/ecm.py b/src/sage/interfaces/ecm.py index ae1379861f2..33a62fe0bf1 100644 --- a/src/sage/interfaces/ecm.py +++ b/src/sage/interfaces/ecm.py @@ -55,7 +55,7 @@ from sage.structure.sage_object import SageObject from sage.rings.integer_ring import ZZ -from sage.env import SAGE_ECMBIN +from sage.env import ECM class ECM(SageObject): @@ -183,7 +183,7 @@ def __init__(self, B1=10, B2=None, **kwds): self._cmd = self._make_cmd(B1, B2, kwds) def _make_cmd(self, B1, B2, kwds): - ecm = [SAGE_ECMBIN] + ecm = [ECM] options = [] for x, v in kwds.items(): if v is False: From c84929a28de6bbe97b3d5bccbdcd633ba5c89f70 Mon Sep 17 00:00:00 2001 From: Dima Pasechnik Date: Sun, 7 Jan 2024 21:20:48 +0000 Subject: [PATCH 234/278] Revert "replace SAGE_ECMBIN with ECM" This reverts commit e1d3f3797809f567dbebf830d3a44ab3f60cf8cd. --- build/pkgs/ecm/spkg-configure.m4 | 2 +- pkgs/sage-conf/_sage_conf/_conf.py.in | 2 +- src/sage/env.py | 2 +- src/sage/features/ecm.py | 4 ++-- src/sage/interfaces/ecm.py | 4 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/build/pkgs/ecm/spkg-configure.m4 b/build/pkgs/ecm/spkg-configure.m4 index d5da087c148..5b80e4c4044 100644 --- a/build/pkgs/ecm/spkg-configure.m4 +++ b/build/pkgs/ecm/spkg-configure.m4 @@ -36,5 +36,5 @@ SAGE_SPKG_CONFIGURE([ecm], [ ], [sage_spkg_install_ecm=yes]) ]) m4_popdef([SAGE_ECM_MINVER]) - AC_SUBST(ECM, $SAGE_ECM_BIN) + AC_SUBST(SAGE_ECMBIN, $SAGE_ECM_BIN) ]) diff --git a/pkgs/sage-conf/_sage_conf/_conf.py.in b/pkgs/sage-conf/_sage_conf/_conf.py.in index b4e1d2e750e..54a59fdbacf 100644 --- a/pkgs/sage-conf/_sage_conf/_conf.py.in +++ b/pkgs/sage-conf/_sage_conf/_conf.py.in @@ -32,7 +32,7 @@ ECL_CONFIG = "@SAGE_ECL_CONFIG@".replace('${prefix}', SAGE_LOCAL) SAGE_NAUTY_BINS_PREFIX = "@SAGE_NAUTY_BINS_PREFIX@" -ECM = "@ECM@" +SAGE_ECMBIN = "@SAGE_ECMBIN@" # Names or paths of the 4ti2 executables FOURTITWO_HILBERT = "@FOURTITWO_HILBERT@" diff --git a/src/sage/env.py b/src/sage/env.py index 54ca5431133..a6a99ca9303 100644 --- a/src/sage/env.py +++ b/src/sage/env.py @@ -220,7 +220,7 @@ def var(key: str, *fallbacks: Optional[str], force: bool = False) -> Optional[st MAXIMA_FAS = var("MAXIMA_FAS") KENZO_FAS = var("KENZO_FAS") SAGE_NAUTY_BINS_PREFIX = var("SAGE_NAUTY_BINS_PREFIX", "") -ECM = var("ECM") +SAGE_ECMBIN = var("SAGE_ECMBIN") RUBIKS_BINS_PREFIX = var("RUBIKS_BINS_PREFIX", "") FOURTITWO_HILBERT = var("FOURTITWO_HILBERT") FOURTITWO_MARKOV = var("FOURTITWO_MARKOV") diff --git a/src/sage/features/ecm.py b/src/sage/features/ecm.py index 1df11efe067..79a1e77918f 100644 --- a/src/sage/features/ecm.py +++ b/src/sage/features/ecm.py @@ -13,7 +13,7 @@ # **************************************************************************** from . import Executable -from sage.env import ECM +from sage.env import SAGE_ECMBIN class Ecm(Executable): @@ -34,7 +34,7 @@ def __init__(self): sage: isinstance(Ecm(), Ecm) True """ - Executable.__init__(self, name="ecm", executable=ECM, + Executable.__init__(self, name="ecm", executable=SAGE_ECMBIN, spkg="ecm", type="standard") diff --git a/src/sage/interfaces/ecm.py b/src/sage/interfaces/ecm.py index 33a62fe0bf1..ae1379861f2 100644 --- a/src/sage/interfaces/ecm.py +++ b/src/sage/interfaces/ecm.py @@ -55,7 +55,7 @@ from sage.structure.sage_object import SageObject from sage.rings.integer_ring import ZZ -from sage.env import ECM +from sage.env import SAGE_ECMBIN class ECM(SageObject): @@ -183,7 +183,7 @@ def __init__(self, B1=10, B2=None, **kwds): self._cmd = self._make_cmd(B1, B2, kwds) def _make_cmd(self, B1, B2, kwds): - ecm = [ECM] + ecm = [SAGE_ECMBIN] options = [] for x, v in kwds.items(): if v is False: From 2ea4345a959b177d32847b20478d27745abe0ed5 Mon Sep 17 00:00:00 2001 From: Dima Pasechnik Date: Sun, 7 Jan 2024 21:29:45 +0000 Subject: [PATCH 235/278] remove call to baseline --- build/pkgs/ecm/spkg-configure.m4 | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/build/pkgs/ecm/spkg-configure.m4 b/build/pkgs/ecm/spkg-configure.m4 index 5b80e4c4044..dbc76db0001 100644 --- a/build/pkgs/ecm/spkg-configure.m4 +++ b/build/pkgs/ecm/spkg-configure.m4 @@ -1,6 +1,6 @@ SAGE_SPKG_CONFIGURE([ecm], [ m4_pushdef([SAGE_ECM_MINVER],[7.0.4]) - SAGE_ECM_BIN=ecm + ECMBIN=ecm SAGE_SPKG_DEPCHECK([gmp], [ AC_CHECK_HEADER(ecm.h, [ AX_ABSOLUTE_HEADER([ecm.h]) @@ -20,21 +20,20 @@ SAGE_SPKG_CONFIGURE([ecm], [ ]) ]) AC_PATH_PROGS([ECMBIN], [ecm gmp-ecm]) - if test x$ECMBIN != x; then - SAGE_ECM_BIN=`basename $ECMBIN` + AS_IF([test x$ECMBIN != x], [ ecmbin_version=`echo 121 | $ECMBIN 4 | grep ^GMP | $SED -n -e 's/GMP\-ECM \([[0-9]]*\.[[0-9]]*\.[[0-9]]*\).*/\1/p'` - fi - AS_IF([test -n "$ecmbin_version"], [ - AX_COMPARE_VERSION([$ecmbin_version], [ge], [$SAGE_ECM_MINVER], [ - ac_cv_ECMBIN="$ecmbin_version" + AS_IF([test -n "$ecmbin_version"], [ + AX_COMPARE_VERSION([$ecmbin_version], [ge], [$SAGE_ECM_MINVER], [ + ac_cv_ECMBIN="$ecmbin_version" + ]) ]) - ]) + ], [ECMBIN=ecm]) fi AS_IF([test -z "$ac_cv_ECM"], [sage_spkg_install_ecm=yes]) AS_IF([test -z "$ac_cv_ECMBIN"], [sage_spkg_install_ecm=yes]) ], [sage_spkg_install_ecm=yes]) ]) m4_popdef([SAGE_ECM_MINVER]) - AC_SUBST(SAGE_ECMBIN, $SAGE_ECM_BIN) + AC_SUBST(SAGE_ECMBIN, $ECMBIN) ]) From 42adc0404373a6c9d80e0f3427ce192253ebe159 Mon Sep 17 00:00:00 2001 From: Sebastian Oehms <47305845+soehms@users.noreply.github.com> Date: Mon, 11 Mar 2024 08:01:26 +0100 Subject: [PATCH 236/278] Fix docstring in join_feature.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Matthias Köppe --- src/sage/features/join_feature.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/sage/features/join_feature.py b/src/sage/features/join_feature.py index 3f950c7dd48..6470067a31f 100644 --- a/src/sage/features/join_feature.py +++ b/src/sage/features/join_feature.py @@ -152,8 +152,9 @@ def hide(self): def unhide(self): r""" - Revert what :meth:`hide` does. It returns the number of events - a present feature has been hidden. + Revert what :meth:`hide` did. + + OUTPUT: The number of events a present feature has been hidden. EXAMPLES:: From dd7e71462bddb58e269048b5a716ac4dc06cc433 Mon Sep 17 00:00:00 2001 From: Sebastian Oehms <47305845+soehms@users.noreply.github.com> Date: Mon, 11 Mar 2024 08:02:28 +0100 Subject: [PATCH 237/278] Fix typo in databases.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Matthias Köppe --- src/sage/features/databases.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/features/databases.py b/src/sage/features/databases.py index fd166129397..844ed54de17 100644 --- a/src/sage/features/databases.py +++ b/src/sage/features/databases.py @@ -52,7 +52,7 @@ class DatabaseCremona(StaticFile): EXAMPLES:: sage: from sage.features.databases import DatabaseCremona - sage: DatabaseCremona('cremona_mini', type='sandard').is_present() + sage: DatabaseCremona('cremona_mini', type='standard').is_present() FeatureTestResult('database_cremona_mini_ellcurve', True) sage: DatabaseCremona().is_present() # optional - database_cremona_ellcurve FeatureTestResult('database_cremona_ellcurve', True) From eb908b4cd83ce9fd21ecedbce4bb91d2c7e5a8b3 Mon Sep 17 00:00:00 2001 From: Sebastian Oehms <47305845+soehms@users.noreply.github.com> Date: Mon, 11 Mar 2024 08:03:21 +0100 Subject: [PATCH 238/278] Fix docstring in features/__init__.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Matthias Köppe --- src/sage/features/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/sage/features/__init__.py b/src/sage/features/__init__.py index 8cff16a527f..876ba91b601 100644 --- a/src/sage/features/__init__.py +++ b/src/sage/features/__init__.py @@ -402,8 +402,9 @@ def hide(self): def unhide(self): r""" - Revert what :meth:`hide` does. It returns the number of events - a present feature has been hidden. + Revert what :meth:`hide` did. + + OUTPUT: The number of events a present feature has been hidden. EXAMPLES: From 40de3565cf9711518d073eb5397bee5728c577cd Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Mon, 11 Mar 2024 13:33:53 +0000 Subject: [PATCH 239/278] Update Conda installation instructions to use Miniforge --- src/doc/en/installation/conda.rst | 53 +++++++++++++++++-------------- 1 file changed, 30 insertions(+), 23 deletions(-) diff --git a/src/doc/en/installation/conda.rst b/src/doc/en/installation/conda.rst index 58c29b1e424..8e514f3a529 100644 --- a/src/doc/en/installation/conda.rst +++ b/src/doc/en/installation/conda.rst @@ -9,17 +9,17 @@ SageMath can be installed on Linux and macOS via Conda from the Both the ``x86_64`` (Intel) architecture and the ``arm64``/``aarch64`` architectures (including Apple Silicon, M1) are supported. -You will need a working Conda installation: either Mambaforge/Miniforge, +You will need a working Conda installation: either Miniforge (or Mambaforge), Miniconda or Anaconda. If you don't have one yet, we recommend installing -`Mambaforge `_ as +`Miniforge `_ as follows. In a terminal, .. code-block:: shell - $ curl -L -O https://github.com/conda-forge/miniforge/releases/latest/download/Mambaforge-$(uname)-$(uname -m).sh - $ sh Mambaforge-$(uname)-$(uname -m).sh + $ curl -L -O "https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-$(uname)-$(uname -m).sh" + $ bash Miniforge3-$(uname)-$(uname -m).sh -* Mambaforge and Miniforge use conda-forge as the default channel. +* Miniforge (and Mambaforge) use conda-forge as the default channel. * If you are using Miniconda or Anaconda, set it up to use conda-forge: @@ -27,14 +27,9 @@ follows. In a terminal, * Change channel priority to strict: ``conda config --set channel_priority strict`` -Optionally, use `mamba `_, +If you installed Miniforge (or Mambaforge), we recommend to use +`mamba `_ in the following, which uses a faster dependency solver than ``conda``. -If you installed Mambaforge, it is already provided. Otherwise, use - -.. code-block:: shell - - $ conda install mamba - .. _sec-installation-conda-binary: @@ -43,10 +38,17 @@ Installing all of SageMath from conda (not for development) Create a new conda environment containing SageMath, either with ``mamba`` or ``conda``: -.. code-block:: shell +.. tab:: mamba + + .. code-block:: shell - $ mamba create -n sage sage python=X # either - $ conda create -n sage sage python=X # or + $ mamba create -n sage sage python=X + +.. tab:: conda + + .. code-block:: shell + + $ conda create -n sage sage python=X where ``X`` is version of Python, e.g. ``3.9``. @@ -109,17 +111,22 @@ Here we assume that you are using a git checkout. $ export SAGE_NUM_THREADS=24 - - As a recommended step, install the ``mamba`` package manager. If - you skip this step, replace ``mamba`` by ``conda`` in the - following steps:: - - $ conda install mamba - - Create and activate a new conda environment with the dependencies of Sage and a few additional developer tools:: - $ mamba env create --file src/environment-dev-3.11-linux.yml --name sage-dev - $ conda activate sage-dev + .. tab:: mamba + + .. code-block:: shell + + $ mamba env create --file src/environment-dev-3.11-linux.yml --name sage-dev + $ conda activate sage-dev + + .. tab:: conda + + .. code-block:: shell + + $ conda env create --file src/environment-dev-3.11-linux.yml --name sage-dev + $ conda activate sage-dev Alternatively, you can use ``src/environment-3.11-linux.yml`` or ``src/environment-optional-3.11-linux.yml``, which will only install standard From 2a21de72c6e7160b0f0707a026e944a341dfe723 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 13 Feb 2024 17:11:27 -0800 Subject: [PATCH 240/278] src/doc/en/installation/index.rst (Windows, no-dev): Copy conda install instructions here # Conflicts: # src/doc/en/installation/index.rst --- src/doc/en/installation/index.rst | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/doc/en/installation/index.rst b/src/doc/en/installation/index.rst index 21d5649f2e0..5dec6801c4c 100644 --- a/src/doc/en/installation/index.rst +++ b/src/doc/en/installation/index.rst @@ -80,9 +80,25 @@ Windows Then go to the Microsoft Store and install Ubuntu (or another Linux distribution). Start Ubuntu from the start menu. - On the Linux running on WSL, you always have root access, so you - can use any of the installation methods described below for - Linux. + In an Ubuntu (WSL) terminal, type the following commands to install + Sage from conda-forge. + + .. code-block:: shell + + $ curl -L -O https://github.com/conda-forge/miniforge/releases/latest/download/Mambaforge-$(uname)-$(uname -m).sh + $ sh Mambaforge-$(uname)-$(uname -m).sh + $ mamba create -n sage sage python=3.11 + + (If there are any installation failures, please report them to + the conda-forge maintainers by opening a `GitHub Issue for + conda-forge/sage-feedstock `_.) + + You can now start SageMath as follows: + + .. code-block:: shell + + $ conda activate sage + $ sage Linux ===== From c103b67d98404d9aeb56bfa814e113afae9fc1c4 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 13 Feb 2024 17:11:46 -0800 Subject: [PATCH 241/278] Update src/doc/en/installation/index.rst Co-authored-by: Tobias Diez # Conflicts: # src/doc/en/installation/index.rst --- src/doc/en/installation/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/doc/en/installation/index.rst b/src/doc/en/installation/index.rst index 5dec6801c4c..a0b50bcf756 100644 --- a/src/doc/en/installation/index.rst +++ b/src/doc/en/installation/index.rst @@ -80,7 +80,7 @@ Windows Then go to the Microsoft Store and install Ubuntu (or another Linux distribution). Start Ubuntu from the start menu. - In an Ubuntu (WSL) terminal, type the following commands to install + Type the following commands to install Sage from conda-forge. .. code-block:: shell From 95f900c44babcb7d0c23d81a2eeb354104e71fa0 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 3 Mar 2024 11:45:12 -0800 Subject: [PATCH 242/278] README.md, src/doc/en/installation/index.rst: Update links to WSL setup guide --- README.md | 9 +++++---- src/doc/en/installation/index.rst | 4 ++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 8add84575d8..bfcafebffea 100644 --- a/README.md +++ b/README.md @@ -78,10 +78,11 @@ mailing list](https://groups.google.com/group/sage-devel). [Windows] Preparing the Platform -------------------------------- -The preferred way to run Sage on Windows is using the [Windows Subsystem for -Linux](https://docs.microsoft.com/en-us/windows/wsl/faq), a.k.a. WSL, which allows -you to install a standard Linux distribution such as Ubuntu within -your Windows. Make sure you allocate WSL sufficient RAM; 5GB is known to work, while +The preferred way to run Sage on Windows is using Windows Subsystem for +Linux (WSL). Follow the +[official WSL setup guide](https://docs.microsoft.com/en-us/windows/wsl/faq) +to install Ubuntu (or another Linux distribution). +Make sure you allocate WSL sufficient RAM; 5GB is known to work, while 2GB might be not enough for building Sage from source. Then all instructions for installation in Linux apply. diff --git a/src/doc/en/installation/index.rst b/src/doc/en/installation/index.rst index a0b50bcf756..8e50218f37e 100644 --- a/src/doc/en/installation/index.rst +++ b/src/doc/en/installation/index.rst @@ -60,7 +60,7 @@ Windows Enable Windows Subsystem for Linux (WSL) by following the `official WSL setup guide - `_. Be + `_. Be sure to do the steps to install WSL2 and set it as default. Make sure to allocate enough RAM to WSL: 5GB is known to be enough, 2GB might not allow you to build some packages. @@ -73,7 +73,7 @@ Windows Enable Windows Subsystem for Linux (WSL) by following the `official WSL setup guide - `_. Be + `_. Be sure to do the steps to install WSL2 and set it as default. Make sure to allocate enough RAM to WSL: 5GB is known to be enough, 2GB might not allow you to build some packages. From a8c409d5cbd1768ed0fc60ef351a372d12f079e6 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 3 Mar 2024 12:50:18 -0800 Subject: [PATCH 243/278] src/doc/en/installation/index.rst: More detailed WSL instructions --- src/doc/en/installation/index.rst | 71 ++++++++++++++++++++++--------- 1 file changed, 51 insertions(+), 20 deletions(-) diff --git a/src/doc/en/installation/index.rst b/src/doc/en/installation/index.rst index 8e50218f37e..0a6015e00be 100644 --- a/src/doc/en/installation/index.rst +++ b/src/doc/en/installation/index.rst @@ -58,30 +58,61 @@ Windows - **Yes, development:** - Enable Windows Subsystem for Linux (WSL) by following the - `official WSL setup guide - `_. Be - sure to do the steps to install WSL2 and set it as default. - Make sure to allocate enough RAM to WSL: 5GB is known to be enough, - 2GB might not allow you to build some packages. - Then go to the Microsoft Store and install Ubuntu (or another - Linux distribution). Start Ubuntu from the start menu. + Enable `Windows Subsystem for Linux (WSL) + ` and install + Ubuntu as follows. - Then follow the instructions for development on Linux below. + - `Run the command ``wsl --install`` as administrator. + `_ + This will install Ubuntu Linux. + + Note that the basic instructions in the linked article apply to + up-to-date installations of Windows 10 and 11, but there are + also links to the procedures for older builds of Windows 10. + + - If you had installed WSL previously or installed it using + different instructions, `verify that you are running WSL 2 + `_. + + - `Set up your Linux username and password. + `_. + Do not include any spaces in your username. + + - If your computer has less than 10GB of RAM, `change the WSL settings + `_ + to make at least 5GB of RAM available to WSL. + + Start Ubuntu from the Start menu. Then follow the instructions for + development on Linux below. - **No development:** - Enable Windows Subsystem for Linux (WSL) by following the - `official WSL setup guide - `_. Be - sure to do the steps to install WSL2 and set it as default. - Make sure to allocate enough RAM to WSL: 5GB is known to be enough, - 2GB might not allow you to build some packages. - Then go to the Microsoft Store and install Ubuntu (or another - Linux distribution). Start Ubuntu from the start menu. - - Type the following commands to install - Sage from conda-forge. + Enable `Windows Subsystem for Linux (WSL) + ` and install + Ubuntu as follows. + + - `Run the command ``wsl --install`` as administrator. + `_ + This will install Ubuntu Linux. + + Note that the basic instructions in the linked article apply to + up-to-date installations of Windows 10 and 11, but there are + also links to the procedures for older builds of Windows 10. + + - If you had installed WSL previously or installed it using + different instructions, `verify that you are running WSL 2 + `_. + + - `Set up your Linux username and password. + `_. + Do not include any spaces in your username. + + - If your computer has less than 8GB of RAM, `change the WSL settings + `_ + to make at least 4GB of RAM available to WSL. + + Start Ubuntu from the Start menu, and type the following commands + to install Sage from conda-forge. .. code-block:: shell From 8edc8b55186a7f6ec1cf841be83b4ff35b48d451 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 3 Mar 2024 13:20:41 -0800 Subject: [PATCH 244/278] src/doc/en/installation/index.rst: Include EFI/BIOS step --- src/doc/en/installation/index.rst | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/doc/en/installation/index.rst b/src/doc/en/installation/index.rst index 0a6015e00be..9ec13e4361f 100644 --- a/src/doc/en/installation/index.rst +++ b/src/doc/en/installation/index.rst @@ -16,6 +16,8 @@ SageMath release. **Where would you like to run SageMath?** Pick one of the following sections. +.. _installation-guide-macos: + macOS ===== @@ -51,6 +53,8 @@ macOS - Alternatively, build SageMath from source as described in section :ref:`sec-installation-from-sources`. +.. _installation-guide-windows: + Windows ======= @@ -62,6 +66,10 @@ Windows ` and install Ubuntu as follows. + - Make sure that hardware-assisted virtualization is enabled in + the EFI or BIOS of your system. If in doubt, refer to your + system's documentation for instructions on how to do this. + - `Run the command ``wsl --install`` as administrator. `_ This will install Ubuntu Linux. @@ -91,6 +99,10 @@ Windows ` and install Ubuntu as follows. + - Make sure that hardware-assisted virtualization is enabled in + the EFI or BIOS of your system. If in doubt, refer to your + system's documentation for instructions on how to do this. + - `Run the command ``wsl --install`` as administrator. `_ This will install Ubuntu Linux. @@ -131,6 +143,8 @@ Windows $ conda activate sage $ sage +.. _installation-guide-linux: + Linux ===== From ddbfad2929249a2fabb4185c78396220a2271e88 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 3 Mar 2024 13:21:15 -0800 Subject: [PATCH 245/278] src/doc/en/installation/source.rst: Reduce section on WSL prerequisities by linking to index.rst instead --- src/doc/en/installation/source.rst | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/src/doc/en/installation/source.rst b/src/doc/en/installation/source.rst index 26454e4d6d3..d365b399b88 100644 --- a/src/doc/en/installation/source.rst +++ b/src/doc/en/installation/source.rst @@ -264,24 +264,10 @@ WSL prerequisites Ubuntu on Windows Subsystem for Linux (WSL) prerequisite installation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Sage can be installed onto Linux running on Windows Subsystem for Linux (WSL). These instructions describe a fresh install of Ubuntu 20.10, but other distributions or installation methods should work too, though have not been tested. - -- Enable hardware-assisted virtualization in the EFI or BIOS of your system. Refer to your system (or motherboard) maker's documentation for instructions on how to do this. - -- Set up WSL by following the `official WSL setup guide `_. Be sure to do the steps to install WSL2 and set it as default. - -- Go to the Microsoft Store and install Ubuntu. - -- Start Ubuntu from the start menu. Update all packages to the latest version. - -- Reboot the all running WSL instances one of the following ways: - - - Open Windows Services and restart the LxssManager service. - - Open the Command Prompt or Powershell and enter this command:: - - wsl --shutdown - -- `Upgrade to the Ubuntu 20.10 `_. This step will not be necessary once Ubuntu 20.10 is available in the Microsoft Store. +Refer to :ref:`installation-guide-windows` for installing Ubuntu on +Windows Subsystem for Linux (WSL). These instructions describe a fresh +install of Ubuntu, the default distribution in WSL, but other +distributions or installation methods should work too. From this point on, follow the instructions in the :ref:`sec-installation-from-sources-linux-recommended-installation` section. It is strongly recommended to put the Sage source files in the Linux file system, for example, in the ``/home/username/sage`` directory, and not in the Windows file system (e.g. ``/mnt/c/...``). From dc0addb6b6bbbefa92b6312681ec339a9df63e35 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 4 Mar 2024 17:39:00 -0800 Subject: [PATCH 246/278] src/doc/en/installation/index.rst: Fix rst markup --- src/doc/en/installation/index.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/doc/en/installation/index.rst b/src/doc/en/installation/index.rst index 9ec13e4361f..553950c386f 100644 --- a/src/doc/en/installation/index.rst +++ b/src/doc/en/installation/index.rst @@ -63,14 +63,14 @@ Windows - **Yes, development:** Enable `Windows Subsystem for Linux (WSL) - ` and install + `_ and install Ubuntu as follows. - Make sure that hardware-assisted virtualization is enabled in the EFI or BIOS of your system. If in doubt, refer to your system's documentation for instructions on how to do this. - - `Run the command ``wsl --install`` as administrator. + - `Run the WSL install command as administrator. `_ This will install Ubuntu Linux. @@ -83,7 +83,7 @@ Windows `_. - `Set up your Linux username and password. - `_. + `_ Do not include any spaces in your username. - If your computer has less than 10GB of RAM, `change the WSL settings @@ -96,14 +96,14 @@ Windows - **No development:** Enable `Windows Subsystem for Linux (WSL) - ` and install + `_ and install Ubuntu as follows. - Make sure that hardware-assisted virtualization is enabled in the EFI or BIOS of your system. If in doubt, refer to your system's documentation for instructions on how to do this. - - `Run the command ``wsl --install`` as administrator. + - `Run the WSL install command as administrator. `_ This will install Ubuntu Linux. @@ -116,7 +116,7 @@ Windows `_. - `Set up your Linux username and password. - `_. + `_ Do not include any spaces in your username. - If your computer has less than 8GB of RAM, `change the WSL settings From 0d69b31a5f9612acd2903380b066f37433e76045 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 11 Mar 2024 10:24:04 -0700 Subject: [PATCH 247/278] src/doc/en/installation/index.rst: Update conda instructions from conda.rst --- src/doc/en/installation/index.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/doc/en/installation/index.rst b/src/doc/en/installation/index.rst index 553950c386f..a2c27cbcd4f 100644 --- a/src/doc/en/installation/index.rst +++ b/src/doc/en/installation/index.rst @@ -128,9 +128,9 @@ Windows .. code-block:: shell - $ curl -L -O https://github.com/conda-forge/miniforge/releases/latest/download/Mambaforge-$(uname)-$(uname -m).sh - $ sh Mambaforge-$(uname)-$(uname -m).sh - $ mamba create -n sage sage python=3.11 + $ curl -L -O "https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-$(uname)-$(uname -m).sh" + $ bash Miniforge3-$(uname)-$(uname -m).sh + $ conda create -n sage sage python=3.11 (If there are any installation failures, please report them to the conda-forge maintainers by opening a `GitHub Issue for From 1835ed48e3925f30a4eccba793a3688dc557bfe1 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 11 Mar 2024 18:53:36 +0100 Subject: [PATCH 248/278] 36741: Insert lost argument --- src/sage/features/__init__.py | 6 +++--- src/sage/features/gap.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sage/features/__init__.py b/src/sage/features/__init__.py index 876ba91b601..7847d066b1e 100644 --- a/src/sage/features/__init__.py +++ b/src/sage/features/__init__.py @@ -28,7 +28,7 @@ Here we test whether the grape GAP package is available:: sage: from sage.features.gap import GapPackage - sage: GapPackage("grape", spkg="gap_packages").is_present() # optional - gap_packages + sage: GapPackage("grape", spkg="gap_packages").is_present() # optional - gap_package_grape FeatureTestResult('gap_package_grape', True) Note that a :class:`FeatureTestResult` acts like a bool in most contexts:: @@ -188,7 +188,7 @@ def is_present(self): EXAMPLES:: sage: from sage.features.gap import GapPackage - sage: GapPackage("grape", spkg="gap_packages").is_present() # optional - gap_packages + sage: GapPackage("grape", spkg="gap_packages").is_present() # optional - gap_package_grape FeatureTestResult('gap_package_grape', True) sage: GapPackage("NOT_A_PACKAGE", spkg="gap_packages").is_present() FeatureTestResult('gap_package_NOT_A_PACKAGE', False) @@ -809,7 +809,7 @@ class StaticFile(FileFeature): To install no_such_file...you can try to run...sage -i some_spkg... Further installation instructions might be available at http://rand.om. """ - def __init__(self, name, filename, search_path=None, type='optional', **kwds): + def __init__(self, name, filename, *, search_path=None, type='optional', **kwds): r""" TESTS:: diff --git a/src/sage/features/gap.py b/src/sage/features/gap.py index df5545e9c07..314ba1cc514 100644 --- a/src/sage/features/gap.py +++ b/src/sage/features/gap.py @@ -53,7 +53,7 @@ def _is_present(self): EXAMPLES:: sage: from sage.features.gap import GapPackage - sage: GapPackage("grape", spkg="gap_packages")._is_present() # optional - gap_packages + sage: GapPackage("grape", spkg="gap_packages")._is_present() # optional - gap_package_grape FeatureTestResult('gap_package_grape', True) """ try: From 03670bd2c216367875e392e9494be53d2aa5e9b1 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 11 Mar 2024 22:29:28 +0100 Subject: [PATCH 249/278] 36741: pycodestyle fixes --- src/sage/features/__init__.py | 3 +-- src/sage/features/join_feature.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/sage/features/__init__.py b/src/sage/features/__init__.py index 7847d066b1e..6af9413e55a 100644 --- a/src/sage/features/__init__.py +++ b/src/sage/features/__init__.py @@ -403,7 +403,7 @@ def hide(self): def unhide(self): r""" Revert what :meth:`hide` did. - + OUTPUT: The number of events a present feature has been hidden. EXAMPLES: @@ -423,7 +423,6 @@ def unhide(self): return int(num_hidings) - class FeatureNotPresentError(RuntimeError): r""" A missing feature error. diff --git a/src/sage/features/join_feature.py b/src/sage/features/join_feature.py index 6470067a31f..24c6583c123 100644 --- a/src/sage/features/join_feature.py +++ b/src/sage/features/join_feature.py @@ -153,7 +153,7 @@ def hide(self): def unhide(self): r""" Revert what :meth:`hide` did. - + OUTPUT: The number of events a present feature has been hidden. EXAMPLES:: From d3a2a16d90d8409fb02a4782a6ae3e897e539a81 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 14 Mar 2024 19:46:49 -0700 Subject: [PATCH 250/278] src/doc/en/installation/index.rst: Prepare user for questions asked by miniforge installer --- src/doc/en/installation/index.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/doc/en/installation/index.rst b/src/doc/en/installation/index.rst index a2c27cbcd4f..5dcd44932fb 100644 --- a/src/doc/en/installation/index.rst +++ b/src/doc/en/installation/index.rst @@ -124,7 +124,10 @@ Windows to make at least 4GB of RAM available to WSL. Start Ubuntu from the Start menu, and type the following commands - to install Sage from conda-forge. + to install Sage from conda-forge. (The ``$`` represents the command + line prompt, don't type it!) The second step will ask a few questions, + and you may need to hit :kbd:`Enter` to confirm or type ``yes`` + and then hit :kbd:`Enter`. .. code-block:: shell From 4eacb7be249eea73761531c35ccc03fede5a89bc Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 14 Mar 2024 20:15:13 -0700 Subject: [PATCH 251/278] src/doc/en/installation/index.rst: At the end of the Windows-no-dev section, point to Launching --- src/doc/en/installation/index.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/doc/en/installation/index.rst b/src/doc/en/installation/index.rst index 5dcd44932fb..f88a92dc5c7 100644 --- a/src/doc/en/installation/index.rst +++ b/src/doc/en/installation/index.rst @@ -146,6 +146,11 @@ Windows $ conda activate sage $ sage + This way of starting Sage gives you the most basic way of using + Sage in the terminal. See :ref:`sec-launching` for recommended next steps, + in particular for setting up the Jupyter notebook, which is required if + you want to use graphics. + .. _installation-guide-linux: Linux From ab1a517b64b02bf15bbcb8d7c2d4d643bd5eff9b Mon Sep 17 00:00:00 2001 From: Release Manager Date: Sun, 17 Mar 2024 15:08:46 +0100 Subject: [PATCH 252/278] Updated SageMath version to 10.3.rc4 --- CITATION.cff | 4 ++-- VERSION.txt | 2 +- build/pkgs/configure/checksums.ini | 6 +++--- build/pkgs/configure/package-version.txt | 2 +- build/pkgs/sage_conf/install-requires.txt | 2 +- build/pkgs/sage_docbuild/install-requires.txt | 2 +- build/pkgs/sage_setup/install-requires.txt | 2 +- build/pkgs/sage_sws2rst/install-requires.txt | 2 +- build/pkgs/sagelib/install-requires.txt | 2 +- build/pkgs/sagemath_bliss/install-requires.txt | 2 +- build/pkgs/sagemath_categories/install-requires.txt | 2 +- build/pkgs/sagemath_coxeter3/install-requires.txt | 2 +- build/pkgs/sagemath_environment/install-requires.txt | 2 +- build/pkgs/sagemath_mcqd/install-requires.txt | 2 +- build/pkgs/sagemath_meataxe/install-requires.txt | 2 +- build/pkgs/sagemath_objects/install-requires.txt | 2 +- build/pkgs/sagemath_repl/install-requires.txt | 2 +- build/pkgs/sagemath_sirocco/install-requires.txt | 2 +- build/pkgs/sagemath_tdlib/install-requires.txt | 2 +- pkgs/sage-conf/VERSION.txt | 2 +- pkgs/sage-conf_conda/VERSION.txt | 2 +- pkgs/sage-conf_pypi/VERSION.txt | 2 +- pkgs/sage-docbuild/VERSION.txt | 2 +- pkgs/sage-setup/VERSION.txt | 2 +- pkgs/sage-sws2rst/VERSION.txt | 2 +- pkgs/sagemath-bliss/VERSION.txt | 2 +- pkgs/sagemath-categories/VERSION.txt | 2 +- pkgs/sagemath-coxeter3/VERSION.txt | 2 +- pkgs/sagemath-environment/VERSION.txt | 2 +- pkgs/sagemath-mcqd/VERSION.txt | 2 +- pkgs/sagemath-meataxe/VERSION.txt | 2 +- pkgs/sagemath-objects/VERSION.txt | 2 +- pkgs/sagemath-repl/VERSION.txt | 2 +- pkgs/sagemath-sirocco/VERSION.txt | 2 +- pkgs/sagemath-tdlib/VERSION.txt | 2 +- src/VERSION.txt | 2 +- src/bin/sage-version.sh | 6 +++--- src/sage/version.py | 6 +++--- 38 files changed, 45 insertions(+), 45 deletions(-) diff --git a/CITATION.cff b/CITATION.cff index d5f292bfe0c..95876d7d4e4 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -4,8 +4,8 @@ title: SageMath abstract: SageMath is a free open-source mathematics software system. authors: - name: "The SageMath Developers" -version: 10.3.rc3 +version: 10.3.rc4 doi: 10.5281/zenodo.593563 -date-released: 2024-03-10 +date-released: 2024-03-17 repository-code: "https://github.com/sagemath/sage" url: "https://www.sagemath.org/" diff --git a/VERSION.txt b/VERSION.txt index 8060c85cf47..ba55211dc5e 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -SageMath version 10.3.rc3, Release Date: 2024-03-10 +SageMath version 10.3.rc4, Release Date: 2024-03-17 diff --git a/build/pkgs/configure/checksums.ini b/build/pkgs/configure/checksums.ini index 3b5a2be2e85..f175ddc1636 100644 --- a/build/pkgs/configure/checksums.ini +++ b/build/pkgs/configure/checksums.ini @@ -1,4 +1,4 @@ tarball=configure-VERSION.tar.gz -sha1=ecd112699d45a08acc904081495200200aa5375d -md5=15351e13c37ab18a3dca25db30f45e33 -cksum=1274317165 +sha1=f06e82486ef07850394e6b6d7f226b70c8330d47 +md5=9dd6037651626866f7b6cf790e45b2dd +cksum=2951679715 diff --git a/build/pkgs/configure/package-version.txt b/build/pkgs/configure/package-version.txt index 3237b99e65b..303ba93c6f2 100644 --- a/build/pkgs/configure/package-version.txt +++ b/build/pkgs/configure/package-version.txt @@ -1 +1 @@ -86e1c3569bfb8b90061cf3aa01801677d0b9cc9e +83d81f55354e4732096fbe0c0cd750eb6334e033 diff --git a/build/pkgs/sage_conf/install-requires.txt b/build/pkgs/sage_conf/install-requires.txt index 950b2d85e30..b73225585e4 100644 --- a/build/pkgs/sage_conf/install-requires.txt +++ b/build/pkgs/sage_conf/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sage-conf ~= 10.3rc3 +sage-conf ~= 10.3rc4 diff --git a/build/pkgs/sage_docbuild/install-requires.txt b/build/pkgs/sage_docbuild/install-requires.txt index fb8bd4c6ec8..7d8263cbc62 100644 --- a/build/pkgs/sage_docbuild/install-requires.txt +++ b/build/pkgs/sage_docbuild/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sage-docbuild ~= 10.3rc3 +sage-docbuild ~= 10.3rc4 diff --git a/build/pkgs/sage_setup/install-requires.txt b/build/pkgs/sage_setup/install-requires.txt index bc940c95321..89568499d00 100644 --- a/build/pkgs/sage_setup/install-requires.txt +++ b/build/pkgs/sage_setup/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sage-setup ~= 10.3rc3 +sage-setup ~= 10.3rc4 diff --git a/build/pkgs/sage_sws2rst/install-requires.txt b/build/pkgs/sage_sws2rst/install-requires.txt index 13d17bb1db4..3cc53bf8f37 100644 --- a/build/pkgs/sage_sws2rst/install-requires.txt +++ b/build/pkgs/sage_sws2rst/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sage-sws2rst ~= 10.3rc3 +sage-sws2rst ~= 10.3rc4 diff --git a/build/pkgs/sagelib/install-requires.txt b/build/pkgs/sagelib/install-requires.txt index 0b0a3eaf0c3..417573d6f4d 100644 --- a/build/pkgs/sagelib/install-requires.txt +++ b/build/pkgs/sagelib/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-standard ~= 10.3rc3 +sagemath-standard ~= 10.3rc4 diff --git a/build/pkgs/sagemath_bliss/install-requires.txt b/build/pkgs/sagemath_bliss/install-requires.txt index c200c1b020f..699d6d369b9 100644 --- a/build/pkgs/sagemath_bliss/install-requires.txt +++ b/build/pkgs/sagemath_bliss/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-bliss ~= 10.3rc3 +sagemath-bliss ~= 10.3rc4 diff --git a/build/pkgs/sagemath_categories/install-requires.txt b/build/pkgs/sagemath_categories/install-requires.txt index df3db476971..e78428baa84 100644 --- a/build/pkgs/sagemath_categories/install-requires.txt +++ b/build/pkgs/sagemath_categories/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-categories ~= 10.3rc3 +sagemath-categories ~= 10.3rc4 diff --git a/build/pkgs/sagemath_coxeter3/install-requires.txt b/build/pkgs/sagemath_coxeter3/install-requires.txt index 9045ba17ccb..1e3289d9826 100644 --- a/build/pkgs/sagemath_coxeter3/install-requires.txt +++ b/build/pkgs/sagemath_coxeter3/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-coxeter3 ~= 10.3rc3 +sagemath-coxeter3 ~= 10.3rc4 diff --git a/build/pkgs/sagemath_environment/install-requires.txt b/build/pkgs/sagemath_environment/install-requires.txt index 84043a926a3..0ad75840b0b 100644 --- a/build/pkgs/sagemath_environment/install-requires.txt +++ b/build/pkgs/sagemath_environment/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-environment ~= 10.3rc3 +sagemath-environment ~= 10.3rc4 diff --git a/build/pkgs/sagemath_mcqd/install-requires.txt b/build/pkgs/sagemath_mcqd/install-requires.txt index b2eb7ac5cb8..28daaa77a8b 100644 --- a/build/pkgs/sagemath_mcqd/install-requires.txt +++ b/build/pkgs/sagemath_mcqd/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-mcqd ~= 10.3rc3 +sagemath-mcqd ~= 10.3rc4 diff --git a/build/pkgs/sagemath_meataxe/install-requires.txt b/build/pkgs/sagemath_meataxe/install-requires.txt index 4abbc869ca9..43ff0fd958e 100644 --- a/build/pkgs/sagemath_meataxe/install-requires.txt +++ b/build/pkgs/sagemath_meataxe/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-meataxe ~= 10.3rc3 +sagemath-meataxe ~= 10.3rc4 diff --git a/build/pkgs/sagemath_objects/install-requires.txt b/build/pkgs/sagemath_objects/install-requires.txt index 106eef70a91..385101bcc9a 100644 --- a/build/pkgs/sagemath_objects/install-requires.txt +++ b/build/pkgs/sagemath_objects/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-objects ~= 10.3rc3 +sagemath-objects ~= 10.3rc4 diff --git a/build/pkgs/sagemath_repl/install-requires.txt b/build/pkgs/sagemath_repl/install-requires.txt index 752e7f07956..ef39766e021 100644 --- a/build/pkgs/sagemath_repl/install-requires.txt +++ b/build/pkgs/sagemath_repl/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-repl ~= 10.3rc3 +sagemath-repl ~= 10.3rc4 diff --git a/build/pkgs/sagemath_sirocco/install-requires.txt b/build/pkgs/sagemath_sirocco/install-requires.txt index c40c697ee2f..5b072cd2588 100644 --- a/build/pkgs/sagemath_sirocco/install-requires.txt +++ b/build/pkgs/sagemath_sirocco/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-sirocco ~= 10.3rc3 +sagemath-sirocco ~= 10.3rc4 diff --git a/build/pkgs/sagemath_tdlib/install-requires.txt b/build/pkgs/sagemath_tdlib/install-requires.txt index f43f4c1fbc5..9ed823b39ee 100644 --- a/build/pkgs/sagemath_tdlib/install-requires.txt +++ b/build/pkgs/sagemath_tdlib/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-tdlib ~= 10.3rc3 +sagemath-tdlib ~= 10.3rc4 diff --git a/pkgs/sage-conf/VERSION.txt b/pkgs/sage-conf/VERSION.txt index befccbfa1a1..8c5dddbbe19 100644 --- a/pkgs/sage-conf/VERSION.txt +++ b/pkgs/sage-conf/VERSION.txt @@ -1 +1 @@ -10.3.rc3 +10.3.rc4 diff --git a/pkgs/sage-conf_conda/VERSION.txt b/pkgs/sage-conf_conda/VERSION.txt index befccbfa1a1..8c5dddbbe19 100644 --- a/pkgs/sage-conf_conda/VERSION.txt +++ b/pkgs/sage-conf_conda/VERSION.txt @@ -1 +1 @@ -10.3.rc3 +10.3.rc4 diff --git a/pkgs/sage-conf_pypi/VERSION.txt b/pkgs/sage-conf_pypi/VERSION.txt index befccbfa1a1..8c5dddbbe19 100644 --- a/pkgs/sage-conf_pypi/VERSION.txt +++ b/pkgs/sage-conf_pypi/VERSION.txt @@ -1 +1 @@ -10.3.rc3 +10.3.rc4 diff --git a/pkgs/sage-docbuild/VERSION.txt b/pkgs/sage-docbuild/VERSION.txt index befccbfa1a1..8c5dddbbe19 100644 --- a/pkgs/sage-docbuild/VERSION.txt +++ b/pkgs/sage-docbuild/VERSION.txt @@ -1 +1 @@ -10.3.rc3 +10.3.rc4 diff --git a/pkgs/sage-setup/VERSION.txt b/pkgs/sage-setup/VERSION.txt index befccbfa1a1..8c5dddbbe19 100644 --- a/pkgs/sage-setup/VERSION.txt +++ b/pkgs/sage-setup/VERSION.txt @@ -1 +1 @@ -10.3.rc3 +10.3.rc4 diff --git a/pkgs/sage-sws2rst/VERSION.txt b/pkgs/sage-sws2rst/VERSION.txt index befccbfa1a1..8c5dddbbe19 100644 --- a/pkgs/sage-sws2rst/VERSION.txt +++ b/pkgs/sage-sws2rst/VERSION.txt @@ -1 +1 @@ -10.3.rc3 +10.3.rc4 diff --git a/pkgs/sagemath-bliss/VERSION.txt b/pkgs/sagemath-bliss/VERSION.txt index befccbfa1a1..8c5dddbbe19 100644 --- a/pkgs/sagemath-bliss/VERSION.txt +++ b/pkgs/sagemath-bliss/VERSION.txt @@ -1 +1 @@ -10.3.rc3 +10.3.rc4 diff --git a/pkgs/sagemath-categories/VERSION.txt b/pkgs/sagemath-categories/VERSION.txt index befccbfa1a1..8c5dddbbe19 100644 --- a/pkgs/sagemath-categories/VERSION.txt +++ b/pkgs/sagemath-categories/VERSION.txt @@ -1 +1 @@ -10.3.rc3 +10.3.rc4 diff --git a/pkgs/sagemath-coxeter3/VERSION.txt b/pkgs/sagemath-coxeter3/VERSION.txt index befccbfa1a1..8c5dddbbe19 100644 --- a/pkgs/sagemath-coxeter3/VERSION.txt +++ b/pkgs/sagemath-coxeter3/VERSION.txt @@ -1 +1 @@ -10.3.rc3 +10.3.rc4 diff --git a/pkgs/sagemath-environment/VERSION.txt b/pkgs/sagemath-environment/VERSION.txt index befccbfa1a1..8c5dddbbe19 100644 --- a/pkgs/sagemath-environment/VERSION.txt +++ b/pkgs/sagemath-environment/VERSION.txt @@ -1 +1 @@ -10.3.rc3 +10.3.rc4 diff --git a/pkgs/sagemath-mcqd/VERSION.txt b/pkgs/sagemath-mcqd/VERSION.txt index befccbfa1a1..8c5dddbbe19 100644 --- a/pkgs/sagemath-mcqd/VERSION.txt +++ b/pkgs/sagemath-mcqd/VERSION.txt @@ -1 +1 @@ -10.3.rc3 +10.3.rc4 diff --git a/pkgs/sagemath-meataxe/VERSION.txt b/pkgs/sagemath-meataxe/VERSION.txt index befccbfa1a1..8c5dddbbe19 100644 --- a/pkgs/sagemath-meataxe/VERSION.txt +++ b/pkgs/sagemath-meataxe/VERSION.txt @@ -1 +1 @@ -10.3.rc3 +10.3.rc4 diff --git a/pkgs/sagemath-objects/VERSION.txt b/pkgs/sagemath-objects/VERSION.txt index befccbfa1a1..8c5dddbbe19 100644 --- a/pkgs/sagemath-objects/VERSION.txt +++ b/pkgs/sagemath-objects/VERSION.txt @@ -1 +1 @@ -10.3.rc3 +10.3.rc4 diff --git a/pkgs/sagemath-repl/VERSION.txt b/pkgs/sagemath-repl/VERSION.txt index befccbfa1a1..8c5dddbbe19 100644 --- a/pkgs/sagemath-repl/VERSION.txt +++ b/pkgs/sagemath-repl/VERSION.txt @@ -1 +1 @@ -10.3.rc3 +10.3.rc4 diff --git a/pkgs/sagemath-sirocco/VERSION.txt b/pkgs/sagemath-sirocco/VERSION.txt index befccbfa1a1..8c5dddbbe19 100644 --- a/pkgs/sagemath-sirocco/VERSION.txt +++ b/pkgs/sagemath-sirocco/VERSION.txt @@ -1 +1 @@ -10.3.rc3 +10.3.rc4 diff --git a/pkgs/sagemath-tdlib/VERSION.txt b/pkgs/sagemath-tdlib/VERSION.txt index befccbfa1a1..8c5dddbbe19 100644 --- a/pkgs/sagemath-tdlib/VERSION.txt +++ b/pkgs/sagemath-tdlib/VERSION.txt @@ -1 +1 @@ -10.3.rc3 +10.3.rc4 diff --git a/src/VERSION.txt b/src/VERSION.txt index befccbfa1a1..8c5dddbbe19 100644 --- a/src/VERSION.txt +++ b/src/VERSION.txt @@ -1 +1 @@ -10.3.rc3 +10.3.rc4 diff --git a/src/bin/sage-version.sh b/src/bin/sage-version.sh index 48feed6d1e2..0f483e4713f 100644 --- a/src/bin/sage-version.sh +++ b/src/bin/sage-version.sh @@ -4,6 +4,6 @@ # which stops "setup.py develop" from rewriting it as a Python file. : # This file is auto-generated by the sage-update-version script, do not edit! -SAGE_VERSION='10.3.rc3' -SAGE_RELEASE_DATE='2024-03-10' -SAGE_VERSION_BANNER='SageMath version 10.3.rc3, Release Date: 2024-03-10' +SAGE_VERSION='10.3.rc4' +SAGE_RELEASE_DATE='2024-03-17' +SAGE_VERSION_BANNER='SageMath version 10.3.rc4, Release Date: 2024-03-17' diff --git a/src/sage/version.py b/src/sage/version.py index db691f276ef..feec9728c2f 100644 --- a/src/sage/version.py +++ b/src/sage/version.py @@ -1,5 +1,5 @@ # Sage version information for Python scripts # This file is auto-generated by the sage-update-version script, do not edit! -version = '10.3.rc3' -date = '2024-03-10' -banner = 'SageMath version 10.3.rc3, Release Date: 2024-03-10' +version = '10.3.rc4' +date = '2024-03-17' +banner = 'SageMath version 10.3.rc4, Release Date: 2024-03-17' From 4cc25f1b5cbfd70677966f6a6c0cdca7106a92dd Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Tue, 19 Mar 2024 02:01:17 +0900 Subject: [PATCH 253/278] Some reviewer changes. --- src/sage/algebras/quantum_oscillator.py | 29 ++++++++++++------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/src/sage/algebras/quantum_oscillator.py b/src/sage/algebras/quantum_oscillator.py index 67b6947c009..9ec66b4e5fc 100644 --- a/src/sage/algebras/quantum_oscillator.py +++ b/src/sage/algebras/quantum_oscillator.py @@ -26,6 +26,7 @@ from sage.sets.family import Family from sage.sets.non_negative_integers import NonNegativeIntegers + class QuantumOscillatorAlgebra(CombinatorialFreeModule): r""" The quantum oscillator algebra. @@ -159,7 +160,7 @@ def __init__(self, q, R): CombinatorialFreeModule.__init__(self, R, indices, category=cat) self._assign_names(('ap', 'am', 'k', 'ki')) - def _repr_(self): + def _repr_(self) -> str: r""" Return a string representation of ``self``. @@ -217,7 +218,7 @@ def algebra_generators(self): return Family(d) @cached_method - def gens(self): + def gens(self) -> tuple: r""" Return the generators of ``self``. @@ -230,7 +231,7 @@ def gens(self): return tuple(self.algebra_generators()) @cached_method - def one_basis(self): + def one_basis(self) -> tuple: r""" Return the index of the basis element of `1`. @@ -242,7 +243,7 @@ def one_basis(self): """ return (ZZ.zero(), ZZ.zero()) - def some_elements(self): + def some_elements(self) -> tuple: r""" Return some elements of ``self``. @@ -275,7 +276,7 @@ def fock_space_representation(self): """ return FockSpaceRepresentation(self) - def _repr_term(self, m): + def _repr_term(self, m) -> str: r""" Return a string representation of the basis element indexed by ``m``. @@ -438,7 +439,7 @@ def product_on_basis(self, ml, mr): return self.element_class(self, {(a, kl+kr+i): c * coeff for i, c in enumerate(kp) if c}) class Element(CombinatorialFreeModule.Element): - def inverse(self): + def __invert__(self): r""" Return the inverse if ``self`` is a basis element. @@ -474,16 +475,14 @@ def inverse(self): O = self.parent() return O.element_class(O, {(a, -k): coeff.inverse_of_unit()}) - __invert__ = inverse - class FockSpaceRepresentation(CombinatorialFreeModule): - """ + r""" The unique Fock space representation of the :class:`~sage.algebras.quantum_oscillator.QuantumOscillatorAlgebra`. """ def __init__(self, O): - """ + r""" Initialize ``self``. EXAMPLES:: @@ -498,7 +497,7 @@ def __init__(self, O): latex_bracket=[r'\lvert', r'\rangle']) def _test_representation(self, **options): - """ + r""" Test that ``self`` is a representation of the quantum oscillator algebra. @@ -519,8 +518,8 @@ def _test_representation(self, **options): return tester.assertEqual((a*b)*elt, a*(b*elt)) - def _repr_(self): - """ + def _repr_(self) -> str: + r""" Return a string representation of ``self``. EXAMPLES:: @@ -559,7 +558,7 @@ def vacuum(self): return self.basis()[0] def some_elements(self): - """ + r""" Return some elements of ``self``. EXAMPLES:: @@ -574,7 +573,7 @@ def some_elements(self): class Element(CombinatorialFreeModule.Element): def _acted_upon_(self, scalar, self_on_left=True): - """ + r""" Return the action of ``scalar`` on ``self``. EXAMPLES:: From 90b5502ebe2a93ca4bc9c1b5e0ac7c3b5edcb928 Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Tue, 19 Mar 2024 02:03:53 +0900 Subject: [PATCH 254/278] Fixing typo. --- src/sage/rings/polynomial/plural.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/polynomial/plural.pyx b/src/sage/rings/polynomial/plural.pyx index 4f12afcf5ee..01f4ddc7b5e 100644 --- a/src/sage/rings/polynomial/plural.pyx +++ b/src/sage/rings/polynomial/plural.pyx @@ -2234,7 +2234,7 @@ cdef class NCPolynomial_plural(RingElement): cpdef dict monomial_coefficients(self, bint copy=True) noexcept: """ - Return a dictonary representation of ``self`` with the keys + Return a dictionary representation of ``self`` with the keys the exponent vectors and the values the corresponding coefficients. INPUT: From 6f5082221097d3a4c3e5972d41c3c4a77bcf2fc9 Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Tue, 19 Mar 2024 18:06:28 +0900 Subject: [PATCH 255/278] Fixing typos. --- src/sage/algebras/lie_algebras/center_uea.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/algebras/lie_algebras/center_uea.py b/src/sage/algebras/lie_algebras/center_uea.py index 0709eb637b9..61f5c6e05d7 100644 --- a/src/sage/algebras/lie_algebras/center_uea.py +++ b/src/sage/algebras/lie_algebras/center_uea.py @@ -47,7 +47,7 @@ class CenterIndices(IndexedFreeAbelianMonoid): This also constructs the lift from the center to the universal enveloping algebra as part of computing the generators and basis elements. The - basic algorithm is to construct the centeralizer of each filtered + basic algorithm is to construct the centralizer of each filtered component in increasing order (as each is a finite dimensional vector space). For more precise details, see [Motsak2006]_. """ @@ -290,7 +290,7 @@ def _construct_next_degree(self): # modulo the currently computed center. for exps in IntegerListsLex(n=self._cur_deg, length=len(gens)): elt = monoid.element_class(monoid, {k: p for k, p in zip(monoid._indices, exps) if p}) - if elt in new_red: # already has a centeral element with this leading term + if elt in new_red: # already has a central element with this leading term continue # A new basis element to consider self._cur_vecs.append(UEA.monomial(elt)) From 483e19adf21abd405ffbdf5c0e1c25d5605daf78 Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Tue, 19 Mar 2024 18:10:08 +0900 Subject: [PATCH 256/278] Reviewer comments by Frederic. --- src/doc/en/reference/references/index.rst | 2 +- src/sage/algebras/quantum_oscillator.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index 8a3100a9c57..11cbe47638b 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -4062,7 +4062,7 @@ REFERENCES: .. [Kuniba2022] Atsuo Kuniba, *Quantum Groups in Three-Dimensional Integrability*. - Theoretical an Mathematical Phyiscs, Springer. 2022. + Theoretical and Mathematical Physics, Springer. 2022. :doi:`10.1007/978-981-19-3262-5`. .. [Kur2008] Chris Kurth, "K Farey package for Sage", diff --git a/src/sage/algebras/quantum_oscillator.py b/src/sage/algebras/quantum_oscillator.py index 9ec66b4e5fc..a688283705f 100644 --- a/src/sage/algebras/quantum_oscillator.py +++ b/src/sage/algebras/quantum_oscillator.py @@ -261,7 +261,7 @@ def some_elements(self) -> tuple: def fock_space_representation(self): r""" - Return the Fock sapce representation of ``self``. + Return the Fock space representation of ``self``. .. SEEALSO:: @@ -481,7 +481,7 @@ class FockSpaceRepresentation(CombinatorialFreeModule): The unique Fock space representation of the :class:`~sage.algebras.quantum_oscillator.QuantumOscillatorAlgebra`. """ - def __init__(self, O): + def __init__(self, oscillator_algebra): r""" Initialize ``self``. @@ -491,9 +491,9 @@ def __init__(self, O): sage: F = O.fock_space_representation() sage: TestSuite(F).run() """ - self._O = O + self._O = oscillator_algebra ind = NonNegativeIntegers() - CombinatorialFreeModule.__init__(self, O.base_ring(), ind, prefix='', bracket=['|', '>'], + CombinatorialFreeModule.__init__(self, oscillator_algebra.base_ring(), ind, prefix='', bracket=['|', '>'], latex_bracket=[r'\lvert', r'\rangle']) def _test_representation(self, **options): From ccc11b6285ce0ee89dbbc3a7645d3b1af5db7534 Mon Sep 17 00:00:00 2001 From: Release Manager Date: Tue, 19 Mar 2024 23:59:45 +0100 Subject: [PATCH 257/278] Updated SageMath version to 10.3 --- CITATION.cff | 4 ++-- VERSION.txt | 2 +- build/pkgs/configure/checksums.ini | 6 +++--- build/pkgs/configure/package-version.txt | 2 +- build/pkgs/sage_conf/install-requires.txt | 2 +- build/pkgs/sage_docbuild/install-requires.txt | 2 +- build/pkgs/sage_setup/install-requires.txt | 2 +- build/pkgs/sage_sws2rst/install-requires.txt | 2 +- build/pkgs/sagelib/install-requires.txt | 2 +- build/pkgs/sagemath_bliss/install-requires.txt | 2 +- build/pkgs/sagemath_categories/install-requires.txt | 2 +- build/pkgs/sagemath_coxeter3/install-requires.txt | 2 +- build/pkgs/sagemath_environment/install-requires.txt | 2 +- build/pkgs/sagemath_mcqd/install-requires.txt | 2 +- build/pkgs/sagemath_meataxe/install-requires.txt | 2 +- build/pkgs/sagemath_objects/install-requires.txt | 2 +- build/pkgs/sagemath_repl/install-requires.txt | 2 +- build/pkgs/sagemath_sirocco/install-requires.txt | 2 +- build/pkgs/sagemath_tdlib/install-requires.txt | 2 +- pkgs/sage-conf/VERSION.txt | 2 +- pkgs/sage-conf_conda/VERSION.txt | 2 +- pkgs/sage-conf_pypi/VERSION.txt | 2 +- pkgs/sage-docbuild/VERSION.txt | 2 +- pkgs/sage-setup/VERSION.txt | 2 +- pkgs/sage-sws2rst/VERSION.txt | 2 +- pkgs/sagemath-bliss/VERSION.txt | 2 +- pkgs/sagemath-categories/VERSION.txt | 2 +- pkgs/sagemath-coxeter3/VERSION.txt | 2 +- pkgs/sagemath-environment/VERSION.txt | 2 +- pkgs/sagemath-mcqd/VERSION.txt | 2 +- pkgs/sagemath-meataxe/VERSION.txt | 2 +- pkgs/sagemath-objects/VERSION.txt | 2 +- pkgs/sagemath-repl/VERSION.txt | 2 +- pkgs/sagemath-sirocco/VERSION.txt | 2 +- pkgs/sagemath-tdlib/VERSION.txt | 2 +- src/VERSION.txt | 2 +- src/bin/sage-version.sh | 6 +++--- src/sage/version.py | 6 +++--- 38 files changed, 45 insertions(+), 45 deletions(-) diff --git a/CITATION.cff b/CITATION.cff index 95876d7d4e4..757b3ed976f 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -4,8 +4,8 @@ title: SageMath abstract: SageMath is a free open-source mathematics software system. authors: - name: "The SageMath Developers" -version: 10.3.rc4 +version: 10.3 doi: 10.5281/zenodo.593563 -date-released: 2024-03-17 +date-released: 2024-03-19 repository-code: "https://github.com/sagemath/sage" url: "https://www.sagemath.org/" diff --git a/VERSION.txt b/VERSION.txt index ba55211dc5e..84f8c01cbe3 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -SageMath version 10.3.rc4, Release Date: 2024-03-17 +SageMath version 10.3, Release Date: 2024-03-19 diff --git a/build/pkgs/configure/checksums.ini b/build/pkgs/configure/checksums.ini index f175ddc1636..f35c061f7c4 100644 --- a/build/pkgs/configure/checksums.ini +++ b/build/pkgs/configure/checksums.ini @@ -1,4 +1,4 @@ tarball=configure-VERSION.tar.gz -sha1=f06e82486ef07850394e6b6d7f226b70c8330d47 -md5=9dd6037651626866f7b6cf790e45b2dd -cksum=2951679715 +sha1=f4a7dd60c09ac2f3e1f8e388cbf2a527dcd5547c +md5=c16cb118d9ad6a495f6c5d509b2ec570 +cksum=3417350968 diff --git a/build/pkgs/configure/package-version.txt b/build/pkgs/configure/package-version.txt index 303ba93c6f2..9ceddfa865f 100644 --- a/build/pkgs/configure/package-version.txt +++ b/build/pkgs/configure/package-version.txt @@ -1 +1 @@ -83d81f55354e4732096fbe0c0cd750eb6334e033 +ab1a517b64b02bf15bbcb8d7c2d4d643bd5eff9b diff --git a/build/pkgs/sage_conf/install-requires.txt b/build/pkgs/sage_conf/install-requires.txt index b73225585e4..9316e759dad 100644 --- a/build/pkgs/sage_conf/install-requires.txt +++ b/build/pkgs/sage_conf/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sage-conf ~= 10.3rc4 +sage-conf ~= 10.3 diff --git a/build/pkgs/sage_docbuild/install-requires.txt b/build/pkgs/sage_docbuild/install-requires.txt index 7d8263cbc62..5c8d7443cd7 100644 --- a/build/pkgs/sage_docbuild/install-requires.txt +++ b/build/pkgs/sage_docbuild/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sage-docbuild ~= 10.3rc4 +sage-docbuild ~= 10.3 diff --git a/build/pkgs/sage_setup/install-requires.txt b/build/pkgs/sage_setup/install-requires.txt index 89568499d00..b8ad6e4a775 100644 --- a/build/pkgs/sage_setup/install-requires.txt +++ b/build/pkgs/sage_setup/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sage-setup ~= 10.3rc4 +sage-setup ~= 10.3 diff --git a/build/pkgs/sage_sws2rst/install-requires.txt b/build/pkgs/sage_sws2rst/install-requires.txt index 3cc53bf8f37..38499a27e81 100644 --- a/build/pkgs/sage_sws2rst/install-requires.txt +++ b/build/pkgs/sage_sws2rst/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sage-sws2rst ~= 10.3rc4 +sage-sws2rst ~= 10.3 diff --git a/build/pkgs/sagelib/install-requires.txt b/build/pkgs/sagelib/install-requires.txt index 417573d6f4d..40a8880c8ca 100644 --- a/build/pkgs/sagelib/install-requires.txt +++ b/build/pkgs/sagelib/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-standard ~= 10.3rc4 +sagemath-standard ~= 10.3 diff --git a/build/pkgs/sagemath_bliss/install-requires.txt b/build/pkgs/sagemath_bliss/install-requires.txt index 699d6d369b9..65de9ee76bb 100644 --- a/build/pkgs/sagemath_bliss/install-requires.txt +++ b/build/pkgs/sagemath_bliss/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-bliss ~= 10.3rc4 +sagemath-bliss ~= 10.3 diff --git a/build/pkgs/sagemath_categories/install-requires.txt b/build/pkgs/sagemath_categories/install-requires.txt index e78428baa84..b9b3b499694 100644 --- a/build/pkgs/sagemath_categories/install-requires.txt +++ b/build/pkgs/sagemath_categories/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-categories ~= 10.3rc4 +sagemath-categories ~= 10.3 diff --git a/build/pkgs/sagemath_coxeter3/install-requires.txt b/build/pkgs/sagemath_coxeter3/install-requires.txt index 1e3289d9826..d39eeb439be 100644 --- a/build/pkgs/sagemath_coxeter3/install-requires.txt +++ b/build/pkgs/sagemath_coxeter3/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-coxeter3 ~= 10.3rc4 +sagemath-coxeter3 ~= 10.3 diff --git a/build/pkgs/sagemath_environment/install-requires.txt b/build/pkgs/sagemath_environment/install-requires.txt index 0ad75840b0b..86896f667e5 100644 --- a/build/pkgs/sagemath_environment/install-requires.txt +++ b/build/pkgs/sagemath_environment/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-environment ~= 10.3rc4 +sagemath-environment ~= 10.3 diff --git a/build/pkgs/sagemath_mcqd/install-requires.txt b/build/pkgs/sagemath_mcqd/install-requires.txt index 28daaa77a8b..320a9322aec 100644 --- a/build/pkgs/sagemath_mcqd/install-requires.txt +++ b/build/pkgs/sagemath_mcqd/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-mcqd ~= 10.3rc4 +sagemath-mcqd ~= 10.3 diff --git a/build/pkgs/sagemath_meataxe/install-requires.txt b/build/pkgs/sagemath_meataxe/install-requires.txt index 43ff0fd958e..3dc2baaae6a 100644 --- a/build/pkgs/sagemath_meataxe/install-requires.txt +++ b/build/pkgs/sagemath_meataxe/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-meataxe ~= 10.3rc4 +sagemath-meataxe ~= 10.3 diff --git a/build/pkgs/sagemath_objects/install-requires.txt b/build/pkgs/sagemath_objects/install-requires.txt index 385101bcc9a..fbc36ea88b3 100644 --- a/build/pkgs/sagemath_objects/install-requires.txt +++ b/build/pkgs/sagemath_objects/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-objects ~= 10.3rc4 +sagemath-objects ~= 10.3 diff --git a/build/pkgs/sagemath_repl/install-requires.txt b/build/pkgs/sagemath_repl/install-requires.txt index ef39766e021..4665036e331 100644 --- a/build/pkgs/sagemath_repl/install-requires.txt +++ b/build/pkgs/sagemath_repl/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-repl ~= 10.3rc4 +sagemath-repl ~= 10.3 diff --git a/build/pkgs/sagemath_sirocco/install-requires.txt b/build/pkgs/sagemath_sirocco/install-requires.txt index 5b072cd2588..538b5712e82 100644 --- a/build/pkgs/sagemath_sirocco/install-requires.txt +++ b/build/pkgs/sagemath_sirocco/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-sirocco ~= 10.3rc4 +sagemath-sirocco ~= 10.3 diff --git a/build/pkgs/sagemath_tdlib/install-requires.txt b/build/pkgs/sagemath_tdlib/install-requires.txt index 9ed823b39ee..140461c5a72 100644 --- a/build/pkgs/sagemath_tdlib/install-requires.txt +++ b/build/pkgs/sagemath_tdlib/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-tdlib ~= 10.3rc4 +sagemath-tdlib ~= 10.3 diff --git a/pkgs/sage-conf/VERSION.txt b/pkgs/sage-conf/VERSION.txt index 8c5dddbbe19..260e37521a9 100644 --- a/pkgs/sage-conf/VERSION.txt +++ b/pkgs/sage-conf/VERSION.txt @@ -1 +1 @@ -10.3.rc4 +10.3 diff --git a/pkgs/sage-conf_conda/VERSION.txt b/pkgs/sage-conf_conda/VERSION.txt index 8c5dddbbe19..260e37521a9 100644 --- a/pkgs/sage-conf_conda/VERSION.txt +++ b/pkgs/sage-conf_conda/VERSION.txt @@ -1 +1 @@ -10.3.rc4 +10.3 diff --git a/pkgs/sage-conf_pypi/VERSION.txt b/pkgs/sage-conf_pypi/VERSION.txt index 8c5dddbbe19..260e37521a9 100644 --- a/pkgs/sage-conf_pypi/VERSION.txt +++ b/pkgs/sage-conf_pypi/VERSION.txt @@ -1 +1 @@ -10.3.rc4 +10.3 diff --git a/pkgs/sage-docbuild/VERSION.txt b/pkgs/sage-docbuild/VERSION.txt index 8c5dddbbe19..260e37521a9 100644 --- a/pkgs/sage-docbuild/VERSION.txt +++ b/pkgs/sage-docbuild/VERSION.txt @@ -1 +1 @@ -10.3.rc4 +10.3 diff --git a/pkgs/sage-setup/VERSION.txt b/pkgs/sage-setup/VERSION.txt index 8c5dddbbe19..260e37521a9 100644 --- a/pkgs/sage-setup/VERSION.txt +++ b/pkgs/sage-setup/VERSION.txt @@ -1 +1 @@ -10.3.rc4 +10.3 diff --git a/pkgs/sage-sws2rst/VERSION.txt b/pkgs/sage-sws2rst/VERSION.txt index 8c5dddbbe19..260e37521a9 100644 --- a/pkgs/sage-sws2rst/VERSION.txt +++ b/pkgs/sage-sws2rst/VERSION.txt @@ -1 +1 @@ -10.3.rc4 +10.3 diff --git a/pkgs/sagemath-bliss/VERSION.txt b/pkgs/sagemath-bliss/VERSION.txt index 8c5dddbbe19..260e37521a9 100644 --- a/pkgs/sagemath-bliss/VERSION.txt +++ b/pkgs/sagemath-bliss/VERSION.txt @@ -1 +1 @@ -10.3.rc4 +10.3 diff --git a/pkgs/sagemath-categories/VERSION.txt b/pkgs/sagemath-categories/VERSION.txt index 8c5dddbbe19..260e37521a9 100644 --- a/pkgs/sagemath-categories/VERSION.txt +++ b/pkgs/sagemath-categories/VERSION.txt @@ -1 +1 @@ -10.3.rc4 +10.3 diff --git a/pkgs/sagemath-coxeter3/VERSION.txt b/pkgs/sagemath-coxeter3/VERSION.txt index 8c5dddbbe19..260e37521a9 100644 --- a/pkgs/sagemath-coxeter3/VERSION.txt +++ b/pkgs/sagemath-coxeter3/VERSION.txt @@ -1 +1 @@ -10.3.rc4 +10.3 diff --git a/pkgs/sagemath-environment/VERSION.txt b/pkgs/sagemath-environment/VERSION.txt index 8c5dddbbe19..260e37521a9 100644 --- a/pkgs/sagemath-environment/VERSION.txt +++ b/pkgs/sagemath-environment/VERSION.txt @@ -1 +1 @@ -10.3.rc4 +10.3 diff --git a/pkgs/sagemath-mcqd/VERSION.txt b/pkgs/sagemath-mcqd/VERSION.txt index 8c5dddbbe19..260e37521a9 100644 --- a/pkgs/sagemath-mcqd/VERSION.txt +++ b/pkgs/sagemath-mcqd/VERSION.txt @@ -1 +1 @@ -10.3.rc4 +10.3 diff --git a/pkgs/sagemath-meataxe/VERSION.txt b/pkgs/sagemath-meataxe/VERSION.txt index 8c5dddbbe19..260e37521a9 100644 --- a/pkgs/sagemath-meataxe/VERSION.txt +++ b/pkgs/sagemath-meataxe/VERSION.txt @@ -1 +1 @@ -10.3.rc4 +10.3 diff --git a/pkgs/sagemath-objects/VERSION.txt b/pkgs/sagemath-objects/VERSION.txt index 8c5dddbbe19..260e37521a9 100644 --- a/pkgs/sagemath-objects/VERSION.txt +++ b/pkgs/sagemath-objects/VERSION.txt @@ -1 +1 @@ -10.3.rc4 +10.3 diff --git a/pkgs/sagemath-repl/VERSION.txt b/pkgs/sagemath-repl/VERSION.txt index 8c5dddbbe19..260e37521a9 100644 --- a/pkgs/sagemath-repl/VERSION.txt +++ b/pkgs/sagemath-repl/VERSION.txt @@ -1 +1 @@ -10.3.rc4 +10.3 diff --git a/pkgs/sagemath-sirocco/VERSION.txt b/pkgs/sagemath-sirocco/VERSION.txt index 8c5dddbbe19..260e37521a9 100644 --- a/pkgs/sagemath-sirocco/VERSION.txt +++ b/pkgs/sagemath-sirocco/VERSION.txt @@ -1 +1 @@ -10.3.rc4 +10.3 diff --git a/pkgs/sagemath-tdlib/VERSION.txt b/pkgs/sagemath-tdlib/VERSION.txt index 8c5dddbbe19..260e37521a9 100644 --- a/pkgs/sagemath-tdlib/VERSION.txt +++ b/pkgs/sagemath-tdlib/VERSION.txt @@ -1 +1 @@ -10.3.rc4 +10.3 diff --git a/src/VERSION.txt b/src/VERSION.txt index 8c5dddbbe19..260e37521a9 100644 --- a/src/VERSION.txt +++ b/src/VERSION.txt @@ -1 +1 @@ -10.3.rc4 +10.3 diff --git a/src/bin/sage-version.sh b/src/bin/sage-version.sh index 0f483e4713f..9837cfb3fae 100644 --- a/src/bin/sage-version.sh +++ b/src/bin/sage-version.sh @@ -4,6 +4,6 @@ # which stops "setup.py develop" from rewriting it as a Python file. : # This file is auto-generated by the sage-update-version script, do not edit! -SAGE_VERSION='10.3.rc4' -SAGE_RELEASE_DATE='2024-03-17' -SAGE_VERSION_BANNER='SageMath version 10.3.rc4, Release Date: 2024-03-17' +SAGE_VERSION='10.3' +SAGE_RELEASE_DATE='2024-03-19' +SAGE_VERSION_BANNER='SageMath version 10.3, Release Date: 2024-03-19' diff --git a/src/sage/version.py b/src/sage/version.py index feec9728c2f..1f26e7a6b8b 100644 --- a/src/sage/version.py +++ b/src/sage/version.py @@ -1,5 +1,5 @@ # Sage version information for Python scripts # This file is auto-generated by the sage-update-version script, do not edit! -version = '10.3.rc4' -date = '2024-03-17' -banner = 'SageMath version 10.3.rc4, Release Date: 2024-03-17' +version = '10.3' +date = '2024-03-19' +banner = 'SageMath version 10.3, Release Date: 2024-03-19' From dea37e7320f3f129a6545a7821e1139b2a71e34c Mon Sep 17 00:00:00 2001 From: Kwankyu Lee Date: Wed, 20 Mar 2024 09:33:54 -0700 Subject: [PATCH 258/278] src/doc/common/static/custom-jupyter-sphinx.css, src/sage_docbuild/conf.py: Refinements --- src/doc/common/static/custom-jupyter-sphinx.css | 2 +- src/sage_docbuild/conf.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/doc/common/static/custom-jupyter-sphinx.css b/src/doc/common/static/custom-jupyter-sphinx.css index a68a5cb05aa..e79b47b4539 100644 --- a/src/doc/common/static/custom-jupyter-sphinx.css +++ b/src/doc/common/static/custom-jupyter-sphinx.css @@ -1,5 +1,5 @@ div.jupyter_container { - margin: .5rem 0; + border: 0; } div.jupyter_container + div.jupyter_container { diff --git a/src/sage_docbuild/conf.py b/src/sage_docbuild/conf.py index 145105bb814..1c705c68d1f 100644 --- a/src/sage_docbuild/conf.py +++ b/src/sage_docbuild/conf.py @@ -903,7 +903,7 @@ def apply(self): linenostart=1) cell_node += cell_input container = TabContainer("", type="tab", new_set=False) - textnodes = [Text('Sage (live)')] + textnodes = [Text('Sage Live')] label = Label("", "", *textnodes) container += label content = Container("", is_div=True, classes=["tab-content"]) From a1cc2cf253cb702478d67eccc12815c50e233595 Mon Sep 17 00:00:00 2001 From: Saatvik Rao Date: Wed, 14 Feb 2024 23:30:27 +0530 Subject: [PATCH 259/278] implemented function for acyclic orientations --- src/sage/graphs/orientations.py | 227 ++++++++++++++++++++++++++++++++ 1 file changed, 227 insertions(+) diff --git a/src/sage/graphs/orientations.py b/src/sage/graphs/orientations.py index 7b7fa8681fc..99f290776d1 100644 --- a/src/sage/graphs/orientations.py +++ b/src/sage/graphs/orientations.py @@ -41,6 +41,233 @@ from sage.graphs.digraph import DiGraph +def acyclic_orientations(G): + r""" + Return an iterator over all acyclic orientations of an undirected graph `G`. + + ALGORITHM: + + The algorithm is based on a paper by Mathew B. Squire, presenting an efficient + algorithm for listing the acyclic orientations of a graph. The algorithm is + shown to require O(n) time per acyclic orientation generated, making it the + most efficient known algorithm for generating acyclic orientations. + + The function uses a recursive approach to generate acyclic orientations of the + graph. It reorders the vertices and edges of the graph, creating a new graph + with updated labels. Then, it iteratively generates acyclic orientations by + considering subsets of edges and checking whether they form upsets in a + corresponding poset. + + INPUT: + + - ``G`` -- an undirected graph. + + OUTPUT: + + - An iterator over all acyclic orientations of the input graph. + + .. NOTE:: + + The function assumes that the input graph is undirected and the edges are unlabelled. + + EXAMPLES: + + To count number acyclic orientations for a graph:: + + sage: G = Graph([(0, 3), (0, 4), (3, 4), (1, 3), (1, 2), (2, 3), (2, 4)]) + sage: it = g.acyclic_orientations(G) + sage: len(list(it)) + 54 + + For C100 graph, we get first 10 orientations pretty quickly:: + + sage: from itertools import islice + sage: import timeit + sage: G_C100, start_time = graphs.CycleGraph(100), timeit.default_timer() + sage: it_C100, first_10_orientations = acyclic_orientations(G_C100), list(islice(acyclic_orientations(G_C100), 10)) + sage: print(timeit.default_timer() - start_time < 0.05) + True + + TESTS: + + Acyclic orientations of a complete graph:: + + sage: G = graphs.CompleteGraph(5) + sage: it = g.acyclic_orientations(G) + sage: len(list(it)) + 120 + """ + from sage.rings.infinity import Infinity + from sage.combinat.subset import Subsets + + def reorder_vertices(G): + n = G.order() + ko = n + k = n + G_copy = G.copy() + vertex_labels = [None] * n + + while G_copy.size() > 0: + min_val = float('inf') + uv = None + for u, v, _ in G_copy.edges(): + du = G_copy.degree(u) + dv = G_copy.degree(v) + val = (du + dv) / (du * dv) + if val < min_val: + min_val = val + uv = (u, v) + + if uv: + u, v = uv + vertex_labels[u] = ko + vertex_labels[v] = ko - 1 + G_copy.delete_vertex(u) + G_copy.delete_vertex(v) + ko -= 2 + + if G_copy.size() == 0: + break + + for i, label in enumerate(vertex_labels): + if label is None: + vertex_labels[i] = ko + ko -= 1 + + return vertex_labels + + def order_edges(G, vertex_labels): + n = len(vertex_labels) + m = 1 + edge_labels = {} + + for j in range(2, n + 1): + for i in range(1, j): + if G.has_edge(i, j): + edge_labels[(i, j)] = m + m += 1 + + return edge_labels + + def is_upset_of_poset(Poset, subset, keys): + for (u, v) in subset: + for (w, x) in keys: + if (Poset[(u, v), (w, x)] == 1 and (w, x) not in subset): + return False + return True + + def generate_orientations(globO, starting_of_Ek, m, k, keys): + # Creating a poset + Poset = {} + for i in range(starting_of_Ek, m - 1): + for j in range(starting_of_Ek, m - 1): + u, v = keys[i] + w, x = keys[j] + Poset[(u, v), (w, x)] = 0 + + # Create a new graph to determine reachable vertices + new_G = DiGraph() + for i in range(0, starting_of_Ek): + u, v = keys[i] + if globO[(u, v)] == 1: + new_G.add_edge(v, u) + else: + new_G.add_edge(u, v) + + for i in range(starting_of_Ek, m - 1): + u, v = keys[i] + if not new_G.has_vertex(u): + new_G.add_vertex(u) + elif not new_G.has_vertex(v): + new_G.add_vertex(v) + + if (globO[(k-1, k)] == 1): + new_G.add_edge(k, k - 1) + else: + new_G.add_edge(k-1, k) + + for i in range(starting_of_Ek, m - 1): + for j in range(starting_of_Ek, m - 1): + u, v = keys[i] + w, x = keys[j] + # w should be reachable from u and v should be reachable from x + if (new_G.shortest_path_length(u, w) < Infinity and new_G.shortest_path_length(x, v) < Infinity): + Poset[(u, v), (w, x)] = 1 + + # For each subset of the base set of E_k, check if it is an upset or not + upsets = [] + for subset in Subsets(keys[starting_of_Ek:m-1]): + if (is_upset_of_poset(Poset, subset, keys[starting_of_Ek:m-1])): + upsets.append(list(subset)) + + for upset in upsets: + for i in range(starting_of_Ek, m - 1): + u, v = keys[i] + if (u, v) in upset: + globO[(u, v)] = 1 + else: + globO[(u, v)] = 0 + + yield globO.copy() + + def helper(G, globO, m, k): + keys = list(globO.keys()) + keys = keys[0:m] + + if m <= 0: + yield {} + return + + starting_of_Ek = 0 + for (u, v) in keys: + if u >= k - 1 or v >= k - 1: + break + else: + starting_of_Ek += 1 + + # s is the size of E_k + s = m - 1 - starting_of_Ek + + # Recursively generate acyclic orientations + orientations_G_small = helper(G, globO, starting_of_Ek, k - 2) + + # For each orientation of G_k-2, yield acyclic orientations + for alpha in orientations_G_small: + for (u, v) in alpha: + globO[(u, v)] = alpha[(u, v)] + + # Orienting H_k as 1 + globO[(k-1, k)] = 1 + yield from generate_orientations(globO, starting_of_Ek, m, k, keys) + + # Orienting H_k as 0 + globO[(k-1, k)] = 0 + yield from generate_orientations(globO, starting_of_Ek, m, k, keys) + + # Reorder vertices based on the logic in reorder_vertices function + vertex_labels = reorder_vertices(G) + + # Create a new graph with updated vertex labels using SageMath + new_G = Graph() + for u, v in G.edges(labels=False): # Assuming the graph edges are unlabelled + new_G.add_edge(vertex_labels[u], vertex_labels[v]) + + G = new_G + + # Order the edges based on the logic in order_edges function + edge_labels = order_edges(G, vertex_labels) + + # Create globO array + m = len(edge_labels) + globO = {} + for (u, v) in edge_labels: + globO[(u, v)] = 0 + + k = len(vertex_labels) + orientations = helper(G, globO, m, k) + return orientations + + def strong_orientations_iterator(G): r""" Return an iterator over all strong orientations of a graph `G`. From 3c1922c2dea043f75a8055a03511ba56a8d7eeda Mon Sep 17 00:00:00 2001 From: Saatvik Rao Date: Thu, 15 Feb 2024 17:32:23 +0530 Subject: [PATCH 260/278] added citation to the paper --- src/doc/en/reference/references/index.rst | 5 +++++ src/sage/graphs/orientations.py | 11 ++++++----- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index 09b78514e1c..c78b804d399 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -5815,6 +5815,11 @@ REFERENCES: Proceedings of the American Mathematical Society, Volume 90. Number 2, February 1984, pp. 199-202. +.. [Sq1998] Matthew B. Squire. + *Generating the acyclic orientations of a graph*. + Journal of Algorithms, Volume 26, Issue 2, Pages 275 - 290, 1998. + (https://doi.org/10.1006/jagm.1997.0891) + .. [SS1983] Shorey and Stewart. "On the Diophantine equation a x^{2t} + b x^t y + c y^2 = d and pure powers in recurrence sequences." Mathematica Scandinavica, 1983. diff --git a/src/sage/graphs/orientations.py b/src/sage/graphs/orientations.py index 99f290776d1..eea46b7f5db 100644 --- a/src/sage/graphs/orientations.py +++ b/src/sage/graphs/orientations.py @@ -47,10 +47,11 @@ def acyclic_orientations(G): ALGORITHM: - The algorithm is based on a paper by Mathew B. Squire, presenting an efficient - algorithm for listing the acyclic orientations of a graph. The algorithm is - shown to require O(n) time per acyclic orientation generated, making it the - most efficient known algorithm for generating acyclic orientations. + The algorithm is based on [Sq1998]_. + It presents an efficient algorithm for listing the acyclic orientations of a + graph. The algorithm is shown to require O(n) time per acyclic orientation + generated, making it the most efficient known algorithm for generating acyclic + orientations. The function uses a recursive approach to generate acyclic orientations of the graph. It reorders the vertices and edges of the graph, creating a new graph @@ -246,7 +247,7 @@ def helper(G, globO, m, k): # Reorder vertices based on the logic in reorder_vertices function vertex_labels = reorder_vertices(G) - + # Create a new graph with updated vertex labels using SageMath new_G = Graph() for u, v in G.edges(labels=False): # Assuming the graph edges are unlabelled From 9308b3d9d3531db3a4ec69934c81d4b7c2780c19 Mon Sep 17 00:00:00 2001 From: Saatvik Rao Date: Fri, 16 Feb 2024 21:22:01 +0530 Subject: [PATCH 261/278] fixed doctests and optimized code --- src/sage/graphs/orientations.py | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/src/sage/graphs/orientations.py b/src/sage/graphs/orientations.py index eea46b7f5db..b4d73e5c3ab 100644 --- a/src/sage/graphs/orientations.py +++ b/src/sage/graphs/orientations.py @@ -85,7 +85,7 @@ def acyclic_orientations(G): sage: from itertools import islice sage: import timeit sage: G_C100, start_time = graphs.CycleGraph(100), timeit.default_timer() - sage: it_C100, first_10_orientations = acyclic_orientations(G_C100), list(islice(acyclic_orientations(G_C100), 10)) + sage: it_C100, first_10_orientations = g.acyclic_orientations(G_C100), list(islice(g.acyclic_orientations(G_C100), 10)) sage: print(timeit.default_timer() - start_time < 0.05) True @@ -168,19 +168,12 @@ def generate_orientations(globO, starting_of_Ek, m, k, keys): # Create a new graph to determine reachable vertices new_G = DiGraph() - for i in range(0, starting_of_Ek): - u, v = keys[i] - if globO[(u, v)] == 1: - new_G.add_edge(v, u) - else: - new_G.add_edge(u, v) - for i in range(starting_of_Ek, m - 1): - u, v = keys[i] - if not new_G.has_vertex(u): - new_G.add_vertex(u) - elif not new_G.has_vertex(v): - new_G.add_vertex(v) + # Process vertices up to starting_of_Ek + new_G.add_edges([(v, u) if globO[(u, v)] == 1 else (u, v) for u, v in keys[:starting_of_Ek]]) + + # Process vertices starting from starting_of_Ek + new_G.add_vertices([u for u, _ in keys[starting_of_Ek:]] + [v for _, v in keys[starting_of_Ek:]]) if (globO[(k-1, k)] == 1): new_G.add_edge(k, k - 1) @@ -192,7 +185,7 @@ def generate_orientations(globO, starting_of_Ek, m, k, keys): u, v = keys[i] w, x = keys[j] # w should be reachable from u and v should be reachable from x - if (new_G.shortest_path_length(u, w) < Infinity and new_G.shortest_path_length(x, v) < Infinity): + if w in new_G.depth_first_search(u) and v in new_G.depth_first_search(x): Poset[(u, v), (w, x)] = 1 # For each subset of the base set of E_k, check if it is an upset or not @@ -259,11 +252,9 @@ def helper(G, globO, m, k): edge_labels = order_edges(G, vertex_labels) # Create globO array - m = len(edge_labels) - globO = {} - for (u, v) in edge_labels: - globO[(u, v)] = 0 + globO = {uv: 0 for uv in edge_labels} + m = len(edge_labels) k = len(vertex_labels) orientations = helper(G, globO, m, k) return orientations From 50b40437d609f80efb6c030309cde56c22a009e3 Mon Sep 17 00:00:00 2001 From: Saatvik Rao Date: Sat, 17 Feb 2024 12:25:02 +0530 Subject: [PATCH 262/278] modified reorder_vertices to handle arbitary vertex labels --- src/sage/graphs/orientations.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sage/graphs/orientations.py b/src/sage/graphs/orientations.py index b4d73e5c3ab..be1631ea100 100644 --- a/src/sage/graphs/orientations.py +++ b/src/sage/graphs/orientations.py @@ -106,7 +106,7 @@ def reorder_vertices(G): ko = n k = n G_copy = G.copy() - vertex_labels = [None] * n + vertex_labels = {v: None for v in G_copy.vertices()} while G_copy.size() > 0: min_val = float('inf') @@ -130,9 +130,9 @@ def reorder_vertices(G): if G_copy.size() == 0: break - for i, label in enumerate(vertex_labels): + for vertex, label in vertex_labels.items(): if label is None: - vertex_labels[i] = ko + vertex_labels[vertex] = ko ko -= 1 return vertex_labels From cb9fca29f2c163d683c849a4f6795165261f9bde Mon Sep 17 00:00:00 2001 From: Saatvik Rao Date: Sat, 17 Feb 2024 13:03:31 +0530 Subject: [PATCH 263/278] added new tests and updated the failing ones --- src/sage/graphs/orientations.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/sage/graphs/orientations.py b/src/sage/graphs/orientations.py index be1631ea100..0a689ecda77 100644 --- a/src/sage/graphs/orientations.py +++ b/src/sage/graphs/orientations.py @@ -75,8 +75,8 @@ def acyclic_orientations(G): To count number acyclic orientations for a graph:: - sage: G = Graph([(0, 3), (0, 4), (3, 4), (1, 3), (1, 2), (2, 3), (2, 4)]) - sage: it = g.acyclic_orientations(G) + sage: g = Graph([(0, 3), (0, 4), (3, 4), (1, 3), (1, 2), (2, 3), (2, 4)]) + sage: it = g.acyclic_orientations() sage: len(list(it)) 54 @@ -85,7 +85,7 @@ def acyclic_orientations(G): sage: from itertools import islice sage: import timeit sage: G_C100, start_time = graphs.CycleGraph(100), timeit.default_timer() - sage: it_C100, first_10_orientations = g.acyclic_orientations(G_C100), list(islice(g.acyclic_orientations(G_C100), 10)) + sage: it_C100, first_10_orientations = G_C100.acyclic_orientations(), list(islice(G_C100.acyclic_orientations(), 10)) sage: print(timeit.default_timer() - start_time < 0.05) True @@ -93,10 +93,17 @@ def acyclic_orientations(G): Acyclic orientations of a complete graph:: - sage: G = graphs.CompleteGraph(5) - sage: it = g.acyclic_orientations(G) + sage: g = graphs.CompleteGraph(5) + sage: it = g.acyclic_orientations() sage: len(list(it)) 120 + + Test for arbitary vertex labels:: + + sage: g_str = Graph([('abc', 'def'), ('ghi', 'def'), ('xyz', 'abc'), ('xyz', 'uvw'), ('uvw', 'abc'), ('uvw', 'ghi')]) + sage: it = acyclic_orientations(G_str) + sage: len(list(it)) + 42 """ from sage.rings.infinity import Infinity from sage.combinat.subset import Subsets From 88fb325002c112284652b39064deb06a44b440eb Mon Sep 17 00:00:00 2001 From: Saatvik Rao Date: Tue, 27 Feb 2024 12:09:50 +0530 Subject: [PATCH 264/278] exposed acyclic orientations to graph --- src/sage/graphs/graph.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/graphs/graph.py b/src/sage/graphs/graph.py index f7eac6cd39c..48c6a0a94ef 100644 --- a/src/sage/graphs/graph.py +++ b/src/sage/graphs/graph.py @@ -10134,7 +10134,7 @@ def bipartite_double(self, extended=False): from sage.graphs.tutte_polynomial import tutte_polynomial from sage.graphs.lovasz_theta import lovasz_theta from sage.graphs.partial_cube import is_partial_cube - from sage.graphs.orientations import strong_orientations_iterator, random_orientation + from sage.graphs.orientations import strong_orientations_iterator, random_orientation, acyclic_orientations from sage.graphs.connectivity import bridges, cleave, spqr_tree from sage.graphs.connectivity import is_triconnected from sage.graphs.comparability import is_comparability From 8e8f7edbca67555cdbe40e330f3a25e2672aeffa Mon Sep 17 00:00:00 2001 From: Saatvik Rao Date: Tue, 5 Mar 2024 13:47:58 +0530 Subject: [PATCH 265/278] added missing import statements --- src/sage/graphs/orientations.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/sage/graphs/orientations.py b/src/sage/graphs/orientations.py index 0a689ecda77..e90cc5fbc6b 100644 --- a/src/sage/graphs/orientations.py +++ b/src/sage/graphs/orientations.py @@ -106,6 +106,8 @@ def acyclic_orientations(G): 42 """ from sage.rings.infinity import Infinity + from sage.graphs.graph import Graph + from sage.graphs.graph import DiGraph from sage.combinat.subset import Subsets def reorder_vertices(G): From 1259a3834259b053342cecc01109f2461bb5e17e Mon Sep 17 00:00:00 2001 From: Saatvik Rao Date: Tue, 5 Mar 2024 15:05:17 +0530 Subject: [PATCH 266/278] fixed import statements --- src/sage/graphs/orientations.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/sage/graphs/orientations.py b/src/sage/graphs/orientations.py index e90cc5fbc6b..9ad998d952a 100644 --- a/src/sage/graphs/orientations.py +++ b/src/sage/graphs/orientations.py @@ -39,6 +39,7 @@ from copy import copy from sage.graphs.digraph import DiGraph +from sage.graphs.graph import Graph def acyclic_orientations(G): @@ -106,8 +107,6 @@ def acyclic_orientations(G): 42 """ from sage.rings.infinity import Infinity - from sage.graphs.graph import Graph - from sage.graphs.graph import DiGraph from sage.combinat.subset import Subsets def reorder_vertices(G): From a6510b919308f4159039ef9c6243441ca6ab4c1f Mon Sep 17 00:00:00 2001 From: Saatvik Rao Date: Tue, 5 Mar 2024 16:05:23 +0530 Subject: [PATCH 267/278] fixed import statements --- src/sage/graphs/graph.py | 1 + src/sage/graphs/orientations.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sage/graphs/graph.py b/src/sage/graphs/graph.py index 48c6a0a94ef..729b001a202 100644 --- a/src/sage/graphs/graph.py +++ b/src/sage/graphs/graph.py @@ -10180,6 +10180,7 @@ def bipartite_double(self, extended=False): "lovasz_theta" : "Leftovers", "strong_orientations_iterator" : "Connectivity, orientations, trees", "random_orientation" : "Connectivity, orientations, trees", + "acyclic_orientations" : "Connectivity, orientations, trees", "bridges" : "Connectivity, orientations, trees", "cleave" : "Connectivity, orientations, trees", "spqr_tree" : "Connectivity, orientations, trees", diff --git a/src/sage/graphs/orientations.py b/src/sage/graphs/orientations.py index 9ad998d952a..c89c185247d 100644 --- a/src/sage/graphs/orientations.py +++ b/src/sage/graphs/orientations.py @@ -39,7 +39,6 @@ from copy import copy from sage.graphs.digraph import DiGraph -from sage.graphs.graph import Graph def acyclic_orientations(G): @@ -250,6 +249,7 @@ def helper(G, globO, m, k): vertex_labels = reorder_vertices(G) # Create a new graph with updated vertex labels using SageMath + from sage.graphs.graph import Graph new_G = Graph() for u, v in G.edges(labels=False): # Assuming the graph edges are unlabelled new_G.add_edge(vertex_labels[u], vertex_labels[v]) From 2f1698688367093e3e6515cc6168e1ebd23afd7f Mon Sep 17 00:00:00 2001 From: Saatvik Rao Date: Wed, 6 Mar 2024 00:07:19 +0530 Subject: [PATCH 268/278] fixed test case --- src/sage/graphs/orientations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/graphs/orientations.py b/src/sage/graphs/orientations.py index c89c185247d..e180becbebf 100644 --- a/src/sage/graphs/orientations.py +++ b/src/sage/graphs/orientations.py @@ -101,7 +101,7 @@ def acyclic_orientations(G): Test for arbitary vertex labels:: sage: g_str = Graph([('abc', 'def'), ('ghi', 'def'), ('xyz', 'abc'), ('xyz', 'uvw'), ('uvw', 'abc'), ('uvw', 'ghi')]) - sage: it = acyclic_orientations(G_str) + sage: it = g_str.acyclic_orientations() sage: len(list(it)) 42 """ From eae3adf1d8538ab276f23e4891a35b55fe2639b6 Mon Sep 17 00:00:00 2001 From: Saatvik Rao Date: Wed, 6 Mar 2024 14:33:52 +0530 Subject: [PATCH 269/278] mistake in test case --- src/sage/graphs/orientations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/graphs/orientations.py b/src/sage/graphs/orientations.py index e180becbebf..3ec5e7c0288 100644 --- a/src/sage/graphs/orientations.py +++ b/src/sage/graphs/orientations.py @@ -86,7 +86,7 @@ def acyclic_orientations(G): sage: import timeit sage: G_C100, start_time = graphs.CycleGraph(100), timeit.default_timer() sage: it_C100, first_10_orientations = G_C100.acyclic_orientations(), list(islice(G_C100.acyclic_orientations(), 10)) - sage: print(timeit.default_timer() - start_time < 0.05) + sage: print(timeit.default_timer() - start_time < 0.5) True TESTS: From 1873f5ec83e93328aca89b860d373108555159dc Mon Sep 17 00:00:00 2001 From: Saatvik Rao Date: Mon, 11 Mar 2024 00:40:19 +0530 Subject: [PATCH 270/278] handled relabelling of vertices --- src/sage/graphs/orientations.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/sage/graphs/orientations.py b/src/sage/graphs/orientations.py index 3ec5e7c0288..9b9716e9655 100644 --- a/src/sage/graphs/orientations.py +++ b/src/sage/graphs/orientations.py @@ -80,15 +80,6 @@ def acyclic_orientations(G): sage: len(list(it)) 54 - For C100 graph, we get first 10 orientations pretty quickly:: - - sage: from itertools import islice - sage: import timeit - sage: G_C100, start_time = graphs.CycleGraph(100), timeit.default_timer() - sage: it_C100, first_10_orientations = G_C100.acyclic_orientations(), list(islice(G_C100.acyclic_orientations(), 10)) - sage: print(timeit.default_timer() - start_time < 0.5) - True - TESTS: Acyclic orientations of a complete graph:: @@ -248,11 +239,8 @@ def helper(G, globO, m, k): # Reorder vertices based on the logic in reorder_vertices function vertex_labels = reorder_vertices(G) - # Create a new graph with updated vertex labels using SageMath - from sage.graphs.graph import Graph - new_G = Graph() - for u, v in G.edges(labels=False): # Assuming the graph edges are unlabelled - new_G.add_edge(vertex_labels[u], vertex_labels[v]) + # Create a new graph with updated vertex labels using SageMath, Assuming the graph edges are unlabelled + new_G = G.relabel(perm=vertex_labels, inplace=False) G = new_G @@ -265,6 +253,18 @@ def helper(G, globO, m, k): m = len(edge_labels) k = len(vertex_labels) orientations = helper(G, globO, m, k) + + # Create a mapping between original and new vertex labels + reverse_vertex_labels = {label: vertex for vertex, label in vertex_labels.items()} + + # Iterate over acyclic orientations and relabel the vertices + for orientation in orientations: + relabeled_orientation = {} + for (u, v), label in orientation.items(): + relabeled_orientation[(reverse_vertex_labels[u], reverse_vertex_labels[v])] = label + + yield relabeled_orientation + return orientations From 4423049e3fea34be81d4043b5f057fdaf7bbd9bc Mon Sep 17 00:00:00 2001 From: Saatvik Rao Date: Mon, 11 Mar 2024 14:33:23 +0530 Subject: [PATCH 271/278] fixed relabelling of graph before return --- src/sage/graphs/orientations.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/sage/graphs/orientations.py b/src/sage/graphs/orientations.py index 9b9716e9655..4be9802df83 100644 --- a/src/sage/graphs/orientations.py +++ b/src/sage/graphs/orientations.py @@ -257,14 +257,13 @@ def helper(G, globO, m, k): # Create a mapping between original and new vertex labels reverse_vertex_labels = {label: vertex for vertex, label in vertex_labels.items()} - # Iterate over acyclic orientations and relabel the vertices + # Iterate over acyclic orientations and create relabeled graphs + from sage.graphs.graph import Graph for orientation in orientations: - relabeled_orientation = {} - for (u, v), label in orientation.items(): - relabeled_orientation[(reverse_vertex_labels[u], reverse_vertex_labels[v])] = label - - yield relabeled_orientation + relabeled_graph = Graph([(reverse_vertex_labels[u], reverse_vertex_labels[v], label) for (u, v), label in orientation.items()]) + yield relabeled_graph + # output the orientations return orientations From 3ffbec1e016e345759df45285a07c08c23a28975 Mon Sep 17 00:00:00 2001 From: Saatvik Rao Date: Tue, 12 Mar 2024 01:39:48 +0530 Subject: [PATCH 272/278] removed unnecessary return statement --- src/sage/graphs/orientations.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/sage/graphs/orientations.py b/src/sage/graphs/orientations.py index 4be9802df83..98a78c19fa0 100644 --- a/src/sage/graphs/orientations.py +++ b/src/sage/graphs/orientations.py @@ -263,9 +263,6 @@ def helper(G, globO, m, k): relabeled_graph = Graph([(reverse_vertex_labels[u], reverse_vertex_labels[v], label) for (u, v), label in orientation.items()]) yield relabeled_graph - # output the orientations - return orientations - def strong_orientations_iterator(G): r""" From 7c4492aee1fd843c26a0155b78e5ec1e594fe020 Mon Sep 17 00:00:00 2001 From: Saatvik Rao Date: Wed, 13 Mar 2024 10:07:06 +0530 Subject: [PATCH 273/278] added edge test cases --- src/sage/graphs/orientations.py | 41 +++++++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 5 deletions(-) diff --git a/src/sage/graphs/orientations.py b/src/sage/graphs/orientations.py index 98a78c19fa0..fa5ff11e9bf 100644 --- a/src/sage/graphs/orientations.py +++ b/src/sage/graphs/orientations.py @@ -80,8 +80,30 @@ def acyclic_orientations(G): sage: len(list(it)) 54 + Test for arbitary vertex labels:: + + sage: g_str = Graph([('abc', 'def'), ('ghi', 'def'), ('xyz', 'abc'), ('xyz', 'uvw'), ('uvw', 'abc'), ('uvw', 'ghi')]) + sage: it = g_str.acyclic_orientations() + sage: len(list(it)) + 42 + TESTS: + To count the number of acyclic orientations for a graph with 0 vertices:: + + sage: list(Graph().acyclic_orientations()) + [Graph on 0 vertices] + + To count the number of acyclic orientations for a graph with 1 vertex:: + + sage: list(Graph(1).acyclic_orientations()) + [Graph on 0 vertices] + + To count the number of acyclic orientations for a graph with 2 vertices:: + + sage: list(Graph(2).acyclic_orientations()) + [Graph on 0 vertices] + Acyclic orientations of a complete graph:: sage: g = graphs.CompleteGraph(5) @@ -89,12 +111,21 @@ def acyclic_orientations(G): sage: len(list(it)) 120 - Test for arbitary vertex labels:: + Graph with one edge:: + + sage: list(Graph([(0, 1)]).acyclic_orientations()) + [Graph on 2 vertices, Graph on 2 vertices] + + Graph with two edges:: + + sage: len(list(Graph([(0, 1), (1, 2)]).acyclic_orientations())) + 4 + + Cycle graph:: + + sage: len(list(Graph([(0, 1), (1, 2), (2, 0)]).acyclic_orientations())) + 6 - sage: g_str = Graph([('abc', 'def'), ('ghi', 'def'), ('xyz', 'abc'), ('xyz', 'uvw'), ('uvw', 'abc'), ('uvw', 'ghi')]) - sage: it = g_str.acyclic_orientations() - sage: len(list(it)) - 42 """ from sage.rings.infinity import Infinity from sage.combinat.subset import Subsets From 816cc88024f17ea5719c4b7c35af62d950327d02 Mon Sep 17 00:00:00 2001 From: Saatvik Rao Date: Thu, 14 Mar 2024 01:44:34 +0530 Subject: [PATCH 274/278] tests for graphs with no edges --- src/sage/graphs/orientations.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/sage/graphs/orientations.py b/src/sage/graphs/orientations.py index fa5ff11e9bf..0653b30817f 100644 --- a/src/sage/graphs/orientations.py +++ b/src/sage/graphs/orientations.py @@ -92,17 +92,17 @@ def acyclic_orientations(G): To count the number of acyclic orientations for a graph with 0 vertices:: sage: list(Graph().acyclic_orientations()) - [Graph on 0 vertices] + [] To count the number of acyclic orientations for a graph with 1 vertex:: sage: list(Graph(1).acyclic_orientations()) - [Graph on 0 vertices] + [] To count the number of acyclic orientations for a graph with 2 vertices:: sage: list(Graph(2).acyclic_orientations()) - [Graph on 0 vertices] + [] Acyclic orientations of a complete graph:: @@ -127,6 +127,10 @@ def acyclic_orientations(G): 6 """ + if not G.size(): + # A graph without edge cannot be oriented + return + from sage.rings.infinity import Infinity from sage.combinat.subset import Subsets From 96344b6dbf0e5f329b5d798ae249911ed6b35952 Mon Sep 17 00:00:00 2001 From: Saatvik Rao Date: Thu, 14 Mar 2024 13:38:56 +0530 Subject: [PATCH 275/278] minor fix --- src/sage/graphs/orientations.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/sage/graphs/orientations.py b/src/sage/graphs/orientations.py index 0653b30817f..924dec0d6ab 100644 --- a/src/sage/graphs/orientations.py +++ b/src/sage/graphs/orientations.py @@ -293,9 +293,8 @@ def helper(G, globO, m, k): reverse_vertex_labels = {label: vertex for vertex, label in vertex_labels.items()} # Iterate over acyclic orientations and create relabeled graphs - from sage.graphs.graph import Graph for orientation in orientations: - relabeled_graph = Graph([(reverse_vertex_labels[u], reverse_vertex_labels[v], label) for (u, v), label in orientation.items()]) + relabeled_graph = DiGraph([(reverse_vertex_labels[u], reverse_vertex_labels[v], label) for (u, v), label in orientation.items()]) yield relabeled_graph From 98fe8ddb5f721c33c9e3d2a3ce02aa79ea720db1 Mon Sep 17 00:00:00 2001 From: Saatvik Rao Date: Thu, 14 Mar 2024 16:06:48 +0530 Subject: [PATCH 276/278] output should be DiGraph --- src/sage/graphs/orientations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/graphs/orientations.py b/src/sage/graphs/orientations.py index 924dec0d6ab..04f9b948b8e 100644 --- a/src/sage/graphs/orientations.py +++ b/src/sage/graphs/orientations.py @@ -114,7 +114,7 @@ def acyclic_orientations(G): Graph with one edge:: sage: list(Graph([(0, 1)]).acyclic_orientations()) - [Graph on 2 vertices, Graph on 2 vertices] + [DiGraph on 2 vertices, DiGraph on 2 vertices] Graph with two edges:: From bef99463b730b5ee916c6e189ee106f31c933b7c Mon Sep 17 00:00:00 2001 From: Saatvik Rao Date: Fri, 15 Mar 2024 13:44:06 +0530 Subject: [PATCH 277/278] mistake in test --- src/sage/graphs/orientations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/graphs/orientations.py b/src/sage/graphs/orientations.py index 04f9b948b8e..1ecb283ba2a 100644 --- a/src/sage/graphs/orientations.py +++ b/src/sage/graphs/orientations.py @@ -114,7 +114,7 @@ def acyclic_orientations(G): Graph with one edge:: sage: list(Graph([(0, 1)]).acyclic_orientations()) - [DiGraph on 2 vertices, DiGraph on 2 vertices] + [Digraph on 2 vertices, Digraph on 2 vertices] Graph with two edges:: From b693ea936fab872567705c7eedf5557bd96aff47 Mon Sep 17 00:00:00 2001 From: Release Manager Date: Tue, 26 Mar 2024 00:26:35 +0100 Subject: [PATCH 278/278] Updated SageMath version to 10.4.beta0 --- .upstream.d/20-github.com-sagemath-sage-releases | 2 +- CITATION.cff | 4 ++-- VERSION.txt | 2 +- build/pkgs/configure/checksums.ini | 6 +++--- build/pkgs/configure/package-version.txt | 2 +- build/pkgs/sage_conf/install-requires.txt | 2 +- build/pkgs/sage_docbuild/install-requires.txt | 2 +- build/pkgs/sage_setup/install-requires.txt | 2 +- build/pkgs/sage_sws2rst/install-requires.txt | 2 +- build/pkgs/sagelib/install-requires.txt | 2 +- build/pkgs/sagemath_bliss/install-requires.txt | 2 +- build/pkgs/sagemath_categories/install-requires.txt | 2 +- build/pkgs/sagemath_coxeter3/install-requires.txt | 2 +- build/pkgs/sagemath_environment/install-requires.txt | 2 +- build/pkgs/sagemath_mcqd/install-requires.txt | 2 +- build/pkgs/sagemath_meataxe/install-requires.txt | 2 +- build/pkgs/sagemath_objects/install-requires.txt | 2 +- build/pkgs/sagemath_repl/install-requires.txt | 2 +- build/pkgs/sagemath_sirocco/install-requires.txt | 2 +- build/pkgs/sagemath_tdlib/install-requires.txt | 2 +- pkgs/sage-conf/VERSION.txt | 2 +- pkgs/sage-conf_conda/VERSION.txt | 2 +- pkgs/sage-conf_pypi/VERSION.txt | 2 +- pkgs/sage-docbuild/VERSION.txt | 2 +- pkgs/sage-setup/VERSION.txt | 2 +- pkgs/sage-sws2rst/VERSION.txt | 2 +- pkgs/sagemath-bliss/VERSION.txt | 2 +- pkgs/sagemath-categories/VERSION.txt | 2 +- pkgs/sagemath-coxeter3/VERSION.txt | 2 +- pkgs/sagemath-environment/VERSION.txt | 2 +- pkgs/sagemath-mcqd/VERSION.txt | 2 +- pkgs/sagemath-meataxe/VERSION.txt | 2 +- pkgs/sagemath-objects/VERSION.txt | 2 +- pkgs/sagemath-repl/VERSION.txt | 2 +- pkgs/sagemath-sirocco/VERSION.txt | 2 +- pkgs/sagemath-tdlib/VERSION.txt | 2 +- src/VERSION.txt | 2 +- src/bin/sage-version.sh | 6 +++--- src/sage/version.py | 6 +++--- 39 files changed, 46 insertions(+), 46 deletions(-) diff --git a/.upstream.d/20-github.com-sagemath-sage-releases b/.upstream.d/20-github.com-sagemath-sage-releases index 1ab20a34a06..db4fdc08b38 100644 --- a/.upstream.d/20-github.com-sagemath-sage-releases +++ b/.upstream.d/20-github.com-sagemath-sage-releases @@ -1,5 +1,5 @@ # Upstream packages as uploaded as GitHub release assets. # This file is automatically updated by the sage-update-version script. +https://github.com/sagemath/sage/releases/download/10.4/ https://github.com/sagemath/sage/releases/download/10.3/ https://github.com/sagemath/sage/releases/download/10.2/ -https://github.com/sagemath/sage/releases/download/10.1/ diff --git a/CITATION.cff b/CITATION.cff index 757b3ed976f..50004258150 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -4,8 +4,8 @@ title: SageMath abstract: SageMath is a free open-source mathematics software system. authors: - name: "The SageMath Developers" -version: 10.3 +version: 10.4.beta0 doi: 10.5281/zenodo.593563 -date-released: 2024-03-19 +date-released: 2024-03-25 repository-code: "https://github.com/sagemath/sage" url: "https://www.sagemath.org/" diff --git a/VERSION.txt b/VERSION.txt index 84f8c01cbe3..e17d5f059fb 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -SageMath version 10.3, Release Date: 2024-03-19 +SageMath version 10.4.beta0, Release Date: 2024-03-25 diff --git a/build/pkgs/configure/checksums.ini b/build/pkgs/configure/checksums.ini index 36b788e296d..62eeaa3ee49 100644 --- a/build/pkgs/configure/checksums.ini +++ b/build/pkgs/configure/checksums.ini @@ -1,4 +1,4 @@ tarball=configure-VERSION.tar.gz -sha1=f7b6730426fe857cb7036a0daae23aca7cb03dec -md5=e6d2b686e56ef1e5ecb5f248255870f7 -cksum=3124816602 +sha1=16786ab29c5625c411bee47d25d62d628477e9dc +md5=f37ca4623241cad2846b45122d180cc3 +cksum=536361291 diff --git a/build/pkgs/configure/package-version.txt b/build/pkgs/configure/package-version.txt index 2094519a0c4..8f9e71c1a7a 100644 --- a/build/pkgs/configure/package-version.txt +++ b/build/pkgs/configure/package-version.txt @@ -1 +1 @@ -1db16f5f41fb1fa9cc6c6e4506d2a44d931d3dbe +b0343d194ea320a6ea030d6b1274fbacdac0d7ac diff --git a/build/pkgs/sage_conf/install-requires.txt b/build/pkgs/sage_conf/install-requires.txt index 9316e759dad..f85b4cdfcb8 100644 --- a/build/pkgs/sage_conf/install-requires.txt +++ b/build/pkgs/sage_conf/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sage-conf ~= 10.3 +sage-conf ~= 10.4b0 diff --git a/build/pkgs/sage_docbuild/install-requires.txt b/build/pkgs/sage_docbuild/install-requires.txt index 5c8d7443cd7..224b629eea2 100644 --- a/build/pkgs/sage_docbuild/install-requires.txt +++ b/build/pkgs/sage_docbuild/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sage-docbuild ~= 10.3 +sage-docbuild ~= 10.4b0 diff --git a/build/pkgs/sage_setup/install-requires.txt b/build/pkgs/sage_setup/install-requires.txt index b8ad6e4a775..a37d8cf5709 100644 --- a/build/pkgs/sage_setup/install-requires.txt +++ b/build/pkgs/sage_setup/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sage-setup ~= 10.3 +sage-setup ~= 10.4b0 diff --git a/build/pkgs/sage_sws2rst/install-requires.txt b/build/pkgs/sage_sws2rst/install-requires.txt index 38499a27e81..c23f8d227fb 100644 --- a/build/pkgs/sage_sws2rst/install-requires.txt +++ b/build/pkgs/sage_sws2rst/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sage-sws2rst ~= 10.3 +sage-sws2rst ~= 10.4b0 diff --git a/build/pkgs/sagelib/install-requires.txt b/build/pkgs/sagelib/install-requires.txt index 40a8880c8ca..60adfbe95bf 100644 --- a/build/pkgs/sagelib/install-requires.txt +++ b/build/pkgs/sagelib/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-standard ~= 10.3 +sagemath-standard ~= 10.4b0 diff --git a/build/pkgs/sagemath_bliss/install-requires.txt b/build/pkgs/sagemath_bliss/install-requires.txt index 65de9ee76bb..e6f0797c09a 100644 --- a/build/pkgs/sagemath_bliss/install-requires.txt +++ b/build/pkgs/sagemath_bliss/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-bliss ~= 10.3 +sagemath-bliss ~= 10.4b0 diff --git a/build/pkgs/sagemath_categories/install-requires.txt b/build/pkgs/sagemath_categories/install-requires.txt index b9b3b499694..bf45cbd1fa8 100644 --- a/build/pkgs/sagemath_categories/install-requires.txt +++ b/build/pkgs/sagemath_categories/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-categories ~= 10.3 +sagemath-categories ~= 10.4b0 diff --git a/build/pkgs/sagemath_coxeter3/install-requires.txt b/build/pkgs/sagemath_coxeter3/install-requires.txt index d39eeb439be..ade373da6e1 100644 --- a/build/pkgs/sagemath_coxeter3/install-requires.txt +++ b/build/pkgs/sagemath_coxeter3/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-coxeter3 ~= 10.3 +sagemath-coxeter3 ~= 10.4b0 diff --git a/build/pkgs/sagemath_environment/install-requires.txt b/build/pkgs/sagemath_environment/install-requires.txt index 86896f667e5..33f664a19ad 100644 --- a/build/pkgs/sagemath_environment/install-requires.txt +++ b/build/pkgs/sagemath_environment/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-environment ~= 10.3 +sagemath-environment ~= 10.4b0 diff --git a/build/pkgs/sagemath_mcqd/install-requires.txt b/build/pkgs/sagemath_mcqd/install-requires.txt index 320a9322aec..64980c5b61c 100644 --- a/build/pkgs/sagemath_mcqd/install-requires.txt +++ b/build/pkgs/sagemath_mcqd/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-mcqd ~= 10.3 +sagemath-mcqd ~= 10.4b0 diff --git a/build/pkgs/sagemath_meataxe/install-requires.txt b/build/pkgs/sagemath_meataxe/install-requires.txt index 3dc2baaae6a..c58a8112bb7 100644 --- a/build/pkgs/sagemath_meataxe/install-requires.txt +++ b/build/pkgs/sagemath_meataxe/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-meataxe ~= 10.3 +sagemath-meataxe ~= 10.4b0 diff --git a/build/pkgs/sagemath_objects/install-requires.txt b/build/pkgs/sagemath_objects/install-requires.txt index fbc36ea88b3..00fb377cc1b 100644 --- a/build/pkgs/sagemath_objects/install-requires.txt +++ b/build/pkgs/sagemath_objects/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-objects ~= 10.3 +sagemath-objects ~= 10.4b0 diff --git a/build/pkgs/sagemath_repl/install-requires.txt b/build/pkgs/sagemath_repl/install-requires.txt index 4665036e331..bd684d1f75b 100644 --- a/build/pkgs/sagemath_repl/install-requires.txt +++ b/build/pkgs/sagemath_repl/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-repl ~= 10.3 +sagemath-repl ~= 10.4b0 diff --git a/build/pkgs/sagemath_sirocco/install-requires.txt b/build/pkgs/sagemath_sirocco/install-requires.txt index 538b5712e82..050fb066235 100644 --- a/build/pkgs/sagemath_sirocco/install-requires.txt +++ b/build/pkgs/sagemath_sirocco/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-sirocco ~= 10.3 +sagemath-sirocco ~= 10.4b0 diff --git a/build/pkgs/sagemath_tdlib/install-requires.txt b/build/pkgs/sagemath_tdlib/install-requires.txt index 140461c5a72..3e1142acd56 100644 --- a/build/pkgs/sagemath_tdlib/install-requires.txt +++ b/build/pkgs/sagemath_tdlib/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-tdlib ~= 10.3 +sagemath-tdlib ~= 10.4b0 diff --git a/pkgs/sage-conf/VERSION.txt b/pkgs/sage-conf/VERSION.txt index 260e37521a9..1d2c036dee5 100644 --- a/pkgs/sage-conf/VERSION.txt +++ b/pkgs/sage-conf/VERSION.txt @@ -1 +1 @@ -10.3 +10.4.beta0 diff --git a/pkgs/sage-conf_conda/VERSION.txt b/pkgs/sage-conf_conda/VERSION.txt index 260e37521a9..1d2c036dee5 100644 --- a/pkgs/sage-conf_conda/VERSION.txt +++ b/pkgs/sage-conf_conda/VERSION.txt @@ -1 +1 @@ -10.3 +10.4.beta0 diff --git a/pkgs/sage-conf_pypi/VERSION.txt b/pkgs/sage-conf_pypi/VERSION.txt index 260e37521a9..1d2c036dee5 100644 --- a/pkgs/sage-conf_pypi/VERSION.txt +++ b/pkgs/sage-conf_pypi/VERSION.txt @@ -1 +1 @@ -10.3 +10.4.beta0 diff --git a/pkgs/sage-docbuild/VERSION.txt b/pkgs/sage-docbuild/VERSION.txt index 260e37521a9..1d2c036dee5 100644 --- a/pkgs/sage-docbuild/VERSION.txt +++ b/pkgs/sage-docbuild/VERSION.txt @@ -1 +1 @@ -10.3 +10.4.beta0 diff --git a/pkgs/sage-setup/VERSION.txt b/pkgs/sage-setup/VERSION.txt index 260e37521a9..1d2c036dee5 100644 --- a/pkgs/sage-setup/VERSION.txt +++ b/pkgs/sage-setup/VERSION.txt @@ -1 +1 @@ -10.3 +10.4.beta0 diff --git a/pkgs/sage-sws2rst/VERSION.txt b/pkgs/sage-sws2rst/VERSION.txt index 260e37521a9..1d2c036dee5 100644 --- a/pkgs/sage-sws2rst/VERSION.txt +++ b/pkgs/sage-sws2rst/VERSION.txt @@ -1 +1 @@ -10.3 +10.4.beta0 diff --git a/pkgs/sagemath-bliss/VERSION.txt b/pkgs/sagemath-bliss/VERSION.txt index 260e37521a9..1d2c036dee5 100644 --- a/pkgs/sagemath-bliss/VERSION.txt +++ b/pkgs/sagemath-bliss/VERSION.txt @@ -1 +1 @@ -10.3 +10.4.beta0 diff --git a/pkgs/sagemath-categories/VERSION.txt b/pkgs/sagemath-categories/VERSION.txt index 260e37521a9..1d2c036dee5 100644 --- a/pkgs/sagemath-categories/VERSION.txt +++ b/pkgs/sagemath-categories/VERSION.txt @@ -1 +1 @@ -10.3 +10.4.beta0 diff --git a/pkgs/sagemath-coxeter3/VERSION.txt b/pkgs/sagemath-coxeter3/VERSION.txt index 260e37521a9..1d2c036dee5 100644 --- a/pkgs/sagemath-coxeter3/VERSION.txt +++ b/pkgs/sagemath-coxeter3/VERSION.txt @@ -1 +1 @@ -10.3 +10.4.beta0 diff --git a/pkgs/sagemath-environment/VERSION.txt b/pkgs/sagemath-environment/VERSION.txt index 260e37521a9..1d2c036dee5 100644 --- a/pkgs/sagemath-environment/VERSION.txt +++ b/pkgs/sagemath-environment/VERSION.txt @@ -1 +1 @@ -10.3 +10.4.beta0 diff --git a/pkgs/sagemath-mcqd/VERSION.txt b/pkgs/sagemath-mcqd/VERSION.txt index 260e37521a9..1d2c036dee5 100644 --- a/pkgs/sagemath-mcqd/VERSION.txt +++ b/pkgs/sagemath-mcqd/VERSION.txt @@ -1 +1 @@ -10.3 +10.4.beta0 diff --git a/pkgs/sagemath-meataxe/VERSION.txt b/pkgs/sagemath-meataxe/VERSION.txt index 260e37521a9..1d2c036dee5 100644 --- a/pkgs/sagemath-meataxe/VERSION.txt +++ b/pkgs/sagemath-meataxe/VERSION.txt @@ -1 +1 @@ -10.3 +10.4.beta0 diff --git a/pkgs/sagemath-objects/VERSION.txt b/pkgs/sagemath-objects/VERSION.txt index 260e37521a9..1d2c036dee5 100644 --- a/pkgs/sagemath-objects/VERSION.txt +++ b/pkgs/sagemath-objects/VERSION.txt @@ -1 +1 @@ -10.3 +10.4.beta0 diff --git a/pkgs/sagemath-repl/VERSION.txt b/pkgs/sagemath-repl/VERSION.txt index 260e37521a9..1d2c036dee5 100644 --- a/pkgs/sagemath-repl/VERSION.txt +++ b/pkgs/sagemath-repl/VERSION.txt @@ -1 +1 @@ -10.3 +10.4.beta0 diff --git a/pkgs/sagemath-sirocco/VERSION.txt b/pkgs/sagemath-sirocco/VERSION.txt index 260e37521a9..1d2c036dee5 100644 --- a/pkgs/sagemath-sirocco/VERSION.txt +++ b/pkgs/sagemath-sirocco/VERSION.txt @@ -1 +1 @@ -10.3 +10.4.beta0 diff --git a/pkgs/sagemath-tdlib/VERSION.txt b/pkgs/sagemath-tdlib/VERSION.txt index 260e37521a9..1d2c036dee5 100644 --- a/pkgs/sagemath-tdlib/VERSION.txt +++ b/pkgs/sagemath-tdlib/VERSION.txt @@ -1 +1 @@ -10.3 +10.4.beta0 diff --git a/src/VERSION.txt b/src/VERSION.txt index 260e37521a9..1d2c036dee5 100644 --- a/src/VERSION.txt +++ b/src/VERSION.txt @@ -1 +1 @@ -10.3 +10.4.beta0 diff --git a/src/bin/sage-version.sh b/src/bin/sage-version.sh index 9837cfb3fae..d5b691b3ef1 100644 --- a/src/bin/sage-version.sh +++ b/src/bin/sage-version.sh @@ -4,6 +4,6 @@ # which stops "setup.py develop" from rewriting it as a Python file. : # This file is auto-generated by the sage-update-version script, do not edit! -SAGE_VERSION='10.3' -SAGE_RELEASE_DATE='2024-03-19' -SAGE_VERSION_BANNER='SageMath version 10.3, Release Date: 2024-03-19' +SAGE_VERSION='10.4.beta0' +SAGE_RELEASE_DATE='2024-03-25' +SAGE_VERSION_BANNER='SageMath version 10.4.beta0, Release Date: 2024-03-25' diff --git a/src/sage/version.py b/src/sage/version.py index 1f26e7a6b8b..9c51c77cb14 100644 --- a/src/sage/version.py +++ b/src/sage/version.py @@ -1,5 +1,5 @@ # Sage version information for Python scripts # This file is auto-generated by the sage-update-version script, do not edit! -version = '10.3' -date = '2024-03-19' -banner = 'SageMath version 10.3, Release Date: 2024-03-19' +version = '10.4.beta0' +date = '2024-03-25' +banner = 'SageMath version 10.4.beta0, Release Date: 2024-03-25'