wuff-mann commited on
Commit
8ddbfff
·
verified ·
1 Parent(s): 51399f1

Archive CIDM-v2 reproducible b2_r2_fix5_exact_dynamics.py

Browse files
cidm_v2/source/B2_R2_P0_FIX5/b2_r2_fix5_exact_dynamics.py ADDED
@@ -0,0 +1,242 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """CIDM-v2 B2-R2/P0 FIX5: exact frozen observable-geometry audit.
2
+
3
+ Original B0-R1 model, original B2-R1 FIX2 wrappers, official data contract.
4
+ No trainable modules, no optimizer, no intervention to original prediction.
5
+ Observe *actual* PortedRestoredCIDM operations: skew is nn.Parameter, decode is nn.Sequential.
6
+ """
7
+ from __future__ import annotations
8
+ import csv, hashlib, io, json, math, os, pathlib, time, zipfile
9
+ from collections import defaultdict
10
+ import numpy as np
11
+ import torch
12
+ from benchmark import Config, Windows, fetch_official, make_cache, load_data, assess as canonical_assess
13
+ from b2_r1_flux_control import (TASKLIST, SEEDS, get_core, check_zip, ControlledV2, assess as fix2_assess)
14
+
15
+ LEADS=(1,3,6,9,12)
16
+ SOURCE_HASH='8b4fd32e3f3675fd23764506267020dc61aba31c9ad54cfdf3ce6ace5d7e2c98'
17
+ PARENT_HASH='69552b29e169ec22ee85d9c1b1a678fdb188f6f25dc18f33b927b88f4dddffd4'
18
+
19
+
20
+ def rms(t):return float(t.detach().float().square().mean().sqrt().item())
21
+ def cos(a,b):
22
+ a=a.detach().float().flatten(start_dim=1);b=b.detach().float().flatten(start_dim=1)
23
+ an=torch.linalg.vector_norm(a,dim=1);bn=torch.linalg.vector_norm(b,dim=1)
24
+ keep=(an>1e-10)&(bn>1e-10)&torch.isfinite(an)&torch.isfinite(bn)
25
+ if not bool(keep.any()):return None
26
+ return float(((a[keep]*b[keep]).sum(dim=1)/(an[keep]*bn[keep])).clamp(-1,1).mean().item())
27
+
28
+ def validate_core(core):
29
+ assert core.kind=='V2_Ported_R5', 'Model mismatch'
30
+ mm=core.model
31
+ assert all(hasattr(mm,x) for x in ('encode','decode','flux','local','skew','coefficients'))
32
+ assert isinstance(mm.skew,torch.nn.Parameter), 'skew must be trained parameter, not hookable module'
33
+ assert callable(mm.decode) and callable(mm.flux) and callable(mm.local)
34
+ return mm
35
+
36
+ @torch.inference_mode()
37
+ def trace_frozen(core,h,truth,task,seed,batch_index,leads=LEADS,parity_atol=2e-6):
38
+ """Probe one baseline rollout at the exact trained operators, no future-truth inputs.
39
+
40
+ For output diagnostic truth is used only *after* prediction to score its direction.
41
+ Counterfactual operator deletion is local at a fixed frozen z; no retrained ablation.
42
+ """
43
+ mm=validate_core(core);is1d=h.ndim==4
44
+ h5=h.unsqueeze(-2) if is1d else h
45
+ y5=truth.unsqueeze(-2) if is1d else truth
46
+ B,L,C,H,W=h5.shape
47
+ assert L==mm.hist==4 and C==mm.channels and y5.shape[1]>=max(leads)
48
+ coeff=mm.coefficients(); skew=mm.skew-mm.skew.T
49
+ history=[x for x in h5.unbind(1)]
50
+ z=mm.encode(h5.reshape(B,L*C,H,W))
51
+ rows=[];forecasts=[];prev_truth=h5[:,-1]
52
+ for t in range(1,max(leads)+1):
53
+ z_before=z
54
+ flux=coeff['flux']*mm.flux(z_before)
55
+ local=coeff['local']*torch.tanh(mm.local(z_before))
56
+ skew_delta=.035*torch.einsum('ij,bjhw->bihw',skew,z_before)
57
+ z=z_before+flux+local+skew_delta
58
+ dlog=mm.decode(z)
59
+ inc=coeff['out']*torch.tanh(dlog)
60
+ x=history[-1]+inc
61
+ forecasts.append(x);history.append(x)
62
+ target=y5[:,t-1]
63
+ true_inc=target-prev_truth
64
+ prev_truth=target
65
+ if t not in leads:continue
66
+ pieces={'flux':flux,'local':local,'skew':skew_delta}
67
+ sum_delta=flux+local+skew_delta
68
+ assert torch.all(torch.isfinite(z)) and torch.all(torch.isfinite(inc)), 'NONFINITE'
69
+ row=dict(task=task,seed=int(seed),batch=int(batch_index),lead=t,n=B,
70
+ flux_rms=rms(flux),local_rms=rms(local),skew_rms=rms(skew_delta),
71
+ update_rms=rms(z-z_before),additive_residual_rel=rms((z-z_before)-sum_delta)/max(rms(z-z_before),1e-10),
72
+ flux_local_cos=cos(flux,local),flux_skew_cos=cos(flux,skew_delta),
73
+ flux_scale=float(coeff['flux']),local_scale=float(coeff['local']),out_scale=float(coeff['out']),
74
+ decoder_logits_rms=rms(dlog),decoder_abs_gt2=float((dlog.abs()>2).float().mean()),
75
+ pred_increment_rms=rms(inc),truth_increment_rms=rms(true_inc),
76
+ pred_increment_cos_truth=cos(inc,true_inc),
77
+ pred_increment_power_ratio=float((inc.square().mean()/(true_inc.square().mean()+1e-20)).item()),
78
+ original_step_mse_normalized=float((x-target).square().mean()),
79
+ latent_state_rms=rms(z),latent_step_rel=rms(sum_delta)/max(rms(z_before),1e-10))
80
+ # The output increments have the same normalized observable units as model prediction.
81
+ responses={}
82
+ for key,v in pieces.items():
83
+ inc_without=coeff['out']*torch.tanh(mm.decode(z-v))
84
+ response=inc-inc_without
85
+ responses[key]=response
86
+ row[key+'_response_rms']=rms(response)
87
+ row[key+'_response_cos_true_increment']=cos(response,true_inc)
88
+ row[key+'_response_cos_pred_increment']=cos(response,inc)
89
+ row[key+'_response_relative_to_inc']=rms(response)/max(rms(inc),1e-10)
90
+ # Diagnostic-only locally removed term, fixed actual latent trajectory.
91
+ row[key+'_local_cf_mse_change_normalized']=float(((history[-2]+inc_without-target).square().mean()-(x-target).square().mean()).item())
92
+ all_response=sum(responses.values())
93
+ base_increment=coeff['out']*torch.tanh(mm.decode(z_before))
94
+ row['decoder_effect_of_total_update_rms']=rms(inc-base_increment)
95
+ row['operator_decoder_nonlinearity_rms']=rms(all_response-(inc-base_increment))
96
+ # The model's OWN generated history only. 4-frame history includes no future truth.
97
+ pred_hist=torch.stack(history[-L:],dim=1)
98
+ re_z=mm.encode(pred_hist.reshape(B,L*C,H,W))
99
+ re_inc=coeff['out']*torch.tanh(mm.decode(re_z))
100
+ row['self_reencode_latent_cos']=cos(z,re_z)
101
+ row['self_reencode_increment_gap_rel']=rms(re_inc-inc)/max(rms(inc),1e-10)
102
+ row['self_reencode_increment_cos']=cos(re_inc,inc)
103
+ row['self_reencode_increment_rms']=rms(re_inc)
104
+ rows.append(row)
105
+ predicted=torch.stack(forecasts,dim=1)
106
+ expected=ControlledV2(core,'baseline')(h,max(leads))
107
+ if is1d:predicted=predicted.squeeze(-2)
108
+ max_diff=float((predicted-expected).abs().max())
109
+ if not math.isfinite(max_diff) or max_diff>parity_atol:
110
+ raise RuntimeError(f'REAL_MODEL_FORWARD_PARITY_FAIL: {task} seed{seed} batch{batch_index}: max_diff={max_diff}')
111
+ return rows,max_diff
112
+
113
+
114
+ def weighted_summary(rows):
115
+ group=defaultdict(list)
116
+ for r in rows:group[(r['task'],r['seed'],r['lead'])].append(r)
117
+ out=[]
118
+ for (task,seed,lead),items in sorted(group.items()):
119
+ n=sum(x['n'] for x in items)
120
+ v={'task':task,'seed':seed,'lead':lead,'num_windows':n,'num_batches':len(items)}
121
+ for k in items[0]:
122
+ if k in ('task','seed','lead','batch','n'):continue
123
+ vals=[(x.get(k),x['n']) for x in items if x.get(k) is not None and isinstance(x.get(k),(float,int)) and math.isfinite(x[k])]
124
+ v[k]=sum(z*w for z,w in vals)/sum(w for _,w in vals) if vals else None
125
+ out.append(v)
126
+ return out
127
+
128
+
129
+ def dump_csv(path,items):
130
+ path=pathlib.Path(path)
131
+ if not items:return
132
+ keys=list(items[0])
133
+ with open(path,'w',newline='',encoding='utf-8') as fp:
134
+ wr=csv.DictWriter(fp,fieldnames=keys,extrasaction='ignore');wr.writeheader();wr.writerows(items)
135
+
136
+ def sha256(path):
137
+ h=hashlib.sha256()
138
+ with open(path,'rb') as f:
139
+ for chunk in iter(lambda:f.read(1<<20),b''):h.update(chunk)
140
+ return h.hexdigest()
141
+
142
+ def write_json(path,obj):pathlib.Path(path).write_text(json.dumps(obj,ensure_ascii=False,indent=2,allow_nan=False,default=str),encoding='utf-8')
143
+
144
+ def run(parent_zip,output_dir,root_dir=None,max_val_windows=16,smoke=False):
145
+ output_dir=pathlib.Path(output_dir);output_dir.mkdir(parents=True,exist_ok=True)
146
+ root=pathlib.Path(root_dir or output_dir.parent/'runtime');root.mkdir(parents=True,exist_ok=True)
147
+ if smoke:return unit_test(output_dir)
148
+ assert sha256(parent_zip)==PARENT_HASH,'PARENT_SHA256_MISMATCH'
149
+ assert sha256(pathlib.Path(__file__).with_name('b2_r1_flux_control.py'))==SOURCE_HASH,'FIX2_DRIVER_SHA256_MISMATCH'
150
+ device=torch.device('cuda' if torch.cuda.is_available() else 'cpu');torch.set_num_threads(min(4,torch.get_num_threads()))
151
+ results=[];replays=[];checks=[];status='PRECHECK'
152
+ (output_dir/'experiment_contract.json').write_text(json.dumps(dict(
153
+ experiment='B2_R2_P0_FIX5',official_only=True,train=False,leads=LEADS,
154
+ val_window_limit=max_val_windows,seed_list=SEEDS,task_list=TASKLIST,
155
+ parent_sha256=PARENT_HASH,fix2_source_sha256=SOURCE_HASH,
156
+ selection_set='none',threshold_search='none',expected_task_seed=9),indent=2),encoding='utf-8')
157
+ try:
158
+ with zipfile.ZipFile(parent_zip) as zp:
159
+ manifest,contract=check_zip(zp)
160
+ assert manifest['gate']['completed_runs']==18
161
+ config=Config(**manifest['config']);
162
+ for task in TASKLIST:
163
+ data_root=pathlib.Path(os.environ.get('CIDM_B2_DATA_ROOT',str(root/'official_data')))
164
+ official=fetch_official(data_root,task)
165
+ cache=make_cache(task,official,config,root/'window_cache')
166
+ ref=next(z for z in contract['tasks'] if z['task']==task)
167
+ assert cache['signature']['source_md5']==ref['source_md5']
168
+ assert cache['ids']==ref['ids']
169
+ for split in ('train','val','test'):
170
+ assert len(cache['splits'][split]['h'])==ref['windows'][split]
171
+ for seed in SEEDS:
172
+ core,checkpoint_sha=get_core(zp,cache,task,seed,device)
173
+ original_batch=config.batch_1d if cache['splits']['test']['h'].ndim==4 else config.batch_2d
174
+ orig=float(json.loads(zp.read(f'{task}__V2_Ported_R5__seed{seed}.json'))['test']['model_12'])
175
+ canonical=canonical_assess(core,load_data(cache,'test',original_batch,seed,False),device,config,mode='off')['model_12']
176
+ wrapped=fix2_assess(core,'baseline',cache,'test',device,batch=original_batch)['rmse12']
177
+ tol=max(1e-5,orig*.001)
178
+ row=dict(task=task,seed=seed,checkpoint_sha256=checkpoint_sha,archived_rmse12=orig,
179
+ canonical_rmse12=canonical,fix2_rmse12=wrapped,batch=original_batch,
180
+ replay_pass=bool(abs(canonical-orig)<=tol and abs(wrapped-canonical)<=tol))
181
+ replays.append(row);dump_csv(output_dir/'exact_replay.csv',replays)
182
+ if not row['replay_pass']:raise RuntimeError('OFFICIAL_REPLAY_FAIL_CLOSED: '+str(row))
183
+ print(f'[REPLAY PASS] {task} seed{seed} archived={orig:.8g} canonical={canonical:.8g} fixed2={wrapped:.8g}',flush=True)
184
+ val_ds=Windows(cache['splits']['val']['h'],cache['splits']['val']['y'],cache['mean'],cache['std'])
185
+ nb=min(len(val_ds),max_val_windows)
186
+ for start in range(0,nb,original_batch):
187
+ xx=[val_ds[j] for j in range(start,min(nb,start+original_batch))]
188
+ h=torch.stack([x[0] for x in xx]).to(device)
189
+ truth=torch.stack([x[1] for x in xx]).to(device)
190
+ diag,parity=trace_frozen(core,h,truth,task,seed,start//original_batch)
191
+ results.extend(diag);checks.append(dict(task=task,seed=seed,batch=start//original_batch,parity=max(float(parity),0.),windows=h.shape[0]))
192
+ summary=weighted_summary(results)
193
+ dump_csv(output_dir/'observable_samples.csv',results)
194
+ dump_csv(output_dir/'lead_seed_summary.csv',summary)
195
+ dump_csv(output_dir/'forward_parity.csv',checks)
196
+ print(f'[DIAGNOSTIC] {task} seed{seed}: {len([x for x in summary if x["task"]==task and x["seed"]==seed])} leads, {nb} validation windows',flush=True)
197
+ write_json(output_dir/'progress.json',{'completed_task_seed':[[r['task'],r['seed']] for r in replays],
198
+ 'all_replay_pass':all(r['replay_pass'] for r in replays),'valid_trace_rows':len(results),'time':time.time()})
199
+ del core
200
+ if device.type=='cuda':torch.cuda.empty_cache()
201
+ summary=weighted_summary(results)
202
+ expected={(task,int(seed),lead) for task in TASKLIST for seed in SEEDS for lead in LEADS}
203
+ actual={(r['task'],int(r['seed']),int(r['lead'])) for r in summary}
204
+ valid=not(expected-actual) and len(replays)==9 and len(results)>0 and all(r['replay_pass'] for r in replays)
205
+ maxerr=max(r['additive_residual_rel'] for r in results)
206
+ paritymax=max(r['parity'] for r in checks)
207
+ valid=valid and maxerr<=1e-4 and paritymax<=2e-6
208
+ status='FROZEN_DIAGNOSTIC_COMPLETED_NO_TRAINING' if valid else 'INCOMPLETE_OR_INTEGRITY_FAIL_NO_TRAINING'
209
+ report={'experiment':'CIDM-v2 B2-R2/P0 FIX5','status':status,'official_data':'MD5_VERIFIED_BY_CANONICAL_BENCHMARK',
210
+ 'replay_models':len(replays),'covered_family_seed_leads':len(actual),'expected_family_seed_leads':len(expected),
211
+ 'raw_batch_lead_rows':len(results),'missing_coverage':sorted([list(k) for k in (expected-actual)]),
212
+ 'max_additive_relative_residual':maxerr,'max_forward_abs_difference':paritymax,
213
+ 'stage':'FROZEN_DIAGNOSTIC_ONLY','train_steps':0,'qualification':'NOT_EVALUATED',
214
+ 'notice':'Lead metrics are on validation windows; test was used only for archived baseline replay, not candidate selection.',
215
+ 'scientific_limits':['Frozen effects are local single-step counterfactuals, not a retrained or full-rollout ablation.',
216
+ 'Latent values depend on learned coordinates; no direct physical conservation conclusion.',
217
+ 'No hyperparameter search or performance-improvement claim; only mechanism diagnosis.']}
218
+ write_json(output_dir/'report.json',report)
219
+ print('[FIX5 FINAL]',json.dumps(report,ensure_ascii=False),flush=True)
220
+ return report
221
+ except BaseException as exc:
222
+ write_json(output_dir/'failure.json',{'status':'BLOCKED_NO_TRAINING','error':repr(exc),'completed_replay':len(replays),'partial_rows':len(results)})
223
+ raise
224
+
225
+ @torch.inference_mode()
226
+ def unit_test(output_dir):
227
+ pathlib.Path(output_dir).mkdir(parents=True,exist_ok=True)
228
+ from benchmark import UnifiedModel
229
+ results=[];parities=[]
230
+ for task,dims,channels in [('smoke_1d',(2,4,1,16),1),('smoke_2d',(2,4,2,8,16),2)]:
231
+ model=UnifiedModel('V2_Ported_R5',channels,8,12,4).eval()
232
+ torch.manual_seed(17)
233
+ h=torch.randn(*dims)*.01
234
+ y=torch.randn(2,12,*dims[2:])*.01
235
+ r,err=trace_frozen(model,h,y,task,17,0)
236
+ assert len(r)==5 and err<=2e-6 and max(q['additive_residual_rel'] for q in r)<1e-4
237
+ assert all(q.get('flux_response_rms') is not None and q.get('self_reencode_increment_gap_rel') is not None for q in r)
238
+ results.extend(r);parities.append(err)
239
+ report={'experiment':'B2_R2_FIX5_CPU_SMOKE','status':'PROGRAM_ONLY_SMOKE_PASS','rows':len(results),
240
+ 'max_forward_abs_difference':max(parities), 'max_additive_relative_residual':max(x['additive_residual_rel'] for x in results)}
241
+ write_json(pathlib.Path(output_dir)/'smoke_report.json',report)
242
+ return report