Patch-Consensus Weak Residual Training / bench_run.py
Failed on benchmark
1import sys, json, math, random
2from pathlib import Path
3import numpy as np
4import torch
5import torch.nn as nn
6sys.path.insert(0, '/home/maxwelhelp/all/math2nn')
7from bench import get_dataset, make_model, evaluate, sweep_baseline, make_report
8from bench.protocol import DEFAULT_SEEDS
9
10V, D = .7, .015
11
12def seed_all(s):
13 random.seed(s); np.random.seed(s); torch.manual_seed(s)
14 if torch.cuda.is_available(): torch.cuda.manual_seed_all(s)
15
16def derivs(net, z):
17 z = z.detach().requires_grad_(True)
18 u = net(z).reshape(-1)
19 g = torch.autograd.grad(u.sum(), z, create_graph=True)[0]
20 ux, ut = g[:,0], g[:,1]
21 gx = torch.autograd.grad(ux.sum(), z, create_graph=True)[0]
22 return u, ux, ut, gx[:,0]
23
24def points(seed, n):
25 g = torch.Generator().manual_seed(seed)
26 return torch.rand(n, 2, generator=g)
27
28def weak_loss(net, z, n_patch=8, q=8, lam=.002, tau=.08):
29 # Each patch uses q quadrature points and a compact tent weight. The
30 # weighted local regression is the weak-form analogue of fitting A_j c=b_j.
31 _, ux, ut, uxx = derivs(net, z)
32 zz = z.reshape(n_patch, q, 2)
33 # Local tents centered at the patch's first quadrature point, with a fixed
34 # compact radius; normalization removes patch-volume scale.
35 centers = zz[:, 0:1, :]
36 dist = torch.abs(zz - centers)
37 ph = ((1 - dist[:, :, 0] / .35).clamp_min(0) *
38 (1 - dist[:, :, 1] / .35).clamp_min(0))
39 A = torch.stack([ux.reshape(n_patch, q), uxx.reshape(n_patch, q)], -1)
40 bb = ut.reshape(n_patch, q)
41 crows=[]
42 eye = torch.eye(2, device=z.device)
43 for j in range(n_patch):
44 w = ph[j]
45 aj = A[j].detach() * w[:, None]
46 bj = bb[j].detach() * w
47 c = torch.linalg.solve(aj.T @ aj + .01*eye, aj.T @ bj)
48 c = torch.sign(c) * torch.relu(torch.abs(c)-lam)
49 crows.append(c)
50 c = torch.stack(crows)
51 support = c.abs() > tau
52 modal = support.float().mean(0) >= .5
53 cbar = torch.where(modal, c.mean(0), torch.zeros_like(c.mean(0)))
54 # Retain gradients through weak A,b while stopping them through decisions.
55 residual = (A * ph[:, :, None]).sum(1) @ cbar.detach() - (bb * ph).sum(1)
56 residual = residual / (ph.sum(1) + 1e-6)
57 consistency = ((c-cbar.detach())**2).mean()
58 return residual.pow(2).mean() + .15*consistency, float(support.float().mean()), int(modal.sum())
59
60def train(seed, cfg, idea):
61 seed_all(seed)
62 ds = get_dataset('pde_patch_consensus', seed, n_train=400, n_test=200)
63 device = 'cuda' if torch.cuda.is_available() else 'cpu'
64 try:
65 net = make_model('mlp_tiny', tuple(ds['xtr'].shape[1:]), 1).to(device)
66 except RuntimeError:
67 device = 'cpu'; net = make_model('mlp_tiny', tuple(ds['xtr'].shape[1:]), 1)
68 xtr, ytr = ds['xtr'].to(device), ds['ytr'].to(device)
69 opt = torch.optim.Adam(net.parameters(), lr=cfg['lr'])
70 last = (0.0, 0)
71 for ep in range(cfg['epochs']):
72 net.train(); perm = torch.randperm(len(xtr), device=device)
73 for ii in range(0, len(xtr), 128):
74 ix = perm[ii:ii+128]
75 loss = ((net(xtr[ix]).reshape(-1) - ytr[ix])**2).mean()
76 z = points(seed*10000 + ep, 64).to(device)
77 if idea:
78 wl, agree, nterms = weak_loss(net, z, lam=cfg['lam'], tau=cfg['tau'])
79 loss = loss + cfg['weight'] * wl
80 last = (agree, nterms)
81 else:
82 _, ux, ut, uxx = derivs(net, z)
83 loss = loss + cfg['weight'] * ((ut + V*ux - D*uxx)**2).mean()
84 opt.zero_grad(set_to_none=True); loss.backward(); opt.step()
85 net.eval()
86 with torch.no_grad():
87 metric = float(((net(ds['xte'].to(device)).reshape(-1) - ds['yte'].to(device))**2).mean())
88 z = points(seed+777, 128).to(device)
89 _, ux, ut, uxx = derivs(net, z)
90 point_rms = float(torch.sqrt(((ut + V*ux - D*uxx)**2).mean()).detach().cpu())
91 obs_rms = math.sqrt(metric)
92 return metric, {'pointwise_pde_rms': point_rms,
93 'prediction_observation_rms': obs_rms,
94 'local_support_rate': last[0], 'modal_terms': last[1]}
95
96def make_train(cfg, idea):
97 def fn(seed): return train(seed,cfg,idea)[0]
98 return fn
99
100def main():
101 # Equal union: every lr and residual weight used by either method is swept on baseline.
102 grid=[{'lr':lr,'weight':w,'epochs':20,'lam':lam,'tau':tau} for lr,w,lam,tau in
103 [(.001,.15,.002,.08),(.003,.30,.002,.08),(.01,.15,.002,.08)]]
104 base=sweep_baseline(lambda c: make_train(c,False),grid,seeds=(0,1,2,3))
105 idea_candidates=[]
106 for c in grid:
107 r=evaluate(make_train(c,True),seeds=DEFAULT_SEEDS)
108 idea_candidates.append({'cfg':c,'result':r})
109 best=min(idea_candidates,key=lambda x:x['result']['mean'])
110 sig=[]
111 for s in DEFAULT_SEEDS:
112 _,q=train(s,best['cfg'],True); sig.append(q)
113 bsig=[]
114 for s in DEFAULT_SEEDS:
115 _,q=train(s,base['best_cfg'],False); bsig.append(q)
116 means=lambda rows,k: float(np.mean([r[k] for r in rows]))
117 extra={'prediction':'weak residual integration plus support consensus should reduce noisy residual variability and increase regional support agreement',
118 'observed_trained_models':{
119 'baseline_pointwise_pde_rms_mean':means(bsig,'pointwise_pde_rms'),
120 'idea_pointwise_pde_rms_mean':means(sig,'pointwise_pde_rms'),
121 'baseline_observation_rms_mean':means(bsig,'prediction_observation_rms'),
122 'idea_observation_rms_mean':means(sig,'prediction_observation_rms'),
123 'idea_local_support_rate_mean':means(sig,'local_support_rate'),
124 'idea_modal_terms_mean':means(sig,'modal_terms')},
125 'confirmed': bool(means(sig,'pointwise_pde_rms') <= means(bsig,'pointwise_pde_rms')*1.05 and means(sig,'local_support_rate')>0)}
126 report=make_report('pde_patch_consensus','mlp_tiny',base,best['result'],extra)
127 report['custom_track']={'name':'pde_patch_consensus','file':'pde_consensus_track.py','domain':'pde'}
128 report['idea_sweep']=[{'cfg':x['cfg'],'mean':x['result']['mean']} for x in idea_candidates]
129 Path('bench_report.json').write_text(json.dumps(report,indent=2))
130 print(json.dumps(report,indent=2))
131if __name__=='__main__': main()