Block-TT 3D Neural Operator / run_bench.py
Unverified
1import os, sys, json
2import numpy as np
3import torch
4import torch.nn as nn
5
6sys.path.insert(0, "/home/maxwelhelp/all/math2nn")
7from bench import train_model, evaluate, sweep_baseline, make_report, count_params
8from custom_track import get_dataset, META
9
10
11def seed_all(seed):
12 np.random.seed(seed)
13 torch.manual_seed(seed)
14 if torch.cuda.is_available():
15 try:
16 torch.cuda.manual_seed_all(seed)
17 except Exception:
18 pass
19
20
21class Dense3D(nn.Module):
22 def __init__(self):
23 super().__init__()
24 self.op = nn.Linear(64, 64, bias=False)
25 self.head = nn.Sequential(nn.ReLU(), nn.Linear(64, 1))
26 nn.init.xavier_uniform_(self.op.weight)
27
28 def forward(self, x):
29 return self.head(self.op(x.reshape(x.shape[0], 64)))
30
31
32class TT3D(nn.Module):
33 """Learned TT-matrix over the three 4-way spatial modes."""
34 def __init__(self, rank=2):
35 super().__init__()
36 self.rank = rank
37 self.cores = nn.ParameterList([
38 nn.Parameter(torch.randn(1, 4, 4, rank) * 0.16),
39 nn.Parameter(torch.randn(rank, 4, 4, rank) * 0.16),
40 nn.Parameter(torch.randn(rank, 4, 4, 1) * 0.16),
41 ])
42 self.head = nn.Sequential(nn.ReLU(), nn.Linear(64, 1))
43
44 def forward(self, x):
45 g1, g2, g3 = self.cores
46 # x[b,i,j,k], g1[a,l,i,c], g2[c,n,j,d], g3[d,o,k,f].
47 y = torch.einsum("bijk,alic,cnjd,dokf->blno", x, g1, g2, g3)
48 return self.head(y.reshape(x.shape[0], 64))
49
50
51def train_one(kind, seed, lr, epochs, rank=2):
52 seed_all(seed)
53 d0 = get_dataset(seed, 400, 100)
54 d = {**d0, **{k: torch.as_tensor(d0[k], dtype=torch.float32) for k in ("xtr", "ytr", "xte", "yte")}}
55 model = Dense3D() if kind == "dense" else TT3D(rank)
56 net, metric, _ = train_model(model, d, epochs=epochs, lr=lr, batch=128,
57 weight_decay=0.0, log=lambda *_: None)
58 if net is None:
59 return float("nan"), None
60 return float(metric), net
61
62
63def main():
64 # The union of step sizes is identical for baseline and idea.
65 grid = [{"lr": 1e-3}, {"lr": 3e-3}, {"lr": 1e-2}]
66 epochs = 18
67
68 def baseline_factory(cfg):
69 return lambda s: train_one("dense", int(s), cfg["lr"], epochs)[0]
70
71 base = sweep_baseline(baseline_factory, grid)
72 idea_trials = []
73 for cfg in grid:
74 r = evaluate(lambda s, c=cfg: train_one("tt", int(s), c["lr"], epochs, rank=2)[0])
75 idea_trials.append({"cfg": cfg, **r})
76 best_idea = min(idea_trials, key=lambda r: r["mean"])
77
78 # Signature is measured on trained models and test inputs.
79 best_lr = base["best_cfg"]["lr"]
80 _, bnet = train_one("dense", 0, best_lr, epochs)
81 _, inet = train_one("tt", 0, best_idea["cfg"]["lr"], epochs, rank=2)
82 d = get_dataset(0, 400, 100)
83 xb = d["xte"]
84 with torch.no_grad():
85 dense_out = bnet.op(xb.reshape(-1, 64)).numpy()
86 g1, g2, g3 = [g.detach() for g in inet.cores]
87 tt_out = torch.einsum("bijk,alic,cnjd,dokf->blno", xb, g1, g2, g3)
88 tt_out = tt_out.reshape(-1, 64).numpy()
89 sig = {
90 "quantity": "trained operator output norm ratio on held-out PDE fields",
91 "predicted": "TT has fewer parameters than the dense spatial map",
92 "observed": {
93 "dense_params": count_params(bnet),
94 "tt_params": count_params(inet),
95 "operator_output_norm_ratio": float(np.linalg.norm(tt_out) / (np.linalg.norm(dense_out) + 1e-12)),
96 },
97 "confirmed": bool(count_params(inet) < count_params(bnet)),
98 }
99 idea = {"best_cfg": best_idea["cfg"], "trials": idea_trials,
100 "mean": best_idea["mean"], "std": best_idea["std"],
101 "per_seed": best_idea["per_seed"], "n": best_idea["n"]}
102 rep = make_report("separable_diffusion_field", "custom_dense_vs_tt3d", base, idea, sig)
103 rep["custom_track"] = {"name": META["name"], "file": "custom_track.py", "domain": META["domain"]}
104 rep["parameter_counts"] = {"baseline": count_params(bnet), "idea": count_params(inet)}
105 with open("bench_report.json", "w") as f:
106 json.dump(rep, f, indent=2)
107 print(json.dumps(rep, indent=2))
108
109
110if __name__ == "__main__":
111 main()