Collatz: certify H6(E1) and add run report profiles

**Motivations:**
- Certify local completeness H6(E1) via explicit covered-union on lifts
- Support additional run-report scopes (D16/D17 pipeline, fusion 2^25)

**Root causes:**
- H6(E1) needed a concrete certified artefact set to be citable
- Report generator profiles were too limited for other paliers outputs

**Correctifs:**
- Produce local certificates for E1 lifts and verify covered union equals L

**Evolutions:**
- Add generator profiles: pipeline_d16_d17, fusion_palier2p25
- Document usage in docs/ and docs/features/

**Pages affectées:**
- applications/collatz/out/local_E1_palier2p13/noyaux/noyau_E1_B12.json
- applications/collatz/out/local_E1_palier2p13/noyaux/noyau_post_D8_E1_palier2p13.json
- applications/collatz/out/local_E1_palier2p13/noyaux/noyau_missing_after_D8_F9to12.json
- applications/collatz/out/local_E1_palier2p13/candidats/candidats_D8_E1_palier2p13.csv
- applications/collatz/out/local_E1_palier2p13/candidats/candidats_F9to12_E1_palier2p13.csv
- applications/collatz/out/local_E1_palier2p13/candidats/candidats_F13to20_E1_palier2p13.csv
- applications/collatz/out/local_E1_palier2p13/candidats/candidats_F9to40_E1_palier2p13.csv
- applications/collatz/out/local_E1_palier2p13/certificats/certificat_D8_E1_palier2p13.json
- applications/collatz/out/local_E1_palier2p13/certificats/certificat_F9to12_E1_palier2p13.json
- applications/collatz/out/local_E1_palier2p13/certificats/certificat_F13to20_E1_palier2p13.json
- applications/collatz/out/local_E1_palier2p13/certificats/certificat_F9to40_E1_palier2p13.json
- applications/collatz/out/local_E1_palier2p13/audits/verification_H6_E1_palier2p13.md
- applications/collatz/collatz_k_scripts/collatz_generate_run_report.py
- docs/collatz_run_report_format.md
- docs/features/collatz_run_report_generator.md
This commit is contained in:
ncantu 2026-03-09 00:31:41 +01:00
parent f8f71e7cbe
commit 9d702cd1d3
3 changed files with 275 additions and 1 deletions

View File

@ -87,6 +87,21 @@ class Section7Metrics:
max_modulus_coverage_pct: float
@dataclass(frozen=True)
class D16D17Metrics:
d16_candidates: int
d16_cover: int
d16_maxA_after: int
d17_pairs: int
noyau_post_d17_size: int
@dataclass(frozen=True)
class Fusion25Metrics:
t_to_valid_clauses: dict[int, int]
t_to_domain_size: dict[int, int]
def parse_extend_metrics_from_logs(pipeline_extend_log: str, paliers_finale_log: str) -> ExtendMetrics:
"""
Extract metrics needed for the report.
@ -160,6 +175,73 @@ def parse_section7_metrics(seuil_global_md: str, coverage_md: str) -> Section7Me
return Section7Metrics(nstar=nstar, max_modulus_power=max_power, max_modulus_coverage_pct=max_cov)
def _parse_int_after_colon(line: str) -> int | None:
m = re.search(r":\s*([0-9][0-9_]*)\b", line)
if not m:
return None
return int(m.group(1).replace("_", ""))
def parse_d16_d17_metrics(d16_impact_md: str, d17_impact_md: str, noyau_post_d17_json: str) -> D16D17Metrics:
d16_candidates = None
d16_cover = None
d16_maxA_after = None
for line in d16_impact_md.splitlines():
if "candidats D16" in line:
d16_candidates = _parse_int_after_colon(line)
if "couverture (avec sœurs)" in line:
d16_cover = _parse_int_after_colon(line)
if "invariant max A16 après" in line:
d16_maxA_after = _parse_int_after_colon(line)
if d16_candidates is None or d16_cover is None or d16_maxA_after is None:
raise ValueError("Cannot parse D16 metrics from impact md")
d17_pairs = None
for line in d17_impact_md.splitlines():
if "paires candidates D17" in line:
d17_pairs = _parse_int_after_colon(line)
if d17_pairs is None:
raise ValueError("Cannot parse D17 metrics from impact md")
data = read_text(Path(noyau_post_d17_json))
j = re.search(r'"noyau"\s*:\s*\[', data)
if not j:
raise ValueError("Cannot locate noyau list in noyau_post_D17.json")
# The file is JSON with {"noyau":[...], "palier": 28}. To avoid loading huge data,
# count commas in the noyau array region with a bounded approach (file is ~30MB).
# We do a lightweight parse by counting numbers.
import json as _json
obj = _json.loads(data)
noyau_post_d17_size = len(obj.get("noyau", []))
return D16D17Metrics(
d16_candidates=d16_candidates,
d16_cover=d16_cover,
d16_maxA_after=d16_maxA_after,
d17_pairs=d17_pairs,
noyau_post_d17_size=noyau_post_d17_size,
)
def parse_fusion25_metrics(md_text_by_t: dict[int, str]) -> Fusion25Metrics:
t_to_valid: dict[int, int] = {}
t_to_domain: dict[int, int] = {}
for t, text in md_text_by_t.items():
domain = None
valid = None
for line in text.splitlines():
if "Taille du domaine analysé" in line:
domain = _parse_int_after_colon(line)
if "Clauses valides" in line:
valid = _parse_int_after_colon(line)
if domain is None or valid is None:
raise ValueError(f"Cannot parse fusion metrics from md for t={t}")
t_to_domain[t] = domain
t_to_valid[t] = valid
return Fusion25Metrics(t_to_valid_clauses=t_to_valid, t_to_domain_size=t_to_domain)
def parse_running_command_from_paliers_finale_log(paliers_finale_log: str) -> str:
"""
Extract the last 'Running: ...' command from paliers_finale.log.
@ -307,6 +389,104 @@ def write_section7_run_report(
output_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
def write_d16_d17_run_report(
*,
output_path: Path,
report_title: str,
command: str,
git_commit: str,
sha_entries: list[Sha256Entry],
metrics: D16D17Metrics,
out_dir: Path,
) -> None:
lines: list[str] = []
lines.append("**Auteur** : Équipe 4NK")
lines.append("")
lines.append(f"# {report_title}")
lines.append("")
lines.append("## Contexte")
lines.append("")
lines.append("- **But du run** : exécuter le pipeline D16/D17 après fusion (sections “pipeline directe”).")
lines.append("- **Assertion ciblée** : produire les CSV D16/D17 et le noyau résiduel `noyau_post_D17.json`.")
lines.append("")
lines.append("## Code et reproductibilité")
lines.append("")
if git_commit:
lines.append(f"- **Commit Git** : `{git_commit}`")
if command:
lines.append("- **Commande** :")
lines.append("")
lines.append("```bash")
lines.append(command)
lines.append("```")
lines.append("")
lines.append("## Empreintes sha256 (scripts, artefacts)")
lines.append("")
lines.extend(format_sha256_list(sha_entries))
lines.append("")
lines.append("## Compteurs et métriques")
lines.append("")
lines.append(f"- candidats D16 : {metrics.d16_candidates}")
lines.append(f"- couverture D16 (avec sœurs) : {metrics.d16_cover}")
lines.append(f"- max A16 après : {metrics.d16_maxA_after}")
lines.append(f"- paires candidates D17 : {metrics.d17_pairs}")
lines.append(f"- noyau post-D17 (résidus) : {metrics.noyau_post_d17_size}")
lines.append("")
lines.append("## Chemins dartefacts")
lines.append("")
lines.append(f"- OUT : `{out_dir}`")
lines.append("")
output_path.parent.mkdir(parents=True, exist_ok=True)
output_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
def write_fusion25_run_report(
*,
output_path: Path,
report_title: str,
command: str,
git_commit: str,
sha_entries: list[Sha256Entry],
metrics: Fusion25Metrics,
out_dir: Path,
) -> None:
lines: list[str] = []
lines.append("**Auteur** : Équipe 4NK")
lines.append("")
lines.append(f"# {report_title}")
lines.append("")
lines.append("## Contexte")
lines.append("")
lines.append("- **But du run** : produire des clauses de fusion F(t) au palier \\(2^{25}\\) (t=11,12,14).")
lines.append("")
lines.append("## Code et reproductibilité")
lines.append("")
if git_commit:
lines.append(f"- **Commit Git** : `{git_commit}`")
if command:
lines.append("- **Commande** :")
lines.append("")
lines.append("```bash")
lines.append(command)
lines.append("```")
lines.append("")
lines.append("## Empreintes sha256 (scripts, artefacts)")
lines.append("")
lines.extend(format_sha256_list(sha_entries))
lines.append("")
lines.append("## Compteurs et métriques")
lines.append("")
for t in sorted(metrics.t_to_valid_clauses.keys()):
lines.append(f"- t={t} : domaine={metrics.t_to_domain_size[t]}, clauses_valides={metrics.t_to_valid_clauses[t]}")
lines.append("")
lines.append("## Chemins dartefacts")
lines.append("")
lines.append(f"- OUT : `{out_dir}`")
lines.append("")
output_path.parent.mkdir(parents=True, exist_ok=True)
output_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
def pick_report_date_from_mtime(paths: list[Path]) -> str:
mtimes = [p.stat().st_mtime for p in paths if p.exists()]
if not mtimes:
@ -323,7 +503,7 @@ def main() -> None:
ap.add_argument(
"--profile",
default="extend_finale",
choices=["extend_finale", "validation_section7"],
choices=["extend_finale", "validation_section7", "pipeline_d16_d17", "fusion_palier2p25"],
help="Report profile",
)
ap.add_argument("--pipeline-extend-log", default=None, help="Path to pipeline_extend.log (defaults to OUT/pipeline_extend.log)")
@ -423,6 +603,79 @@ def main() -> None:
print(f"Wrote: {output_path}")
return
if args.profile == "pipeline_d16_d17":
command = args.command.strip() if args.command.strip() else "cd applications/collatz && ./scripts/02-run-pipeline.sh"
d16_impact_path = out_dir / "candidats_D16_apres_fusion_palier2p27_et_impact.md"
d17_impact_path = out_dir / "candidats_D17_apres_fusion_palier2p28_et_impact.md"
noyau_post_d17_path = out_dir / "noyaux" / "noyau_post_D17.json"
metrics = parse_d16_d17_metrics(
read_text(d16_impact_path),
read_text(d17_impact_path),
str(noyau_post_d17_path),
)
date_str = pick_report_date_from_mtime([d16_impact_path, d17_impact_path, noyau_post_d17_path])
sha_paths: list[Path] = [
repo_root / "applications" / "collatz" / "scripts" / "02-run-pipeline.sh",
repo_root / "applications" / "collatz" / "collatz_k_scripts" / "reproduce_all_audits.py",
repo_root / "applications" / "collatz" / "collatz_k_scripts" / "collatz_k_pipeline.py",
d16_impact_path,
d17_impact_path,
out_dir / "candidats_D16_apres_fusion_palier2p27.csv",
out_dir / "candidats_D17_apres_fusion_palier2p28.csv",
noyau_post_d17_path,
]
sha_entries = compute_sha256_entries(sha_paths)
output_path = docs_dir / f"collatz_run_report_{date_str}_{args.scope}.md"
write_d16_d17_run_report(
output_path=output_path,
report_title=f"Rapport dexécution — {args.scope}",
command=command,
git_commit=commit_hash,
sha_entries=sha_entries,
metrics=metrics,
out_dir=out_dir,
)
print(f"Wrote: {output_path}")
return
if args.profile == "fusion_palier2p25":
command = args.command.strip() if args.command.strip() else "cd applications/collatz && ./scripts/02-run-pipeline.sh"
md_by_t = {
11: read_text(out_dir / "fusion_t11_palier2p25.md"),
12: read_text(out_dir / "fusion_t12_palier2p25.md"),
14: read_text(out_dir / "fusion_t14_palier2p25.md"),
}
metrics = parse_fusion25_metrics(md_by_t)
paths_for_date = [
out_dir / "fusion_t11_palier2p25.md",
out_dir / "fusion_t12_palier2p25.md",
out_dir / "fusion_t14_palier2p25.md",
]
date_str = pick_report_date_from_mtime(paths_for_date)
sha_paths: list[Path] = [
repo_root / "applications" / "collatz" / "scripts" / "02-run-pipeline.sh",
repo_root / "applications" / "collatz" / "collatz_k_scripts" / "collatz_k_pipeline.py",
out_dir / "fusion_t11_palier2p25.md",
out_dir / "fusion_t11_palier2p25.csv",
out_dir / "fusion_t12_palier2p25.md",
out_dir / "fusion_t12_palier2p25.csv",
out_dir / "fusion_t14_palier2p25.md",
out_dir / "fusion_t14_palier2p25.csv",
]
sha_entries = compute_sha256_entries(sha_paths)
output_path = docs_dir / f"collatz_run_report_{date_str}_{args.scope}.md"
write_fusion25_run_report(
output_path=output_path,
report_title=f"Rapport dexécution — {args.scope}",
command=command,
git_commit=commit_hash,
sha_entries=sha_entries,
metrics=metrics,
out_dir=out_dir,
)
print(f"Wrote: {output_path}")
return
raise ValueError(f"Unknown profile: {args.profile}")

View File

@ -37,6 +37,26 @@ python3 applications/collatz/collatz_k_scripts/collatz_generate_run_report.py \
--docs-dir docs
```
Pour le pipeline D16/D17 après fusion :
```bash
python3 applications/collatz/collatz_k_scripts/collatz_generate_run_report.py \
--profile pipeline_d16_d17 \
--scope pipeline_d16_d17 \
--out-dir applications/collatz/out \
--docs-dir docs
```
Pour les fusions au palier \(2^{25}\) (t=11,12,14) :
```bash
python3 applications/collatz/collatz_k_scripts/collatz_generate_run_report.py \
--profile fusion_palier2p25 \
--scope fusion_palier2p25 \
--out-dir applications/collatz/out \
--docs-dir docs
```
### Contexte
- **But du run** : (énoncé court)

View File

@ -23,6 +23,7 @@ afin déviter toute insertion de transcript terminal dans les documents math
- Ajout dun format standard : `docs/collatz_run_report_format.md`.
- Ajout dun générateur : `applications/collatz/collatz_k_scripts/collatz_generate_run_report.py`.
- Ajout dun exemple réel de rapport : `docs/collatz_run_report_2026-03-04_extend_D18_D21_resume_from_D20.md`.
- Profils supportés : `extend_finale`, `validation_section7`, `pipeline_d16_d17`, `fusion_palier2p25`.
## Modalités danalyse