369 lines
27 KiB
Python
369 lines
27 KiB
Python
#!/usr/bin/env python3
|
|
"""Synthetic counterexamples guard against false coupled-correction successes."""
|
|
import csv
|
|
import json
|
|
import math
|
|
from pathlib import Path
|
|
import tempfile
|
|
import unittest
|
|
|
|
import check_coupled_trial as c
|
|
|
|
|
|
def csv_file(path,data,fields=None):
|
|
with path.open('w',newline='') as stream:
|
|
writer=csv.DictWriter(stream,fieldnames=fields or list(data[0]));writer.writeheader();writer.writerows(data)
|
|
|
|
|
|
def vec_file(path,values):
|
|
path.write_text(str(len(values))+'\n'+'\n'.join(repr(v) for v in values)+'\n')
|
|
|
|
|
|
class CoupledTrialCheckerTests(unittest.TestCase):
|
|
def setUp(self):
|
|
self.temp=tempfile.TemporaryDirectory();self.addCleanup(self.temp.cleanup)
|
|
self.campaign=Path(self.temp.name).resolve();self.folder=self.campaign/'synthetic';self.folder.mkdir()
|
|
self.make_case()
|
|
|
|
def make_case(self,mode='kept'):
|
|
for path in self.folder.iterdir():path.unlink()
|
|
folder=self.folder;nan=math.nan
|
|
x=[0.,0.,0.];f=[1.,1.,1.];p=[-1.,-1.,-1.];j=p[:];raw_state=p[:]
|
|
factors=[2.]*3;fn=math.sqrt(3.);pn=fn;alpha=1.
|
|
raw_prepared=mode!='raw_failure';attempted=mode not in ('original_accepted','raw_failure')
|
|
before=[.5,0.,0.] if mode=='original_accepted' else [10.,0.,0.]
|
|
rhs=[-v for v in before]
|
|
correction=[-5.,0.,0.] if mode=='failed_gate' else [-10.,0.,0.]
|
|
action=correction[:];defect=[v-w for v,w in zip(action,rhs)]
|
|
cn=c.norm(correction);dn=c.norm(defect)
|
|
cap_ratio=1. if mode=='cap' else 10.;cap=cap_ratio*pn
|
|
core=1 if mode=='failed_gate' else (2 if mode=='cap' else 0)
|
|
corrected_observed=attempted and core==0
|
|
corrected=[12100.,0.,0.] if mode=='worse' else ([2.,0.,0.] if mode=='improved_not_accepted' else [0.,0.,0.])
|
|
corrected_state=[v+w for v,w in zip(raw_state,correction)]
|
|
kept=corrected_observed and mode!='worse'
|
|
status='kept' if kept else ('not_improved' if mode=='worse' else ('linear_failure' if mode=='failed_gate' else
|
|
('norm_limit' if mode=='cap' else ('original_accepted' if mode=='original_accepted' else 'not_attempted'))))
|
|
selected_state=corrected_state if kept else raw_state[:]
|
|
selected=corrected if kept else before[:]
|
|
aux_preps=1 if kept else (2 if mode=='worse' else 0)
|
|
aux_restores=int(mode=='worse')
|
|
def save(name,values):vec_file(folder/(c.P+name+'.txt'),values)
|
|
for name,values in {'base_normalized':x,'base_physical':x,'base_residual':f,'state_factors':factors,
|
|
'residual_factors':[3.]*3,'direction':p,'base_action':j,'base_defect':[0.]*3,'restored_residual':f,
|
|
'restored_action':j,'prediction_a0':[0.]*3,'raw_state_a0':raw_state}.items():save(name,values)
|
|
if raw_prepared:
|
|
for name,values in {'raw_residual_a0':before,'before_state_a0':raw_state,'before_residual_a0':before,
|
|
'selected_state_a0':selected_state,'selected_residual_a0':selected,
|
|
'selected_physical_a0':[v/2 for v in selected_state]}.items():save(name,values)
|
|
if attempted:
|
|
for name,values in {'soc_rhs_a0':rhs,'soc_correction_a0':correction,'soc_action_a0':action,'soc_defect_a0':defect}.items():save(name,values)
|
|
if corrected_observed:
|
|
for name,values in {'corrected_state_a0':corrected_state,'corrected_residual_a0':corrected,
|
|
'corrected_physical_a0':[v/2 for v in corrected_state]}.items():save(name,values)
|
|
layout=[{'kind':kind,'block':name,'offset':i,'size':1} for kind,names in (
|
|
('value',['density','surface_deformation','specific_enthalpy']),
|
|
('residual',['closure','shape','hydrostatic'])) for i,name in enumerate(names)]
|
|
csv_file(folder/(c.P+'layout.csv'),layout)
|
|
def merit(v):return .5*c.norm(v)**2
|
|
cutoff=(1-1.e-4)*1.5
|
|
row={'sample':0,'alpha':alpha,'gravity_path':0,'raw_prepared':int(raw_prepared),
|
|
'raw_rejection_reason':-1 if raw_prepared else 2,'raw_rejection_stage':-1 if raw_prepared else 6,
|
|
'raw_norm':c.norm(before) if raw_prepared else nan,'raw_merit':merit(before) if raw_prepared else nan,
|
|
'gravity_attempted':0,'gravity_reported_gate':1,'gravity_prepared_gate':1,'gravity_target_defect':nan,
|
|
'gravity_affine_disagreement':nan,'gravity_target_threshold':nan,'before_prepared':int(raw_prepared),
|
|
'before_usable':int(raw_prepared),'before_norm':c.norm(before) if raw_prepared else nan,
|
|
'before_merit':merit(before) if raw_prepared else nan,'before_accepted':int(raw_prepared and merit(before)<=cutoff),
|
|
'soc_called':int(raw_prepared),'soc_status':c.STATUS.index(status),'soc_attempted':int(attempted),
|
|
'aux_status':0 if attempted else -1,'aux_reported_gate':int(attempted),'aux_iterations':1 if attempted else 0,
|
|
'aux_rhs_norm':c.norm(rhs) if attempted else nan,'aux_reported_true_norm':0. if attempted else nan,
|
|
'aux_verified_true_norm':dn if attempted else nan,'aux_verified_gate':int(attempted and dn<=.03*c.norm(rhs)),
|
|
'aux_threshold':.03*c.norm(rhs) if attempted else nan,'aux_verification_applied':int(attempted),
|
|
'correction_norm':cn if attempted else nan,'correction_cap':cap if attempted else nan,
|
|
'correction_ratio':cn/pn if attempted else nan,'corrected_metric_observed':int(corrected_observed),
|
|
'corrected_norm':c.norm(corrected) if corrected_observed else nan,
|
|
'corrected_merit':merit(corrected) if corrected_observed else nan,
|
|
'corrected_accepted':int(corrected_observed and merit(corrected)<=cutoff),'selected_prepared':int(raw_prepared),
|
|
'selected_norm':c.norm(selected) if raw_prepared else nan,'selected_merit':merit(selected) if raw_prepared else nan,
|
|
'selected_accepted':int(raw_prepared and merit(selected)<=cutoff),'selected_is_original':int(raw_prepared and not kept),
|
|
'fallback_residual_difference':0. if raw_prepared and not kept else nan,
|
|
'raw_seconds':.1,'gravity_seconds':0.,'soc_seconds':.3 if attempted else 0.,
|
|
'soc_refresh_seconds':.02 if attempted else 0.,'soc_solve_seconds':.1 if attempted else 0.,
|
|
'soc_verification_seconds':.01 if attempted else 0.,'soc_preparation_seconds':.05*aux_preps,
|
|
'soc_preparations':aux_preps,'soc_restorations':aux_restores,'aux_operator_applications':2 if attempted else 0,
|
|
'aux_inverse_applications':1 if attempted else 0,'verified_action_defect_norm':dn if attempted else nan,
|
|
'capture_seconds':.1,'total_sample_seconds':2.,'aux_core_status':core if attempted else -1,
|
|
'rhs_saved':int(attempted),'correction_saved':int(attempted),'verified_vectors_saved':int(attempted)}
|
|
csv_file(folder/(c.P+'trials.csv'),[row])
|
|
blocks=[]
|
|
for stage,values in [('raw',before),('before',before),('corrected',corrected),('selected',selected)]:
|
|
if not raw_prepared or (stage=='corrected' and not corrected_observed):continue
|
|
for name,start,size in [('all',0,3),('closure',0,1),('shape',1,1),('hydrostatic',2,1)]:
|
|
blocks.append({'sample':0,'alpha':1.,'stage':stage,'block':name,'offset':start,'size':size,
|
|
'residual_norm':c.norm(values[start:start+size])})
|
|
csv_file(folder/(c.P+'blocks.csv'),blocks,['sample','alpha','stage','block','offset','size','residual_norm'])
|
|
preparations=[]
|
|
for kind,prepared,rn in [('initial_base_restore',True,fn),('capture_base',True,fn),('raw',raw_prepared,c.norm(before)),('final_base_restore',True,fn)]:
|
|
preparations.append({'sequence':len(preparations)+1,'kind':kind,'sample':0 if kind=='raw' else -1,
|
|
'alpha':1. if kind=='raw' else 0.,'prepared':int(prepared),'rejection_reason':-1 if prepared else 2,
|
|
'rejection_stage':-1 if prepared else 6,'seconds':.1,'residual_norm':rn if prepared else nan})
|
|
csv_file(folder/(c.P+'preparations.csv'),preparations)
|
|
summary={'schema_version':1,'independent_frozen_alphas':1,'accepted_commits':0,'outer_linear_tolerance':.03,
|
|
'sufficient_decrease':1.e-4,'gravity_path':0,'seed_control':1,'coupled_maximum_correction_ratio':cap_ratio,
|
|
'coupled_relative_tolerance':.03,'coupled_absolute_tolerance':1.e-14,'coupled_maximum_iterations':10,'coupled_restart':4,
|
|
'gravity_relative_tolerance':1.e-10,'gravity_absolute_tolerance':1.e-14,'gravity_maximum_iterations':100,'gravity_restart':20,
|
|
'polynomial_direction_repairs':0,'sampled_enthalpy_policy':1,'coupled_attempt_budget_per_independent_alpha':1,
|
|
'alpha_0':1.,'base_residual_norm':fn,'base_merit':1.5,'direction_norm':pn,'base_merit_slope':-3.,
|
|
'outer_verified_defect_norm':0.,'outer_verified_relative_defect':0.,'outer_verified_threshold':.03*fn,
|
|
'outer_verified_gate':1,'outer_gate_failure_skips_trials':0,'soc_attempts':int(attempted),'soc_kept':int(kept),
|
|
'soc_fallbacks':int(attempted and not kept),'soc_iterations':int(attempted),
|
|
'soc_solve_seconds':row['soc_solve_seconds'],'soc_verification_seconds':row['soc_verification_seconds'],
|
|
'soc_preparation_seconds':row['soc_preparation_seconds'],'soc_refresh_seconds':row['soc_refresh_seconds'],
|
|
'soc_preparations':aux_preps,'soc_restorations':aux_restores,'probe_exception':0,
|
|
'accepted_buffers_bitwise_identical':1,'normalization_factors_bitwise_identical':1,'direction_buffers_bitwise_identical':1,
|
|
'direction_buffers_unchanged_before_restoration':1,'maximum_base_restoration_error':0.,'restored_base_action_error':0.,
|
|
'fresh_operator_and_inverse_current':1,'explicit_probe_preparations':4,'explicit_probe_preparation_seconds':.4,
|
|
'explicit_probe_inverse_refreshes':3,'explicit_probe_inverse_refresh_seconds':.06,
|
|
'explicit_probe_operator_applications':2,'explicit_probe_operator_seconds':.02,'total_seconds':10.}
|
|
summary.update({'soc_status_'+name:i for i,name in enumerate(c.STATUS)})
|
|
csv_file(folder/(c.P+'summary.csv'),[{'metric':k,'value':v} for k,v in summary.items()])
|
|
meta={'n':1.5,'K':1.,'M':1.,'J':0,'central_phase':0,'coupled_trial_probe':1,'linear_tol':.03,'coupled_tolerance':.03,
|
|
'coupled_iterations':10,'coupled_restart':4,'coupled_maximum_ratio':cap_ratio,'coupled_attempts_per_iteration':1,
|
|
'coupled_seed_control':1,'gravity_tolerance':1.e-10,'gravity_iterations':100,'gravity_restart':20,
|
|
'strategy':'coupled','coupled_trial_alphas_are_independent':1,'coupled_trial_admissibility':'sampled_enthalpy_without_repair'}
|
|
(folder/'metadata.txt').write_text('\n'.join(f'{k}={v}' for k,v in meta.items())+'\n')
|
|
physical=[]
|
|
def measurement(sample,source,q,key,value):
|
|
physical.append({'sample':sample,'source':source,'quadrature':q,'metric':key,'value':value})
|
|
for key in ('accepted_buffers_unchanged_before_restoration','accepted_buffers_unchanged_after_restoration',
|
|
'normalization_factors_unchanged','direction_buffers_unchanged','accepted_operator_and_inverse_current'):
|
|
measurement(-1,'restoration',0,key,1)
|
|
measurement(-1,'replay',0,'selected_snapshots_measured',int(raw_prepared))
|
|
if raw_prepared:
|
|
source=c.P+'selected_physical_a0.txt';measurement(0,source,0,'fresh_preparation_passed',1)
|
|
for q in (14,18):
|
|
metrics={'quadrature_order':q,'polytropic_index':1.5,'polytropic_constant':1.,'target_mass':1.,
|
|
'stellar_samples':10,'valid_stellar_samples':10,'invalid_stellar_samples':0,'negative_density_samples':0,
|
|
'negative_enthalpy_samples':0,'invalid_density_eos_samples':0,'invalid_enthalpy_eos_samples':0,
|
|
'volume':1.,'complete_volume_measurement':1,'pressure_from_enthalpy_defined_everywhere':1,
|
|
'pressure_from_density_defined_everywhere':1,'saved_prepared_l2_difference':0.,
|
|
'saved_prepared_maximum_difference':0.,'saved_prepared_maximum_scaled_difference':0.,
|
|
'normalized_bordered_residual':c.norm(selected),'normalized_unbordered_residual':c.norm(selected),
|
|
'normalized_border_action':0.,'central_border':0.,'mass':1.,'mass_absolute_error':0.,
|
|
'minimum_density':0.,'minimum_enthalpy':0.,'binding_energy':-3.,'force_binding_energy':-3.,
|
|
'kinetic_energy':0.,'pressure_integral':1.,'enthalpy_pressure_integral':1.}
|
|
for prefix in ('','enthalpy_'):
|
|
for force in ('','force_'):
|
|
metrics[prefix+force+'virial_signed']=0.;metrics[prefix+force+'virial_error']=0.
|
|
for key,value in metrics.items():measurement(0,source,q,key,value)
|
|
csv_file(folder/(c.P+'physical.csv'),physical)
|
|
state=self.campaign/'input_state.txt';direction=self.campaign/'input_direction.txt';vec_file(state,x);vec_file(direction,p)
|
|
binary=self.campaign/'plateau_diagnostic';binary.write_bytes(b'synthetic executable identity')
|
|
mesh=self.campaign/'mesh.smesh';mesh.write_text('synthetic mesh identity')
|
|
args=['--coupled-trial-probe','--probe-state',str(state),'--probe-direction',str(direction),'--strategy','coupled','--coupled-seed-control']
|
|
cases=[{'id':'synthetic','arguments':args}]
|
|
provenance={'commit':'synthetic','source':str(self.campaign),'mesh':str(mesh),'mesh_sha256':c.digest(mesh),
|
|
'binary_sha256':c.digest(binary),'cases':cases}
|
|
(self.campaign/'provenance.json').write_text(json.dumps(provenance));(self.campaign/'cases.json').write_text(json.dumps(cases))
|
|
record={'id':'synthetic','finished_utc':'2026-09-18T00:00:00Z','exit_code':0,
|
|
'command':[str(binary),*args,'--mesh',str(mesh),'--output',str(folder)],
|
|
'input_state_hashes':{str(state):c.digest(state),str(direction):c.digest(direction)},
|
|
'output_hashes':{p.name:c.digest(p) for p in folder.iterdir()}}
|
|
(self.campaign/'synthetic_run.json').write_text(json.dumps(record))
|
|
|
|
def alter(self,name,callback):
|
|
path=self.folder/(c.P+name+'.csv');data=c.rows(path);callback(data);csv_file(path,data)
|
|
|
|
def valid(self,result):
|
|
failed=[x for x in result.get('checks',result.get('provenance_checks',[])) if not x['passed']]
|
|
failed += [x for case in result.get('cases',{}).values() for x in case['checks'] if not x['passed']]
|
|
self.assertTrue(result['passed'],failed)
|
|
|
|
def test_exact_mixed_correction_and_provenance(self):
|
|
self.valid(c.inspect_campaign(self.campaign))
|
|
|
|
def test_saved_exact_correction_reports_geometry_without_dividing_by_zero_defect(self):
|
|
result=c.inspect_case(self.folder);self.valid(result)
|
|
mechanism=result['trial_outcomes'][0]['correction_mechanism']
|
|
self.assertAlmostEqual(mechanism['correction_direction_cosine'],1/math.sqrt(3.))
|
|
self.assertAlmostEqual(mechanism['correction_over_rhs_norm'],1.)
|
|
self.assertAlmostEqual(mechanism['correction_over_alpha_squared'],10.)
|
|
self.assertAlmostEqual(mechanism['corrected_displacement_over_outer_step_norm'],math.sqrt(41.))
|
|
self.assertAlmostEqual(mechanism['effective_direction_projection_t'],13/3)
|
|
self.assertEqual(mechanism['remainder_before_norm'],10.)
|
|
self.assertEqual(mechanism['remainder_after_norm'],0.)
|
|
self.assertEqual(mechanism['remainder_decomposition_error_norm'],0.)
|
|
self.assertIsNone(mechanism['own_taylor_over_verified_defect'])
|
|
|
|
def test_quadratic_correction_separates_linear_defect_from_own_curvature(self):
|
|
# F(x)=1+x+x^2 at x0=0 has p=-1. At x=-1 the Jacobian
|
|
# is -1; the inexact c=.98 passes eta=.03 but nearly undoes p.
|
|
# Its own E=c^2=.9604 dominates the .02 verified linear defect.
|
|
corrected_x=-.02;corrected_f=1+corrected_x+corrected_x**2
|
|
mechanism=c.correction_mechanism([0.],[-1.],1.,[0.],[1.],[corrected_x],
|
|
[corrected_f],[.98],[-1.],[-.98])
|
|
self.assertAlmostEqual(mechanism['correction_direction_cosine'],-1.)
|
|
self.assertAlmostEqual(mechanism['correction_over_rhs_norm'],.98)
|
|
self.assertAlmostEqual(mechanism['corrected_displacement_over_outer_step_norm'],.02)
|
|
self.assertAlmostEqual(mechanism['effective_direction_projection_t'],.02)
|
|
self.assertAlmostEqual(mechanism['remainder_after_norm'],.9804)
|
|
self.assertAlmostEqual(mechanism['verified_linear_defect_norm'],.02)
|
|
self.assertAlmostEqual(mechanism['own_taylor_remainder_norm'],.9604)
|
|
self.assertAlmostEqual(mechanism['own_taylor_over_correction_squared'],1.)
|
|
self.assertAlmostEqual(mechanism['own_taylor_over_verified_defect'],48.02)
|
|
self.assertAlmostEqual(mechanism['linear_defect_dot_own_taylor'],.019208)
|
|
self.assertLess(mechanism['remainder_decomposition_error_norm'],1.e-15)
|
|
json.dumps(mechanism,allow_nan=False)
|
|
|
|
def test_failed_verified_gate_is_valid_evidence_but_not_a_correction_success(self):
|
|
self.make_case('failed_gate');r=c.inspect_case(self.folder);self.valid(r)
|
|
self.assertFalse(r['trial_outcomes'][0]['aux_verified_gate']);self.assertFalse(r['trial_outcomes'][0]['full_merit_improved'])
|
|
|
|
def test_cap_rejection_preserves_original_trial(self):
|
|
self.make_case('cap');r=c.inspect_case(self.folder);self.valid(r)
|
|
self.assertEqual(r['trial_outcomes'][0]['transaction_status'],'norm_limit')
|
|
|
|
def test_worse_corrected_merit_requires_fallback(self):
|
|
self.make_case('worse');r=c.inspect_case(self.folder);self.valid(r)
|
|
self.assertEqual(r['trial_outcomes'][0]['transaction_status'],'not_improved')
|
|
self.assertFalse(r['trial_outcomes'][0]['selected_armijo'])
|
|
|
|
def test_kept_correction_does_not_imply_armijo_acceptance(self):
|
|
self.make_case('improved_not_accepted');r=c.inspect_case(self.folder);self.valid(r)
|
|
self.assertTrue(r['trial_outcomes'][0]['full_merit_improved']);self.assertFalse(r['trial_outcomes'][0]['selected_armijo'])
|
|
|
|
def test_already_acceptable_trial_needs_no_auxiliary_solve(self):
|
|
self.make_case('original_accepted');r=c.inspect_case(self.folder);self.valid(r)
|
|
self.assertFalse(r['trial_outcomes'][0]['correction_attempted']);self.assertTrue(r['trial_outcomes'][0]['selected_armijo'])
|
|
|
|
def test_failed_raw_preparation_has_no_false_correction_certificate(self):
|
|
self.make_case('raw_failure');r=c.inspect_case(self.folder);self.valid(r)
|
|
self.assertFalse(r['trial_outcomes'][0]['before_usable']);self.assertFalse(r['trial_outcomes'][0]['selected_armijo'])
|
|
|
|
def test_failed_outer_gate_is_valid_evidence_without_trials(self):
|
|
for path in self.folder.glob(c.P+'*_a*.txt'):path.unlink()
|
|
for name,values in {'base_action':[-.5]*3,'restored_action':[-.5]*3,'base_defect':[.5]*3}.items():
|
|
vec_file(self.folder/(c.P+name+'.txt'),values)
|
|
for name in ('trials','blocks'):
|
|
path=self.folder/(c.P+name+'.csv');csv_file(path,[],list(c.rows(path)[0]))
|
|
updates={'base_merit_slope':-1.5,'outer_verified_defect_norm':math.sqrt(.75),
|
|
'outer_verified_relative_defect':.5,'outer_verified_gate':0,'outer_gate_failure_skips_trials':1,
|
|
'explicit_probe_preparations':2,'explicit_probe_preparation_seconds':.2}
|
|
self.alter('summary',lambda rows:[r.update(value=updates[r['metric']]) for r in rows if r['metric'] in updates])
|
|
def preparations(rows):
|
|
rows[:]=[r for r in rows if r['kind'] in ('initial_base_restore','final_base_restore')]
|
|
for i,row in enumerate(rows):row['sequence']=i+1
|
|
self.alter('preparations',preparations)
|
|
def physical(rows):
|
|
rows[:]=[r for r in rows if r['sample']=='-1']
|
|
next(r for r in rows if r['metric']=='selected_snapshots_measured')['value']=0
|
|
self.alter('physical',physical)
|
|
result=c.inspect_case(self.folder);self.valid(result)
|
|
self.assertFalse(result['outer_linear_gate']);self.assertEqual(result['trial_outcomes'],[])
|
|
|
|
def test_gravity_path_then_full_correction_has_distinct_saved_targets(self):
|
|
def save(name,values):vec_file(self.folder/(c.P+name+'_a0.txt'),values)
|
|
save('gravity_correction',[2.,0.,0.]);save('before_state',[1.,-1.,-1.]);save('before_residual',[0.,10.,0.])
|
|
for name in ('soc_rhs','soc_correction','soc_action'):save(name,[0.,-10.,0.])
|
|
for name in ('corrected_state','selected_state'):save(name,[1.,-11.,-1.])
|
|
for name in ('corrected_physical','selected_physical'):save(name,[.5,-5.5,-.5])
|
|
self.alter('trials',lambda rows:rows[0].update(gravity_path=1,gravity_attempted=1,
|
|
gravity_target_defect=0.,gravity_affine_disagreement=0.,gravity_target_threshold=1.e-12))
|
|
self.alter('summary',lambda rows:next(r for r in rows if r['metric']=='gravity_path').update(value=1))
|
|
meta=self.folder/'metadata.txt';meta.write_text(meta.read_text().replace('strategy=coupled','strategy=curvature-coupled'))
|
|
self.alter('layout',lambda rows:[r.update(block={'density':'gravity_potential','closure':'poisson_balance'}.get(r['block'],r['block'])) for r in rows])
|
|
def blocks(rows):
|
|
for row in rows:
|
|
if row['stage']=='before' and row['block']=='closure':row['residual_norm']=0.
|
|
if row['stage']=='before' and row['block']=='shape':row['residual_norm']=10.
|
|
if row['block']=='closure':row['block']='poisson_balance'
|
|
self.alter('blocks',blocks)
|
|
self.valid(c.inspect_case(self.folder))
|
|
self.alter('trials',lambda rows:rows[0].update(gravity_target_defect=1.e-6))
|
|
self.assertFalse(c.inspect_case(self.folder)['passed'])
|
|
|
|
def test_false_saved_true_gate_cannot_pass(self):
|
|
self.make_case('failed_gate');self.alter('trials',lambda rows:rows[0].update(aux_verified_gate=1))
|
|
self.assertFalse(c.inspect_case(self.folder)['passed'])
|
|
|
|
def test_finite_failed_verification_cannot_be_hidden_by_save_flags(self):
|
|
self.make_case('failed_gate');self.alter('trials',lambda rows:rows[0].update(verified_vectors_saved=0))
|
|
for name in ('soc_action_a0','soc_defect_a0'):(self.folder/(c.P+name+'.txt')).unlink()
|
|
self.assertFalse(c.inspect_case(self.folder)['passed'])
|
|
|
|
def test_looser_reported_controls_cannot_override_requested_controls(self):
|
|
self.make_case('failed_gate')
|
|
self.alter('summary',lambda rows:next(r for r in rows if r['metric']=='coupled_relative_tolerance').update(value=1.))
|
|
self.assertFalse(c.inspect_case(self.folder)['passed'])
|
|
|
|
def test_conflicting_raw_preparation_is_not_usable_evidence(self):
|
|
self.alter('preparations',lambda rows:next(r for r in rows if r['kind']=='raw').update(prepared=0,rejection_reason=2,rejection_stage=6))
|
|
self.assertFalse(c.inspect_case(self.folder)['passed'])
|
|
|
|
def test_physical_replay_is_bound_to_selected_residual(self):
|
|
self.alter('physical',lambda rows:next(r for r in rows if r['metric']=='normalized_bordered_residual').update(value=.1))
|
|
self.assertFalse(c.inspect_case(self.folder)['passed'])
|
|
|
|
def test_undefined_density_pressure_is_valid_evidence_not_virial_success(self):
|
|
def negative_density(rows):
|
|
for row in rows:
|
|
if row['sample']=='0' and row['quadrature'] in ('14','18'):
|
|
key=row['metric']
|
|
if key=='negative_density_samples':row['value']=1
|
|
elif key=='minimum_density':row['value']=-.001
|
|
elif key=='pressure_from_density_defined_everywhere':row['value']=0
|
|
elif key in ('pressure_integral','virial_signed','virial_error','force_virial_signed','force_virial_error'):row['value']=math.nan
|
|
self.alter('physical',negative_density);result=c.inspect_case(self.folder);self.valid(result)
|
|
self.assertIsNone(result['physical_observations'][0]['virial_status']['virial_within_1e5'])
|
|
self.assertTrue(result['physical_observations'][0]['virial_status']['enthalpy_virial_within_1e5'])
|
|
self.assertEqual(result['physical_observations'][0]['global_enthalpy_certificate'],'not_measured')
|
|
|
|
def test_false_physical_virial_cannot_be_declared(self):
|
|
self.alter('physical',lambda rows:next(r for r in rows if r['metric']=='enthalpy_pressure_integral').update(value=2.))
|
|
self.assertFalse(c.inspect_case(self.folder)['passed'])
|
|
|
|
def test_missing_physical_restoration_guard_fails(self):
|
|
self.alter('physical',lambda rows:rows.__setitem__(slice(None),[r for r in rows if r['metric']!='direction_buffers_unchanged']))
|
|
self.assertFalse(c.inspect_case(self.folder)['passed'])
|
|
|
|
def test_corrupted_corrected_displacement_is_detected(self):
|
|
path=self.folder/(c.P+'corrected_state_a0.txt');v=c.vector(path);v[1]+=.1;vec_file(path,v)
|
|
self.assertFalse(c.inspect_case(self.folder)['passed'])
|
|
|
|
def test_rejected_correction_cannot_replace_original_selected_state(self):
|
|
self.make_case('worse');path=self.folder/(c.P+'selected_state_a0.txt');vec_file(path,c.vector(self.folder/(c.P+'corrected_state_a0.txt')))
|
|
self.assertFalse(c.inspect_case(self.folder)['passed'])
|
|
|
|
def test_missing_restoration_invariant_is_not_success(self):
|
|
self.alter('summary',lambda data:data.__setitem__(slice(None),[r for r in data if r['metric']!='direction_buffers_unchanged_before_restoration']))
|
|
self.assertFalse(c.inspect_case(self.folder)['passed'])
|
|
|
|
def test_false_armijo_acceptance_is_detected(self):
|
|
self.make_case('improved_not_accepted');self.alter('trials',lambda rows:rows[0].update(selected_accepted=1,corrected_accepted=1))
|
|
self.assertFalse(c.inspect_case(self.folder)['passed'])
|
|
|
|
def test_unhashed_auxiliary_action_cannot_support_campaign(self):
|
|
path=self.campaign/'synthetic_run.json';r=json.loads(path.read_text());del r['output_hashes'][c.P+'soc_action_a0.txt'];path.write_text(json.dumps(r))
|
|
self.assertFalse(c.inspect_campaign(self.campaign)['passed'])
|
|
|
|
def test_incomplete_producer_cannot_support_campaign(self):
|
|
path=self.campaign/'synthetic_run.json';r=json.loads(path.read_text());del r['finished_utc'];path.write_text(json.dumps(r))
|
|
self.assertFalse(c.inspect_campaign(self.campaign)['passed'])
|
|
|
|
def test_later_missing_producer_suppresses_earlier_interpretation(self):
|
|
path=self.campaign/'provenance.json';provenance=json.loads(path.read_text())
|
|
provenance['cases'].append({'id':'unfinished','arguments':provenance['cases'][0]['arguments']})
|
|
path.write_text(json.dumps(provenance));(self.campaign/'cases.json').write_text(json.dumps(provenance['cases']))
|
|
result=c.inspect_campaign(self.campaign);self.assertFalse(result['passed'])
|
|
self.assertFalse(result['cases']['synthetic']['observations_valid'])
|
|
self.assertEqual(result['cases']['synthetic']['trial_outcomes'],[])
|
|
self.assertEqual(result['cases']['synthetic']['physical_observations'],[])
|
|
|
|
def test_rehashed_changed_input_direction_is_not_the_measured_direction(self):
|
|
path=self.campaign/'input_direction.txt';vec_file(path,[1.,2.,3.]);rp=self.campaign/'synthetic_run.json'
|
|
r=json.loads(rp.read_text());r['input_state_hashes'][str(path)]=c.digest(path);rp.write_text(json.dumps(r))
|
|
self.assertFalse(c.inspect_campaign(self.campaign)['passed'])
|
|
|
|
|
|
if __name__=='__main__':unittest.main()
|