Research-Stack/4-Infrastructure/shim/pist_matrix_builder.py
allaun 558431fc45 feat(lean,infra): Corpus278→250 rename + SLOS-calibrated braid defaults
- Renamed Corpus278/Matrices278 to Corpus250/Matrices250 across all
  Lean modules, Python shims, JSON schemas, and documentation
- Applied SLOS-calibrated defaults to braid_search.py:
  bracket_cost base=8x/16x, crossing_penalty gap_reward=0.01
- Synced same defaults to research-compute-fabric submodule
- Updated Burgers 0D defaults: nu=0.9995, advection=0.075
- Fixed 278→250 refs in pist_predictions JSON (claim_boundary, total)
- Fixed stale schema refs in .devin/skills/lean-proof/SKILL.md
- Updated AGENTS.md build baselines to post-clean counts (3314 jobs)
- Updated lake-manifest.json sparkle rev to match lakefile.toml
- Fixed Q16_16 signed conversion bug in qaoa_adapter tunable costs
- NBody.lean: pre-existing dirty changes (introN failures, NOT our work)

Build: 3314 jobs, 0 errors (lake build Compiler)
2026-06-18 22:16:52 -05:00

315 lines
13 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""pist_matrix_builder — reproducible 8×8 braid adjacency matrix from RRC equations.
Builds token→strand adjacency matrices. This is a feature-extraction shim
only — it produces no classifier output and no Lean spectral analysis.
proxy_pred/exact_pred are left null for the Lean surface.
Output schema: rrc_pist_predictions_250_v1 (claim_boundary: matrix-only)
"""
import hashlib
import json
import os
import re
import sys
from collections import defaultdict
N_STRANDS = 8
RECEIPT_JSON = os.path.join(
os.path.dirname(__file__), "../..",
"archive/experimental-shim-probes/rrc_equation_classifier_receipt.json",
)
OUTPUT_FILE = os.path.join(
os.path.dirname(__file__), "../..",
"shared-data/rrc_pist_predictions_250_v1.json",
)
# ═══════════════════════════════════════════════════════════════════════
# §1 PARSING
# ═══════════════════════════════════════════════════════════════════════
def load_equations() -> tuple[list[dict], int]:
"""Load equations grouped by invariant_receipt.object_id.
Returns:
(equations, total_source_records)
equations — one per unique object_id with provenance fields
total_source_records — count of all compiled_equations entries
Representative selection (deterministic):
Within each object_id group, the record with the lexicographically
smallest ``equation_record.equation_id`` supplies the name and
equation text for tokenization. All records are captured in
``source_records`` for auditability.
"""
with open(RECEIPT_JSON) as f:
d = json.load(f)
raw = d.get("compiled_equations", [])
groups = defaultdict(list)
for idx, eq in enumerate(raw):
er = eq.get("equation_record", {})
ir = eq.get("invariant_receipt", {})
oid = ir.get("object_id", "")
if not oid:
continue
groups[oid].append({
"index": idx,
"equation_id": er.get("equation_id", ""),
"name": er.get("name", ""),
"equation_text": er.get("equation", ""),
"shape": ir.get("shape", "HoldForUnlawfulOrUnderspecifiedShape"),
"status": ir.get("status", "HOLD"),
})
equations = []
for oid, records in sorted(groups.items()):
# Deterministic representative: lexicographically smallest equation_id
rep = min(records, key=lambda r: r["equation_id"])
equations.append({
"equation_id": oid,
"name": rep["name"],
"equation_text": rep["equation_text"],
"shape": rep["shape"],
"status": rep["status"],
"source_records": [
{"equation_id": r["equation_id"], "name": r["name"]}
for r in sorted(records, key=lambda r: r["equation_id"])
],
})
return equations, len(raw)
def tokenize(text: str) -> list[str]:
"""Split text into tokens.
Normalization rules (versioned, deterministic):
1. Replace '-' with '_'
2. Split on '_' and ':'
3. Drop empty strings
Token source: ``equation_record.name`` (the human-readable equation title).
The full equation text is preserved in ``equation_record.equation`` but is
not the tokenization source.
"""
normalized = text.replace("-", "_")
return [p for p in re.split(r"[_:]", normalized) if p]
# ═══════════════════════════════════════════════════════════════════════
# §2 VOCABULARY
# ═══════════════════════════════════════════════════════════════════════
def build_global_vocabulary(equations: list[dict]) -> dict[str, int]:
"""Build global token→index mapping (sorted, deterministic)."""
all_tokens = set()
for eq in equations:
for t in tokenize(eq["name"]):
all_tokens.add(t)
sorted_tokens = sorted(all_tokens)
return {t: i for i, t in enumerate(sorted_tokens)}
def global_vocab_hash(vocab: dict[str, int]) -> str:
"""SHA256 of ``|``-joined sorted vocabulary tokens."""
joined = "|".join(vocab.keys())
return hashlib.sha256(joined.encode("utf-8")).hexdigest()
# ═══════════════════════════════════════════════════════════════════════
# §3 STRAND ADJACENCY MATRIX
# ═══════════════════════════════════════════════════════════════════════
def strand_for_token(token: str, vocab: dict[str, int]) -> int:
"""Map token to strand index via global vocabulary position (mod 8)."""
return vocab[token] % N_STRANDS
def build_adjacency_matrix(tokens: list[str], vocab: dict[str, int]) -> list[list[int]]:
"""Build 8×8 adjacency matrix from token ordering.
For each adjacent pair (t_i, t_{i+1}) in the original token sequence,
increment ``M[strand(t_i)][strand(t_{i+1})]`` by 1.
No symmetrization, no operator projection, no diagonal self-crossings.
"""
M = [[0] * N_STRANDS for _ in range(N_STRANDS)]
strands = [strand_for_token(t, vocab) for t in tokens]
for i in range(len(strands) - 1):
s1, s2 = strands[i], strands[i + 1]
M[s1][s2] += 1
return M
# ═══════════════════════════════════════════════════════════════════════
# §4 HERMITE POLYNOMIAL KERNEL (RRC sieve refinement)
#
# The generalized bilinear generating function (Mehler kernel) lifts the
# RRC similarity metric from linear (bigram frequency) to polynomial:
#
# K_ij = H_{p,q}(M[i·], M[j·] | ρ)
#
# where M[i·] is the i-th row of the adjacency matrix, H_{p,q} is the
# generalized HermiteKampé de Fériet polynomial (Giani et al. 2025, Lemma 1),
# and ρ ∈ (0,1) is a coupling parameter.
#
# For p=q=0 (standard Mehler):
# K_ij = exp(2·M[i]·M[j]·ρ - (|M[i]|²+|M[j]|²)·ρ²) / √(1-ρ²)
#
# This is the simplest non-trivial kernel and already captures algebraic
# variety structure beyond linear adjacency.
# ═══════════════════════════════════════════════════════════════════════
_HERMITE_RHO: float = 0.5 # coupling parameter (tunable, 0 < ρ < 1)
def _dot(a: list[int], b: list[int]) -> int:
"""Dot product of two integer vectors."""
return sum(x * y for x, y in zip(a, b))
def _norm2(a: list[int]) -> int:
"""Squared Euclidean norm of integer vector."""
return sum(x * x for x in a)
def mehler_kernel_row(i: int, M: list[list[int]], rho: float) -> list[float]:
"""Compute Mehler kernel row K[i·] for adjacency matrix M.
K_ij = exp(2·M[i]·M[j]·ρ - (|M[i]|²+|M[j]|²)·ρ²) / √(1-ρ²)
This is the generalized bilinear generating function G from
Giani et al. (2025) eq. (5) with p=q=0, x=M[i], y=M[j].
"""
import math
n = len(M)
row_i = M[i]
norm_i = _norm2(row_i)
denom = math.sqrt(1.0 - rho * rho)
result = []
for j in range(n):
row_j = M[j]
inner = 2.0 * _dot(row_i, row_j) * rho - (norm_i + _norm2(row_j)) * rho * rho
result.append(math.exp(inner) / denom)
return result
def build_hermite_kernel(M: list[list[int]]) -> list[list[float]]:
"""Build full 8×8 Hermite kernel matrix from adjacency M.
Returns K[i][j] = mehler_kernel_row(i, M, _HERMITE_RHO)[j]
"""
n = len(M)
return [mehler_kernel_row(i, M, _HERMITE_RHO) for i in range(n)]
def canonical_hermite_json(K: list[list[float]]) -> str:
"""Rowmajor JSON, fixed 8×8 nesting, scientific notation for floats."""
return json.dumps(K, separators=(",", ":"))
def hermite_hash(K: list[list[float]]) -> str:
return hashlib.sha256(canonical_hermite_json(K).encode("utf-8")).hexdigest()
# ═══════════════════════════════════════════════════════════════════════
# §5 HASHING
# ═══════════════════════════════════════════════════════════════════════
def canonical_matrix_json(M: list[list[int]]) -> str:
"""Rowmajor JSON, integers only, no whitespace, fixed 8×8 nesting."""
return json.dumps(M, separators=(",", ":"))
def matrix_hash(M: list[list[int]]) -> str:
return hashlib.sha256(canonical_matrix_json(M).encode("utf-8")).hexdigest()
# ═══════════════════════════════════════════════════════════════════════
# §5 MAIN
# ═══════════════════════════════════════════════════════════════════════
def main() -> int:
equations, total_source_records = load_equations()
print(f"Loaded {len(equations)} unique equations "
f"(from {total_source_records} source records)", flush=True)
vocab = build_global_vocabulary(equations)
gvh = global_vocab_hash(vocab)
print(f"Global vocabulary: {len(vocab)} unique tokens hash={gvh}", flush=True)
predictions = []
hash_counts = {}
hermite_hash_counts = {}
for eq in equations:
tokens = tokenize(eq["name"])
M = build_adjacency_matrix(tokens, vocab)
K = build_hermite_kernel(M)
mh = matrix_hash(M)
kh = hermite_hash(K)
hash_counts[mh] = hash_counts.get(mh, 0) + 1
hermite_hash_counts[kh] = hermite_hash_counts.get(kh, 0) + 1
predictions.append({
"equation_id": eq["equation_id"],
"proxy_pred": None,
"exact_pred": None,
"matrix_hash": mh,
"matrix_8x8": M,
"hermite_kernel_hash": kh,
"hermite_kernel_8x8": K,
"hermite_kernel_rho": _HERMITE_RHO,
"source_records": eq["source_records"],
"notes": "generated from equation_record.equation_id token adjacency; "
"hermite kernel via generalized bilinear generating function (ρ=0.5)",
})
n_unique_adj = len(hash_counts)
n_unique_hermite = len(hermite_hash_counts)
n_total = len(predictions)
n_collisions_adj = sum(1 for c in hash_counts.values() if c > 1)
n_collisions_hermite = sum(1 for c in hermite_hash_counts.values() if c > 1)
print(f"Matrix hash collisions (adjacency): {n_collisions_adj} groups "
f"({n_unique_adj} unique / {n_total} total)", flush=True)
print(f"Matrix hash collisions (hermite): {n_collisions_hermite} groups "
f"({n_unique_hermite} unique / {n_total} total)", flush=True)
if n_collisions_hermite < n_collisions_adj:
print("Hermite kernel DISAMBIGUATES more equations than raw adjacency: "
"the algebraic variety metric is strictly finer.", flush=True)
elif n_collisions_hermite == n_collisions_adj:
print("Hermite kernel matches adjacency resolution at ρ={}.".format(_HERMITE_RHO),
flush=True)
else:
print("Hermite kernel merges some distinct equations — "
"consider tuning ρ.", flush=True)
artifact = {
"schema": "rrc_pist_predictions_250_v1",
"claim_boundary": "matrix-only;hermite-kernel-added;no-classifier;no-lean-spectral",
"matrix_schema": "token_strand_adjacency_8x8_v1",
"hermite_kernel_schema": "mehler_kernel_8x8_v1_generalized_bilinear_generating_function",
"hermite_kernel_rho": _HERMITE_RHO,
"global_vocab_hash": gvh,
"summary": {
"total_source_records": total_source_records,
"unique_equation_ids": n_total,
"unique_matrix_hashes": n_unique_adj,
"unique_hermite_hashes": n_unique_hermite,
"hermite_disambiguation": n_collisions_adj - n_collisions_hermite,
},
"predictions": predictions,
}
with open(OUTPUT_FILE, "w") as f:
json.dump(artifact, f, indent=2, sort_keys=True)
print(f"\nWrote {OUTPUT_FILE}", flush=True)
return 0
if __name__ == "__main__":
sys.exit(main())