wuff-mann commited on
Commit
e64ee5c
·
verified ·
1 Parent(s): 1948e54

Archive CIDM-v2 reproducible b2_r5_local_geometry.py

Browse files
cidm_v2/source/B2_R5_P0/b2_r5_local_geometry.py ADDED
@@ -0,0 +1,215 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """CIDM-v2 B2-R5/P0 frozen spatial operator-span geometry audit.
2
+
3
+ Future validation truth ONLY used for retrospective diagnostic gradients.
4
+ This code does not train, use test labels in diagnostics, or issue a deployable forecast.
5
+ """
6
+ from __future__ import annotations
7
+ import hashlib,zipfile,io,json,csv,os,math,random
8
+ from pathlib import Path
9
+ import numpy as np
10
+ import torch
11
+ from benchmark import Config,Windows,fetch_official,make_cache,make_synthetic_cache,UnifiedModel
12
+ from b2_r1_flux_control import check_zip,get_core,assess as baseline_assess
13
+ from b2_r3_readout_probe import original_parity
14
+ from b2_r4_tangent_controllability import exact_rollout_states,sample_rms,span_project,flatten_cos,write_json,write_csv,scalar_list
15
+
16
+ PARENT_SHA='69552b29e169ec22ee85d9c1b1a678fdb188f6f25dc18f33b927b88f4dddffd4'
17
+ TASKS=('burgers_1d','reacdiff_2d','swe_2d'); SEEDS=(17,41,73);LEADS=(1,3,6,9,12)
18
+ VAL_START=8;WINDOWS_PER_SEED=8
19
+ BUDGETS=(.001,.003,.01,.03,.05)
20
+ METHODS=('full','global3','spatial3','random_spatial3','random_ambient')
21
+
22
+ def file_sha(path):
23
+ h=hashlib.sha256()
24
+ with open(path,'rb') as f:
25
+ for x in iter(lambda:f.read(1<<22),b''):h.update(x)
26
+ return h.hexdigest()
27
+
28
+ def per_site_projection(grad,vectors,mask_by_slot=None):
29
+ """Orthogonally project each D-dim spatial pixel gradient on K<=3 local operator D-vectors.
30
+ Unlike global3, weights may vary by grid point. Rank-matched random spatial
31
+ basis is required: an expanded span necessarily captures more gradient.
32
+ """
33
+ assert grad.ndim==4
34
+ qs=[];result=torch.zeros_like(grad);masks=[]
35
+ for vv in vectors:
36
+ v=vv.detach().to(grad.dtype)
37
+ for q in qs:v=v-(v*q).sum(1,keepdim=True)*q
38
+ # Each local basis must be sufficiently nonzero; avoid tiny norm / numerical noise.
39
+ nn=v.square().sum(1,keepdim=True).sqrt()
40
+ active=(nn>1e-7)
41
+ if mask_by_slot is not None:active=active & mask_by_slot[len(qs)]
42
+ q=torch.where(active, v/nn.clamp_min(1e-7), torch.zeros_like(v))
43
+ masks.append(active)
44
+ qs.append(q)
45
+ result=result+(grad*q).sum(1,keepdim=True)*q
46
+ denom=grad.square().flatten(1).sum(1).clamp_min(1e-30)
47
+ frac=(result.square().flatten(1).sum(1)/denom).clamp(0,1)
48
+ return result,frac,masks
49
+
50
+ def normalize_rms(x):
51
+ rms=sample_rms(x)
52
+ return torch.where(rms>1e-14,x/rms.clamp_min(1e-14),torch.zeros_like(x))
53
+
54
+ def comparison(core,state,truth,sd,seed):
55
+ mm=core.model;coeff=mm.coefficients()
56
+ z=state['z'].detach().float();prev=state['prev'].detach().float()
57
+ truth=truth.detach().float()
58
+ sd=sd.float().to(z.device).reshape(1,-1,1,1)
59
+ with torch.enable_grad():
60
+ var=z.detach().clone().requires_grad_(True)
61
+ p=prev+coeff['out']*torch.tanh(mm.decode(var))
62
+ assert (p.detach()-state['pred']).abs().max()<2e-5
63
+ loss=((p-truth)*sd).square().flatten(1).mean(1)
64
+ grad=torch.autograd.grad(loss.sum(),var)[0].detach()
65
+ if not torch.isfinite(grad).all():raise RuntimeError('NONFINITE_GRAD')
66
+ vec=[state[k].detach().float() for k in ('flux','local','skew')]
67
+ glob,frac_g=span_project(grad,vec)
68
+ site,frac_s,operator_masks=per_site_projection(grad,vec)
69
+ gen=torch.Generator(device='cpu').manual_seed(int(seed))
70
+ rb=[torch.randn(z.shape,generator=gen,dtype=z.dtype) for _ in range(3)]
71
+ rb=[v.to(z.device) for v in rb]
72
+ random_site,frac_rand,_=per_site_projection(grad,rb,mask_by_slot=operator_masks)
73
+ random_amb=torch.randn(z.shape,generator=gen,dtype=z.dtype).to(z.device)
74
+ # Gradient-conditioned projection into a random subspace is also an oracle.
75
+ moves={'full':-normalize_rms(grad), 'global3':-normalize_rms(glob),
76
+ 'spatial3':-normalize_rms(site), 'random_spatial3':-normalize_rms(random_site),
77
+ 'random_ambient':normalize_rms(random_amb)}
78
+ zscale=sample_rms(z).clamp_min(1.)
79
+ metrics={'base_mse_physical':scalar_list(loss.detach()),
80
+ 'grad_global3_frac':scalar_list(frac_g),'grad_spatial3_frac':scalar_list(frac_s),
81
+ 'grad_random_spatial3_frac':scalar_list(frac_rand),
82
+ 'spatial_operator_mean_rank':scalar_list(torch.stack([mask.float().squeeze(1) for mask in operator_masks],0).sum(0).flatten(1).mean(1)),
83
+ 'grad_rms':scalar_list(sample_rms(grad)),
84
+ 'decoder_abslogit_gt2':scalar_list((state['logit'].abs()>2).float().flatten(1).mean(1)),
85
+ 'latent_rms':scalar_list(sample_rms(z)),
86
+ 'future_truth_gradient_oracle':1}
87
+ for method,move in moves.items():
88
+ for budget in BUDGETS:
89
+ with torch.no_grad():
90
+ shifted=z+budget*zscale*move
91
+ predicted=prev+coeff['out']*torch.tanh(mm.decode(shifted))
92
+ after=((predicted-truth)*sd).square().flatten(1).mean(1)
93
+ metrics[f'{method}_mse_ratio_{budget:.3f}']=scalar_list(after/loss.detach().clamp_min(1e-22))
94
+ # Oracle within-sample optimal move is only available with future truth; do not call this a forecast.
95
+ for method in METHODS:
96
+ stacked=torch.stack([torch.tensor(metrics[f'{method}_mse_ratio_{b:.3f}']) for b in BUDGETS],0)
97
+ minimum=torch.cat([torch.ones((1,z.shape[0])),stacked],0).amin(0)
98
+ metrics[f'{method}_best_truth_line_search_ratio']=minimum.tolist()
99
+ return metrics
100
+
101
+ def per_task(cache,core,task,seed,device,indices,smoke=False):
102
+ val=Windows(cache['splits']['val']['h'],cache['splits']['val']['y'],cache['mean'],cache['std'])
103
+ sd=val.sd.to(device)
104
+ rows=[];batch=2 if not task.endswith('_1d') else 4
105
+ for offset in range(0,len(indices),batch):
106
+ ids=indices[offset:offset+batch]
107
+ h=torch.stack([val[i][0] for i in ids]).to(device)
108
+ y=torch.stack([val[i][1] for i in ids]).to(device)
109
+ if y.ndim==4:y=y.unsqueeze(-2)
110
+ st=exact_rollout_states(core,h,leads=LEADS)
111
+ for lead in LEADS:
112
+ metrics=comparison(core,st[lead],y[:,lead-1],sd,int(seed)*1000003+int(lead)*101+offset)
113
+ for j,idx in enumerate(ids):
114
+ r={'task':task,'seed':seed,'lead':lead,'window_index':idx,'smoke_only':int(smoke)}
115
+ for k,v in metrics.items():r[k]=v[j] if isinstance(v,list) else v
116
+ rows.append(r)
117
+ return rows
118
+
119
+ def aggregate(rows):
120
+ grouped={}
121
+ for r in rows: grouped.setdefault((r['task'],r['seed'],r['lead']),[]).append(r)
122
+ result=[]
123
+ for key,samples in sorted(grouped.items()):
124
+ task,seed,lead=key
125
+ row={'task':task,'seed':seed,'lead':lead,'windows':len(samples)}
126
+ bases=np.array([r['base_mse_physical'] for r in samples],dtype=np.float64)
127
+ if np.any(~np.isfinite(bases)) or np.any(bases<0):raise RuntimeError('BAD_BASE_LOSS')
128
+ for k in samples[0]:
129
+ if k in row or k in ('window_index','task','seed','lead','smoke_only','future_truth_gradient_oracle'):continue
130
+ v=np.array([r[k] for r in samples],dtype=np.float64)
131
+ if not np.isfinite(v).all():raise RuntimeError('NONFINITE_'+k)
132
+ if '_ratio' in k:
133
+ row[k+'_macro']=float(v.mean())
134
+ row[k+'_pooled']=float(np.dot(v,bases)/bases.sum())
135
+ row[k+'_win_fraction']=float((v<1).mean())
136
+ row[k+'_p90']=float(np.quantile(v,.9))
137
+ else:row[k]=float(v.mean())
138
+ result.append(row)
139
+ return result
140
+
141
+ def summary_report(rows,replays,smoke=False):
142
+ agg=aggregate(rows)
143
+ tasks=sorted(set(r['task'] for r in rows))
144
+ report={'experiment':'CIDM-v2 B2-R5/P0 Spatial vs Global Operator Span Retrospective Audit',
145
+ 'status':'PROGRAM_ONLY_SMOKE' if smoke else 'FROZEN_SPATIAL_SPAN_AUDIT_COMPLETED_NO_TRAINING',
146
+ 'baseline_replays':len(replays),'groups':len(agg),'sample_rows':len(rows),
147
+ 'budgets':BUDGETS,'methods':METHODS,'validation_indices':[VAL_START,VAL_START+WINDOWS_PER_SEED-1],
148
+ 'future_truth_use':'ONLY retrospective gradients and oracle line search; NEVER deployment',
149
+ 'metric_warning':'Use pooled=sum(after)/sum(before) as primary error ratio. Unweighted macro(sample ratios) is susceptible to tiny baseline errors.',
150
+ 'interpretation_warning':'spatial3 has 3 local coefficients per pixel rather than 3 global coefficients; compare against dimension-matched random_spatial3 oracle. Local oracle does NOT train causal coefficients.',
151
+ 'qualification':'DIAGNOSTIC_ONLY_NO_TRAINING_PERMISSION','lead12':{}}
152
+ for task in tasks:
153
+ xx=[r for r in rows if r['task']==task and r['lead']==12]
154
+ if not xx:continue
155
+ bases=np.array([r['base_mse_physical'] for r in xx])
156
+ info={'samples':len(xx)}
157
+ for k in ('grad_global3_frac','grad_spatial3_frac','grad_random_spatial3_frac','spatial_operator_mean_rank','decoder_abslogit_gt2'):
158
+ info[k]=float(np.mean([r[k] for r in xx]))
159
+ for m in METHODS:
160
+ k=f'{m}_mse_ratio_0.010';ratio=np.array([r[k] for r in xx]);info[m+'_0.01_pooled']=float(np.dot(ratio,bases)/bases.sum());info[m+'_0.01_macro']=float(np.mean(ratio))
161
+ report['lead12'][task]=info
162
+ return report,agg
163
+
164
+ def run(parent,root,out,smoke=False):
165
+ out=Path(out);out.mkdir(parents=True,exist_ok=True)
166
+ allrows=[];replays=[]
167
+ if smoke:
168
+ cfg=Config(profile='smoke',history=4,train_horizon=6,eval_horizon=12)
169
+ cache=make_synthetic_cache(cfg)
170
+ for task,is1 in [('smoke_1d',True),('smoke_2d',False)]:
171
+ src=cache if not is1 else {**cache,'splits':{k:{'h':v['h'].mean(dim=-2) if v['h'].ndim==5 else v['h'],
172
+ 'y':v['y'].mean(dim=-2) if v['y'].ndim==5 else v['y']} for k,v in cache['splits'].items()}}
173
+ ch=int(src['splits']['train']['h'].shape[2]);core=UnifiedModel('V2_Ported_R5',ch,8,12,4).eval().requires_grad_(False)
174
+ ids=[0,1]
175
+ allrows.extend(per_task(src,core,task,17,'cpu',ids,True))
176
+ else:
177
+ if file_sha(parent)!=PARENT_SHA:raise RuntimeError('PARENT_SHA_MISMATCH')
178
+ with zipfile.ZipFile(parent) as zp:
179
+ manifest,contract=check_zip(zp);cfg=Config(**manifest['config'])
180
+ for task in TASKS:
181
+ data=fetch_official(Path(root)/'official_data',task)
182
+ cache=make_cache(task,data,cfg,Path(root)/'window_cache')
183
+ con=next(c for c in contract['tasks'] if c['task']==task)
184
+ if cache['signature']['source_md5']!=con['source_md5'] or cache['ids']!=con['ids']:
185
+ raise RuntimeError('PDEBENCH_MD5_OR_SPLIT_MISMATCH:'+task)
186
+ if any(len(cache['splits'][s]['h'])!=con['windows'][s] for s in ('train','val','test')):raise RuntimeError('PDEBENCH_WINDOW_COUNT_MISMATCH:'+task)
187
+ for seed in SEEDS:
188
+ device='cuda' if torch.cuda.is_available() else 'cpu'
189
+ core,sha=get_core(zp,cache,task,seed,device)
190
+ core.eval().requires_grad_(False)
191
+ batch=cfg.batch_1d if task.endswith('_1d') else cfg.batch_2d
192
+ orig=float(json.loads(zp.read(f'{task}__V2_Ported_R5__seed{seed}.json'))['test']['model_12'])
193
+ check=baseline_assess(core,'baseline',cache,'test',device,batch=batch)['rmse12']
194
+ if abs(check-orig)>max(1e-5,orig*.001):raise RuntimeError('BASELINE_REPLAY_FAIL')
195
+ val=Windows(cache['splits']['val']['h'],cache['splits']['val']['y'],cache['mean'],cache['std'])
196
+ h=torch.stack([val[i][0] for i in range(2)]).to(device)
197
+ parity=original_parity(core,h)
198
+ if parity>2e-6:raise RuntimeError('BASELINE_PARITY_FAIL')
199
+ replays.append({'task':task,'seed':seed,'checkpoint_sha256':sha,'expected_test_rmse12':orig,'replay_test_rmse12':check,'max_forward_difference':parity})
200
+ if len(val)<VAL_START+WINDOWS_PER_SEED:raise RuntimeError('INSUFFICIENT_VAL_WINDOWS')
201
+ allrows.extend(per_task(cache,core,task,seed,device,list(range(VAL_START,VAL_START+WINDOWS_PER_SEED))))
202
+ write_csv(out/'sample_spatial_span_oracle.csv',allrows)
203
+ write_csv(out/'exact_replay.csv',replays)
204
+ print('[B2-R5]',task,'seed',seed,'rows',len(allrows),flush=True)
205
+ if len(replays)!=9 or len(allrows)!=9*5*WINDOWS_PER_SEED:raise RuntimeError('INCOMPLETE_9_RUN_COVERAGE')
206
+ report,agg=summary_report(allrows,replays,smoke)
207
+ if not smoke and len(agg)!=45:raise RuntimeError('INCOMPLETE_45_GROUP_COVERAGE')
208
+ write_csv(out/'sample_spatial_span_oracle.csv',allrows)
209
+ write_csv(out/'lead_seed_spatial_summary.csv',agg)
210
+ write_csv(out/'exact_replay.csv',replays)
211
+ write_json(out/'report.json',report)
212
+ return report
213
+
214
+ if __name__=='__main__':
215
+ print(run(None,'.','./B2_R5_smoke',smoke=True))