Add error-controlled DP5(4) integration and trusted first-crossing localization, including non-monotonic energy thresholds and representable-time stepping. Preserve adaptive state and independent step/time retry grants across RayPool, refinement and movie scheduling. Expose numerical controls, record actual persistent-sample costs, and add v3 lens-map provenance with legacy v2 RK4 import. Use DP54 by default and select an 8M Schwarzschild maximum step from bounded scans and a two-run 4K comparison. Retain the conservative minimum-step guard and document critical-ray and backend capability limits. Archive self-contained benchmark inputs and raw output; keep fixed RK4 HDR references explicit. Validation: make -B -j4 BUILD_TYPE=Debug test passed; explicit RK4 HDR references have zero differences. Bounded convergence checks, benchmark reproduction, Release build and focused reviews passed. No numerical-relativity backend is added.
125 lines
5.5 KiB
Python
Executable File
125 lines
5.5 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""One-task, at-most-two-attempt 4K comparison (R100, 3840x2160, FOV45).
|
|
|
|
This driver does NOT run as part of routine verification. It refuses to do
|
|
anything unless ``--authorize-two-4k`` is passed explicitly, and an attempt
|
|
ledger makes the task non-repeatable. Any future execution requires a fresh,
|
|
explicit user authorization for that task; authorization never extends to later
|
|
tasks or routine tests.
|
|
|
|
Usage:
|
|
run_4k_pair.py --authorize-two-4k [--out DIR]
|
|
|
|
OUT defaults to <repo>/local/adaptive_bounds_4k (distinct from any older
|
|
one-off ledger). The renderer binary is expected at
|
|
build/Release/schwarzschild_sky (see README for the make command).
|
|
"""
|
|
import argparse
|
|
import hashlib
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import shlex
|
|
import subprocess
|
|
import sys
|
|
import time
|
|
|
|
from mesh_maps import load, compare
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
ENV = dict(os.environ, OMP_NUM_THREADS='16', OMP_DYNAMIC='FALSE')
|
|
BUILD_CMD = 'make -j4 BUILD_TYPE=Release SPACETIME=schwarzschild backend'
|
|
|
|
REFUSAL = (
|
|
'REFUSING TO RUN: the 4K hmax 2-vs-8 pair is a one-task, at-most-two-attempt '
|
|
'experiment. Pass --authorize-two-4k only after an explicit fresh user '
|
|
'authorization for this task. It is not a routine verification target.')
|
|
|
|
|
|
def main():
|
|
ap = argparse.ArgumentParser(description=__doc__)
|
|
ap.add_argument('--authorize-two-4k', action='store_true',
|
|
help='explicit authorization for the two-attempt 4K pair')
|
|
ap.add_argument('--out', default=str(ROOT / 'local/adaptive_bounds_4k'),
|
|
help='output directory (default: local/adaptive_bounds_4k)')
|
|
args = ap.parse_args()
|
|
if not args.authorize_two_4k:
|
|
print(REFUSAL, file=sys.stderr)
|
|
return 2
|
|
out = Path(args.out).resolve()
|
|
binary = ROOT / 'build/Release/schwarzschild_sky'
|
|
if not binary.exists():
|
|
print(f'missing {binary}; build it first:\n {BUILD_CMD}',
|
|
file=sys.stderr)
|
|
return 2
|
|
out.mkdir(parents=True, exist_ok=True)
|
|
ledger = out / 'attempts.json'
|
|
attempts = json.loads(ledger.read_text()) if ledger.exists() else []
|
|
if attempts:
|
|
print('This one-task pair has already been attempted; no reruns '
|
|
'authorized.', file=sys.stderr)
|
|
return 3
|
|
catalog = out / 'single_dim_star.csv'
|
|
catalog.write_text('longitude_deg,latitude_deg,temperature_K,amplitude\n'
|
|
'262.5,-30,6000,1e-30\n')
|
|
with (out / 'cpu.txt').open('w') as f:
|
|
subprocess.run(['lscpu'], stdout=f, check=True)
|
|
metadata = {
|
|
'binary_sha256': hashlib.sha256(binary.read_bytes()).hexdigest(),
|
|
'utc': time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()),
|
|
'source_sha256': {
|
|
p: hashlib.sha256((ROOT / p).read_bytes()).hexdigest()
|
|
for p in ('src/main.c', 'src/geodesic.c', 'src/frame.c')},
|
|
'catalog': 'one negligible synthetic point; no survey catalog',
|
|
'scope': 'README R100 45-degree 3840x2160 coarse16 refine4 jacobian0.2',
|
|
'threads': 16, 'max_attempts': 2,
|
|
'authorization': 'explicit --authorize-two-4k for this task only'}
|
|
(out / 'metadata.json').write_text(json.dumps(metadata, indent=2) + '\n')
|
|
maps = {}
|
|
results = {}
|
|
for upper in (2, 8):
|
|
name = f'r100_4k_hmax{upper}'
|
|
cmd = [str(binary), '--integrator', 'dp54', '--width', '3840',
|
|
'--height', '2160', '--look-ra-deg', '262.5',
|
|
'--look-dec-deg', '-30', '--fov-deg', '45',
|
|
'--observer-radius', '100', '--coarse-cell-pixels', '16',
|
|
'--refine-max-level', '4', '--refine-jacobian-min', '.2',
|
|
'--catalog', str(catalog), '--exposure', '1',
|
|
'--tone-map', 'reinhard', '--psf-min-y', '1e-20',
|
|
'--psf-relative-tail', '1e-4', '--ode-min-step', '1e-12',
|
|
'--ode-max-step', str(upper), '--verbose',
|
|
'--lens-map-output', str(out / (name + '.grlens')),
|
|
'--output', str(out / (name + '.png'))]
|
|
attempts.append({'upper': upper, 'command': shlex.join(cmd),
|
|
'state': 'attempted'})
|
|
ledger.write_text(json.dumps(attempts, indent=2) + '\n')
|
|
start = time.perf_counter()
|
|
with (out / (name + '.log')).open('w') as log:
|
|
log.write('OMP_NUM_THREADS=16 OMP_DYNAMIC=FALSE ' +
|
|
shlex.join(cmd) + '\n')
|
|
log.flush()
|
|
result = subprocess.run(cmd, cwd=ROOT, env=ENV, stdout=log,
|
|
stderr=subprocess.STDOUT, timeout=900)
|
|
attempts[-1].update(returncode=result.returncode,
|
|
wall_seconds=time.perf_counter() - start,
|
|
state='completed')
|
|
ledger.write_text(json.dumps(attempts, indent=2) + '\n')
|
|
if result.returncode:
|
|
raise SystemExit(f'4K attempt failed: {name}; no automatic retry.')
|
|
info, maps[upper] = load(out / (name + '.grlens'))
|
|
info['wall_seconds'] = attempts[-1]['wall_seconds']
|
|
results[name] = info
|
|
print(name, json.dumps(info), flush=True)
|
|
results['comparison'] = compare(maps[2], maps[8])
|
|
p, q = (results[f'r100_4k_hmax{h}']['provenance'] for h in (2, 8))
|
|
assert p['max_step'] == 2 and q['max_step'] == 8
|
|
assert {k: v for k, v in p.items() if k != 'max_step'} == \
|
|
{k: v for k, v in q.items() if k != 'max_step'}
|
|
print('comparison', json.dumps(results['comparison']), flush=True)
|
|
(out / 'summary.json').write_text(json.dumps(results, indent=2) + '\n')
|
|
return 0
|
|
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main())
|