Generalize local H6 run reports with a parameterized profile
**Motivations:** - Avoid one-profile-per-state duplication for local H6 completeness checks - Reuse a single strict verifier for any versioned local H6 artefacts set **Root causes:** - The initial local_H6_E1 profile was state-specific and not reusable for other E **Correctifs:** - Factor local H6 verification into a generic union-vs-lift equality check **Evolutions:** - Add `local_H6` profile with `--local-h6-artefacts-dir` - Keep `local_H6_E1` as a stable alias to the E1 artefacts directory - Document the generalized usage and update the E1 report output **Pages affectées:** - applications/collatz/collatz_k_scripts/collatz_generate_run_report.py - docs/collatz_run_report_format.md - docs/features/collatz_run_report_generator.md - docs/collatz_run_report_2026-03-09_local_H6_E1.md
This commit is contained in:
parent
70ee05cf37
commit
a4e8a325a2
@ -109,51 +109,74 @@ class Fusion25Metrics:
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class LocalH6E1Metrics:
|
||||
class LocalH6Metrics:
|
||||
base_palier: int
|
||||
base_domain_size: int
|
||||
target_palier: int
|
||||
lifted_domain_size: int
|
||||
d8_covered: int
|
||||
f9to40_covered: int
|
||||
union_covered: int
|
||||
certificate_coverages: list[tuple[str, int]]
|
||||
|
||||
|
||||
def compute_local_h6_e1_metrics(
|
||||
def _discover_base_noyau_path(artefacts_dir: Path) -> Path:
|
||||
candidates = sorted((artefacts_dir / "noyaux").glob("noyau_*_B12.json"))
|
||||
if len(candidates) != 1:
|
||||
raise ValueError(
|
||||
"Cannot auto-detect base noyau file: expected exactly one match "
|
||||
f"for {artefacts_dir / 'noyaux' / 'noyau_*_B12.json'}"
|
||||
)
|
||||
return candidates[0]
|
||||
|
||||
|
||||
def _discover_certificate_paths(artefacts_dir: Path) -> list[Path]:
|
||||
paths = sorted((artefacts_dir / "certificats").glob("certificat_*.json"))
|
||||
if not paths:
|
||||
raise ValueError(f"No certificate JSON found under {artefacts_dir / 'certificats'}")
|
||||
return paths
|
||||
|
||||
|
||||
def compute_local_h6_metrics(
|
||||
*,
|
||||
noyau_e1_b12_path: Path,
|
||||
certificat_d8_path: Path,
|
||||
certificat_f9to40_path: Path,
|
||||
) -> LocalH6E1Metrics:
|
||||
noyau_obj = read_json(noyau_e1_b12_path)
|
||||
noyau_base_path: Path,
|
||||
certificate_paths: list[Path],
|
||||
) -> LocalH6Metrics:
|
||||
noyau_obj = read_json(noyau_base_path)
|
||||
if not isinstance(noyau_obj, dict):
|
||||
raise ValueError("Invalid JSON: noyau_E1_B12.json must be an object")
|
||||
raise ValueError("Invalid JSON: base noyau must be an object")
|
||||
noyau = noyau_obj.get("noyau")
|
||||
base_palier = noyau_obj.get("palier")
|
||||
if not isinstance(noyau, list) or not all(isinstance(x, int) for x in noyau):
|
||||
raise ValueError("Invalid JSON: noyau_E1_B12.json must contain an integer list field 'noyau'")
|
||||
raise ValueError("Invalid JSON: base noyau must contain an integer list field 'noyau'")
|
||||
if not isinstance(base_palier, int):
|
||||
raise ValueError("Invalid JSON: noyau_E1_B12.json must contain an integer field 'palier'")
|
||||
raise ValueError("Invalid JSON: base noyau must contain an integer field 'palier'")
|
||||
|
||||
d8_obj = read_json(certificat_d8_path)
|
||||
f_obj = read_json(certificat_f9to40_path)
|
||||
if not isinstance(d8_obj, dict) or not isinstance(f_obj, dict):
|
||||
raise ValueError("Invalid JSON: certificates must be objects")
|
||||
if not certificate_paths:
|
||||
raise ValueError("compute_local_h6_metrics requires at least one certificate path")
|
||||
|
||||
d8_cov = d8_obj.get("covered")
|
||||
f_cov = f_obj.get("covered")
|
||||
if not isinstance(d8_cov, list) or not all(isinstance(x, int) for x in d8_cov):
|
||||
raise ValueError("Invalid JSON: certificat_D8 must contain an integer list field 'covered'")
|
||||
if not isinstance(f_cov, list) or not all(isinstance(x, int) for x in f_cov):
|
||||
raise ValueError("Invalid JSON: certificat_F9to40 must contain an integer list field 'covered'")
|
||||
palier: int | None = None
|
||||
covered_union: set[int] = set()
|
||||
certificate_coverages: list[tuple[str, int]] = []
|
||||
for cert_path in certificate_paths:
|
||||
cert_obj = read_json(cert_path)
|
||||
if not isinstance(cert_obj, dict):
|
||||
raise ValueError(f"Invalid JSON: certificate must be an object: {cert_path}")
|
||||
cov = cert_obj.get("covered")
|
||||
if not isinstance(cov, list) or not all(isinstance(x, int) for x in cov):
|
||||
raise ValueError(f"Invalid JSON: certificate must contain an integer list field 'covered': {cert_path}")
|
||||
cert_palier = cert_obj.get("palier")
|
||||
if not isinstance(cert_palier, int):
|
||||
raise ValueError(f"Invalid JSON: certificate must contain an integer field 'palier': {cert_path}")
|
||||
if palier is None:
|
||||
palier = cert_palier
|
||||
elif cert_palier != palier:
|
||||
raise ValueError(f"Certificate palier mismatch: {cert_path} has palier={cert_palier}, expected {palier}")
|
||||
s = set(cov)
|
||||
covered_union |= s
|
||||
certificate_coverages.append((str(cert_path), len(s)))
|
||||
|
||||
d8_palier = d8_obj.get("palier")
|
||||
f_palier = f_obj.get("palier")
|
||||
if not isinstance(d8_palier, int) or not isinstance(f_palier, int):
|
||||
raise ValueError("Invalid JSON: certificates must contain an integer field 'palier'")
|
||||
if d8_palier != f_palier:
|
||||
raise ValueError(f"Certificate palier mismatch: D8={d8_palier}, F9to40={f_palier}")
|
||||
target_palier = d8_palier
|
||||
if palier is None:
|
||||
raise ValueError("Cannot infer target palier from certificates")
|
||||
target_palier = palier
|
||||
|
||||
if target_palier < base_palier:
|
||||
raise ValueError(f"Target palier must be >= base palier (target={target_palier}, base={base_palier})")
|
||||
@ -166,25 +189,20 @@ def compute_local_h6_e1_metrics(
|
||||
for j in range(lift_factor):
|
||||
lifted_domain.add(r + j * lift_step)
|
||||
|
||||
d8_set = set(d8_cov)
|
||||
f_set = set(f_cov)
|
||||
union = d8_set | f_set
|
||||
|
||||
missing = lifted_domain - union
|
||||
extra = union - lifted_domain
|
||||
missing = lifted_domain - covered_union
|
||||
extra = covered_union - lifted_domain
|
||||
if missing:
|
||||
raise ValueError(f"Local H6(E1) check failed: missing {len(missing)} lifted residues")
|
||||
raise ValueError(f"Local H6 check failed: missing {len(missing)} lifted residues")
|
||||
if extra:
|
||||
raise ValueError(f"Local H6(E1) check failed: extra {len(extra)} residues outside lifted domain")
|
||||
raise ValueError(f"Local H6 check failed: extra {len(extra)} residues outside lifted domain")
|
||||
|
||||
return LocalH6E1Metrics(
|
||||
return LocalH6Metrics(
|
||||
base_palier=base_palier,
|
||||
base_domain_size=len(noyau),
|
||||
target_palier=target_palier,
|
||||
lifted_domain_size=len(lifted_domain),
|
||||
d8_covered=len(d8_set),
|
||||
f9to40_covered=len(f_set),
|
||||
union_covered=len(union),
|
||||
union_covered=len(covered_union),
|
||||
certificate_coverages=certificate_coverages,
|
||||
)
|
||||
|
||||
|
||||
@ -579,7 +597,7 @@ def write_local_h6_e1_run_report(
|
||||
report_title: str,
|
||||
command: str,
|
||||
sha_entries: list[Sha256Entry],
|
||||
metrics: LocalH6E1Metrics,
|
||||
metrics: LocalH6Metrics,
|
||||
artefacts_dir: Path,
|
||||
) -> None:
|
||||
lines: list[str] = []
|
||||
@ -612,9 +630,9 @@ def write_local_h6_e1_run_report(
|
||||
lines.append(f"- |B12(E1)| : {metrics.base_domain_size}")
|
||||
lines.append(f"- palier cible : {metrics.target_palier}")
|
||||
lines.append(f"- |Lift| : {metrics.lifted_domain_size}")
|
||||
lines.append(f"- |covered(D8)| : {metrics.d8_covered}")
|
||||
lines.append(f"- |covered(F9–F40)| : {metrics.f9to40_covered}")
|
||||
lines.append(f"- |covered(D8 ∪ F9–F40)| : {metrics.union_covered}")
|
||||
for path_str, c in metrics.certificate_coverages:
|
||||
lines.append(f"- |covered({Path(path_str).name})| : {c}")
|
||||
lines.append(f"- |covered(∪ certificats)| : {metrics.union_covered}")
|
||||
lines.append("")
|
||||
lines.append("## Chemins d’artefacts (versionnés)")
|
||||
lines.append("")
|
||||
@ -640,7 +658,7 @@ def main() -> None:
|
||||
ap.add_argument(
|
||||
"--profile",
|
||||
default="extend_finale",
|
||||
choices=["extend_finale", "validation_section7", "pipeline_d16_d17", "fusion_palier2p25", "local_H6_E1"],
|
||||
choices=["extend_finale", "validation_section7", "pipeline_d16_d17", "fusion_palier2p25", "local_H6", "local_H6_E1"],
|
||||
help="Report profile",
|
||||
)
|
||||
ap.add_argument("--pipeline-extend-log", default=None, help="Path to pipeline_extend.log (defaults to OUT/pipeline_extend.log)")
|
||||
@ -651,6 +669,11 @@ def main() -> None:
|
||||
default="",
|
||||
help="Command line to embed in report (profile-dependent default if empty)",
|
||||
)
|
||||
ap.add_argument(
|
||||
"--local-h6-artefacts-dir",
|
||||
default="",
|
||||
help="For profile local_H6: path to the versioned artefacts directory (e.g. docs/artefacts/collatz/local_E1_palier2p13)",
|
||||
)
|
||||
args = ap.parse_args()
|
||||
|
||||
repo_root = Path(args.repo_root).resolve() if args.repo_root else Path.cwd().resolve()
|
||||
@ -813,46 +836,44 @@ def main() -> None:
|
||||
print(f"Wrote: {output_path}")
|
||||
return
|
||||
|
||||
if args.profile == "local_H6_E1":
|
||||
command = (
|
||||
args.command.strip()
|
||||
if args.command.strip()
|
||||
else "python3 applications/collatz/collatz_k_scripts/collatz_generate_run_report.py --profile local_H6_E1 --scope local_H6_E1 --out-dir applications/collatz/out --docs-dir docs"
|
||||
)
|
||||
artefacts_dir = docs_dir / "artefacts" / "collatz" / "local_E1_palier2p13"
|
||||
noyau_e1_b12_path = artefacts_dir / "noyaux" / "noyau_E1_B12.json"
|
||||
noyau_post_d8_path = artefacts_dir / "noyaux" / "noyau_post_D8_E1_palier2p13.json"
|
||||
candidats_d8_path = artefacts_dir / "candidats" / "candidats_D8_E1_palier2p13.csv"
|
||||
candidats_f9to40_path = artefacts_dir / "candidats" / "candidats_F9to40_E1_palier2p13.csv"
|
||||
certificat_d8_path = artefacts_dir / "certificats" / "certificat_D8_E1_palier2p13.json"
|
||||
certificat_f9to40_path = artefacts_dir / "certificats" / "certificat_F9to40_E1_palier2p13.json"
|
||||
audit_md_path = artefacts_dir / "audits" / "verification_H6_E1_palier2p13.md"
|
||||
if args.profile in ("local_H6", "local_H6_E1"):
|
||||
command = args.command.strip()
|
||||
if not command:
|
||||
if args.profile == "local_H6_E1":
|
||||
command = (
|
||||
"python3 applications/collatz/collatz_k_scripts/collatz_generate_run_report.py "
|
||||
"--profile local_H6_E1 --scope local_H6_E1 --out-dir applications/collatz/out --docs-dir docs"
|
||||
)
|
||||
else:
|
||||
command = (
|
||||
"python3 applications/collatz/collatz_k_scripts/collatz_generate_run_report.py "
|
||||
"--profile local_H6 --scope local_H6 --out-dir applications/collatz/out --docs-dir docs "
|
||||
"--local-h6-artefacts-dir <PATH>"
|
||||
)
|
||||
|
||||
metrics = compute_local_h6_e1_metrics(
|
||||
noyau_e1_b12_path=noyau_e1_b12_path,
|
||||
certificat_d8_path=certificat_d8_path,
|
||||
certificat_f9to40_path=certificat_f9to40_path,
|
||||
)
|
||||
date_str = pick_report_date_from_mtime(
|
||||
[
|
||||
noyau_e1_b12_path,
|
||||
noyau_post_d8_path,
|
||||
candidats_d8_path,
|
||||
candidats_f9to40_path,
|
||||
certificat_d8_path,
|
||||
certificat_f9to40_path,
|
||||
audit_md_path,
|
||||
]
|
||||
)
|
||||
if args.profile == "local_H6_E1":
|
||||
artefacts_dir = docs_dir / "artefacts" / "collatz" / "local_E1_palier2p13"
|
||||
else:
|
||||
if not args.local_h6_artefacts_dir.strip():
|
||||
raise ValueError("--local-h6-artefacts-dir is required for profile local_H6")
|
||||
artefacts_dir = Path(args.local_h6_artefacts_dir).resolve()
|
||||
|
||||
noyau_base_path = _discover_base_noyau_path(artefacts_dir)
|
||||
certificate_paths = _discover_certificate_paths(artefacts_dir)
|
||||
metrics = compute_local_h6_metrics(noyau_base_path=noyau_base_path, certificate_paths=certificate_paths)
|
||||
|
||||
all_files: list[Path] = []
|
||||
for sub in ("noyaux", "candidats", "certificats", "audits"):
|
||||
p = artefacts_dir / sub
|
||||
if p.exists():
|
||||
all_files.extend(sorted([f for f in p.glob("*") if f.is_file()]))
|
||||
if not all_files:
|
||||
raise ValueError(f"No artefact files found under {artefacts_dir}")
|
||||
|
||||
date_str = pick_report_date_from_mtime(all_files)
|
||||
sha_paths: list[Path] = [
|
||||
repo_root / "applications" / "collatz" / "collatz_k_scripts" / "collatz_generate_run_report.py",
|
||||
noyau_e1_b12_path,
|
||||
noyau_post_d8_path,
|
||||
candidats_d8_path,
|
||||
candidats_f9to40_path,
|
||||
certificat_d8_path,
|
||||
certificat_f9to40_path,
|
||||
audit_md_path,
|
||||
*all_files,
|
||||
]
|
||||
sha_entries = compute_sha256_entries(sha_paths)
|
||||
output_path = docs_dir / f"collatz_run_report_{date_str}_{args.scope}.md"
|
||||
|
||||
@ -19,7 +19,7 @@ python3 applications/collatz/collatz_k_scripts/collatz_generate_run_report.py --
|
||||
## Empreintes sha256 (scripts, artefacts)
|
||||
|
||||
- `/home/ncantu/code/algo/applications/collatz/collatz_k_scripts/collatz_generate_run_report.py`
|
||||
- sha256: `23734fe5f6bd2e97c16546d89c4c7b94ab9a783da3b1f7d45d8bf115c9de7658`
|
||||
- sha256: `b51cab5d56ab63ec864b92f3693edde8aaaa80e356b3fa0f023f69417b918296`
|
||||
- `/home/ncantu/code/algo/docs/artefacts/collatz/local_E1_palier2p13/noyaux/noyau_E1_B12.json`
|
||||
- sha256: `7f6bb5e8893ae7456f54963491a3eb80d03a7cff3f87feb12702788e43661875`
|
||||
- `/home/ncantu/code/algo/docs/artefacts/collatz/local_E1_palier2p13/noyaux/noyau_post_D8_E1_palier2p13.json`
|
||||
@ -41,9 +41,9 @@ python3 applications/collatz/collatz_k_scripts/collatz_generate_run_report.py --
|
||||
- |B12(E1)| : 16
|
||||
- palier cible : 13
|
||||
- |Lift| : 32
|
||||
- |covered(D8)| : 2
|
||||
- |covered(F9–F40)| : 30
|
||||
- |covered(D8 ∪ F9–F40)| : 32
|
||||
- |covered(certificat_D8_E1_palier2p13.json)| : 2
|
||||
- |covered(certificat_F9to40_E1_palier2p13.json)| : 30
|
||||
- |covered(∪ certificats)| : 32
|
||||
|
||||
## Chemins d’artefacts (versionnés)
|
||||
|
||||
|
||||
@ -67,6 +67,17 @@ python3 applications/collatz/collatz_k_scripts/collatz_generate_run_report.py \
|
||||
--docs-dir docs
|
||||
```
|
||||
|
||||
Pour une complétude locale H6 généralisée (artefacts versionnés, répertoire paramétrable) :
|
||||
|
||||
```bash
|
||||
python3 applications/collatz/collatz_k_scripts/collatz_generate_run_report.py \
|
||||
--profile local_H6 \
|
||||
--scope local_H6_E42 \
|
||||
--local-h6-artefacts-dir docs/artefacts/collatz/local_E42_palier2p13 \
|
||||
--out-dir applications/collatz/out \
|
||||
--docs-dir docs
|
||||
```
|
||||
|
||||
### Contexte
|
||||
|
||||
- **But du run** : (énoncé court)
|
||||
|
||||
@ -23,13 +23,14 @@ afin d’éviter toute insertion de transcript terminal dans les documents math
|
||||
- Ajout d’un format standard : `docs/collatz_run_report_format.md`.
|
||||
- Ajout d’un générateur : `applications/collatz/collatz_k_scripts/collatz_generate_run_report.py`.
|
||||
- Ajout d’un 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`, `local_H6_E1`.
|
||||
- Profils supportés : `extend_finale`, `validation_section7`, `pipeline_d16_d17`, `fusion_palier2p25`, `local_H6_E1`, `local_H6`.
|
||||
|
||||
## Modalités d’analyse
|
||||
|
||||
- Vérifier que `applications/collatz/out/pipeline_extend.log` et `applications/collatz/out/paliers_finale.log` existent et contiennent un run complet.
|
||||
- Vérifier la présence des artefacts listés (noyaux/candidats/certificats) dans `applications/collatz/out/`.
|
||||
- Pour `local_H6_E1`, vérifier la présence des artefacts versionnés dans `docs/artefacts/collatz/local_E1_palier2p13/`.
|
||||
- Pour `local_H6`, fournir `--local-h6-artefacts-dir` et vérifier la présence d’un noyau base `noyaux/noyau_*_B12.json` et d’au moins un certificat `certificats/certificat_*.json`.
|
||||
|
||||
## Modalités de déploiement
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user