Alcubierre: support superluminal v_s

This commit is contained in:
wyj committed 2026-10-06 02:51:20 -04:00
1 parent 0a46a7095b
commit b3f1e837d5
8 files changed
+440 -162

No files matched your search

+219 -90
View File
@@ -11,6 +11,7 @@ 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 math
import os
import re
import struct
@@ -139,6 +140,28 @@ def sum_rhs(vertices):
return sum(v[14] for v in vertices)
def alcubierre_budget(escape, vs, dark_threshold):
"""Reference implementation of the production Alcubierre time allowance:
B = 5*escape / max(|1-|v_s||, exp(-D)), with the DBL_MIN..DBL_MAX/4
saturation and the log-space fallback used when the ordinary division is
not finite and positive. exp(D) is never formed."""
sep = abs(1.0 - abs(vs))
floor = math.exp(-dark_threshold)
denom = max(sep, floor)
upper = sys.float_info.max / 4.0
if denom > 0.0 and math.isfinite(denom):
scaled = 5.0 * (escape / denom)
if math.isfinite(scaled) and scaled > 0.0:
return min(max(scaled, sys.float_info.min), upper)
log_sep = math.log(sep) if sep > 0.0 else -math.inf
log_budget = math.log(5.0) + math.log(escape) - max(log_sep, -dark_threshold)
if not math.isfinite(log_budget):
log_budget = math.log(upper)
log_budget = min(log_budget, math.log(upper))
log_budget = max(log_budget, math.log(sys.float_info.min))
return min(max(math.exp(log_budget), sys.float_info.min), upper)
with tempfile.TemporaryDirectory(prefix='gr-adaptive-cli-',
dir=str(TMP_ROOT)) as directory:
tmp = Path(directory)
@@ -435,111 +458,217 @@ with tempfile.TemporaryDirectory(prefix='gr-adaptive-cli-',
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.
# Alcubierre production policy: sub- and super-luminal velocities share one
# finite resource allowance B = 5*escape / max(|1-|v_s||, exp(-D)); the DP54
# default must validate, actually integrate the warp feature, and reach both
# the shared DARK terminal and escapes. All images are tiny 8x4/16x8.
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)
assert '|v_s| < 1' not in alc_help, 'help still claims |v_s| < 1'
escape1 = 1.0 + 20.0 / 1.0 # R = sigma = 1
# 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_camera(vs, ra_deg, dec_deg=0.0):
return ['--observer-position', '0', '0', '0',
'--observer-velocity', repr(vs), '0', '0',
'--look-ra-deg', repr(ra_deg),
'--look-dec-deg', repr(dec_deg)]
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]
def alc_map_prov(tag, vs, D=8.0, radius=1.0, extra=(), allow=True,
width=8, height=4, cell=4, camera=None,
catalog=True):
m = tmp / f'alc_{tag}.grlens'
args = ['--alcubierre-vs', repr(vs), '--alcubierre-radius',
repr(radius), '--alcubierre-sigma', '1',
'--dark-threshold', repr(D), '--width', str(width),
'--height', str(height), '--fov-deg', 80, '--exposure',
'1e-3', '--coarse-cell-pixels', str(cell),
'--refine-max-level', 0, '--psf-relative-tail', 1e-4]
if catalog:
args += ['--catalog', 'assets/sky_grid_5deg.csv']
if camera is not None:
args += camera
if allow:
args.append('--allow-incomplete')
args += ['--output', str(tmp / f'alc_{tag}.{ext}'),
'--lens-map-output', str(m), *extra]
run(alc, *args)
return map_provenance(m)[2], m
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
# 1) Ordinary sub-luminal separation: B is exactly 5*escape/sep and is
# independent of the step count and of the initial step.
p_sub, _ = alc_map_prov('sub', 0.3, camera=alc_camera(0.3, 0.0))
assert p_sub['integrator'] == 1, p_sub
assert math.isclose(p_sub['max_lookback_time'],
alcubierre_budget(escape1, 0.3, 8.0), rel_tol=1e-12)
assert p_sub['min_step'] <= p_sub['coordinate_time_step'] \
<= p_sub['max_step']
p_steps, _ = alc_map_prov('sub_steps', 0.3,
extra=('--trace-max-steps', '8'),
camera=alc_camera(0.3, 0.0))
assert p_steps['initial_max_steps'] == 8
assert math.isclose(p_steps['max_lookback_time'],
alcubierre_budget(escape1, 0.3, 8.0), rel_tol=1e-12)
p_step, _ = alc_map_prov('sub_step', 0.3,
extra=('--ode-initial-step', '0.02'),
camera=alc_camera(0.3, 0.0))
assert abs(p_step['coordinate_time_step'] - 0.02) < 1e-15
assert math.isclose(p_step['max_lookback_time'],
alcubierre_budget(escape1, 0.3, 8.0), rel_tol=1e-12)
# 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'
# 2) Near-luminal (vs = 1, separation 0): the exp(-D) floor makes the
# allowance finite, and it grows with the dark threshold D.
p8, _ = alc_map_prov('near8', 1.0, D=8.0, camera=alc_camera(1.0, 0.0))
p12, _ = alc_map_prov('near12', 1.0, D=12.0, camera=alc_camera(1.0, 0.0))
assert math.isclose(p8['max_lookback_time'],
alcubierre_budget(escape1, 1.0, 8.0), rel_tol=1e-12)
assert math.isclose(p12['max_lookback_time'],
alcubierre_budget(escape1, 1.0, 12.0), rel_tol=1e-12)
assert p12['max_lookback_time'] > p8['max_lookback_time']
# 3) Both sides of the threshold-derived vcut and exactly luminal
# values use either separation or the finite floor; a tiny film traces
# and resolves without INCOMPLETE outcomes.
vcut = 1.0 - math.exp(-8.0)
for vs in (0.999, math.nextafter(vcut, 0.0),
math.nextafter(vcut, 1.0), 0.9999, math.nextafter(1.0, 0.0),
math.nextafter(1.0, 2.0)):
pv, mv = alc_map_prov(f'vcut_{vs!r}', vs, camera=alc_camera(vs, 0.0))
assert math.isclose(pv['max_lookback_time'],
alcubierre_budget(escape1, vs, 8.0),
rel_tol=1e-12), (vs, pv['max_lookback_time'])
vv, _ = map_vertices(mv)
assert 3 not in {v[10] for v in vv}, (vs, 'INCOMPLETE ray')
assert sum_rhs(vv) > 0, vs
# 4) The default camera (0,0,15 for R=5) must work with a superluminal
# bubble: the generic radius-15 camera lies inside escape radius 25.
pd, md = alc_map_prov('defaultcam', 2.0, radius=5.0)
dv, _ = map_vertices(md)
assert 3 not in {v[10] for v in dv}, 'default camera left INCOMPLETE rays'
assert sum_rhs(dv) > 0
# 5) Real small images at the critical velocities: the direction along
# the bubble motion is the DARK direction (Pi_x = sign(vs)); the
# central vertex must be DARK and some edge ray must escape. The
# 16x8 film with a 4-pixel coarse cell places a vertex exactly at the
# image center.
for vs in (1.0, -1.0, 2.0, -2.0):
ra = 0.0 if vs < 0.0 else 180.0
pimg, mimg = alc_map_prov(f'img_{vs!r}', vs, width=16, height=8,
cell=4, camera=alc_camera(vs, ra))
verts, _ = map_vertices(mimg)
outcomes = {v[10] for v in verts}
assert 3 not in outcomes, (vs, 'INCOMPLETE outcome', outcomes)
assert 0 in outcomes and 1 in outcomes, (vs, outcomes)
assert sum_rhs(verts) > 0, vs
central = min(verts, key=lambda v: (v[0] - 8.0) ** 2
+ (v[1] - 4.0) ** 2)
assert central[10] == 1, \
(vs, 'central vertex is not DARK', central[0], central[1],
central[10])
# 6) Extreme tiny-budget quota path: an explicit 4-step, 1-time
# allowance from an inside camera must stop as UNRESOLVED, never as
# a fabricated escape from a trace that took no steps.
extreme = (alc_camera(0.99999999, 0.0)
+ ['--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'])
ext_map = tmp / 'alc_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)
'--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'alc_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'
assert all(v[10] == 2 for v in ext_vertices), \
('a 1-time/4-step trace fabricated a non-UNRESOLVED outcome',
[v[10] for v in ext_vertices])
# 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
# 7) Large dark threshold (D=1000) with vs=1: exp(-1000) underflows to
# 0 and the separation is exactly 0, so the log fallback saturates
# the default allowance to DBL_MAX/4. A 4-step/4-total cap with
# retry disabled keeps the trace short while the provenance records
# the non-masked saturated default. An explicit --trace-lookback
# overrides it without masking the independent step allowance.
psat, _ = alc_map_prov('sat1000', 1.0, D=1000.0,
extra=('--trace-max-steps', '4',
'--max-total-steps', '4',
'--retry-step-increment', '0'),
camera=alc_camera(1.0, 0.0))
assert math.isclose(psat['max_lookback_time'],
sys.float_info.max / 4.0, rel_tol=1e-12), \
psat['max_lookback_time']
assert psat['initial_max_steps'] == 4
psat_ov, _ = alc_map_prov('sat1000_ov', 1.0, D=1000.0,
extra=('--trace-lookback-time', '1',
'--trace-max-steps', '4',
'--max-total-steps', '4',
'--retry-step-increment', '0'),
camera=alc_camera(1.0, 0.0))
assert psat_ov['max_lookback_time'] == 1.0, psat_ov
assert psat_ov['initial_max_steps'] == 4, psat_ov
# Explicit time override must not mask the (default) step allowance.
pind, _ = alc_map_prov('indep', 1.0, D=8.0,
extra=('--trace-lookback-time', '1'),
camera=alc_camera(1.0, 0.0))
assert pind['max_lookback_time'] == 1.0, pind
assert pind['initial_max_steps'] > 1, pind
print('alcubierre: default DP config validated and produced escapes; '
'extreme DP quota path ok, RK4 guard still rejects', flush=True)
# 8) RK4 guard: a super-luminal default (vs=2, small estimate) is no
# longer rejected; the guard triggers only when the *derived default*
# estimate exceeds the cap (D=12, vs=1), and an explicit
# --trace-max-steps is a user allowance that bypasses it.
run(alc, '--integrator', 'rk4', '--alcubierre-vs', '2',
'--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', '--allow-incomplete',
'--output', tmp / f'alc_rk4_v2.{ext}')
rk4_guard = run(
alc, '--integrator', 'rk4', '--alcubierre-vs', '1',
'--alcubierre-radius', '1', '--alcubierre-sigma', '1',
'--dark-threshold', '12', '--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'alc_rk4_guard.{ext}', ok=False)
assert rk4_guard.returncode == 2, rk4_guard.stderr
assert 'cap' in rk4_guard.stderr, rk4_guard.stderr
run(alc, '--integrator', 'rk4', '--alcubierre-vs', '1',
'--alcubierre-radius', '1', '--alcubierre-sigma', '1',
'--dark-threshold', '12', '--trace-max-steps', '4',
'--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', '--allow-incomplete',
'--output', tmp / f'alc_rk4_expl.{ext}')
# 9) Impossible parameters stay clearly rejected.
for bad in (['--alcubierre-vs', 'nan'], ['--alcubierre-vs', 'inf'],
['--alcubierre-radius', '0'], ['--alcubierre-sigma', '0']):
bad_out = tmp / f'alc_bad.{ext}'
rejected = run(alc, *bad, '--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', bad_out, ok=False)
assert rejected.returncode != 0, rejected.stderr
assert not bad_out.exists()
print('alcubierre: budget/provenance, vcut scans, vcut images with '
'central DARK, default camera, large-D saturation, explicit '
'overrides and RK4 guard all passed', flush=True)