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
|