Repository navigation
Bibliotheques
Version enrichie — Projet Riemann_Lab · hprzeta Mise à jour : 2 juin 2026 · Auteur : hprzeta
# Activation obligatoire avant tout script
source ~/projet_zeta/zeta_env/bin/activate
export PYTHONPATH="${PYTHONPATH}:${HOME}/projet_zeta/src"
python mon_script.pyModules du projet (dans ~/projet_zeta/src/calculs/optimisation/) :
theta_rapide.py # θ(t) asymptotique + Z_fast
riemann_siegel_batch.py # Z_batch GPU/CPU, scanner_batch + Illinois
turing_validation.py # N(T), S(T), valider_turing
parallel_scanner.py # calcul multiprocessing
compute_zeros_v3.py # orchestrateur principal
Rôle : calcul exact de
from mpmath import mp, mpc, mpf, zeta, loggamma, zetazero
from mpmath import siegelz, siegeltheta # wrappers haut niveau
from mpmath import findroot, arg, re, im, pi, log
# ── Précision adaptative (règle v3) ─────────────────────────────────────
mp.dps = 25 # détection rapide (changement de signe Z)
mp.dps = 35 # affinage Illinois (atteint tol=1e-12)
mp.dps = 50 # validation / publication des 1000 premiers zéros
# ── θ(t) exact (pour t < 20 ou vérification) ────────────────────────────
theta_exact = float(
mp.im(loggamma(mpf("0.25") + mpc(0, t) / 2))
- (t / 2) * mp.log(mp.pi)
)
# ── Z(t) optimisé — un seul appel à zeta() ──────────────────────────────
import math
s = mpc("0.5", t)
z = zeta(s) # appel unique
val = math.cos(th) * float(z.real) - math.sin(th) * float(z.imag)
# ── Zéros via zetazero (lent, pour vérification seulement) ──────────────
zetazero(1) # 0.5 + 14.1347...j — précis mais ~1 s/zéro
zetazero(10) # 0.5 + 49.7738...j
# ── Affinage Illinois ────────────────────────────────────────────────────
mp.dps = 35
t0 = findroot(
lambda x: Z_fast(float(x), dps=35),
(t_a, t_b), # intervalle avec changement de signe
solver="illinois",
tol=1e-12,
maxsteps=80,
)
# ⚠️ tol=1e-12 cohérent avec 35 dps. tol=1e-20 à 35 dps → IMPOSSIBLE (bug v2)
# ── Sauvegarde/restauration de précision ────────────────────────────────
dps_save = mp.dps
mp.dps = 35
# ... calcul ...
mp.dps = dps_save # toujours restaurer après usage localRôle : θ(t) vectorisé, matrice de phases RS, détection de signes.
import numpy as np
# ── θ(t) vectorisé (asymptotique de Stirling) ───────────────────────────
LOG_2PI = math.log(2 * math.pi)
PI_8 = math.pi / 8.0
t2 = ts * ts; t3 = t2 * ts; t5 = t3 * t2
thetas = (
(ts / 2.0) * (np.log(ts) - LOG_2PI)
- ts / 2.0 - PI_8
+ 1.0 / (48.0 * ts)
+ 7.0 / (5760.0 * t3)
- 31.0 / (80640.0 * t5)
)
# ── Matrice de phases RS (cœur de Z_batch) ──────────────────────────────
ns = np.arange(1, N_max + 1, dtype=np.float64)
log_ns = np.log(ns)
inv_sqn = 1.0 / np.sqrt(ns)
phases = thetas[:, None] - ts[:, None] * log_ns[None, :] # shape (M, N_max)
Z = 2.0 * np.dot(np.cos(phases), inv_sqn) # shape (M,)
# ── Détection des changements de signe ──────────────────────────────────
signes = np.sign(Z_vals)
passages = np.where(np.diff(signes) != 0)[0] # indices des changements
# ── Espacements normalisés (conjecture de Montgomery) ───────────────────
ecarts = np.diff(zeros)
t_mid = zeros[:-1]
delta_n = ecarts * np.log(np.array(t_mid) / (2 * math.pi)) / (2 * math.pi)Rôle : accélération ×8 à ×12 de Z_batch via CUDA.
import cupy as cp
# ── Détection GPU ────────────────────────────────────────────────────────
n_devices = cp.cuda.runtime.getDeviceCount() # 0 si GPU inactive
# ⚠️ Activer le GPU : sudo prime-select nvidia puis redémarrage
# ⚠️ Paquets : pip install cupy-cuda12x (doit matcher CUDA 12.2)
# ── Calcul GPU identique à numpy, backend différent ─────────────────────
ts_g = cp.asarray(ts_cpu, dtype=cp.float64)
thetas_g = cp.asarray(thetas_cpu, dtype=cp.float64)
log_ns_g = cp.asarray(log_ns, dtype=cp.float64)
inv_sqn_g= cp.asarray(inv_sqn, dtype=cp.float64)
phases = thetas_g[:, None] - ts_g[:, None] * log_ns_g[None, :]
Z_g = 2.0 * cp.dot(cp.cos(phases), inv_sqn_g)
Z_cpu = cp.asnumpy(Z_g) # retour vers numpy
# ── Limites mémoire GTX 960M (4 GB VRAM) ────────────────────────────────
# bloc recommandé : int(1_500_000_000 / (N_max * 8))
# T = 10 000 (N_max=39) → bloc=500 000 → 156 MB ✅
# T = 100 000 (N_max=126) → bloc=200 000 → 201 MB ✅
# ⚠️ BLAS thread contention : benchmarker les modes isolément (pas en concurrent)Rôle : partitionner
import multiprocessing
# ⚠️ Ne PAS utiliser joblib avec mpmath :
# GMP (MPFR) a un état global non thread-safe → corruption mémoire.
# Solution : multiprocessing (fork → chaque processus a sa propre copie GMP)
n_workers = multiprocessing.cpu_count() # 4 sur i7
# ── Partitionnement ──────────────────────────────────────────────────────
longueur = (T_MAX - T_MIN) / n_workers
segments = [
(T_MIN + i * longueur, T_MIN + (i + 1) * longueur)
for i in range(n_workers)
]
# ⚠️ Chevauchement de 2·STEP aux jonctions pour ne rater aucun zéro de bord
# ── Lancement ────────────────────────────────────────────────────────────
with multiprocessing.Pool(n_workers) as pool:
resultats = pool.map(worker_func, args_list)
# ── Fusion et déduplication ──────────────────────────────────────────────
zeros_bruts = [t for segment in resultats for t in segment]
zeros_bruts.sort()
zeros = dedupliquer(zeros_bruts, tolerance=0.001)Rôle : stocker les zéros calculés avec métadonnées.
import pandas as pd
from pathlib import Path
from datetime import datetime
horodatage = datetime.now().strftime("%Y%m%d_%H%M%S")
nom_csv = f"zeros_v3_T{T_MAX:.0f}_{horodatage}.csv"
df = pd.DataFrame({
"n": range(1, len(zeros) + 1),
"partie_imaginaire": zeros,
"T_MAX": T_MAX,
"methode": "RS-detection + Illinois-affinage",
"step_adaptatif": STEP,
"n_workers": N_WORKERS,
"turing_complet": True,
"calcule_le": horodatage,
})
df.to_csv(str(Path("calculs") / nom_csv), index=False)
# ── Chargement ultérieur ─────────────────────────────────────────────────
df = pd.read_csv("zeros_zeta_T10000_20260424_205325.csv")
zeros = df["partie_imaginaire"].tolist()Rôle : tracer Z(t), espacements, droite critique.
import matplotlib.pyplot as plt
import numpy as np
# ⚠️ TOUJOURS plt.savefig() + plt.close() — JAMAIS plt.show() en production
# plt.show() est BLOQUANT : empêche la génération du log/CSV après le graphique.
fig, axes = plt.subplots(1, 3, figsize=(18, 5))
fig.suptitle(f"Zéros ζ — {len(zeros)} zéros [T={T_MAX:.0f}]")
# ── Z(t) avec marqueurs aux zéros ───────────────────────────────────────
ax = axes[0]
ax.plot(t_vals, Z_vals, 'b-', linewidth=0.8, label='Z(t)')
ax.axhline(0, color='k', linewidth=0.5)
for t0 in zeros:
ax.axvline(t0, color='r', linewidth=0.5, alpha=0.4)
# ── Histogramme espacements vs GUE ──────────────────────────────────────
ax = axes[1]
ax.hist(delta_n, bins=50, density=True, alpha=0.75, color='steelblue')
s_vals = np.linspace(0, 4, 200)
gue = (math.pi / 2) * s_vals * np.exp(-math.pi * s_vals**2 / 4)
ax.plot(s_vals, gue, 'r-', linewidth=2, label='GUE (Wigner-Dyson)')
# ── Droite critique ──────────────────────────────────────────────────────
ax = axes[2]
ax.scatter([0.5] * len(zeros), zeros, s=3, color='darkblue', alpha=0.4)
ax.axvline(0.5, color='r', linestyle='--', linewidth=1.5)
plt.tight_layout()
plt.savefig("zeros_v3.png", dpi=150)
plt.close() # ← libère la mémoire, ne bloque pasRôle : journal d'exécution persistant avec rotation.
from loguru import logger
from pathlib import Path
logger.add(
Path("~/projet_zeta/logs/projet_zeta.log"),
rotation="100 MB",
retention="30 days",
format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {message}"
)
logger.info(f"Calcul v3 démarré : T_MAX={T_MAX:.0f}")
logger.success(f"Zéro #{n:5d} : t = {t:.14f} (écart LMFDB : {ecart:.2e})")
logger.warning(f"Worker {wid} : surplus de {delta} zéros aux bords")
logger.error(f"Illinois non convergé : [{t_a:.4f}, {t_b:.4f}]")from tqdm import tqdm
for t in tqdm(np.arange(T_MIN, T_MAX, STEP), desc="Scan Z(t)", unit="pt"):
# calcul...from sympy import zeta, gamma, pi, I, re, im, Symbol, simplify
from sympy.ntheory import primerange, isprime, nextprime
s = Symbol('s')
zeta(s) # objet symbolique
list(primerange(1, 100)) # premiers jusqu'à 100
nextprime(100) # 101Note : sympy n'est pas utilisé dans le pipeline de production (trop lent). Utiliser mpmath pour les calculs numériques de haute précision.
import psutil
n_cores = psutil.cpu_count(logical=True) # 4 (i7)
ram_gb = psutil.virtual_memory().total / 1e9 # 8.0 GB
ram_pc = psutil.virtual_memory().percent # % utilisé# Vérifier que le GPU NVIDIA est actif
nvidia-smi
# Si "no devices found" :
sudo prime-select nvidia
# → reboot nécessaire
# Paquet CuPy correct pour CUDA 12.2 :
pip install cupy-cuda12x --break-system-packagesPourquoi cette section : la §17 de
Formules_zeta.md(mur de latence) montre que le goulot du calcul est la latence d'un appelmpmath.siegelz(~296 ms à$t\approx 9000$ ), répétée ~27 fois par zéro. Le seul levier réaliste pour des positions$<10^{-10}$ sous 30 min n'est ni le GPU ni la RAM (absents de la formule de coût), mais le remplacement dempmathpar Arb à l'étape d'affinage.
Rôle : évaluer mpmath.siegelz à précision égale.
# ── Installation (Ubuntu) ────────────────────────────────────────────────
sudo apt install libflint-dev libarb-dev # bibliothèques C
pip install python-flint --break-system-packages # binding Python (FLINT + Arb)# ── Précision en BITS (≈ dps · 3.33) ─────────────────────────────────────
from flint import arb, ctx
ctx.prec = 200 # ~60 chiffres décimaux
# Fonction clé côté Arb : acb_dirichlet_hardy_z → Z(t) de Hardy avec bornes
# d'erreur rigoureuses. Si python-flint ne l'expose pas directement, l'appeler
# via un petit wrapper ctypes vers libarb (acb_dirichlet_hardy_z).Le facteur ×27 est mesuré (benchmark 2026-06-09 sur 200 points
$t \in [100, 10000]$ ). Bonus à grand volume : l'algorithme Odlyzko–Schönhage (multi-évaluation par FFT) casse le coût asymptotique — pertinent seulement pour$T \gg 10 000$ .
Statut : VALIDÉ ×27 (2026-06-09)
| Paramètre | Valeur |
|---|---|
| Speedup vs mpmath | ×27 mesuré |
| temps/appel | 0.77 ms vs 21.13 ms |
| Précision | double ~15 dps, erreur |
| Accès | ctypes + libflint bundlée python-flint 0.8.0 |
| Module | src/calculs/optimisation/arb_wrapper.py |
| Pattern |
arb_hardy_z(t) remplace float(mp.siegelz(t))
|
| Outil | Rôle | Statut projet |
|---|---|---|
mpmath.siegelz |
affinage fallback (Voie B) | production — remplacé par Arb |
Arb arb_fpwrap_cdouble_hardy_z
|
affinage rapide (double natif, 0 malloc) | VALIDÉ ×27 |
| FLINT | dépendance d'Arb (entiers, polynômes) | requis pour Arb |
| Run | STEP | Zéros | Manquants | Temps | Turing |
|---|---|---|---|---|---|
| T=10 000 v1 | 0.1 fixe | 10 137 / 10 142 | 6 | ~2.58 min | ❌ INCOMPLET |
| T=10 000 v2 | 0.05 pour t≥5k | 10 141 / 10 142 | 0 | 2.60 min | ✅ COMPLET |
| T=100 000 v1 | 0.1 fixe | 137 904 / 138 069 | 356 | 1h58 | ❌ INCOMPLET |
| T=100 000 v2 | 0.1/0.05/0.02 adaptatif | 138 039 / 138 069 | 68 | 105.1 min | ❌ INCOMPLET |
| T=100 000 v3 | 0.05/0.010 | EN COURS | — | ~3–4h | attendu ✅ |
v1 → STEP=0.1 fixe (trop grand à grand t). v2 → STEP adaptatif 0.1/0.05/0.02 + overlap=2.0 (commit
50837f7) — STEP=0.02 insuffisant à t≥50k. v3 → STEP adaptatif 0.05/0.010 (commit181fdd1) — lancé 2026-06-10 16h42.
| Bibliothèque | Usage dans v3 | Mode |
|---|---|---|
mpmath |
θ(t) exact, Z_fast, affinage Illinois, validation | CPU précis |
numpy |
θ(t) vectorisé, phases RS, détection signes | CPU rapide |
cupy |
Z_batch GPU (si GTX 960M active) | GPU ×10 |
multiprocessing |
Parallélisation sur 4 cœurs | CPU ×4 |
pandas |
Sauvegarde/chargement CSV zéros | I/O |
matplotlib |
Z(t), espacements, droite critique | Visualisation |
loguru |
Journal d'exécution complet | Logging |
tqdm |
Barres de progression | Interface |
sympy |
Exploration symbolique seulement | Hors prod. |
psutil |
Monitoring CPU/RAM | Diagnostic |
arb / python-flint
|
Affinage Z(t) rapide (levier §17, à benchmarker) | CPU certifié |
Découvert lors du diagnostic pipeline v4.1 (3 juin 2026) : workprec ne contrôle
pas la précision des fonctions mpmath internes.
import mpmath as mp
mp.mp.dps = 35 # global : 35 dps
with mp.workprec(50): # 50 bits ≈ 15 dps — contexte local
x = mp.siegelz(100.0) # ⚠️ siegelz re-lit mp.dps = 35 → précision 35 dps
y = mp.findroot(
mp.siegelz, (14.1, 14.2),
solver="illinois"
)
# La boucle Illinois tourne à 50 bits, MAIS chaque éval siegelz = 35 dpsUsage correct de workprec : opérations arithmétiques pures (additions, multiplications,
racines) — PAS pour les fonctions de haut niveau comme siegelz, zeta, gamma.
# float64 natif — ~1e-15 de précision, pas de surcharge multi-précision
z = mp.fp.siegelz(t) # float64
theta = mp.fp.siegeltheta(t)
# findroot en float64 (bracket obligatoire pour stabilité) :
zero = mp.fp.findroot(
mp.fp.siegelz, (a, b),
solver="illinois", tol=1e-12, maxsteps=80,
)Quand utiliser fp : t < 300 (N < 7 termes RS), tol = 1e-12, bracket fourni.
float64 (
Quand NE PAS utiliser fp : grand t, précision < 1e-12 exigée, ou validation LMFDB (qui nécessite ≥ 30 dps pour comparer γ à 14 décimales significatives).
| API | Précision | Coût relatif | Usage recommandé |
|---|---|---|---|
mp.siegelz(t) dps=35 |
~1e-35 | référence (1×) | affinage final, LMFDB |
mp.siegelz(t) dps=15 |
~1e-15 | ~×4 plus rapide | — (workprec ne marche pas) |
mp.fp.siegelz(t) |
~1e-15 | ~×40 plus rapide | t<300, bracket fourni |
| C/libmpfr PREC=170 bits | ~1e-50 | ~×10 vs mpmath | t≥300, Illinois_C |
Auteur : hprzeta — Riemann_Lab — Mise à jour : 3 juin 2026
Contexte : Phase C, branche
Riemann_Lab_C. Le module Cillinois_mpfr.soexposeillinois_refinedepuis le commit581e34d. Règle post-fork obligatoire (voir skillphase-c-illinois).
import ctypes, os, multiprocessing
SO_PATH = os.path.join(
os.path.dirname(__file__),
"c_modules", "illinois_mpfr.so"
)
_lib = None # handle par-process, initialisé APRÈS le fork
def _init_worker():
"""Chargé dans chaque worker du Pool — post-fork obligatoire."""
global _lib
_lib = ctypes.CDLL(SO_PATH)
# ── illinois_refine : fa/fb passés depuis Python (Option B) ──────────
_lib.illinois_refine.restype = ctypes.c_double
_lib.illinois_refine.argtypes = [
ctypes.c_double, # a — borne gauche de l'intervalle
ctypes.c_double, # b — borne droite
ctypes.c_double, # fa = Z_vect_correct(a) déjà calculé
ctypes.c_double, # fb = Z_vect_correct(b)
ctypes.c_int, # prec_bits (170 bits = ~51 décimales)
ctypes.c_double, # tol (1e-12)
ctypes.c_int, # max_iter (200)
]
# ⚠️ Le .so ne doit PAS être chargé dans le processus parent (avant Pool)
# → chargement pré-fork = handle sérialisé → ×1.8 au lieu de ×4
with multiprocessing.Pool(4, initializer=_init_worker) as pool:
resultats = pool.map(worker_func, args_list)def affiner_illinois_c(a: float, b: float,
fa: float, fb: float,
tol: float = 1e-12) -> float:
"""Affinage illinois via C/libmpfr (Option B).
fa/fb : valeurs Z déjà calculées par Z_vect_correct — zéro recalcul."""
return _lib.illinois_refine(a, b, fa, fb, 170, tol, 200)Pourquoi passer fa/fb ? Si Illinois recalculait
| Méthode | ms/appel ( |
ms/appel ( |
Précision position | Statut |
|---|---|---|---|---|
mpmath.findroot(siegelz) dps=35 |
~296 ms | ~296 ms |
|
lent mais fiable |
illinois_refine C/libmpfr 170 bits |
~58.9 ms | ~159 ms |
|
production |
mp.fp.siegelz (float64) |
~7 ms | ~7 ms |
|
t<300 seulement |
⚠️ Arrêt immédiat si.soabsent — pas de fallback silencieux :if not os.path.exists(SO_PATH): raise FileNotFoundError(f"illinois_mpfr.so introuvable : {SO_PATH} — `make`")
Auteur : hprzeta — Riemann_Lab — Mise à jour : 3 juin 2026 (§14 ajouté) · 6 juin 2026 (§15 ajouté)
Présent sur
Riemann_Lab_Cdepuis commitd26ed12. Confirmé le 6 juin 2026 (ancêtre de191d60a).
Mesurer le temps cumulé, le nombre d'appels et le coût ms/appel pour chacune des
4 phases du pipeline compute_zeros_v4_1.py :
| Phase (clé) | Description | Implémentation |
|---|---|---|
detection |
Balayage |
Z_vect_correct (numpy) |
illinois_C |
Affinage haute précision (t ≥ 300) |
illinois_refine (C/libmpfr, 170 bits) |
mpmath_petit_t |
Fallback t < 300 ( |
mp.fp.siegelz ou mp.siegelz dps=35 |
turing |
Validation Turing-Backlund en fin de run | turing_validation.py |
from chrono_phases import ChronoPhases
chrono = ChronoPhases()
# ── Dans la boucle worker ────────────────────────────────────────────────
with chrono.phase("detection"):
Z_vals = Z_vect_correct(ts)
with chrono.phase("illinois_C"):
gamma = affiner_illinois_c(a, b, fa, fb)
with chrono.phase("mpmath_petit_t"):
gamma = mp.fp.findroot(mp.fp.siegelz, (a, b),
solver="illinois", tol=1e-12)
with chrono.phase("turing"):
turing_ok = valider_turing(zeros, T_MAX)
# ── Rapport de fin de run ───────────────────────────────────────────────
chrono.rapport()
# → affiche : Phase | temps cumulé | appels | ms/appel | % mur×WRun T=1 000 (4 workers, cumulé) — goulot = mpmath_petit_t :
| Phase | Temps cumulé | Appels | ms/appel | % mur×W |
|---|---|---|---|---|
mpmath_petit_t |
11.27 s | 138 | 81.65 | 35.6 % |
illinois_C |
5.22 s | 511 | 10.21 | 16.5 % |
turing |
2.33 s | 1 | 2 330.99 | 7.4 % |
detection |
0.09 s | 4 | 21.53 | 0.3 % |
Run T=10 000 (4 workers, cumulé) — goulot = illinois_C :
| Phase | Temps cumulé | Appels | ms/appel |
|---|---|---|---|
illinois_C |
1 592.5 s | 10 004 | 159 ms |
mpmath_petit_t |
79.5 s | 138 | 576 ms |
turing |
35.0 s | 1 | — |
detection |
2.8 s | 20 | 138 ms |
Lecture clé : à T=1 000, le goulot est
mpmath_petit_t(138 zéros$t < 300$ viamp.siegelzdps=35). À T=10 000, il bascule surillinois_Cdont le coût croît en$O(\sqrt{t})$ (§20 deFormules_zeta.md). Ladetectionest toujours négligeable (< 0.3 %).
Auteur : hprzeta — Riemann_Lab — Mise à jour : 6 juin 2026 (§15 ajouté) · 9 juin 2026 (§12 mis à jour, VALIDÉ ×27) · 10 juin 2026 (résultats runs Arb §12 : v2 terminé 68 manquants, v3 0.05/0.010 EN COURS)
| Attribut | Valeur |
|---|---|
| Fichier | src/calculs/optimisation/c_modules/brent_mpfr.c |
| Dépendances |
libmpfr-dev, libgmp-dev
|
| Compilation | gcc -O3 -march=native -shared -fPIC -o brent_mpfr.so brent_mpfr.c -lmpfr -lgmp |
| Chargement | POST-FORK uniquement (segfault sinon) |
| Phase 1 | prec_fast=64 bits → 1 limb mpfr → SIMD AVX2 → ×16 local |
| Phase 2 | prec_full=80 bits → validation finale < 1e-11 |
Fichier : /etc/sudoers.d/zeta_turbo
Commandes NOPASSWD :
/usr/bin/cpupower frequency-set -g performance/usr/bin/cpupower frequency-set -g powersave-
/usr/sbin/sysctl -w vm.swappiness=10←/usr/sbin/sur Ubuntu 24.04 /usr/sbin/sysctl -w vm.swappiness=60/usr/bin/systemctl stop NetworkManager/usr/bin/systemctl start NetworkManager
sysctl est à /usr/sbin/sysctl, pas /sbin/sysctl.
Gain turbo mesuré : 26.6 min T=100k (vs 28.0 min sans turbo — gain ×1.05 seulement)
illinois_arb.c remplace brent_mpfr.c comme backend d'affinage. Algorithme à 2 phases :
| Phase | Fonction | Coût | Précision | Condition |
|---|---|---|---|---|
| Phase 1 |
Z_rs_double (Illinois C, double natif) |
~0.015 ms/appel | ~2e-16 | itérer jusqu'à |
| Phase 2 | 2 Newton steps Z_arb (Arb/FLINT) |
~3.5 ms × 2 | 2 pas fixes | |
| Fallback | Illinois Z_arb classique |
~3.5 ms/iter |
|
| Attribut | Valeur |
|---|---|
| Fichier | src/calculs/optimisation/c_modules/illinois_arb.c |
| Dépendances |
libflint-dev, libgmp-dev (Arb/FLINT intégré depuis FLINT 3.x) |
| Compilation | gcc -O3 -march=native -shared -fPIC -o illinois_arb.so illinois_arb.c -lflint -lgmp |
| Chargement | POST-FORK uniquement |
| Phase 1 backend |
arb_fpwrap_cdouble_hardy_z — double natif IEEE 754 |
| Phase 2 backend |
arb_cdouble_hardy_z — Arb adaptative ~3.5 ms |
avec :
-
$C_{\text{Phase1}} \approx 0.015 \text{ms}$ (Z_rs_double double natif) -
$C_{\text{Phase2}} \approx 3.5 \text{ms}$ (Z_arb Arb/FLINT) -
$n_1 \approx 24$ itérations Phase 1 (bracket 0.010 → 1e-6)
Coût moyen observé T=100k :
| Indicateur | Valeur |
|---|---|
| Zéros | ~138 080 / 138 069 attendus |
| Manquants | 0 ✅ |
| Durée | 8.8 min (vs 23.7 min v10) |
| Gain vs v10 | ×2.69 direct · ×16.9 benchmark |
| Turing-Backlund | COMPLET ✅ |
| LMFDB | 20/20 ✅ |
| Phase 1 utilisée | ~99.94 % des zéros |
| Fallback mpmath | ~0.06 % (t < 200) |
Voir analyse_problemes_v10_v12 pour l'analyse complète.
Fichiers : illinois_arb.c et scan_arb.c
#define N_MAX_CACHE 2100 /* couvre T ≲ 27M (N_RS=2100 termes) */
static double log_n_cache[N_MAX_CACHE + 1]; /* log(n) pour n=1..2100 */
static double isqrt_n_cache[N_MAX_CACHE + 1]; /* 1/sqrt(n) */
static int g_cache_ready = 0;
static void init_rs_cache(void) {
if (g_cache_ready) return;
for (int n = 1; n <= N_MAX_CACHE; n++) {
log_n_cache[n] = log((double)n);
isqrt_n_cache[n] = 1.0 / sqrt((double)n);
}
g_cache_ready = 1;
}Taille mémoire : 2 × 2101 × 8 octets = 33 KB — tient en cache L2.
Initialisation : appelée une fois par worker après fork(). Idempotente.
Couverture :
Impact dans la boucle RS :
/* avant v14 */
sum += cos(th - t * log((double)n)) / sqrt((double)n); /* log + sqrt à chaque terme */
/* v14+ */
sum += cos(th - t * log_n_cache[n]) * isqrt_n_cache[n]; /* lecture tableau : ~4 cycles */Gain mesuré : ×1.10 sur T=100k (log/sqrt ≈ 50 cycles chacun vs accès L2 ≈ 4 cycles).
| Indicateur | Valeur |
|---|---|
| Coût moyen à T=100k ( |
≈ 1,8 ms/appel |
| Coût à T=5M ( |
≈ 0,9 ms/appel |
| Flags standard |
flags=0 (précision automatique avec garantie d'erreur) |
| Part Illinois dans T=100k | ≈ 98 % du temps total |
Le coût Z_arb diminue relativement avec T car Arb ajuste sa précision interne. À T=5M, chaque appel traite plus de termes mais avec une précision suffisante plus facilement détectable — coût sub-linéaire en
$N_\text{RS}$ .
Principe (dérivation complète : Formules_zeta §30) : le biais de Z_rs fait
diverger le pseudo-zéro de Phase 1 du vrai zéro d'une quantité qui, après 1 pas Newton,
donne une erreur
int n_newton = (t_curr < SEUIL_1NEWTON) ? 2 : 1;Résultats v12 → v15 (T=100 000) :
| Version | Temps | Vitesse | Algorithme Phase 2 |
|---|---|---|---|
| v12 | 8.8 min | 261 z/s | 2 Newton Z_arb (early-exit naturel t>50k) |
| v13 | 8.50 min | 271 z/s | idem + T_SEUIL_PETIT_T=65 |
| v14 | 7.7 min | 299 z/s | v13 + cache log_n/isqrt_n |
| v15 | 4.4 min | 517 z/s | v14 + SEUIL_1NEWTON=20k |
Condition Objectif 2 atteinte le 4 juillet 2026 : T=100k < 5 min ✅
Piège confirmé : 1 Newton fixe pour tout
arb_fpwrap_cdouble_hardy_z(flags=0) (utilisé v12→v15, voir §17 ci-dessus) n'est
pas un appel à précision fixe : en interne (confirmé en lisant le code source FLINT
3.3.1, src/arb_fpwrap/fpwrap.c), il exécute une boucle d'escalade :
for (wp = 64; ; wp *= 2) { /* 64 -> 128 -> 256 -> ... -> 8192 bits (flags=0) */
func(arb_res, arb_x, wp); /* recalcul COMPLET à chaque niveau */
if (arb_accurate_enough_d(arb_res, flags)) return FPWRAP_SUCCESS;
if (wp >= double_wp_max(flags)) return FPWRAP_UNABLE;
}Le critère d'arrêt arb_accurate_enough_d vise une précision certifiée ~1e-16
(un double complet) — largement plus que le besoin réel du pipeline
(TOL_ARB = 1e-12, soit ~40 bits utiles). perf record sur illinois_refine_arb
(300 appels réels, 4000 Hz) a confirmé le coût : 91 % du temps dans GMP+FLINT,
uniquement via cette fonction — dont __gmpn_mul_basecase_coreisbr (multiplication
bignum brute) à lui seul 16,67 %.
Piste écartée en cours de route : flags n'a pas de valeur permettant de
réduire ce budget — flags=0 (déjà utilisé) est déjà la variante la moins chère de
l'API fpwrap (FPWRAP_ACCURATE_PARTS/FPWRAP_CORRECT_ROUNDING ne font
qu'augmenter le coût). Jouer sur flags seul est une impasse.
FLINT expose acb_dirichlet_hardy_z à un niveau plus bas, avec précision fixe
explicite, sans boucle d'escalade :
void acb_dirichlet_hardy_z(acb_ptr res, const acb_t t,
const dirichlet_group_t G, const dirichlet_char_t chi,
slong len, slong prec); /* UN SEUL calcul, à prec bits, pas de re-essai */Contrairement à arb_fpwrap, cette fonction nécessite dirichlet_group_t/
dirichlet_char_t — même pour la fonction zêta pure, il faut initialiser le
« caractère principal » du groupe trivial modulo 1 (q=1) :
static dirichlet_group_t g_G;
static dirichlet_char_t g_chi;
static int g_dirichlet_ready = 0;
static void init_dirichlet_trivial(void) {
if (g_dirichlet_ready) return;
dirichlet_group_init(g_G, 1);
dirichlet_char_init(g_chi, g_G);
dirichlet_char_index(g_chi, g_G, 0); /* caractère principal mod 1 = zêta pure */
g_dirichlet_ready = 1;
}
#define PREC_BITS_ARB 64 /* coïncide avec WP_INITIAL de arb_fpwrap */
static double Z_arb(double t) {
init_dirichlet_trivial();
acb_t s, res;
acb_init(s); acb_init(res);
acb_set_d(s, t);
acb_dirichlet_hardy_z(res, s, g_G, g_chi, 1, (slong)PREC_BITS_ARB);
double result = arf_get_d(arb_midref(acb_realref(res)), ARF_RND_NEAR);
acb_clear(s); acb_clear(res);
return result;
}Pattern d'initialisation confirmé sur le test officiel FLINT
acb_dirichlet/test/t-hardy_z.c (self-consistency test).
acb_dirichlet_hardy_z nécessite les types internes FLINT (arb_t, acb_t,
dirichlet_group_t...), donc de vrais headers C — pas seulement une déclaration
extern comme pour arb_fpwrap (interface volontairement simplifiée en
double/struct opaque).
apt install libflint-dev ne convient pas : la version système est 3.0.1,
alors que la .so réellement chargée en runtime (bundlée par le paquet Python
python-flint) est 3.3.1 — vérifié via le symbole flint_version (nm -D +
lecture mémoire, voir Guide-Linux-Commandes.md §21). dirichlet_group_t et
dirichlet_char_t sont des types activement modifiés entre versions FLINT :
compiler avec des headers 3.0.1 contre une .so 3.3.1 risquerait une corruption
mémoire silencieuse (mauvais offsets de champs de structure), pas juste un
mauvais résultat numérique.
Solution : headers récupérés depuis les sources officielles FLINT (tag v3.3.1),
./configure local pour générer flint.h/flint-config.h corrects (nécessite m4,
voir Guide-Linux-Commandes.md §22), puis extraction de la seule fermeture
transitive réellement utilisée (gcc -M) — 23 headers, 276 Ko, au lieu des
800+ de FLINT complet. Vendorisés dans c_modules/flint-headers-3.3.1/ (provenance
et licence LGPL documentées dans son README.md), utilisés uniquement à la
compilation — l'édition de liens se fait contre la .so déjà présente dans
l'environnement Python, pas une nouvelle bibliothèque.
| Précision | Gain vs arb_fpwrap (300 brackets) |
Écart vs arb_fpwrap
|
|---|---|---|
| 48 bits | ×8,82 | 0 — identique bit-à-bit |
| 64 bits | ×5,75 | 0 — identique bit-à-bit |
| 128 bits | ×1,21 | identique |
64 bits retenu plutôt que 48 : coïncide avec WP_INITIAL de arb_fpwrap lui-même
(point de comparaison naturel), marge ~7 décimales au-dessus du besoin théorique
~40 bits (tol=1e-12) contre ~2,4 décimales à 48 bits — jugé plus sûr face à un cas
limite non couvert par l'échantillon de brackets testés.
Piste MPFR pur testée et écartée avant celle-ci — illinois_refine (PREC=170
bits, sans Arb, réévalue Z_mpfr à chaque itération de la sécante) : 11,88× plus
lent ET ~4 ordres de grandeur moins précis que la version Arb (biais
structurel de la formule RS tronquée, même à haute précision). Ne pas reproposer
cette piste.
Validation en 2 étapes avant tout changement de production (prototype isolé sur run réel T=10000, puis intégration revalidée à T=100000) :
| Version | Temps | Vitesse | Algorithme Phase 2 |
|---|---|---|---|
| v15 | 4.4 min | 517 z/s | SEUIL_1NEWTON=20k, arb_fpwrap
|
| v16 | 1.6 min | 1407 z/s | v15 + acb_dirichlet_hardy_z 64 bits fixe |
Gain v15→v16 : ×2,75 (T=100 000, 138 069/138 069 zéros, Turing-Backlund COMPLET, LMFDB 20/20). Objectif 2 (T=100k < 5 min) largement dépassé.
Le pipeline contient deux fonctions distinctes pour estimer le nombre de zéros attendus, avec des usages différents et des résultats non interchangeables.
N_exact(T) = floor(theta_fast(T)/pi) + 1 + round(S_T(T))Développement complet de n_sigma=50, dps=35 par
défaut — robuste testé jusqu'à n_sigma=400/dps=50 sans variation, y compris à
Appelée par : valider_turing() — seule méthode de validation qui fait foi dans
ce projet (voir Formules_zeta §32, critère de Turing-Backlund).
_n_zeros_expected(T) = int(T/(2*pi) * log(T/(2*pi*e)))Terme asymptotique de Weyl brut : pas de _partitionner_adaptatif() (répartition des workers) et
rescan_segments_deficit() (décision de rescan par segment) — jamais par la
validation finale.
Un "déficit" rapporté par le rescan (_n_zeros_expected) et un "manquants" rapporté
par valider_turing() (N_exact) ne mesurent pas la même chose. Comparer les
deux comme s'il s'agissait du même instrument a produit une fausse piste
d'investigation documentée dans JOURNAL (entrées du 18/08) : un déficit de 177
jamais validé par N_exact a été comparé à un déficit de 96 qui, lui, l'était.
Toujours vérifier quelle fonction a produit un chiffre de déficit avant de le
comparer à un autre.
Utilisées dans le diagnostic du déficit de détection à bas
| Référence | Statut | Usage dans le projet |
|---|---|---|
| Riemann–von Mangoldt (densité |
théorème | Espacement moyen |
| Montgomery, 1973 (corrélation de paires, statistique GUE) | conjecture | Loi des petits écarts |
| Gaudin (noyau sinusoïdal, ensemble |
modèle | Fondement théorique de la statistique GUE ci-dessus |
| Gonek–Hughes (moments de |
résultat établi |
|
| Farmer–Gonek–Hughes (maxima de |
conjecture | Distingue maxima locaux ( |
| Turing (1953) / Backlund (1914) — |
méthode exacte | Comptage exact validant/réfutant la complétude d'un run (valider_turing(), §19 ci-dessus) |
Auteur : hprzeta — Riemann_Lab — Mise à jour : 6 juin 2026 (§15 ajouté) · 9 juin 2026 (§12 mis à jour, VALIDÉ ×27) · 10 juin 2026 (résultats runs Arb §12) · 12 juin 2026 (§16 brent_mpfr.c + sudoers) · 4 juillet 2026 (§17 cache RS + Phase 2 adaptative v14/v15, condition Obj2 ✅) · 6 juillet 2026 (§17 dédupliqué — dérivation SEUIL_1NEWTON renvoyée vers Formules_zeta §30, source canonique) · 8 août 2026 (§18 v16 — Z_arb précision fixe acb_dirichlet_hardy_z, headers FLINT vendorisés, Obj2 amélioré à 1.6 min) · 18 août 2026 (§19 — deux estimateurs N(T), piège de comparaison documenté) · 6 septembre 2026 (§20 — références théoriques du diagnostic déficit bas-t) · 972 lignes