def _reduce_cusp(self, c): r""" Compute a minimal representative for the given cusp c. 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. The minimal representative for a cusp is the element in `P^1(Q)` in lowest terms with minimal positive denominator, and minimal positive numerator for that denominator. Two cusps `u1/v1` and `u2/v2` are equivalent modulo `\Gamma_H(N)` if and only if - `v1 = h*v2 (mod N)` and `u1 = h^(-1)*u2 (mod gcd(v1,N))` or - `v1 = -h*v2 (mod N)` and `u1 = -h^(-1)*u2 (mod gcd(v1,N))` for some `h \in H`. Then t is 1 or -1 as c and c' fall into the first or second case, respectively. EXAMPLES:: sage: GammaH(6,[5])._reduce_cusp(Cusp(5,3)) (1/3, -1) sage: GammaH(12,[5])._reduce_cusp(Cusp(8,9)) (1/3, -1) sage: GammaH(12,[5])._reduce_cusp(Cusp(5,12)) (Infinity, 1) sage: GammaH(12,[])._reduce_cusp(Cusp(5,12)) (5/12, 1) sage: GammaH(21,[5])._reduce_cusp(Cusp(-9/14)) (1/7, 1) """ c = Cusp(c) N = int(self.level()) Cusps = c.parent() v = int(c.denominator() % N) H = self._list_of_elements_in_H() # First, if N | v, take care of this case. If u is in \pm H, # then we return Infinity. If not, let u_0 be the minimum # of \{ h*u | h \in \pm H \}. Then return u_0/N. if not v: u = c.numerator() % N if u in H: return Cusps((1,0)), 1 if (N-u) in H: return Cusps((1,0)), -1 ls = [ (u*h)%N for h in H ] m1 = min(ls) m2 = N-max(ls) if m1 < m2: return Cusps((m1,N)), 1 else: return Cusps((m2,N)), -1 u = int(c.numerator() % v) gcd = get_gcd(N) d = gcd(v,N) # If (N,v) == 1, let v_0 be the minimal element # in \{ v * h | h \in \pm H \}. Then we either return # Infinity or 1/v_0, as v is or is not in \pm H, # respectively. if d == 1: if v in H: return Cusps((0,1)), 1 if (N-v) in H: return Cusps((0,1)), -1 ls = [ (v*h)%N for h in H ] m1 = min(ls) m2 = N-max(ls) if m1 < m2: return Cusps((1,m1)), 1 else: return Cusps((1,m2)), -1 val_min = v inv_mod = get_inverse_mod(N) # Now we're in the case (N,v) > 1. So we have to do several # steps: first, compute v_0 as above. While computing this # minimum, keep track of *all* pairs of (h,s) which give this # value of v_0. hs_ls = [(1,1)] for h in H: tmp = (v*h)%N if tmp < val_min: val_min = tmp hs_ls = [(inv_mod(h,N), 1)] elif tmp == val_min: hs_ls.append((inv_mod(h,N), 1)) if (N-tmp) < val_min: val_min = N - tmp hs_ls = [(inv_mod(h,N), -1)] elif (N-tmp) == val_min: hs_ls.append((inv_mod(h,N), -1)) # Finally, we find our minimal numerator. Let u_1 be the # minimum of s*h^-1*u mod d as (h,s) ranges over the elements # of hs_ls. We must find the smallest integer u_0 which is # smaller than v_0, congruent to u_1 mod d, and coprime to # v_0. Then u_0/v_0 is our minimal representative. u_min = val_min sign = None for h_inv,s in hs_ls: tmp = (h_inv * s * u)%d while gcd(tmp, val_min) > 1 and tmp < u_min: tmp += d if tmp < u_min: u_min = tmp sign = s return Cusps((u_min, val_min)), sign
def _reduce_cusp(self, c): r""" Compute a minimal representative for the given cusp c. 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. The minimal representative for a cusp is the element in `P^1(Q)` in lowest terms with minimal positive denominator, and minimal positive numerator for that denominator. Two cusps `u1/v1` and `u2/v2` are equivalent modulo `\Gamma_H(N)` if and only if - `v1 = h*v2 (mod N)` and `u1 = h^(-1)*u2 (mod gcd(v1,N))` or - `v1 = -h*v2 (mod N)` and `u1 = -h^(-1)*u2 (mod gcd(v1,N))` for some `h \in H`. Then t is 1 or -1 as c and c' fall into the first or second case, respectively. EXAMPLES:: sage: GammaH(6,[5])._reduce_cusp(Cusp(5,3)) (1/3, -1) sage: GammaH(12,[5])._reduce_cusp(Cusp(8,9)) (1/3, -1) sage: GammaH(12,[5])._reduce_cusp(Cusp(5,12)) (Infinity, 1) sage: GammaH(12,[])._reduce_cusp(Cusp(5,12)) (5/12, 1) sage: GammaH(21,[5])._reduce_cusp(Cusp(-9/14)) (1/7, 1) """ c = Cusp(c) N = int(self.level()) Cusps = c.parent() v = int(c.denominator() % N) H = self._list_of_elements_in_H() # First, if N | v, take care of this case. If u is in \pm H, # then we return Infinity. If not, let u_0 be the minimum # of \{ h*u | h \in \pm H \}. Then return u_0/N. if not v: u = c.numerator() % N if u in H: return Cusps((1, 0)), 1 if (N - u) in H: return Cusps((1, 0)), -1 ls = [(u * h) % N for h in H] m1 = min(ls) m2 = N - max(ls) if m1 < m2: return Cusps((m1, N)), 1 else: return Cusps((m2, N)), -1 u = int(c.numerator() % v) gcd = get_gcd(N) d = gcd(v, N) # If (N,v) == 1, let v_0 be the minimal element # in \{ v * h | h \in \pm H \}. Then we either return # Infinity or 1/v_0, as v is or is not in \pm H, # respectively. if d == 1: if v in H: return Cusps((0, 1)), 1 if (N - v) in H: return Cusps((0, 1)), -1 ls = [(v * h) % N for h in H] m1 = min(ls) m2 = N - max(ls) if m1 < m2: return Cusps((1, m1)), 1 else: return Cusps((1, m2)), -1 val_min = v inv_mod = get_inverse_mod(N) # Now we're in the case (N,v) > 1. So we have to do several # steps: first, compute v_0 as above. While computing this # minimum, keep track of *all* pairs of (h,s) which give this # value of v_0. hs_ls = [(1, 1)] for h in H: tmp = (v * h) % N if tmp < val_min: val_min = tmp hs_ls = [(inv_mod(h, N), 1)] elif tmp == val_min: hs_ls.append((inv_mod(h, N), 1)) if (N - tmp) < val_min: val_min = N - tmp hs_ls = [(inv_mod(h, N), -1)] elif (N - tmp) == val_min: hs_ls.append((inv_mod(h, N), -1)) # Finally, we find our minimal numerator. Let u_1 be the # minimum of s*h^-1*u mod d as (h,s) ranges over the elements # of hs_ls. We must find the smallest integer u_0 which is # smaller than v_0, congruent to u_1 mod d, and coprime to # v_0. Then u_0/v_0 is our minimal representative. u_min = val_min sign = None for h_inv, s in hs_ls: tmp = (h_inv * s * u) % d while gcd(tmp, val_min) > 1 and tmp < u_min: tmp += d if tmp < u_min: u_min = tmp sign = s return Cusps((u_min, val_min)), sign
def _coset_reduction_data_first_coord(G): """ Compute data used for determining the canonical coset representative of an element of SL_2(Z) modulo G. This function specifically returns data needed for the first part of the reduction step (the first coordinate). INPUT: G -- a congruence subgroup Gamma_0(N), Gamma_1(N), or Gamma_H(N). OUTPUT: A list v such that v[u] = (min(u*h: h in H), gcd(u,N) , an h such that h*u = min(u*h: h in H)). EXAMPLES:: sage: G = Gamma0(12) sage: sage.modular.arithgroup.congroup_gammaH.GammaH_class._coset_reduction_data_first_coord(G) [(0, 12, 0), (1, 1, 1), (2, 2, 1), (3, 3, 1), (4, 4, 1), (1, 1, 5), (6, 6, 1), (1, 1, 7), (4, 4, 5), (3, 3, 7), (2, 2, 5), (1, 1, 11)] """ H = [ int(x) for x in G._list_of_elements_in_H() ] N = int(G.level()) # Get some useful fast functions for inverse and gcd inverse_mod = get_inverse_mod(N) # optimal inverse function gcd = get_gcd(N) # optimal gcd function # We will be filling this list in below. reduct_data = [0] * N # We can fill in 0 and all elements of H immediately reduct_data[0] = (0,N,0) for u in H: reduct_data[u] = (1, 1, inverse_mod(u, N)) # Make a table of the reduction of H (mod N/d), one for each # divisor d. repr_H_mod_N_over_d = {} for d in divisors(N): # We special-case N == d because in this case, # 1 % N_over_d is 0 if N == d: repr_H_mod_N_over_d[d] = [1] break N_over_d = N//d # For each element of H, we look at its image mod # N_over_d. If we haven't yet seen it, add it on to # the end of z. w = [0] * N_over_d z = [1] for x in H: val = x%N_over_d if not w[val]: w[val] = 1 z.append(x) repr_H_mod_N_over_d[d] = z # Compute the rest of the tuples. The values left to process # are those where reduct_data has a 0. Note that several of # these values are processed on each loop below, so re-index # each time. while True: try: u = reduct_data.index(0) except ValueError: break d = gcd(u, N) for x in repr_H_mod_N_over_d[d]: reduct_data[(u*x)%N] = (u, d, inverse_mod(x,N)) return reduct_data
def _coset_reduction_data_first_coord(G): """ Compute data used for determining the canonical coset representative of an element of SL_2(Z) modulo G. This function specifically returns data needed for the first part of the reduction step (the first coordinate). INPUT: G -- a congruence subgroup Gamma_0(N), Gamma_1(N), or Gamma_H(N). OUTPUT: A list v such that v[u] = (min(u*h: h in H), gcd(u,N) , an h such that h*u = min(u*h: h in H)). EXAMPLES:: sage: G = Gamma0(12) sage: sage.modular.arithgroup.congroup_gammaH.GammaH_class._coset_reduction_data_first_coord(G) [(0, 12, 0), (1, 1, 1), (2, 2, 1), (3, 3, 1), (4, 4, 1), (1, 1, 5), (6, 6, 1), (1, 1, 7), (4, 4, 5), (3, 3, 7), (2, 2, 5), (1, 1, 11)] """ H = [int(x) for x in G._list_of_elements_in_H()] N = int(G.level()) # Get some useful fast functions for inverse and gcd inverse_mod = get_inverse_mod(N) # optimal inverse function gcd = get_gcd(N) # optimal gcd function # We will be filling this list in below. reduct_data = [0] * N # We can fill in 0 and all elements of H immediately reduct_data[0] = (0, N, 0) for u in H: reduct_data[u] = (1, 1, inverse_mod(u, N)) # Make a table of the reduction of H (mod N/d), one for each # divisor d. repr_H_mod_N_over_d = {} for d in divisors(N): # We special-case N == d because in this case, # 1 % N_over_d is 0 if N == d: repr_H_mod_N_over_d[d] = [1] break N_over_d = N // d # For each element of H, we look at its image mod # N_over_d. If we haven't yet seen it, add it on to # the end of z. w = [0] * N_over_d z = [1] for x in H: val = x % N_over_d if not w[val]: w[val] = 1 z.append(x) repr_H_mod_N_over_d[d] = z # Compute the rest of the tuples. The values left to process # are those where reduct_data has a 0. Note that several of # these values are processed on each loop below, so re-index # each time. while True: try: u = reduct_data.index(0) except ValueError: break d = gcd(u, N) for x in repr_H_mod_N_over_d[d]: reduct_data[(u * x) % N] = (u, d, inverse_mod(x, N)) return reduct_data