diff --git a/src/sage/modular/arithgroup/congroup_gammaH.py b/src/sage/modular/arithgroup/congroup_gammaH.py index 26dade4ce23..6417890d621 100644 --- a/src/sage/modular/arithgroup/congroup_gammaH.py +++ b/src/sage/modular/arithgroup/congroup_gammaH.py @@ -8,7 +8,7 @@ - David Loeffler """ -################################################################################ +# ############################################################################# # # Copyright (C) 2009, The Sage Group -- http://www.sagemath.org/ # @@ -18,18 +18,20 @@ # # https://www.gnu.org/licenses/ # -################################################################################ +# ############################################################################# +from typing import Iterator from sage.arith.functions import lcm -from sage.arith.misc import euler_phi, gcd, divisors, get_inverse_mod, get_gcd, factor, xgcd -from sage.modular.modsym.p1list import lift_to_sl2z +from sage.arith.misc import (euler_phi, gcd, divisors, get_inverse_mod, + get_gcd, factor, xgcd) from .congroup_generic import CongruenceSubgroup -from sage.modular.cusps import Cusp -from sage.misc.cachefunc import cached_method -from sage.rings.integer_ring import ZZ -from sage.rings.finite_rings.integer_mod_ring import Zmod from sage.groups.matrix_gps.finitely_generated import MatrixGroup from sage.matrix.constructor import matrix +from sage.misc.cachefunc import cached_method +from sage.modular.cusps import Cusp +from sage.modular.modsym.p1list import lift_to_sl2z +from sage.rings.finite_rings.integer_mod_ring import Zmod +from sage.rings.integer_ring import ZZ from sage.structure.richcmp import richcmp_method, richcmp @@ -116,7 +118,7 @@ def is_GammaH(x): return isinstance(x, GammaH_class) -def _normalize_H(H, level): +def _normalize_H(H, level) -> list: """ Normalize representatives for a given subgroup H of the units modulo level. @@ -206,7 +208,7 @@ class GammaH_class(CongruenceSubgroup): 0 """ - def __init__(self, level, H, Hlist=None): + def __init__(self, level, H, Hlist=None) -> None: r""" The congruence subgroup `\Gamma_H(N)`. @@ -281,7 +283,7 @@ def __reduce__(self): """ return GammaH_constructor, (self.level(), self.__H) - def divisor_subgroups(self): + def divisor_subgroups(self) -> list: r""" Given this congruence subgroup `\Gamma_H(N)`, return all subgroups `\Gamma_G(M)` for `M` a divisor of `N` and such that @@ -319,10 +321,9 @@ def to_even_subgroup(self): """ if self.is_even(): return self - else: - return GammaH_constructor(self.level(), self._generators_for_H() + [-1]) + return GammaH_constructor(self.level(), self._generators_for_H() + [-1]) - def __richcmp__(self, other, op): + def __richcmp__(self, other, op) -> bool: """ Compare ``self`` to ``other``. @@ -379,8 +380,7 @@ def __richcmp__(self, other, op): self._list_of_elements_in_H()), (other.level(), -other.index(), other._list_of_elements_in_H()), op) - else: - return NotImplemented + return NotImplemented def _generators_for_H(self): """ @@ -396,7 +396,7 @@ def _generators_for_H(self): """ return self.__H - def _repr_(self): + def _repr_(self) -> str: """ Return the string representation of ``self``. @@ -407,7 +407,7 @@ def _repr_(self): """ return "Congruence Subgroup Gamma_H(%s) with H generated by %s" % (self.level(), self.__H) - def _latex_(self): + def _latex_(self) -> str: r""" Return the \LaTeX representation of ``self``. @@ -418,7 +418,7 @@ def _latex_(self): """ return '\\Gamma_H(%s, %s)' % (self.level(), self.__H) - def _list_of_elements_in_H(self): + def _list_of_elements_in_H(self) -> list: """ Return a sorted list of Python ints that are representatives between 1 and N-1 of the elements of H. @@ -490,16 +490,16 @@ def generators(self, algorithm='farey'): """ if algorithm == "farey": return self.farey_symbol().generators() - elif algorithm == "todd-coxeter": + if algorithm == "todd-coxeter": from sage.modular.modsym.ghlist import GHlist from .congroup import generators_helper level = self.level() gen_list = generators_helper(GHlist(self), level) return [self(g, check=False) for g in gen_list] - else: - raise ValueError("Unknown algorithm '%s' (should be either 'farey' or 'todd-coxeter')" % algorithm) - def _coset_reduction_data_first_coord(self): + raise ValueError("Unknown algorithm '%s' (should be either 'farey' or 'todd-coxeter')" % algorithm) + + def _coset_reduction_data_first_coord(self) -> list[tuple]: """ Compute data used for determining the canonical coset representative of an element of SL_2(Z) modulo ``self``. @@ -576,7 +576,7 @@ def _coset_reduction_data_first_coord(self): return reduct_data - def _coset_reduction_data_second_coord(self): + def _coset_reduction_data_second_coord(self) -> dict: """ Compute data used for determining the canonical coset representative of an element of SL_2(Z) modulo ``self``. @@ -629,7 +629,7 @@ def _coset_reduction_data_second_coord(self): return v @cached_method - def _coset_reduction_data(self): + def _coset_reduction_data(self) -> tuple[list[tuple], dict]: """ Compute data used for determining the canonical coset representative of an element of SL_2(Z) modulo ``self``. @@ -644,7 +644,7 @@ def _coset_reduction_data(self): return (self._coset_reduction_data_first_coord(), self._coset_reduction_data_second_coord()) - def _reduce_coset(self, uu, vv): + def _reduce_coset(self, uu, vv) -> tuple: r""" Compute a canonical form for a given Manin symbol. @@ -721,10 +721,11 @@ def _reduce_coset(self, uu, vv): def reduce_cusp(self, c): r""" - Compute a minimal representative for the given cusp c. Returns - a cusp c' which is equivalent to the given cusp, and is in - lowest terms with minimal positive denominator, and minimal - positive numerator for that denominator. + Compute a minimal representative for the given cusp c. + + This returns a cusp c' which is equivalent to the given cusp, + and is in lowest terms with minimal positive denominator, and + minimal positive numerator for that denominator. Two cusps `u_1/v_1` and `u_2/v_2` are equivalent modulo `\Gamma_H(N)` if and only if @@ -760,11 +761,11 @@ def reduce_cusp(self, c): """ return self._reduce_cusp(c)[0] - def _reduce_cusp(self, c): + def _reduce_cusp(self, c) -> tuple: r""" Compute a minimal representative for the given cusp c. - Returns a pair (c', t), where c' is the minimal representative + This returns a pair (c', t), where c' is the minimal representative for the given cusp, and t is either 1 or -1, as explained below. Largely for internal use. @@ -881,7 +882,7 @@ def _reduce_cusp(self, c): return Cusps((u_min, val_min)), sign - def _find_cusps(self): + def _find_cusps(self) -> list: r""" Return an ordered list of inequivalent cusps for ``self``, i.e. a set of representatives for the orbits of ``self`` on @@ -889,20 +890,21 @@ def _find_cusps(self): form; see self.reduce_cusp for the definition of reduced. ALGORITHM: - Lemma 3.2 in Cremona's 1997 book shows that for the action - of Gamma1(N) on "signed projective space" - `\Q^2 / (\Q_{\geq 0}^+)`, we have `u_1/v_1 \sim u_2 / v_2` - if and only if `v_1 = v_2 \bmod N` and `u_1 = u_2 \bmod - gcd(v_1, N)`. It follows that every orbit has a - representative `u/v` with `v \le N` and `0 \le u \le - gcd(v, N)`. We iterate through all pairs `(u,v)` - satisfying this. - - Having found a set containing at least one of every - equivalence class modulo Gamma1(N), we can be sure of - picking up every class modulo GammaH(N) since this - contains Gamma1(N); and the reduce_cusp call does the - checking to make sure we don't get any duplicates. + + Lemma 3.2 in Cremona's 1997 book shows that for the action + of Gamma1(N) on "signed projective space" + `\Q^2 / (\Q_{\geq 0}^+)`, we have `u_1/v_1 \sim u_2 / v_2` + if and only if `v_1 = v_2 \bmod N` and `u_1 = u_2 \bmod + gcd(v_1, N)`. It follows that every orbit has a + representative `u/v` with `v \le N` and `0 \le u \le + gcd(v, N)`. We iterate through all pairs `(u,v)` + satisfying this. + + Having found a set containing at least one of every + equivalence class modulo Gamma1(N), we can be sure of + picking up every class modulo GammaH(N) since this + contains Gamma1(N); and the reduce_cusp call does the + checking to make sure we don't get any duplicates. EXAMPLES:: @@ -915,7 +917,6 @@ def _find_cusps(self): sage: GammaH(24, [13,17])._find_cusps() == GammaH(24,[13,17]).cusps(algorithm='modsym') True """ - s = [] hashes = [] N = self.level() @@ -935,7 +936,7 @@ def _find_cusps(self): s.append(c) return sorted(s) - def _contains_sl2(self, a, b, c, d): + def _contains_sl2(self, a, b, c, d) -> bool: r""" Test whether [a,b,c,d] is an element of this subgroup. @@ -957,7 +958,7 @@ def _contains_sl2(self, a, b, c, d): N = self.level() return (c % N == 0) and (d % N in self._list_of_elements_in_H()) - def gamma0_coset_reps(self): + def gamma0_coset_reps(self) -> list: r""" Return a set of coset representatives for ``self \\ Gamma0(N)``, where N is the level of ``self``. @@ -978,9 +979,10 @@ def gamma0_coset_reps(self): """ from .all import SL2Z N = self.level() - return [SL2Z(lift_to_sl2z(0, d.lift(), N)) for d in _GammaH_coset_helper(N, self._list_of_elements_in_H())] + return [SL2Z(lift_to_sl2z(0, d.lift(), N)) + for d in _GammaH_coset_helper(N, self._list_of_elements_in_H())] - def coset_reps(self): + def coset_reps(self) -> Iterator: r""" Return a set of coset representatives for ``self \\ SL2Z``. @@ -1128,15 +1130,18 @@ def ncusps(self): """ N = self.level() H = self._list_of_elements_in_H() - c = ZZ(0) - for d in (d for d in N.divisors() if d**2 <= N): + c = ZZ.zero() + for d in N.divisors(): + d2 = d**2 + if d2 > N: + break Nd = lcm(d, N // d) Hd = {x % Nd for x in H} lenHd = len(Hd) if Nd - 1 not in Hd: lenHd *= 2 summand = euler_phi(d) * euler_phi(N // d) // lenHd - if d**2 == N: + if d2 == N: c = c + summand else: c = c + 2 * summand @@ -1144,10 +1149,11 @@ def ncusps(self): def nregcusps(self): r""" - Return the number of orbits of regular cusps for this subgroup. A cusp is regular - if we may find a parabolic element generating the stabiliser of that - cusp whose eigenvalues are both +1 rather than -1. If G contains -1, - all cusps are regular. + Return the number of orbits of regular cusps for this subgroup. + + A cusp is regular if we may find a parabolic element + generating the stabiliser of that cusp whose eigenvalues are + both +1 rather than -1. If G contains -1, all cusps are regular. EXAMPLES:: @@ -1170,13 +1176,16 @@ def nregcusps(self): N = self.level() H = self._list_of_elements_in_H() - c = ZZ(0) - for d in (d for d in divisors(N) if d**2 <= N): + c = ZZ.zero() + for d in divisors(N): + d2 = d**2 + if d2 > N: + break Nd = lcm(d, N // d) Hd = {x % Nd for x in H} if Nd - 1 not in Hd: summand = euler_phi(d) * euler_phi(N // d) // (2 * len(Hd)) - if d**2 == N: + if d2 == N: c = c + summand else: c = c + 2 * summand @@ -1191,13 +1200,14 @@ def nirregcusps(self): sage: GammaH(3212, [2045, 2773]).nirregcusps() 720 """ - return self.ncusps() - self.nregcusps() def dimension_cusp_forms(self, k=2): r""" Return the dimension of the space of weight k cusp forms for this - group. For `k \ge 2`, this is given by a standard formula in terms of k + group. + + For `k \ge 2`, this is given by a standard formula in terms of k and various invariants of the group; see Diamond + Shurman, "A First Course in Modular Forms", section 3.5 and 3.6. If k is not given, default to k = 2. @@ -1222,9 +1232,9 @@ def dimension_cusp_forms(self, k=2): k = ZZ(k) if k != 1: return CongruenceSubgroup.dimension_cusp_forms(self, k) - else: - from sage.modular.modform.weight1 import dimension_wt1_cusp_forms_gH - return dimension_wt1_cusp_forms_gH(self) + + from sage.modular.modform.weight1 import dimension_wt1_cusp_forms_gH + return dimension_wt1_cusp_forms_gH(self) def dimension_new_cusp_forms(self, k=2, p=0): r""" @@ -1377,7 +1387,7 @@ def characters_mod_H(self, sign=None, galois_orbits=False): return A -def _list_subgroup(N, gens): +def _list_subgroup(N, gens) -> list: r""" Given an integer ``N`` and a list of integers ``gens``, return a list of the elements of the subgroup of `(\ZZ / N\ZZ)^\times` generated by the @@ -1402,7 +1412,7 @@ def _list_subgroup(N, gens): return sorted(H) -def _GammaH_coset_helper(N, H): +def _GammaH_coset_helper(N, H) -> list: r""" Return a list of coset representatives for H in (Z / NZ)^*. @@ -1412,7 +1422,7 @@ def _GammaH_coset_helper(N, H): sage: _GammaH_coset_helper(108, [1, 107]) [1, 5, 7, 11, 13, 17, 19, 23, 25, 29, 31, 35, 37, 41, 43, 47, 49, 53] """ - t = [Zmod(N)(1)] + t = [Zmod(N).one()] W = [Zmod(N)(h) for h in H] HH = [Zmod(N)(h) for h in H] k = euler_phi(N) @@ -1453,6 +1463,13 @@ def mumu(N): -2 sage: mumu(9*25) 1 + + TESTS:: + + sage: mumu(0) + Traceback (most recent call last): + ... + ValueError: N must be at least 1 """ if N < 1: raise ValueError("N must be at least 1") diff --git a/src/sage/rings/noncommutative_ideals.pyx b/src/sage/rings/noncommutative_ideals.pyx index 48f19dd4596..9f4002a4481 100644 --- a/src/sage/rings/noncommutative_ideals.pyx +++ b/src/sage/rings/noncommutative_ideals.pyx @@ -401,7 +401,8 @@ class Ideal_nc(Ideal_generic): """ if isinstance(other, Ideal_nc) and self.ring() == other.ring(): if self.side() == "left" and other.side() == "right": - gens = [z for z in (x * y for x in self.gens() for y in other.gens()) if z] + it = (x * y for x in self.gens() for y in other.gens()) + gens = [z for z in it if z] return self.ring().ideal(gens, side='twosided') raise NotImplementedError("cannot multiply non-commutative ideals")