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
@@ -35,11 +35,11 @@ make -j
|
|||||||
|
|
||||||
With no explicit `SPACETIME` setting, this builds all supported spacetimes:
|
With no explicit `SPACETIME` setting, this builds all supported spacetimes:
|
||||||
|
|
||||||
| Executable | Spacetime |
|
| Executable | Spacetime |
|
||||||
| --- | --- |
|
| --------------------------------- | --------------------------------------------------------- |
|
||||||
| `build/Release/minkowski_sky` | Flat Minkowski spacetime |
|
| `build/Release/minkowski_sky` | Flat Minkowski spacetime |
|
||||||
| `build/Release/schwarzschild_sky` | Analytic Schwarzschild in ingoing Kerr–Schild coordinates |
|
| `build/Release/schwarzschild_sky` | Analytic Schwarzschild in ingoing Kerr–Schild coordinates |
|
||||||
| `build/Release/alcubierre_sky` | Analytic moving Alcubierre warp bubble, `x_s(t)=v_s t` (no capture) |
|
| `build/Release/alcubierre_sky` | Analytic moving Alcubierre warp bubble, `x_s(t)=v_s t` |
|
||||||
|
|
||||||
To build only one:
|
To build only one:
|
||||||
|
|
||||||
|
|||||||
@@ -1130,8 +1130,6 @@ residual、Chebyshev 表或解析主项;运行期不得建表。
|
|||||||
各 trial 先确定可表示的目标时间,再用实际 `target-before.t` 推进状态、构造
|
各 trial 先确定可表示的目标时间,再用实际 `target-before.t` 推进状态、构造
|
||||||
dense interpolant 与提交时间;不得以舍入前的步长推进空间、舍入后的步长记时间。
|
dense interpolant 与提交时间;不得以舍入前的步长推进空间、舍入后的步长记时间。
|
||||||
worldtube 采样的坐标时间同样使用实际差值回推运动,不假设半步总是可表示。
|
worldtube 采样的坐标时间同样使用实际差值回推运动,不假设半步总是可表示。
|
||||||
Alcubierre 的默认时间范围由 `1.25*4*R_escape/(1-|v_s|)` 给出;固定步的步数
|
|
||||||
估算不能截短自适应路径的历史,也不能替代其独立资源上限。
|
|
||||||
- 初始 accepted 状态的 RHS/metric 失败直接报告具体 point reason,不缩步;后续 stage
|
- 初始 accepted 状态的 RHS/metric 失败直接报告具体 point reason,不缩步;后续 stage
|
||||||
的 `OUT_OF_DOMAIN/INVALID_METRIC` 可有界缩步重试;`TIME_UNAVAILABLE/INTERNAL_ERROR`
|
的 `OUT_OF_DOMAIN/INVALID_METRIC` 可有界缩步重试;`TIME_UNAVAILABLE/INTERNAL_ERROR`
|
||||||
不靠无限缩步;最小步长或时间不可进导致失败时报告 `INCOMPLETE/INTEGRATION_ERROR`。
|
不靠无限缩步;最小步长或时间不可进导致失败时报告 `INCOMPLETE/INTEGRATION_ERROR`。
|
||||||
|
|||||||
+71
-35
@@ -846,8 +846,8 @@ static void print_help(const char *program) {
|
|||||||
stdout);
|
stdout);
|
||||||
#ifdef SPACETIME_ALCUBIERRE
|
#ifdef SPACETIME_ALCUBIERRE
|
||||||
fputs(
|
fputs(
|
||||||
"\nAlcubierre warp bubble (moving x_s(t)=v_s*t; no capture):\n"
|
"\nAlcubierre warp bubble (moving x_s(t)=v_s*t; no geometric capture, shared dark policy):\n"
|
||||||
" --alcubierre-vs V Constant bubble velocity v_s, |v_s| < 1 (default: 0.5)\n"
|
" --alcubierre-vs V Constant bubble velocity v_s (any finite value, default: 0.5)\n"
|
||||||
" --alcubierre-radius R Bubble radius R > 0 (default: 5)\n"
|
" --alcubierre-radius R Bubble radius R > 0 (default: 5)\n"
|
||||||
" --alcubierre-sigma S Wall sharpness sigma > 0 (default: 1)\n"
|
" --alcubierre-sigma S Wall sharpness sigma > 0 (default: 1)\n"
|
||||||
" The escape radius R + 20/sigma is derived internally; the\n"
|
" The escape radius R + 20/sigma is derived internally; the\n"
|
||||||
@@ -991,19 +991,57 @@ static double alcubierre_time_step(const Settings *s) {
|
|||||||
return fmin(0.1, 0.05 / s->alcubierre_sigma);
|
return fmin(0.1, 0.05 / s->alcubierre_sigma);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Coordinate-time coverage a past-directed Alcubierre ray must be granted.
|
/* Coordinate-time resource allowance for a past-directed Alcubierre ray:
|
||||||
* A ray that is nearly comoving with the bubble separates from its center in
|
*
|
||||||
* the propagation direction at only ~1 - |v_s|, so crossing the ~4*escape
|
* B = margin * 4 * R_escape / max(|1 - |v_s||, exp(-D)), D = dark threshold.
|
||||||
* domain takes up to ~4*escape/(1 - |v_s|), scaled by ALCUBIERRE_BUDGET_MARGIN
|
*
|
||||||
* for lingering in the wall. This is a physical budget derived from the bubble
|
* For a sub-luminal ray whose separation |1 - |v_s|| dominates exp(-D) this
|
||||||
* geometry and the asymptotic separation rate; it is independent of the chosen
|
* reduces to the historical margin*4*escape/(1-|v_s|) geometry budget
|
||||||
* step size and of the accepted-step count. */
|
* (margin*4 == 5). For
|
||||||
|
* |v_s| -> 1, including exactly |v_s| = 1 and super-luminal |v_s| > 1, the
|
||||||
|
* exp(-D) floor is motivated by the center-comoving axial relation
|
||||||
|
* exp(-(L-L0)) = 1 - |v_s|*(1-f); it cuts off the separation scale at the
|
||||||
|
* finite dark threshold. This is a resource heuristic, not a universal bound
|
||||||
|
* for arbitrary cameras or directions. B is a resource allowance, not a
|
||||||
|
* guarantee that every ray escapes: a ray that needs more coordinate time ends
|
||||||
|
* as UNRESOLVED/BUDGET_EXHAUSTED, which the render-level publication gate
|
||||||
|
* handles. B is independent of the chosen step size and accepted-step count.
|
||||||
|
*
|
||||||
|
* Saturation rules:
|
||||||
|
* - exp(-D) may underflow to 0 for a large D; the max still selects the
|
||||||
|
* finite separation unless the separation itself is 0.
|
||||||
|
* - when the ordinary 5*R/denom is not finite and positive (denom == 0, or
|
||||||
|
* the division/scale overflows), fall back to a log-space evaluation
|
||||||
|
* log B = log(5) + log(R) - max(log(separation), -D), which never forms
|
||||||
|
* exp(D); clamp the log to [log(DBL_MIN), log(DBL_MAX/4)] and exponentiate.
|
||||||
|
* - the ordinary result is clamped to the same [DBL_MIN, DBL_MAX/4] range,
|
||||||
|
* reserving DBL_MAX/4 for the default 4x retry growth.
|
||||||
|
* - exp(D) is never evaluated. */
|
||||||
static double alcubierre_trace_time_budget(const Settings *s) {
|
static double alcubierre_trace_time_budget(const Settings *s) {
|
||||||
const double escape =
|
const double escape =
|
||||||
spacetime_alcubierre_escape_radius(s->alcubierre_radius,
|
spacetime_alcubierre_escape_radius(s->alcubierre_radius,
|
||||||
s->alcubierre_sigma);
|
s->alcubierre_sigma);
|
||||||
const double separation = 1.0 - fabs(s->alcubierre_vs);
|
const double separation = fabs(1.0 - fabs(s->alcubierre_vs));
|
||||||
return ALCUBIERRE_BUDGET_MARGIN * 4.0 * escape / separation;
|
const double floor = exp(-s->dark_threshold); /* may underflow to 0 */
|
||||||
|
const double upper = DBL_MAX / 4.0;
|
||||||
|
if (!isfinite(escape) || escape <= 0.0)
|
||||||
|
return DBL_MIN;
|
||||||
|
const double denom = fmax(separation, floor);
|
||||||
|
if (denom > 0.0 && isfinite(denom)) {
|
||||||
|
/* Do not pre-scale 5*R (which could overflow); divide first. */
|
||||||
|
const double scaled = (escape / denom) * (ALCUBIERRE_BUDGET_MARGIN * 4.0);
|
||||||
|
if (isfinite(scaled) && scaled > 0.0)
|
||||||
|
return fmin(fmax(scaled, DBL_MIN), upper);
|
||||||
|
}
|
||||||
|
/* Extreme fallback: separation == 0 with exp(-D) underflowed to 0, or the
|
||||||
|
* ordinary arithmetic saturated. Evaluate in log space without exp(D). */
|
||||||
|
double log_budget = log(ALCUBIERRE_BUDGET_MARGIN * 4.0) + log(escape) -
|
||||||
|
fmax(log(separation), -s->dark_threshold);
|
||||||
|
if (!isfinite(log_budget))
|
||||||
|
log_budget = log(upper);
|
||||||
|
log_budget = fmin(log_budget, log(upper));
|
||||||
|
log_budget = fmax(log_budget, log(DBL_MIN));
|
||||||
|
return fmin(fmax(exp(log_budget), DBL_MIN), upper);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Legacy accepted-step estimate for the same coverage, used only by the
|
/* Legacy accepted-step estimate for the same coverage, used only by the
|
||||||
@@ -1190,10 +1228,13 @@ static GeodesicTraceConfig trace_config(const Settings *s) {
|
|||||||
#elif defined(SPACETIME_ALCUBIERRE)
|
#elif defined(SPACETIME_ALCUBIERRE)
|
||||||
default_step = alcubierre_time_step(s);
|
default_step = alcubierre_time_step(s);
|
||||||
{
|
{
|
||||||
/* Accepted-step resource estimate along the legacy fixed-step policy. It
|
/* Accepted-step resource estimate along the legacy fixed-step policy.
|
||||||
* may be capped at ALCUBIERRE_MAX_TRACE_STEPS; the coordinate-time coverage
|
* `alcubierre_step_budget` may be +inf or overflow for a near-luminal or
|
||||||
* below is what guarantees the physical budget, so a capped count never
|
* super-luminal bubble, so the estimate is only cast when it is finite and
|
||||||
* shortens the trusted history. */
|
* strictly below the cap; otherwise the cap is kept. A capped count is an
|
||||||
|
* independent resource allowance and never shortens the trusted history,
|
||||||
|
* which the coordinate-time coverage below defines. It does not guarantee
|
||||||
|
* escape for every ray. */
|
||||||
const double estimated_steps = alcubierre_step_budget(s);
|
const double estimated_steps = alcubierre_step_budget(s);
|
||||||
unsigned int budget = ALCUBIERRE_MAX_TRACE_STEPS;
|
unsigned int budget = ALCUBIERRE_MAX_TRACE_STEPS;
|
||||||
if (isfinite(estimated_steps) && estimated_steps < (double)budget)
|
if (isfinite(estimated_steps) && estimated_steps < (double)budget)
|
||||||
@@ -2393,33 +2434,28 @@ int main(int argc, char **argv) {
|
|||||||
if (spacetime_create_alcubierre(&spacetime, settings.alcubierre_vs,
|
if (spacetime_create_alcubierre(&spacetime, settings.alcubierre_vs,
|
||||||
settings.alcubierre_radius,
|
settings.alcubierre_radius,
|
||||||
settings.alcubierre_sigma)) {
|
settings.alcubierre_sigma)) {
|
||||||
fputs("Could not create Alcubierre spacetime source; require |v_s| < 1, "
|
fputs("Could not create Alcubierre spacetime source; require finite "
|
||||||
"R > 0, sigma > 0.\n", stderr);
|
"v_s, R > 0, sigma > 0, and a derived escape radius beyond R.\n",
|
||||||
|
stderr);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
/* The fixed-step RK4 comparison path sizes its history from
|
/* The fixed-step RK4 comparison path sizes its history from
|
||||||
* step * accepted-step count, so its domain guard still rejects an
|
* step * accepted-step count, so its derived default estimate is still
|
||||||
* estimate that exceeds the cap. The adaptive path uses an independent
|
* bounded by the cap. An explicit --trace-max-steps is a user resource
|
||||||
* coordinate-time budget and must not be rejected here: trustworthy quota
|
* allowance and is never rejected here; the adaptive path uses an
|
||||||
* exhaustion is reported as UNRESOLVED and handled by the publication
|
* independent coordinate-time budget and is never rejected here either
|
||||||
* gate, and explicit lookback/step overrides remain independent. */
|
* (trustworthy quota exhaustion is reported as UNRESOLVED and handled by
|
||||||
|
* the publication gate). */
|
||||||
if (settings.stepper == GEODESIC_STEPPER_RK4 &&
|
if (settings.stepper == GEODESIC_STEPPER_RK4 &&
|
||||||
|
!settings.trace_max_steps_specified &&
|
||||||
alcubierre_step_budget(&settings) >
|
alcubierre_step_budget(&settings) >
|
||||||
(double)ALCUBIERRE_MAX_TRACE_STEPS) {
|
(double)ALCUBIERRE_MAX_TRACE_STEPS) {
|
||||||
/* The estimate has a V-shaped minimum at sigma = 0.5, where the step
|
|
||||||
* stops being capped: below it the 20/sigma term dominates (increase
|
|
||||||
* sigma helps), above it the step scales as 1/sigma (decrease sigma
|
|
||||||
* helps), and at exactly 0.5 neither direction improves anything. */
|
|
||||||
const char *sigma_advice = "";
|
|
||||||
if (settings.alcubierre_sigma > 0.5)
|
|
||||||
sigma_advice = "decrease --alcubierre-sigma, ";
|
|
||||||
else if (settings.alcubierre_sigma < 0.5)
|
|
||||||
sigma_advice = "increase --alcubierre-sigma, ";
|
|
||||||
fprintf(stderr,
|
fprintf(stderr,
|
||||||
"Fixed-step RK4 Alcubierre trace budget exceeds the %u-step "
|
"Fixed-step RK4 Alcubierre default trace budget exceeds the "
|
||||||
"cap; decrease --alcubierre-radius, %sor move --alcubierre-vs "
|
"%u-step cap; decrease --alcubierre-radius, adjust "
|
||||||
"away from +/-1, or use the adaptive default.\n",
|
"--dark-threshold or --alcubierre-sigma, or use the adaptive "
|
||||||
ALCUBIERRE_MAX_TRACE_STEPS, sigma_advice);
|
"default or an explicit --trace-max-steps.\n",
|
||||||
|
ALCUBIERRE_MAX_TRACE_STEPS);
|
||||||
spacetime_destroy(&spacetime);
|
spacetime_destroy(&spacetime);
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-2
@@ -122,8 +122,10 @@ int spacetime_create_default(SpacetimeSource *source);
|
|||||||
int spacetime_create_minkowski(SpacetimeSource *source, double escape_radius);
|
int spacetime_create_minkowski(SpacetimeSource *source, double escape_radius);
|
||||||
int spacetime_create_schwarzschild_ks(SpacetimeSource *source, double mass,
|
int spacetime_create_schwarzschild_ks(SpacetimeSource *source, double mass,
|
||||||
double escape_radius);
|
double escape_radius);
|
||||||
/* Moving Alcubierre bubble with x_s(t) = vs*t and x_s(0) = 0. Requires
|
/* Moving Alcubierre bubble with x_s(t) = vs*t and x_s(0) = 0. Requires a
|
||||||
* |vs| < 1, R > 0, and sigma > 0. */
|
* finite vs, R > 0, and sigma > 0. Sub- and super-luminal |vs| are accepted;
|
||||||
|
* classify() only ever reports ACTIVE or ESCAPED, and the shared
|
||||||
|
* camera-relative dark policy may terminate a ray as DARK for any finite vs. */
|
||||||
int spacetime_create_alcubierre(SpacetimeSource *source, double vs,
|
int spacetime_create_alcubierre(SpacetimeSource *source, double vs,
|
||||||
double radius, double sigma);
|
double radius, double sigma);
|
||||||
/* Bubble-centered escape radius used by the Alcubierre backend; also lets
|
/* Bubble-centered escape radius used by the Alcubierre backend; also lets
|
||||||
|
|||||||
@@ -107,11 +107,8 @@ static SpacetimePointStatus alcubierre_eval(const SpacetimeSource *source,
|
|||||||
return SPACETIME_POINT_OK;
|
return SPACETIME_POINT_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* A warp bubble has no curvature singularity or horizon for |v_s| < 1, so
|
/* The exotic matter that would source the bubble is treated as optically
|
||||||
* rays are only ever ACTIVE or ESCAPED; the exotic matter that would source
|
* transparent. The escape sphere follows the moving bubble. */
|
||||||
* the bubble is treated as optically transparent. The escape sphere follows
|
|
||||||
* the bubble, so rays terminate only once the metric is flat to machine
|
|
||||||
* precision at their current location. */
|
|
||||||
static SpacetimeRayStatus alcubierre_classify(const SpacetimeSource *source,
|
static SpacetimeRayStatus alcubierre_classify(const SpacetimeSource *source,
|
||||||
double t, const double x[3]) {
|
double t, const double x[3]) {
|
||||||
const AlcubierreContext *context = source->context;
|
const AlcubierreContext *context = source->context;
|
||||||
@@ -179,7 +176,7 @@ double spacetime_alcubierre_escape_radius(double radius, double sigma) {
|
|||||||
|
|
||||||
int spacetime_create_alcubierre(SpacetimeSource *source, double vs,
|
int spacetime_create_alcubierre(SpacetimeSource *source, double vs,
|
||||||
double radius, double sigma) {
|
double radius, double sigma) {
|
||||||
if (source == NULL || !isfinite(vs) || fabs(vs) >= 1.0 ||
|
if (source == NULL || !isfinite(vs) ||
|
||||||
!isfinite(radius) || radius <= 0.0 || !isfinite(sigma) || sigma <= 0.0)
|
!isfinite(radius) || radius <= 0.0 || !isfinite(sigma) || sigma <= 0.0)
|
||||||
return -1;
|
return -1;
|
||||||
/* Reject parameter combinations whose derived domain overflows or does not
|
/* Reject parameter combinations whose derived domain overflows or does not
|
||||||
|
|||||||
+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
|
runtime short; physical comparisons use stored lens-map endpoints, not only the
|
||||||
rendered PNG.
|
rendered PNG.
|
||||||
"""
|
"""
|
||||||
|
import math
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import struct
|
import struct
|
||||||
@@ -139,6 +140,28 @@ def sum_rhs(vertices):
|
|||||||
return sum(v[14] for v in 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-',
|
with tempfile.TemporaryDirectory(prefix='gr-adaptive-cli-',
|
||||||
dir=str(TMP_ROOT)) as directory:
|
dir=str(TMP_ROOT)) as directory:
|
||||||
tmp = Path(directory)
|
tmp = Path(directory)
|
||||||
@@ -435,111 +458,217 @@ with tempfile.TemporaryDirectory(prefix='gr-adaptive-cli-',
|
|||||||
|
|
||||||
print(f'{backend}: adaptive CLI checks passed', flush=True)
|
print(f'{backend}: adaptive CLI checks passed', flush=True)
|
||||||
|
|
||||||
# Alcubierre production-default smoke: the DP54 default policy must
|
# Alcubierre production policy: sub- and super-luminal velocities share one
|
||||||
# validate and the migrated lookback budget must actually cover the warp
|
# finite resource allowance B = 5*escape / max(|1-|v_s||, exp(-D)); the DP54
|
||||||
# bubble feature (rays integrate and escape) instead of pre-routing all
|
# default must validate, actually integrate the warp feature, and reach both
|
||||||
# misses. No movie/thread sweep for this third backend.
|
# the shared DARK terminal and escapes. All images are tiny 8x4/16x8.
|
||||||
alc = BUILD / 'alcubierre_sky'
|
alc = BUILD / 'alcubierre_sky'
|
||||||
if not alc.exists():
|
if not alc.exists():
|
||||||
print('alcubierre: binary absent, skipping', flush=True)
|
print('alcubierre: binary absent, skipping', flush=True)
|
||||||
else:
|
else:
|
||||||
alc_help = run(alc, '--help').stdout
|
alc_help = run(alc, '--help').stdout
|
||||||
ext = 'png' if '.png' in alc_help else 'ppm'
|
ext = 'png' if '.png' in alc_help else 'ppm'
|
||||||
alc_map = tmp / 'alcubierre_default.grlens'
|
assert '|v_s| < 1' not in alc_help, 'help still claims |v_s| < 1'
|
||||||
alc_out = tmp / f'alcubierre_default.{ext}'
|
escape1 = 1.0 + 20.0 / 1.0 # R = sigma = 1
|
||||||
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
|
def alc_camera(vs, ra_deg, dec_deg=0.0):
|
||||||
# margin*4*escape/(1-|v_s|) with escape = R + 20/sigma, and it is
|
return ['--observer-position', '0', '0', '0',
|
||||||
# independent of the accepted-step count and of the chosen initial
|
'--observer-velocity', repr(vs), '0', '0',
|
||||||
# step. Two cheap maps with different resource overrides must keep the
|
'--look-ra-deg', repr(ra_deg),
|
||||||
# same lookback.
|
'--look-dec-deg', repr(dec_deg)]
|
||||||
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):
|
def alc_map_prov(tag, vs, D=8.0, radius=1.0, extra=(), allow=True,
|
||||||
m = tmp / f'alcubierre_{tag}.grlens'
|
width=8, height=4, cell=4, camera=None,
|
||||||
run(alc, '--alcubierre-vs', 0.3, '--alcubierre-radius', 1,
|
catalog=True):
|
||||||
'--alcubierre-sigma', 1, '--catalog', 'assets/sky_grid_5deg.csv',
|
m = tmp / f'alc_{tag}.grlens'
|
||||||
'--width', 16, '--height', 8, '--fov-deg', 80, '--exposure',
|
args = ['--alcubierre-vs', repr(vs), '--alcubierre-radius',
|
||||||
1e-3, '--coarse-cell-pixels', 8, '--refine-max-level', 0,
|
repr(radius), '--alcubierre-sigma', '1',
|
||||||
'--psf-relative-tail', 1e-4, '--allow-incomplete',
|
'--dark-threshold', repr(D), '--width', str(width),
|
||||||
'--output', tmp / f'alcubierre_{tag}.{ext}',
|
'--height', str(height), '--fov-deg', 80, '--exposure',
|
||||||
'--lens-map-output', m, *options)
|
'1e-3', '--coarse-cell-pixels', str(cell),
|
||||||
return map_provenance(m)[2]
|
'--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)
|
# 1) Ordinary sub-luminal separation: B is exactly 5*escape/sep and is
|
||||||
assert steps_override['initial_max_steps'] == 8
|
# independent of the step count and of the initial step.
|
||||||
assert abs(steps_override['max_lookback_time'] - alc_expected_lookback) \
|
p_sub, _ = alc_map_prov('sub', 0.3, camera=alc_camera(0.3, 0.0))
|
||||||
< 1e-12, steps_override
|
assert p_sub['integrator'] == 1, p_sub
|
||||||
step_override = alc_map_with('step_override', '--ode-initial-step', 0.02)
|
assert math.isclose(p_sub['max_lookback_time'],
|
||||||
assert abs(step_override['coordinate_time_step'] - 0.02) < 1e-15
|
alcubierre_budget(escape1, 0.3, 8.0), rel_tol=1e-12)
|
||||||
assert abs(step_override['max_lookback_time'] - alc_expected_lookback) \
|
assert p_sub['min_step'] <= p_sub['coordinate_time_step'] \
|
||||||
< 1e-12, step_override
|
<= 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
|
# 2) Near-luminal (vs = 1, separation 0): the exp(-D) floor makes the
|
||||||
# estimate far exceeds the cap. The DP path must accept an explicit
|
# allowance finite, and it grows with the dark threshold D.
|
||||||
# tiny coordinate-time budget instead of being rejected at startup by
|
p8, _ = alc_map_prov('near8', 1.0, D=8.0, camera=alc_camera(1.0, 0.0))
|
||||||
# the fixed-step guard, and it must actually exercise the quota path
|
p12, _ = alc_map_prov('near12', 1.0, D=12.0, camera=alc_camera(1.0, 0.0))
|
||||||
# (UNRESOLVED rays or real RHS work), not pre-route everything to
|
assert math.isclose(p8['max_lookback_time'],
|
||||||
# escapes. --allow-incomplete publishes the diagnostic frame.
|
alcubierre_budget(escape1, 1.0, 8.0), rel_tol=1e-12)
|
||||||
extreme = ['--alcubierre-vs', 0.99999999, '--alcubierre-radius', 1,
|
assert math.isclose(p12['max_lookback_time'],
|
||||||
'--alcubierre-sigma', 1, '--catalog',
|
alcubierre_budget(escape1, 1.0, 12.0), rel_tol=1e-12)
|
||||||
'assets/sky_grid_5deg.csv', '--width', 8, '--height', 4,
|
assert p12['max_lookback_time'] > p8['max_lookback_time']
|
||||||
'--fov-deg', 80, '--exposure', 1e-3,
|
|
||||||
'--coarse-cell-pixels', 4, '--refine-max-level', 0,
|
# 3) Both sides of the threshold-derived vcut and exactly luminal
|
||||||
'--psf-relative-tail', 1e-4,
|
# values use either separation or the finite floor; a tiny film traces
|
||||||
'--observer-position', 0, 0, 0,
|
# and resolves without INCOMPLETE outcomes.
|
||||||
'--observer-velocity', 0.99999999, 0, 0,
|
vcut = 1.0 - math.exp(-8.0)
|
||||||
'--look-ra-deg', 0, '--look-dec-deg', 0]
|
for vs in (0.999, math.nextafter(vcut, 0.0),
|
||||||
ext_map = tmp / 'alcubierre_extreme.grlens'
|
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',
|
run(alc, *extreme, '--integrator', 'dp54',
|
||||||
'--trace-lookback-time', 1, '--trace-max-steps', 4,
|
'--trace-lookback-time', '1', '--trace-max-steps', '4',
|
||||||
'--max-total-steps', 4, '--retry-step-increment', 0,
|
'--max-total-steps', '4', '--retry-step-increment', '0',
|
||||||
'--max-total-lookback-time', 1, '--retry-lookback-increment', 0,
|
'--max-total-lookback-time', '1', '--retry-lookback-increment', '0',
|
||||||
'--allow-incomplete', '--output',
|
'--allow-incomplete', '--output', tmp / f'alc_extreme.{ext}',
|
||||||
tmp / f'alcubierre_extreme.{ext}', '--lens-map-output', ext_map)
|
'--lens-map-output', ext_map)
|
||||||
_, _, ext_prov = map_provenance(ext_map)
|
_, _, ext_prov = map_provenance(ext_map)
|
||||||
assert ext_prov['integrator'] == 1
|
assert ext_prov['integrator'] == 1
|
||||||
assert abs(ext_prov['max_lookback_time'] - 1.0) < 1e-15, ext_prov
|
assert abs(ext_prov['max_lookback_time'] - 1.0) < 1e-15, ext_prov
|
||||||
assert ext_prov['initial_max_steps'] == 4, ext_prov
|
assert ext_prov['initial_max_steps'] == 4, ext_prov
|
||||||
ext_vertices, _ = map_vertices(ext_map)
|
ext_vertices, _ = map_vertices(ext_map)
|
||||||
assert any(v[10] == 2 for v in ext_vertices) or \
|
assert all(v[10] == 2 for v in ext_vertices), \
|
||||||
sum_rhs(ext_vertices) > 0, \
|
('a 1-time/4-step trace fabricated a non-UNRESOLVED outcome',
|
||||||
'extreme Alcubierre case did not exercise the quota path'
|
[v[10] for v in ext_vertices])
|
||||||
|
|
||||||
# The same parameters without explicit DP budgets keep the legacy
|
# 7) Large dark threshold (D=1000) with vs=1: exp(-1000) underflows to
|
||||||
# fixed-step startup guard, which still rejects the estimated domain.
|
# 0 and the separation is exactly 0, so the log fallback saturates
|
||||||
rk4_extreme = run(
|
# the default allowance to DBL_MAX/4. A 4-step/4-total cap with
|
||||||
alc, '--integrator', 'rk4', '--alcubierre-vs', 0.99999999,
|
# retry disabled keeps the trace short while the provenance records
|
||||||
'--alcubierre-radius', 1, '--alcubierre-sigma', 1, '--catalog',
|
# the non-masked saturated default. An explicit --trace-lookback
|
||||||
'assets/sky_grid_5deg.csv', '--width', 8, '--height', 4,
|
# overrides it without masking the independent step allowance.
|
||||||
'--fov-deg', 80, '--exposure', 1e-3, '--coarse-cell-pixels', 4,
|
psat, _ = alc_map_prov('sat1000', 1.0, D=1000.0,
|
||||||
'--refine-max-level', 0, '--psf-relative-tail', 1e-4,
|
extra=('--trace-max-steps', '4',
|
||||||
'--output', tmp / f'alcubierre_rk4_extreme.{ext}', ok=False)
|
'--max-total-steps', '4',
|
||||||
assert rk4_extreme.returncode == 2, rk4_extreme.stderr
|
'--retry-step-increment', '0'),
|
||||||
assert 'cap' in rk4_extreme.stderr, rk4_extreme.stderr
|
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; '
|
# 8) RK4 guard: a super-luminal default (vs=2, small estimate) is no
|
||||||
'extreme DP quota path ok, RK4 guard still rejects', flush=True)
|
# 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};
|
SpacetimeSource source = {0};
|
||||||
MetricData metric;
|
MetricData metric;
|
||||||
CHECK(spacetime_create_alcubierre(&source, vs, radius, sigma) == 0);
|
CHECK(spacetime_create_alcubierre(&source, vs, radius, sigma) == 0);
|
||||||
CHECK(spacetime_create_alcubierre(&source, 1.0, radius, sigma) != 0);
|
/* Sub- and super-luminal velocities are both accepted. A successful
|
||||||
CHECK(spacetime_create_alcubierre(&source, -1.5, radius, sigma) != 0);
|
* 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, 0.0, sigma) != 0);
|
||||||
CHECK(spacetime_create_alcubierre(&source, vs, radius, 0.0) != 0);
|
CHECK(spacetime_create_alcubierre(&source, vs, radius, 0.0) != 0);
|
||||||
/* A derived escape radius that overflows or does not exceed R is rejected. */
|
/* 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);
|
spacetime_destroy(&source);
|
||||||
puts("alcubierre regression passed");
|
puts("alcubierre regression passed");
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
@@ -116,32 +116,39 @@ with the bubble center following the constant-velocity worldline
|
|||||||
$$f(r) = \frac{\tanh(\sigma(r+R)) - \tanh(\sigma(r-R))}{2\tanh(\sigma R)},\qquad
|
$$f(r) = \frac{\tanh(\sigma(r+R)) - \tanh(\sigma(r-R))}{2\tanh(\sigma R)},\qquad
|
||||||
r_s = \sqrt{(x-x_s)^2 + y^2 + z^2}.$$
|
r_s = \sqrt{(x-x_s)^2 + y^2 + z^2}.$$
|
||||||
|
|
||||||
The bubble therefore propagates through the coordinates, and the metric is
|
The renderer evaluates the moving bubble's time-dependent metric along each
|
||||||
time-dependent: the renderer evaluates `f(r_s)` and its spatial derivatives at
|
ray. The exotic matter sourcing the bubble is treated as optically transparent.
|
||||||
each coordinate time, while the extrinsic curvature supplies the required
|
The shared dark policy terminates rays when `L - L0 >= T` (`T = 8` by default,
|
||||||
`d_t gamma` information to the 3+1 null-ray equations. The exotic matter that
|
set with `--dark-threshold`); this finite threshold can also be reached at
|
||||||
would source the bubble is treated as optically transparent and there is no
|
sub-luminal bubble velocities.
|
||||||
horizon, so in practice rays are active or escaped: the bubble has no causal
|
|
||||||
boundary at which `L - L0` can diverge, and the shared dark policy is not
|
|
||||||
expected to trigger. This is why the backend requires a sub-luminal
|
|
||||||
`|v_s| < 1`; at or above `1` the metric develops an ergoregion/event horizon and
|
|
||||||
static observers cease to exist, which is outside the current scope.
|
|
||||||
|
|
||||||
| Option | Meaning / default |
|
| Option | Meaning / default |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `--alcubierre-vs V` | Constant shift parameter, `|V| < 1` (default 0.5) |
|
| `--alcubierre-vs V` | Constant bubble velocity `v_s` (any finite value, default 0.5) |
|
||||||
| `--alcubierre-radius R` | Bubble radius `R > 0` (default 5) |
|
| `--alcubierre-radius R` | Bubble radius `R > 0` (default 5) |
|
||||||
| `--alcubierre-sigma S` | Wall sharpness `S > 0` (default 1) |
|
| `--alcubierre-sigma S` | Wall sharpness `S > 0` (default 1) |
|
||||||
|
|
||||||
`f` decays to zero past `r_s = R` over a transition width `~1/sigma`, so the
|
A camera must be timelike with an orthonormal tetrad. Its coordinate velocity
|
||||||
finite escape sphere is bubble-centered with radius `R + 20/sigma` and needs no
|
`V = dx/dt` must satisfy `|V - v_s f e_x| < 1`. A static camera requires
|
||||||
CLI option; it follows the moving bubble, so rays terminate only once the local
|
`|v_s f| < 1`; at the bubble center, the comoving velocity `(v_s, 0, 0)` is
|
||||||
metric is flat to below double precision. The single-frame camera default is
|
timelike even for super-luminal bubbles. Cameras may lie inside or outside the
|
||||||
`(0,0,15)` at `t = 0`, when the bubble is still at the origin; it must lie
|
escape sphere; exterior rays are routed to their first entry or to infinity.
|
||||||
inside the escape sphere, or the observer build fails with an explicit error.
|
|
||||||
The per-ray step budget scales with the escape radius and `1/(1-|v_s|)`, so
|
The escape sphere follows the bubble with radius `R + 20/sigma`; escaping rays
|
||||||
near-luminal `v_s` still lets grazing rays escape; combinations whose
|
continue through a Minkowski exterior. The single-frame camera defaults to
|
||||||
worst-case budget would exceed the internal cap are rejected at startup.
|
`(0,0,15)` at `t = 0`. The finite-radius truncation leaves a residual shift of
|
||||||
|
order `|v_s| e^{-40}`; accuracy at extremely large velocities is not guaranteed.
|
||||||
|
|
||||||
|
Per-ray coordinate-time coverage is a **resource allowance**
|
||||||
|
|
||||||
|
$$B = \frac{5\,R_\text{escape}}{\max\!\big(|1-|v_s||,\; e^{-T}\big)},\qquad
|
||||||
|
R_\text{escape} = R + \frac{20}{\sigma},$$
|
||||||
|
|
||||||
|
with `T = --dark-threshold`, clamped to `[DBL_MIN, DBL_MAX/4]`. This is not a
|
||||||
|
completion guarantee: quota exhaustion returns `UNRESOLVED/BUDGET_EXHAUSTED`.
|
||||||
|
`--trace-lookback-time` overrides `B` independently of `--trace-max-steps`.
|
||||||
|
RK4 rejects a derived default step estimate above its internal cap; an explicit
|
||||||
|
`--trace-max-steps` bypasses that check.
|
||||||
|
|
||||||
Lensing and frequency shifts come from the bubble wall. The configuration is
|
Lensing and frequency shifts come from the bubble wall. The configuration is
|
||||||
invariant under the isometry `(t, x) -> (t + T, x + v_s T)`, so observers
|
invariant under the isometry `(t, x) -> (t + T, x + v_s T)`, so observers
|
||||||
@@ -152,7 +159,7 @@ example:
|
|||||||
make -j PSF_BACKEND=cpu SPACETIME=alcubierre backend
|
make -j PSF_BACKEND=cpu SPACETIME=alcubierre backend
|
||||||
./build/Release/alcubierre_sky --catalog assets/sky_grid_5deg.csv \
|
./build/Release/alcubierre_sky --catalog assets/sky_grid_5deg.csv \
|
||||||
--observer-radius 15 --look-ra-deg 90 --look-dec-deg -90 \
|
--observer-radius 15 --look-ra-deg 90 --look-dec-deg -90 \
|
||||||
--alcubierre-vs 0.5 --alcubierre-radius 5 --alcubierre-sigma 1 \
|
--alcubierre-vs 1.5 --alcubierre-radius 1 --alcubierre-sigma 1 \
|
||||||
--width 640 --height 360 --fov-deg 60 --exposure 1 \
|
--width 640 --height 360 --fov-deg 60 --exposure 1 \
|
||||||
--coarse-cell-pixels 16 --refine-max-level 2 --psf-direct \
|
--coarse-cell-pixels 16 --refine-max-level 2 --psf-direct \
|
||||||
--output output/imgs/alcubierre_wall.png
|
--output output/imgs/alcubierre_wall.png
|
||||||
|
|||||||
Reference in new issue
Block a user