Alcubierre: support superluminal v_s
This commit is contained in:
1 parent
0a46a7095b
commit
b3f1e837d5
8 files changed
+440
-162
No files matched your search
+219
-90
@@ -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)
|
||||
+111
-2
@@ -35,8 +35,24 @@ int main(void) {
|
||||
SpacetimeSource source = {0};
|
||||
MetricData metric;
|
||||
CHECK(spacetime_create_alcubierre(&source, vs, radius, sigma) == 0);
|
||||
CHECK(spacetime_create_alcubierre(&source, 1.0, radius, sigma) != 0);
|
||||
CHECK(spacetime_create_alcubierre(&source, -1.5, radius, sigma) != 0);
|
||||
/* Sub- and super-luminal velocities are both accepted. A successful
|
||||
* constructor installs a context, so each acceptance uses its own temporary
|
||||
* source that is destroyed immediately; the shared `source` above is never
|
||||
* overwritten with a second live context. */
|
||||
{
|
||||
SpacetimeSource luminal = {0};
|
||||
CHECK(spacetime_create_alcubierre(&luminal, 1.0, radius, sigma) == 0);
|
||||
spacetime_destroy(&luminal);
|
||||
}
|
||||
{
|
||||
SpacetimeSource superluminal = {0};
|
||||
CHECK(spacetime_create_alcubierre(&superluminal, -1.5, radius, sigma) == 0);
|
||||
spacetime_destroy(&superluminal);
|
||||
}
|
||||
/* Non-finite velocities stay rejected. */
|
||||
CHECK(spacetime_create_alcubierre(&source, NAN, radius, sigma) != 0);
|
||||
CHECK(spacetime_create_alcubierre(&source, INFINITY, radius, sigma) != 0);
|
||||
CHECK(spacetime_create_alcubierre(&source, -INFINITY, radius, sigma) != 0);
|
||||
CHECK(spacetime_create_alcubierre(&source, vs, 0.0, sigma) != 0);
|
||||
CHECK(spacetime_create_alcubierre(&source, vs, radius, 0.0) != 0);
|
||||
/* A derived escape radius that overflows or does not exceed R is rejected. */
|
||||
@@ -322,6 +338,99 @@ int main(void) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Production DP54 axial superluminal check. A comoving bubble-center camera
|
||||
* at R = 1, sigma = 1 (escape radius 21) sees Pi_x = +-1 along the bubble
|
||||
* axis. The shared camera-relative dark policy must fire at threshold 8 for
|
||||
* the direction along the bubble motion and the opposite direction must
|
||||
* escape. The vs = +-2 constants were computed independently with mpmath;
|
||||
* this test has no dependency on any local experiment fixture. */
|
||||
{
|
||||
const double R1 = 1.0, sig1 = 1.0;
|
||||
static const double vlist[] = {1.0, -1.0, 2.0, -2.0, 0.9999};
|
||||
for (size_t k = 0; k < sizeof vlist / sizeof vlist[0]; ++k) {
|
||||
const double v = vlist[k];
|
||||
const double sgn = v > 0.0 ? 1.0 : -1.0;
|
||||
SpacetimeSource fast = {0};
|
||||
CHECK(spacetime_create_alcubierre(&fast, v, R1, sig1) == 0);
|
||||
ObserverCamera cam = {.coordinate_time = 0.0,
|
||||
.position = {0.0, 0.0, 0.0},
|
||||
.velocity = {v, 0.0, 0.0},
|
||||
.look_ra_deg = 0.0,
|
||||
.look_dec_deg = 0.0,
|
||||
.roll_deg = 0.0};
|
||||
MetricData m;
|
||||
CHECK(eval(&fast, 0.0, cam.position, &m) == 0);
|
||||
ObserverState o;
|
||||
CHECK(observer_from_coordinate_camera(&m, &cam, &o, NULL) ==
|
||||
OBSERVER_BUILD_OK);
|
||||
/* A coordinate-static center camera is timelike only for |v| < 1. */
|
||||
ObserverCamera stat = cam;
|
||||
stat.velocity[0] = stat.velocity[1] = stat.velocity[2] = 0.0;
|
||||
ObserverState so;
|
||||
const int static_ok = observer_from_coordinate_camera(&m, &stat, &so,
|
||||
NULL) ==
|
||||
OBSERVER_BUILD_OK;
|
||||
CHECK(static_ok == (fabs(v) < 1.0));
|
||||
/* An outer static camera in the flat exterior is always legal. */
|
||||
{
|
||||
const double pos[3] = {26.0, 0.0, 0.0};
|
||||
MetricData om;
|
||||
ObserverCamera oc = {.coordinate_time = 0.0,
|
||||
.position = {26.0, 0.0, 0.0},
|
||||
.look_ra_deg = 0.0,
|
||||
.look_dec_deg = 0.0,
|
||||
.roll_deg = 0.0};
|
||||
CHECK(eval(&fast, 0.0, pos, &om) == 0);
|
||||
ObserverState oo;
|
||||
CHECK(observer_from_coordinate_camera(&om, &oc, &oo, NULL) ==
|
||||
OBSERVER_BUILD_OK);
|
||||
}
|
||||
const GeodesicTraceConfig trace = {
|
||||
.coordinate_time_step = 0.05,
|
||||
.max_steps = 100000u,
|
||||
.threshold = {.kind = THRESHOLD_LOG_ENERGY_GROWTH,
|
||||
.value = 8.0,
|
||||
.policy_version = 1},
|
||||
.stepper = GEODESIC_STEPPER_DP54,
|
||||
.atol_x = 1e-9,
|
||||
.atol_Pi = 1e-9,
|
||||
.atol_L = 1e-9,
|
||||
.rtol = 1e-9,
|
||||
.min_step = 1e-12,
|
||||
.max_step = 0.4,
|
||||
.consecutive_rejection_limit = 32,
|
||||
.max_lookback_time = 30000.0};
|
||||
const double n_dark[3] = {-sgn, 0.0, 0.0};
|
||||
const double n_esc[3] = {sgn, 0.0, 0.0};
|
||||
GeodesicRayState idark, iesc;
|
||||
CHECK(geodesic_initialize_past_ray_metric(&m, &o, n_dark, &idark) == 0);
|
||||
CHECK(geodesic_initialize_past_ray_metric(&m, &o, n_esc, &iesc) == 0);
|
||||
printf("alcubierre vs=%.6g dark Pi_x=%.17g escape Pi_x=%.17g\n", v,
|
||||
idark.Pi[0], iesc.Pi[0]);
|
||||
CHECK(sgn * idark.Pi[0] > 0.999 && sgn * idark.Pi[0] < 1.000000001);
|
||||
CHECK(sgn * iesc.Pi[0] < -0.999 && sgn * iesc.Pi[0] > -1.000000001);
|
||||
const RayEndpoint dark = geodesic_trace_past(&fast, &o, n_dark, &trace);
|
||||
CHECK(dark.outcome == RAY_OUTCOME_DARK);
|
||||
CHECK(isfinite(dark.stop_coordinate_time));
|
||||
CHECK(fabs(dark.threshold_value - 8.0) < 1e-6);
|
||||
CHECK(fabs((dark.final_log_alpha_p0 - dark.final_log_alpha_p0_0) - 8.0) <
|
||||
1e-6);
|
||||
if (fabs(v) == 2.0) {
|
||||
const double q = dark.final_x[0] - v * dark.stop_coordinate_time;
|
||||
CHECK(fabs(fabs(q) - 1.2181434100155241) < 1e-6);
|
||||
CHECK(fabs(dark.stop_coordinate_time + 7.09915163394274) < 1e-6);
|
||||
}
|
||||
const RayEndpoint esc = geodesic_trace_past(&fast, &o, n_esc, &trace);
|
||||
CHECK(esc.outcome == RAY_OUTCOME_ESCAPED);
|
||||
CHECK(isfinite(esc.frequency_ratio) && esc.frequency_ratio > 0.0);
|
||||
if (fabs(v) == 2.0)
|
||||
CHECK(fabs(esc.frequency_ratio - 3.0) < 1e-6);
|
||||
for (int i = 0; i < 3; ++i)
|
||||
CHECK(isfinite(esc.n_infinity[i]));
|
||||
spacetime_destroy(&fast);
|
||||
}
|
||||
}
|
||||
|
||||
spacetime_destroy(&source);
|
||||
puts("alcubierre regression passed");
|
||||
return 0;
|
||||
|
||||
Reference in new issue
Block a user