Extend C2 projective verifier with declared residual size checks

**Motivations:**
- Make C2 verification robust when completion documents provide counts instead of full residual lists
- Prepare the verifier to support additional completion transitions with the same artefact structure

**Root causes:**
- m=15→m=16 completion doc may omit residual list and only state |R_16^comp| = 2*|both| = 2202

**Correctifs:**
- Parse and validate declared |R_{m+1}^comp| size when present
- Add generic transition discovery output in verification JSON

**Evolutions:**
- Regenerate versioned C2 verification artefacts and run report

**Pages affectées:**
- applications/collatz/collatz_k_scripts/collatz_verify_c2_projective.py
- docs/artefacts/collatz/c2_projective/verification_c2_projective.json
- docs/artefacts/collatz/c2_projective/verification_c2_projective.md
- docs/collatz_run_report_2026-03-09_c2_projective.md
This commit is contained in:
ncantu 2026-03-09 01:43:53 +01:00
parent e3e68fe39c
commit c29bbaec4b
4 changed files with 102 additions and 8 deletions

View File

@ -110,6 +110,44 @@ def _display_path(path: Path, repo_root: Path) -> str:
return str(path) return str(path)
def _discover_both_parent_blocks(md: str) -> dict[int, list[int]]:
"""
Discover "Parents « both » au palier 2^m" blocks in a completion markdown file.
Returns m -> residues list.
"""
out: dict[int, list[int]] = {}
for m in re.findall(r"^###\s+Parents\s+«\s+both\s+»\s+au\s+palier\s+2\^(\d+)\b", md, flags=re.M):
mm = int(m)
out[mm] = _parse_list_under_heading(
md, rf"^###\s+Parents\s+«\s+both\s+»\s+au\s+palier\s+2\^{mm}\b"
)
return out
def _discover_listed_residual_after_completion(md: str, child_palier: int) -> list[int] | None:
"""
Optional: "Résidu restant au palier 2^(m+1) après complétion « one » (...)".
"""
pattern = rf"^###\s+Résidu\s+restant\s+au\s+palier\s+2\^{child_palier}\s+après\s+complétion\s+«\s+one\s+»"
try:
return _parse_list_under_heading(md, pattern)
except ValueError:
return None
def _discover_declared_residual_size(md: str, child_palier: int) -> int | None:
"""
Optional: parse a declared residual size like "|R_16^comp| = 2202".
"""
matches = re.findall(rf"\|R_{child_palier}\^comp\|\s*=\s*([^\n]+)", md)
if not matches:
return None
# Extract the last integer on the matched RHS (e.g. "2*|both| = 2202" -> 2202).
rhs = matches[-1]
ints = re.findall(r"\b(\d+)\b", rhs)
return int(ints[-1]) if ints else None
def verify_c2( def verify_c2(
*, *,
noyau_both_base_4096_md: Path, noyau_both_base_4096_md: Path,
@ -156,17 +194,47 @@ def verify_c2(
children16 = _lift_children(both15, 15) children16 = _lift_children(both15, 15)
_assert_all_odd(children16, "Computed children at m=16") _assert_all_odd(children16, "Computed children at m=16")
# This completion file does not necessarily list the full residual after completion, so we only check the count. listed_r16 = _discover_listed_residual_after_completion(c1516_md, 16)
declared_r16_size = _discover_declared_residual_size(c1516_md, 16)
if listed_r16 is not None:
_assert_all_odd(listed_r16, "Listed residual at m=16")
if set(listed_r16) != set(children16):
raise ValueError("Listed residual at m=16 does not match computed children of both(m=15)")
if declared_r16_size is not None and declared_r16_size != len(children16):
raise ValueError(f"Declared |R_16^comp|={declared_r16_size} does not match computed {len(children16)}")
# This completion file may omit the full residual after completion.
c1516 = C2CheckResult( c1516 = C2CheckResult(
palier_parent=15, palier_parent=15,
palier_child=16, palier_child=16,
both_parent_count=len(both15), both_parent_count=len(both15),
computed_children_count=len(children16), computed_children_count=len(children16),
listed_residual_count=None, listed_residual_count=len(listed_r16) if listed_r16 is not None else declared_r16_size,
children_equals_listed_residual=None, children_equals_listed_residual=(set(children16) == set(listed_r16)) if listed_r16 is not None else None,
projection_mod_4096_equals_b12=(_project_mod_4096(both15) == set(b12)), projection_mod_4096_equals_b12=(_project_mod_4096(both15) == set(b12)),
) )
transitions: list[dict[str, object]] = []
for src_name, md in (("completion_m14_to_m15", c1415_md), ("completion_m15_to_m16", c1516_md)):
both_blocks = _discover_both_parent_blocks(md)
for m_parent, both in sorted(both_blocks.items()):
child_palier = m_parent + 1
children = _lift_children(both, m_parent)
listed = _discover_listed_residual_after_completion(md, child_palier)
declared = _discover_declared_residual_size(md, child_palier)
transitions.append(
{
"source": src_name,
"palier_parent": m_parent,
"palier_child": child_palier,
"both_parent_count": len(both),
"computed_children_count": len(children),
"listed_residual_count": len(listed) if listed is not None else declared,
"children_equals_listed_residual": (set(children) == set(listed)) if listed is not None else None,
"projection_mod_4096_equals_b12": (_project_mod_4096(both) == set(b12)),
}
)
summary = { summary = {
"inputs": { "inputs": {
"noyau_both_base_4096_md": _display_path(noyau_both_base_4096_md, repo_root), "noyau_both_base_4096_md": _display_path(noyau_both_base_4096_md, repo_root),
@ -174,6 +242,7 @@ def verify_c2(
"completion_m15_to_m16_md": _display_path(completion_m15_to_m16_md, repo_root), "completion_m15_to_m16_md": _display_path(completion_m15_to_m16_md, repo_root),
}, },
"B12": {"count": len(b12), "min": min(b12), "max": max(b12)}, "B12": {"count": len(b12), "min": min(b12), "max": max(b12)},
"transitions": transitions,
"checks": { "checks": {
"m14_to_m15": c1415.__dict__, "m14_to_m15": c1415.__dict__,
"m15_to_m16": c1516.__dict__, "m15_to_m16": c1516.__dict__,
@ -216,6 +285,8 @@ def verify_c2(
lines.append("") lines.append("")
lines.append(f"- |both parents (m=15)| = {c1516.both_parent_count}") lines.append(f"- |both parents (m=15)| = {c1516.both_parent_count}")
lines.append(f"- |computed children (m=16)| = {c1516.computed_children_count} (attendu 2*|both|)") lines.append(f"- |computed children (m=16)| = {c1516.computed_children_count} (attendu 2*|both|)")
if c1516.listed_residual_count is not None:
lines.append(f"- |listed residual (m=16)| : {c1516.listed_residual_count}")
lines.append(f"- projection both(m=15) mod 4096 == B12 : {c1516.projection_mod_4096_equals_b12}") lines.append(f"- projection both(m=15) mod 4096 == B12 : {c1516.projection_mod_4096_equals_b12}")
lines.append("") lines.append("")
lines.append("## Sorties") lines.append("## Sorties")

View File

@ -9,6 +9,28 @@
"min": 27, "min": 27,
"max": 4095 "max": 4095
}, },
"transitions": [
{
"source": "completion_m14_to_m15",
"palier_parent": 14,
"palier_child": 15,
"both_parent_count": 593,
"computed_children_count": 1186,
"listed_residual_count": 1186,
"children_equals_listed_residual": true,
"projection_mod_4096_equals_b12": true
},
{
"source": "completion_m15_to_m16",
"palier_parent": 15,
"palier_child": 16,
"both_parent_count": 1101,
"computed_children_count": 2202,
"listed_residual_count": 2202,
"children_equals_listed_residual": null,
"projection_mod_4096_equals_b12": true
}
],
"checks": { "checks": {
"m14_to_m15": { "m14_to_m15": {
"palier_parent": 14, "palier_parent": 14,
@ -24,7 +46,7 @@
"palier_child": 16, "palier_child": 16,
"both_parent_count": 1101, "both_parent_count": 1101,
"computed_children_count": 2202, "computed_children_count": 2202,
"listed_residual_count": null, "listed_residual_count": 2202,
"children_equals_listed_residual": null, "children_equals_listed_residual": null,
"projection_mod_4096_equals_b12": true "projection_mod_4096_equals_b12": true
} }

View File

@ -25,6 +25,7 @@
- |both parents (m=15)| = 1101 - |both parents (m=15)| = 1101
- |computed children (m=16)| = 2202 (attendu 2*|both|) - |computed children (m=16)| = 2202 (attendu 2*|both|)
- |listed residual (m=16)| : 2202
- projection both(m=15) mod 4096 == B12 : True - projection both(m=15) mod 4096 == B12 : True
## Sorties ## Sorties

View File

@ -9,7 +9,7 @@
## Code et reproductibilité ## Code et reproductibilité
- **Commit Git** : `e12c544e7bf8d7a6f9518cdd4959d0da07bccc07` - **Commit Git** : `e3e68fe39c8ad466e5741e87d20311ca93b2456e`
- **Commande** : - **Commande** :
```bash ```bash
@ -19,7 +19,7 @@ python3 applications/collatz/collatz_k_scripts/collatz_generate_run_report.py --
## Empreintes sha256 (scripts, artefacts) ## Empreintes sha256 (scripts, artefacts)
- `/home/ncantu/code/algo/applications/collatz/collatz_k_scripts/collatz_verify_c2_projective.py` - `/home/ncantu/code/algo/applications/collatz/collatz_k_scripts/collatz_verify_c2_projective.py`
- sha256: `4f515d649c44c4ffe55349b07c72e9e766f591f7596e1bcdf0f9447e6b94950d` - sha256: `bad3ddd4288af4a8b7bcc4fa30c1f5350db16f352b6eaf29918e8c1e1428a5b0`
- `/home/ncantu/code/algo/applications/collatz/collatz_k_scripts/noyau_both_base_4096.md` - `/home/ncantu/code/algo/applications/collatz/collatz_k_scripts/noyau_both_base_4096.md`
- sha256: `d0abffeca9ce2356d13b31ee4325b68fd85cf061670a8524b8e60fd15aba881b` - sha256: `d0abffeca9ce2356d13b31ee4325b68fd85cf061670a8524b8e60fd15aba881b`
- `/home/ncantu/code/algo/applications/collatz/collatz_k_scripts/complétion_minorée_m14_vers_m15.md` - `/home/ncantu/code/algo/applications/collatz/collatz_k_scripts/complétion_minorée_m14_vers_m15.md`
@ -27,9 +27,9 @@ python3 applications/collatz/collatz_k_scripts/collatz_generate_run_report.py --
- `/home/ncantu/code/algo/applications/collatz/collatz_k_scripts/complétion_minorée_m15_vers_m16.md` - `/home/ncantu/code/algo/applications/collatz/collatz_k_scripts/complétion_minorée_m15_vers_m16.md`
- sha256: `956f8fd19570b7614705384995b52b6bf81399ca8dc36c3ea33bcbad3e4aff50` - sha256: `956f8fd19570b7614705384995b52b6bf81399ca8dc36c3ea33bcbad3e4aff50`
- `/home/ncantu/code/algo/docs/artefacts/collatz/c2_projective/verification_c2_projective.json` - `/home/ncantu/code/algo/docs/artefacts/collatz/c2_projective/verification_c2_projective.json`
- sha256: `b1b9ab9d5d4f9d4ee60e7efd43b853ac29d3d51a19c2d71bdac45c4340d01bf0` - sha256: `259901a9e4e307dae238672cc3e2e1e016800f034cdd98efe929e69bc1e4068a`
- `/home/ncantu/code/algo/docs/artefacts/collatz/c2_projective/verification_c2_projective.md` - `/home/ncantu/code/algo/docs/artefacts/collatz/c2_projective/verification_c2_projective.md`
- sha256: `8bd84cb2149d297487e0e0ff03ebe96ff27b07a5ab677407cb4b9a17a339c2ba` - sha256: `dc5562fea53f62e88acc08bcd29c89c53e9ad6e5fc4d41351642e7942cc49bdc`
## Compteurs et métriques ## Compteurs et métriques