diff --git a/src/sage/modular/all.py b/src/sage/modular/all.py index 0eeb5e273a1..4b4e80a7f17 100644 --- a/src/sage/modular/all.py +++ b/src/sage/modular/all.py @@ -18,6 +18,8 @@ ArithmeticSubgroup_Permutation, CongruenceSubgroup, FareySymbol) +from .hecke_character import HeckeCharacterGroup + from sage.modular.cusps import Cusp, Cusps from sage.modular.etaproducts import (EtaGroup, EtaProduct, EtaGroupElement, diff --git a/src/sage/modular/hecke_character.py b/src/sage/modular/hecke_character.py new file mode 100644 index 00000000000..d2c8fca66d0 --- /dev/null +++ b/src/sage/modular/hecke_character.py @@ -0,0 +1,462 @@ +# -*- coding: utf-8 -*- +r""" +Hecke characters + +AUTHORS: + +- Robert Harron (2016-08-15): initial version + +""" +# **************************************************************************** +# Copyright (C) 2016 Robert Harron +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# https://www.gnu.org/licenses/ +# **************************************************************************** +from sage.structure.factory import UniqueFactory +from sage.arith.all import lcm +from sage.rings.integer_ring import ZZ +from sage.groups.abelian_gps.dual_abelian_group_element import DualAbelianGroupElement +from sage.groups.abelian_gps.dual_abelian_group import DualAbelianGroup_class +from sage.lfunctions.dokchitser import Dokchitser + +from cypari2.handle_error import PariError + + +class HeckeCharacter(DualAbelianGroupElement): + def __call__(self, g): + """ + Evaluate this Hecke character on the input ``g``. + + INPUT: + + - ``g`` -- either an element of the ray class group on which this character + is defined, or something that can be turned into an ideal. + + OUTPUT: + + The value of this character at ``g``. + + EXAMPLES: + + Evaluating on an element of the ray class group. :: + + sage: F. = QuadraticField(5) + sage: H = HeckeCharacterGroup(F.modulus(F.ideal(16), [0,1])) + sage: chi = H.gens()[0]; chi(H.group().gens()[0]) + zeta4 + + Evaluating at an element of the base field. :: + + sage: chi(a / 2 + 33 / 2) + 1 + + Evaluating at ideals of the base field. :: + + sage: chi(F.ideal(6)) + 0 + sage: chi(F.ideal(a)) + zeta4 + """ + R = self.parent().group() + if g.parent() is R: + return DualAbelianGroupElement.__call__(self, g) + K = R.number_field() + g = K.ideal(g) + if self.parent().modulus().finite_part().is_coprime(g): + return DualAbelianGroupElement.__call__(self, R(g)) + return self.parent().base_ring().zero() + + def __mul__(self, right): + """ + Multiply the two characters, if necessary by extending them to + a common modulus. + + EXAMPLES: + + Multiplying two characters with the same parent. :: + + sage: F. = QuadraticField(11) + sage: H = HeckeCharacterGroup(F.modulus(5, [0, 1])) + sage: prod(H.gens()) + chi0*chi1 + + Multiplying two characters with different moduli. :: + + sage: chi1 = HeckeCharacterGroup(F.modulus(3, [0])).gen()^4 + sage: chi2 = HeckeCharacterGroup(F.modulus(3, [1])).gen()^4 + sage: chi = chi1 * chi2; chi + 1 + sage: chi.parent() + Group of finite order Hecke characters modulo (Fractional ideal (3)) * infinity_0 * infinity_1 + """ + if self.parent() is right.parent(): + return DualAbelianGroupElement._mul_(self, right) + Lmod = self.modulus() + Rmod = right.modulus() + if Rmod.divides(Lmod): + return self * right.extend(self.parent()) + elif Lmod.divides(Rmod): + return self.extend(right.parent()) * right + m = self.modulus().lcm(right.modulus()) + return self.extend(m) * right.extend(m) + + def _log_values_on_gens(self): + r""" + Return a tuple of integers `(a_j)` such that the value of this + character on the jth generator of the ray class group is + `\exp(2\pi ia_j/d_j)`, where `d_j` is the order of the jth + generator. + + This tuple is simply the exponents of the character + with respect the generators of its Hecke character group. + + EXAMPLES:: + + sage: F = QuadraticField(5) + sage: H = HeckeCharacterGroup(F.ideal(16).modulus([0,1])) + sage: prod(H.gens())._log_values_on_gens() + (1, 1, 1) + """ + return self.exponents() + + def modulus(self): + """ + Return the modulus modulo which this character is defined. + + EXAMPLES:: + + sage: F = QuadraticField(2) + sage: H = HeckeCharacterGroup(F.modulus(F.ideal(8), [0,1])) + sage: chi = H.gens()[0] + sage: chi.modulus() + (Fractional ideal (8)) * infinity_0 * infinity_1 + sage: chi.conductor() + (Fractional ideal (2)) * infinity_0 * infinity_1 + """ + return self.parent().modulus() + + def level(self): + """ + Return the modulus modulo which this character is defined. + + An alias for :meth:`modulus`. + + EXAMPLES:: + + sage: F = QuadraticField(5) + sage: H = HeckeCharacterGroup(F.ideal(F.gen()).modulus([0,1])) + sage: H.gens()[0].level() + (Fractional ideal (a)) * infinity_0 * infinity_1 + """ + return self.modulus() + + def conductor(self): + """ + Return the conductor of this character. + + Uses pari to compute the conductor of this character, i.e. the smallest + modulus for which this character is defined. + + EXAMPLES:: + + sage: F. = NumberField(x^3 - 3*x -1) + sage: H = HeckeCharacterGroup(F.modulus(3, [0,1,2])) + sage: H.gen().conductor() + (Fractional ideal (-a^2 + 1)) * infinity_0 * infinity_1 * infinity_2 + sage: H.gen().modulus() + (Fractional ideal (3)) * infinity_0 * infinity_1 * infinity_2 + """ + R = self.parent().group() + K = R.number_field() + bnr = R.pari_bnr() + modulus = bnr.bnrconductor(self._log_values_on_gens()) + # in a newer version of pari, this is replaced with bnrconductor + infinite = [] + m1 = modulus[1] + conversion = self.parent().number_field()._pari_real_places_to_sage() + for i in range(len(m1)): + if m1[i] != 0: + infinite.append(conversion[i]) + infinite.sort() + return K.modulus(K.ideal(modulus[0]), infinite) + + def is_primitive(self): + """ + Determine whether this character is primitive. + + This means it checks whether its conductor is its modulus. + + EXAMPLES:: + + sage: F. = NumberField(x^4-5) + sage: H = HeckeCharacterGroup(F.modulus(2, [0, 1])) + sage: bools = [chi.is_primitive() for chi in H]; bools.sort() + sage: bools + [False, False, False, True] + """ + # When a newer version of pari is part of Sage, call + # bnrisconductor instead. + return self.conductor() == self.modulus() + + def primitive_character(self): + """ + Return the associated primitive character. + + EXAMPLES:: + + sage: F. = QuadraticField(13) + sage: H = HeckeCharacterGroup(F.ideal(-1/2*a + 7/2).modulus([0, 1])) + sage: chi = H.gen() + sage: chi.conductor() + (Fractional ideal (-1/2*a + 1/2)) * infinity_0 + sage: chi0 = chi.primitive_character() + sage: chi0.conductor() + (Fractional ideal (-1/2*a + 1/2)) * infinity_0 + sage: chi0.parent() + Group of finite order Hecke characters modulo (Fractional ideal (-1/2*a + 1/2)) * infinity_0 + """ + cond = self.conductor() + if cond == self.modulus(): + return self + H = HeckeCharacterGroup(cond) + Rgens = [cond.equivalent_ideal_coprime_to_other(I, self.modulus()) + for I in H.ray_class_gens()] + return H.element_from_values_on_gens([self(I) for I in Rgens]) + + def extend(self, m, check=True): + r""" + Return the extension of this character to the larger modulus ``m``. + + INPUT: + + - ``m`` -- a modulus that is a multiple of this Hecke character's modulus. + - ``check`` -- (default: ``True``) if ``True``, ensure that this Hecke character's + modulus divides ``m``; if it does not, a ``ValueError`` is raised. + + OUTPUT: + + The extension of this Hecke character to the one of modulus ``m``. + + EXAMPLES:: + + sage: F. = QuadraticField(13) + sage: H = HeckeCharacterGroup(F.ideal(-1/2*a + 1/2).modulus([0])) + sage: m_big = F.ideal(-1/2*a + 7/2).modulus([0, 1]) + sage: chi = H.gen().extend(m_big) + sage: chi.parent() + Group of finite order Hecke characters modulo (Fractional ideal (-1/2*a + 7/2)) * infinity_0 * infinity_1 + sage: chi.exponents() + (1,) + """ + if isinstance(m, HeckeCharacterGroup_class): + if check and not self.modulus().divides(m.modulus()): + raise ValueError("Hecke character can only be extended to a modulus that is a multiple of its own modulus.") + return m.element_from_values_on_gens([self(I) for I in m.ray_class_gens()]) + if check and not self.modulus().divides(m): + raise ValueError("Hecke character can only be extended to a modulus that is a multiple of its own modulus.") + if self.modulus() == m: + return self + #if not isinstance(m, HeckeCharacterGroup_class): + m = HeckeCharacterGroup(m) + return m.element_from_values_on_gens([self(I) for I in m.ray_class_gens()]) + + def analytic_conductor(self): + r""" + Return the analytic conductor of this Hecke character. + + This is the conductor that appears in the functional equation of this + character's `L`-function. + + The analytic conductor of a Hecke character is merely the norm + of the finite part of its conductor times the absolute value + of the discriminant of the number field which it is over. + + EXAMPLES: + + Over a real quadratic field:: + + sage: F. = QuadraticField(5) + sage: H = HeckeCharacterGroup(F.modulus(F.ideal(a), [0,1])) + sage: H.gens()[0].analytic_conductor() + 25 + + Over an imaginary quadratic field:: + + sage: F. = QuadraticField(-13) + sage: H = HeckeCharacterGroup(F.ideal(7).modulus()) + sage: H.gens()[0].analytic_conductor() + 364 + """ + N = self.conductor().finite_part().norm() + return N * self.parent().group().number_field().disc().abs() + + def root_number(self): + r""" + Return the Artin root number of this Hecke character. + + EXAMPLES: + + Some root numbers over a pure cubic field:: + + sage: F. = NumberField(x^3-3) + sage: H = HeckeCharacterGroup(F.ideal(5*a).modulus([0])) + sage: chis = [chi for chi in H if chi.order() == 2] + sage: rns = [chi.root_number() for chi in chis]; rns.sort() + sage: rns + [1, 1, 1] + + A quadratic character whose root number is not 1:: + + sage: F. = QuadraticField(-11) + sage: H = HeckeCharacterGroup(F.modulus(a + 2)) + sage: (H.gen()^2).root_number() + 1 + """ + #try-except only necessary because of http://pari.math.u-bordeaux.fr/cgi-bin/bugreport.cgi?bug=1848 + #According to Belabas this bug seems to occur because of a problem dealing with + #imprimitive characters, thus the code in the except clause below. Once we incorporate a version + #a pari where this bug is fixed, the command in the try itself should be sufficient. + #The first example above (with Q(cubert(3))) fails without the except clause and so can + #be used to test if the pari bug has been fixed. + try: + rn = self.parent().group().pari_bnr().bnrrootnumber(self._log_values_on_gens()) + except PariError: + chi = self.primitive_character() + rn = chi.parent().group().pari_bnr().bnrrootnumber(chi._log_values_on_gens()) + return rn.sage() + + def dirichlet_series_coefficients(self, max_n): + """ + documentation missing here + """ + Idict = self.parent().number_field().ideals_of_bdd_norm(max_n) + ans = [ZZ(1)] + [ZZ(0)] * (max_n - 1) + for n in range(2, max_n + 1): + Is = Idict[n] + if len(Is) == 0: + continue + ans[n - 1] = sum(self(I) for I in Is) + return ans + + def Lfunction(self, prec=53): + r""" + Return this character's L-function as a :class:`sage.lfunctions.dokchitser.Dokchitser` object. + + INPUT: + + - ``prec`` -- (default: 53) the number of bits of real precision. + + OUTPUT: + + A Dokchitser L-function object used to compute values of the L-function of this Hecke character. + + EXAMPLES: + + A totally odd character of a real quadratic field:: + + sage: F. = NumberField(x^2 - 5) + sage: mf = F.modulus(F.ideal(4), [0, 1]) + sage: H = HeckeCharacterGroup(mf) + sage: chi = H.gens()[1] + sage: L = chi.Lfunction(); L + Hecke L-function of chi1 + sage: [L(-n) for n in range(3)] + [1.00000000000000, 0.000000000000000, 15.0000000000000] + """ + # Figure out Gamma factors for more general characters + gamma_factors = [0] * self.parent().number_field().degree() + for i in self.conductor().infinite_part(): + gamma_factors[i] = 1 + ana_cond = self.analytic_conductor() + rn = self.root_number() + it_worked = False + number_of_allocs = 0 + while not it_worked: + #Sage automatically ups the memory allocation, but this messes with L.num_coeffs for some reason I have yet to figure out, so L needs to be remade completely every time memory is auto-allocated. If we can control the auto-allocation process/rewrite the Dokchitser code, then we can remove the extra stuff here. + L = Dokchitser(ana_cond, gamma_factors, 1, rn, prec=prec) + for n in range(number_of_allocs): + L.gp().eval('allocatemem()') + try: + L.init_coeffs(self.dirichlet_series_coefficients(L.num_coeffs())) + it_worked = True + except RuntimeError: + number_of_allocs += 1 + L.rename('Hecke L-function of %s' % self) + return L + + +class HeckeCharacterGroup_class(DualAbelianGroup_class): + r""" + EXAMPLES:: + + sage: F. = NumberField(x^2 - 5) + sage: mf = F.modulus(F.prime_above(5) * F.prime_above(29), [0,1]) + sage: H = HeckeCharacterGroup(mf); H + Group of finite order Hecke characters modulo (Fractional ideal (-11/2*a - 5/2)) * infinity_0 * infinity_1 + sage: [[chi(F.ideal(31)), chi(F.ideal(-12672))] for chi in H.gens()] + [[zeta4, 1], [1, -1]] + """ + Element = HeckeCharacter + + def __init__(self, ray_class_group, base_ring=None, names=None): + if base_ring is None: + from sage.rings.number_field.number_field import CyclotomicField + base_ring = CyclotomicField(lcm(ray_class_group.gens_orders())) + if names is None: + names = 'chi' + DualAbelianGroup_class.__init__(self, ray_class_group, names, base_ring) + + def _repr_(self): + return 'Group of finite order Hecke characters modulo ' + str(self.modulus()) + + def modulus(self): + return self.group().modulus() + + def level(self): + return self.modulus() + + def number_field(self): + return self.group().number_field() + + def ray_class_gens(self): + return self.group().gens_ideals() + + def element_from_values_on_gens(self, vals): + gens_orders = self.gens_orders() + if len(vals) != len(gens_orders): + raise ValueError("Incorrect number of values specified. %s specified, but needed %s" % (len(vals), len(gens_orders))) + exponents = [gens_orders[i].divide_knowing_divisible_by(vals[i].multiplicative_order()) if vals[i] != 1 else ZZ.zero() + for i in range(len(gens_orders))] + return self.element_class(self, exponents) + + #def _element_constructor_(self, *args, **kwds): + # if isinstance(args[0], (str, bytes)): + # raise TypeError("Wrong type to coerce into HeckeCharacterGroup.") + # try: + # n = len(args[0]) + # except TypeError: + # return DualAbelianGroup_class._element_constructor_(self, *args, **kwds) + # gens_orders = self.gens_orders() + # if n != len(gens_orders): + # return DualAbelianGroup_class._element_constructor_(self, *args, **kwds) + # exponents = [gens_orders[i].divide_knowing_divisible_by(args[0][i].multiplicative_order()) for i in range(n)] + # return self.element_class(self, exponents) + +class HeckeCharacterGroupFactory(UniqueFactory): + def create_key(self, modulus, base_ring=None, names=None):#(m, base_ring=None, names=None): + #from sage.structure.category_object import normalize_names + #names = normalize_names() + if names is None: + names = 'chi' + return (base_ring, modulus, names) + + def create_object(self, version, key, **extra_args): + base_ring, modulus, names = key + return HeckeCharacterGroup_class(modulus.number_field().ray_class_group(modulus), base_ring, names) + + +HeckeCharacterGroup = HeckeCharacterGroupFactory("HeckeCharacterGroup") diff --git a/src/sage/rings/number_field/class_group.py b/src/sage/rings/number_field/class_group.py index e1185a499bb..5a1cc50eefe 100644 --- a/src/sage/rings/number_field/class_group.py +++ b/src/sage/rings/number_field/class_group.py @@ -1,16 +1,26 @@ r""" Class groups of number fields -An element of a class group is stored as a pair consisting of both an explicit -ideal in that ideal class, and a list of exponents giving that ideal class in -terms of the generators of the parent class group. These can be accessed with -the ``ideal()`` and ``exponents()`` methods respectively. +Sage can compute class groups, ray class groups, and `S`-class groups of number +fields, and does so by wrapping the functionality from the PARI C-library. Some +of what can be computed includes the group structure, representative ideals, the +class of a given ideal, generators of the group, and products of elements. This +file also implements moduli of number fields. -EXAMPLES:: +AUTHORS: + +- ??? (???): Initial version +- Robert Harron (2016-08-15): implemented ray class groups and moduli + +EXAMPLES: + +Computations with the class group of a quadratic field:: sage: x = polygen(ZZ, 'x') sage: K. = NumberField(x^2 + 23) - sage: I = K.class_group().gen(); I + sage: H = K.class_group(); H + Class group of order 3 with structure C3 of Number Field in a with defining polynomial x^2 + 23 + sage: I = H.gen(); I Fractional ideal class (2, 1/2*a - 1/2) sage: I.ideal() Fractional ideal (2, 1/2*a - 1/2) @@ -37,13 +47,425 @@ False sage: K.fractional_ideal([2, 1/2*a + 1/2])^3 Fractional ideal (1/2*a - 3/2) + +Computations with an `S`-class group of a quadratic field:: + + sage: K. = QuadraticField(40) + sage: CS = K.S_class_group(K.primes_above(31)); CS + S-class group of order 2 with structure C2 of Number Field in a with defining polynomial x^2 - 40 + sage: CS.gens() # random gens (platform dependent) + (Fractional S-ideal class (3, 1/2*a + 2),) + sage: CS(2) + Trivial S-ideal class + sage: c1 = CS(K.ideal([6, a+2])); c1 + Fractional S-ideal class (6, a + 2) + sage: c2 = CS(K.ideal([6, a+4])); c2 + Fractional S-ideal class (6, a + 4) + sage: c1 == c2 + True + sage: c1.order() + 2 + +Computations with a ray class group of a quadratic field:: + + sage: F = QuadraticField(40) + sage: m = F.ideal(3).modulus([0, 1]); m + (Fractional ideal (3)) * infinity_0 * infinity_1 + sage: R = F.ray_class_group(m); R + Ray class group of order 8 with structure C4 x C2 of Number Field in a with defining polynomial x^2 - 40 of modulus (Fractional ideal (3)) * infinity_0 * infinity_1 + +Unlike for class groups and `S`-class groups, ray class group elements +do not carry around a representative ideal (for reasons of efficiency). +Nevertheless, one can be demanded. The returned ideal should be somewhat +'small.' + +:: + + sage: R.gens() + (c0, c1) + sage: R.gens_ideals() + (Fractional ideal (430, 1/2*a + 200), Fractional ideal (-3/2*a + 2)) + sage: c = R.gens()[0]^3 * R.gens()[1]; c + c0^3*c1 + sage: c.ideal() + Fractional ideal (10, a) + sage: c = R(F.ideal(2)); c + c0^2 + sage: R.gen(0).ideal()^2 + Fractional ideal (30*a - 470) + sage: R(R.gen(0).ideal()^2).ideal() + Fractional ideal (2) + +Narrow class groups are implemented via ray class groups:: + + sage: F. = QuadraticField(3) + sage: F.class_group() + Class group of order 1 of Number Field in a with defining polynomial x^2 - 3 + sage: Hn = F.narrow_class_group(); Hn + Narrow class group of order 2 with structure C2 of Number Field in a with defining polynomial x^2 - 3 + sage: Hn.gens() + (c,) + sage: Hn.gens_ideals() + (Fractional ideal (a + 1),) """ +from sage.structure.sage_object import SageObject +from sage.misc.cachefunc import cached_method from sage.groups.abelian_gps.values import AbelianGroupWithValues_class, AbelianGroupWithValuesElement +from sage.groups.abelian_gps.abelian_group import AbelianGroup_class from sage.groups.abelian_gps.abelian_group_element import AbelianGroupElement from sage.structure.element import MonoidElement from sage.rings.integer_ring import ZZ +from sage.rings.finite_rings.finite_field_constructor import GF +from sage.libs.pari.all import pari + + +def _integer_n_tuple_L1_iterator(n): + if n == 1: + i = 1 + while True: + yield i + yield -i + i += 1 + else: + from sage.combinat.partition import OrderedPartitions + from sage.combinat.subset import Subsets + N = 1 + sign_options_dict = {} + Subsets_of_n = [] + while True: + for k in range(1, n + 1): + Ps = OrderedPartitions(N, k) + for P in Ps: + try: + Ss = Subsets_of_n[k - 1] + except IndexError: + Ss = Subsets(range(n), k) + Subsets_of_n.append(Ss) + for S in Ss: + i = [0] * n + for j in range(k): + i[S[j]] = P[j] + yield i + try: + sign_options = sign_options_dict[S] + except KeyError: + sign_options = Subsets(S)[1:] + sign_options_dict[S] = sign_options + for signs in sign_options: + ii = list(i) + for index in signs: + ii[index] = - ii[index] + yield ii + N += 1 + +class Modulus(SageObject): + def __init__(self, finite, infinite=None, check=True): + r""" + Create a modulus of a number field. + + INPUT: + + - ``finite`` -- a non-zero fractional ideal in a number field. + - ``infinite`` -- a list of indices corresponding to real places + of the number field, sorted. + - ``check`` (default: True) -- If ``True``, run a few checks on the input. + """ + self._finite = finite + if infinite is None: + self._infinite = () + else: + self._infinite = tuple(ZZ(i) for i in infinite) + K = self._finite.number_field() + self._number_field = K + if check: + #insert various checks here + if self._finite == 0: + raise ValueError("Finite component of a modulus must be non-zero.") + sgn = K.signature()[0] + for i in self._infinite: + if i < 0 or i >= sgn: + raise ValueError("Infinite component of a modulus must be a list non-negative integers less than the number of real places of K") + return + + def _repr_(self): + r""" + EXAMPLES:: + + sage: K. = NumberField(x^2-5) + sage: m = K.modulus(K.ideal(31), [0,1]); m + (Fractional ideal (31)) * infinity_0 * infinity_1 + """ + if len(self._infinite) == 0: + return str(self._finite) + str_inf = '' + for i in self._infinite: + str_inf += ' * infinity_%s'%(i) + return '(' + str(self._finite) + ')' + str_inf + + def __eq__(self, other): + return self._number_field == other._number_field and self._finite == other._finite and self._infinite == other._infinite + + def __mul__(self, other): + r""" + Multiply two moduli. + + This multiplies the two finite parts and performs an exclusive or on the real places. + + EXAMPLES:: + + sage: K = NumberField(x^3 - 2, 'a') + sage: m1 = K.modulus(K.ideal(2), [0]) + sage: m2 = K.modulus(K.ideal(3), [0]) + sage: m1 * m2 + Fractional ideal (6) + + A higher degree totally real field:: + + sage: K = NumberField(x^5 - x^4 - 4*x^3 + 3*x^2 + 3*x - 1, 'a') + sage: m1 = K.modulus(K.ideal(5), [2, 3]) + sage: m2 = K.modulus(K.ideal(25), [0, 1, 3, 4]) + sage: m1 * m2 + (Fractional ideal (125)) * infinity_0 * infinity_1 * infinity_2 * infinity_4 + sage: _ == m2 * m1 + True + """ + inf = tuple(set(self.infinite_part()).symmetric_difference(other.infinite_part())) + return Modulus(self.finite_part() * other.finite_part(), inf, check=False) + + def lcm(self, other): + inf = tuple(set(self.infinite_part()).union(other.infinite_part())) + #Pe_out = [] + self_fact_P, self_fact_e = zip(*self.finite_part().factor()) + self_fact_P = list(self_fact_P) + self_fact_e = list(self_fact_e) + #self_facts = self.finite_part().factor() + #of = other.finite_part() + other_facts = other.finite_part().factor() + mf = self._number_field.ideal_monoid().one() + for P, e in other_facts: + try: + i = self_fact_P.index(P) + except ValueError: + #Pe_out.append([P, e]) + mf *= P**e + continue + #Pe_out.append([P, max(e, self_fact_e[i])]) + mf *= P**max(e, self_fact_e[i]) + del self_fact_P[i] + del self_fact_e[i] + for i in range(len(self_fact_P)): + mf *= self_fact_P[i]**self_fact_e[i] + return Modulus(mf, inf, check=False) + + def divides(self, other): + if not set(self.infinite_part()).issubset(other.infinite_part()): + return False + return self.finite_part().divides(other.finite_part()) + + def number_field(self): + return self._number_field + + def finite_part(self): + return self._finite + + def infinite_part(self): + return self._infinite + + def finite_factors(self): + try: + return self._finite_factors + except AttributeError: + self._finite_factors = self.finite_part().factor() + return self._finite_factors + + #def _pari_finite_factors(self): + # """ + # Return + # """ + # return self._number_field.pari_nf().idealfactor(self._finite) + + def equivalent_coprime_ideal_multiplier(self, I, other): + r""" + Given ``I`` coprime to this modulus `m`, return a number field element `\beta` + such that `\beta I` is coprime to the modulus ``other`` and equivalent to + ``I`` `\mathrm{mod}^\ast m`; in particular, `\beta` will be `1 \mathrm{mod}^\ast m`. + + EXAMPLES: + + An example with two prime factors difference between this modulus and ``other``. + + :: + + sage: F. = QuadraticField(5) + sage: m_small = F.modulus(3/2*a - 1/2, [0, 1]) + sage: m_big = F.modulus(2*a - 30, [0, 1]) + sage: m_small.equivalent_coprime_ideal_multiplier(F.ideal(6), m_big) + 109/54 + """ + F = self._number_field + other_Ps = [P for P, _ in other.finite_factors() if self._finite.valuation(P) == 0] + if len(other_Ps) == 0: #If prime factors of other is a subset of prime factors of this modulus + return F.one() + alpha = I.idealcoprime(other._finite) + if self.number_is_one_mod_star(alpha): + return alpha + nf = F.pari_nf() + other_Ps_facts = [nf.idealfactor(P) for P in other_Ps] + other_Ps_fact_mat = pari(other_Ps_facts).Col().matconcat() + self_fact_mat = nf.idealfactor(self.finite_part()) + x = pari([self_fact_mat, other_Ps_fact_mat]).Col().matconcat() + conditions = [~alpha] * self_fact_mat.nrows() + [1] * len(other_Ps) + gamma = F(nf.idealchinese(x, conditions)) + beta = alpha * gamma + if self.number_is_one_mod_star(beta): + return beta + from sage.misc.misc_c import prod + beta_fixed = F.modulus(self._finite * prod(other_Ps), self._infinite).fix_signs(beta) #should be able to do this more efficiently + return beta_fixed + + def equivalent_ideal_coprime_to_other(self, I, other): + r""" + Given ``I`` coprime to this modulus `m`, return an ideal `J` such that `J` is coprime + to the modulus ``other`` and equivalent to ``I`` `\mathrm{mod}^\ast m`. + + This is useful for lowering the level of a non-primitive Hecke character. + + INPUT: + + - ``I`` -- an ideal relatively prime to this modulus (not checked). + - ``other`` -- some other modulus. + + OUTPUT: + + an ideal coprime to ``other`` and equivalent to ``I`` in the ray class + group modulo this modulus. + """ + return self.equivalent_coprime_ideal_multiplier(I, other) * I + + def number_is_one_mod_star(self, a): + K = self.number_field() + am1 = K(a - 1) + for P, e in self.finite_factors(): + if am1.valuation(P) < e: + return False + inf_places = K.places() + for i in self.infinite_part(): + if inf_places[i](a) <= 0: + return False + return True + + def fix_signs(self, a): + r""" + Given ``a`` in ``self.number_field()``, find `b` congruent to ``a`` `mod^\ast` ``self.finite_part()`` + such that `b` is positive at the infinite places dividing ``self``. + """ + if self.is_finite() or a == 0: + return a + places = self.number_field().places() + positive = [] + negative = [] + for i in self.infinite_part(): + if places[i](a) > 0: + positive.append(i) + else: + negative.append(i) + if not negative: + return a + t = self.get_one_mod_star_finite_with_fixed_signs(positive, negative) + return t * a + + def get_one_mod_star_finite_with_fixed_signs(self, positive, negative): + if len(negative) == 0: + return self.number_field().one() + negative = tuple(negative) + try: + return self._one_mod_star[negative] + except AttributeError: + self._one_mod_star = {} + except KeyError: + pass + try: + beta_is, Ainv = self._beta_is_Ainv + except AttributeError: + beta_is, Ainv = self._find_beta_is_Ainv() + d = len(self.infinite_part()) + v = [0] * d + for i in negative: + v[i] = 1 + v = (GF(2)**d)(v) + w = Ainv * v + t = self.number_field().one() + for i in range(d): + if w[i] != 0: + t *= (1 + beta_is[i]) + self._one_mod_star[negative] = t + return t + + def _find_beta_is_Ainv(self): + r""" + Step 2 of Algorithm 4.2.20 of Cohen's Advanced... + """ + from sage.matrix.special import column_matrix + gammas = self.finite_part().basis() + k = len(self.infinite_part()) + beta_is = [] + Acols = [] + V = GF(2)**k + it = _integer_n_tuple_L1_iterator(k) + while len(beta_is) < k: + e = next(it) + beta = sum(e[i] * gammas[i] for i in range(k)) + sbeta = V(self._signs(beta)) + Acols_new = Acols + [sbeta] + A = column_matrix(GF(2), Acols_new) + if A.rank() == len(Acols_new): + Acols = Acols_new + beta_is.append(beta) + self._beta_is_Ainv = (beta_is, ~A) + return self._beta_is_Ainv + + def _signs(self, b): + if b == 0: + raise ValueError("Non-zero input required.") + sigmas = self._number_field.real_places() + return [ZZ.one() if sigmas[i](b).sign() == -1 else ZZ.zero() for i in self.infinite_part()] + + def is_finite(self): + return len(self._infinite) == 0 + + def is_infinite(self): + return self._finite.is_one() + + def __pari__(self): + """ + Return the corresponding pari modulus. + Note that this function performs the conversion between the ordering + of the real places of the number field in Sage and the ordering of the + underlying pari nf object. + + EXAMPLES: + + An example where the places in Sage and pari are in a different order. + + :: + + sage: x = polygen(QQ) + sage: f = x^4 - x^3 - 3*x^2 + x + 1 + sage: F. = NumberField(f(1-2*x)) + sage: F.modulus(1, [2, 3]).__pari__()[1] + [0, 1, 1, 0] + """ + inf_mod = [0] * self._number_field.signature()[0] + conversion = self._number_field._pari_real_places_to_sage() + for i in self._infinite: + pari_index = conversion.index(i) + inf_mod[pari_index] = 1 + return pari([self._finite, inf_mod]) + + def __hash__(self): + return hash((self._finite, self._infinite)) class FractionalIdealClass(AbelianGroupWithValuesElement): r""" @@ -63,7 +485,7 @@ class FractionalIdealClass(AbelianGroupWithValuesElement): sage: K. = QuadraticField(-23) sage: OK = K.ring_of_integers() sage: C = OK.class_group() - sage: P2a, P2b = [P for P, e in (2*K).factor()] + sage: P2a, P2b = [P for P,e in (2*OK).factor()] sage: c = C(P2a); c Fractional ideal class (2, 1/2*w - 1/2) sage: c.gens() @@ -88,7 +510,7 @@ def _repr_(self): r""" Return string representation of this fractional ideal class. - EXAMPLES:: + EXAMPLES:: sage: x = polygen(ZZ, 'x') sage: K. = NumberField(x^2 + 23,'a'); G = K.class_group() @@ -247,7 +669,7 @@ def ideal(self): sage: K. = QuadraticField(-23) sage: OK = K.ring_of_integers() sage: C = OK.class_group() - sage: P2a, P2b = [P for P, e in (2*K).factor()] + sage: P2a, P2b = [P for P,e in (2*OK).factor()] sage: c = C(P2a); c Fractional ideal class (2, 1/2*w - 1/2) sage: c.ideal() @@ -289,11 +711,11 @@ def representative_prime(self, norm_bound=1000): Fractional ideal (17, 1/2*a + 27/2), Fractional ideal (2, 1/2*a - 1/2)] """ - if self.value().is_prime(): - return self.value() + if self.ideal().is_prime(): + return self.ideal() c = self.reduce() - if c.value().is_prime(): - return c.value() + if c.ideal().is_prime(): + return c.ideal() # otherwise we just search: Cl = self.parent() K = Cl.number_field() @@ -313,7 +735,7 @@ def gens(self) -> tuple: sage: K. = QuadraticField(-23) sage: OK = K.ring_of_integers() sage: C = OK.class_group() - sage: P2a, P2b = [P for P, e in (2*K).factor()] + sage: P2a, P2b = [P for P,e in (2*OK).factor()] sage: c = C(P2a); c Fractional ideal class (2, 1/2*w - 1/2) sage: c.gens() @@ -322,6 +744,132 @@ def gens(self) -> tuple: return self.ideal().gens() +class RayClassGroupElement(AbelianGroupElement): + #@@def __init__(self, parent, element, ideal=None): + #@@if element is None: + #@@ if not parent.modulus().finite_part().is_coprime(ideal): + #@@ raise ValueError("Ideal is not coprime to the modulus.") + #@@ element = parent._ideal_log(ideal) + #Should treat the else case for coprime-ness as well since the code can coerce from different moduli + #@@FractionalIdealClass.__init__(self, parent, element, ideal) + + #def _repr_(self): + # return AbelianGroupWithValuesElement._repr_(self) + + #Should be able to get rid of the operations if make the reduce function a method of the parent + #def _mul_(self, other): + # m = AbelianGroupElement._mul_(self, other) + # nf = self.parent()._number_field.pari_nf() + # m._value = nf.idealred(nf.idealmul(self.value(), other.value())) + # return m + + #def _div_(self, other): + # m = AbelianGroupElement._div_(self, other) + # nf = self.parent()._number_field.pari_nf() + # m._value = nf.idealred(nf.idealdiv(self.value(), other.value())) + # return m + + #def inverse(self): + # m = AbelianGroupElement.inverse(self) + # nf = self.parent()._number_field.pari_nf() + # m._value = nf.idealred(nf.idealinv(self.value())) + # return m + + #__invert__ = inverse + + def ideal(self, reduce=True): + """ + Return an ideal representing this ray class. + + If ``reduce`` is ``True`` (by default) the returned ideal is + reduced to 'small' (this can be slow on large inputs). + + INPUT: + + - ``reduce`` -- (default: ``True``) determine whether or not + to output a 'small' representative. + + OUTPUT: + + An ideal representing this ray class. If ``reduce`` is ``True``, + the ideal returned is made 'small' by the ideal's + ``reduce_equiv`` function (and ``reduce_equiv`` is used at + each step of computing this representative). Otherwise, the + output is just the appropriate product of the powers of the + generators of the ray class group. + + EXAMPLES: + + Over a real quadratic field field of class number 1:: + + sage: F. = NumberField(x^2 - 5) + sage: m = F.ideal(11).modulus([0, 1]) + sage: R = F.ray_class_group(m) + sage: c0, c1 = R.gens() + sage: c = c0^4*c1; c.ideal() + Fractional ideal (-a) + sage: c.ideal(False) + Fractional ideal (-6242265*a + 1268055) + + Over a real quadratic field of class number 2:: + + sage: F = QuadraticField(40) + sage: R = F.ray_class_group(F.prime_above(13).modulus([0, 1])) + sage: for c in R: + ....: if R(c.ideal()) != c: + ....: print("Bug!") + """ + #R = self.parent() + #exps = self.exponents() + #gens = R.gens_values() + #i = 0 + #while exps[i] == 0: + # i += 1 + # if i == L: + # return R.one() + #nf = R.number_field().pari_nf() + #I = nf.idealpow(gens[i], exps[i], flag=1) + #i += 1 + #while i < L: + # e = exps[i] + # g = gens[i] + # i += 1 + # if e != 0: + # I = nf.idealmul(I, nf.idealpow(g, e, flag=1), flag=1) + #bnr = R.pari_bnr() + #return R.number_field().ideal(nf.idealmul(I[0], nf.nffactorback(I[1]))) + R = self.parent() + #m = R.modulus() + exps = self.exponents() + gens = R.gens_ideals() + L = len(exps) + #Speed this up later using binary powering + i = 0 + while exps[i] == 0: + i += 1 + if i == L: + return R.one() + I = (gens[i]**exps[i]) + if reduce: + #I = I.reduce_equiv(m) + I = R.ideal_reduce(I) + i += 1 + while i < L: + e = exps[i] + g = gens[i] + i += 1 + if e != 0: + I = (I * (g**e)) + if reduce: + #I = I.reduce_equiv(m) + I = R.ideal_reduce(I) + return I + + #def reduce(self): + # nf = self.parent()._number_field.pari_nf() + # return RayClassGroupElement(self.parent(), self.exponents(), nf.idealred(self.value())) + + class SFractionalIdealClass(FractionalIdealClass): r""" An `S`-fractional ideal class in a number field for a tuple `S` of primes. @@ -628,6 +1176,231 @@ def number_field(self): """ return self._number_field +class RayClassGroup(AbelianGroup_class): + Element = RayClassGroupElement + + def __init__(self, gens_orders, names, modulus, gens, bnr, proof=True): + r""" + ``gens`` -- a tuple of pari extended ideals + """ + #AbelianGroupWithValues_class.__init__(self, gens_orders, names, gens, + # values_group=modulus.number_field().ideal_monoid()) + AbelianGroup_class.__init__(self, gens_orders, names) + self._gens = gens + self._proof_flag = proof #TODO: use this, in _ideal_log? + self._modulus = modulus + self._number_field = modulus.number_field() + self._bnr = bnr + self._is_narrow = (len(modulus.infinite_part()) == self._number_field.signature()[0]) and modulus.finite_part().is_one() + + def _element_constructor_(self, *args, **kwds): + try: + L = len(args[0]) + except TypeError: + L = -1 + if L == self.ngens(): + return self.element_class(self, args[0]) + if isinstance(args[0], RayClassGroupElement): + c = args[0] + if c.parent() is self: + return self.element_class(self, c.exponents()) + nf = self._number_field.pari_nf() + bnr = self._bnr + old_exps = c.exponents() + old_gens = c.parent().gens_values() + L = len(old_exps) + if L ==0: + return self.one() + i = 0 + while old_exps[i] == 0: + i += 1 + if i == L: + return self.one() + I = nf.idealpow(old_gens[i], old_exps[i], flag=1) + i += 1 + while i < L: + e = old_exps[i] + g = old_gens[i] + i += 1 + if e != 0: + I = nf.idealmul(I, nf.idealpow(g, e, flag=1), flag=1) + exps = tuple(ZZ(c) for c in bnr.bnrisprincipal(nf.idealmul(I[0], nf.nffactorback(I[1])), flag = 0)) + return self.element_class(self, exps) + else: + I = self._number_field.ideal(*args, **kwds) + if I.is_zero(): + raise TypeError("The zero ideal is not a fractional ideal") + exps = self._ideal_log(I) + return self.element_class(self, exps) + + def _repr_(self): + if self._is_narrow: + s0 = 'Narrow ' + else: + s0 = 'Ray ' + s = 'class group of order %s '%self.order() + if self.order() > 1: + s += 'with structure %s '%self._group_notation(self.gens_orders()) + s += 'of %s'%(self._number_field) + if self._is_narrow: + return s0 + s + s += ' of modulus %s'%(self._modulus) + return s0 + s + + def ray_class_field(self, subgroup=None, names=None, algorithm='stark'): + r""" + Two different algorithms are possible: pari's :pari:`bnrstark` and + :pari:`rnfkummer`. The first one uses the Stark conjecture and only + deals with totally real extensions of a totally real base + field. The second one uses Kummer theory and only deals with + extensions of prime degree. + + INPUT: + + - algorithm -- (default: ``stark``) if the value is ``stark``, + then pari's :pari:`bnrstark` function is tried first, and if that + fails, :pari:`rnfkummer` will be attempted. If the value is + ``kummer``, then pari's :pari:`rnfkummer` is tried first, with + :pari:`bnrstark` as a backup. Using ``stark_only`` or ``kummer_only`` + will just raise an exception if the first attempt fails. + + OUTPUT: + + The class field corresponding to the given subgroup, or the + ray class field if ``subgroup`` is ``None``, as a relative + number field. + + EXAMPLES: + + Class fields of `\QQ(\sqrt{3})`:: + + sage: F. = QuadraticField(3) + sage: m = F.ideal(7).modulus() + sage: R = F.ray_class_group(m) + sage: R.ray_class_field(names='b') + Number Field in b with defining polynomial x^6 + a*x^5 - 4*x^4 - 4*a*x^3 + 2*x^2 + 2*a*x - 1 over its base field + sage: S = R.subgroup([R.gen()^2]) + sage: R.ray_class_field(S, names='b') + Number Field in b with defining polynomial x^2 - a*x - 1 over its base field + sage: m = F.modulus(20) + sage: R = F.ray_class_group(m) + sage: S = R.subgroup([R.gens()[0]^2, R.gens()[1]]) + sage: R.ray_class_field(S, names='b') + Number Field in b with defining polynomial x^2 + (a - 1)*x + 2*a - 4 over its base field + + An example where :pari:`bnrstark` fails, but :pari:`rnfkummer` saves the day:: + + sage: F. = NumberField(x^8 - 12*x^6 + 36*x^4 - 36*x^2 + 9) + sage: m = F.ideal(2).modulus() + sage: R = F.ray_class_group(m) + sage: set_verbose(1) + sage: K = R.ray_class_field(names='b'); K + verbose 1 (...: class_group.py, ray_class_field) bnrstark failed; trying rnfkummer. + Number Field in b with defining polynomial x^2 + (1/3*a^6 - 10/3*a^4 + 5*a^2)*x + 1/3*a^6 - 1/3*a^5 - 11/3*a^4 + 3*a^3 + 8*a^2 - 4*a - 5 over its base field + sage: set_verbose(0) + """ + if subgroup is not None: + try: + test_subgrp = (subgroup.ambient_group() is self) + except AttributeError: + subgroup = self.subgroup(subgroup) + test_subgrp = (subgroup.ambient_group() is self) + if not test_subgrp: + raise ValueError("subgroup does not define a subgroup of this ray class group.") + gens_coords = [h.exponents() for h in subgroup.gens()] + if len(gens_coords) == 0: + subgroup = None + subgroup_mat = None + else: + from sage.matrix.special import column_matrix + subgroup_mat = column_matrix(gens_coords) + else: + subgroup_mat = None + + from cypari2.handle_error import PariError + from sage.misc.verbose import verbose + + bnr = self._bnr + if algorithm == 'stark_only': + if len(self._modulus.infinite_part()) > 0 or not self._number_field.is_totally_real(): + raise NotImplementedError("Stark's conjecture algorithm only implemented for totally real extensions of a totally real base field.") + f = bnr.bnrstark(subgroup=subgroup_mat) + elif algorithm == 'kummer_only': + if (subgroup is None and not self.order().is_prime()) or (subgroup is not None and not self.order().divide_knowing_divisible_by(subgroup.order()).is_prime()): + raise NotImplementedError("Kummer theory algorithm only implemented extensions of prime degree.") + f = bnr.rnfkummer(subgp=subgroup_mat) + elif algorithm == 'stark': + if len(self._modulus.infinite_part()) > 0 or not self._number_field.is_totally_real(): + if (subgroup is None and not self.order().is_prime()) or (subgroup is not None and not self.order().divide_knowing_divisible_by(subgroup.order()).is_prime()): + raise NotImplementedError("Ray class fields only implemented for totally real extensions of totally real base fields, or for extensions of prime degree.") + f = bnr.rnfkummer(subgp=subgroup_mat) + else: + try: + f = bnr.bnrstark(subgroup=subgroup_mat) + except PariError: + if (subgroup is None and self.order().is_prime()) or (subgroup is not None and self.order().divide_knowing_divisible_by(subgroup.order()).is_prime()): + verbose("bnrstark failed; trying rnfkummer.") + f = bnr.rnfkummer(subgp=subgroup_mat) + else: + raise + elif algorithm == 'kummer': + if (subgroup is None and self.order().is_prime()) or (subgroup is not None and self.order().divide_knowing_divisible_by(subgroup.order()).is_prime()): + f = bnr.rnfkummer(subgp=subgroup_mat) + else: + f = bnr.bnrstark(subgroup=subgroup_mat) + else: + raise ValueError("Value of algorithm must be one of \'stark\', \'stark_only\', \'kummer\', or \'kummer_only\'.") + if f.type() == 't_VEC': + raise NotImplementedError("bnrstark returned a list of polynomials. Dealing with this has not been implemented.") + F = self._number_field + nf = F.pari_nf() + f = nf.rnfpolredbest(f) + d = f.poldegree() + cs = [F(f.polcoef(i)) for i in range(d + 1)] + from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing + f = PolynomialRing(F, 'x')(cs) + return F.extension(f, names=names) + + def _ray_class_field_stark(self, subgroup=None, names=None): + pass + + def _ray_class_field_kummer(self, subgroup=None, names=None): + pass + + def _ideal_log(self, ideal): + return tuple(ZZ(c) for c in self._bnr.bnrisprincipal(ideal, flag=0)) + + def ideal_reduce(self, ideal): + from cypari2.handle_error import PariError + ideal = pari(ideal) + try: + pari_ideal = self._bnr.idealmoddivisor(ideal) + except PariError as err: + if err.errtext().find('not coprime') != -1: + raise ValueError('Ideal in ideal_reduce is not coprime to the modulus of this ray class group.') + else: + raise err + return self._number_field.ideal(pari_ideal) + + def gens_values(self): + return self._gens + + @cached_method + def gens_ideals(self): + return tuple(self._number_field._pari_extended_ideal_to_sage(v) for v in self.gens_values()) + + def modulus(self): + return self._modulus + + def number_field(self): + return self._number_field + + def pari_bnr(self): + return self._bnr + + def pari_gens(self): + return self._bnr[4][2] + class SClassGroup(ClassGroup): r""" diff --git a/src/sage/rings/number_field/number_field.py b/src/sage/rings/number_field/number_field.py index 68031c3d642..fe322a76c44 100644 --- a/src/sage/rings/number_field/number_field.py +++ b/src/sage/rings/number_field/number_field.py @@ -106,6 +106,7 @@ from .unit_group import UnitGroup from .class_group import ClassGroup from .class_group import SClassGroup +from .class_group import RayClassGroup from sage.structure.element import Element from sage.structure.parent import Parent @@ -3645,6 +3646,120 @@ def fractional_ideal(self, *gens, **kwds): gens = I.gens() return self._fractional_ideal_class_()(self, gens, **kwds) + def _pari_extended_ideal_to_sage(self, Ix): + """ + Convert an 'extended ideal' in pari format to an ideal in Sage. + + INPUT: + + - ``Ix`` -- A pair whose first entry is a pari ideal and whose second + entry is a pari factorization matrix representing an algebraic number. + + OUTPUT: + + - The ideal that is the product of the ideal ``Ix[0]`` and the principal + ideal generated by ``Ix[1]``. + + EXAMPLES:: + + sage: F = NumberField(x^3 - 2, 'z') + sage: I = F.ideal(27) + sage: a = Matrix(0) + sage: F._pari_extended_ideal_to_sage(pari([I, a])) == I + True + sage: factorization_matrix = pari([pari([pari([31, 0, 1]).Col(), 1]).Mat(), pari([pari([5, 1, 0]).Col(), 1]).Mat()]).Col().matconcat() + sage: F._pari_extended_ideal_to_sage(pari([I, factorization_matrix])) + Fractional ideal (-135*z^2 - 837*z - 4239) + sage: F._pari_extended_ideal_to_sage(F.ray_class_group(I).gens_values()[0]) + Fractional ideal (-8*z^2 - 9*z + 1) + """ + I = self.ideal(Ix[0]) + nf = self.pari_nf() + a = self(nf.nfbasistoalg(nf.nffactorback(Ix[1]))) + return a * I + + def _pari_real_places_to_sage(self): + """ + Return a list converting from the ordering of real places in pari to that + of Sage's :func:`places`. + + EXAMPLES: + + A totally real quartic field where the pari and Sage orderings are different. + + :: + + sage: x = polygen(QQ) + sage: f = x^4 - x^3 - 3*x^2 + x + 1 + sage: F. = NumberField(f(1-2*x)) + sage: F.defining_polynomial() + 16*x^4 - 24*x^3 + 8*x - 1 + sage: F.pari_polynomial() + x^4 - 6*x^2 - 5*x - 1 + sage: F._pari_real_places_to_sage() + (0, 3, 2, 1) + + A quintic field with three real places. + + :: + + sage: x = polygen(QQ) + sage: f = x^5 - x^3 - 2 * x^2 + 1 + sage: F. = NumberField(f(1 - x)) + sage: F._pari_real_places_to_sage() + (2, 1, 0) + """ + try: + return self._pari_real_places + except AttributeError: + pass + pari_conv = self._pari_absolute_structure()[1].lift() + pari_conv = [pari_conv.polcoef(i).sage() + for i in range(pari_conv.poldegree() + 1)] + R = self.defining_polynomial().parent() + pari_conv = R(pari_conv) + pari_roots = [pari_conv(r.sage()) + for r in self.pari_nf()[5][:self.signature()[0]]] + pari_roots_sorted = list(pari_roots) + pari_roots_sorted.sort() + self._pari_real_places = tuple(pari_roots_sorted.index(r) + for r in pari_roots) + return self._pari_real_places + + def modulus(self, finite, infinite=None): + """ + Return the :class:`sage.rings.number_field.class_group.Modulus` specified + by the ideal ``finite`` and the infinite places whose indices are listed + in ``infinite``. + + INPUT: + + - ``finite`` -- An integral ideal of this number field, or something that + :func:`ideal` will accept. + - ``infinite`` (default: ``None``) -- A list of indices indicating which real + places appear in the modulus. The indices refer to the list returned by + :func:`places`. + + OUTPUT: + + A :class:`sage.rings.number_field.class_group.Modulus` whose finite part + is given by ``finite`` and whose infinite part is given by ``infinite``. + The latter being ``None`` is the same as it being empty. + + EXAMPLES:: + + sage: F. = NumberField(x^2-22) + sage: F.modulus(F.ideal(3)) + Fractional ideal (3) + sage: F.modulus(F.ideal(a), []) + Fractional ideal (a) + sage: F.modulus(a, [0, 1]) + (Fractional ideal (a)) * infinity_0 * infinity_1 + """ + if not (finite.parent() is self.ideal_monoid()): + finite = self.ideal(finite) + return finite.modulus(infinite=infinite) + def ideals_of_bdd_norm(self, bound): r""" Return all integral ideals of bounded norm. @@ -4645,6 +4760,76 @@ def class_number(self, proof=None): proof = proof_flag(proof) return self.class_group(proof).order() + def ray_class_group(self, modulus, proof=None, names='c'): + r""" + Return the ray class group modulo ``modulus``. + + EXAMPLES: + + Ray class group of a real quadratic field of class number 1. + + :: + + sage: F = NumberField(x^2 - 5, 'a') + sage: m = F.modulus(F.prime_above(5) * F.prime_above(29), [0, 1]) + sage: G = F.ray_class_group(m); G + Ray class group of order 8 with structure C4 x C2 of Number Field in a with defining polynomial x^2 - 5 of modulus (Fractional ideal (-11/2*a - 5/2)) * infinity_0 * infinity_1 + sage: G.elementary_divisors() + (2, 4) + sage: G.gens_ideals() + (Fractional ideal (31), Fractional ideal (12672)) + sage: G.gens_orders() + (4, 2) + + Ray class group of an imaginary quadratic field of class number 3. + + :: + + sage: F. = QuadraticField(-23) + sage: R = F.ray_class_group(F.ideal(3/2 + a/2)); R + Ray class group of order 6 with structure C6 of Number Field in a with defining polynomial x^2 + 23 of modulus Fractional ideal (1/2*a + 3/2) + sage: R.gens_ideals() + (Fractional ideal (3, 1/2*a + 1/2),) + sage: R.modulus().finite_part().norm() + 8 + sage: F.class_group().gens_ideals() + (Fractional ideal (2, 1/2*a - 1/2),) + + Over `\QQ`, the ray class group modulo `(m)\infty` is the unit group of `\ZZ/m\ZZ`, while + the ray class group modulo `(m)` is the latter modulo `\{\pm1\}`. + + :: + + sage: Q = NumberField(x - 1, 'a') + sage: Q.ray_class_group(Q.ideal(40).modulus([0])).gens_ideals() + (Fractional ideal (17), Fractional ideal (31), Fractional ideal (21)) + sage: Zmod(40).unit_gens() + (31, 21, 17) + sage: Q.ray_class_group(Q.ideal(40)).gens_ideals() + (Fractional ideal (17), Fractional ideal (21)) + """ + proof = proof_flag(proof) + try: + if modulus.parent() is self.ideal_monoid(): + modulus = modulus.modulus() + except AttributeError: + pass + try: + return self.__ray_class_group[modulus, proof, names] + except KeyError: + pass + except AttributeError: + self.__ray_class_group = {} + Kbnf = self.pari_bnf() + Kbnr = Kbnf.bnrinit(pari(modulus), 1) + cycle_structure = tuple(ZZ(c) for c in Kbnr[4][1]) + #@@gens = tuple(self.ideal(hnf) for hnf in Kbnr[4][2]) + from sage.matrix.constructor import Matrix + gens = tuple([Kbnf.idealred([hnf, pari(Matrix(0))]) for hnf in Kbnr[4][2]])#@@ + G = RayClassGroup(cycle_structure, names, modulus, gens, proof=proof, bnr=Kbnr) + self.__ray_class_group[modulus, proof, names] = G + return G + def S_class_group(self, S, proof=None, names='c'): """ Return the S-class group of this number field over its base field. @@ -6681,6 +6866,10 @@ def narrow_class_group(self, proof=None): r""" Return the narrow class group of this field. + If this field is totally imaginary, this function just calls + :func:`class_group`. Otherwise, it calls :func:`ray_class_group` + with the modulus consisting of all real places of this field. + INPUT: - ``proof`` -- (default: ``None``) use the global proof setting, which @@ -6698,9 +6887,26 @@ def narrow_class_group(self, proof=None): Multiplicative Abelian group isomorphic to C2 """ proof = proof_flag(proof) - k = self.pari_bnf(proof) - s = k.bnfnarrow().sage() - return sage.groups.abelian_gps.abelian_group.AbelianGroup(s[1]) + try: + return self.__narrow_class_group + except AttributeError: + r1 = self.signature()[0] + if r1 == 0: + self.__narrow_class_group = self.class_group() + else: + self.__narrow_class_group = self.ray_class_group(self.ideal(1).modulus(range(r1))) + return self.__narrow_class_group + + def narrow_class_number(self, proof=None): + """ + Return the narrow class number of this field. + + EXAMPLES:: + + sage: [QuadraticField(d).narrow_class_number() for d in srange(2, 10) if d.is_squarefree()] + [1, 2, 1, 2, 2] + """ + return self.narrow_class_group(proof=proof).order() def ngens(self): """ diff --git a/src/sage/rings/number_field/number_field_ideal.py b/src/sage/rings/number_field/number_field_ideal.py index e0d16090cc7..119bc3cffe0 100644 --- a/src/sage/rings/number_field/number_field_ideal.py +++ b/src/sage/rings/number_field/number_field_ideal.py @@ -728,18 +728,86 @@ def free_module(self): """ return basis_to_module(self.basis(), self.number_field()) - def reduce_equiv(self): + def modulus(self, infinite=None): """ - Return a small ideal that is equivalent to ``self`` in the group - of fractional ideals modulo principal ideals. Very often (but - not always) if ``self`` is principal then this function returns - the unit ideal. + Return a modulus whose finite part is this ideal and with + specified infinite part. + + INPUT: - ALGORITHM: Calls :pari:`idealred` function. + - ``infinite`` (default: ``None``) -- A list of indices + indicating which real places appear in the modulus. The + indices refer to the list returned by + :func:`sage.rings.number_field.number_field.places`. + + OUTPUT: + + A :class:`sage.rings.number_field.class_group.Modulus` whose finite part + is given by ``finite`` and whose infinite part is given by ``infinite``. + The latter being ``None`` is the same as it being empty. EXAMPLES:: sage: x = polygen(ZZ) + sage: F. = NumberField(x^2-34) + sage: F.ideal(5).modulus() + Fractional ideal (5) + sage: F.ideal(a^2).modulus([]) + Fractional ideal (34) + sage: F.ideal(a).modulus([1]) + (Fractional ideal (a)) * infinity_1 + + .. TODO:: + + Allow infinite to be a list of real places, or even images + of the generator of the number field, rather than just + their indices like + K.ideal(3).modulus(infinite=[K.places()[1]]). + """ + from .class_group import Modulus + if infinite is None: + return Modulus(self) + infinite = list(infinite) + infinite.sort() + return Modulus(self, infinite=infinite) + + def reduce_equiv(self, modulus=None): + """ + Return a small ideal that is equivalent to ``self`` in the group + of fractional ideals modulo principal ideals. + + Return a small ideal that is equivalent to this ideal in the + class group, or in the ray class group of the given modulus. + + Very often (but not always) if ``self`` is principal then this + function returns the unit ideal. + + INPUT: + + - ``modulus`` (default: ``None``) -- a :class:`sage/rings/number_field/class_group.Modulus`. + + OUTPUT: + + An ideal that is 'small' in some sense and equivalent to + ``self`` in the class group, or the ray class group modulo + ``modulus``. + + When ``modulus`` is ``None``, very often (but not always) if + this ideal is principal, then this function returns the unit + ideal. + + ALGORITHM: Calls pari's :pari:`idealred` function if + ``modulus`` is ``None``, or pari's + :func:`sage.libs.pari.gen.idealmoddivisor` otherwise. + + .. NOTE:: + + When ``modulus`` is non-trivial, this may be slow for large input. + + EXAMPLES: + + Finding equivalent ideals in the class group:: + sage: K. = NumberField(x^2 + 23) sage: I = ideal(w*23^5); I Fractional ideal (6436343*w) @@ -749,12 +817,24 @@ def reduce_equiv(self): Fractional ideal (1024, 1/2*w + 979/2) sage: I.reduce_equiv() Fractional ideal (2, 1/2*w - 1/2) + + Now, in a ray class group:: + + sage: I.reduce_equiv(K.ideal(3).modulus()) + Fractional ideal (13, 1/2*w + 9/2) + sage: I.reduce_equiv(K.ideal(2).modulus()) + Traceback (most recent call last): + ... + ValueError: Ideal in ideal_reduce is not coprime to the modulus of this ray class group. """ K = self.number_field() - P = K.pari_nf() - hnf = P.idealred(self.pari_hnf()) - gens = self.__elements_from_hnf(hnf) - return K.ideal(gens) + if modulus is None: + P = K.pari_nf() + hnf = P.idealred(self.pari_hnf()) + gens = self.__elements_from_hnf(hnf) + return K.ideal(gens) + R = K.ray_class_group(modulus) #find any ray class group of this modulus? + return R.ideal_reduce(self) def gens_reduced(self, proof=None): r"""