Files
GR-raytracing/benchmarks/adaptive_step_bounds_2026-10-05/run_mesh_reference.py
T
wyj 0a46a7095b Feat: Complete adaptive geodesic tracing with DP54
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.
2026-10-05 20:27:42 -04:00

83 lines
3.3 KiB
Python
Executable File

#!/usr/bin/env python3
"""Two bounded tight-tolerance mesh references and comparisons (no 4K).
Usage:
run_mesh_reference.py [OUT_DIR]
OUT_DIR defaults to <repo>/local/adaptive_bounds_mesh and must already contain
the eight ``<scene>_hmax<h>.grlens`` maps from ``run_mesh.py``. The renderer
binary is expected at build/Release/schwarzschild_sky (see README for the make
command). Same one-point synthetic dim catalog, no external CSV.
"""
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]
OUT = Path(sys.argv[1]).resolve() if len(sys.argv) > 1 else \
ROOT / 'local/adaptive_bounds_mesh'
BIN = ROOT / 'build/Release/schwarzschild_sky'
BUILD_CMD = 'make -j4 BUILD_TYPE=Release SPACETIME=schwarzschild backend'
ENV = dict(os.environ, OMP_NUM_THREADS='8', OMP_DYNAMIC='FALSE')
def scene_args(scene):
if scene == 'r100':
return ['--observer-radius', '100', '--look-ra-deg', '262.5',
'--look-dec-deg', '-30', '--fov-deg', '45',
'--refine-jacobian-min', '.2']
return ['--observer-position', '2.1', '0', '0', '--observer-velocity',
'0', '0', '0', '--look-ra-deg', '0', '--look-dec-deg', '0',
'--fov-deg', '90']
def main():
if not BIN.exists():
raise SystemExit(f'missing {BIN}; build it first:\n {BUILD_CMD}')
OUT.mkdir(parents=True, exist_ok=True)
catalog = OUT / 'single_dim_star.csv'
if not catalog.exists():
catalog.write_text('longitude_deg,latitude_deg,temperature_K,amplitude\n'
'262.5,-30,6000,1e-30\n')
results = {}
for scene in ('r100', 'r2p1'):
name = scene + '_tight_reference'
cmd = [str(BIN), '--integrator', 'dp54', '--width', '320',
'--height', '180', '--coarse-cell-pixels', '8',
'--refine-max-level', '3', '--catalog', str(catalog),
'--exposure', '1', '--psf-min-y', '1e-20',
'--psf-relative-tail', '1e-4', '--ode-min-step', '1e-12',
'--ode-max-step', '.5', '--ode-rtol', '1e-12',
'--ode-atol-x', '1e-12', '--ode-atol-pi', '1e-12',
'--ode-atol-l', '1e-12', '--verbose',
'--lens-map-output', str(OUT / (name + '.grlens')),
'--output', str(OUT / (name + '.png')), *scene_args(scene)]
start = time.perf_counter()
with (OUT / (name + '.log')).open('w') as log:
log.write('OMP_NUM_THREADS=8 OMP_DYNAMIC=FALSE ' +
shlex.join(cmd) + '\n')
log.flush()
subprocess.run(cmd, cwd=ROOT, env=ENV, stdout=log,
stderr=subprocess.STDOUT, check=True, timeout=180)
info, ref = load(OUT / (name + '.grlens'))
info['wall_seconds'] = time.perf_counter() - start
results[name] = info
print(name, json.dumps(info), flush=True)
for upper in (.5, 2, 8, 32):
_, vertices = load(OUT / f'{scene}_hmax{upper:g}.grlens')
result = compare(ref, vertices)
results[f'{scene}_ref_vs_{upper:g}'] = result
print(scene, 'ref vs', upper, json.dumps(result), flush=True)
(OUT / 'reference_summary.json').write_text(
json.dumps(results, indent=2) + '\n')
if __name__ == '__main__':
main()