def _conjugacy_representatives(self, max_block_length=ZZ(0), D=None): r""" Store conjugacy representatives up to block length ``max_block_length`` (a non-negative integer, default: 0) in the internal dictionary. Previously calculated data is reused. This is a helper function for e.g. :meth:`class_number`. The set of all (hyperbolic) conjugacy types of block length ``t`` is stored in ``self._conj_block[t]``. The set of all primitive representatives (so far) with discriminant ``D`` is stored in ``self._conj_prim[D]``. The set of all non-primitive representatives (so far) with discriminant ``D`` is stored in ``self._conj_nonprim[D]``. The case of non-positive discriminants is done manually. INPUT: - ``max_block_length`` -- A non-negative integer (default: ``0``), the maximal block length. - ``D`` -- An element/discriminant of the base ring or more generally an upper bound for the involved discriminants. If ``D != None`` then an upper bound for ``max_block_length`` is deduced from ``D`` (default: ``None``). EXAMPLES:: sage: from sage.modular.modform_hecketriangle.hecke_triangle_groups import HeckeTriangleGroup sage: G = HeckeTriangleGroup(n=5) sage: G.element_repr_method("conj") sage: G._conjugacy_representatives(2) sage: list(G._conj_block[2]) [((4, 1), (3, 1)), ((2, 2),), ((3, 2),), ((3, 1), (1, 1)), ((4, 1), (1, 1)), ((4, 1), (2, 1)), ((3, 1), (2, 1)), ((2, 1), (1, 1))] sage: [key for key in sorted(G._conj_prim)] [-4, lam - 3, 0, 4*lam, 7*lam + 6, 9*lam + 5, 15*lam + 6, 33*lam + 21] sage: for key in sorted(G._conj_prim): ....: print(G._conj_prim[key]) [[S], [S]] [[U], [U]] [[V(4)]] [[V(3)], [V(2)]] [[V(1)*V(4)]] [[V(3)*V(4)], [V(1)*V(2)]] [[V(1)*V(3)], [V(2)*V(4)]] [[V(2)*V(3)]] sage: [key for key in sorted(G._conj_nonprim)] [-lam - 2, lam - 3, 32*lam + 16] sage: for key in sorted(G._conj_nonprim): ....: print(G._conj_nonprim[key]) [[U^(-2)], [U^2], [U^(-2)], [U^2]] [[U^(-1)], [U^(-1)]] [[V(2)^2], [V(3)^2]] sage: G.element_repr_method("default") """ from sage.combinat.partition import OrderedPartitions from sage.combinat.combinat import tuples from sage.arith.all import divisors if not D is None: max_block_length = max(coerce_AA(0), coerce_AA((D + 4) / (self.lam()**2))).sqrt().floor() else: try: max_block_length = ZZ(max_block_length) if max_block_length < 0: raise TypeError except TypeError: raise ValueError( "max_block_length must be a non-negative integer!") if not hasattr(self, "_max_block_length"): self._max_block_length = ZZ(0) self._conj_block = {} self._conj_nonprim = {} self._conj_prim = {} # It is not clear how to define the class number for D=0: # Conjugacy classes are V(n-1)^(+-k) for arbitrary k # and the trivial class (what about self_conj_block[0]?). # # One way is to define it using the fixed points and in # that case V(n-1) would be a good representative. # The non-primitive case is unclear however... # # We set it here to ensure that 0 is enlisted as a discriminant... # self._conj_prim[ZZ(0)] = [] self._conj_prim[ZZ(0)].append(self.V(self.n() - 1)) self._elliptic_conj_reps() if max_block_length <= self._max_block_length: return def is_cycle(seq): length = len(seq) for n in divisors(length): if n < length and is_cycle_of_length(seq, n): return True return False def is_cycle_of_length(seq, n): for i in range(n, len(seq)): if seq[i] != seq[i % n]: return False return True j_list = range(1, self.n()) for t in range(self._max_block_length + 1, max_block_length + 1): t_ZZ = ZZ(t) if t_ZZ not in self._conj_block: self._conj_block[t_ZZ] = set() partitions = OrderedPartitions(t).list() for par in partitions: len_par = len(par) exp_list = tuples(j_list, len_par) for ex in exp_list: keep = True if len_par > 1: for k in range(-1, len_par - 1): if ex[k] == ex[k + 1]: keep = False break # We don't want powers of V(1) elif ex[0] == 1: keep = False # But: Do we maybe want powers of V(n-1)?? # For now we exclude the parabolic cases... elif ex[0] == self.n() - 1: keep = False if keep: conj_type = cyclic_representative( tuple((ZZ(ex[k]), ZZ(par[k])) for k in range(len_par))) self._conj_block[t_ZZ].add(conj_type) for el in self._conj_block[t_ZZ]: group_el = prod( [self.V(el[k][0])**el[k][1] for k in range(len(el))]) #if el != group_el.conjugacy_type(): # raise AssertionError("This shouldn't happen!") D = group_el.discriminant() if coerce_AA(D) < 0: raise AssertionError("This shouldn't happen!") if coerce_AA(D) == 0: raise AssertionError("This shouldn't happen!") #continue # The primitive cases #if group_el.is_primitive(): if not ((len(el) == 1 and el[0][1] > 1) or is_cycle(el)): if D not in self._conj_prim: self._conj_prim[D] = [] self._conj_prim[D].append(group_el) # The remaining cases else: if D not in self._conj_nonprim: self._conj_nonprim[D] = [] self._conj_nonprim[D].append(group_el) self._max_block_length = max_block_length
def _conjugacy_representatives(self, max_block_length=ZZ(0), D=None): r""" Store conjugacy representatives up to block length ``max_block_length`` (a non-negative integer, default: 0) in the internal dictionary. Previously calculated data is reused. This is a helper function for e.g. :meth:`class_number`. The set of all (hyperbolic) conjugacy types of block length ``t`` is stored in ``self._conj_block[t]``. The set of all primitive representatives (so far) with discriminant ``D`` is stored in ``self._conj_prim[D]``. The set of all non-primitive representatives (so far) with discriminant ``D`` is stored in ``self._conj_nonprim[D]``. The case of non-positive discriminants is done manually. INPUT: - ``max_block_length`` -- A non-negative integer (default: ``0``), the maximal block length. - ``D`` -- An element/discriminant of the base ring or more generally an upper bound for the involved discriminants. If ``D != None`` then an upper bound for ``max_block_length`` is deduced from ``D`` (default: ``None``). EXAMPLES:: sage: from sage.modular.modform_hecketriangle.hecke_triangle_groups import HeckeTriangleGroup sage: G = HeckeTriangleGroup(n=5) sage: G.element_repr_method("conj") sage: G._conjugacy_representatives(2) sage: list(G._conj_block[2]) [((4, 1), (3, 1)), ((2, 2),), ((3, 2),), ((3, 1), (1, 1)), ((4, 1), (1, 1)), ((4, 1), (2, 1)), ((3, 1), (2, 1)), ((2, 1), (1, 1))] sage: [key for key in sorted(G._conj_prim)] [-4, lam - 3, 0, 4*lam, 7*lam + 6, 9*lam + 5, 15*lam + 6, 33*lam + 21] sage: for key in sorted(G._conj_prim): ....: print G._conj_prim[key] [[S], [S]] [[U], [U]] [[V(4)]] [[V(3)], [V(2)]] [[V(1)*V(4)]] [[V(3)*V(4)], [V(1)*V(2)]] [[V(1)*V(3)], [V(2)*V(4)]] [[V(2)*V(3)]] sage: [key for key in sorted(G._conj_nonprim)] [-lam - 2, lam - 3, 32*lam + 16] sage: for key in sorted(G._conj_nonprim): ....: print G._conj_nonprim[key] [[U^(-2)], [U^2], [U^(-2)], [U^2]] [[U^(-1)], [U^(-1)]] [[V(2)^2], [V(3)^2]] sage: G.element_repr_method("default") """ from sage.combinat.partition import OrderedPartitions from sage.combinat.combinat import tuples from sage.arith.all import divisors if not D is None: max_block_length = max(coerce_AA(0), coerce_AA((D + 4)/(self.lam()**2))).sqrt().floor() else: try: max_block_length = ZZ(max_block_length) if max_block_length < 0: raise TypeError except TypeError: raise ValueError("max_block_length must be a non-negative integer!") if not hasattr(self, "_max_block_length"): self._max_block_length = ZZ(0) self._conj_block = {} self._conj_nonprim = {} self._conj_prim = {} # It is not clear how to define the class number for D=0: # Conjugacy classes are V(n-1)^(+-k) for arbitrary k # and the trivial class (what about self_conj_block[0]?). # # One way is to define it using the fixed points and in # that case V(n-1) would be a good representative. # The non-primitive case is unclear however... # # We set it here to ensure that 0 is enlisted as a discriminant... # self._conj_prim[ZZ(0)] = [] self._conj_prim[ZZ(0)].append(self.V(self.n()-1)) self._elliptic_conj_reps() if max_block_length <= self._max_block_length: return def is_cycle(seq): length = len(seq) for n in divisors(length): if n < length and is_cycle_of_length(seq, n): return True return False def is_cycle_of_length(seq, n): for i in range(n, len(seq)): if seq[i] != seq[i % n]: return False return True j_list = range(1, self.n()) for t in range(self._max_block_length + 1, max_block_length + 1): t_ZZ = ZZ(t) if t_ZZ not in self._conj_block: self._conj_block[t_ZZ] = set() partitions = OrderedPartitions(t).list() for par in partitions: len_par = len(par) exp_list = tuples(j_list, len_par) for ex in exp_list: keep = True if len_par > 1: for k in range(-1,len_par-1): if ex[k] == ex[k+1]: keep = False break # We don't want powers of V(1) elif ex[0] == 1: keep = False # But: Do we maybe want powers of V(n-1)?? # For now we exclude the parabolic cases... elif ex[0] == self.n()-1: keep = False if keep: conj_type = cyclic_representative(tuple((ZZ(ex[k]), ZZ(par[k])) for k in range(len_par))) self._conj_block[t_ZZ].add(conj_type) for el in self._conj_block[t_ZZ]: group_el = prod([self.V(el[k][0])**el[k][1] for k in range(len(el))]) #if el != group_el.conjugacy_type(): # raise AssertionError("This shouldn't happen!") D = group_el.discriminant() if coerce_AA(D) < 0: raise AssertionError("This shouldn't happen!") if coerce_AA(D) == 0: raise AssertionError("This shouldn't happen!") #continue # The primitive cases #if group_el.is_primitive(): if not ((len(el) == 1 and el[0][1] > 1) or is_cycle(el)): if D not in self._conj_prim: self._conj_prim[D] = [] self._conj_prim[D].append(group_el) # The remaining cases else: if D not in self._conj_nonprim: self._conj_nonprim[D] = [] self._conj_nonprim[D].append(group_el) self._max_block_length = max_block_length