diff --git a/notes/p593-phaseA-rlinear-definition-20260913.md b/notes/p593-phaseA-rlinear-definition-20260913.md new file mode 100644 index 000000000..c3a921078 --- /dev/null +++ b/notes/p593-phaseA-rlinear-definition-20260913.md @@ -0,0 +1,82 @@ +# p593 Phase A — pinning down the definition of `r_linear` (2026-09-13) + +Issue: #593. Context: the #599 probe measured block-Hankel / McMillan orders of +9/8, 4/5, 2/2 at widths 4..6 for the P398 projected-memory object, while the +rank tables in #593/#588 report `r_linear` = 10, 26, 72. Phase A asks which +candidate definition of `r_linear` reproduces 10/26/72, and whether the probe +numbers contradict the reported ones. + +## Method + +All quantities computed at `eta = 0` from the repository's own exact generator +(`p398_intervention_transport.Generator`, widths 4..8), with integer-valued +readouts and exact elimination over the Mersenne prime 2147483647 (the +repository's `RANK_PRIME`; no pivot tolerance anywhere). Script: +`scripts/p593_phaseA_rlinear_candidates.py`; raw output: +`results/p593-phaseA-rlinear/raw/candidates.json`. + +## Result table + +| quantity | w=4 | w=5 | w=6 | w=7 | w=8 | +|---|---|---|---|---|---| +| states | 14 | 42 | 132 | 429 | 1430 | +| **C1: joint Krylov `dim span{G0^k f}`, D0 seeds** | **10** | **26** | **72** | **218** | **689** | +| C1 variant: seeds + constant `1` | 10 | 26 | 72 | 218 | 689 | +| C1 variant: D2 dictionary seeds (8 readouts) | 10 | 42 | 76 | 415 | 750 | +| C1 variant: `k >= 1` only (drop the seeds) | 9 | 25 | 71 | 217 | 688 | +| C1 variant: Krylov under `H` instead of `G0` | 10 | 22 | 76 | 214 | 750 | +| C1 variant: floating-point Krylov | 10 | 27 | 81 | – | – | +| C2: coarsest exact lumping vs `G0` only (block count) | 10 | 26 | 76 | 232 | 750 | +| C2: coarsest exact lumping vs `G0` and `H` jointly | 10 | 26 | 76 | 232 | 750 | +| C2: distinct D0 readout value-tuples | 8 | 12 | 18 | 24 | 32 | +| C3: rank `G0` mod p | 13 | 41 | 131 | 428 | 1429 | +| C3: rank `H` mod p | 13 | 32 | 130 | 392 | 1429 | +| C3: rank `J` / rank `D` mod p | 13 | 41 | 131 | 428 | 1429 | +| C3: distinct rows of `G0` | 14 | 42 | 132 | 429 | 1430 | +| repository `observable_reachable_dimension` (cross-check) | 10 | 26 | 72 | 218 | not run (budget) | + +## Conclusion + +**Candidate 1 is pinned, exactly and only.** `r_linear` is the joint Krylov +dimension + +```text +r_linear = dim span{ G0^k f : k >= 0, f in {blocks, singletons, wrap} } +``` + +computed by exact elimination over the prime 2147483647. It reproduces +10/26/72 at widths 4..6 and 218 at width 7, and agrees digit-for-digit with +`rank_notions.D0_additive_local_counts.r_linear` stored in +`results/p398-intervention-transport/latest.json`. + +- **Candidate 2 (lumping degrees of freedom) is falsified**: the coarsest + exact strong lumping gives 76/232/750 at widths 6..8 — that is `r_positive`, + not `r_linear`. (It coincides with `r_linear` only at widths 4 and 5.) +- **Candidate 3 (pencil ranks / combinatorial structures) is falsified**: + every pencil rank is `size - 1` or near it, and no combinatorial count + matches. +- The #599 probe numbers 9/8, 4/5, 2/2 are **not** a competing measurement of + `r_linear`; they are block-Hankel / McMillan orders of the *projected + memory kernel* on the frozen rank-6 span — a strictly smaller and different + object (its `k >= 1`-flavoured count at width 4 is 9, which is the probe's + block-Hankel value, but the sequence diverges immediately at width 5). + +## New value produced + +- **True `r_linear` at width 8 = 689** (Krylov depth 621). The repository + entry was budget-truncated (`>= 150`, `LINEAR_RANK_BUDGET_ABOVE`); the + exact elimination at full budget is now affordable and the number is a + certified lower bound on the rational rank. + +## Notes + +- The floating-point Krylov variant overestimates (27 at width 5, 81 at + width 6 against true 26/72), reproducing the failure mode the ticket + documents for float Gram-Schmidt. +- `dim span{G0^k f}` with the constant function added to the seeds is + unchanged at every width: `1` is already in the Krylov span of the D0 + readouts. +- Full-budget `r_linear` at widths 9 and 10 is computed in Phase B + (`results/p593-width9-10-memory-degree/`). + +Full Matching-One repository CI has not been run for this commit. diff --git a/results/p593-phaseA-rlinear/REPORT.md b/results/p593-phaseA-rlinear/REPORT.md new file mode 100644 index 000000000..418c6e55c --- /dev/null +++ b/results/p593-phaseA-rlinear/REPORT.md @@ -0,0 +1,30 @@ +# p593 Phase A — r_linear definition pinned (raw: candidates.json) + +- date: 2026-09-13 +- issue: 593 (Phase A) +- question: which candidate definition of `r_linear` reproduces 10/26/72 at widths 4..6? +- answer: the joint Krylov dimension `dim span{G0^k f : k >= 0, f in D0 readouts}` under exact elimination mod 2147483647; candidates 2 (lumping) and 3 (pencil ranks) are falsified. +- new value: true `r_linear` at width 8 = 689 (repository had budget-truncated `>= 150`). + +## Commands + +```bash +cd scripts +python p593_phaseA_rlinear_candidates.py +``` + +## Environment + +- Python 3.13 (managed env), numpy 2.5.2, scipy 1.18.1 +- repository generator/codec code unchanged; all arithmetic exact over Z/2147483647 +- no frozen configuration was substituted: seeds, readouts, dictionary nesting and the prime are the repository's own declarations + +## Verdict per candidate + +| candidate | definition | verdict | +|---|---|---| +| 1 | joint Krylov over D0 seeds, exact mod-p | **matches 10/26/72/218 exactly** | +| 2 | linear lumping (coarsest exact strong lumping block count) | gives 76/232/750 at widths 6..8 = `r_positive`, not `r_linear` | +| 3 | pencil ranks (G0, H, J, D mod p; row patterns; value tuples) | no match at any width | + +Full Matching-One repository CI has not been run for this commit. diff --git a/results/p593-phaseA-rlinear/commands.txt b/results/p593-phaseA-rlinear/commands.txt new file mode 100644 index 000000000..c3886b425 --- /dev/null +++ b/results/p593-phaseA-rlinear/commands.txt @@ -0,0 +1,2 @@ +cd scripts && python p593_phaseA_rlinear_candidates.py +# output -> raw/candidates.json (stdout lines, one JSON object per width) diff --git a/results/p593-phaseA-rlinear/metadata.json b/results/p593-phaseA-rlinear/metadata.json new file mode 100644 index 000000000..b24fc1b53 --- /dev/null +++ b/results/p593-phaseA-rlinear/metadata.json @@ -0,0 +1,12 @@ +{ + "issue": 593, + "phase": "A", + "date": "2026-09-13", + "topic": "r_linear definition", + "conclusion": "r_linear = dim span{G0^k f : k>=0, f in D0 readouts}, exact elimination mod 2147483647; candidates 2 and 3 falsified; true r_linear(8) = 689", + "widths": [4, 5, 6, 7, 8], + "prime": 2147483647, + "artifacts": ["raw/candidates.json"], + "scripts": ["scripts/p593_phaseA_rlinear_candidates.py"], + "ci_status": "Full Matching-One repository CI has not been run for this commit." +} diff --git a/results/p593-phaseA-rlinear/raw/candidates.json b/results/p593-phaseA-rlinear/raw/candidates.json new file mode 100644 index 000000000..b2d9b33cd --- /dev/null +++ b/results/p593-phaseA-rlinear/raw/candidates.json @@ -0,0 +1,104 @@ +[ + { + "width": 4, + "size": 14, + "c1_krylov_D0": 10, + "c1_krylov_D0_depth": 5, + "c1_krylov_D0_plus_constant": 10, + "c1_krylov_D2": 10, + "c1_krylov_D0_kge1": 9, + "c1_krylov_D0_with_H": 10, + "c1_float_krylov_D0": 10, + "repo_observable_reachable_dimension": 10, + "c2_lumping_G0_only": 10, + "c2_lumping_G0_and_H_joint": 10, + "c2_distinct_readout_tuples": 8, + "c3_rank_G0_mod_p": 13, + "c3_rank_H_mod_p": 13, + "c3_rank_J_mod_p": 13, + "c3_rank_D_mod_p": 13, + "c3_distinct_rows_G0": 14, + "seconds": 0.0 + }, + { + "width": 5, + "size": 42, + "c1_krylov_D0": 26, + "c1_krylov_D0_depth": 17, + "c1_krylov_D0_plus_constant": 26, + "c1_krylov_D2": 42, + "c1_krylov_D0_kge1": 25, + "c1_krylov_D0_with_H": 22, + "c1_float_krylov_D0": 27, + "repo_observable_reachable_dimension": 26, + "c2_lumping_G0_only": 26, + "c2_lumping_G0_and_H_joint": 26, + "c2_distinct_readout_tuples": 12, + "c3_rank_G0_mod_p": 41, + "c3_rank_H_mod_p": 32, + "c3_rank_J_mod_p": 41, + "c3_rank_D_mod_p": 41, + "c3_distinct_rows_G0": 42, + "seconds": 0.0 + }, + { + "width": 6, + "size": 132, + "c1_krylov_D0": 72, + "c1_krylov_D0_depth": 53, + "c1_krylov_D0_plus_constant": 72, + "c1_krylov_D2": 76, + "c1_krylov_D0_kge1": 71, + "c1_krylov_D0_with_H": 76, + "c1_float_krylov_D0": 81, + "repo_observable_reachable_dimension": 72, + "c2_lumping_G0_only": 76, + "c2_lumping_G0_and_H_joint": 76, + "c2_distinct_readout_tuples": 18, + "c3_rank_G0_mod_p": 131, + "c3_rank_H_mod_p": 130, + "c3_rank_J_mod_p": 131, + "c3_rank_D_mod_p": 131, + "c3_distinct_rows_G0": 132, + "seconds": 0.3 + }, + { + "width": 7, + "size": 429, + "c1_krylov_D0": 218, + "c1_krylov_D0_depth": 184, + "c1_krylov_D0_plus_constant": 218, + "c1_krylov_D2": 415, + "c1_krylov_D0_kge1": 217, + "c1_krylov_D0_with_H": 214, + "repo_observable_reachable_dimension": 218, + "c2_lumping_G0_only": 232, + "c2_lumping_G0_and_H_joint": 232, + "c2_distinct_readout_tuples": 24, + "c3_rank_G0_mod_p": 428, + "c3_rank_H_mod_p": 392, + "c3_rank_J_mod_p": 428, + "c3_rank_D_mod_p": 428, + "c3_distinct_rows_G0": 429, + "seconds": 7.7 + }, + { + "width": 8, + "size": 1430, + "c1_krylov_D0": 689, + "c1_krylov_D0_depth": 621, + "c1_krylov_D0_plus_constant": 689, + "c1_krylov_D2": 750, + "c1_krylov_D0_kge1": 688, + "c1_krylov_D0_with_H": 750, + "c2_lumping_G0_only": 750, + "c2_lumping_G0_and_H_joint": 750, + "c2_distinct_readout_tuples": 32, + "c3_rank_G0_mod_p": 1429, + "c3_rank_H_mod_p": 1429, + "c3_rank_J_mod_p": 1429, + "c3_rank_D_mod_p": 1429, + "c3_distinct_rows_G0": 1430, + "seconds": 167.8 + } +] \ No newline at end of file diff --git a/scripts/p593_phaseA_rlinear_candidates.py b/scripts/p593_phaseA_rlinear_candidates.py new file mode 100644 index 000000000..a1d49a66a --- /dev/null +++ b/scripts/p593_phaseA_rlinear_candidates.py @@ -0,0 +1,297 @@ +#!/usr/bin/env python3 +"""Issue #593 Phase A: which candidate definition reproduces r_linear = 10/26/72? + +The #599 probe measured block-Hankel / McMillan degrees of 9/8, 4/5, 2/2 at +widths 4..6, while #593/#588 report r_linear = 10, 26, 72. This script +computes every candidate "linear dimension" notion at widths 4..8 and checks +which one (if any) reproduces the reported triple: + + candidate 1 joint Krylov span dim span{G0^k f} over the readout seeds + (variants: D0 seeds, D0+constant, D2 seeds, k>=1 only, H0 powers) + candidate 2 linear lumping degrees of freedom + (coarsest exact strong lumping vs G0 only / G0+H jointly, + distinct readout value tuples) + candidate 3 pencil ranks and combinatorial structures + (rank G0, rank H, rank J, rank D mod p; distinct rows of G0) + +All modular arithmetic is exact over the prime 2147483647, matching the +repository's RANK_PRIME convention (integer vectors, no pivot tolerance). +""" + +from __future__ import annotations + +import json +import sys +import time +from pathlib import Path + +import numpy as np + +SCRIPTS = Path(__file__).resolve().parent +sys.path.insert(0, str(SCRIPTS)) + +from p398_intervention_transport import ( # noqa: E402 + DICTIONARIES, + Generator, + RANK_PRIME, + exact_lumping, + observable_reachable_dimension, +) +from noncrossing_connectivity_codec import noncrossing_states # noqa: E402 +from planar_state_operations import rgs_to_blocks # noqa: E402 + +P = RANK_PRIME + +OBSERVABLES = { + "blocks": lambda s: float(max(s) + 1), + "singletons": lambda s: float(sum(1 for b in rgs_to_blocks(s) if len(b) == 1)), + "wrap": lambda s: 1.0 if s[0] == s[-1] else 0.0, + "max_block": lambda s: float(max(len(b) for b in rgs_to_blocks(s))), + "linked_pairs": lambda s: float( + sum(len(b) * (len(b) - 1) // 2 for b in rgs_to_blocks(s)) + ), + "boundary_span": lambda s: float(sum(max(b) - min(b) for b in rgs_to_blocks(s))), + "halves_linked": lambda s: _halves_linked(s), + "covering_depth": lambda s: _covering_depth(s), +} + + +def _halves_linked(state): + width = len(state) + half = width // 2 + for block in rgs_to_blocks(state): + if any(p < half for p in block) and any(p >= half for p in block): + return 1.0 + return 0.0 + + +def _covering_depth(state): + blocks = [sorted(b) for b in rgs_to_blocks(state)] + best = 0 + for point in range(len(state)): + depth = sum( + 1 for b in blocks if b[0] < point < b[-1] and point not in b + ) + best = max(best, depth) + return float(best) + + +def sparse_mod_rows(gen, rates): + """Integer mod-p rows of the generator (row = source, acting on functions).""" + rows = [] + for row in gen.rows(rates): + rows.append([(c, int(round(v)) % P) for c, v in row]) + return rows + + +def matvec_mod(rows_sparse, size, vec): + out = [0] * size + for i, row in enumerate(rows_sparse): + acc = 0 + for c, v in row: + acc += v * vec[c] + out[i] = acc % P + return out + + +def rank_mod(vectors, size): + """Exact rank of integer vectors in Z_p^size via echelon insertion.""" + pivots = {} # pivot column -> normalized vector (numpy int64) + + def insert(vec): + working = np.array(vec, dtype=np.int64) + for col in sorted(pivots): + pv = pivots[col] + f = int(working[col]) + if f: + working = (working - f * pv) % P + nz = np.nonzero(working)[0] + if nz.size == 0: + return False + col = int(nz[0]) + inv = pow(int(working[col]), P - 2, P) + working = (working * inv) % P + # back-substitute for reduced echelon (keeps future inserts cheap) + for other, pv in list(pivots.items()): + if int(pv[col]): + pivots[other] = (pv - int(pv[col]) * working) % P + pivots[col] = working + return True + + for v in vectors: + insert(v) + return len(pivots) + + +def krylov_dimension(gen, rows_sparse, size, seed_vectors, max_depth=None): + """dim span{G^k f} mod p, with depth tracked until 2 consecutive plateaus.""" + frontier = [[int(round(x)) % P for x in v] for v in seed_vectors] + echelon = {} + + def insert(vec): + working = list(vec) + for col, pv in echelon.items(): + f = working[col] + if f: + working = [(a - f * b) % P for a, b in zip(working, pv)] + col = next((i for i, a in enumerate(working) if a), None) + if col is None: + return None + inv = pow(working[col], P - 2, P) + working = [(a * inv) % P for a in working] + for other, pv in list(echelon.items()): + if pv[col]: + echelon[other] = [(a - pv[col] * b) % P for a, b in zip(pv, working)] + echelon[col] = working + return working + + depth = 0 + history = [] + added_any = True + while frontier and added_any: + added = [] + for v in frontier: + w = insert(v) + if w is not None: + added.append(w) + history.append(len(echelon)) + depth += 1 + if max_depth is not None and depth >= max_depth: + break + if not added: + added_any = False + break + frontier = [matvec_mod(rows_sparse, size, v) for v in added] + if len(history) >= 3 and history[-1] == history[-2] == history[-3]: + break + return len(echelon), depth + + +def float_krylov_dimension(gen, rows_float, size, seed_vectors, tol=1e-8): + """Floating-point Krylov dimension (the documented failure mode).""" + basis = [] + frontier = [list(map(float, v)) for v in seed_vectors] + while frontier: + added = [] + for v in frontier: + w = list(v) + for b in basis: + dot = sum(a * x for a, x in zip(b, w)) + w = [a - dot * x for a, x in zip(w, b)] + nrm = sum(a * a for a in w) ** 0.5 + if nrm > tol: + basis.append([a / nrm for a in w]) + added.append(w) + if not added: + break + frontier = [ + [sum(v * vec[c] for c, v in row_i) for row_i in rows_float] + for vec in added + ] + return len(basis) + + +def lumping_block_count(gen, colours, operators): + result = exact_lumping(gen, colours, operators) + return result["blocks"] + + +def analyse_width(width): + gen = Generator(width) + size = gen.size + states = gen.states + baseline = gen.baseline_rates() + rows_sparse = sparse_mod_rows(gen, baseline) + h_rows = sparse_mod_rows(gen, gen.coefficients("uniform_join_minus_detach")) + + # integer observation vectors + obs = {name: [int(OBSERVABLES[name](s)) for s in states] for name in OBSERVABLES} + D0 = ["blocks", "singletons", "wrap"] + D2 = DICTIONARIES["D2_plus_nonlocal_topology"] + + seeds_D0 = [obs[n] for n in D0] + seeds_D0_one = [[1] * size] + seeds_D0 + seeds_D2 = [obs[n] for n in D2] + colour_D0 = list(zip(obs["blocks"], obs["singletons"], obs["wrap"])) + + out = {"width": width, "size": size} + + # --- candidate 1: joint Krylov spans ----------------------------------- + dim, depth = krylov_dimension(gen, rows_sparse, size, seeds_D0) + out["c1_krylov_D0"] = dim + out["c1_krylov_D0_depth"] = depth + dim, _ = krylov_dimension(gen, rows_sparse, size, seeds_D0_one) + out["c1_krylov_D0_plus_constant"] = dim + dim, _ = krylov_dimension(gen, rows_sparse, size, seeds_D2) + out["c1_krylov_D2"] = dim + + # k >= 1 only: apply G once to the seeds, then iterate + shifted = [matvec_mod(rows_sparse, size, [int(x) % P for x in v]) for v in seeds_D0] + dim, _ = krylov_dimension(gen, rows_sparse, size, shifted) + out["c1_krylov_D0_kge1"] = dim + + # H-powered Krylov + dim, _ = krylov_dimension(gen, h_rows, size, seeds_D0) + out["c1_krylov_D0_with_H"] = dim + + # float failure-mode check (cheap, widths <= 6 only) + if width <= 6: + rows_float = gen.rows(baseline) + out["c1_float_krylov_D0"] = float_krylov_dimension( + gen, rows_float, size, seeds_D0 + ) + + # cross-check against the repository's own implementation (width <= 7) + if width <= 7: + out["repo_observable_reachable_dimension"] = ( + observable_reachable_dimension(gen, seeds_D0, size)["dimension"] + ) + + # --- candidate 2: linear lumping degrees of freedom --------------------- + out["c2_lumping_G0_only"] = lumping_block_count(gen, colour_D0, [baseline]) + out["c2_lumping_G0_and_H_joint"] = lumping_block_count( + gen, colour_D0, [baseline, gen.coefficients("uniform_join_minus_detach")] + ) + out["c2_distinct_readout_tuples"] = len(set(colour_D0)) + + # --- candidate 3: pencil ranks / combinatorial structures --------------- + out["c3_rank_G0_mod_p"] = _matrix_rank_mod(rows_sparse, size) + out["c3_rank_H_mod_p"] = _matrix_rank_mod(h_rows, size) + + join_only = {m: (1.0 if m[0] == "join" else 0.0) for m in gen.moves} + detach_only = {m: (1.0 if m[0] == "detach" else 0.0) for m in gen.moves} + out["c3_rank_J_mod_p"] = _matrix_rank_mod(sparse_mod_rows(gen, join_only), size) + out["c3_rank_D_mod_p"] = _matrix_rank_mod(sparse_mod_rows(gen, detach_only), size) + + row_patterns = set() + for row in gen.rows(baseline): + row_patterns.add(tuple((c, round(v)) for c, v in row)) + out["c3_distinct_rows_G0"] = len(row_patterns) + + return out + + +def _matrix_rank_mod(rows_sparse, size): + """Rank of the generator matrix itself over Z_p (rows = sources).""" + dense_rows = [] + for row in rows_sparse: + v = [0] * size + for c, val in row: + v[c] = val + dense_rows.append(v) + return rank_mod(dense_rows, size) + + +def main(): + results = [] + for width in (4, 5, 6, 7, 8): + t0 = time.time() + row = analyse_width(width) + row["seconds"] = round(time.time() - t0, 1) + results.append(row) + print(json.dumps(row), flush=True) + Path("/tmp/p593-phaseA-results.json").write_text(json.dumps(results, indent=2)) + + +if __name__ == "__main__": + main()