Smith-normal-form Cayley positional encoding / stage2_bench.py
Beats tuned baseline
1import json
2import sys
3from pathlib import Path
4import numpy as np
5import torch
6
7sys.path.insert(0, "/home/maxwelhelp/all/math2nn")
8from bench import make_model, train_model, evaluate, sweep_baseline, make_report
9import cayley_graph_track as track
10
11SEEDS = tuple(range(8))
12NTRAIN, NTEST = 400, 100
13EPOCHS, BATCH = 18, 128
14# Shared union: both systems are evaluated at every learning rate.
15GRID = [{"lr": 1e-3}, {"lr": 3e-3}, {"lr": 1e-2}]
16
17
18def ds_torch(seed, kind):
19 raw = track.get_dataset(seed, NTRAIN, NTEST)
20 d = track.encode(raw, kind)
21 return {**d,
22 "xtr": torch.tensor(d["xtr"], dtype=torch.float32),
23 "ytr": torch.tensor(d["ytr"], dtype=torch.float32),
24 "xte": torch.tensor(d["xte"], dtype=torch.float32),
25 "yte": torch.tensor(d["yte"], dtype=torch.float32)}
26
27
28def train_one(seed, kind, cfg, return_model=False):
29 torch.manual_seed(10000 + int(seed))
30 np.random.seed(10000 + int(seed))
31 d = ds_torch(seed, kind)
32 net = make_model("mlp_tiny", d["input_shape"], d["out_dim"])
33 model, metric, history = train_model(net, d, epochs=EPOCHS,
34 lr=float(cfg["lr"]), batch=BATCH,
35 log=lambda *_: None)
36 if return_model:
37 return model, metric, d
38 return float(metric)
39
40
41def baseline_factory(cfg):
42 return lambda seed: train_one(seed, "baseline", cfg)
43
44
45def idea_factory(cfg):
46 return lambda seed: train_one(seed, "idea", cfg)
47
48
49def signature(cfg):
50 """Measure the trained models, not an analytic proxy.
51 A simultaneous cyclic shift of both endpoints must preserve g_uv."""
52 raw = track.get_dataset(0, NTRAIN, NTEST)
53 n = raw["n_nodes"]
54 shift = 7
55 shifted = dict(raw)
56 shifted["xte"] = (np.asarray(raw["xte"]) + shift) % n
57 vals = {}
58 for kind in ("baseline", "idea"):
59 model, _, d = train_one(0, kind, cfg, return_model=True)
60 if model is None:
61 vals[kind] = float("nan")
62 continue
63 model.eval()
64 a = track.encode(raw, kind)
65 b = track.encode(shifted, kind)
66 device = next(model.parameters()).device
67 with torch.no_grad():
68 pa = model(torch.tensor(a["xte"], dtype=torch.float32, device=device)).cpu()
69 pb = model(torch.tensor(b["xte"], dtype=torch.float32, device=device)).cpu()
70 vals[kind] = float(torch.abs(pa - pb).mean())
71 confirmed = bool(np.isfinite(vals["idea"]) and vals["idea"] < 0.25 * max(vals["baseline"], 1e-12))
72 return {"prediction": "relative group differences are invariant to simultaneous vertex translation",
73 "baseline_mean_output_change": vals["baseline"],
74 "idea_mean_output_change": vals["idea"],
75 "predicted_idea_change": 0.0, "confirmed": confirmed}
76
77
78def main():
79 math = track.math_check()
80 assert math["path_independence"] and math["cycle_sum_mod_n"] == 0
81 base = sweep_baseline(baseline_factory, GRID, seeds=SEEDS)
82 idea_trials = []
83 for cfg in GRID:
84 r = evaluate(idea_factory(cfg), seeds=SEEDS)
85 idea_trials.append({"cfg": cfg, "mean": r["mean"], "per_seed": r["per_seed"]})
86 best_cfg = min(idea_trials, key=lambda x: x["mean"])["cfg"]
87 idea_full = evaluate(idea_factory(best_cfg), seeds=SEEDS)
88 idea_full["best_cfg"] = best_cfg
89 idea_full["sweep"] = [{"cfg": x["cfg"], "mean": x["mean"]} for x in idea_trials]
90 rep = make_report("cayley_cycle_distance", "mlp_tiny", base, idea_full,
91 {"mechanism_signature": signature(best_cfg),
92 "custom_track": {"name": track.META["name"], "file": "cayley_graph_track.py", "domain": track.META["domain"]},
93 "math_check": math,
94 "protocol_notes": {"epochs": EPOCHS, "batch": BATCH, "paired_seeds": list(SEEDS), "grid_union": GRID,
95 "structure_justification": "The custom task is a cyclic Cayley graph distance prediction problem, directly containing generator increments, cycle constraints, and relative group displacements."}})
96 Path("bench_report.json").write_text(json.dumps(rep, indent=2, sort_keys=True))
97 print(json.dumps(rep, indent=2, sort_keys=True))
98
99
100if __name__ == "__main__":
101 main()