collatz: parameterize C3 palier and add iteration protocol tooling

**Motivations:**
- Enable deterministic iteration beyond palier 2^13 (C1→C2→C3).

**Root causes:**
- The C3 verifier and reporting were hard-coded to palier 13, blocking palier-by-palier iteration.

**Correctifs:**
- Parameterize `collatz_verify_c3_local_descent.py` with `--palier` and suffix outputs for m!=13.
- Add deterministic iteration protocol generator and versioned protocol artefacts.
- Extend run report generator `c3_local_descent` profile with `--c3-palier`.

**Evolutions:**
- Reference the protocol in proof documents.

**Pages affectées:**
- applications/collatz/collatz_k_scripts/collatz_verify_c3_local_descent.py
- applications/collatz/collatz_k_scripts/collatz_iterate_palier_protocol.py
- docs/artefacts/collatz/iteration_protocol/*
- applications/collatz/collatz_k_scripts/collatz_generate_run_report.py
- docs/collatz_run_report_format.md
- applications/collatz/démonstration collatz.md
This commit is contained in:
ncantu 2026-03-09 04:56:44 +01:00
parent ab56157c05
commit 67eb6a5e68
7 changed files with 408 additions and 23 deletions

View File

@ -1124,6 +1124,12 @@ def main() -> None:
default="",
help="For profile c3_local_descent: path to deterministic artefacts directory (e.g. docs/artefacts/collatz/c3_local_descent)",
)
ap.add_argument(
"--c3-palier",
type=int,
default=13,
help="For profile c3_local_descent: palier m (default 13). For m!=13, expects suffixed filenames.",
)
ap.add_argument(
"--universal-clauses-artefacts-dir",
default="",
@ -1388,7 +1394,8 @@ def main() -> None:
"python3 applications/collatz/collatz_k_scripts/collatz_generate_run_report.py "
"--profile c3_local_descent --scope c3_local_descent "
"--out-dir applications/collatz/out --docs-dir docs "
"--c3-artefacts-dir docs/artefacts/collatz/c3_local_descent"
"--c3-artefacts-dir docs/artefacts/collatz/c3_local_descent "
"--c3-palier 13"
)
artefacts_dir = (
@ -1396,8 +1403,13 @@ def main() -> None:
if args.c3_artefacts_dir.strip()
else (docs_dir / "artefacts" / "collatz" / "c3_local_descent")
)
if int(args.c3_palier) == 13:
verification_json = artefacts_dir / "verification_c3_local_descent.json"
verification_md = artefacts_dir / "verification_c3_local_descent.md"
else:
m = int(args.c3_palier)
verification_json = artefacts_dir / f"verification_c3_local_descent_palier2p{m}.json"
verification_md = artefacts_dir / f"verification_c3_local_descent_palier2p{m}.md"
metrics = parse_c3_local_descent_metrics(str(verification_json))
date_str = pick_report_date_from_mtime([verification_json, verification_md])

View File

@ -0,0 +1,223 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
collatz_iterate_palier_protocol.py
Deterministic "next palier" protocol generator for the hybrid chain C1C2C3.
It does NOT run expensive computations. It inspects existing versioned artefacts
and writes a checklist (JSON + MD) describing:
- current certified palier for C3 (from verification JSON)
- current max C2 completion transition (from verification JSON)
- availability of local H6 directories at a chosen target palier
- commands to generate / verify / report the next iteration (2^m 2^(m+1))
Outputs are intended to be versioned under `docs/artefacts/`.
"""
from __future__ import annotations
import argparse
import json
from dataclasses import dataclass
from pathlib import Path
def _read_json(path: Path) -> object:
return json.loads(path.read_text(encoding="utf-8", errors="strict"))
def _req_int(d: dict[str, object], key: str) -> int:
v = d.get(key)
if not isinstance(v, int):
raise ValueError(f"Expected int for {key}")
return v
def _write_json(path: Path, obj: object) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(obj, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
def _write_md(path: Path, lines: list[str]) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text("\n".join(lines) + "\n", encoding="utf-8")
def _discover_local_h6_state_dirs(root: Path, palier: int) -> list[Path]:
return sorted([p for p in root.glob(f"local_E*_palier2p{palier}") if p.is_dir()])
@dataclass(frozen=True)
class ProtocolState:
c3_palier: int
c2_max_child_palier: int
target_palier: int
local_h6_states_found: int
local_h6_missing_state_ids: list[int]
def _load_c3_palier(c3_verification_json: Path) -> int:
obj = _read_json(c3_verification_json)
if not isinstance(obj, dict):
raise ValueError("Invalid C3 verification JSON: expected object")
domain = obj.get("domain")
if not isinstance(domain, dict):
raise ValueError("Invalid C3 verification JSON: missing domain")
return _req_int(domain, "palier")
def _load_c2_max_child_palier(c2_verification_json: Path) -> int:
obj = _read_json(c2_verification_json)
if not isinstance(obj, dict):
raise ValueError("Invalid C2 verification JSON: expected object")
transitions = obj.get("transitions")
if not isinstance(transitions, list) or not all(isinstance(x, dict) for x in transitions):
raise ValueError("Invalid C2 verification JSON: missing transitions")
child_paliers = [_req_int(t, "palier_child") for t in transitions]
if not child_paliers:
raise ValueError("Invalid C2 verification JSON: empty transitions")
return max(child_paliers)
def _infer_state_id_from_dirname(dirname: str) -> int:
# expected: local_E{sid}_palier2p{m}
parts = dirname.split("_")
for p in parts:
if p.startswith("E") and p[1:].isdigit():
return int(p[1:])
raise ValueError(f"Cannot infer state id from {dirname}")
def run(
*,
local_h6_root: Path,
c2_verification_json: Path,
c3_verification_json: Path,
output_dir: Path,
target_palier: int | None,
) -> None:
c3_palier = _load_c3_palier(c3_verification_json)
c2_max_child = _load_c2_max_child_palier(c2_verification_json)
tgt = (c3_palier + 1) if target_palier is None else int(target_palier)
if tgt < 13:
raise ValueError("target_palier must be >= 13")
found_dirs = _discover_local_h6_state_dirs(local_h6_root, tgt)
found_ids = sorted({_infer_state_id_from_dirname(p.name) for p in found_dirs})
missing_ids = [sid for sid in range(1, 61) if sid not in found_ids]
state = ProtocolState(
c3_palier=c3_palier,
c2_max_child_palier=c2_max_child,
target_palier=tgt,
local_h6_states_found=len(found_ids),
local_h6_missing_state_ids=missing_ids,
)
commands = {
"C1_local_H6_generate": (
"python3 applications/collatz/collatz_k_scripts/collatz_generate_local_h6_artefacts.py "
f"--docs-dir docs --output-root docs/artefacts/collatz --state-ids all "
f"--palier-start {tgt} --palier-max {tgt} "
"--t-min 9 --t-max 120 --write-index --index-path docs/artefacts/collatz/local_H6_index.md"
),
"C3_verify_local_descent": (
"python3 applications/collatz/collatz_k_scripts/collatz_verify_c3_local_descent.py "
"--local-h6-root docs/artefacts/collatz "
"--output-dir docs/artefacts/collatz/c3_local_descent "
f"--palier {tgt}"
),
"Universal_clauses_extract": (
"python3 applications/collatz/collatz_k_scripts/collatz_extract_universal_clauses.py "
f"--verification-json docs/artefacts/collatz/c3_local_descent/verification_c3_local_descent_palier2p{tgt}.json "
"--output-dir docs/artefacts/collatz/universal_clauses"
),
"Universal_clauses_verify": (
"python3 applications/collatz/collatz_k_scripts/collatz_verify_universal_clauses.py "
f"--verification-json docs/artefacts/collatz/c3_local_descent/verification_c3_local_descent_palier2p{tgt}.json "
"--clauses-json docs/artefacts/collatz/universal_clauses/clauses_universelles.json "
"--output-dir docs/artefacts/collatz/universal_clauses"
),
"C2_verify_projective": (
"python3 applications/collatz/collatz_k_scripts/collatz_verify_c2_projective.py "
"--repo-root /home/ncantu/code/algo --output-dir docs/artefacts/collatz/c2_projective"
),
}
out_obj = {
"inputs": {
"local_h6_root": str(local_h6_root),
"c2_verification_json": str(c2_verification_json),
"c3_verification_json": str(c3_verification_json),
},
"state": {
"c3_palier": state.c3_palier,
"c2_max_child_palier": state.c2_max_child_palier,
"target_palier": state.target_palier,
"local_h6_states_found": state.local_h6_states_found,
"local_h6_missing_state_ids": state.local_h6_missing_state_ids,
},
"commands": commands,
}
output_dir.mkdir(parents=True, exist_ok=True)
out_json = output_dir / "iteration_protocol.json"
out_md = output_dir / "iteration_protocol.md"
_write_json(out_json, out_obj)
md: list[str] = []
md.append("**Auteur** : Équipe 4NK")
md.append("")
md.append("# Protocole déterministe — itération de palier (C1→C2→C3)")
md.append("")
md.append("## État courant (artefacts)")
md.append("")
md.append(f"- C3 palier certifié : 2^{state.c3_palier} (daprès `{c3_verification_json}`)")
md.append(f"- C2 transition max auditée : palier enfant 2^{state.c2_max_child_palier} (daprès `{c2_verification_json}`)")
md.append("")
md.append("## Palier cible")
md.append("")
md.append(f"- palier cible : 2^{state.target_palier}")
md.append(f"- H6 locale présente (états) : {state.local_h6_states_found}/60")
if state.local_h6_missing_state_ids:
md.append(f"- états manquants : {state.local_h6_missing_state_ids}")
md.append("")
md.append("## Étapes (commandes reproductibles)")
md.append("")
for k, cmd in commands.items():
md.append(f"- **{k}** :")
md.append("")
md.append("```bash")
md.append(cmd)
md.append("```")
md.append("")
md.append("## Notes")
md.append("")
md.append("- Pour `C3_verify_local_descent` : si `palier != 13`, les sorties attendues sont suffixées `..._palier2p<m>.{json,md}`.")
md.append("- La génération de run reports (sha256 + compteurs) pour des paliers `m != 13` peut nécessiter lextension du profil `c3_local_descent` du générateur de rapports.")
_write_md(out_md, md)
def main() -> None:
ap = argparse.ArgumentParser(description="Generate a deterministic next-palier protocol for C1→C2→C3")
ap.add_argument("--local-h6-root", default="docs/artefacts/collatz", help="Root containing local_E*_palier2p<m>/ dirs")
ap.add_argument("--c2-verification-json", default="docs/artefacts/collatz/c2_projective/verification_c2_projective.json")
ap.add_argument("--c3-verification-json", default="docs/artefacts/collatz/c3_local_descent/verification_c3_local_descent.json")
ap.add_argument("--output-dir", default="docs/artefacts/collatz/iteration_protocol")
ap.add_argument("--target-palier", default="", help="Override target palier m (defaults to c3_palier+1)")
args = ap.parse_args()
tgt = int(args.target_palier) if args.target_palier.strip() else None
run(
local_h6_root=Path(args.local_h6_root).resolve(),
c2_verification_json=Path(args.c2_verification_json).resolve(),
c3_verification_json=Path(args.c3_verification_json).resolve(),
output_dir=Path(args.output_dir).resolve(),
target_palier=tgt,
)
if __name__ == "__main__":
main()

View File

@ -7,7 +7,7 @@ Deterministic verification artefact for the C3 step, starting from the
instrumented C1 (local H6 cover) and C2 (projective reduction).
Scope of this verifier:
- domain L := Lift_{12->13}(B12) (represented by the versioned local H6 artefacts)
- domain L := Lift_{12->m}(B12) (represented by the versioned local H6 artefacts)
- for each n in L, select a witnessed elimination:
- D8 branch: verify U^8(n) < n (direct check on the representative n)
- Fusion branch: verify the row witnesses a fusion to a strictly smaller preimage m < n,
@ -17,7 +17,7 @@ Scope of this verifier:
proof for representatives is closed unconditionally at this finite bound.
Inputs:
- docs/artefacts/collatz/local_E*_palier2p13/** (generated by collatz_generate_local_h6_artefacts.py)
- docs/artefacts/collatz/local_E*_palier2p<m>/** (generated by collatz_generate_local_h6_artefacts.py)
Outputs:
- docs/artefacts/collatz/c3_local_descent/verification_c3_local_descent.json
@ -40,10 +40,10 @@ def _read_json(path: Path) -> object:
return json.loads(path.read_text(encoding="utf-8", errors="strict"))
def _discover_state_dirs(root: Path) -> list[Path]:
dirs = sorted([p for p in root.glob("local_E*_palier2p13") if p.is_dir()])
def _discover_state_dirs(root: Path, palier: int) -> list[Path]:
dirs = sorted([p for p in root.glob(f"local_E*_palier2p{palier}") if p.is_dir()])
if not dirs:
raise ValueError(f"No local_E*_palier2p13 directories found under {root}")
raise ValueError(f"No local_E*_palier2p{palier} directories found under {root}")
return dirs
@ -79,9 +79,9 @@ def _parse_int_field(row: dict[str, str], key: str, source: Path) -> int:
raise ValueError(f"Invalid int for {key} in {source}: {raw}") from e
def _load_fusion_witnesses(state_dir: Path) -> list[FusionWitness]:
def _load_fusion_witnesses(state_dir: Path, palier: int) -> list[FusionWitness]:
candidats_dir = state_dir / "candidats"
paths = sorted(candidats_dir.glob("candidats_F9to*_E*_palier2p13.csv"))
paths = sorted(candidats_dir.glob(f"candidats_F9to*_E*_palier2p{palier}.csv"))
if not paths:
return []
@ -143,7 +143,7 @@ def _pick_best_witness(witnesses: list[FusionWitness]) -> dict[int, FusionWitnes
def _infer_state_id(state_dir: Path) -> int:
m = re.search(r"local_E(\d+)_palier2p13", state_dir.name)
m = re.search(r"local_E(\d+)_palier2p(\d+)", state_dir.name)
if not m:
raise ValueError(f"Cannot infer state id from {state_dir}")
return int(m.group(1))
@ -153,8 +153,9 @@ def run(
*,
artefacts_root: Path,
output_dir: Path,
palier: int,
) -> None:
state_dirs = _discover_state_dirs(artefacts_root)
state_dirs = _discover_state_dirs(artefacts_root, palier)
# Domain L as union of per-state lifted noyaux
L: set[int] = set()
@ -163,7 +164,7 @@ def run(
for sd in state_dirs:
sid = _infer_state_id(sd)
lift_candidates = sorted((sd / "noyaux").glob(f"noyau_Lift_E{sid}_palier2p13.json"))
lift_candidates = sorted((sd / "noyaux").glob(f"noyau_Lift_E{sid}_palier2p{palier}.json"))
if len(lift_candidates) != 1:
raise ValueError(f"Expected exactly one lift noyau for {sd}, got {len(lift_candidates)}")
lift_obj = _read_json(lift_candidates[0])
@ -174,11 +175,11 @@ def run(
raise ValueError(f"Invalid lift noyau list: {lift_candidates[0]}")
L |= set(lift_list)
cert_d8 = sd / "certificats" / f"certificat_D8_E{sid}_palier2p13.json"
cert_d8 = sd / "certificats" / f"certificat_D8_E{sid}_palier2p{palier}.json"
if cert_d8.exists():
d8_covered |= _load_covered_from_cert(cert_d8)
fusion_witnesses_all.extend(_load_fusion_witnesses(sd))
fusion_witnesses_all.extend(_load_fusion_witnesses(sd, palier))
if not L:
raise ValueError("Empty domain L")
@ -200,7 +201,7 @@ def run(
d8_exact_base_cases = 0
d8_exact_descent_witnesses = 0
shift = 1 << (13 - 1)
shift = 1 << (palier - 1)
d8_exact_set: set[int] = set()
d8_brother_set: set[int] = set()
for n in d8_nodes:
@ -330,7 +331,7 @@ def run(
"states": len(state_dirs),
},
"domain": {
"palier": 13,
"palier": palier,
"lifted_domain_size": len(L),
"min_n": min(L),
"max_n": max_n,
@ -353,8 +354,12 @@ def run(
}
output_dir.mkdir(parents=True, exist_ok=True)
if palier == 13:
out_json = output_dir / "verification_c3_local_descent.json"
out_md = output_dir / "verification_c3_local_descent.md"
else:
out_json = output_dir / f"verification_c3_local_descent_palier2p{palier}.json"
out_md = output_dir / f"verification_c3_local_descent_palier2p{palier}.md"
out_json.write_text(json.dumps(summary, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
lines: list[str] = []
@ -368,8 +373,8 @@ def run(
lines.append("")
lines.append("## Domaine")
lines.append("")
lines.append(f"- palier : 2^13")
lines.append(f"- |L| (Lift(B12) à 2^13) : {len(L)}")
lines.append(f"- palier : 2^{palier}")
lines.append(f"- |L| (Lift(B12) à 2^{palier}) : {len(L)}")
lines.append(f"- min(L) : {min(L)}, max(L) : {max_n}")
lines.append("")
lines.append("## Élimination (témoins)")
@ -398,21 +403,23 @@ def run(
def main() -> None:
ap = argparse.ArgumentParser(description="Deterministic C3 verification from local H6 artefacts (palier 2^13)")
ap = argparse.ArgumentParser(description="Deterministic C3 verification from local H6 artefacts (palier 2^m)")
ap.add_argument(
"--local-h6-root",
default="docs/artefacts/collatz",
help="Root directory containing local_E*_palier2p13 directories",
help="Root directory containing local_E*_palier2p<m> directories",
)
ap.add_argument(
"--output-dir",
default="docs/artefacts/collatz/c3_local_descent",
help="Output directory for deterministic artefacts (JSON + MD)",
)
ap.add_argument("--palier", type=int, default=13, help="Target palier m for Lift_{12->m}(B12)")
args = ap.parse_args()
run(
artefacts_root=Path(args.local_h6_root),
output_dir=Path(args.output_dir),
palier=int(args.palier),
)

View File

@ -157,3 +157,13 @@ Artefacts déterministes :
- vérification : `applications/collatz/collatz_k_scripts/collatz_verify_universal_clauses.py`
- sorties versionnées : `docs/artefacts/collatz/universal_clauses/{clauses_universelles,verification_universal_clauses}.{json,md}`
- rapport dexécution : `docs/collatz_run_report_2026-03-09_universal_clauses.md`
6.6. Protocole déterministe ditération de palier (au-delà de \(2^{13}\))
Pour organiser litération \(2^m\to 2^{m+1}\) (C1→C2→C3) à partir des artefacts versionnés, un protocole déterministe produit un état courant (paliers certifiés, transitions C2 disponibles) et une checklist de commandes reproductibles pour un palier cible.
- script : `applications/collatz/collatz_k_scripts/collatz_iterate_palier_protocol.py`
- sorties : `docs/artefacts/collatz/iteration_protocol/{iteration_protocol.json,iteration_protocol.md}`
Le vérificateur C3 est paramétrable en palier :
- `applications/collatz/collatz_k_scripts/collatz_verify_c3_local_descent.py --palier m`

View File

@ -0,0 +1,81 @@
{
"inputs": {
"local_h6_root": "/home/ncantu/code/algo/docs/artefacts/collatz",
"c2_verification_json": "/home/ncantu/code/algo/docs/artefacts/collatz/c2_projective/verification_c2_projective.json",
"c3_verification_json": "/home/ncantu/code/algo/docs/artefacts/collatz/c3_local_descent/verification_c3_local_descent.json"
},
"state": {
"c3_palier": 13,
"c2_max_child_palier": 17,
"target_palier": 14,
"local_h6_states_found": 1,
"local_h6_missing_state_ids": [
1,
2,
3,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
16,
17,
18,
19,
20,
21,
22,
23,
24,
25,
26,
27,
28,
29,
30,
31,
32,
33,
34,
35,
36,
37,
38,
39,
40,
41,
42,
43,
44,
45,
46,
47,
48,
49,
50,
51,
52,
53,
54,
55,
56,
57,
58,
59,
60
]
},
"commands": {
"C1_local_H6_generate": "python3 applications/collatz/collatz_k_scripts/collatz_generate_local_h6_artefacts.py --docs-dir docs --output-root docs/artefacts/collatz --state-ids all --palier-start 14 --palier-max 14 --t-min 9 --t-max 120 --write-index --index-path docs/artefacts/collatz/local_H6_index.md",
"C3_verify_local_descent": "python3 applications/collatz/collatz_k_scripts/collatz_verify_c3_local_descent.py --local-h6-root docs/artefacts/collatz --output-dir docs/artefacts/collatz/c3_local_descent --palier 14",
"Universal_clauses_extract": "python3 applications/collatz/collatz_k_scripts/collatz_extract_universal_clauses.py --verification-json docs/artefacts/collatz/c3_local_descent/verification_c3_local_descent_palier2p14.json --output-dir docs/artefacts/collatz/universal_clauses",
"Universal_clauses_verify": "python3 applications/collatz/collatz_k_scripts/collatz_verify_universal_clauses.py --verification-json docs/artefacts/collatz/c3_local_descent/verification_c3_local_descent_palier2p14.json --clauses-json docs/artefacts/collatz/universal_clauses/clauses_universelles.json --output-dir docs/artefacts/collatz/universal_clauses",
"C2_verify_projective": "python3 applications/collatz/collatz_k_scripts/collatz_verify_c2_projective.py --repo-root /home/ncantu/code/algo --output-dir docs/artefacts/collatz/c2_projective"
}
}

View File

@ -0,0 +1,51 @@
**Auteur** : Équipe 4NK
# Protocole déterministe — itération de palier (C1→C2→C3)
## État courant (artefacts)
- C3 palier certifié : 2^13 (daprès `/home/ncantu/code/algo/docs/artefacts/collatz/c3_local_descent/verification_c3_local_descent.json`)
- C2 transition max auditée : palier enfant 2^17 (daprès `/home/ncantu/code/algo/docs/artefacts/collatz/c2_projective/verification_c2_projective.json`)
## Palier cible
- palier cible : 2^14
- H6 locale présente (états) : 1/60
- états manquants : [1, 2, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60]
## Étapes (commandes reproductibles)
- **C1_local_H6_generate** :
```bash
python3 applications/collatz/collatz_k_scripts/collatz_generate_local_h6_artefacts.py --docs-dir docs --output-root docs/artefacts/collatz --state-ids all --palier-start 14 --palier-max 14 --t-min 9 --t-max 120 --write-index --index-path docs/artefacts/collatz/local_H6_index.md
```
- **C3_verify_local_descent** :
```bash
python3 applications/collatz/collatz_k_scripts/collatz_verify_c3_local_descent.py --local-h6-root docs/artefacts/collatz --output-dir docs/artefacts/collatz/c3_local_descent --palier 14
```
- **Universal_clauses_extract** :
```bash
python3 applications/collatz/collatz_k_scripts/collatz_extract_universal_clauses.py --verification-json docs/artefacts/collatz/c3_local_descent/verification_c3_local_descent_palier2p14.json --output-dir docs/artefacts/collatz/universal_clauses
```
- **Universal_clauses_verify** :
```bash
python3 applications/collatz/collatz_k_scripts/collatz_verify_universal_clauses.py --verification-json docs/artefacts/collatz/c3_local_descent/verification_c3_local_descent_palier2p14.json --clauses-json docs/artefacts/collatz/universal_clauses/clauses_universelles.json --output-dir docs/artefacts/collatz/universal_clauses
```
- **C2_verify_projective** :
```bash
python3 applications/collatz/collatz_k_scripts/collatz_verify_c2_projective.py --repo-root /home/ncantu/code/algo --output-dir docs/artefacts/collatz/c2_projective
```
## Notes
- Pour `C3_verify_local_descent` : si `palier != 13`, les sorties attendues sont suffixées `..._palier2p<m>.{json,md}`.
- La génération de run reports (sha256 + compteurs) pour des paliers `m != 13` peut nécessiter lextension du profil `c3_local_descent` du générateur de rapports.

View File

@ -112,6 +112,7 @@ python3 applications/collatz/collatz_k_scripts/collatz_generate_run_report.py \
--profile c3_local_descent \
--scope c3_local_descent \
--c3-artefacts-dir docs/artefacts/collatz/c3_local_descent \
--c3-palier 13 \
--out-dir applications/collatz/out \
--docs-dir docs
```