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.
405 lines
14 KiB
C
405 lines
14 KiB
C
/*
|
|
* Experiment B: observed accepted-step sizes of the production DP54 core.
|
|
*
|
|
* This translation unit textually includes src/geodesic.c so the real,
|
|
* production `dp_advance_one` driver and the real `State` (GeodesicRayState)
|
|
* are exercised directly on a finite trajectory interval. The production
|
|
* initialization is reproduced exactly as `geodesic_trace_past` does for an
|
|
* INSIDE route: `geodesic_initialize_past_ray_metric` at the camera event,
|
|
* then `next_step = config->coordinate_time_step` (the init already sets
|
|
* `integration_start_time`, `log_alpha_p0` and `log_alpha_p0_0`). This is the
|
|
* same initialization the public endpoint uses, not a re-implementation.
|
|
*
|
|
* For each selected ray we:
|
|
* 1. run the public endpoint with tol=1e-12 to get a trusted reference
|
|
* stop time and terminal class (physical terminal class is owned by A);
|
|
* 2. integrate the same ray with the production DP core up to
|
|
* T = min(20, ref_span) observed steps <= 2000, recording each accepted
|
|
* step magnitude, boundary-limit flag, rejection/RHS deltas and the null
|
|
* residual gamma^{ij} Pi_i Pi_j - 1.
|
|
*
|
|
* Link line (geodesic.c is textually included, so it is NOT linked; the
|
|
* analytic backends are also textually included here):
|
|
* cc -std=c11 -O2 -Isrc b_actual_h.c asymptotic.c asymptotic_schwarzschild.c \
|
|
* spacetime_common.c observer.c -lm
|
|
*/
|
|
#define _POSIX_C_SOURCE 200809L
|
|
|
|
#include <math.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
|
|
#define spacetime_create_default spacetime_create_default_minkowski_b
|
|
#include "../../src/spacetime_minkowski.c"
|
|
#undef spacetime_create_default
|
|
#define spacetime_create_default spacetime_create_default_schwarzschild_b
|
|
#include "../../src/spacetime_schwarzschild.c"
|
|
#undef spacetime_create_default
|
|
#define spacetime_create_default spacetime_create_default_alcubierre_b
|
|
#include "../../src/spacetime_alcubierre.c"
|
|
#undef spacetime_create_default
|
|
|
|
/* The production geodesic core, textually included. Its static State,
|
|
* dp_advance_one and invert become visible to the code below. */
|
|
#include "../../src/geodesic.c"
|
|
|
|
#define PI 3.14159265358979323846
|
|
#define MAX_BCASES 12
|
|
#define MAX_BDIRS 8
|
|
#define MAX_OBS_STEPS 2000
|
|
|
|
typedef struct {
|
|
char name[32];
|
|
int prov; /* 0 sch, 1 mink, 2 alc */
|
|
double mass, esc;
|
|
double vs, radius, sigma;
|
|
double look_ra_deg, look_dec_deg;
|
|
double pos[3], vel[3];
|
|
double initial, max_step;
|
|
double ref_max_step, lookback;
|
|
unsigned int ref_max_steps;
|
|
int ndirs;
|
|
double dirs[MAX_BDIRS][3];
|
|
} BCase;
|
|
|
|
static double b_now(void) {
|
|
struct timespec ts;
|
|
clock_gettime(CLOCK_MONOTONIC, &ts);
|
|
return (double)ts.tv_sec + 1e-9 * (double)ts.tv_nsec;
|
|
}
|
|
|
|
static void b_dir(double th, double n[3]) {
|
|
if (th == 0.0) {
|
|
n[0] = 1.0;
|
|
n[1] = n[2] = 0.0;
|
|
} else if (th == PI) {
|
|
n[0] = -1.0;
|
|
n[1] = n[2] = 0.0;
|
|
} else {
|
|
n[0] = cos(th);
|
|
n[1] = sin(th);
|
|
n[2] = 0.0;
|
|
}
|
|
const double nn = sqrt(n[0] * n[0] + n[1] * n[1] + n[2] * n[2]);
|
|
for (int i = 0; i < 3; ++i)
|
|
n[i] /= nn;
|
|
}
|
|
|
|
static double b_critical_angle(double r) {
|
|
const double b = 3.0 * sqrt(3.0) * sqrt(1.0 - 2.0 / r) / r;
|
|
return (b < 1.0) ? asin(b) : PI / 2.0;
|
|
}
|
|
|
|
static GeodesicTraceConfig b_make_dp(double initial, double min_step,
|
|
double max_step, double tol,
|
|
unsigned int max_steps, double lookback) {
|
|
GeodesicTraceConfig c;
|
|
memset(&c, 0, sizeof c);
|
|
c.coordinate_time_step = initial;
|
|
c.max_steps = max_steps;
|
|
c.threshold.kind = THRESHOLD_LOG_ENERGY_GROWTH;
|
|
c.threshold.value = 8.0;
|
|
c.threshold.policy_version = 3;
|
|
c.stepper = GEODESIC_STEPPER_DP54;
|
|
c.atol_x = c.atol_Pi = c.atol_L = c.rtol = tol;
|
|
c.min_step = min_step;
|
|
c.max_step = max_step;
|
|
c.consecutive_rejection_limit = 32;
|
|
c.max_lookback_time = lookback;
|
|
return c;
|
|
}
|
|
|
|
static int b_build_source(const BCase *c, SpacetimeSource *s) {
|
|
if (c->prov == 0)
|
|
return spacetime_create_schwarzschild_ks(s, c->mass, c->esc);
|
|
if (c->prov == 1)
|
|
return spacetime_create_minkowski(s, c->esc);
|
|
return spacetime_create_alcubierre(s, c->vs, c->radius, c->sigma);
|
|
}
|
|
|
|
static int b_build_observer(const BCase *c, const SpacetimeSource *s,
|
|
ObserverState *o) {
|
|
MetricData m;
|
|
if (spacetime_eval(s, 0.0, c->pos, &m) != SPACETIME_POINT_OK)
|
|
return -1;
|
|
ObserverCamera cam;
|
|
memset(&cam, 0, sizeof cam);
|
|
for (int i = 0; i < 3; ++i) {
|
|
cam.position[i] = c->pos[i];
|
|
cam.velocity[i] = c->vel[i];
|
|
}
|
|
cam.look_ra_deg = c->look_ra_deg;
|
|
cam.look_dec_deg = c->look_dec_deg;
|
|
return observer_from_coordinate_camera(&m, &cam, o, NULL) ==
|
|
OBSERVER_BUILD_OK
|
|
? 0
|
|
: -1;
|
|
}
|
|
|
|
static void b_add_sch(BCase *c, const char *name, double r, double look,
|
|
const double *thetas, int nt) {
|
|
memset(c, 0, sizeof *c);
|
|
snprintf(c->name, sizeof c->name, "%s", name);
|
|
c->prov = 0;
|
|
c->mass = 1.0;
|
|
c->esc = 256.0;
|
|
c->look_ra_deg = look;
|
|
c->pos[0] = r;
|
|
c->initial = 0.1;
|
|
c->max_step = 2.0;
|
|
c->ref_max_step = 0.25;
|
|
c->lookback = 6553.6;
|
|
c->ref_max_steps = 65536;
|
|
c->ndirs = nt;
|
|
for (int i = 0; i < nt; ++i)
|
|
b_dir(thetas[i], c->dirs[i]);
|
|
}
|
|
|
|
static void b_add_mink(BCase *c) {
|
|
memset(c, 0, sizeof *c);
|
|
snprintf(c->name, sizeof c->name, "mink_moving");
|
|
c->prov = 1;
|
|
c->esc = 64.0;
|
|
c->look_ra_deg = 0.0;
|
|
c->pos[0] = 10.0;
|
|
c->pos[1] = 20.0;
|
|
c->pos[2] = -15.0;
|
|
c->vel[0] = 0.3;
|
|
c->vel[1] = 0.2;
|
|
c->vel[2] = 0.1;
|
|
c->initial = 1.0;
|
|
c->max_step = 16.0;
|
|
c->ref_max_step = 1.0;
|
|
c->lookback = 2048.0;
|
|
c->ref_max_steps = 4096;
|
|
c->ndirs = 2;
|
|
b_dir(0.0, c->dirs[0]);
|
|
b_dir(PI, c->dirs[1]);
|
|
}
|
|
|
|
static void b_add_alc(BCase *c, const char *name, double vs, double sigma,
|
|
const double *thetas, int nt) {
|
|
memset(c, 0, sizeof *c);
|
|
snprintf(c->name, sizeof c->name, "%s", name);
|
|
c->prov = 2;
|
|
c->vs = vs;
|
|
c->radius = 1.0;
|
|
c->sigma = sigma;
|
|
c->look_ra_deg = 0.0;
|
|
c->vel[0] = vs;
|
|
c->initial = fmin(0.1, 0.05 / sigma);
|
|
c->max_step = c->initial * 8.0;
|
|
c->ref_max_step = c->initial;
|
|
const double esc = spacetime_alcubierre_escape_radius(1.0, sigma);
|
|
c->lookback = 1.25 * 4.0 * esc / (1.0 - fabs(vs));
|
|
c->ref_max_steps = 200000;
|
|
c->ndirs = nt;
|
|
for (int i = 0; i < nt; ++i)
|
|
b_dir(thetas[i], c->dirs[i]);
|
|
}
|
|
|
|
/* gamma^{ij} Pi_i Pi_j - 1; `invert` is the production static from
|
|
* geodesic.c, so this is the exact production metric contraction. */
|
|
static double b_null_residual(const MetricData *m, const double Pi[3]) {
|
|
double g[3][3], inv[3][3];
|
|
for (int i = 0; i < 3; ++i)
|
|
for (int j = 0; j < 3; ++j)
|
|
g[i][j] = m->gamma[i][j];
|
|
if (invert(g, inv))
|
|
return NAN;
|
|
double v = 0.0;
|
|
for (int i = 0; i < 3; ++i)
|
|
for (int j = 0; j < 3; ++j)
|
|
v += inv[i][j] * Pi[i] * Pi[j];
|
|
return v - 1.0;
|
|
}
|
|
|
|
int main(void) {
|
|
static BCase cases[MAX_BCASES];
|
|
int nc = 0;
|
|
|
|
{
|
|
const double tc = b_critical_angle(30.0);
|
|
const double th[6] = {0.0, PI, tc - 1e-7, tc + 1e-7, tc - 1e-5, tc + 1e-5};
|
|
b_add_sch(&cases[nc++], "r30_inward", 30.0, 180.0, th, 6);
|
|
}
|
|
{
|
|
const double tc = b_critical_angle(100.0);
|
|
const double th[4] = {0.0, PI, tc, tc - 1e-7};
|
|
b_add_sch(&cases[nc++], "r100_inward", 100.0, 180.0, th, 4);
|
|
}
|
|
{
|
|
const double tc = b_critical_angle(2.1);
|
|
const double th[5] = {0.0, tc, tc - 1e-7, tc + 1e-7, PI};
|
|
b_add_sch(&cases[nc++], "r2p1_outward", 2.1, 0.0, th, 5);
|
|
}
|
|
{
|
|
const double th[2] = {0.0, PI};
|
|
b_add_sch(&cases[nc++], "r1p5_freefall", 1.5, 180.0, th, 2);
|
|
/* free-fall velocity is set below (b_add_sch leaves it zero). */
|
|
}
|
|
b_add_mink(&cases[nc++]);
|
|
{
|
|
const double th[2] = {0.0, PI};
|
|
b_add_alc(&cases[nc++], "alc_v3_s1", 0.3, 1.0, th, 2);
|
|
b_add_alc(&cases[nc++], "alc_v9_s10", 0.9, 10.0, th, 2);
|
|
b_add_alc(&cases[nc++], "alc_v9_s100", 0.9, 100.0, th, 2);
|
|
}
|
|
/* Free-fall coordinate velocity for r1.5 (same formula as A). */
|
|
{
|
|
BCase *c = &cases[3];
|
|
const double y = sqrt(2.0 / c->pos[0]);
|
|
c->vel[0] = -y * (1.0 + y) / (1.0 + y + y * y);
|
|
}
|
|
|
|
FILE *hf = fopen("raw/b_actual_h.csv", "w");
|
|
FILE *sf = fopen("raw/b_summary.csv", "w");
|
|
if (!hf || !sf) {
|
|
fprintf(stderr, "FATAL: cannot open b csv\n");
|
|
return 2;
|
|
}
|
|
fprintf(hf, "case,dir,step,t,h,boundary_limited,rhs_delta,reject_delta,"
|
|
"null_residual\n");
|
|
fprintf(sf, "case,dir,ref_outcome,ref_stop_t,span,target,observed_steps,"
|
|
"reached_target,terminated_reason,h_min,h_max,h_first,h_last,"
|
|
"boundary_steps,sum_rhs,sum_reject,max_reject_delta,"
|
|
"null_residual_max,final_t,wall_s\n");
|
|
|
|
long total_steps = 0;
|
|
for (int ci = 0; ci < nc; ++ci) {
|
|
BCase *c = &cases[ci];
|
|
SpacetimeSource source;
|
|
ObserverState observer;
|
|
if (b_build_source(c, &source) || b_build_observer(c, &source, &observer)) {
|
|
fprintf(stderr, "FATAL: cannot build B case %s\n", c->name);
|
|
return 2;
|
|
}
|
|
for (int di = 0; di < c->ndirs; ++di) {
|
|
/* 1. trusted reference via the public endpoint (physical terminal). */
|
|
GeodesicTraceConfig refcfg =
|
|
b_make_dp(c->initial, 1e-12, c->ref_max_step, 1e-12, c->ref_max_steps,
|
|
c->lookback);
|
|
RayEndpoint ref =
|
|
geodesic_trace_past(&source, &observer, c->dirs[di], &refcfg);
|
|
const double span =
|
|
isfinite(ref.stop_coordinate_time)
|
|
? (observer.coordinate_time - ref.stop_coordinate_time)
|
|
: 20.0;
|
|
const double finite_T = fmin(20.0, span > 0.0 ? span : 20.0);
|
|
const double target = observer.coordinate_time - finite_T;
|
|
|
|
/* 2. production-exact INSIDE-route initialization. */
|
|
MetricData metric;
|
|
if (spacetime_eval(&source, observer.coordinate_time,
|
|
observer.coordinate_position, &metric) !=
|
|
SPACETIME_POINT_OK) {
|
|
fprintf(stderr, "FATAL: camera metric %s\n", c->name);
|
|
return 2;
|
|
}
|
|
State st;
|
|
if (geodesic_initialize_past_ray_metric(&metric, &observer, c->dirs[di],
|
|
&st)) {
|
|
fprintf(stderr, "FATAL: init %s dir %d\n", c->name, di);
|
|
return 2;
|
|
}
|
|
GeodesicTraceConfig cfg =
|
|
b_make_dp(c->initial, 1e-12, c->max_step, 1e-9, 0u, 0.0);
|
|
st.next_step = cfg.coordinate_time_step;
|
|
|
|
MetricSlab *slab = NULL;
|
|
if (spacetime_load_slab(&source, st.coordinate_time, target - 1.0,
|
|
&slab)) {
|
|
fprintf(stderr, "FATAL: slab %s dir %d\n", c->name, di);
|
|
return 2;
|
|
}
|
|
|
|
double h_min = INFINITY, h_max = 0.0, h_first = NAN, h_last = NAN;
|
|
double null_max = 0.0, sum_wall = 0.0;
|
|
unsigned long sum_rhs = 0;
|
|
unsigned int sum_rej = 0, max_rej_delta = 0, boundary_steps = 0;
|
|
int reached = 0, terminated_reason = -1;
|
|
int step_index = 0;
|
|
const double start_wall = b_now();
|
|
while (st.coordinate_time > target && step_index < MAX_OBS_STEPS) {
|
|
const double before_t = st.coordinate_time;
|
|
unsigned long rhs = st.rhs_evaluations;
|
|
unsigned int rej = st.rejected_steps;
|
|
RayReason reason = RAY_REASON_INTEGRATION_ERROR;
|
|
const int rc =
|
|
dp_advance_one(slab, &cfg, &st, target, &reason, &rhs, &rej, NULL);
|
|
const unsigned long rhs_delta = rhs - st.rhs_evaluations;
|
|
const unsigned int rej_delta = rej - st.rejected_steps;
|
|
st.rhs_evaluations = rhs;
|
|
st.rejected_steps = rej;
|
|
sum_rhs += rhs_delta;
|
|
sum_rej += rej_delta;
|
|
if (rej_delta > max_rej_delta)
|
|
max_rej_delta = rej_delta;
|
|
if (rc) {
|
|
terminated_reason = (int)reason;
|
|
break;
|
|
}
|
|
const double h = before_t - st.coordinate_time;
|
|
const int boundary = (st.coordinate_time == target);
|
|
if (boundary)
|
|
++boundary_steps;
|
|
if (h < h_min)
|
|
h_min = h;
|
|
if (h > h_max)
|
|
h_max = h;
|
|
if (step_index == 0)
|
|
h_first = h;
|
|
h_last = h;
|
|
++step_index;
|
|
MetricData m;
|
|
double nr = NAN;
|
|
if (spacetime_slab_eval(slab, st.coordinate_time, st.x, &m) ==
|
|
SPACETIME_POINT_OK)
|
|
nr = b_null_residual(&m, st.Pi);
|
|
if (isfinite(nr) && fabs(nr) > null_max)
|
|
null_max = fabs(nr);
|
|
fprintf(hf, "%s,%d,%d,%.17g,%.17g,%d,%lu,%u,%.6g\n", c->name, di,
|
|
step_index, st.coordinate_time, h, boundary, rhs_delta,
|
|
rej_delta, nr);
|
|
if (st.coordinate_time <= target)
|
|
reached = 1;
|
|
}
|
|
sum_wall = b_now() - start_wall;
|
|
if (step_index >= MAX_OBS_STEPS)
|
|
reached = 0;
|
|
if (!isfinite(h_first)) {
|
|
h_first = NAN;
|
|
h_max = NAN;
|
|
}
|
|
if (!isfinite(h_min) || h_min == INFINITY)
|
|
h_min = NAN;
|
|
if (h_last < 0.0)
|
|
h_last = NAN;
|
|
fprintf(sf,
|
|
"%s,%d,%d,%.17g,%.17g,%.17g,%d,%d,%d,%.17g,%.17g,%.17g,%.17g,"
|
|
"%u,%lu,%u,%u,%.6g,%.17g,%.6g\n",
|
|
c->name, di, ref.outcome, ref.stop_coordinate_time, span, target,
|
|
step_index, reached, terminated_reason, h_min, h_max, h_first,
|
|
h_last, boundary_steps, sum_rhs, sum_rej, max_rej_delta, null_max,
|
|
st.coordinate_time, sum_wall);
|
|
printf("B %s dir=%d ref=%d refstop=%.9g span=%.6g T=%.6g target=%.6g "
|
|
"steps=%d reached=%d term=%d h=[%.6g,%.6g] first=%.6g last=%.6g "
|
|
"boundary=%u rhs=%lu rej=%u nullmax=%.3g final_t=%.9g wall=%.4fs\n",
|
|
c->name, di, ref.outcome, ref.stop_coordinate_time, span, finite_T,
|
|
target, step_index, reached, terminated_reason, h_min, h_max,
|
|
h_first, h_last, boundary_steps, sum_rhs, sum_rej, null_max,
|
|
st.coordinate_time, sum_wall);
|
|
fflush(stdout);
|
|
total_steps += step_index;
|
|
spacetime_free_slab(slab);
|
|
}
|
|
spacetime_destroy(&source);
|
|
}
|
|
fclose(hf);
|
|
fclose(sf);
|
|
printf("TOTAL_OBSERVED_STEPS %ld\n", total_steps);
|
|
return 0;
|
|
}
|