Wittrick–Williams Mode Enumerator / run_bench.py
Unverified
1import sys, json, random
2import numpy as np
3import torch
4from scipy.linalg import eigh
5sys.path.insert(0, '/home/maxwelhelp/all/math2nn')
6from bench import make_model, sweep_baseline, evaluate, make_report
7from custom_beam_track import get_dataset, matrices
8
9SEEDS = tuple(range(8))
10GRID = [
11 {'lr': 1e-3, 'epochs': 30},
12 {'lr': 3e-3, 'epochs': 30},
13 {'lr': 1e-3, 'epochs': 50},
14]
15TARGET = 3 # one-based mode index
16
17
18def seed_all(s):
19 random.seed(s); np.random.seed(s); torch.manual_seed(s)
20
21
22def ww_bracket(scale, target=TARGET, tol=2e-4):
23 K, M = matrices(float(scale))
24 def inertia(w):
25 A = K - (w*w)*M
26 ev = np.linalg.eigvalsh(A)
27 return int(np.sum(ev < -1e-10 * max(1., np.max(np.abs(A)))))
28 exact = np.sqrt(np.maximum(eigh(K, M, eigvals_only=True), 0.0))
29 upper = float(exact[-1] * 1.05); lo = 0.0
30 grid = np.linspace(0.0, upper, 180)
31 for a, b in zip(grid[:-1], grid[1:]):
32 if inertia(a) == target-1 and inertia(b) >= target:
33 lo, upper = float(a), float(b); break
34 while upper - lo > tol:
35 mid = (lo + upper) / 2
36 if inertia(mid) < target: lo = mid
37 else: upper = mid
38 return lo, upper, exact
39
40
41def as_tensors(d):
42 return (torch.tensor(d['xtr'], dtype=torch.float32),
43 torch.tensor(d['ytr'], dtype=torch.float32),
44 torch.tensor(d['xte'], dtype=torch.float32),
45 torch.tensor(d['yte'], dtype=torch.float32))
46
47
48def train_one(seed, cfg, idea=False, inspect=False):
49 seed_all(seed)
50 d = get_dataset(seed, 400, 200)
51 xtr, ytr, xte, yte = as_tensors(d)
52 # Preprocessing is deterministic and independent of the trained model.
53 brackets = np.asarray([ww_bracket(x[0])[0:2] for x in xtr.numpy()], np.float32)
54 test_brackets = np.asarray([ww_bracket(x[0])[0:2] for x in xte.numpy()], np.float32)
55 device = 'cuda' if torch.cuda.is_available() else 'cpu'
56 try:
57 net = make_model('mlp_tiny', (1,), 1).to(device)
58 opt = torch.optim.Adam(net.parameters(), lr=cfg['lr'])
59 xx, yy = xtr.to(device), ytr.to(device)
60 bb = torch.tensor(brackets, device=device)
61 for epoch in range(cfg['epochs']):
62 opt.zero_grad(set_to_none=True)
63 pred = net(xx)
64 loss = torch.mean((pred - yy)**2)
65 if idea:
66 # Same MLP and supervised task; only WW certified box loss differs.
67 low, high = bb[:, :1], bb[:, 1:]
68 box = torch.relu(low-pred)**2 + torch.relu(pred-high)**2
69 loss = loss + cfg.get('box_weight', 100.0) * box.mean()
70 loss.backward(); opt.step()
71 with torch.no_grad():
72 pred = net(xte.to(device)).cpu().numpy().reshape(-1)
73 metric = float(np.mean((pred - yte.numpy().reshape(-1))**2))
74 if inspect:
75 inside = ((pred >= test_brackets[:,0]) & (pred <= test_brackets[:,1]))
76 return metric, {'pred': pred, 'inside': inside, 'brackets': test_brackets}
77 return metric
78 except Exception:
79 if device == 'cuda':
80 torch.cuda.empty_cache()
81 torch.set_default_device('cpu')
82 return train_one(seed, cfg, idea, inspect)
83 raise
84
85
86def main():
87 # Baseline sweep uses exactly the same union of learning rates/epochs as idea.
88 base = sweep_baseline(lambda cfg: (lambda s: train_one(s, cfg, False)), GRID)
89 best = base['best_cfg']
90 idea_grid = [dict(c, box_weight=100.0) for c in GRID]
91 # The idea sweep is intentionally three nearby settings; baseline evaluated all shared lr/epoch settings.
92 idea_runs = []
93 for cfg in idea_grid:
94 r = evaluate(lambda s, c=cfg: train_one(s, c, True))
95 idea_runs.append({'cfg': cfg, 'result': r})
96 idea_block = min(idea_runs, key=lambda z: z['result']['mean'])
97 idea = idea_block['result']
98
99 # Signature is measured on predictions of the selected trained idea models, not analytically.
100 sigs=[]
101 for s in SEEDS:
102 _, info = train_one(s, idea_block['cfg'], True, True)
103 sigs.append(float(np.mean(info['inside'])))
104 # Stage-1 prediction: certified box should prevent frequency leaving its mode interval.
105 signature = {
106 'prediction': 'WW box constraint keeps predicted frequency inside the one-mode certified bracket',
107 'predicted_inside_rate': 1.0,
108 'observed_inside_rate_mean': float(np.mean(sigs)),
109 'observed_inside_rate_per_seed': sigs,
110 'confirmed': bool(np.mean(sigs) >= 0.95)
111 }
112 report = make_report('custom_beam_eigenfrequency', 'mlp_tiny', base, idea,
113 {'custom_track': {'name':'beam_eigenfrequency','file':'custom_beam_track.py','domain':'structural_eigenproblem'},
114 'idea_sweep': idea_runs, 'mechanism_signature': signature})
115 with open('bench_report.json','w') as f: json.dump(report, f, indent=2)
116 print(json.dumps(report, indent=2))
117
118if __name__ == '__main__': main()