Research-Stack/4-Infrastructure/shim/tang9k_routed_template_witness.py
2026-05-11 22:18:31 -05:00

130 lines
5.1 KiB
Python

#!/usr/bin/env python3
"""Witness routed math templates through the Tang9K symbol surface.
This converts top entries from eigen_solved_math_router.py into short
Surface-0 payloads. The FPGA still only witnesses substitution/hash/counts;
the host owns the full template ranking and codec logic.
"""
from __future__ import annotations
import argparse
import importlib.util
import json
from pathlib import Path
from typing import Any
DEFAULT_ROUTER = Path("4-Infrastructure/shim/eigen_solved_math_router_compression.json")
SURFACE_PATH = Path("4-Infrastructure/shim/tang9k_hutter_symbol_surface.py")
def load_surface_module():
spec = importlib.util.spec_from_file_location("tang9k_hutter_symbol_surface", SURFACE_PATH)
if spec is None or spec.loader is None:
raise RuntimeError(f"cannot load {SURFACE_PATH}")
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
def template_payload(entry: dict[str, Any], max_len: int = 16) -> bytes:
name = str(entry.get("model_name") or entry.get("family") or "template")
# Keep a readable bank-local token: uppercase, underscores become spaces,
# punctuation drops. This intentionally targets the tiny hardware LUT.
normalized = []
for ch in name.upper().replace("_", " "):
if ch.isalnum() or ch == " ":
normalized.append(ch)
token = "".join(normalized).strip() or "TEMPLATE"
return token.encode("ascii", "ignore")[:max_len]
def witness_entry(surface, entry: dict[str, Any], seq: int, port: str | None, baud: int, retries: int) -> dict[str, Any]:
payload = template_payload(entry)
frame = surface.build_frame(seq, payload)
expected = surface.receipt_for_payload(payload)
result = {
"schema": "tang9k_routed_template_witness_v1",
"model_name": entry.get("model_name"),
"family": entry.get("family"),
"evidence_tier": entry.get("evidence_tier"),
"routed_score": entry.get("routed_score"),
"payload_ascii": payload.decode("ascii", "replace"),
"payload_hex": payload.hex(),
"frame_hex": frame.hex(),
"expected": expected,
"expected_led_reservoir": surface.led_reservoir_address(
surface.pbacs_cmyk_state_for_payload(payload), expected["mapped_count"]
),
}
if port:
raw = surface.send_serial(port, baud, frame, retries=retries)
result["hardware_receipt_hex"] = raw.hex()
if raw:
try:
parsed = surface.parse_receipt(raw)
result["hardware_receipt"] = parsed
result["hardware_led_reservoir"] = surface.led_reservoir_address(
surface.pbacs_cmyk_state_for_payload(payload), parsed["mapped_count"]
)
result["hardware_matches_expected"] = (
parsed["status"] == 0
and parsed["hash16"] == expected["hash16"]
and parsed["mapped_count"] == expected["mapped_count"]
and parsed["literal_count"] == expected["literal_count"]
)
except ValueError as exc:
result["hardware_receipt"] = None
result["hardware_matches_expected"] = False
result["hardware_note"] = str(exc)
else:
result["hardware_receipt"] = None
result["hardware_matches_expected"] = False
result["hardware_note"] = "no UART receipt received"
return result
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--router-json", type=Path, default=DEFAULT_ROUTER)
parser.add_argument("--limit", type=int, default=5)
parser.add_argument("--seq-base", type=lambda value: int(value, 0), default=0xE0)
parser.add_argument("--port")
parser.add_argument("--baud", type=int, default=115200)
parser.add_argument("--retries", type=int, default=5)
parser.add_argument("--out", type=Path)
args = parser.parse_args()
router = json.loads(args.router_json.read_text(encoding="utf-8"))
surface = load_surface_module()
witnesses = []
for index, entry in enumerate(router.get("entries", [])[: args.limit]):
witnesses.append(
witness_entry(
surface=surface,
entry=entry,
seq=(args.seq_base + index) & 0xFF,
port=args.port,
baud=args.baud,
retries=args.retries,
)
)
bundle = {
"schema": "tang9k_routed_template_witness_bundle_v1",
"claim_boundary": "FPGA witnesses compact template tokens only; ranking and model validity remain host-side.",
"router_json": str(args.router_json),
"witness_count": len(witnesses),
"hardware_count": sum(1 for item in witnesses if item.get("hardware_matches_expected")),
"witnesses": witnesses,
}
text = json.dumps(bundle, indent=2, ensure_ascii=False)
if args.out:
args.out.parent.mkdir(parents=True, exist_ok=True)
args.out.write_text(text + "\n", encoding="utf-8")
print(text)
return 0
if __name__ == "__main__":
raise SystemExit(main())