Harmonic Global Latent Channels / run_bench.py
Mechanism confirmed, baseline not beaten
1import json, random
2import numpy as np
3import torch
4from torch import nn
5
6import sys
7sys.path.insert(0, '/home/maxwelhelp/all/math2nn')
8from bench import get_dataset, train_model, evaluate, sweep_baseline, make_report, compare_results, count_params
9import bench.data as bench_data
10import custom_harmonic_track
11from custom_harmonic_track import harmonic_basis
12
13TRACK = 'harmonic_cycle_circulation'
14# Register the local custom module in-memory; bench itself remains read-only.
15_existing = bench_data.custom_tracks()
16_existing[TRACK] = custom_harmonic_track
17bench_data._CUSTOM_CACHE = _existing
18SEEDS = tuple(range(8))
19LRS = (1e-3, 3e-3, 1e-2)
20EPOCHS = 24
21BATCH = 64
22
23
24def math_check():
25 B, M, H = harmonic_basis()
26 closed = np.linalg.norm(np.zeros((0, B.shape[1])) @ H)
27 coclosed = np.linalg.norm(B.T @ M @ H)
28 ortho = float((H.T @ M @ H)[0, 0])
29 rng = np.random.RandomState(123)
30 x = rng.normal(size=(24, 20))
31 c = (H.T @ M @ x).reshape(-1)
32 u = x - H @ c[None, :]
33 gauge = np.max(np.abs(H.T @ M @ u))
34 recon = np.max(np.abs(x - (u + H @ c[None, :])))
35 period = np.max(np.abs((H.T @ M) @ H - np.ones((1, 1))))
36 return {'closed_residual': float(closed), 'coclosed_residual': float(coclosed),
37 'weighted_orthonormality': ortho, 'max_gauge_residual': float(gauge),
38 'max_reconstruction_residual': float(recon), 'period_basis_residual': float(period)}
39
40
41class RingNet(nn.Module):
42 def __init__(self, n=24, width=32, harmonic=False, H=None, M=None):
43 super().__init__()
44 self.harmonic = harmonic
45 self.inp = nn.Linear(1, width)
46 self.layers = nn.ModuleList([nn.Linear(width, width) for _ in range(4)])
47 self.local = nn.Linear(width * 2, 1)
48 if harmonic:
49 self.coeff = nn.Sequential(nn.Linear(width, width), nn.Tanh(), nn.Linear(width, 1))
50 self.register_buffer('H', torch.tensor(H, dtype=torch.float32))
51 self.register_buffer('Mdiag', torch.tensor(np.diag(M), dtype=torch.float32))
52
53 def forward(self, x):
54 # x [batch, edges, 1]; ring message passing is local and identical on both sides.
55 z = torch.tanh(self.inp(x))
56 for layer in self.layers:
57 msg = (torch.roll(z, 1, 1) + torch.roll(z, -1, 1)) / 2.0
58 z = torch.tanh(layer(z + msg))
59 edge_z = (z + torch.roll(z, -1, 1)) / 2.0
60 raw = self.local(torch.cat([z, edge_z], dim=-1)).squeeze(-1)
61 if not self.harmonic:
62 return raw
63 # Gauge projection in the M-inner product, then explicit global latent.
64 coeff_local = (raw * (self.Mdiag * self.H[:, 0])).sum(dim=1)
65 u = raw - coeff_local[:, None] * self.H[:, 0][None, :]
66 a = self.coeff(z.mean(dim=1)).squeeze(-1)
67 return u + a[:, None] * self.H[:, 0][None, :]
68
69
70def make_system(harmonic):
71 _, M, H = harmonic_basis()
72 return RingNet(harmonic=harmonic, H=H, M=M)
73
74
75def train_metric(harmonic, lr, seed, return_model=False):
76 random.seed(seed); np.random.seed(seed); torch.manual_seed(seed)
77 try:
78 raw = custom_harmonic_track.get_dataset(seed, n_train=400, n_test=400)
79 # The stock custom loader assumes scalar regression; this task is a
80 # discrete k-form with 24 edge outputs, so preserve the vector target.
81 ds = {'xtr': torch.as_tensor(raw['xtr'], dtype=torch.float32),
82 'ytr': torch.as_tensor(raw['ytr'], dtype=torch.float32),
83 'xte': torch.as_tensor(raw['xte'], dtype=torch.float32),
84 'yte': torch.as_tensor(raw['yte'], dtype=torch.float32),
85 'task': 'regression', 'metric': 'mse'}
86 model, metric, _ = train_model(make_system(harmonic), ds, epochs=EPOCHS,
87 lr=lr, batch=BATCH, weight_decay=0.0, log=lambda *_: None)
88 if model is None:
89 raise RuntimeError('train_model returned None')
90 return (float(metric), model, ds) if return_model else float(metric)
91 except Exception:
92 # Explicit CPU fallback is also handled by train_model; this keeps a failed
93 # configuration visible rather than silently fabricating a metric.
94 raise
95
96
97def full_eval(harmonic, lr):
98 return evaluate(lambda s: train_metric(harmonic, lr, s), seeds=SEEDS)
99
100
101def mechanism_signature():
102 # Re-test the proposed mechanism on a trained NN, not on an analytical identity.
103 metric, model, ds = train_metric(True, 3e-3, 0, return_model=True)
104 _, M, H = harmonic_basis()
105 device = next(model.parameters()).device
106 with torch.no_grad():
107 pred = model(ds['xte'].to(device)).detach().cpu().numpy()
108 target = ds['yte'].numpy()
109 P = H.T @ M
110 pp = pred @ P.T
111 pt = target @ P.T
112 # Input source amplitude is the observed global circulation coordinate.
113 observed = ds['xte'][:, 0, 0].numpy()
114 slope = float(np.polyfit(observed, pp[:, 0], 1)[0])
115 corr = float(np.corrcoef(observed, pp[:, 0])[0, 1])
116 target_slope = float(np.polyfit(observed, pt[:, 0], 1)[0])
117 # Quantitative prediction: explicit channel should transmit coefficient with
118 # slope near one and high correlation on held-out examples.
119 return {'trained_seed': 0, 'period_input_slope': slope,
120 'target_period_input_slope': target_slope, 'period_input_correlation': corr,
121 'idea_test_mse': metric, 'confirmed': bool(abs(slope - 1.0) < 0.25 and corr > 0.9)}
122
123
124def main():
125 checks = math_check()
126 # Baseline sweep uses the mandated four-seed tuning protocol.
127 grid = [{'lr': lr} for lr in LRS]
128 base_block = sweep_baseline(lambda cfg: lambda seed: train_metric(False, cfg['lr'], seed),
129 grid, seeds=(0, 1, 2, 3))
130 # Evaluate every union lr on all eight seeds, ensuring parity with the idea sweep.
131 base_all = {str(lr): full_eval(False, lr) for lr in LRS}
132 idea_all = {str(lr): full_eval(True, lr) for lr in LRS}
133 best_lr = min(LRS, key=lambda lr: idea_all[str(lr)]['mean'])
134 idea_res = idea_all[str(best_lr)]
135 base_block['all_full'] = base_all
136 base_block['selected_lr'] = base_block['best_cfg']['lr']
137 report = make_report(TRACK, 'ring_message_passing_custom', base_block, idea_res,
138 {'mechanism_signature': mechanism_signature(),
139 'custom_track': {'name': TRACK, 'file': 'custom_harmonic_track.py', 'domain': 'pde'},
140 'idea_sweep': [{'lr': lr, 'full': idea_all[str(lr)]} for lr in LRS],
141 'math_check': checks,
142 'protocol': {'seeds': list(SEEDS), 'epochs': EPOCHS, 'batch': BATCH,
143 'baseline_union_lr_full_evals': True,
144 'idea_best_lr': best_lr,
145 'matched_architecture': 'same four local ring layers; idea adds only harmonic projection/global coefficient'}})
146 # Also provide the direct fair comparison at the baseline-selected lr.
147 report['comparison_at_baseline_best_lr'] = compare_results(base_all[str(base_block['best_cfg']['lr'])], idea_all[str(base_block['best_cfg']['lr'])])
148 with open('bench_report.json', 'w') as f:
149 json.dump(report, f, indent=2)
150 print(json.dumps(report, indent=2))
151
152if __name__ == '__main__':
153 main()