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.
This commit is contained in:
wyj committed 2026-10-05 20:27:42 -04:00
1 parent c894fdb11a
commit 0a46a7095b
48 files changed
+11491 -380

No files matched your search

+545
View File
@@ -0,0 +1,545 @@
#!/usr/bin/env python3
"""Exercise the production DP54 default, v3 lens-map provenance/replay and the
adaptive retry/budget policy from the CLI.
The production default is now adaptive Dormand-Prince 5(4). These checks
require that a run without --integrator exports wire code 1 and is physically
and bit-for-bit equal to an explicit --integrator dp54 run, so `make test`
truly covers the production default rather than only the explicit path.
--integrator rk4 stays available for the legacy wire code and for a
fixed-step reference convergence check. Small CPU 16x8/32x16 scenes keep the
runtime short; physical comparisons use stored lens-map endpoints, not only the
rendered PNG.
"""
import os
import re
import struct
import subprocess
import sys
import tempfile
import zlib
from pathlib import Path
# Keep scratch data inside the pre-approved OpenCode scratch directory instead
# of creating directories directly under the system temporary root.
TMP_ROOT = Path('/tmp/opencode')
TMP_ROOT.mkdir(parents=True, exist_ok=True)
BUILD = Path(sys.argv[1] if len(sys.argv) > 1 else 'build/Release').resolve()
TESTDIR = Path(sys.argv[2]).resolve() if len(sys.argv) > 2 else BUILD
ENV = dict(os.environ, OMP_NUM_THREADS='4')
# v3 wire offsets (see src/lens_map.c). The header is deliberately not part of
# the payload CRC.
VERSION_OFFSET = 8
FRAME_COUNT_OFFSET = 32
PROVENANCE_OFFSET = 40
PROVENANCE_V3_OFFSET = 100
VERTEX_COUNT_OFFSET = 200
TRIANGLE_COUNT_OFFSET = 208
VERTEX_START = 224
VERTEX_SIZE = 108
TRIANGLE_SIZE = 32
ATOL_FIELDS = ('atol_x', 'atol_Pi', 'atol_L', 'rtol', 'min_step', 'max_step',
'max_lookback_time', 'retry_lookback_increment',
'max_total_lookback_time')
# Machine-roundoff floor for comparing two adaptive integrations; below this a
# difference carries no convergence information.
ROUNDOFF_FLOOR = 1e-12
ENDPOINT_ASSERT = 1e-6
def run(binary, *args, ok=True, env=ENV):
result = subprocess.run([str(binary), *map(str, args)], env=env,
capture_output=True, text=True)
if (result.returncode == 0) != ok:
raise AssertionError(
f'{binary.name} {args}: rc={result.returncode}\n{result.stderr}')
return result
def image_payload(path):
data = path.read_bytes()
assert data[:8] == b'\x89PNG\r\n\x1a\n', f'not a PNG: {path}'
offset, compressed = 8, bytearray()
while offset < len(data):
count, kind = struct.unpack_from('>I4s', data, offset)
payload = data[offset + 8:offset + 8 + count]
if kind == b'IDAT':
compressed.extend(payload)
offset += count + 12
return bytes(zlib.decompress(compressed))
def map_provenance(path):
data = path.read_bytes()
assert data[:8] == b'GRLENS\x01\x00'
version = struct.unpack_from('<I', data, VERSION_OFFSET)[0]
frame_count = struct.unpack_from('<Q', data, FRAME_COUNT_OFFSET)[0]
base = PROVENANCE_OFFSET
prov = {}
prov['threshold_kind'], prov['threshold_policy_version'] = struct.unpack_from(
'<II', data, base)
prov['threshold_value'] = struct.unpack_from('<d', data, base + 8)[0]
(prov['retry_step_increment'], prov['max_total_steps'], prov['max_level'],
prov['integrator']) = struct.unpack_from('<IIII', data, base + 16)
prov['min_edge_pixels'] = struct.unpack_from('<d', data, base + 32)[0]
prov['min_area_pixels2'] = struct.unpack_from('<d', data, base + 40)[0]
prov['coordinate_time_step'] = struct.unpack_from('<d', data, base + 48)[0]
prov['initial_max_steps'] = struct.unpack_from('<I', data, base + 56)[0]
if version == 3:
off = PROVENANCE_V3_OFFSET
for name in ATOL_FIELDS:
prov[name] = struct.unpack_from('<d', data, off)[0]
off += 8
prov['max_consecutive_rejections'] = struct.unpack_from('<I', data, off)[0]
return version, frame_count, prov
def map_vertices(path):
data = path.read_bytes()
version = struct.unpack_from('<I', data, VERSION_OFFSET)[0]
assert version == 3, f'expected a v3 map, got v{version}'
vertices = struct.unpack_from('<Q', data, VERTEX_COUNT_OFFSET)[0]
triangles = struct.unpack_from('<Q', data, TRIANGLE_COUNT_OFFSET)[0]
offset = VERTEX_START
values = []
for _ in range(vertices):
values.append(struct.unpack_from('<9dIIIQQQ', data, offset))
offset += VERTEX_SIZE
return values, data[offset:offset + triangles * TRIANGLE_SIZE]
def endpoint_deviation(a, b):
"""(mismatched provenance count, max direction/log-g deviation).
Vertices whose end_id/outcome/reason differ are counted as mismatches and
excluded from the numeric deviation; the caller requires zero mismatches.
"""
mismatches = 0
worst = 0.0
assert len(a) == len(b)
for x, y in zip(a, b):
if x[9:12] != y[9:12]:
mismatches += 1
continue
for k in (3, 4, 5, 6, 7, 8):
worst = max(worst, abs(x[k] - y[k]))
return mismatches, worst
def trace_cost(stderr, label):
match = re.search(label + r' trace cost: accepted=(\d+) rejected=(\d+) '
r'rhs=(\d+)', stderr)
return None if match is None else match.groups()
def sum_rhs(vertices):
return sum(v[14] for v in vertices)
with tempfile.TemporaryDirectory(prefix='gr-adaptive-cli-',
dir=str(TMP_ROOT)) as directory:
tmp = Path(directory)
for backend in ('minkowski', 'schwarzschild'):
binary = BUILD / f'{backend}_sky'
if not binary.exists():
print(f'{backend}: binary absent, skipping', flush=True)
continue
help_text = run(binary, '--help').stdout
for option in ('--integrator', '--ode-rtol', '--ode-atol-x',
'--ode-atol-pi', '--ode-atol-l', '--ode-initial-step',
'--ode-min-step', '--ode-max-step',
'--ode-max-rejections', '--trace-max-steps',
'--trace-lookback-time', '--retry-step-increment',
'--max-total-steps', '--retry-lookback-increment',
'--max-total-lookback-time'):
assert option in help_text, (backend, option)
ext = 'png' if '.png' in help_text else 'ppm'
hdr_available = '--hdr-output' in help_text
hdr_args = ['--hdr-output'] if hdr_available else []
common = ['--catalog', 'assets/sky_grid_5deg.csv', '--width', 32,
'--height', 16, '--fov-deg', 80, '--exposure', 1e-3,
'--coarse-cell-pixels', 8, '--refine-max-level', 0,
'--psf-relative-tail', 1e-4]
def single(name, *options, ok=True, env=ENV, use_common=common):
out = tmp / f'{backend}_{name}.{ext}'
result = run(binary, *use_common, '--output', out, *options,
ok=ok, env=env)
return out, result
# 1) The production default (no --integrator) must be DP54 and must
# match an explicit --integrator dp54 run physically and in its
# PNG/HDR output.
dflt_map = tmp / f'{backend}_dflt.grlens'
dflt_out, dflt_run = single('dflt', *hdr_args, '--verbose',
'--lens-map-output', dflt_map)
version, frame_count, dflt_prov = map_provenance(dflt_map)
assert version == 3 and frame_count == 1
assert dflt_prov['integrator'] == 1, dflt_prov
assert dflt_prov['min_step'] == 1e-12, dflt_prov
assert dflt_prov['max_step'] == {'minkowski': 16.0,
'schwarzschild': 8.0}[backend], dflt_prov
assert dflt_prov['min_step'] <= dflt_prov['coordinate_time_step'] \
<= dflt_prov['max_step']
assert dflt_prov['atol_x'] > 0 and dflt_prov['rtol'] > 0
assert dflt_prov['max_lookback_time'] > 0
assert dflt_prov['max_consecutive_rejections'] > 0
expl_map = tmp / f'{backend}_expl.grlens'
expl_out, _ = single('expl', *hdr_args, '--integrator', 'dp54',
'--lens-map-output', expl_map)
assert dflt_map.read_bytes() == expl_map.read_bytes(), \
'default map differs from explicit dp54'
assert image_payload(dflt_out) == image_payload(expl_out)
if hdr_available:
dflt_hdr = dflt_out.with_name(dflt_out.stem + '_HDR.fits')
expl_hdr = expl_out.with_name(expl_out.stem + '_HDR.fits')
assert dflt_hdr.read_bytes() == expl_hdr.read_bytes()
dflt_vertices, _ = map_vertices(dflt_map)
# 2) The legacy RK4 wire code must be 0 and its cost counters must be
# real (nonzero RHS evaluations), not legacy zeros.
rk4_map = tmp / f'{backend}_rk4.grlens'
single('rk4', '--integrator', 'rk4', '--lens-map-output', rk4_map)
_, _, rk4_prov = map_provenance(rk4_map)
assert rk4_prov['integrator'] == 0, rk4_prov
rk4_vertices, _ = map_vertices(rk4_map)
assert sum_rhs(rk4_vertices) > 0, 'RK4 RHS cost counters are not real'
# 3) Same-camera tolerance convergence with three levels. Outcome,
# reason and end must not change between levels, and the deviation
# from the tightest reference must shrink as the tolerance tightens.
# These are local ODE tolerances, not a global sky-error bound.
tol_maps = {}
for tol in ('1e-7', '1e-9', '1e-12'):
m = tmp / f'{backend}_tol_{tol}.grlens'
single(f'tol_{tol}', '--integrator', 'dp54', '--ode-rtol', tol,
'--ode-atol-x', tol, '--ode-atol-pi', tol,
'--ode-atol-l', tol, '--lens-map-output', m)
tol_maps[tol] = map_vertices(m)[0]
mism7, err7 = endpoint_deviation(tol_maps['1e-7'], tol_maps['1e-12'])
mism9, err9 = endpoint_deviation(tol_maps['1e-9'], tol_maps['1e-12'])
assert mism7 == 0 and mism9 == 0, \
f'{backend}: tolerance levels disagree on outcome/end'
assert err9 < ENDPOINT_ASSERT, (backend, 'default vs tight', err9)
assert err7 + ROUNDOFF_FLOOR >= err9, \
f'{backend}: tightening tolerance did not reduce error ' \
f'({err7} -> {err9})'
print(f'{backend}: default==dp54, tol errors 1e-7={err7:.3g} '
f'1e-9={err9:.3g}', flush=True)
# 4) Render-only replay of the default map must be bit-identical and
# its stored statistics must equal the live trace cost.
replay_out = tmp / f'{backend}_replay.{ext}'
replay_run = run(binary, *common, *hdr_args, '--lens-map-input',
dflt_map, '--verbose', '--output', replay_out)
assert image_payload(replay_out) == image_payload(dflt_out)
if hdr_available:
replay_hdr = replay_out.with_name(replay_out.stem + '_HDR.fits')
assert dflt_out.with_name(dflt_out.stem + '_HDR.fits').read_bytes() \
== replay_hdr.read_bytes()
live_cost = trace_cost(dflt_run.stderr, 'Frame 0')
replay_cost = trace_cost(replay_run.stderr, 'Imported map')
assert live_cost is not None and replay_cost is not None
assert live_cost == replay_cost, (live_cost, replay_cost)
# 5) Explicit zeros in the retry policy must survive, not be filled in
# by the derived defaults.
zero_step_map = tmp / f'{backend}_zero_step.grlens'
single('zero_step', '--integrator', 'dp54', '--retry-step-increment',
'0', '--lens-map-output', zero_step_map)
_, _, zero_step = map_provenance(zero_step_map)
assert zero_step['retry_step_increment'] == 0, zero_step
assert zero_step['max_total_steps'] > 0, zero_step
zero_time_map = tmp / f'{backend}_zero_time.grlens'
single('zero_time', '--integrator', 'dp54',
'--retry-lookback-increment', '0', '--lens-map-output',
zero_time_map)
_, _, zero_time = map_provenance(zero_time_map)
assert zero_time['retry_lookback_increment'] == 0, zero_time
assert zero_time['max_total_lookback_time'] >= \
zero_time['max_lookback_time'], zero_time
# 6) RK4 rejects every DP-only option rather than silently ignoring it.
rk4_errors = [
(['--integrator', 'rk4', '--ode-rtol', 1e-9], 'applies only'),
(['--integrator', 'rk4', '--ode-min-step', 1e-9], 'applies only'),
(['--integrator', 'rk4', '--ode-max-step', 1e-3], 'applies only'),
(['--integrator', 'rk4', '--ode-max-rejections', 4], 'applies only'),
(['--integrator', 'rk4', '--trace-lookback-time', 1], 'applies only'),
(['--integrator', 'rk4', '--retry-lookback-increment', 1], 'applies only'),
(['--integrator', 'rk4', '--max-total-lookback-time', 2], 'applies only'),
]
# DP cross-field validation and malformed values fail immediately.
invalid = [
(['--integrator', 'bogus'], None),
(['--integrator'], None),
(['--integrator', 'dp54', '--ode-rtol', 0], None),
(['--integrator', 'dp54', '--ode-rtol', -1], None),
(['--integrator', 'dp54', '--ode-atol-x', 'nan'], None),
(['--integrator', 'dp54', '--ode-max-rejections', 0], None),
(['--integrator', 'dp54', '--trace-max-steps', 0], None),
(['--integrator', 'dp54', '--ode-initial-step', 5,
'--ode-max-step', 1], 'min <= initial <= max'),
(['--integrator', 'dp54', '--ode-min-step', 4,
'--ode-initial-step', 2], 'min <= initial <= max'),
(['--integrator', 'dp54', '--trace-lookback-time', 0], None),
(['--integrator', 'dp54', '--retry-step-increment', -1], None),
]
for options, message in rk4_errors + invalid:
missing = tmp / 'adaptive_should_not_exist.csv'
result = run(binary, '--catalog', missing, *options, ok=False)
if message:
assert message in result.stderr, (options, result.stderr)
assert not missing.exists(), result.stderr
# 7) Replay must consume the stored policy: explicit tracing options
# cannot be layered on top of --lens-map-input.
conflict = run(binary, *common, '--lens-map-input', dflt_map,
'--integrator', 'rk4', '--output',
tmp / 'conflict.png', ok=False)
assert 'cannot be combined with --lens-map-input' in conflict.stderr, \
conflict.stderr
conflict2 = run(binary, *common, '--lens-map-input', dflt_map,
'--trace-max-steps', 100, '--output',
tmp / 'conflict2.png', ok=False)
assert 'cannot be combined with --lens-map-input' in conflict2.stderr, \
conflict2.stderr
conflict3 = run(binary, *common, '--lens-map-input', dflt_map,
'--integrator', 'dp54', '--ode-rtol', 1e-9,
'--output', tmp / 'conflict3.png', ok=False)
assert 'cannot be combined with --lens-map-input' in conflict3.stderr, \
conflict3.stderr
# 8) Single vs movie: the same physical observer event must agree, and
# threads/slabs must not change the stored adaptive endpoints.
track = tmp / f'{backend}.csv'
observer_test = TESTDIR / f'test_observer_{backend}'
if observer_test.exists():
run(observer_test, track)
moving_single = tmp / f'{backend}_moving.grlens'
single('moving', '--observer-position', 3, -4, 5,
'--observer-velocity', 0.2, -0.1, 0.3,
'--look-ra-deg', 37, '--look-dec-deg', -23,
'--camera-roll-deg', 19, '--lens-map-output', moving_single)
movie_map = tmp / f'{backend}_movie.grlens'
run(binary, *common, '--observer-track', track, '--frames-dir', tmp,
'--frames-prefix', f'{backend}_movie', '--duration', 0,
'--fps', 1, '--lens-map-output', movie_map)
single_v, _ = map_vertices(moving_single)
movie_v, _ = map_vertices(movie_map)
mism, dev = endpoint_deviation(single_v, movie_v)
assert mism == 0 and dev < ENDPOINT_ASSERT, (mism, dev)
thread_maps = []
for threads in (1, 2, 4):
m = tmp / f'{backend}_movie_{threads}thr.grlens'
run(binary, *common, '--observer-track', track, '--frames-dir',
tmp, '--frames-prefix', f'{backend}_m{threads}',
'--duration', 0, '--fps', 1, '--lens-map-output', m,
env=dict(ENV, OMP_NUM_THREADS=str(threads)))
thread_maps.append(m)
reference = thread_maps[0].read_bytes()
for m in thread_maps[1:]:
assert m.read_bytes() == reference, \
f'{backend}: DP movie map changed across threads'
slab_maps = []
for slab in (2, 8, 64):
m = tmp / f'{backend}_slab_{slab}.grlens'
run(binary, *common, '--observer-track', track, '--frames-dir',
tmp, '--frames-prefix', f'{backend}_s{slab}',
'--slab-duration', slab, '--duration', 0, '--fps', 1,
'--lens-map-output', m)
slab_maps.append(m)
base_v, _ = map_vertices(slab_maps[0])
for m in slab_maps[1:]:
other_v, _ = map_vertices(m)
mism, dev = endpoint_deviation(base_v, other_v)
assert mism == 0 and dev < ENDPOINT_ASSERT, (mism, dev)
print(f'{backend}: DP movie threads/slabs agree', flush=True)
# 9) Budget: a tiny initial coordinate-time budget leaves UNRESOLVED
# rays; with no room to grow the publication gate refuses the frame,
# while retry increments that can grow resolve it.
if backend == 'schwarzschild':
refused = tmp / f'{backend}_refused.{ext}'
small = ['--integrator', 'dp54', '--trace-lookback-time', 1e-6,
'--retry-lookback-increment', 0,
'--max-total-lookback-time', 1e-6]
result = run(binary, *common, '--output', refused, *small, ok=False)
assert 'Incomplete render refused' in result.stderr, result.stderr
assert not refused.exists()
allow = tmp / f'{backend}_allow.{ext}'
run(binary, *common, '--output', allow, '--allow-incomplete', *small)
assert allow.exists()
direct = tmp / f'{backend}_direct.{ext}'
direct_result = subprocess.run(
[str(binary), *map(str, common), '--output', str(direct),
'--integrator', 'dp54', '--trace-lookback-time', '4000'],
env=ENV, capture_output=True, text=True)
if direct_result.returncode == 0:
retried = tmp / f'{backend}_retried.{ext}'
run(binary, *common, '--output', retried, '--integrator', 'dp54',
'--trace-lookback-time', 1e-6,
'--retry-lookback-increment', 25,
'--max-total-lookback-time', 4000)
assert image_payload(retried)
print('schwarzschild: retry budget resolves previously '
'unresolved rays', flush=True)
else:
print('schwarzschild: direct budget scene still unresolved; '
'retry-resolution case skipped', flush=True)
# 10) Fixed-step reference convergence on a small scene. The
# accepted budget is explicit and large so the finer step does
# not silently shorten the traced history. Both fixed-step
# levels and the DP54 default must agree below ENDPOINT_ASSERT.
# If the reference itself does not converge this must FAIL and
# be reported, never loosened into a false zero.
ref_common = ['--catalog', 'assets/sky_grid_5deg.csv', '--width',
16, '--height', 8, '--fov-deg', 80, '--exposure',
1e-3, '--coarse-cell-pixels', 8, '--refine-max-level',
0, '--psf-relative-tail', 1e-4]
def ref_map(tag, *options):
m = tmp / f'{backend}_ref_{tag}.grlens'
run(binary, *ref_common, '--output',
tmp / f'{backend}_ref_{tag}.{ext}', '--lens-map-output', m,
*options)
return map_vertices(m)[0]
rk4_04 = ref_map('rk4_04', '--integrator', 'rk4',
'--ode-initial-step', 0.04, '--trace-max-steps',
'262144')
rk4_02 = ref_map('rk4_02', '--integrator', 'rk4',
'--ode-initial-step', 0.02, '--trace-max-steps',
'262144')
dp_tight = ref_map('dp_tight', '--ode-rtol', '1e-12',
'--ode-atol-x', '1e-12', '--ode-atol-pi',
'1e-12', '--ode-atol-l', '1e-12')
assert sum(1 for v in rk4_02 if v[10] == 0) > 0, \
'reference scene has no escaped ray'
mism, ref_dev = endpoint_deviation(rk4_04, rk4_02)
assert mism == 0, 'RK4 reference levels disagree on outcome/end'
assert ref_dev < ENDPOINT_ASSERT, \
f'RK4 reference not converged: {ref_dev}; report to parent'
mism, dp_dev = endpoint_deviation(dp_tight, rk4_02)
assert mism == 0, 'DP54 default disagrees with RK4 reference'
assert dp_dev < ENDPOINT_ASSERT, (dp_dev,)
print(f'schwarzschild: RK4 ref convergence {ref_dev:.3g}, '
f'DP default vs fine RK4 {dp_dev:.3g}', flush=True)
print(f'{backend}: adaptive CLI checks passed', flush=True)
# Alcubierre production-default smoke: the DP54 default policy must
# validate and the migrated lookback budget must actually cover the warp
# bubble feature (rays integrate and escape) instead of pre-routing all
# misses. No movie/thread sweep for this third backend.
alc = BUILD / 'alcubierre_sky'
if not alc.exists():
print('alcubierre: binary absent, skipping', flush=True)
else:
alc_help = run(alc, '--help').stdout
ext = 'png' if '.png' in alc_help else 'ppm'
alc_map = tmp / 'alcubierre_default.grlens'
alc_out = tmp / f'alcubierre_default.{ext}'
run(alc, '--alcubierre-vs', 0.3, '--alcubierre-radius', 1,
'--alcubierre-sigma', 1, '--catalog', 'assets/sky_grid_5deg.csv',
'--width', 16, '--height', 8, '--fov-deg', 80, '--exposure', 1e-3,
'--coarse-cell-pixels', 8, '--refine-max-level', 0,
'--psf-relative-tail', 1e-4, '--verbose', '--output', alc_out,
'--lens-map-output', alc_map)
_, _, alc_prov = map_provenance(alc_map)
assert alc_prov['integrator'] == 1, alc_prov
assert alc_prov['min_step'] <= alc_prov['coordinate_time_step'] \
<= alc_prov['max_step']
assert alc_prov['max_lookback_time'] > 0
alc_vertices, _ = map_vertices(alc_map)
outcomes = {v[10] for v in alc_vertices}
assert 3 not in outcomes, f'Alcubierre default left INCOMPLETE rays'
assert any(v[10] == 0 for v in alc_vertices), \
'Alcubierre default produced no escaped ray'
assert sum_rhs(alc_vertices) > 0, \
'Alcubierre default pre-routed every ray; lookback misses feature'
assert image_payload(alc_out)
# The migrated coordinate-time coverage is the physical geometry budget
# margin*4*escape/(1-|v_s|) with escape = R + 20/sigma, and it is
# independent of the accepted-step count and of the chosen initial
# step. Two cheap maps with different resource overrides must keep the
# same lookback.
alc_escape = 1.0 + 20.0 / 1.0 # R + 20/sigma for R=sigma=1
alc_sep = 1.0 - abs(0.3)
alc_expected_lookback = 1.25 * 4.0 * alc_escape / alc_sep
assert abs(alc_prov['max_lookback_time'] - alc_expected_lookback) \
< 1e-12, alc_prov['max_lookback_time']
def alc_map_with(tag, *options):
m = tmp / f'alcubierre_{tag}.grlens'
run(alc, '--alcubierre-vs', 0.3, '--alcubierre-radius', 1,
'--alcubierre-sigma', 1, '--catalog', 'assets/sky_grid_5deg.csv',
'--width', 16, '--height', 8, '--fov-deg', 80, '--exposure',
1e-3, '--coarse-cell-pixels', 8, '--refine-max-level', 0,
'--psf-relative-tail', 1e-4, '--allow-incomplete',
'--output', tmp / f'alcubierre_{tag}.{ext}',
'--lens-map-output', m, *options)
return map_provenance(m)[2]
steps_override = alc_map_with('steps_override', '--trace-max-steps', 8)
assert steps_override['initial_max_steps'] == 8
assert abs(steps_override['max_lookback_time'] - alc_expected_lookback) \
< 1e-12, steps_override
step_override = alc_map_with('step_override', '--ode-initial-step', 0.02)
assert abs(step_override['coordinate_time_step'] - 0.02) < 1e-15
assert abs(step_override['max_lookback_time'] - alc_expected_lookback) \
< 1e-12, step_override
# Extreme separation (v_s = 0.99999999) where the legacy fixed-step
# estimate far exceeds the cap. The DP path must accept an explicit
# tiny coordinate-time budget instead of being rejected at startup by
# the fixed-step guard, and it must actually exercise the quota path
# (UNRESOLVED rays or real RHS work), not pre-route everything to
# escapes. --allow-incomplete publishes the diagnostic frame.
extreme = ['--alcubierre-vs', 0.99999999, '--alcubierre-radius', 1,
'--alcubierre-sigma', 1, '--catalog',
'assets/sky_grid_5deg.csv', '--width', 8, '--height', 4,
'--fov-deg', 80, '--exposure', 1e-3,
'--coarse-cell-pixels', 4, '--refine-max-level', 0,
'--psf-relative-tail', 1e-4,
'--observer-position', 0, 0, 0,
'--observer-velocity', 0.99999999, 0, 0,
'--look-ra-deg', 0, '--look-dec-deg', 0]
ext_map = tmp / 'alcubierre_extreme.grlens'
run(alc, *extreme, '--integrator', 'dp54',
'--trace-lookback-time', 1, '--trace-max-steps', 4,
'--max-total-steps', 4, '--retry-step-increment', 0,
'--max-total-lookback-time', 1, '--retry-lookback-increment', 0,
'--allow-incomplete', '--output',
tmp / f'alcubierre_extreme.{ext}', '--lens-map-output', ext_map)
_, _, ext_prov = map_provenance(ext_map)
assert ext_prov['integrator'] == 1
assert abs(ext_prov['max_lookback_time'] - 1.0) < 1e-15, ext_prov
assert ext_prov['initial_max_steps'] == 4, ext_prov
ext_vertices, _ = map_vertices(ext_map)
assert any(v[10] == 2 for v in ext_vertices) or \
sum_rhs(ext_vertices) > 0, \
'extreme Alcubierre case did not exercise the quota path'
# The same parameters without explicit DP budgets keep the legacy
# fixed-step startup guard, which still rejects the estimated domain.
rk4_extreme = run(
alc, '--integrator', 'rk4', '--alcubierre-vs', 0.99999999,
'--alcubierre-radius', 1, '--alcubierre-sigma', 1, '--catalog',
'assets/sky_grid_5deg.csv', '--width', 8, '--height', 4,
'--fov-deg', 80, '--exposure', 1e-3, '--coarse-cell-pixels', 4,
'--refine-max-level', 0, '--psf-relative-tail', 1e-4,
'--output', tmp / f'alcubierre_rk4_extreme.{ext}', ok=False)
assert rk4_extreme.returncode == 2, rk4_extreme.stderr
assert 'cap' in rk4_extreme.stderr, rk4_extreme.stderr
print('alcubierre: default DP config validated and produced escapes; '
'extreme DP quota path ok, RK4 guard still rejects', flush=True)
+52 -38
View File
@@ -307,10 +307,11 @@ static void test_boundary_semantics_minkowski(void) {
spacetime_destroy(&source);
}
static void test_boundary_semantics_generic(void) {
/* velocity_constant == 0 forces the generic bracketed driver. A finite
* history bounds the outward/tangent searches, which must not be reported
* as entries (they end as TIME_RANGE_EXHAUSTED instead). */
static void test_nonconstant_route_unsupported(void) {
/* velocity_constant == 0 signals an arbitrary accelerated worldtube. The
* camera-inside containment test still runs first; once the ray starts
* outside, the non-constant route is refused explicitly instead of being
* searched with a coarse bracket. */
SyntheticContext context = {.radius = 10.0,
.valid_t_min = -100.0,
.constant = 0};
@@ -321,15 +322,15 @@ static void test_boundary_semantics_generic(void) {
&source, &on_boundary, (double[]){-1.0, 0.0, 0.0}, &route);
CHECK(inward == ASYMPTOTIC_OK &&
route.kind == ASYMPTOTIC_ROUTE_INSIDE,
"generic on-boundary inward is inside");
"nonconstant on-boundary inward is still inside");
const AsymptoticStatus outward = asymptotic_route_camera(
&source, &on_boundary, (double[]){1.0, 0.0, 0.0}, &route);
CHECK(outward == ASYMPTOTIC_TIME_RANGE_EXHAUSTED,
"generic on-boundary outward is not an entry");
CHECK(outward == ASYMPTOTIC_UNSUPPORTED,
"nonconstant on-boundary outward is unsupported");
const AsymptoticStatus tangent = asymptotic_route_camera(
&source, &on_boundary, (double[]){0.0, 1.0, 0.0}, &route);
CHECK(tangent == ASYMPTOTIC_TIME_RANGE_EXHAUSTED,
"generic on-boundary tangent is not an entry");
CHECK(tangent == ASYMPTOTIC_UNSUPPORTED,
"nonconstant on-boundary tangent is unsupported");
}
static void test_negative_radius_root_guard(void) {
@@ -525,11 +526,12 @@ static void test_interior_crossing_bisection_failure(void) {
"interior crossing bisection propagates history exhaustion");
}
static void test_generic_bisection_failure(void) {
/* The isolated invalid window lands on a bisection midpoint while the
* bracket endpoints stay valid, so only the bisection can see it. With the
* strict F < 0 entry test, the bracket is s = 80 (F == 0) to s = 90
* (F < 0), so the first midpoint is t = -85. */
static void test_nonconstant_preroute_unsupported(void) {
/* The accelerated fixture is refused explicitly at the route entry; its
* former coarse bracketed search (and the bisection-midpoint history-hole
* case it exercised) is replaced by the unsupported contract. The history
* reason itself stays covered by the constant and piecewise-constant
* worldtube tests below. */
SyntheticContext context = {.radius = 20.0,
.valid_t_min = -1.0e30,
.constant = 0,
@@ -538,9 +540,26 @@ static void test_generic_bisection_failure(void) {
SpacetimeSource source = {.ops = &synthetic_ops, .context = &context};
const ObserverState camera = flat_observer(100.0, 0.0, 0.0);
AsymptoticRoute route;
CHECK(asymptotic_route_camera(&source, &camera, (double[]){-1.0, 0.0, 0.0},
&route) == ASYMPTOTIC_UNSUPPORTED,
"nonconstant worldtube route is unsupported");
}
static void test_piecewise_constant_history_hole(void) {
/* Piecewise-constant motion stays supported, including its history
* contract: when the segment walk steps past the valid history, the route
* reports TIME_RANGE_EXHAUSTED and never disguises it as a miss. */
SyntheticContext context = {.radius = 10.0,
.valid_t_min = -3.0,
.constant = 1,
.segment_t = -5.0,
.has_segment = 1};
SpacetimeSource source = {.ops = &synthetic_ops, .context = &context};
const ObserverState camera = flat_observer(50.0, 0.0, 0.0);
AsymptoticRoute route;
CHECK(asymptotic_route_camera(&source, &camera, (double[]){-1.0, 0.0, 0.0},
&route) == ASYMPTOTIC_TIME_RANGE_EXHAUSTED,
"generic worldtube bisection propagates sample failure");
"piecewise-constant segment history hole is exhausted");
}
static void test_interior_history_exhaustion(void) {
@@ -641,36 +660,30 @@ static void test_moving_sphere(void) {
"co-moving ray misses");
}
static void test_accelerated_worldtube(void) {
static void test_accelerated_worldtube_unsupported(void) {
/* A genuinely accelerating (non-constant velocity) worldtube has no strict
* relative-motion interval bound, so the route is explicitly unsupported.
* Its former finite-history branch is covered by the constant and
* piecewise-constant history tests instead. */
SyntheticContext context = {.vx = 0.0, .accel = 0.02, .radius = 20.0,
.valid_t_min = -1.0e30, .constant = 0};
SpacetimeSource source = {.ops = &synthetic_ops, .context = &context};
const ObserverState camera = flat_observer(100.0, 0.0, 0.0);
AsymptoticRoute route;
CHECK(asymptotic_route_camera(&source, &camera, (double[]){-1.0, 0.0, 0.0},
&route) == ASYMPTOTIC_OK &&
route.kind == ASYMPTOTIC_ROUTE_ENTRY,
"accelerated worldtube entry");
double value;
CHECK(asymptotic_worldtube_value(&source, route.end_id, route.activate_t,
route.x, &value) == 0 &&
fabs(value) <= 1e-13 * 20.0 * 20.0,
"accelerated entry lies on worldtube");
CHECK(route.activate_t < -40.0 && route.activate_t > -60.0,
"accelerated entry time in range");
context.valid_t_min = -30.0;
CHECK(asymptotic_route_camera(&source, &camera, (double[]){-1.0, 0.0, 0.0},
&route) == ASYMPTOTIC_TIME_RANGE_EXHAUSTED,
"exhausted history is not a miss");
&route) == ASYMPTOTIC_UNSUPPORTED,
"accelerated worldtube route is unsupported");
}
static void test_accelerated_segment(void) {
static void test_piecewise_segment_entry(void) {
/* The two-segment motion is piecewise constant, so velocity_constant == 1
* per segment and the closed quadratic path must still find the entry in
* the second segment (the analytic segmented first-entry case). */
SyntheticContext context = {.vx = 0.0,
.accel = 0.0,
.radius = 20.0,
.valid_t_min = -1.0e30,
.constant = 0,
.constant = 1,
.segment_t = -50.0,
.has_segment = 1};
SpacetimeSource source = {.ops = &synthetic_ops, .context = &context};
@@ -679,7 +692,7 @@ static void test_accelerated_segment(void) {
CHECK(asymptotic_route_camera(&source, &camera, (double[]){-1.0, 0.0, 0.0},
&route) == ASYMPTOTIC_OK &&
route.kind == ASYMPTOTIC_ROUTE_ENTRY,
"cross-segment entry");
"piecewise cross-segment entry");
CHECK(route.activate_t < context.segment_t,
"entry lies past the motion-segment boundary");
CHECK(fabs(route.activate_t + 65.0) < 1e-6, "cross-segment entry time");
@@ -731,16 +744,17 @@ int main(void) {
test_large_radius_quadratic();
test_round_trip();
test_moving_sphere();
test_accelerated_worldtube();
test_accelerated_segment();
test_accelerated_worldtube_unsupported();
test_piecewise_segment_entry();
test_boundary_semantics_minkowski();
test_boundary_semantics_generic();
test_nonconstant_route_unsupported();
test_negative_radius_root_guard();
test_source_finalize();
test_motion_segment_domain();
test_schwarzschild_sample_failures();
test_end_protocol_error();
test_generic_bisection_failure();
test_nonconstant_preroute_unsupported();
test_piecewise_constant_history_hole();
test_interior_history_exhaustion();
test_interior_crossing_bisection_failure();
test_ray_pool_lifecycle();
+25 -10
View File
@@ -88,19 +88,31 @@ def image_payload(path, dimensions=(64, 48), allow_black=False):
return raw
# Version 3 wire layout. The first 100 bytes are the v2 provenance; the v3
# adaptive policy appends 9 doubles and a u32 (76 bytes) so the provenance
# block ends at 176. Each frame header is 48 bytes, so vertex payload starts at
# 224. A v3 vertex keeps the 84-byte v2 record and appends three u64 cost
# counters; a triangle stays 32 bytes. The CRC covers only the vertex+triangle
# payload, never the header.
MAP_PROVENANCE_END = 176
MAP_FRAME_HEADER_START = MAP_PROVENANCE_END
MAP_VERTEX_START = MAP_PROVENANCE_END + 48
MAP_VERTEX_SIZE = 84 + 24
MAP_TRIANGLE_SIZE = 32
def map_vertices(path):
data = path.read_bytes()
assert data[:8] == b'GRLENS\x01\x00'
assert struct.unpack_from('<I', data, 8)[0] == 3, 'expected v3 lens map'
assert struct.unpack_from('<Q', data, 32)[0] == 1 # frame_count
# Header: 40 bytes, then a 60-byte provenance block, then the 48-byte
# per-frame header, so vertices start at offset 148 in the v2 format.
vertices, triangles = struct.unpack_from('<QQ', data, 124)
offset = 148
vertices, triangles = struct.unpack_from('<QQ', data, MAP_FRAME_HEADER_START + 24)
offset = MAP_VERTEX_START
values = []
for _ in range(vertices):
values.append(struct.unpack_from('<9dIII', data, offset))
offset += 84
return values, data[offset:offset + triangles * 32]
values.append(struct.unpack_from('<9dIIIQQQ', data, offset))
offset += MAP_VERTEX_SIZE
return values, data[offset:offset + triangles * MAP_TRIANGLE_SIZE]
with tempfile.TemporaryDirectory(prefix='gr-camera-cli-') as directory:
@@ -282,7 +294,10 @@ with tempfile.TemporaryDirectory(prefix='gr-camera-cli-') as directory:
assert len(a) == len(b) and ta == tb
max_error = 0
for x, y in zip(a, b):
assert x[9:] == y[9:], 'ray endpoint provenance mismatch'
# Indices 9..11 are end_id/outcome/reason; indices 12..14 are the
# per-vertex integration cost counters, which are not physical ray
# provenance and may differ between the single and movie schedulers.
assert x[9:12] == y[9:12], 'ray endpoint provenance mismatch'
max_error = max(max_error, *(abs(v - w) for v, w in zip(x[:9], y[:9])))
assert max_error < 1e-9, max_error
@@ -305,9 +320,9 @@ with tempfile.TemporaryDirectory(prefix='gr-camera-cli-') as directory:
# so this tests completion semantics rather than corruption detection.
incomplete_map = tmp / f'{backend}_incomplete.grlens'
payload = bytearray(single_map.read_bytes())
struct.pack_into('<II', payload, 148 + 76, 3, 5) # INCOMPLETE / INVALID_METRIC
struct.pack_into('<II', payload, MAP_VERTEX_START + 76, 3, 5) # INCOMPLETE / INVALID_METRIC
struct.pack_into('<I', payload, len(payload)-4,
zlib.crc32(payload[148:-4]))
zlib.crc32(payload[MAP_VERTEX_START:-4]))
incomplete_map.write_bytes(payload)
refused = tmp / f'{backend}_refused.{ext}'
failure = run(binary, *common, '--lens-map-input', incomplete_map,
+644 -2
View File
@@ -9,6 +9,107 @@
#include <string.h>
#include <unistd.h>
/* Self-contained legacy v2 lens-map fixture writer. The production writer now
* emits v3, so this serializes a real little-endian v2 map (v2 provenance, no
* per-vertex cost counters) from a live mesh to keep the import path covered by
* genuine bytes instead of a hand-maintained golden blob. */
static uint32_t v2_crc32(uint32_t crc, const void *data, size_t size) {
const unsigned char *bytes = data;
for (size_t i = 0; i < size; ++i) {
crc ^= bytes[i];
for (int bit = 0; bit < 8; ++bit)
crc = (crc >> 1) ^ (0xedb88320u & (uint32_t)-(int)(crc & 1));
}
return crc;
}
static int v2_fwrite_u32(FILE *f, uint32_t v) {
unsigned char b[4] = {(unsigned char)v, (unsigned char)(v >> 8),
(unsigned char)(v >> 16), (unsigned char)(v >> 24)};
return fwrite(b, 1, sizeof b, f) == sizeof b ? 0 : -1;
}
static int v2_fwrite_u64(FILE *f, uint64_t v) {
unsigned char b[8];
for (int i = 0; i < 8; ++i) b[i] = (unsigned char)(v >> (8 * i));
return fwrite(b, 1, sizeof b, f) == sizeof b ? 0 : -1;
}
static int v2_fwrite_double(FILE *f, double v) {
uint64_t bits; memcpy(&bits, &v, sizeof bits);
return v2_fwrite_u64(f, bits);
}
static int write_v2_lens_map(const char *path, int width, int height, double fov,
const LensMapProvenance *p,
const LensMapFrame *frame) {
static const unsigned char magic[8] = {'G', 'R', 'L', 'E', 'N', 'S', 1, 0};
FILE *f = fopen(path, "wb");
if (f == NULL) return -1;
int failed = fwrite(magic, 1, sizeof magic, f) != sizeof magic ||
v2_fwrite_u32(f, 2) || v2_fwrite_u32(f, 0x01020304u) ||
v2_fwrite_u32(f, (uint32_t)width) || v2_fwrite_u32(f, (uint32_t)height) ||
v2_fwrite_double(f, fov) || v2_fwrite_u64(f, 1) ||
v2_fwrite_u32(f, p->threshold_kind) ||
v2_fwrite_u32(f, p->threshold_policy_version) ||
v2_fwrite_double(f, p->threshold_value) ||
v2_fwrite_u32(f, p->retry_step_increment) ||
v2_fwrite_u32(f, p->max_total_steps) || v2_fwrite_u32(f, p->max_level) ||
v2_fwrite_u32(f, p->integrator) ||
v2_fwrite_double(f, p->min_edge_pixels) ||
v2_fwrite_double(f, p->min_area_pixels2) ||
v2_fwrite_double(f, p->coordinate_time_step) ||
v2_fwrite_u32(f, p->initial_max_steps);
const FrameLensMesh *m = &frame->mesh;
failed = failed || v2_fwrite_u64(f, frame->frame_id) ||
v2_fwrite_double(f, frame->coordinate_time) ||
v2_fwrite_double(f, frame->proper_time) ||
v2_fwrite_u64(f, (uint64_t)m->vertex_count) ||
v2_fwrite_u64(f, (uint64_t)m->triangle_count) ||
v2_fwrite_u64(f, (uint64_t)m->retry_requests);
/* The payload CRC covers vertices+triangles only; the header stays
* deliberately independent, exactly as in the production writers. */
uint32_t crc = UINT32_MAX;
for (size_t i = 0; i < m->vertex_count; ++i) {
const LensVertex *v = &m->vertices[i];
const double vals[9] = {v->image_x, v->image_y, v->camera_direction[0],
v->camera_direction[1], v->camera_direction[2],
v->n_infinity[0], v->n_infinity[1], v->n_infinity[2],
v->log_frequency_ratio};
const uint32_t tail[3] = {(uint32_t)v->end_id, (uint32_t)v->outcome,
(uint32_t)v->reason};
unsigned char b[84]; size_t off = 0;
for (int k = 0; k < 9; ++k) {
uint64_t bits; memcpy(&bits, &vals[k], sizeof bits);
for (int q = 0; q < 8; ++q) b[off++] = (unsigned char)(bits >> (8 * q));
}
for (int k = 0; k < 3; ++k) {
b[off++] = (unsigned char)tail[k];
b[off++] = (unsigned char)(tail[k] >> 8);
b[off++] = (unsigned char)(tail[k] >> 16);
b[off++] = (unsigned char)(tail[k] >> 24);
}
if (fwrite(b, 1, sizeof b, f) != sizeof b) failed = 1;
crc = v2_crc32(crc, b, sizeof b);
}
for (size_t i = 0; i < m->triangle_count; ++i) {
unsigned char b[32]; size_t off = 0;
for (int j = 0; j < 3; ++j) {
const uint64_t idx = (uint64_t)m->triangles[i].vertex[j];
for (int q = 0; q < 8; ++q) b[off++] = (unsigned char)(idx >> (8 * q));
}
const uint32_t tail[2] = {m->triangles[i].level,
(uint32_t)m->triangles[i].approx_black};
for (int k = 0; k < 2; ++k) {
b[off++] = (unsigned char)tail[k];
b[off++] = (unsigned char)(tail[k] >> 8);
b[off++] = (unsigned char)(tail[k] >> 16);
b[off++] = (unsigned char)(tail[k] >> 24);
}
if (fwrite(b, 1, sizeof b, f) != sizeof b) failed = 1;
crc = v2_crc32(crc, b, sizeof b);
}
if (v2_fwrite_u32(f, crc ^ UINT32_MAX)) failed = 1;
if (fclose(f)) failed = 1;
return failed ? -1 : 0;
}
static int mesh_has_hanging_vertex(const FrameLensMesh *mesh) {
for (size_t triangle = 0; triangle < mesh->triangle_count; ++triangle)
for (size_t side = 0; side < 3; ++side) {
@@ -139,6 +240,9 @@ static int review_probe_regressions(void) {
m.vertices[i].n_infinity[0]=1;
}
m.vertices[0].outcome=RAY_OUTCOME_UNRESOLVED;
/* A genuinely budget-exhausted vertex has consumed its accepted-step
* quota; the retry layer keys on that blocking quota. */
m.vertices[0].continuation_steps=10;
m.vertices[0].continuation_limit=10;
if (frame_lens_mesh_prepare_generation(&m,&c)<1 || m.retry_requests==0)
return -1; /* the retry counter is produced by real retry requests */
@@ -153,11 +257,408 @@ static int review_probe_regressions(void) {
return 0;
}
/* A budget-exhausted vertex carrying the full adaptive resume state, so the
* retry layer sees the same payload the production store_endpoint path writes. */
static LensVertex unresolved_vertex(double image_x, double t, double start,
unsigned int steps,
unsigned int step_limit,
double lookback_limit) {
LensVertex v;
memset(&v, 0, sizeof v);
v.image_x = image_x;
v.image_y = 0.0;
v.camera_direction[0] = 1.0;
v.outcome = RAY_OUTCOME_UNRESOLVED;
v.traced = 1;
v.continuation_t = t;
v.continuation_x[0] = 1.0;
v.continuation_Pi[0] = -1.0;
v.continuation_log_alpha_p0 = 0.125;
v.continuation_log_alpha_p0_0 = 0.5;
v.continuation_steps = steps;
v.continuation_limit = step_limit;
v.continuation_integration_start_time = start;
v.continuation_next_step = 0.25;
v.continuation_rejected_steps = 2;
v.continuation_rhs_evaluations = 20;
v.continuation_previous_rejected = 0;
v.continuation_lookback_limit = lookback_limit;
return v;
}
static int uuu_fixture(LensVertex vertices[3], LensTriangle *triangle) {
for (int i = 0; i < 3; ++i)
vertices[i].camera_direction[0] = 1.0;
*triangle = (LensTriangle){{0, 1, 2}, 0, 0, 0};
return 0;
}
/* Independent step/time retry budgets: a request is allowed only while every
* quota that actually blocked the ray can still grow, and the two quotas are
* saturated separately. Also verifies the resume-state round trip used by
* both the frame and movie paths. */
static int review_retry_quota_regressions(void) {
/* 1. Step-blocked, both quotas growable: the step quota advances, and the
* request records the independently saturated time quota. */
{
LensVertex vertices[3] = {
unresolved_vertex(0.0, -1.0, 0.0, 20, 20, 2.0),
unresolved_vertex(10.0, -1.0, 0.0, 20, 20, 2.0),
unresolved_vertex(0.0, -1.0, 0.0, 20, 20, 2.0)};
LensTriangle triangle;
uuu_fixture(vertices, &triangle);
FrameLensMesh m = {.vertices = vertices, .vertex_count = 3,
.triangles = &triangle, .triangle_count = 1};
RefinementConfig c = {.max_level = 0,
.retry_step_increment = 10,
.max_total_steps = 100,
.retry_lookback_increment = 1.0,
.max_total_lookback_time = 10.0};
if (frame_lens_mesh_prepare_generation(&m, &c) != 3) {
free(m.samples); free(m.probe_slots);
return -1;
}
for (size_t i = 0; i < m.sample_count; ++i)
if (m.samples[i].kind != FRAME_SAMPLE_RETRY ||
m.samples[i].step_limit != 30 ||
m.samples[i].lookback_limit != 3.0) {
free(m.samples); free(m.probe_slots);
return -1;
}
free(m.samples); free(m.probe_slots);
}
/* 2. Step quota at its cap while time can still grow: no request, because
* more time cannot buy an accepted step. The UUU triangle stays a
* budget-incomplete boundary, never blackened. */
{
LensVertex vertices[3] = {
unresolved_vertex(0.0, -1.0, 0.0, 20, 20, 2.0),
unresolved_vertex(10.0, -1.0, 0.0, 20, 20, 2.0),
unresolved_vertex(0.0, -1.0, 0.0, 20, 20, 2.0)};
LensTriangle triangle;
uuu_fixture(vertices, &triangle);
FrameLensMesh m = {.vertices = vertices, .vertex_count = 3,
.triangles = &triangle, .triangle_count = 1};
RefinementConfig c = {.max_level = 0,
.retry_step_increment = 10,
.max_total_steps = 20,
.retry_lookback_increment = 1.0,
.max_total_lookback_time = 10.0};
if (frame_lens_mesh_prepare_generation(&m, &c) != 0) {
free(m.samples); free(m.probe_slots);
return -1;
}
FrameBoundaryStats stats;
frame_lens_mesh_boundary_stats(&m, &c, &stats);
if (stats.uuu != 1 || stats.budget_incomplete_triangles == 0 ||
stats.approx_black_triangles != 0) {
free(m.samples); free(m.probe_slots);
return -1;
}
free(m.samples); free(m.probe_slots);
}
/* 3. Time-blocked with a disabled step increment: the time quota alone
* enables the retry and the step budget is left untouched. */
{
LensVertex vertices[3] = {
unresolved_vertex(0.0, -3.0, 0.0, 5, 10, 2.0),
unresolved_vertex(10.0, -3.0, 0.0, 5, 10, 2.0),
unresolved_vertex(0.0, -3.0, 0.0, 5, 10, 2.0)};
LensTriangle triangle;
uuu_fixture(vertices, &triangle);
FrameLensMesh m = {.vertices = vertices, .vertex_count = 3,
.triangles = &triangle, .triangle_count = 1};
RefinementConfig c = {.max_level = 0,
.retry_step_increment = 0,
.max_total_steps = 10,
.retry_lookback_increment = 1.0,
.max_total_lookback_time = 10.0};
if (frame_lens_mesh_prepare_generation(&m, &c) != 3) {
free(m.samples); free(m.probe_slots);
return -1;
}
for (size_t i = 0; i < m.sample_count; ++i)
if (m.samples[i].kind != FRAME_SAMPLE_RETRY ||
m.samples[i].step_limit != 10 ||
m.samples[i].lookback_limit != 3.0) {
free(m.samples); free(m.probe_slots);
return -1;
}
free(m.samples); free(m.probe_slots);
}
/* 4. Both blocking quotas at their caps: no request. */
{
LensVertex vertices[3] = {
unresolved_vertex(0.0, -6.0, 0.0, 20, 20, 5.0),
unresolved_vertex(10.0, -6.0, 0.0, 20, 20, 5.0),
unresolved_vertex(0.0, -6.0, 0.0, 20, 20, 5.0)};
LensTriangle triangle;
uuu_fixture(vertices, &triangle);
FrameLensMesh m = {.vertices = vertices, .vertex_count = 3,
.triangles = &triangle, .triangle_count = 1};
RefinementConfig c = {.max_level = 0,
.retry_step_increment = 10,
.max_total_steps = 20,
.retry_lookback_increment = 1.0,
.max_total_lookback_time = 5.0};
if (frame_lens_mesh_prepare_generation(&m, &c) != 0) {
free(m.samples); free(m.probe_slots);
return -1;
}
free(m.samples); free(m.probe_slots);
}
/* 5. Resume-state round trip: the helper reproduces every control field, and
* install back-fills the vertex with the endpoint's new state and quota. */
{
LensVertex source = unresolved_vertex(0.0, -1.0, 0.0, 20, 20, 2.0);
GeodesicRayState state;
if (frame_vertex_continuation_state(&source, &state) ||
state.coordinate_time != -1.0 || state.steps != 20 ||
state.integration_start_time != 0.0 || state.next_step != 0.25 ||
state.rejected_steps != 2 || state.rhs_evaluations != 20 ||
state.previous_rejected != 0 || state.log_alpha_p0 != 0.125 ||
state.log_alpha_p0_0 != 0.5 || state.x[0] != 1.0 ||
state.Pi[0] != -1.0)
return -1;
LensVertex target = {.image_x = 0.0, .camera_direction = {1.0, 0.0, 0.0}};
FrameLensMesh mesh = {.vertices = &target, .vertex_count = 1};
RefinementConfig plain = {.max_level = 0};
if (frame_lens_mesh_prepare_generation(&mesh, &plain) != 1) {
free(mesh.samples); free(mesh.probe_slots);
return -1;
}
const RayEndpoint endpoint = {
.magnification = 1.0,
.end_id = SPACETIME_END_NONE,
.outcome = RAY_OUTCOME_UNRESOLVED,
.reason = RAY_REASON_BUDGET_EXHAUSTED,
.stop_coordinate_time = -4.0,
.accepted_steps = 7,
.final_x = {5.0, 0.0, 0.0},
.final_Pi = {-1.0, 0.0, 0.0},
.final_log_alpha_p0 = 0.125,
.final_log_alpha_p0_0 = 0.5,
.threshold_value = NAN,
.integration_start_time = 0.0,
.next_step = 0.5,
.rejected_steps = 3,
.rhs_evaluations = 70,
.previous_rejected = 1,
.lookback_limit = 2.5};
if (frame_lens_mesh_install_sample(&mesh, 0, &endpoint) ||
target.outcome != RAY_OUTCOME_UNRESOLVED ||
target.continuation_t != -4.0 || target.continuation_steps != 7 ||
target.continuation_limit != 7 ||
target.continuation_integration_start_time != 0.0 ||
target.continuation_next_step != 0.5 ||
target.continuation_rejected_steps != 3 ||
target.continuation_rhs_evaluations != 70 ||
target.continuation_previous_rejected != 1 ||
target.continuation_lookback_limit != 2.5) {
free(mesh.samples); free(mesh.probe_slots);
return -1;
}
GeodesicRayState rebuilt;
if (frame_vertex_continuation_state(&target, &rebuilt) ||
rebuilt.coordinate_time != -4.0 || rebuilt.steps != 7 ||
rebuilt.next_step != 0.5 || rebuilt.rejected_steps != 3 ||
rebuilt.rhs_evaluations != 70 || rebuilt.previous_rejected != 1) {
free(mesh.samples); free(mesh.probe_slots);
return -1;
}
free(mesh.samples); free(mesh.probe_slots);
}
return 0;
}
static GeodesicTraceConfig frame_dp_config(double lookback) {
GeodesicTraceConfig c;
memset(&c, 0, sizeof c);
c.stepper = GEODESIC_STEPPER_DP54;
c.coordinate_time_step = 0.5;
c.max_steps = 100;
c.threshold = (ThresholdPolicy){.kind = THRESHOLD_DISABLED, .value = 0.0,
.policy_version = 0};
c.atol_x = c.atol_Pi = c.atol_L = c.rtol = 1e-9;
c.min_step = 1e-12;
c.max_step = 1e6;
c.consecutive_rejection_limit = 1000;
c.max_lookback_time = lookback;
return c;
}
/* A real production trace must record the configured step/time grant distinct
* from the spent counts, so a time-limited ray can retry with extra time while
* keeping its full step grant. A retry candidate also counts only when it
* opens a strictly larger representable region. */
static int review_real_grant_and_time_growth(void) {
SpacetimeSource source = {0};
if (spacetime_create_minkowski(&source, 10.0))
return -1;
const ObserverState observer = observer_fixed_at_origin();
int failed = 0;
/* A. Real trace at the origin, time-limited after ~2 of 100 granted steps.
* With the step increment disabled and the time increment enabled, the
* retry must keep step_limit == 100 and only grow the time budget. */
{
LensVertex vertices[3] = {{.camera_direction = {1.0, 0.0, 0.0}},
{.camera_direction = {1.0, 0.0, 0.0}},
{.camera_direction = {1.0, 0.0, 0.0}}};
LensTriangle triangle = {{0, 1, 2}, 0, 0, 0};
FrameLensMesh m = {.vertices = vertices, .vertex_count = 3,
.triangles = &triangle, .triangle_count = 1};
const GeodesicTraceConfig trace = frame_dp_config(1.0);
if (frame_lens_mesh_trace(&m, &source, &observer, &trace))
failed = 1;
for (size_t i = 0; i < 3 && !failed; ++i) {
if (vertices[i].outcome != RAY_OUTCOME_UNRESOLVED ||
vertices[i].continuation_limit != 100u ||
vertices[i].continuation_steps >= 100u ||
vertices[i].continuation_lookback_limit != 1.0)
failed = 1;
}
const RefinementConfig retry = {.max_level = 0,
.retry_step_increment = 0,
.max_total_steps = 100,
.retry_lookback_increment = 1.0,
.max_total_lookback_time = 100.0};
if (!failed && frame_lens_mesh_prepare_generation(&m, &retry) != 3)
failed = 1;
for (size_t i = 0; i < m.sample_count && !failed; ++i)
if (m.samples[i].kind != FRAME_SAMPLE_RETRY ||
m.samples[i].step_limit != 100u ||
m.samples[i].lookback_limit != 2.0)
failed = 1;
free(m.samples);
free(m.probe_slots);
}
/* B. Translated time origins (positive, negative and zero) still detect the
* lookback boundary with the integrator's own comparison and retry with a
* strictly larger time region while preserving the step grant. */
for (int origin = 0; origin < 3 && !failed; ++origin) {
const double t0 = origin == 0 ? 0.0 : origin == 1 ? 1.0e9 : -1.0e9;
ObserverState o = observer_fixed_at_origin();
o.coordinate_time = t0;
const GeodesicTraceConfig trace = frame_dp_config(0.3);
MetricData metric;
if (spacetime_eval(&source, t0, o.coordinate_position, &metric)) {
failed = 1;
break;
}
GeodesicRayState state;
if (geodesic_initialize_past_ray_metric(&metric, &o, (double[]){1, 0, 0},
&state)) {
failed = 1;
break;
}
const RayEndpoint endpoint =
geodesic_trace_past_from_state(&source, &state, &trace);
if (endpoint.outcome != RAY_OUTCOME_UNRESOLVED ||
endpoint.accepted_step_limit != 100u || endpoint.accepted_steps != 1u) {
failed = 1;
break;
}
LensVertex vertices[3];
memset(vertices, 0, sizeof vertices);
vertices[1].traced = vertices[2].traced = 1;
vertices[1].outcome = vertices[2].outcome = RAY_OUTCOME_ESCAPED;
vertices[1].n_infinity[0] = vertices[2].n_infinity[0] = 1.0;
vertices[1].end_id = vertices[2].end_id = 0;
LensTriangle triangle = {{0, 1, 2}, 0, 0, 0};
FrameLensMesh m = {.vertices = vertices, .vertex_count = 3,
.triangles = &triangle, .triangle_count = 1};
const RefinementConfig plain = {.max_level = 0};
if (frame_lens_mesh_prepare_generation(&m, &plain) != 1 ||
frame_lens_mesh_install_sample(&m, 0, &endpoint) ||
vertices[0].continuation_limit != 100u ||
vertices[0].continuation_lookback_limit != 0.3) {
free(m.samples);
free(m.probe_slots);
failed = 1;
break;
}
free(m.samples);
m.samples = NULL;
m.sample_count = m.sample_capacity = 0;
const RefinementConfig retry = {.max_level = 0,
.retry_step_increment = 0,
.max_total_steps = 100,
.retry_lookback_increment = 0.3,
.max_total_lookback_time = 10.0};
if (frame_lens_mesh_prepare_generation(&m, &retry) != 1 ||
m.samples[0].kind != FRAME_SAMPLE_RETRY ||
m.samples[0].step_limit != 100u ||
m.samples[0].lookback_limit != 0.6)
failed = 1;
free(m.samples);
free(m.probe_slots);
}
/* C. A larger quota that does not move the left boundary (increment rounds
* away, or a large time origin absorbs it) must not launch a retry. */
{
LensVertex vertices[3] = {
unresolved_vertex(0.0, -1.0e9, 0.0, 1, 100, 1.0e9),
unresolved_vertex(10.0, -1.0e9, 0.0, 1, 100, 1.0e9),
unresolved_vertex(0.0, -1.0e9, 0.0, 1, 100, 1.0e9)};
LensTriangle triangle;
uuu_fixture(vertices, &triangle);
FrameLensMesh m = {.vertices = vertices, .vertex_count = 3,
.triangles = &triangle, .triangle_count = 1};
const RefinementConfig rounded = {.max_level = 0,
.retry_step_increment = 0,
.max_total_steps = 100,
.retry_lookback_increment = 1.0e-8,
.max_total_lookback_time = 2.0e9};
if (frame_lens_mesh_prepare_generation(&m, &rounded) != 0)
failed = 1;
FrameBoundaryStats stats;
frame_lens_mesh_boundary_stats(&m, &rounded, &stats);
if (stats.uuu != 1 || stats.budget_incomplete_triangles == 0)
failed = 1;
free(m.samples);
free(m.probe_slots);
}
{
const double start = 1.0e12;
const double quota = 1.0e6;
LensVertex vertices[3] = {
unresolved_vertex(0.0, start - quota, start, 1, 100, quota),
unresolved_vertex(10.0, start - quota, start, 1, 100, quota),
unresolved_vertex(0.0, start - quota, start, 1, 100, quota)};
LensTriangle triangle;
uuu_fixture(vertices, &triangle);
FrameLensMesh m = {.vertices = vertices, .vertex_count = 3,
.triangles = &triangle, .triangle_count = 1};
const RefinementConfig absorbed = {.max_level = 0,
.retry_step_increment = 0,
.max_total_steps = 100,
.retry_lookback_increment = 1.0e-6,
.max_total_lookback_time = 2.0e6};
if (frame_lens_mesh_prepare_generation(&m, &absorbed) != 0)
failed = 1;
free(m.samples);
free(m.probe_slots);
}
spacetime_destroy(&source);
return failed ? -1 : 0;
}
int main(void) {
if (review_probe_regressions()) {
fputs("probe persistence / retry invalidation regression failed\n",stderr);
return 1;
}
if (review_retry_quota_regressions()) {
fputs("independent retry quota regression failed\n", stderr);
return 1;
}
if (review_real_grant_and_time_growth()) {
fputs("real grant / representable time growth regression failed\n", stderr);
return 1;
}
const int width = 100, height = 100;
const double test_exposure = 1e-3;
const double psf_relative_tail = 1e-8;
@@ -187,7 +688,7 @@ int main(void) {
}
/* A finalized mesh can be persisted independently of spacetime and then
* drive the exact same catalog inverse-map and PSF pass. */
const char *lens_map_path = "/tmp/gr_lens_map_test.grlens";
const char *lens_map_path = "/tmp/opencode/gr_lens_map_test.grlens";
mesh.retry_requests = 2; /* cumulative per-frame retry accounting round-trips */
const LensMapFrame saved_frame = {.frame_id = 7,
.coordinate_time = 3.0,
@@ -259,7 +760,7 @@ int main(void) {
/* A version-1 header must be rejected outright: its captured bit cannot be
* upgraded into the new dark/unresolved/error provenance. */
{
const char *legacy_path = "/tmp/gr_lens_map_v1_test.grlens";
const char *legacy_path = "/tmp/opencode/gr_lens_map_v1_test.grlens";
FILE *legacy = fopen(legacy_path, "wb");
int legacy_failed = legacy == NULL;
if (!legacy_failed) {
@@ -906,6 +1407,147 @@ int main(void) {
}
free(ud_mesh.probe_slots);
}
/* v3 DP54 round-trip: every adaptive/quota field and the per-vertex cost
* counters must survive the wire exactly. */
{
const char *dp_path = "/tmp/opencode/gr_lens_map_v3_dp_test.grlens";
LensVertex dv[3];
for (int i = 0; i < 3; ++i)
dv[i] = (LensVertex){.image_x = (double)i, .image_y = 2.0,
.camera_direction = {0.0, 0.0, -1.0},
.outcome = RAY_OUTCOME_DARK,
.reason = RAY_REASON_REDSHIFT_LIMIT,
.end_id = SPACETIME_END_NONE, .traced = 1};
dv[0].trace_accepted_steps = 11; dv[0].trace_rejected_steps = 2;
dv[0].trace_rhs_evaluations = 79;
dv[1].trace_accepted_steps = 5;
LensTriangle dt = {{0, 1, 2}, 1, 1, 0};
FrameLensMesh dm = {.vertices = dv, .triangles = &dt, .vertex_count = 3,
.vertex_capacity = 3, .triangle_count = 1,
.triangle_capacity = 1};
LensMapFrame df = {.frame_id = 3, .coordinate_time = 1.5,
.proper_time = 1.25, .mesh = dm};
const LensMapProvenance dp = {.threshold_kind = THRESHOLD_LOG_ENERGY_GROWTH,
.threshold_policy_version = 3, .threshold_value = 8.0,
.retry_step_increment = 64, .max_total_steps = 256, .max_level = 2,
.integrator = (uint32_t)GEODESIC_STEPPER_DP54,
.min_edge_pixels = 0.5, .min_area_pixels2 = 0.25,
.coordinate_time_step = 0.1, .initial_max_steps = 1024,
.atol_x = 1e-9, .atol_Pi = 1e-9, .atol_L = 1e-9, .rtol = 1e-9,
.min_step = 1e-12, .max_step = 2.0, .max_lookback_time = 102.4,
.retry_lookback_increment = 102.4, .max_total_lookback_time = 409.6,
.max_consecutive_rejections = 32};
LensMap dloaded = {0};
if (lens_map_write(dp_path, 4, 3, 30.0, &dp, &df, 1) ||
lens_map_read(dp_path, NULL, &dloaded)) {
fputs("lens-map v3 DP54 round-trip regression failed\n", stderr);
lens_map_destroy(&dloaded); unlink(dp_path); goto done;
}
const LensMapProvenance *lp = &dloaded.provenance;
if (dloaded.file_version != 3 ||
lp->integrator != (uint32_t)GEODESIC_STEPPER_DP54 ||
lp->atol_x != 1e-9 || lp->atol_Pi != 1e-9 || lp->atol_L != 1e-9 ||
lp->rtol != 1e-9 || lp->min_step != 1e-12 || lp->max_step != 2.0 ||
lp->max_lookback_time != 102.4 ||
lp->retry_lookback_increment != 102.4 ||
lp->max_total_lookback_time != 409.6 ||
lp->max_consecutive_rejections != 32 ||
lp->retry_step_increment != 64 || lp->max_total_steps != 256 ||
lp->coordinate_time_step != 0.1 || lp->initial_max_steps != 1024 ||
dloaded.frames[0].mesh.vertices[0].trace_accepted_steps != 11 ||
dloaded.frames[0].mesh.vertices[0].trace_rejected_steps != 2 ||
dloaded.frames[0].mesh.vertices[0].trace_rhs_evaluations != 79 ||
dloaded.frames[0].mesh.vertices[1].trace_accepted_steps != 5 ||
dloaded.frames[0].mesh.triangles[0].level != 1) {
fputs("lens-map v3 DP54 field round-trip regression failed\n", stderr);
lens_map_destroy(&dloaded); unlink(dp_path); goto done;
}
lens_map_destroy(&dloaded);
/* Unknown wire code and non-finite/out-of-bounds DP fields must be rejected
* by the shared schema validator, not accepted as a usable map. */
{
/* offset, is_double, double_value, u32_value */
const struct { long offset; int is_double; double dvalue; uint32_t uvalue; }
corruptions[4] = {{68, 0, 0.0, 7u}, /* unknown integrator code */
{100, 1, NAN, 0u}, /* atol_x = NaN */
{108, 1, -1.0, 0u}, /* atol_Pi below zero */
{156, 1, -1.0, 0u}}; /* max_lookback below zero */
for (size_t c = 0; c < 4; ++c) {
if (lens_map_write(dp_path, 4, 3, 30.0, &dp, &df, 1)) {
fputs("lens-map v3 corruption fixture write failed\n", stderr);
unlink(dp_path); goto done;
}
FILE *bad = fopen(dp_path, "r+b");
int bad_failed =
bad == NULL || fseek(bad, corruptions[c].offset, SEEK_SET);
if (!bad_failed) {
if (corruptions[c].is_double)
bad_failed = fwrite(&corruptions[c].dvalue,
sizeof(double), 1, bad) != 1;
else
bad_failed = fwrite(&corruptions[c].uvalue,
sizeof(uint32_t), 1, bad) != 1;
}
if (bad != NULL && fclose(bad)) bad_failed = 1;
if (bad_failed || !lens_map_read(dp_path, NULL, &dloaded)) {
fputs("lens-map v3 invalid DP54 field rejection regression failed\n",
stderr);
lens_map_destroy(&dloaded); unlink(dp_path); goto done;
}
lens_map_destroy(&dloaded);
}
}
unlink(dp_path);
}
/* Legacy v2 import: a real v2 map (no adaptive fields, no cost counters)
* must load as RK4 with an explicit zero adaptive policy and render
* identically to the live mesh. */
{
const char *v2_path = "/tmp/opencode/gr_lens_map_v2_legacy_test.grlens";
const LensMapProvenance v2p = {.threshold_kind = THRESHOLD_LOG_ALPHA_P0,
.threshold_policy_version = 1, .threshold_value = 8.0,
.retry_step_increment = 16, .max_total_steps = 64, .max_level = 2,
.integrator = (uint32_t)GEODESIC_STEPPER_RK4,
.min_edge_pixels = 0.5, .min_area_pixels2 = 0.25,
.coordinate_time_step = 0.1, .initial_max_steps = 4096};
const LensMapFrame v2f = {.frame_id = 7, .coordinate_time = 3.0,
.proper_time = 2.0, .mesh = mesh};
LensMap v2loaded = {0};
double *v2_hdr = calloc((size_t)width * height * 3, sizeof *v2_hdr);
double *live_hdr = calloc((size_t)width * height * 3, sizeof *live_hdr);
if (v2_hdr == NULL || live_hdr == NULL ||
write_v2_lens_map(v2_path, width, height, 30.0, &v2p, &v2f) ||
lens_map_read(v2_path, NULL, &v2loaded) ||
v2loaded.file_version != 2 ||
v2loaded.provenance.integrator != (uint32_t)GEODESIC_STEPPER_RK4 ||
v2loaded.provenance.atol_x != 0.0 ||
v2loaded.provenance.max_lookback_time != 0.0 ||
v2loaded.provenance.max_total_lookback_time != 0.0 ||
v2loaded.provenance.max_consecutive_rejections != 0 ||
v2loaded.frames[0].mesh.vertices[0].trace_accepted_steps != 0 ||
v2loaded.frames[0].mesh.vertices[0].trace_rhs_evaluations != 0) {
fputs("lens-map v2 legacy import regression failed\n", stderr);
free(v2_hdr); free(live_hdr); lens_map_destroy(&v2loaded);
unlink(v2_path); goto done;
}
const size_t live_images = frame_splat_catalog(
&mesh, &catalog, live_hdr, width, height, test_exposure, &psf, NULL,
INFINITY, 1.0, psf_relative_tail, 0.0, 0, 1,
FRAME_CATALOG_PREFETCH_FRAME, NULL, NULL, NULL, NULL, NULL);
const size_t v2_images = frame_splat_catalog(
&v2loaded.frames[0].mesh, &catalog, v2_hdr, width, height,
test_exposure, &psf, NULL, INFINITY, 1.0, psf_relative_tail, 0.0, 0, 1,
FRAME_CATALOG_PREFETCH_FRAME, NULL, NULL, NULL, NULL, NULL);
int render_equal = live_images == v2_images && live_images == images;
for (int k = 0; render_equal && k < width * height * 3; ++k)
if (live_hdr[k] != v2_hdr[k]) render_equal = 0;
free(v2_hdr); free(live_hdr);
lens_map_destroy(&v2loaded); unlink(v2_path);
if (!render_equal) {
fputs("lens-map v2 legacy render mismatch regression failed\n", stderr);
goto done;
}
}
result = 0;
done:
frame_lens_mesh_destroy(&mesh);
File diff suppressed because it is too large. Load diff