Preserve native gate outcomes and source/output hashes. No simulation or production source changes.
90 lines
10 KiB
Python
90 lines
10 KiB
Python
"""Read-only postprocessing of the six existing, authorized simulation outputs."""
|
|
import csv
|
|
import hashlib
|
|
import json
|
|
import math
|
|
from pathlib import Path
|
|
import subprocess
|
|
import datetime
|
|
import numpy as np
|
|
|
|
Q = Path('/home/frank14f/CelerisLab-release-evidence-20261020/physical-750e8a4/followup-queue')
|
|
R = Path('/home/frank14f/CelerisLab-release-prep-20261020')
|
|
SHA = '750e8a483ec47dcf472e528edc1cf2936f0e7e83'
|
|
def read(p):
|
|
return json.loads(p.read_text())
|
|
def sha(p):
|
|
return hashlib.sha256(p.read_bytes()).hexdigest()
|
|
def cmd(*a):
|
|
return subprocess.check_output(a, text=True).strip()
|
|
def span(a):
|
|
return float(np.ptp(a))
|
|
def finite(x):
|
|
if isinstance(x, dict): return all(finite(v) for v in x.values())
|
|
if isinstance(x, list): return all(finite(v) for v in x)
|
|
return not isinstance(x, (int,float)) or math.isfinite(x)
|
|
def stats(rows, key):
|
|
t=np.array([v['tstar'] for v in rows]); v=np.array([r[key] for r in rows])
|
|
d=np.diff(v); nz=np.sign(d[d!=0])
|
|
return dict(samples=len(v),first=float(v[0]),last=float(v[-1]),min=float(min(v)),max=float(max(v)),mean=float(np.mean(v)),range=span(v),ols_slope_per_tstar=float(np.polyfit(t-t[0],v,1)[0]),increment_sign_changes=int(np.count_nonzero(nz[1:]!=nz[:-1])),total_variation=float(np.abs(d).sum()))
|
|
|
|
state=read(Q/'state.json'); manifest=read(Q/'manifest.json'); execution=read(Q/'execution.json')
|
|
out=dict(schema=1,utc=datetime.datetime.now(datetime.timezone.utc).isoformat(),sha=SHA,evidence_root=str(Q),postprocessor_sha256=sha(Path(__file__)),scope='Offline read-only digest; no new simulation, gate edit, acceptance promotion, or performance claim',verification={},cases={})
|
|
v=out['verification']; v['all_six_completed']=len(state['jobs'])==6 and all(x['status']=='COMPLETED' for x in state['jobs'].values())
|
|
v['all_exit_zero_no_timeout']=all(x['exit_code']==0 and not x['timeout'] for x in state['jobs'].values())
|
|
v['head']=cmd('git','-C',str(R),'rev-parse','HEAD');v['git_status_porcelain']=cmd('git','-C',str(R),'status','--porcelain');v['clean_fixed_head']=v['head']==SHA and not v['git_status_porcelain']
|
|
v['tree']=cmd('git','-C',str(R),'rev-parse','HEAD^{tree}');v['src_tree']=cmd('git','-C',str(R),'rev-parse','HEAD:src')
|
|
v['tracked_source_map_matches_manifest']=all(sha(R/p)==h for p,h in manifest['files_sha256'].items());v['tracked_source_map_count']=len(manifest['files_sha256'])
|
|
v['execution_final_source_matches_manifest']=all(execution['source_after'][k]==manifest[k] for k in ['sha','tree','src_tree','git_status','git_diff_sha256','files_sha256'])
|
|
pids=[state['scheduler_pid']]+[x['pid'] for x in state['jobs'].values()]
|
|
v['tracked_processes']={}
|
|
for pid in pids:
|
|
p=subprocess.run(['ps','-p',str(pid),'-o','pid=,ppid=,stat=,comm='],capture_output=True,text=True)
|
|
v['tracked_processes'][str(pid)]=p.stdout.strip() or 'absent'
|
|
v['no_tracked_process_running']=all(s=='absent' or ' Z' in s for s in v['tracked_processes'].values())
|
|
v['gpu_compute_processes']=cmd('nvidia-smi','--query-compute-apps=gpu_uuid,pid,process_name,used_gpu_memory','--format=csv')
|
|
out['hashes']={p.name:sha(p) for p in [Q/'manifest.json',Q/'execution.json',Q/'state.json',Q/'queue-plan.json',Q/'receipt-preflight.json',Q/'authorized_followup_queue.py',Q/'v4_external_timing.py']}
|
|
for label,sj in state['jobs'].items():
|
|
p=Q/label; r=read(p/'result.json'); c=out['cases'][label]=dict(status=sj['native_status'],exit_code=sj['exit_code'],timeout=sj['timeout'],process_wall_seconds=sj['wall_seconds'],pid=sj['pid'],gpu_uuid=sj['gpu_uuid'],source_unchanged=r['source_unchanged'])
|
|
c['source_matches_manifest']=all(r['source'][k]==manifest[k] and r['source_after'][k]==manifest[k] for k in ['sha','tree','src_tree','git_status','git_diff_sha256','files_sha256'])
|
|
c['external_manifest_hash_matches']=r['external_manifest_sha256']==sha(Q/'manifest.json')
|
|
c['hashes']={str(z.relative_to(p)):sha(z) for z in [p/'command.json',p/'result.json',*sorted((p/'output').rglob('*.json')),*sorted((p/'output').rglob('*.csv')),*sorted((p/'output').rglob('*.jsonl'))]}
|
|
if label.startswith('v4'):
|
|
n=read(p/'output/result.json')[0]; rows=n['rows']
|
|
captured=r['native_results'][0]
|
|
c['native_receipt_matches_external_result']=all(n[k]==z for k,z in captured.items() if k not in ['last_row','rows_count']) and captured['last_row']==rows[-1] and captured['rows_count']==len(rows)
|
|
c['external_receipt_history_handling']='V4 external receipt stores row count and last row; full native rows are separately hashed'
|
|
c['native_all_finite']=finite(n)
|
|
c['native']={k:z for k,z in n.items() if k not in ['rows','lattice']}
|
|
c['full_series']={k:stats(rows,k) for k in ['cd','cl','p_diff']}
|
|
c['ten_tstar_windows']={str(a):{k:stats([z for z in rows if a<=z['tstar']<=a+10],k) for k in ['cd','cl','p_diff']} for a in range(0,60,10)}
|
|
c['last_window']=dict(tstar=[50,60],metrics={k:stats(rows[-21:],k) for k in ['cd','cl','p_diff']})
|
|
c['health']=dict(rho_min=min(z['rho_min'] for z in rows),rho_max=max(z['rho_max'] for z in rows),max_mach=max(z['max_mach'] for z in rows))
|
|
continue
|
|
case=label.rsplit('-',1)[1]; base=p/'output'/case;n=read(base/'summary.json');plan=read(p/'output/plan.json')
|
|
c['native_receipt_matches_external_result']={k:z for k,z in n.items() if k!='history'}==r['native_summary']
|
|
c['external_receipt_history_handling']='V2 external receipt omits the history path; complete history.csv is separately hashed'
|
|
c['native_all_finite']=finite(n)
|
|
c['native_runner_hash_matches']=plan['runner_sha256']==sha(R/'tests/validation/run_refined_dfg.py')
|
|
sm=plan['source_manifest'];c['native_source_capture_matches']=sm['checkout_revision']==SHA and not sm['checkout_dirty'] and all(sha(R/f)==h==manifest['files_sha256'][f] for f,h in sm['sha256'].items())
|
|
c['native_source_capture_file_count']=len(sm['sha256'])
|
|
c['native']={k:z for k,z in n.items() if k not in ['history','cycles','jet_diagnostic_windows']}; c['comparison']=read(p/'output/comparison.json');c['gates']=plan['gates'];c['reference_gates']=plan['reference_gates'];c['diagnostic_windows']=n['jet_diagnostic_windows']
|
|
rows=[{k:float(z) for k,z in row.items()} for row in csv.DictReader((base/'history.csv').open())];late=[x for x in rows if x['tstar']>=290]
|
|
c['health']=dict(rows=len(rows),tstar=[rows[0]['tstar'],rows[-1]['tstar']],all_finite=finite(rows),rho_min=min(z['rho_min'] for z in rows),rho_max=max(z['rho_max'] for z in rows),max_mach=max(z['max_mach'] for z in rows),mass_first=rows[0]['mass'],mass_last=rows[-1]['mass'],mass_change_fraction=(rows[-1]['mass']-rows[0]['mass'])/rows[0]['mass'],mass_late_range_fraction=span([z['mass'] for z in late])/np.mean([z['mass'] for z in late]))
|
|
if n['cycles']:
|
|
tail=n['cycles'][-5:]; periods=[z['period'] for z in tail]; masses=[z['mass_time_mean'] for z in tail]
|
|
c['periodic_actual']=dict(cycles_total=len(n['cycles']),tail_cycles=len(tail),tail_start=tail[0]['start'],tail_end=tail[-1]['end'],period_min=min(periods),period_max=max(periods),period_mean=float(np.mean(periods)),period_relative_range=span(periods)/np.mean(periods),last_cycle_age=rows[-1]['tstar_force']-tail[-1]['end'],consecutive_max_gap=max(abs(a['end']-b['start']) for a,b in zip(tail,tail[1:])),minimum_lift_amplitude=min(z['cl_amplitude'] for z in tail),extrema_ranges={k+'_'+e:span([z[k+'_'+e] for z in tail]) for k in ['cd','cl','dp'] for e in ['min','max']},mass_relative_range=span(masses)/abs(np.mean(masses)),max_cycle_flux_relative=max(z['flux_relative'] for z in tail),last_cycle={k:z for k,z in tail[-1].items() if not k.endswith('_waveform')})
|
|
cv=[json.loads(x) for x in (base/'cv_history.jsonl').read_text().splitlines()];t=np.array([x['time_root'] for x in cv]);inv=np.array([x['inventory'] for x in cv]);flux=np.array([x['outward_flux'] for x in cv]);j=np.array([x['interval_impulse_root'] for x in cv[1:]]).sum(axis=0);duration=t[-1]-t[0];norm=duration*.5*.01**2*40
|
|
cvout=c['cv']=dict(samples=len(cv),all_finite=finite(cv),tstar=[cv[0]['tstar'],cv[-1]['tstar']],duration_root=duration,coefficient_normalization=norm,raw_body_reaction_impulse_root=j.tolist(),first_preceding_impulse_excluded=True,contours_root=plan['cv_observation'][case+'_contours_root'],mass_qualification='Apparent mass source only: raw Jet mass_into_fluid ledger was not recorded, so mass-source-subtracted closure cannot be assessed',acceptance='offline diagnostics only; no CV pass threshold was defined',strides={})
|
|
for stride in [1,2,4]:
|
|
idx=np.arange(0,len(t),stride);assert idx[-1]==len(t)-1
|
|
integral=np.trapz(flux[idx],t[idx],axis=0); delta=inv[-1]-inv[0];jcv=-delta[:,1:]-integral[:,1:];res=jcv-j
|
|
cvout['strides'][str(stride)]=dict(samples=len(idx),momentum_delta_root=delta[:,1:].tolist(),outward_momentum_integral_root=integral[:,1:].tolist(),cv_reaction_impulse_root=jcv.tolist(),residual_impulse_root=res.tolist(),residual_coefficient=(res/norm).tolist(),apparent_mass_source_root=(delta[:,0]+integral[:,0]).tolist(),apparent_mass_source_fraction_initial_inventory=((delta[:,0]+integral[:,0])/inv[0,:,0]).tolist())
|
|
a=np.array(cvout['strides']['1']['residual_coefficient']);cvout['contour_residual_coefficient_difference']=(a[1]-a[0]).tolist();cvout['coarsening_coefficient_difference']={s:(np.array(cvout['strides'][s]['residual_coefficient'])-a).tolist() for s in ['2','4']}
|
|
v['all_six_source_unchanged']=all(c['source_unchanged'] and c['source_matches_manifest'] and c['external_manifest_hash_matches'] for c in out['cases'].values())
|
|
v['all_six_native_receipts_match']=all(c['native_receipt_matches_external_result'] and c['native_all_finite'] for c in out['cases'].values())
|
|
v['all_v2_native_captures_match']=all(c['native_runner_hash_matches'] and c['native_source_capture_matches'] for k,c in out['cases'].items() if k.startswith('v2'))
|
|
out['limitations']=['V1 historical source pin remains blocked','V3 original TG source guard blocked all four dry-runs before CUDA; no TG result or acceptance','Short suite remains 767 passed, 24 failed, 1 skipped; not a release pass','Matched_case_pass remains false because each invocation selected one case; native paired-case comparison branch did not execute','Fixed Jet CV is diagnostic; mass source ledger absent; no physical acceptance','V4 both NOT_STEADY with original ranges/drifts/bands; boundary deviations retained','No formal performance claim: timings include observation/profiling and startup is separately recorded']
|
|
dest=Q/'final-physical-digest.json';dest.write_text(json.dumps(out,indent=2,allow_nan=False)+'\n')
|
|
print(json.dumps(dict(output=str(dest),bytes=dest.stat().st_size,sha256=sha(dest),verification=v),indent=2))
|