Files
digital-drosophila/models/male-cns-lif/apl-timing-validation-v1/run_suite.py
T
2026-09-30 13:18:18 +08:00

152 lines
7.7 KiB
Python

"""Ordered fixed-D18 validation; representation failure never becomes execution failure.
Only the two predeclared first-odor/first-seed mode probes use Brian per cohort.
Their GPU r0 outputs are reused in the full 60-case block. No novel-to-AJ
trajectory can start until the complete seed block and its repeats pass.
"""
import argparse
import subprocess
import time
import traceback
from context import ROOT, MODEL, EXP, CFG, check, save, read, budget
import runtime_bridge as bridge
SCIENCE = ROOT / 'models/male-cns-lif/.venv/bin/python'
ORACLE = ROOT / 'experiments/phase4ad-author-semantics/environment/.venv/bin/python'
class RepresentationGateFailed(RuntimeError):
"""Completed valid trajectories did not pass the predeclared representation gate."""
def child(backend, group, name, repeat=0):
# Do not resolve either interpreter: the venv invocation path is meaningful.
interpreter = ORACLE if backend == 'oracle' else SCIENCE
bridge.require(interpreter.is_file(), 'Missing fixed runtime interpreter: ' + str(interpreter))
bridge.authorize_case(EXP, CFG, group, name, backend, repeat)
log = EXP / 'logs' / f'{backend}-{group}-{name}-r{repeat}.log'
log.parent.mkdir(parents=True, exist_ok=True)
command = [str(interpreter), '-B', str(MODEL / 'run_case.py'), backend, group, name, '--repeat', str(repeat)]
print('START', backend, group, name, repeat, flush=True)
started = time.perf_counter()
with log.open('x', encoding='utf-8') as stream:
result = subprocess.run(command, stdout=stream, stderr=subprocess.STDOUT, timeout=1800, check=False)
print('FINISH', backend, group, name, repeat, 'exit', result.returncode,
'wall', round(time.perf_counter() - started, 2), flush=True)
bridge.require(result.returncode == 0, f'{backend} {group}/{name} failed; preserve partial evidence and inspect {log}')
def control(row):
name = row['name']
child('gpu', 'controls', name)
case = EXP / 'cases/controls' / name
output = EXP / 'gpu/controls' / (name + '-r0')
report = bridge.control_identity(output, row['source_output'], case, row['source_case'],
EXP / 'audit' / ('control-' + name + '.json'))
bridge.require(report['pass_all'], 'Fresh fixed-D18 control differs from sealed AJ: ' + name)
child('component', 'controls', name)
return dict(case=name, group='controls', pass_all=True, fresh_gpu_runs=1, new_oracle_runs=0)
def probe(row):
name, group = row['name'], row['cohort']
child('gpu', group, name)
child('oracle', group, name)
result = bridge.compare(EXP / 'cases' / group / name, EXP / 'gpu' / group / (name + '-r0'),
EXP / 'oracle' / group / name, EXP / 'audit' / ('compare-' + group + '-' + name + '.json'))
bridge.require(result['pass_all'], 'Closed-loop fixed-D18 comparison failed: ' + name)
child('component', group, name)
return dict(case=name, group=group, pass_all=True, fresh_gpu_runs=1, new_oracle_runs=1)
def validation_block(cohort, records):
from validation import summarize
rows = bridge.cohort_rows(records, CFG, cohort)
probes = {row['name'] for row in bridge.probe_rows(records, CFG, cohort)}
repeated = []
for row in rows:
name = row['name']
if name not in probes:
child('gpu', cohort, name)
else:
comparison = read(EXP / 'audit' / ('compare-' + cohort + '-' + name + '.json'))
bridge.require(comparison.get('pass_all') is True and comparison.get('case') == name,
'Missing successful probe evidence for reused r0: ' + name)
if row['seed'] == CFG['validation']['seeds'][0]:
child('gpu', cohort, name, 1)
result = bridge.compare_gpu_repeats(EXP / 'gpu' / cohort / (name + '-r0'),
EXP / 'gpu' / cohort / (name + '-r1'), EXP / 'audit' / ('repeat-' + cohort + '-' + name + '.json'))
bridge.require(result['pass_all'], 'Fixed-input GPU repeat diverged: ' + name)
repeated.append(name)
bridge.require(len(rows) == 60 and len(repeated) == 12, 'Incomplete registered base/repeat block')
result = summarize(cohort)
return result, repeated
def run(stage):
bridge.require(stage in bridge.STAGES, 'Unknown stage')
check(); budget()
bridge.ensure_unstopped(EXP)
receipt = EXP / 'audit' / (stage + '-gate.json')
bridge.require(not receipt.exists() and not (EXP / 'audit' / ('stopped-' + stage + '.json')).exists(),
'Stage already has completion/stop evidence; do not rerun or overwrite')
controls, records = bridge.inventories(EXP, CFG)
try:
if stage == 'controls':
rows = [control(row) for row in controls]
result = dict(stage=stage, pass_all=True, cases=rows, scope='Same-input fresh D18 GPU identity with sealed AJ; no new model or Brian run')
else:
bridge.gate_pass(EXP, 'controls')
cohort = 'seed_validation' if stage.startswith('seed-') else 'novel_to_aj'
if cohort == 'novel_to_aj':
bridge.gate_pass(EXP, 'seed-validation', representation=True, cfg=CFG)
if stage.endswith('-probes'):
rows = [probe(row) for row in bridge.probe_rows(records, CFG, cohort)]
result = dict(stage=stage, pass_all=True, cases=rows,
scope='Two fixed first-odor first-seed mode probes; independent LIF with shared unchanged Torch APL component')
else:
bridge.gate_pass(EXP, 'seed-probes' if cohort == 'seed_validation' else 'novel-probes')
summary, repeats = validation_block(cohort, records)
execution = summary.get('execution_gate_pass') is True
representation = summary.get('representation_gate_pass') is True
if execution:
bridge.validate_summary_coverage(summary, EXP, CFG, cohort)
result = dict(stage=stage, cohort=cohort, pass_all=execution,
execution_gate_pass=execution, representation_gate_pass=representation,
base_count=60, repeat_count=len(repeats), repeat_cases=repeats,
summary='validation/' + cohort + '/summary.json',
summary_sha256=bridge.digest(EXP / 'validation' / cohort / 'summary.json'),
validation_inventory_sha256=bridge.digest(EXP / 'protocol/validation-cases.json'),
scope='Complete fixed representation block; no pooled rescue, learning, tuning, or historically-unseen-odor claim')
save(receipt, result)
if stage.endswith('-validation'):
bridge.require(result['execution_gate_pass'], 'Validation execution/numerical/integrity gate failed')
if not result['representation_gate_pass']:
raise RepresentationGateFailed('Completed fixed validation block failed representation; no later cohort is authorized')
budget()
return result
except Exception as error:
save(EXP / 'audit' / ('stopped-' + stage + '.json'), dict(stage=stage,
type=type(error).__name__, message=str(error), traceback=traceback.format_exc(),
stop_kind='representation_failure' if isinstance(error, RepresentationGateFailed) else 'execution_or_numerical_failure',
representation_failure_is_execution_failure=False,
later_stages_not_permitted=True, partial_outputs_preserved=True,
parameters_changed=False, replacement_seeds_or_odors_selected=False))
raise
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('stage', choices=bridge.STAGES)
args = parser.parse_args()
try:
run(args.stage)
except RepresentationGateFailed:
raise SystemExit(2)
if __name__ == '__main__':
main()