14 - Composition des formes quadratiques binaires et nombre de classes h(D)
A Course in Arithmetic, chapitre 4 (Serre, GTM 7) — Disquisitiones Arithmeticae §§ 222-256 (Gauss, 1801). Le carnet 11 a établi la densité de Chebotarev 1/2 et la liste des 9 corps quadratiques imaginaires de Heegner pour lesquels h(D) = 1. Le carnet 13 a posé le symbole de Jacobi et l’extension Kronecker en O(log n). Ce carnet 14 franchit une étape : comment calculer h(D) ? La réponse canonique pour les discriminants quadratiques imaginaires D < 2 est l’algorithme de réduction de Gauss sur les formes quadratiques binaires — l’ancêtre de l’arithmetique des corps de nombres, antérieur de 100 ans aux idéaux de Dedekind. Outils : Python stdlib pur (math.isqrt). Le carnet 11 a posé la notion de h(D) (citation en fin de parcours) ; ce 14 la calcule et la vérifie numériquement.
Plan
Formes quadratiques binaires (a, b, c) de discriminant D = b² - 4ac
Réduction de Gauss : algorithme des pas b → b ± 2a et swap(a,c)
Nombre de classes h(D) pour D < 0 : énumération des formes réduites
Vérification sur les 9 corps de Heegner (h(D) = 1)
Exercices
1. Formes quadratiques binaires
Une forme quadratique binaire de discriminant D est un triplet (a, b, c) avec a > 0 et \[f(x, y) = ax^2 + bxy + cy^2, \qquad D = b^2 - 4ac.\] Pour un corps quadratique\(K = \mathbb{Q}(\sqrt{D})\) de discriminant quadratique fondamental D (premier ou produit d’impar d ≥ 1), chaque idéal de \(\mathcal{O}_K\) correspond à une classe de formes équivalentes sous l’action de SL₂(Z). Le nombre de classes h(D) est le nombre de classes d’équivalence de formes réduites : - \(|b| \leq a \leq c\) ; - si \(|b| = a\) ou \(a = c\), alors \(b \geq 0\). Pour D < 0 (corps quadratique imaginaire), h(D) est fini. Pour D > 1 (corps quadratique réel), h(D) est infini (les classes sont parametrées par l’unité fondamentale).
from math import isqrtdef gauss_reduce(a, b, c):# Précondition (section 1) : a > 0, forme positive définie (D < 0).# Pour a <= 0 la descente n'a pas de sens : on refuse l'entrée.if a <=0:raiseValueError('a doit être strictement positif (forme positive définie)') D = b * b -4* a * cwhileTrue:# Translation T^m de SL2(Z) : centrer b dans (-a, a] par pas de 2a.# C'est une translation (pas une réflexion) : la classe propre est préservée. b = b - ((b + a) // (2* a)) * (2* a)# c RECALCULÉ à chaque étape : seule façon de garder D = b^2 - 4ac invariant. c = (b * b - D) // (4* a)if a > c: a, b, c = c, -b, acontinueif (a == c orabs(b) == a) and b <0: b =-breturn (a, b, c)print('Réduction de Gauss : invariance du discriminant et forme réduite')print()tests = [(1, 1, 1), (2, 1, 3), (5, 13, 9), (2, 5, 9), (9, 25, 21), (3, 3, 5), (1, 0, 5), (2, 2, 3), (7, 13, 9)]print(f"{'Entrée':>13} | {'Réduite':>13} | {'D entrée':>9} | {'D sortie':>9} | invariant")print('-'*66)for (a, b, c) in tests: D_in = b * b -4* a * c red = gauss_reduce(a, b, c) D_out = red[1] * red[1] -4* red[0] * red[2] ok ='oui'if D_in == D_out else'NON'print(f'{str((a, b, c)):>13} | {str(red):>13} | {D_in:>9} | {D_out:>9} | {ok}')print()print('Rejet de la précondition a > 0 :')try: gauss_reduce(-3, 1, 1)exceptValueErroras e:print(f'gauss_reduce(-3, 1, 1) -> ValueError : {e}')
Sur les 9 formes de test (toutes à \(a > 0\), conformément à la précondition de la section 1), la sortie vérifie ligne à ligne les deux garanties de l’algorithme : - Invariance du discriminant : la colonne « D sortie » est identique à « D entrée » à chaque ligne — chaque étape est une transformation \(SL_2(\mathbb{Z})\) (translation \(T^m\) pour centrer \(b\) dans \((-a, a]\), puis swap \((a,b,c) \leftrightarrow (c,-b,a)\) tant que \(a > c\)), avec \(c\) recalculé à chaque étape pour maintenir \(D = b^2 - 4ac\). - Bornage de la forme réduite : \(|b| \leq a \leq c\), avec \(b \geq 0\) dans les cas frontière \(|b| = a\) ou \(a = c\) — la sortie \((5, 3, 7)\) pour l’entrée \((9, 25, 21)\) en est l’illustration (trois itérations de swap). La précondition \(a > 0\) est enforced : une entrée à \(a\) négatif est rejetée par un ValueError (dernière ligne de la sortie) — pour \(a < 0\), la « réduction » \(b \to b \pm 2a\) fait croître \(|b|\) au lieu de le réduire, il n’y a pas de forme réduite dans cette direction.
2. Nombre de classes h(D) pour D < 0
Pour \(D < 0\) discriminant quadratique fondamental, h(D) = nombre de formes réduites de discriminant D. C’est un algorithme en \(O(\sqrt{|D|})\) : on itère sur \(b \in [0, \sqrt{|D|/3}]\) (borne due à la condition \(a \leq c \Rightarrow b^2 \leq -3D\)), puis sur les diviseurs \(a\) de \((b^2 - D)/4\), et on garde ceux qui satisfont \(|b| \leq a \leq c\). Pour les 9 corps quadratiques imaginaires de Heegner, l’arithmétique exceptionnelle donne h(D) = 1. Cette liste est célèbre : c’est le théorème de Stark-Heegner (1952/1967), qui résout l’équation $x^2 + Dy^2 = $ premier en généralisant le théorème des deux carrés.
def enumerate_reduced_forms(D): forms = [] b_step =2 b_parity =0if D %4==0else1# Borne de réduction : |b| <= a <= sqrt(|D|/3) (cf. section 2) - la même# borne pour D = 0 mod 4 et D = 1 mod 4 (une borne -D/4 raterait des formes# comme (6, 6, 7) de D = -132). b_max = isqrt(-D //3) if-D >=3else0for b inrange(b_parity, b_max +1, b_step):if (b * b - D) %4!=0:continue a_c = (b * b - D) //4for a inrange(1, isqrt(a_c) +1):if a_c % a !=0:continue c = a_c // aifabs(b) <= a <= c:if (a == c orabs(b) == a) and b <0:continue forms.append((a, b, c))# Miroir (a, -b, c) : classe PROPRE distincte quand# 0 < b < a < c (forme non ambiguë). L'omettre sous-compte h(D)# d'un facteur ~2 sur les discriminants génériques.if b >0and b < a and a < c: forms.append((a, -b, c))return formsHEEGNER_D = [-3, -4, -7, -8, -11, -19, -43, -67, -163]print('Nombre de classes h(D) pour D < 0 (9 corps de Heegner + 4 controles a h>1)')print()print(f"{'D':>6} | {'h(D)':>4} | {'Formes réduites'}")print('-'*60)test_D = HEEGNER_D + [-15, -20, -24, -35]for D in test_D: forms = enumerate_reduced_forms(D) h =len(forms)if h <=3: forms_str =str(forms)else: forms_str =f'({h} formes, premiere = {forms[0]})'print(f'{D:>5} | {h:>4} | {forms_str}')
Les 9 corps quadratiques de Heegner ont tous h(D) = 1 : - D = -3 : une seule forme \((1, 1, 1)\) (anneau des entiers de \(\mathbb{Q}(\sqrt{-3})\) = \(\mathbb{Z}[(1+\sqrt{-3})/2]\)). - D = -4 : forme \((1, 0, 1)\) (anneau des entiers de Gauss \(\mathbb{Z}[i]\)). - D = -7 : forme \((1, 1, 2)\). - D = -8 : forme \((1, 0, 2)\). - D = -11 : forme \((1, 1, 3)\). - D = -19 : forme \((1, 1, 5)\). - D = -43 : forme \((1, 1, 11)\). - D = -67 : forme \((1, 1, 17)\). - D = -163 : forme \((1, 1, 41)\). En control, les discriminants \(-15, -20, -24, -35\) ont h(D) = 2, 2, 2, 2 respectivement (les plus petits non-Heegner). La liste de Heegner est close : c’est l’invariant h(D) = 1 qui definit le théorème de Stark-Heegner.
3. Vérification empirique du théorème de Stark-Heegner
Le théorème de Stark-Heegner (1952 pour h(D) ≤ 1, 1967 pour h(D) = 1 effectif) dit que la liste \(\{D \in \mathbb{Z} : D \leq -3, h(D) = 1\}\) est exactement les 9 discriminants \(\{-3, -4, -7, -8, -11, -19, -43, -67, -163\}\). C’est l’une des listes les plus célèbres de l’arithmétique. La démonstration originale de Stark (1967) utilise l’analyse complexe (formule de Dirichlet pour h(D) comme residu de la \(L\)-serie \(L(1, \chi_D)\)) ; celle de Heegner (1952) utilise les fonctions modulaires et les valeurs de la fonction \(\eta\) de Dedekind.
print('Verification : h(D) = 1 sur tous les D entre -3 et -200 ?')print()HEEGNER_D = [-3, -4, -7, -8, -11, -19, -43, -67, -163]def is_fundamental_disc(D):# Discriminant quadratique fondamental (Python : D % 4 in (0, 1) garde# les D négatifs congrus à 1 mod 4, que % == 1 exclusait à tort).if D %4notin (0, 1):returnFalseif D %4==1: m = Delse: m = D //4if m %4notin (2, 3):returnFalse n =abs(m)for d inrange(2, isqrt(n) +1):if n % (d * d) ==0:returnFalsereturnTrueh_eq_1 = []for D inrange(-3, -200, -1):ifnot is_fundamental_disc(D):continue forms = enumerate_reduced_forms(D)iflen(forms) ==1: h_eq_1.append(D)print(f'Discriminants quadratiques fondamentaux D entre -3 et -200 avec h(D) = 1 :')print(h_eq_1)print()print(f'Heegner connu : {HEEGNER_D[:9]}')print()matches =sorted(set(h_eq_1) &set(HEEGNER_D)) ==sorted(h_eq_1)print(f'Concordance numerique avec la liste de Heegner : {matches}')
Verification : h(D) = 1 sur tous les D entre -3 et -200 ?
Discriminants quadratiques fondamentaux D entre -3 et -200 avec h(D) = 1 :
[-3, -4, -7, -8, -11, -19, -43, -67, -163]
Heegner connu : [-3, -4, -7, -8, -11, -19, -43, -67, -163]
Concordance numerique avec la liste de Heegner : True
Lecture du résultat
Avec le filtre de discriminant fondamental corrigé, l’algorithme de Gauss retrouve la liste de Heegner à partir de la seule condition h(D) = 1 sur l’intervalle D ∈ [-3, -200] : - Pour chaque D discriminant quadratique fondamental (D ≡ 1 mod 4 avec |D| squarefree, ou D = 4m avec m ≡ 2, 3 mod 4 et |m| squarefree — le filtre exclut par exemple -12 = 4 × (-3) où -3 ≡ 1 mod 4), on énumère les formes réduites. - La sortie donne les 9 discriminants h(D) = 1, exactement les Heegner : la concordance est True. - Aucun autre D ∈ [-3, -200] fondamental n’a h(D) = 1. C’est la vérification numérique du théorème de Stark-Heegner — pas la preuve (qui demande l’analyse complexe ou les fonctions modulaires), mais l’observation que la liste est close. Le carnet 07 (zéros de fonctions L) est lié : la formule analytique \(h(D) = \frac{w \sqrt{|D|}}{2\pi} L(1, \chi_D)\) où \(w\) est le nombre de racines de l’unité dans \(\mathcal{O}_K\) (exercice 2 ci-dessous).
4. Exercices
Trois prolongements qui poussent h(D) au-delà de la routine : Exercice 1 : Calculer h(D) pour tous les D entre -3 et -500, et lister les D avec h(D) = 2 ou h(D) = 3. Comparer avec la suite OEIS A014603 (Heegner) et la table des h(D) pour D < 0. Exercice 2 : Pour D = -163 (le plus grand Heegner), vérifier la formule analytique de Dirichlet \(L(1, \chi_{-163}) = \frac{2\pi \, h(D)}{w \sqrt{|D|}}\) où \(w = 2\) (unités ±1), en la confrontant à un calcul indépendant de \(L(1, \chi_D)\) : le produit d’Euler tronqué \(\prod_p (1 - \chi_D(p)/p)^{-1}\). Exercice 3 : Implémenter la composition de Gauss de deux formes réduites : pour (a1, b1, c1) et (a2, b2, c2) de discriminant D, calculer (a3, b3, c3) tel que la classe du composé soit le produit des classes. (Indication : pgcd(a1, a2, (b1+b2)/2), puis algorithme de Lagrange.)
print('Exo 1 : h(D) pour D entre -3 et -500, lister h=2 et h=3')print()h1_list = []h2_list = []h3_list = []for D inrange(-500, -2):ifnot is_fundamental_disc(D):continue forms = enumerate_reduced_forms(D) h =len(forms)if h ==1: h1_list.append(D)elif h ==2: h2_list.append(D)elif h ==3: h3_list.append(D)print(f'h(D) = 1 ({len(h1_list)} discriminants) : {sorted(h1_list)}')print(f'Concordance Heegner : {sorted(h1_list) ==sorted(HEEGNER_D)}')print()print(f'h(D) = 2 ({len(h2_list)} discriminants) : {sorted(h2_list)}')print()print(f'h(D) = 3 ({len(h3_list)} discriminants) : {sorted(h3_list)}')
import mathprint('Exo 2 : formule analytique de Dirichlet pour D = -163')print()D =-163h =len(enumerate_reduced_forms(D))w =2# unités ±1 (D < -4)sqrtD = math.sqrt(-D)# Côté 1 : formule de classes de Dirichlet, avec h compté par énumération des formesL1_dirichlet =2* math.pi * h / (w * sqrtD)print(f'h(-163) = {h}, w = {w} : L(1, chi_D) = 2*pi*h/(w*sqrt(|D|)) = {L1_dirichlet:.6f}')print()# chi_D(p) = (D/p) : critère d'Euler pour p impair, cas p = 2 à part# (chi_D(2) = +1 si D = ±1 mod 8, -1 si D = ±3 mod 8).def euler_product_L1(D, N): sieve =bytearray([1]) * (N +1) sieve[0:2] =b'\x00\x00' prod =1.0if D %2!=0: chi2 =1if D %8in (1, 7) else-1 prod /= (1- chi2 /2)for p inrange(3, N +1, 2):ifnot sieve[p]:continuefor m inrange(p * p, N +1, p): sieve[m] =0if D % p ==0:continue# premier ramifié : facteur local 1 chi =pow(D, (p -1) //2, p) prod /= (1- (1if chi ==1else-1) / p)return prodL1_euler =Nonefor N in (10**4, 10**5): L1_euler = euler_product_L1(D, N) écart =abs(L1_euler - L1_dirichlet)print(f'N = {N:>6} : L(1, chi_D) par produit d Euler = {L1_euler:.6f}, écart = {écart:.2e}')print()print(f'Concordance (écart < 1e-3 à N = 10^5) : {abs(L1_euler - L1_dirichlet) <1e-3}')
Exo 2 : formule analytique de Dirichlet pour D = -163
h(-163) = 1, w = 2 : L(1, chi_D) = 2*pi*h/(w*sqrt(|D|)) = 0.246069
N = 10000 : L(1, chi_D) par produit d Euler = 0.245458, écart = 6.10e-04
N = 100000 : L(1, chi_D) par produit d Euler = 0.245861, écart = 2.07e-04
Concordance (écart < 1e-3 à N = 10^5) : True
from math import gcddef gauss_compose_step1(a1, b1, c1, a2, b2, c2):if (b1 + b2) %2!=0:raiseValueError('b1 + b2 doit etre pair') D = b1*b1 -4*a1*c1if D != b2*b2 -4*a2*c2:raiseValueError('Discriminants incompatibles') g = gcd(gcd(a1, a2), (b1 + b2) //2)return gprint('Exo 3 : pgcd pour composition de Gauss')print()tests = [((1, 1, 2), (1, 1, 2), -7), ((1, 1, 3), (1, 1, 3), -11), ((2, 1, 3), (2, 1, 3), -23)]for (a1, b1, c1), (a2, b2, c2), D in tests: g = gauss_compose_step1(a1, b1, c1, a2, b2, c2)print(f'gcd pour compose({(a1,b1,c1)}, {(a2,b2,c2)}) avec D={D} : g = {g}')
Exo 3 : pgcd pour composition de Gauss
gcd pour compose((1, 1, 2), (1, 1, 2)) avec D=-7 : g = 1
gcd pour compose((1, 1, 3), (1, 1, 3)) avec D=-11 : g = 1
gcd pour compose((2, 1, 3), (2, 1, 3)) avec D=-23 : g = 1
Conclusion
Le nombre de classes h(D) est l’invariant arithmetique fondamental d’un corps quadratique. Pour D < 0, c’est un entier calculable par énumération des formes réduites (Gauss). Pour D > 0, h(D) est infini (les classes sont parametrées par l’unité fondamentale). La liste des 9 corps de Heegner (h(D) = 1) est close et vérifiable : c’est le théorème de Stark-Heegner, à la croisée de l’arithmétique, de l’analyse et de la théorie des nombres. Suite naturelle : - Carnet 15.1 envisagé : composition de Gauss explicite (algorithme de Lagrange complet), vérification que le groupe de classes est bien abélien. - Pont avec la série GenAI : la formule analytique \(h(D) = \frac{w \sqrt{|D|}}{2\pi} L(1, \chi_D)\) est reliée à l’étude des zéros de fonctions L (carnet 07).
Ressources
Serre, A Course in Arithmetic, chapitre 4 (Springer GTM 7) — référence canonique.
Cox, Primes of the Form x² + ny² (Wiley) — chapitre 7 sur la composition de Gauss.