Nullspace-coordinate constrained operator blocks / bench_stage2.py
Mechanism confirmed, baseline not beaten
1import json, random, sys
2import numpy as np
3import torch
4
5sys.path.insert(0, '/home/maxwelhelp/all/math2nn')
6from bench import get_dataset, make_model, train_model, sweep_baseline, evaluate, make_report
7
8TRACK = 'poisson_dirichlet'
9MODEL = 'mlp_tiny'
10SEEDS = tuple(range(8))
11EPOCHS = 20
12BATCH = 128
13# Shared union: every idea LR is also tested by baseline.
14GRID = [
15 {'lr': 1e-3, 'weight_decay': 0.0},
16 {'lr': 3e-3, 'weight_decay': 0.0},
17 {'lr': 1e-2, 'weight_decay': 0.0},
18]
19
20
21def seed_all(seed):
22 random.seed(seed)
23 np.random.seed(seed)
24 torch.manual_seed(seed)
25 if torch.cuda.is_available():
26 torch.cuda.manual_seed_all(seed)
27
28
29
30def basis_matrix():
31 n_grid, n_modes = 32, 12
32 x = np.arange(1, n_grid + 1, dtype=np.float32) / (n_grid + 1)
33 return (np.sqrt(2.0) * np.sin(np.pi * np.outer(
34 x, np.arange(1, n_modes + 1)))).astype(np.float32)
35
36def get_ds(seed):
37 d = get_dataset(TRACK, seed=int(seed), n_train=400, n_test=400)
38 # bench flattens custom-track y arrays; restore one field per sample.
39 nout = int(d['out_dim'])
40 for k in ('xtr', 'xte'):
41 d[k] = torch.as_tensor(d[k], dtype=torch.float32)
42 for k, n in (('ytr', len(d['xtr'])), ('yte', len(d['xte']))):
43 a = np.asarray(d[k]).reshape(n, nout)
44 d[k] = torch.as_tensor(a, dtype=torch.float32)
45 return d
46
47
48class NullspaceModel(torch.nn.Module):
49 def __init__(self, core, basis):
50 super().__init__()
51 self.core = core
52 self.register_buffer('V', torch.as_tensor(basis, dtype=torch.float32))
53
54 def forward(self, x):
55 # core predicts z; decoder always lies in ker(C).
56 interior = self.core(x) @ self.V.T
57 z = torch.zeros((interior.shape[0], 1), device=interior.device, dtype=interior.dtype)
58 return torch.cat((z, interior, z), dim=1)
59
60
61def fit(seed, cfg, constrained, return_model=False):
62 seed_all(seed)
63 d = get_ds(seed)
64 # Baseline predicts the full field, idea predicts the 12 nullspace coordinates.
65 out_dim = int(basis_matrix().shape[1]) if constrained else int(d['out_dim'])
66 core = make_model(MODEL, tuple(d['xtr'].shape[1:]), out_dim)
67 net = NullspaceModel(core, basis_matrix()) if constrained else core
68 net, metric, history = train_model(
69 net, d, epochs=EPOCHS, lr=float(cfg['lr']), batch=BATCH,
70 weight_decay=float(cfg['weight_decay']), log=lambda *_: None)
71 if net is None or metric is None:
72 return (float('inf'), None, d)
73 return (float(metric), net if return_model else None, d)
74
75
76def make_fn(constrained):
77 def fn(cfg):
78 return lambda seed: fit(seed, cfg, constrained)[0]
79 return fn
80
81
82def run():
83 # Baseline sweep uses four seeds as specified, then re-evaluates best on eight.
84 base = sweep_baseline(make_fn(False), GRID, seeds=(0, 1, 2, 3))
85 # Explicitly run the idea at all three shared settings; report best using the
86 # same four-seed selection budget, then evaluate its selected config on 8.
87 idea_trials = []
88 best_cfg, best_mean = None, float('inf')
89 for cfg in GRID:
90 r = evaluate(make_fn(True)(cfg), seeds=(0, 1, 2, 3))
91 idea_trials.append({'cfg': cfg, 'mean': r['mean']})
92 if r['mean'] < best_mean:
93 best_cfg, best_mean = cfg, r['mean']
94 idea_full = evaluate(make_fn(True)(best_cfg), seeds=SEEDS)
95 base['idea_union_trials'] = idea_trials
96
97 # Signature is measured from trained systems: boundary residual and decoded
98 # nullspace violation on actual held-out inputs. C selects endpoints.
99 probe_seed = 0
100 bmetric, bnet, bd = fit(probe_seed, base['best_cfg'], False, True)
101 imetric, inet, idata = fit(probe_seed, best_cfg, True, True)
102 C = torch.zeros((2, 34), dtype=torch.float32)
103 C[0, 0] = 1.0; C[1, -1] = 1.0
104 with torch.no_grad():
105 db = next(bnet.parameters()).device
106 di = next(inet.parameters()).device
107 ub = bnet(idata['xte'].to(db)).detach().cpu()
108 ui = inet(idata['xte'].to(di)).detach().cpu()
109 rb = torch.linalg.norm(ub @ C.T, dim=1)
110 ri = torch.linalg.norm(ui @ C.T, dim=1)
111 signature = {
112 'prediction': 'nullspace decoder should make C u exactly zero while ambient output violates endpoints',
113 'predicted_boundary_residual_idea': 0.0,
114 'observed_boundary_residual_idea_max': float(ri.max()),
115 'observed_boundary_residual_baseline_max': float(rb.max()),
116 'observed_boundary_residual_baseline_mean': float(rb.mean()),
117 'observed_boundary_residual_idea_mean': float(ri.mean()),
118 'ratio_baseline_to_idea_max': float((rb.max() + 1e-12) / (ri.max() + 1e-12)),
119 'confirmed': bool(float(ri.max()) < 1e-5 and float(rb.max()) > 1e-3),
120 'trained_models': True,
121 }
122 report = make_report(TRACK, MODEL, base, idea_full, {
123 'selection': 'best of shared 3-point LR/weight-decay grid on seeds 0-3',
124 'idea_best_cfg': best_cfg,
125 **signature,
126 })
127 report['custom_track'] = {
128 'name': 'poisson_dirichlet',
129 'file': '/home/maxwelhelp/all/math2nn/bench/custom_tracks/poisson_dirichlet.py',
130 'domain': 'pde',
131 'reason': 'built-in custom PDE track has fixed-grid field outputs and homogeneous Dirichlet constraints',
132 }
133 report['protocol'] = {'epochs': EPOCHS, 'batch': BATCH, 'seeds': list(SEEDS), 'grid': GRID}
134 with open('bench_report.json', 'w') as f:
135 json.dump(report, f, indent=2)
136 print(json.dumps(report, indent=2))
137
138
139if __name__ == '__main__':
140 run()