Feat: Complete adaptive geodesic tracing with DP54

Add error-controlled DP5(4) integration and trusted first-crossing localization, including non-monotonic energy thresholds and representable-time stepping.

Preserve adaptive state and independent step/time retry grants across RayPool, refinement and movie scheduling. Expose numerical controls, record actual persistent-sample costs, and add v3 lens-map provenance with legacy v2 RK4 import.

Use DP54 by default and select an 8M Schwarzschild maximum step from bounded scans and a two-run 4K comparison. Retain the conservative minimum-step guard and document critical-ray and backend capability limits. Archive self-contained benchmark inputs and raw output; keep fixed RK4 HDR references explicit.

Validation: make -B -j4 BUILD_TYPE=Debug test passed; explicit RK4 HDR references have zero differences. Bounded convergence checks, benchmark reproduction, Release build and focused reviews passed. No numerical-relativity backend is added.
This commit is contained in:
wyj committed 2026-10-05 20:27:42 -04:00
1 parent c894fdb11a
commit 0a46a7095b
48 files changed
+11491 -380

No files matched your search

+10 -1
View File
@@ -106,6 +106,7 @@ endif
# built for a different backend.
TEST_OUT_DIR := $(OBJECT_DIR)/$(HDR_BUILD_TAG)
TEST_TARGET := $(TEST_OUT_DIR)/test_geodesic
ADAPTIVE_GEODESIC_TEST_TARGET := $(TEST_OUT_DIR)/test_geodesic_adaptive
ASYMPTOTIC_TEST_TARGET := $(TEST_OUT_DIR)/test_asymptotic
ASYMPTOTIC_SCHWARZSCHILD_TEST_TARGET := $(TEST_OUT_DIR)/test_asymptotic_schwarzschild
TERMINATION_ORACLE_TEST_TARGET := $(TEST_OUT_DIR)/test_termination_oracle
@@ -207,6 +208,12 @@ $(TEST_OUT_DIR): | $(BUILD_DIR)
$(TEST_TARGET): tests/test_geodesic.c $(CORE_MINKOWSKI_SOURCES) $(CPU_FFTW_SOURCES) | $(TEST_OUT_DIR)
$(CC) $(CPPFLAGS) $(BUILD_CPPFLAGS) $(CFLAGS) $(BUILD_CFLAGS) $(OPENMP_FLAGS) -Isrc $^ $(LDLIBS) -o $@
# Explicit adaptive DP5(4) core and RayPool regression. The test textually
# includes both analytic providers (renaming spacetime_create_default), so the
# link line deliberately omits src/spacetime_{minkowski,schwarzschild}.c.
$(ADAPTIVE_GEODESIC_TEST_TARGET): tests/test_geodesic_adaptive.c $(COMMON_SOURCES) $(CPU_FFTW_SOURCES) | $(TEST_OUT_DIR)
$(CC) $(CPPFLAGS) $(BUILD_CPPFLAGS) $(CFLAGS) $(BUILD_CFLAGS) $(OPENMP_FLAGS) -DGEODESIC_EVENT_TESTING -Isrc $^ $(LDLIBS) -o $@
$(ASYMPTOTIC_TEST_TARGET): tests/test_asymptotic.c $(CORE_MINKOWSKI_SOURCES) $(CPU_FFTW_SOURCES) | $(TEST_OUT_DIR)
$(CC) $(CPPFLAGS) $(BUILD_CPPFLAGS) $(CFLAGS) $(BUILD_CFLAGS) $(OPENMP_FLAGS) -Isrc $^ $(LDLIBS) -o $@
@@ -273,10 +280,11 @@ FAST_PSF_FFTW_TEST_DEP :=
FAST_PSF_FFTW_TEST_RUN :=
endif
test: $(CAMERA_TEST_TARGETS) $(TEST_TARGET) $(ASYMPTOTIC_TEST_TARGET) $(ASYMPTOTIC_SCHWARZSCHILD_TEST_TARGET) $(TERMINATION_ORACLE_TEST_TARGET) $(FRAME_TEST_TARGET) $(SCHWARZSCHILD_TEST_TARGET) $(ALCUBIERRE_TEST_TARGET) $(OBSERVER_TRACK_TEST_TARGET) $(CATALOG_PREFETCH_TEST_TARGET) $(FAST_PSF_FFTW_TEST_DEP) $(TONE_MAP_TEST_TARGET) $(MOVIE_OUTPUT_TEST_TARGET) $(SENSOR_BLOOM_TEST_TARGET)
test: $(CAMERA_TEST_TARGETS) $(TEST_TARGET) $(ADAPTIVE_GEODESIC_TEST_TARGET) $(ASYMPTOTIC_TEST_TARGET) $(ASYMPTOTIC_SCHWARZSCHILD_TEST_TARGET) $(TERMINATION_ORACLE_TEST_TARGET) $(FRAME_TEST_TARGET) $(SCHWARZSCHILD_TEST_TARGET) $(ALCUBIERRE_TEST_TARGET) $(OBSERVER_TRACK_TEST_TARGET) $(CATALOG_PREFETCH_TEST_TARGET) $(FAST_PSF_FFTW_TEST_DEP) $(TONE_MAP_TEST_TARGET) $(MOVIE_OUTPUT_TEST_TARGET) $(SENSOR_BLOOM_TEST_TARGET)
$(TEST_OUT_DIR)/test_observer_minkowski
$(TEST_OUT_DIR)/test_observer_schwarzschild
$(TEST_TARGET)
$(ADAPTIVE_GEODESIC_TEST_TARGET)
$(ASYMPTOTIC_TEST_TARGET)
$(ASYMPTOTIC_SCHWARZSCHILD_TEST_TARGET)
$(TERMINATION_ORACLE_TEST_TARGET)
@@ -290,6 +298,7 @@ test: $(CAMERA_TEST_TARGETS) $(TEST_TARGET) $(ASYMPTOTIC_TEST_TARGET) $(ASYMPTOT
$(MOVIE_OUTPUT_TEST_TARGET)
$(SENSOR_BLOOM_TEST_TARGET)
python3 tests/test_camera_cli.py $(BUILD_DIR) $(TEST_OUT_DIR)
python3 tests/test_adaptive_cli.py $(BUILD_DIR) $(TEST_OUT_DIR)
tone-map-test: $(TONE_MAP_TEST_TARGET)
$(TONE_MAP_TEST_TARGET)
@@ -0,0 +1,224 @@
# adaptive_step_bounds_2026-10-05
Long-term benchmark for calibrating the DP54 adaptive-integrator step bounds
(`min_step` / `max_step`) of the past-directed null-geodesic tracer, and the
public record of the one authorised 4K Schwarzschild `max_step` 2-vs-8 pair.
The benchmark runs the public endpoint `geodesic_trace_past()`, the production
DP core, and bounded adaptive-mesh renders against the analytic
Minkowski / Schwarzschild / Alcubierre backends. It does not modify production
sources.
## Build (required before renderer scripts)
The Python mesh/4K drivers start from an existing renderer binary. The shell
endpoint/core drivers compile their own small harnesses against production code.
```
make -j4 BUILD_TYPE=Release SPACETIME=schwarzschild backend
# binary: build/Release/schwarzschild_sky
```
## Files
| file | role |
|---|---|
| `a_public_endpoints.c` | Experiment A: step-bound scans through the public endpoint, tol=1e-12 reference, RK4 h-halving on near-critical rays. |
| `b_actual_h.c` | Experiment B: textually includes `src/geodesic.c`, drives the production `dp_advance_one` on finite trusted intervals to measure observed accepted `h`. |
| `critical_ref_check.c` | Critical-classification probe (r30/r100 inward, r2.1 outward; `theta_crit` and `theta_crit±1e-7`; tol/max_step ladder). |
| `mesh_maps.py` | Pure GRLENS v3 `load`/`compare`; no renderer, no untracked import. |
| `run_mesh.py` | 8 bounded 320x180 candidates (R100 and R2.1, `max_step` .5/2/8/32). |
| `run_mesh_reference.py` | 2 tight-tolerance references and comparisons against the 8 maps. |
| `summarize_4k.py` | Postprocess existing maps only; never invokes a renderer. |
| `run_4k_pair.py` | One-task, at-most-two-attempt 4K pair; refuses to run without `--authorize-two-4k`. |
| `record_environment.py` | Provenance (git/hashes/compiler/cpu/build/run commands); no render. |
| `failure_floor.c` | Failure-path floor diagnostic; includes `../../tests/test_geodesic_adaptive.c`. |
| `run_failure_floor.sh` | Builds/runs `failure_floor.c` with `-DGEODESIC_EVENT_TESTING`. |
| `run_limited.sh` | Builds/runs Experiment A + B; all output under `OUT_DIR`. |
| `run_critical_ref.sh` | Builds/runs `critical_ref_check.c`; all output under `OUT_DIR`. |
| `summarize.py` | `OUT_DIR` CLI arg (default `local/adaptive_step_bounds_2026-10-05`); writes `OUT_DIR/logs/summary.txt`. |
| `results/` | Tracked long-term evidence (see below). |
## Reproduction
```
AB_OUT="$PWD/local/adaptive_step_bounds_2026-10-05"
OUT="$PWD/local/adaptive_bounds_mesh"
bash benchmarks/adaptive_step_bounds_2026-10-05/run_limited.sh "$AB_OUT"
bash benchmarks/adaptive_step_bounds_2026-10-05/run_critical_ref.sh "$AB_OUT"
python3 benchmarks/adaptive_step_bounds_2026-10-05/summarize.py "$AB_OUT"
python3 benchmarks/adaptive_step_bounds_2026-10-05/run_mesh.py "$OUT"
python3 benchmarks/adaptive_step_bounds_2026-10-05/run_mesh_reference.py "$OUT"
bash benchmarks/adaptive_step_bounds_2026-10-05/run_failure_floor.sh "$OUT"
# 4K only after explicit fresh user authorization for that task:
python3 benchmarks/adaptive_step_bounds_2026-10-05/run_4k_pair.py \
--authorize-two-4k --out local/adaptive_bounds_4k
```
Defaults: A/B `OUT_DIR` is `local/adaptive_step_bounds_2026-10-05`; mesh
`OUT_DIR` is `local/adaptive_bounds_mesh`; 4K `--out` default is
`local/adaptive_bounds_4k`. All logs/CSV go to `OUT_DIR`; the tracked
benchmark directory holds only sources and `results/`. Scratch binaries go to
`/tmp/opencode/step_bounds/`.
The `run_4k_pair.py` driver refuses to run without `--authorize-two-4k`, keeps
an attempt ledger, and has no automatic retry. Authorisation does not extend to
later tasks or routine tests.
All catalog inputs are generated inline by the scripts (column header
`longitude_deg,latitude_deg,temperature_K,amplitude`, one 1e-30 star); no
external survey CSV is read.
## Results
### 1. Public-endpoint step-bound scan (Experiment A/B)
Environment and full table inputs: `results/source_environment.txt`,
`results/summary_tables.txt`; full stdout: `results/expA.log`,
`results/expB.log`. Per-ray raw CSV (not tracked) is reproduced by
`run_limited.sh`.
| backend | worst near-critical sky error vs tol=1e-12 ref | notes |
|---|---|---|
| Schwarzschild r30 | 2.82e-4 rad (`max_step`≥1); 8.67e-5 rad at 0.25 | r100 8.0e-5 / 2.6e-5; r2.1 2.87e-4 |
| Minkowski | Δn = 0, Δg/g = 0 (analytic) | crossing x/t ≤ 7.7e-11 at `max_step`≤256 |
| Alcubierre | ≤1.8e-10 rad, no class change | 8x vs 1x `initial` RHS cost 4.3x–5.7x |
- Dark terminal `L−L0` margin agrees with the reference to ≤2.1e-13 in every
config; near-critical dark stop times differ up to ~41 time units between
tol=1e-9 and tol=1e-12 (orbit-count sensitivity, reported not hidden).
- Schwarzschild floor plateau: `min_step` `1e-14 … 1e-2` give bit-identical
results and RHS; only `0.1` breaks one r1.5 ray
(`INCOMPLETE/INTEGRATION_ERROR`). No evidence to raise the default floor,
and no evidence that `1e-12` is uniquely optimal (it is never active).
- Observed accepted `h` (Experiment B, finite window `T=min(20, ref span)`):
**Schwarzschild only** — r1.5 radial 0.0921, all other sampled Schwarzschild
rays ≥0.1; Alcubierre minimum = its initial step (0.05 / 0.005 / 0.0005);
Minkowski 1.0. This is a finite-window diagnostic, **not** a full-trajectory
minimum.
- RK4 h-halving worst (r2.1, `theta_crit−1e-7`): 1.04e-5 rad.
### 2. Bounded 320x180 adaptive mesh
Numeric inputs: `results/mesh_summary.json`,
`results/mesh_reference_summary.json`. Scene: R100/FOV45/jacobian0.2 and
R2.1/outward/FOV90; DP54, hmin 1e-12, initial 0.1, tol 1e-9 (tight reference
1e-12), 8 threads, one-point dim catalog.
| scene | `max_step` | persistent vertices | RHS | wall (s) |
|---|---:|---:|---:|---:|
| R100 | 0.5 | 3158 | 16813349 | 2.071 |
| R100 | 2 | 3158 | 6030185 | 0.979 |
| R100 | 8 | 3158 | 4230835 | 0.823 |
| R100 | 32 | 3158 | 4081609 | 0.830 |
| R2.1 | 0.5 | 5496 | 20136508 | 1.974 |
| R2.1 | 2 | 5496 | 9548434 | 1.178 |
| R2.1 | 8 | 5496 | 7389760 | 0.970 |
| R2.1 | 32 | 5496 | 7197806 | 0.972 |
- 2→8 RHS reduction 29.84% (R100) / 22.61% (R2.1); 8→32 only 3.53% / 2.60%.
- Identical terminal classifications and mesh/sample counts for all four caps.
- Error vs tight reference: R100 max sky 9.216e-8 (hmax2) / 8.744e-8 (hmax8),
mutual 2-vs-8 difference 7.063e-9 rad; R2.1 max sky 1.4772e-5 rad at both,
mutual difference 8.370e-11 rad. The larger R2.1 reference error is
tolerance/conditioning sensitive and is not cured by a smaller `max_step`.
- Max log-frequency differences ≤8.51e-10 (R100) / ≤2.10e-9 (R2.1).
- Short single-run wall times include setup/cache/output; not a timing study.
### 3. One authorised 4K pair (R100, 3840x2160, FOV45)
Raw terminal output (byte-for-byte): `results/4k_hmax2.log`,
`results/4k_hmax8.log`; provenance: `results/4k_metadata.json`,
`results/4k_attempts.json`; postprocessed: `results/4k_final_summary.json`,
`results/4k_raw_summary.log`.
| metric | `max_step`=2 | `max_step`=8 |
|---|---:|---:|
| persistent vertices / triangles | 66045 / 131338 | 66045 / 131338 |
| outcomes | 62419 ESC, 3626 DARK | 62419 ESC, 3626 DARK |
| unresolved / error | 0 | 0 |
| persistent accepted | 15,768,677 | 10,183,020 |
| persistent rejected | 97,176 | 194,781 |
| persistent RHS | 130,409,139 | 91,991,144 |
| trace+refine time (s) | 10.774 | 7.805 |
| process wall (s) | 11.3893 | 8.38925 |
- Triangle payload is bit-identical; shared film identities 66045, terminal
mismatches 0; max mutual endpoint sky difference 7.9544e-7 rad, max logg
difference 3.4711e-11.
- Persistent-vertex RHS reduction −29.46%; accepted −35.42%; wall −26.34%.
- **Cost scope:** the v3 map stores final persistent vertices only. The
requested-sample count including discarded refinement probes is 116308
(> 66045), so discarded probes contribute no stored cost and these sums must
not be read as full-render executed RHS totals.
- **Timing:** one sequential pair only; no statistical timing confidence.
- PSF splat cached/direct = 0 and `--psf-min-y` discarded 1965 (hmax2) / 1954
(hmax8) events; this does not measure image error. The PNGs are dark
diagnostic backgrounds; no 2MASS catalog was used.
### 4. Critical-classification reference check
Full output: `results/critical_ref.log`, `results/critical_ref.csv` (36
endpoint traces). The exact `theta_crit` direction is a classification
separatrix; it is reported, never asserted.
- r2.1 `theta_crit`: ref tol1e-12/h0.25 vs tol1e-13/h0.125 sky difference
**2.33638 rad** — the exactly-critical escape direction is not
independently converged. `EXACT_CRITICAL_REFERENCE_NOT_CONVERGED`.
- r30 `theta_crit` (DARK) stop-time difference 13.056; r100 `theta_crit` (DARK)
12.700. At tol1e-11, h2-vs-h8 at r2.1 `theta_crit` is 5.09e-5 rad.
- Neighbours `±1e-7`: both reference tiers agree (same outcome/reason/end,
clean, positive steps) at every neighbour. Escaped-neighbour sky differences
between the layers are ~1e-7 rad (9.47e-8 r30, 2.74e-8 r100, 1.42e-7 r2.1),
consistent with the loose 1e-4 magnitude trend; DARK neighbours report
stop-time differences 1.5e-7 … 7.5e-7.
- `CRITICAL_REFERENCE_CHECK status=0` (no neighbour reference disagreement or
error endpoint).
## Decision summary
- **Schwarzschild `max_step = 8`**: supported by the far-field cost plateau
(2→8 saves ~23–30% persistent RHS; 8→32 only ~3%) together with the tight
reference and 4K 2-vs-8 comparisons. This is not claimed as globally optimal
and gives no guarantee for arbitrary near-critical/shadow directions.
- **`min_step = 1e-12`**: retained as a non-binding numerical guard; not a
measured optimum.
- **Minkowski `max_step = 16`** and **Alcubierre `max_step = 8 × initial`**:
retained as conservative caps. Their bounded accuracy checks support keeping
these values, not claiming cost optimality; larger Alcubierre caps still
reduce RHS, but have larger frequency errors in some cases.
- The R2.1 exactly-critical difference is tolerance-sensitive;
`critical_ref_check.c` confirms the exact direction is not
independently converged and the report does not treat it as certified.
## Caveats
- Analytic backends only; no time-dependent numerical-relativity metric was
exercised. Bounds and the `unit = 1` calibration are not transferable to NR.
- Experiment B samples `T = min(20, reference span)`; no full-trajectory
minimum-step claim is made.
- The 4K numbers are one sequential pair; no timing-stability confidence and
no image-error bound (inverse-lens-map Jacobian amplification is separate).
- Sky-angle differences divided by pixel angle are not inverse-lens-map image
error bounds.
- `failure_floor.c` reuses the regression invalid-metric temporal-domain
fixture; it is not a physical shadow or floor calibration.
## Verification of the selected defaults and reproduction tools
`make -B -j4 BUILD_TYPE=Debug test` passed after selecting the Schwarzschild
cap of 8 and adding CLI provenance assertions for the default bounds. Full
terminal output is retained locally at
`local/step_bounds_mesh/final_debug_tests.log` (`STEP_BOUNDS_FULL_DEBUG_EXIT=0`).
The explicit-RK4 HDR reference tests remain bit-identical. The Release
Schwarzschild binary was rebuilt with the selected default.
The self-contained limited driver was also executed with the relative output
path `local/step_bounds_reproduction`: 1500 endpoint traces and 2427 observed
finite-window steps completed, including summary generation. Small angular
differences use `atan2(|cross|,dot)` rather than rounded `acos(dot)`. Running the
4K driver without fresh explicit authorization refuses with exit code 2;
this check launches no renderer. No further 4K attempt was made.
@@ -0,0 +1,867 @@
/*
* Experiment A: DP54 step-bound scan through the *public* production
* endpoint geodesic_trace_past().
*
* Sub-commands:
* schwarzschild : r30/r100 static inward, r2.1 static outward, r1.5 free-fall
* minkowski : moving observer, escape sphere 64, analytic flat check
* alcubierre : comoving bubble-center camera, vs .3/.9, sigma 1/10/100
*
* Each sub-command computes one DP54 tol=1e-12 reference per ray, then scans
* - upper scan : max_step over a list, min_step fixed
* - min scan : min_step over a list, max_step fixed
* and compares every result against the reference (class, sky angle, g,
* dark threshold margin / stop time, cost). Raw per-ray CSV and stdout
* summaries are produced. No production source is modified.
*
* Link line (do NOT link geodesic.c twice; the backends are textually included
* below, so only the common sources are linked):
* cc -std=c11 -O2 -Isrc a_public_endpoints.c geodesic.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>
/* Textually include all three analytic backends with renamed default
* constructors so one binary can exercise every provider (the files each
* define spacetime_create_default, which would collide at link time). */
#define spacetime_create_default spacetime_create_default_minkowski_local
#include "../../src/spacetime_minkowski.c"
#undef spacetime_create_default
#define spacetime_create_default spacetime_create_default_schwarzschild_local
#include "../../src/spacetime_schwarzschild.c"
#undef spacetime_create_default
#define spacetime_create_default spacetime_create_default_alcubierre_local
#include "../../src/spacetime_alcubierre.c"
#undef spacetime_create_default
#include "geodesic.h"
#include "observer.h"
#include "spacetime.h"
#define PI 3.14159265358979323846
#define MAX_CASES 16
#define MAX_DIRS 16
#define RAY_CAP 2500
static long g_rays = 0;
/* ------------------------------------------------------------------ */
/* Small helpers */
/* ------------------------------------------------------------------ */
static double dot3(const double a[3], const double b[3]) {
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
}
static double ang_delta(const double a[3], const double b[3]) {
const double na = sqrt(dot3(a, a)), nb = sqrt(dot3(b, b));
if (!(na > 0.0) || !(nb > 0.0))
return NAN;
const double ca = dot3(a, b) / (na * nb);
const double cross[3] = {a[1] * b[2] - a[2] * b[1],
a[2] * b[0] - a[0] * b[2],
a[0] * b[1] - a[1] * b[0]};
const double sn = sqrt(dot3(cross, cross)) / (na * nb);
return atan2(sn, ca);
}
static double now_s(void) {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (double)ts.tv_sec + 1e-9 * (double)ts.tv_nsec;
}
static int is_dark(int outcome) { return outcome == RAY_OUTCOME_DARK; }
static int is_escaped(int outcome) { return outcome == RAY_OUTCOME_ESCAPED; }
/* Critical local angle (static observer, M=1) for the Schwarzschild monopole.
* theta_c = asin(3 sqrt(3) sqrt(1-2/r) / r). */
static double critical_angle(double r) {
const double b = 3.0 * sqrt(3.0) * sqrt(1.0 - 2.0 / r) / r;
if (!(b < 1.0))
return PI / 2.0;
return asin(b);
}
/* ------------------------------------------------------------------ */
/* Case / result / comparison structures */
/* ------------------------------------------------------------------ */
typedef struct {
char name[32];
int prov; /* 0 schwarzschild, 1 minkowski, 2 alcubierre */
double mass, escape_radius;
double alc_vs, alc_radius, alc_sigma;
double look_ra_deg, look_dec_deg;
double position[3], velocity[3];
double initial_step, lookback;
unsigned int max_steps;
unsigned int ref_max_steps; /* budget for the tol=1e-12 reference only */
double max_step_for_min_scan; /* fixed upper during the min scan */
int n_dirs;
double dirs[MAX_DIRS][3];
double theta[MAX_DIRS]; /* NAN for non-angle direction sets */
} Case;
typedef struct {
int outcome, reason, end_id;
double stop_t, g, thr;
double n[3];
unsigned int steps, rejected;
unsigned long rhs;
double wall;
} Res;
typedef struct {
int has_ref;
int class_match;
double dn_ang, dlogg, dgrel, dstopT, dmargin;
} Cmp;
typedef struct {
long n, class_mismatch;
double max_dn_ang, max_dlogg, max_dgrel, max_dstopT, max_dmargin;
unsigned long sum_rhs, max_rhs;
unsigned int max_rejected;
double sum_wall;
long escaped, dark, unresolved, incomplete;
} Agg;
static void agg_init(Agg *a) {
memset(a, 0, sizeof *a);
a->max_dn_ang = a->max_dlogg = a->max_dgrel = a->max_dstopT =
a->max_dmargin = -1.0;
}
static void bump(double *m, double v) {
if (!isfinite(v))
return;
if (v > *m)
*m = v;
}
static void agg_add(Agg *a, const Res *r, const Cmp *c) {
++a->n;
if (c->has_ref && !c->class_match)
++a->class_mismatch;
if (c->has_ref) {
bump(&a->max_dn_ang, c->dn_ang);
bump(&a->max_dlogg, c->dlogg);
bump(&a->max_dgrel, c->dgrel);
bump(&a->max_dstopT, c->dstopT);
bump(&a->max_dmargin, c->dmargin);
}
a->sum_rhs += r->rhs;
if (r->rhs > a->max_rhs)
a->max_rhs = r->rhs;
if (r->rejected > a->max_rejected)
a->max_rejected = r->rejected;
a->sum_wall += r->wall;
if (is_dark(r->outcome))
++a->dark;
else if (is_escaped(r->outcome))
++a->escaped;
else if (r->outcome == RAY_OUTCOME_UNRESOLVED)
++a->unresolved;
else
++a->incomplete;
}
/* ------------------------------------------------------------------ */
/* Config builders */
/* ------------------------------------------------------------------ */
static GeodesicTraceConfig 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 = tol;
c.atol_Pi = tol;
c.atol_L = tol;
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 GeodesicTraceConfig make_rk4(double step, unsigned int max_steps) {
GeodesicTraceConfig c;
memset(&c, 0, sizeof c);
c.coordinate_time_step = step;
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_RK4;
return c;
}
/* ------------------------------------------------------------------ */
/* Trace + compare */
/* ------------------------------------------------------------------ */
static Res run_ray(const SpacetimeSource *source, const ObserverState *observer,
const double dir[3], const GeodesicTraceConfig *config) {
Res r;
memset(&r, 0, sizeof r);
++g_rays;
if (g_rays > RAY_CAP) {
fprintf(stderr, "FATAL: ray cap %d exceeded\n", RAY_CAP);
exit(3);
}
const double t0 = now_s();
const RayEndpoint e = geodesic_trace_past(source, observer, dir, config);
r.wall = now_s() - t0;
r.outcome = e.outcome;
r.reason = e.reason;
r.end_id = e.end_id;
r.stop_t = e.stop_coordinate_time;
r.g = e.frequency_ratio;
r.thr = e.threshold_value;
for (int i = 0; i < 3; ++i)
r.n[i] = e.n_infinity[i];
r.steps = e.accepted_steps;
r.rejected = e.rejected_steps;
r.rhs = e.rhs_evaluations;
return r;
}
static void compare(const Res *a, const Res *ref, Cmp *c) {
memset(c, 0, sizeof *c);
c->has_ref = 1;
c->class_match = (a->outcome == ref->outcome && a->reason == ref->reason);
if (is_escaped(a->outcome) && is_escaped(ref->outcome)) {
c->dn_ang = ang_delta(a->n, ref->n);
if (a->g > 0.0 && ref->g > 0.0 && isfinite(a->g) && isfinite(ref->g)) {
c->dlogg = fabs(log(a->g) - log(ref->g));
c->dgrel = fabs(a->g / ref->g - 1.0);
} else {
c->dlogg = c->dgrel = NAN;
}
} else {
c->dn_ang = c->dlogg = c->dgrel = NAN;
}
c->dstopT = (isfinite(a->stop_t) && isfinite(ref->stop_t))
? fabs(a->stop_t - ref->stop_t)
: NAN;
c->dmargin = (is_dark(a->outcome) && is_dark(ref->outcome) &&
isfinite(a->thr) && isfinite(ref->thr))
? fabs(a->thr - ref->thr)
: NAN;
}
static void write_row(FILE *f, const char *phase, const Case *c, int di,
double upper, double min_step, double tol, const Res *r,
const Cmp *cmp) {
fprintf(f,
"%s,%s,%d,%.17g,%.6g,%.6g,%.6g,%d,%d,%u,%.17g,%u,%u,%lu,%.6g,"
"%.17g,%.17g,%.17g,%.17g,%.17g",
phase, c->name, di, c->theta[di], upper, min_step, tol, r->outcome,
r->reason, r->end_id, r->stop_t, r->steps, r->rejected, r->rhs,
r->wall, r->n[0], r->n[1], r->n[2], r->g, r->thr);
if (cmp->has_ref)
fprintf(f, ",%d,%.17g,%.17g,%.17g,%.17g,%.17g", cmp->class_match,
cmp->dn_ang, cmp->dlogg, cmp->dgrel, cmp->dstopT, cmp->dmargin);
else
fprintf(f, ",,nan,nan,nan,nan,nan");
fprintf(f, "\n");
}
static const char *ROW_HEADER =
"phase,case,dir,theta,upper,min_step,tol,outcome,reason,end_id,stop_t,"
"steps,rejected,rhs,wall_s,nx,ny,nz,g,thr,class_match,dn_ang,dlogg,dgrel,"
"dstopT,dmargin\n";
static void write_agg(FILE *f, const char *phase, const Case *c, double upper,
double min_step, double tol, const Agg *a) {
fprintf(f,
"%s,%s,%.6g,%.6g,%.6g,%ld,%ld,%.6g,%.6g,%.6g,%.6g,%.6g,%lu,%lu,%u,"
"%.4f,%ld,%ld,%ld,%ld\n",
phase, c->name, upper, min_step, tol, a->n, a->class_mismatch,
a->max_dn_ang, a->max_dlogg, a->max_dgrel, a->max_dstopT,
a->max_dmargin, a->sum_rhs, a->max_rhs, a->max_rejected, a->sum_wall,
a->escaped, a->dark, a->unresolved, a->incomplete);
}
static const char *AGG_HEADER =
"phase,case,upper,min_step,tol,n,class_mismatch,max_dn_ang,max_dlogg,"
"max_dgrel,max_dstopT,max_dmargin,sum_rhs,max_rhs,max_rejected,sum_wall,"
"escaped,dark,unresolved,incomplete\n";
/* ------------------------------------------------------------------ */
/* Case builders */
/* ------------------------------------------------------------------ */
static void dir_from_theta(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(dot3(n, n));
for (int i = 0; i < 3; ++i)
n[i] /= nn;
}
static void fill_static_dirs(Case *c, double r) {
const double tc = critical_angle(r);
const double th[MAX_DIRS] = {0.0, PI, PI / 2.0, tc,
tc - 1e-3, tc + 1e-3, tc - 1e-5, tc + 1e-5,
tc - 1e-7, tc + 1e-7, tc + 0.05, tc - 0.05};
c->n_dirs = 12;
for (int i = 0; i < c->n_dirs; ++i) {
c->theta[i] = th[i];
dir_from_theta(th[i], c->dirs[i]);
}
}
static void case_static(Case *c, const char *name, double r, double look_ra) {
memset(c, 0, sizeof *c);
snprintf(c->name, sizeof c->name, "%s", name);
c->prov = 0;
c->mass = 1.0;
c->escape_radius = 256.0;
c->look_ra_deg = look_ra;
c->look_dec_deg = 0.0;
c->position[0] = r;
c->position[1] = c->position[2] = 0.0;
c->velocity[0] = c->velocity[1] = c->velocity[2] = 0.0;
c->initial_step = 0.1;
c->lookback = 6553.6;
c->max_steps = 65536;
c->ref_max_steps = 65536;
c->max_step_for_min_scan = 2.0;
fill_static_dirs(c, r);
}
static void case_freefall(Case *c, const char *name, double r) {
memset(c, 0, sizeof *c);
snprintf(c->name, sizeof c->name, "%s", name);
c->prov = 0;
c->mass = 1.0;
c->escape_radius = 256.0;
c->look_ra_deg = 180.0;
c->look_dec_deg = 0.0;
c->position[0] = r;
c->position[1] = c->position[2] = 0.0;
/* Free-fall from rest at infinity, coordinate velocity dx/dt (the formula
* used by local/p2d_validation/observer_endpoints.c). */
const double y = sqrt(2.0 / r);
c->velocity[0] = -y * (1.0 + y) / (1.0 + y + y * y);
c->velocity[1] = c->velocity[2] = 0.0;
c->initial_step = 0.1;
c->lookback = 6553.6;
c->max_steps = 65536;
c->ref_max_steps = 65536;
c->max_step_for_min_scan = 2.0;
{
const double th[4] = {0.0, PI / 2.0, PI, 3.0 * PI / 4.0};
c->n_dirs = 4;
for (int i = 0; i < 4; ++i) {
c->theta[i] = th[i];
dir_from_theta(th[i], c->dirs[i]);
}
}
}
static void case_minkowski(Case *c) {
memset(c, 0, sizeof *c);
snprintf(c->name, sizeof c->name, "mink_moving");
c->prov = 1;
c->escape_radius = 64.0;
c->look_ra_deg = 0.0;
c->look_dec_deg = 0.0;
c->position[0] = 10.0;
c->position[1] = 20.0;
c->position[2] = -15.0;
c->velocity[0] = 0.3;
c->velocity[1] = 0.2;
c->velocity[2] = 0.1;
c->initial_step = 1.0;
c->lookback = 2048.0;
c->max_steps = 2048;
c->ref_max_steps = 4096;
c->max_step_for_min_scan = 16.0;
{
const double d[6][3] = {{1.0, 0.0, 0.0}, {-1.0, 0.0, 0.0},
{0.0, 1.0, 0.0}, {0.0, 0.0, 1.0},
{0.6, 0.8, 0.0}, {-0.6, 0.8, 0.0}};
c->n_dirs = 6;
for (int i = 0; i < 6; ++i) {
c->theta[i] = NAN;
for (int k = 0; k < 3; ++k)
c->dirs[i][k] = d[i][k];
}
}
}
static void case_alcubierre(Case *c, const char *name, double vs, double sigma) {
memset(c, 0, sizeof *c);
snprintf(c->name, sizeof c->name, "%s", name);
c->prov = 2;
c->alc_vs = vs;
c->alc_radius = 1.0;
c->alc_sigma = sigma;
c->look_ra_deg = 0.0;
c->look_dec_deg = 0.0;
c->position[0] = c->position[1] = c->position[2] = 0.0;
c->velocity[0] = vs; /* comoving with the bubble center */
c->velocity[1] = c->velocity[2] = 0.0;
c->initial_step = fmin(0.1, 0.05 / sigma);
c->max_steps = 100000;
c->ref_max_steps = 200000;
{
const double esc = spacetime_alcubierre_escape_radius(1.0, sigma);
c->lookback = 1.25 * 4.0 * esc / (1.0 - fabs(vs));
}
c->max_step_for_min_scan = c->initial_step * 8.0;
{
const double d[6][3] = {{1.0, 0.0, 0.0}, {-1.0, 0.0, 0.0},
{0.0, 1.0, 0.0}, {0.0, 0.0, 1.0},
{0.6, 0.8, 0.0}, {-0.6, 0.8, 0.0}};
c->n_dirs = 6;
for (int i = 0; i < 6; ++i) {
c->theta[i] = NAN;
for (int k = 0; k < 3; ++k)
c->dirs[i][k] = d[i][k];
}
}
}
/* ------------------------------------------------------------------ */
/* Source/observer construction */
/* ------------------------------------------------------------------ */
static int build_source(const Case *c, SpacetimeSource *s) {
if (c->prov == 0)
return spacetime_create_schwarzschild_ks(s, c->mass, c->escape_radius);
if (c->prov == 1)
return spacetime_create_minkowski(s, c->escape_radius);
return spacetime_create_alcubierre(s, c->alc_vs, c->alc_radius,
c->alc_sigma);
}
static int build_observer(const Case *c, const SpacetimeSource *s,
ObserverState *o) {
MetricData m;
if (spacetime_eval(s, 0.0, c->position, &m) != SPACETIME_POINT_OK)
return -1;
ObserverCamera cam;
memset(&cam, 0, sizeof cam);
cam.coordinate_time = 0.0;
for (int i = 0; i < 3; ++i) {
cam.position[i] = c->position[i];
cam.velocity[i] = c->velocity[i];
}
cam.look_ra_deg = c->look_ra_deg;
cam.look_dec_deg = c->look_dec_deg;
cam.roll_deg = 0.0;
return observer_from_coordinate_camera(&m, &cam, o, NULL) ==
OBSERVER_BUILD_OK
? 0
: -1;
}
/* ------------------------------------------------------------------ */
/* Generic scan driver */
/* ------------------------------------------------------------------ */
static void run_grid(const Case *cases, int ncases, const double *uppers,
int n_up, const double *floors, int n_fl, double tol,
const char *phase, const char *rows_path,
const char *agg_path, Res refs[][MAX_DIRS]) {
FILE *rf = fopen(rows_path, "w");
FILE *af = fopen(agg_path, "w");
if (!rf || !af) {
fprintf(stderr, "FATAL: cannot open %s / %s\n", rows_path, agg_path);
exit(2);
}
fprintf(rf, "%s", ROW_HEADER);
fprintf(af, "%s", AGG_HEADER);
for (int ci = 0; ci < ncases; ++ci) {
const Case *c = &cases[ci];
SpacetimeSource source;
ObserverState observer;
if (build_source(c, &source) || build_observer(c, &source, &observer)) {
fprintf(stderr, "FATAL: cannot build case %s\n", c->name);
exit(2);
}
for (int ui = 0; ui < n_up; ++ui) {
for (int fi = 0; fi < n_fl; ++fi) {
Agg agg;
agg_init(&agg);
for (int di = 0; di < c->n_dirs; ++di) {
const GeodesicTraceConfig cfg =
make_dp(c->initial_step, floors[fi], uppers[ui], tol, c->max_steps,
c->lookback);
const Res r = run_ray(&source, &observer, c->dirs[di], &cfg);
Cmp cmp;
compare(&r, &refs[ci][di], &cmp);
write_row(rf, phase, c, di, uppers[ui], floors[fi], tol, &r, &cmp);
agg_add(&agg, &r, &cmp);
}
write_agg(af, phase, c, uppers[ui], floors[fi], tol, &agg);
printf("SUMMARY %s %s upper=%.6g min=%.6g n=%ld mismatch=%ld "
"max_dn_ang=%.6g max_dgrel=%.6g max_dstopT=%.6g sum_rhs=%lu "
"esc=%ld dark=%ld unres=%ld inc=%ld wall=%.3fs\n",
phase, c->name, uppers[ui], floors[fi], agg.n,
agg.class_mismatch, agg.max_dn_ang, agg.max_dgrel,
agg.max_dstopT, agg.sum_rhs, agg.escaped, agg.dark,
agg.unresolved, agg.incomplete, agg.sum_wall);
fflush(stdout);
}
}
spacetime_destroy(&source);
}
fclose(rf);
fclose(af);
}
/* Compute DP tol=1e-12 reference for every direction of every case.
* upper_ref <= 0 means "use the case's own initial step" as the max_step. */
static void compute_refs(const Case *cases, int ncases, double upper_ref,
const char *path, Res refs[][MAX_DIRS]) {
FILE *f = fopen(path, "w");
if (!f) {
fprintf(stderr, "FATAL: cannot open %s\n", path);
exit(2);
}
fprintf(f, "%s", ROW_HEADER);
for (int ci = 0; ci < ncases; ++ci) {
const Case *c = &cases[ci];
SpacetimeSource source;
ObserverState observer;
if (build_source(c, &source) || build_observer(c, &source, &observer)) {
fprintf(stderr, "FATAL: cannot build case %s\n", c->name);
exit(2);
}
const double upper = (upper_ref > 0.0) ? upper_ref : c->initial_step;
for (int di = 0; di < c->n_dirs; ++di) {
const GeodesicTraceConfig cfg = make_dp(c->initial_step, 1e-12, upper,
1e-12, c->ref_max_steps,
c->lookback);
refs[ci][di] = run_ray(&source, &observer, c->dirs[di], &cfg);
Cmp none;
memset(&none, 0, sizeof none);
write_row(f, "ref", c, di, upper, 1e-12, 1e-12, &refs[ci][di], &none);
printf("REF %s dir=%d theta=%.6g outcome=%d reason=%d stop_t=%.9g "
"steps=%u rejected=%u rhs=%lu g=%.12g thr=%.12g\n",
c->name, di, c->theta[di], refs[ci][di].outcome,
refs[ci][di].reason, refs[ci][di].stop_t, refs[ci][di].steps,
refs[ci][di].rejected, refs[ci][di].rhs, refs[ci][di].g,
refs[ci][di].thr);
fflush(stdout);
}
spacetime_destroy(&source);
}
fclose(f);
}
/* ------------------------------------------------------------------ */
/* Sub-command: Schwarzschild */
/* ------------------------------------------------------------------ */
static void cmd_schwarzschild(void) {
static Case cases[MAX_CASES];
static Res refs[MAX_CASES][MAX_DIRS];
memset(refs, 0, sizeof refs);
int nc = 0;
case_static(&cases[nc++], "r30_inward", 30.0, 180.0);
case_static(&cases[nc++], "r100_inward", 100.0, 180.0);
case_static(&cases[nc++], "r2p1_outward", 2.1, 0.0);
case_freefall(&cases[nc++], "r1p5_freefall", 1.5);
compute_refs(cases, nc, 0.25, "raw/a_sch_reference.csv", refs);
static const double uppers[9] = {0.25, 0.5, 1.0, 2.0, 4.0,
8.0, 16.0, 32.0, 64.0};
static const double floors[10] = {1e-1, 1e-2, 1e-3, 1e-4, 1e-5,
1e-6, 1e-8, 1e-10, 1e-12, 1e-14};
run_grid(cases, nc, uppers, 9, (double[]){1e-12}, 1, 1e-9, "upper",
"raw/a_sch_upper.csv", "raw/a_sch_upper_summary.csv", refs);
run_grid(cases, nc, (double[]){2.0}, 1, floors, 10, 1e-9, "min",
"raw/a_sch_min.csv", "raw/a_sch_min_summary.csv", refs);
/* Sensitive-ray RK4 h-halving reference (extra independent check).
* (case index, dir index): r30 and r2.1, theta_c +- 1e-5 / +- 1e-7. */
const int sens[8][2] = {{0, 8}, {0, 9}, {0, 6}, {0, 7},
{2, 8}, {2, 9}, {2, 6}, {2, 7}};
FILE *f = fopen("raw/a_sch_rk4_sensitive.csv", "w");
if (!f) {
fprintf(stderr, "FATAL: cannot open rk4 csv\n");
exit(2);
}
fprintf(f, "%s", ROW_HEADER);
for (int k = 0; k < 8; ++k) {
const Case *c = &cases[sens[k][0]];
const int di = sens[k][1];
SpacetimeSource source;
ObserverState observer;
if (build_source(c, &source) || build_observer(c, &source, &observer)) {
fprintf(stderr, "FATAL: cannot build case %s\n", c->name);
exit(2);
}
GeodesicTraceConfig rk01 = make_rk4(0.01, 262144);
GeodesicTraceConfig rk005 = make_rk4(0.005, 262144);
Res r01 = run_ray(&source, &observer, c->dirs[di], &rk01);
Res r005 = run_ray(&source, &observer, c->dirs[di], &rk005);
Cmp hh, vsdp;
compare(&r005, &r01, &hh);
compare(&r005, &refs[sens[k][0]][di], &vsdp);
Cmp none;
memset(&none, 0, sizeof none);
write_row(f, "rk4_0.01", c, di, 0.01, 0.0, 0.0, &r01, &none);
write_row(f, "rk4_0.005", c, di, 0.005, 0.0, 0.0, &r005, &none);
printf("RK4 %s dir=%d theta=%.6g o01=%d/%d o005=%d/%d "
"hhalve_dn=%.6g hhalve_dgrel=%.6g vsdp_dn=%.6g vsdp_dgrel=%.6g "
"vsdp_class=%d\n",
c->name, di, c->theta[di], r01.outcome, r01.reason, r005.outcome,
r005.reason, hh.dn_ang, hh.dgrel, vsdp.dn_ang, vsdp.dgrel,
vsdp.class_match);
spacetime_destroy(&source);
}
fclose(f);
}
/* ------------------------------------------------------------------ */
/* Sub-command: Minkowski (analytic flat verification) */
/* ------------------------------------------------------------------ */
/* Analytic flat past escape from an observer at (t0,x0) with tetrad-frame
* direction n: coordinate photon velocity u = k_vec/k^0, crossing radius R at
* s = t-t0 < 0, sky direction n_inf = -u, frequency ratio g = 1/k^0. */
static int mink_analytic(const Case *c, const ObserverState *o,
const double n[3], double R, double *t_cross,
double *xc, double *ninf, double *g) {
double k[4];
for (int mu = 0; mu < 4; ++mu)
k[mu] = o->tetrad[0][mu];
for (int a = 0; a < 3; ++a)
for (int mu = 0; mu < 4; ++mu)
k[mu] -= n[a] * o->tetrad[a + 1][mu];
if (!(k[0] > 0.0))
return -1;
double u[3];
for (int i = 0; i < 3; ++i)
u[i] = k[i + 1] / k[0];
const double uu = dot3(u, u);
const double B = 2.0 * dot3(c->position, u);
const double C = dot3(c->position, c->position) - R * R;
const double disc = B * B - 4.0 * uu * C;
if (disc < 0.0)
return -1;
const double sq = sqrt(disc);
const double s1 = (-B + sq) / (2.0 * uu);
const double s2 = (-B - sq) / (2.0 * uu);
const double s = (s1 < 0.0) ? fmin(s1, s2) : s2;
if (!(s < 0.0))
return -1;
*t_cross = o->coordinate_time + s;
for (int i = 0; i < 3; ++i)
xc[i] = c->position[i] + u[i] * s;
const double nu = sqrt(uu);
for (int i = 0; i < 3; ++i)
ninf[i] = -u[i] / nu;
*g = 1.0 / k[0];
return 0;
}
static void cmd_minkowski(void) {
Case c;
case_minkowski(&c);
static Res refs[MAX_CASES][MAX_DIRS];
memset(refs, 0, sizeof refs);
SpacetimeSource source;
ObserverState observer;
if (build_source(&c, &source) || build_observer(&c, &source, &observer)) {
fprintf(stderr, "FATAL: cannot build minkowski case\n");
exit(2);
}
FILE *mf = fopen("raw/a_mink_analytic.csv", "w");
if (!mf) {
fprintf(stderr, "FATAL: cannot open analytic csv\n");
exit(2);
}
fprintf(mf,
"case,dir,upper,min_step,tol,outcome,stop_t,x_err,t_err,dn_ang,"
"dgrel,g_analytic,stop_t_analytic\n");
/* Reference: tol=1e-12, max_step = initial (1.0). */
const GeodesicTraceConfig refcfg =
make_dp(c.initial_step, 1e-12, c.initial_step, 1e-12, c.max_steps,
c.lookback);
for (int di = 0; di < c.n_dirs; ++di)
refs[0][di] = run_ray(&source, &observer, c.dirs[di], &refcfg);
static const double uppers[5] = {1.0, 4.0, 16.0, 64.0, 256.0};
static const double floors[3] = {1e-1, 1e-6, 1e-12};
for (int ui = 0; ui < 5; ++ui) {
for (int fi = 0; fi < 3; ++fi) {
Agg agg;
agg_init(&agg);
for (int di = 0; di < c.n_dirs; ++di) {
const GeodesicTraceConfig cfg =
make_dp(c.initial_step, floors[fi], uppers[ui], 1e-9, c.max_steps,
c.lookback);
const Res r = run_ray(&source, &observer, c.dirs[di], &cfg);
Cmp cmp;
compare(&r, &refs[0][di], &cmp);
agg_add(&agg, &r, &cmp);
double t_an, x_an[3], n_an[3], g_an;
const int ok = mink_analytic(&c, &observer, c.dirs[di], c.escape_radius,
&t_an, x_an, n_an, &g_an);
double x_err = NAN, t_err = NAN, dn_an = NAN, dg_an = NAN;
if (ok == 0 && is_escaped(r.outcome)) {
/* Crossing position is checked through the direct trace in
* a_mink_analytic_x.csv; here compare sky direction, g and time. */
t_err = fabs(r.stop_t - t_an);
dn_an = ang_delta(r.n, n_an);
if (r.g > 0.0 && g_an > 0.0)
dg_an = fabs(r.g / g_an - 1.0);
}
fprintf(mf,
"%s,%d,%.6g,%.6g,%.6g,%d,%.17g,%.17g,%.17g,%.17g,%.17g,%.17g,"
"%.17g\n",
c.name, di, uppers[ui], floors[fi], 1e-9, r.outcome, r.stop_t,
x_err, t_err, dn_an, dg_an, g_an, t_an);
}
printf("SUMMARY mink upper=%.6g min=%.6g n=%ld mismatch=%ld "
"max_dn_ang=%.6g max_dgrel=%.6g sum_rhs=%lu esc=%ld dark=%ld "
"unres=%ld inc=%ld\n",
uppers[ui], floors[fi], agg.n, agg.class_mismatch,
agg.max_dn_ang, agg.max_dgrel, agg.sum_rhs, agg.escaped, agg.dark,
agg.unresolved, agg.incomplete);
fflush(stdout);
}
}
/* Direct endpoint check for the crossing position (x) on a few configs. */
FILE *xf = fopen("raw/a_mink_analytic_x.csv", "w");
if (!xf) {
fprintf(stderr, "FATAL: cannot open analytic x csv\n");
exit(2);
}
fprintf(xf, "case,dir,upper,min_step,tol,outcome,x_err_x,x_err_y,x_err_z,"
"t_err\n");
for (int ui = 0; ui < 5; ++ui) {
for (int fi = 0; fi < 3; ++fi) {
for (int di = 0; di < c.n_dirs; ++di) {
const GeodesicTraceConfig cfg =
make_dp(c.initial_step, floors[fi], uppers[ui], 1e-9, c.max_steps,
c.lookback);
const RayEndpoint e =
geodesic_trace_past(&source, &observer, c.dirs[di], &cfg);
++g_rays;
double t_an, x_an[3], n_an[3], g_an;
const int ok = mink_analytic(&c, &observer, c.dirs[di], c.escape_radius,
&t_an, x_an, n_an, &g_an);
double ex = NAN, ey = NAN, ez = NAN, te = NAN;
if (ok == 0 && e.outcome == RAY_OUTCOME_ESCAPED) {
ex = fabs(e.final_x[0] - x_an[0]);
ey = fabs(e.final_x[1] - x_an[1]);
ez = fabs(e.final_x[2] - x_an[2]);
te = fabs(e.stop_coordinate_time - t_an);
}
fprintf(xf, "%s,%d,%.6g,%.6g,%.6g,%d,%.17g,%.17g,%.17g,%.17g\n",
c.name, di, uppers[ui], floors[fi], 1e-9, e.outcome, ex, ey, ez,
te);
}
}
}
fclose(xf);
fclose(mf);
spacetime_destroy(&source);
}
/* ------------------------------------------------------------------ */
/* Sub-command: Alcubierre */
/* ------------------------------------------------------------------ */
static void cmd_alcubierre(void) {
static Case cases[MAX_CASES];
static Res refs[MAX_CASES][MAX_DIRS];
memset(refs, 0, sizeof refs);
int nc = 0;
case_alcubierre(&cases[nc++], "alc_v3_s1", 0.3, 1.0);
case_alcubierre(&cases[nc++], "alc_v3_s10", 0.3, 10.0);
case_alcubierre(&cases[nc++], "alc_v9_s1", 0.9, 1.0);
case_alcubierre(&cases[nc++], "alc_v9_s10", 0.9, 10.0);
case_alcubierre(&cases[nc++], "alc_v9_s100", 0.9, 100.0);
compute_refs(cases, nc, -1.0, "raw/a_alc_reference.csv", refs);
/* Standard grid: upper = initial*{1,4,8,16,32,64}, floor {1e-4,1e-8,1e-12}. */
{
static const double floors[3] = {1e-4, 1e-8, 1e-12};
Case sub[4];
for (int i = 0; i < 4; ++i)
sub[i] = cases[i];
/* run_grid uses one upper array for all cases, so use per-case initial by
* calling run_grid four times. */
for (int i = 0; i < 4; ++i) {
char rp[128], ap[128];
snprintf(rp, sizeof rp, "raw/a_alc_%.31s_upper.csv", sub[i].name);
snprintf(ap, sizeof ap, "raw/a_alc_%.31s_upper_summary.csv", sub[i].name);
const double base = sub[i].initial_step;
const double up[6] = {base, 4 * base, 8 * base,
16 * base, 32 * base, 64 * base};
run_grid(&sub[i], 1, up, 6, floors, 3, 1e-9, "upper", rp, ap, &refs[i]);
}
}
/* Focused lower-bound stress: vs=.9, sigma=100, upper = initial*8 = 0.004,
* floors {1e-4,1e-5,1e-6,1e-8,1e-12,1e-14}. */
{
const Case *c = &cases[4];
const double up[1] = {c->initial_step * 8.0};
const double floors[6] = {1e-4, 1e-5, 1e-6, 1e-8, 1e-12, 1e-14};
run_grid(c, 1, up, 1, floors, 6, 1e-9, "min_stress",
"raw/a_alc_v9_s100_min.csv", "raw/a_alc_v9_s100_min_summary.csv",
&refs[4]);
}
}
/* ------------------------------------------------------------------ */
int main(int argc, char **argv) {
if (argc < 2) {
fprintf(stderr,
"usage: %s schwarzschild|minkowski|alcubierre\n",
argv[0]);
return 1;
}
if (!strcmp(argv[1], "schwarzschild"))
cmd_schwarzschild();
else if (!strcmp(argv[1], "minkowski"))
cmd_minkowski();
else if (!strcmp(argv[1], "alcubierre"))
cmd_alcubierre();
else {
fprintf(stderr, "unknown sub-command %s\n", argv[1]);
return 1;
}
printf("TOTAL_RAYS %ld\n", g_rays);
return 0;
}
@@ -0,0 +1,404 @@
/*
* 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;
}
@@ -0,0 +1,341 @@
/*
* Standalone critical-classification reference check.
*
* Three static Schwarzschild cameras (M=1, escape sphere 256):
* r30 look 180 (inward), r100 look 180 (inward), r2.1 look 0 (outward),
* each aimed at theta_crit exactly and at theta_crit +- 1e-7, where
* theta_crit = asin(3 sqrt(3) sqrt(1 - 2/r) / r)
* is the local tetrad angle from the camera forward direction.
*
* For every direction four DP54 configs are run (initial step 0.1,
* min_step 1e-14, max_steps 65536, max_lookback_time 6553.6, reject limit 32,
* camera-relative dark threshold 8):
*
* ref_tol1e-12_h0.25 tol 1e-12, max_step 0.25
* ref_tol1e-13_h0.125 tol 1e-13, max_step 0.125
* tol1e-11_h2 tol 1e-11, max_step 2
* tol1e-11_h8 tol 1e-11, max_step 8
*
* Total 3 cameras * 3 directions * 4 configs = 36 endpoint traces.
*
* Raw per-run rows go to raw/critical_ref.csv; stdout prints, for all nine
* directions, ref_vs_tighter / h2_vs_h8 / ref_vs_h2 / ref_vs_h8 comparisons
* (outcome/reason/end, sky angle, relative g, dark stop-time and threshold
* shifts) and a NEIGHBOR_REFERENCE_CHECK that verifies the two reference
* tiers agree at the +-1e-7 neighbours. The exactly-critical direction is a
* separatrix: a class flip or a large direction/stop-time difference there is
* reported as EXACT_CRITICAL_REFERENCE_NOT_CONVERGED and is diagnostic only.
*
* Exit status: 0 when the neighbours' reference tiers agree and no neighbour
* returned an error/unresolved endpoint or a zero-step trace; 1 when a
* neighbour reference check fails; 2 on setup error. The exact-critical
* separatrix is never an assertion.
*
* Link line (the Schwarzschild backend is textually included):
* cc -std=c11 -O2 -Isrc critical_ref_check.c geodesic.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 "../../src/spacetime_schwarzschild.c"
#include "geodesic.h"
#include "observer.h"
#include "spacetime.h"
#define PI 3.14159265358979323846
typedef struct {
const char *name;
double tol;
double max_step;
} CfgDef;
static const CfgDef cfgs[4] = {
{"ref_tol1e-12_h0.25", 1e-12, 0.25},
{"ref_tol1e-13_h0.125", 1e-13, 0.125},
{"tol1e-11_h2", 1e-11, 2.0},
{"tol1e-11_h8", 1e-11, 8.0},
};
typedef struct {
const char *name;
double r;
double look_ra_deg;
} CamDef;
static const CamDef cams[3] = {
{"r30_inward", 30.0, 180.0},
{"r100_inward", 100.0, 180.0},
{"r2p1_outward", 2.1, 0.0},
};
typedef struct {
int outcome, reason;
unsigned int end_id;
double stop_t, g, thr;
double L, L0;
double n[3];
unsigned int steps, rejected;
unsigned long rhs;
} Res;
static double b_dot3(const double a[3], const double b[3]) {
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
}
static double b_ang_delta(const double a[3], const double b[3]) {
const double na = sqrt(b_dot3(a, a)), nb = sqrt(b_dot3(b, b));
if (!(na > 0.0) || !(nb > 0.0))
return NAN;
const double cross[3] = {a[1] * b[2] - a[2] * b[1],
a[2] * b[0] - a[0] * b[2],
a[0] * b[1] - a[1] * b[0]};
return atan2(sqrt(b_dot3(cross, cross)) / (na * nb),
b_dot3(a, b) / (na * nb));
}
static double 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 void dir_from_theta(double th, double n[3]) {
n[0] = cos(th);
n[1] = sin(th);
n[2] = 0.0;
const double nn = sqrt(b_dot3(n, n));
for (int i = 0; i < 3; ++i)
n[i] /= nn;
}
static GeodesicTraceConfig make_cfg(const CfgDef *d) {
GeodesicTraceConfig c;
memset(&c, 0, sizeof c);
c.coordinate_time_step = 0.1;
c.max_steps = 65536;
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 = d->tol;
c.min_step = 1e-14;
c.max_step = d->max_step;
c.consecutive_rejection_limit = 32;
c.max_lookback_time = 6553.6;
return c;
}
static int build_observer(const CamDef *cam, const SpacetimeSource *s,
ObserverState *o) {
const double pos[3] = {cam->r, 0.0, 0.0};
MetricData m;
if (spacetime_eval(s, 0.0, pos, &m) != SPACETIME_POINT_OK)
return -1;
ObserverCamera camera;
memset(&camera, 0, sizeof camera);
camera.position[0] = pos[0];
camera.position[1] = pos[1];
camera.position[2] = pos[2];
camera.look_ra_deg = cam->look_ra_deg;
camera.look_dec_deg = 0.0;
return observer_from_coordinate_camera(&m, &camera, o, NULL) ==
OBSERVER_BUILD_OK
? 0
: -1;
}
static Res run_one(const SpacetimeSource *s, const ObserverState *o,
const double dir[3], const GeodesicTraceConfig *cfg) {
Res r;
memset(&r, 0, sizeof r);
const RayEndpoint e = geodesic_trace_past(s, o, dir, cfg);
r.outcome = e.outcome;
r.reason = e.reason;
r.end_id = e.end_id;
r.stop_t = e.stop_coordinate_time;
r.g = e.frequency_ratio;
r.thr = e.threshold_value;
r.L = e.final_log_alpha_p0;
r.L0 = e.final_log_alpha_p0_0;
for (int i = 0; i < 3; ++i)
r.n[i] = e.n_infinity[i];
r.steps = e.accepted_steps;
r.rejected = e.rejected_steps;
r.rhs = e.rhs_evaluations;
return r;
}
static const char *outcome_name(int o) {
switch (o) {
case RAY_OUTCOME_ESCAPED:
return "ESC";
case RAY_OUTCOME_DARK:
return "DARK";
case RAY_OUTCOME_UNRESOLVED:
return "UNRES";
default:
return "INC";
}
}
static void print_pair(const char *cam, int di, double theta, const char *pn,
const Res *a, const Res *b) {
const int same = a->outcome == b->outcome && a->reason == b->reason &&
a->end_id == b->end_id;
const int esc = a->outcome == RAY_OUTCOME_ESCAPED &&
b->outcome == RAY_OUTCOME_ESCAPED;
const double dn = esc ? b_ang_delta(a->n, b->n) : NAN;
const double dg =
(esc && a->g > 0.0 && b->g > 0.0) ? fabs(a->g / b->g - 1.0) : NAN;
const double dstop =
(isfinite(a->stop_t) && isfinite(b->stop_t)) ? a->stop_t - b->stop_t
: NAN;
const double dthr = (isfinite(a->thr) && isfinite(b->thr))
? a->thr - b->thr
: NAN;
printf("CAPAIR camera=%s dir=%d theta=%.17g pair=%s class=%s/%s same=%d "
"dn_ang=%.17g dgrel=%.17g dstop_t=%.17g dthr=%.17g end=%u/%u\n",
cam, di, theta, pn, outcome_name(a->outcome), outcome_name(b->outcome),
same, dn, dg, dstop, dthr, a->end_id, b->end_id);
}
/* A two-reference-tier agreement check for one neighbour direction. Returns
* 1 when the pair is a valid, agreeing ESC/DARK reference, 0 otherwise. */
static int neighbour_reference_check(const char *cam, int di, double theta,
const Res *a, const Res *b) {
const int class_same = a->outcome == b->outcome && a->reason == b->reason &&
a->end_id == b->end_id;
const int clean = (a->outcome == RAY_OUTCOME_ESCAPED ||
a->outcome == RAY_OUTCOME_DARK) &&
(b->outcome == RAY_OUTCOME_ESCAPED ||
b->outcome == RAY_OUTCOME_DARK);
const int stepped = a->steps > 0 && b->steps > 0;
const int esc = a->outcome == RAY_OUTCOME_ESCAPED &&
b->outcome == RAY_OUTCOME_ESCAPED;
const double dn = esc ? b_ang_delta(a->n, b->n) : NAN;
const double dg =
(esc && a->g > 0.0 && b->g > 0.0) ? fabs(a->g / b->g - 1.0) : NAN;
const double dstop =
(isfinite(a->stop_t) && isfinite(b->stop_t)) ? a->stop_t - b->stop_t
: NAN;
printf("NEIGHBOR_REFERENCE_CHECK camera=%s dir=%d theta=%.17g "
"ref_class0=%s/%d/%u ref_class1=%s/%d/%u same=%d clean=%d stepped=%d "
"dn_ang=%.17g dgrel=%.17g dstop_t=%.17g\n",
cam, di, theta, outcome_name(a->outcome), a->reason, a->end_id,
outcome_name(b->outcome), b->reason, b->end_id, class_same, clean,
stepped, dn, dg, dstop);
return class_same && clean && stepped;
}
int main(void) {
SpacetimeSource source;
if (spacetime_create_schwarzschild_ks(&source, 1.0, 256.0)) {
fprintf(stderr, "FATAL: cannot create Schwarzschild source\n");
return 2;
}
FILE *f = fopen("raw/critical_ref.csv", "w");
if (!f) {
fprintf(stderr, "FATAL: cannot open raw/critical_ref.csv\n");
return 2;
}
fprintf(f, "camera,dir,theta,cfg,outcome,reason,end_id,stop_t,steps,"
"rejected,rhs,nx,ny,nz,g,thr,L,L0\n");
Res res[3][3][4];
double thetas[3][3];
for (int ci = 0; ci < 3; ++ci) {
ObserverState observer;
if (build_observer(&cams[ci], &source, &observer)) {
fprintf(stderr, "FATAL: cannot build observer %s\n", cams[ci].name);
return 2;
}
const double tc = critical_angle(cams[ci].r);
const double th[3] = {tc - 1e-7, tc, tc + 1e-7};
for (int di = 0; di < 3; ++di) {
double dir[3];
dir_from_theta(th[di], dir);
thetas[ci][di] = th[di];
for (int fi = 0; fi < 4; ++fi) {
const GeodesicTraceConfig cfg = make_cfg(&cfgs[fi]);
const Res r = run_one(&source, &observer, dir, &cfg);
res[ci][di][fi] = r;
fprintf(f, "%s,%d,%.17g,%s,%d,%d,%u,%.17g,%u,%u,%lu,%.17g,%.17g,"
"%.17g,%.17g,%.17g,%.17g,%.17g\n",
cams[ci].name, di, th[di], cfgs[fi].name, r.outcome, r.reason,
r.end_id, r.stop_t, r.steps, r.rejected, r.rhs, r.n[0], r.n[1],
r.n[2], r.g, r.thr, r.L, r.L0);
printf("RAW camera=%s dir=%d theta=%.17g cfg=%s outcome=%d reason=%d "
"end=%u stop_t=%.17g steps=%u rejected=%u rhs=%lu g=%.17g "
"thr=%.17g L=%.17g L0=%.17g nx=%.17g ny=%.17g nz=%.17g\n",
cams[ci].name, di, th[di], cfgs[fi].name, r.outcome, r.reason,
r.end_id, r.stop_t, r.steps, r.rejected, r.rhs, r.g, r.thr, r.L,
r.L0, r.n[0], r.n[1], r.n[2]);
fflush(stdout);
}
}
}
fclose(f);
/* All nine directions: ref_vs_tighter, h2_vs_h8, ref_vs_h2, ref_vs_h8. */
for (int ci = 0; ci < 3; ++ci) {
for (int di = 0; di < 3; ++di) {
print_pair(cams[ci].name, di, thetas[ci][di], "ref_vs_tighter",
&res[ci][di][0], &res[ci][di][1]);
print_pair(cams[ci].name, di, thetas[ci][di], "h2_vs_h8",
&res[ci][di][2], &res[ci][di][3]);
print_pair(cams[ci].name, di, thetas[ci][di], "ref_vs_h2",
&res[ci][di][0], &res[ci][di][2]);
print_pair(cams[ci].name, di, thetas[ci][di], "ref_vs_h8",
&res[ci][di][0], &res[ci][di][3]);
}
}
/* Exact-critical: diagnostic only, never asserted. */
for (int ci = 0; ci < 3; ++ci) {
const Res *a = &res[ci][1][0];
const Res *b = &res[ci][1][1];
const int same = a->outcome == b->outcome && a->reason == b->reason &&
a->end_id == b->end_id;
const int esc = a->outcome == RAY_OUTCOME_ESCAPED &&
b->outcome == RAY_OUTCOME_ESCAPED;
const int dark = a->outcome == RAY_OUTCOME_DARK &&
b->outcome == RAY_OUTCOME_DARK;
const double dn = esc ? b_ang_delta(a->n, b->n) : NAN;
const double dstop =
(isfinite(a->stop_t) && isfinite(b->stop_t)) ? a->stop_t - b->stop_t
: NAN;
const int nonconverged =
!same || (esc && !(dn <= 1e-6)) || (dark && fabs(dstop) > 1e-4);
printf("EXACT_CRITICAL_DIRECTION camera=%s theta=%.17g ref0=%s/%d/%u "
"ref1=%s/%d/%u same=%d dn_ang=%.17g dstop_t=%.17g\n",
cams[ci].name, thetas[ci][1], outcome_name(a->outcome), a->reason,
a->end_id, outcome_name(b->outcome), b->reason, b->end_id, same, dn,
dstop);
if (nonconverged)
printf("EXACT_CRITICAL_REFERENCE_NOT_CONVERGED camera=%s dn_ang=%.17g "
"dstop_t=%.17g class_same=%d\n",
cams[ci].name, dn, dstop, same);
}
/* Neighbour reference check: must pass; this is the assertion-valid path. */
int fail = 0;
for (int ci = 0; ci < 3; ++ci) {
for (int di = 0; di < 3; di += 2) {
if (!neighbour_reference_check(cams[ci].name, di, thetas[ci][di],
&res[ci][di][0], &res[ci][di][1]))
fail = 1;
}
}
spacetime_destroy(&source);
printf("CRITICAL_REFERENCE_CHECK status=%d\n", fail ? 1 : 0);
printf("CRITICAL_REF_DONE\n");
return fail ? 1 : 0;
}
@@ -0,0 +1,26 @@
/* Diagnostic only: reuse the production regression's invalid-metric fixture. */
#define main adaptive_regression_main_not_called
#include "../../tests/test_geodesic_adaptive.c"
#undef main
int main(void) {
const double floors[] = {1e-2,1e-4,1e-6,1e-8,1e-10,1e-12,1e-14};
for (unsigned i=0;i<sizeof floors/sizeof *floors;++i) {
FixtureContext context={.bad_status=SPACETIME_POINT_INVALID_METRIC,
.bad_lo=-10,.bad_hi=-.05,.radius=100};
SpacetimeSource source={.context=&context,.ops=&fixture_ops};
GeodesicTraceConfig config=dp_config(1e-9,.1,10);
config.min_step=floors[i]; config.max_step=1;
const ObserverState observer=flat_observer_at((double[3]){0,0,0});
const double direction[3]={1,0,0};
RayEndpoint endpoint=geodesic_trace_past(&source,&observer,direction,&config);
printf("FAILURE_FLOOR min=%.0e outcome=%d reason=%d t=%.17g distance_to_invalid=%.17g accepted=%u rejected=%u rhs=%lu\n",
floors[i],endpoint.outcome,endpoint.reason,endpoint.stop_coordinate_time,
endpoint.stop_coordinate_time+.05,endpoint.accepted_steps,
endpoint.rejected_steps,endpoint.rhs_evaluations);
if(endpoint.outcome!=RAY_OUTCOME_INCOMPLETE ||
endpoint.stop_coordinate_time<=-.05) return 1;
}
puts("FAILURE_FLOOR_EXIT=0");
return 0;
}
@@ -0,0 +1,77 @@
#!/usr/bin/env python3
"""Pure GRLENS v3 map load/compare helpers.
No renderer, no environment probing, no data generation: this module is only
imported by the benchmark scripts under
benchmarks/adaptive_step_bounds_2026-10-05/. Keeping it here means the
benchmark never imports an untracked module from local/.
"""
import collections
import hashlib
import math
import struct
import zlib
def load(path):
"""Parse one GRLENS v3 map; returns (info dict, {film_pos: vertex tuple})."""
data = path.read_bytes()
assert data[:8] == b'GRLENS\x01\x00'
assert struct.unpack_from('<I', data, 8)[0] == 3
assert struct.unpack_from('<Q', data, 32)[0] == 1
nv, nt = struct.unpack_from('<QQ', data, 200)
end = 224 + nv * 108 + nt * 32
assert end + 4 == len(data)
assert zlib.crc32(data[224:end]) == struct.unpack_from('<I', data, end)[0]
vertices = [struct.unpack_from('<9dIIIQQQ', data, 224 + i * 108)
for i in range(nv)]
costs = [sum(v[j] for v in vertices) for j in (12, 13, 14)]
result = dict(vertices=nv, triangles=nt,
outcomes=dict(collections.Counter(v[10] for v in vertices)),
reasons=dict(collections.Counter(v[11] for v in vertices)),
accepted=costs[0], rejected=costs[1], rhs=costs[2])
names = ('atol_x', 'atol_Pi', 'atol_L', 'rtol', 'min_step', 'max_step',
'max_lookback_time', 'retry_lookback_increment',
'max_total_lookback_time')
result['provenance'] = dict(zip(names, struct.unpack_from('<9d', data, 100)))
result['provenance'].update(
integrator=struct.unpack_from('<I', data, 68)[0],
initial_step=struct.unpack_from('<d', data, 88)[0],
initial_max_steps=struct.unpack_from('<I', data, 96)[0],
threshold=struct.unpack_from('<d', data, 48)[0],
retry_step_increment=struct.unpack_from('<I', data, 56)[0],
max_total_steps=struct.unpack_from('<I', data, 60)[0])
for j, name in ((12, 'accepted'), (13, 'rejected'), (14, 'rhs')):
xs = sorted(v[j] for v in vertices)
result[name + '_percentiles'] = {
str(q): xs[min(len(xs) - 1, int(q * (len(xs) - 1)))]
for q in (0, .5, .9, .99, 1)}
result['sha256'] = hashlib.sha256(data).hexdigest()
result['triangle_sha256'] = hashlib.sha256(
data[224 + nv * 108:end]).hexdigest()
by_film = {(v[0], v[1]): v for v in vertices}
result['unique_film_positions'] = len(by_film)
result['duplicate_film_positions'] = len(vertices) - len(by_film)
return result, by_film
def compare(a, b):
"""Compare two {film_pos: vertex} maps by shared film-coordinate identity."""
keys = a.keys() & b.keys()
mismatches = 0
worst_sky = worst_logg = 0.
for k in keys:
x, y = a[k], b[k]
if x[9:12] != y[9:12]:
mismatches += 1
elif x[10] == 0:
n, m = x[5:8], y[5:8]
cross = (n[1] * m[2] - n[2] * m[1], n[2] * m[0] - n[0] * m[2],
n[0] * m[1] - n[1] * m[0])
angle = math.atan2(math.sqrt(sum(v * v for v in cross)),
sum(v * w for v, w in zip(n, m)))
worst_sky = max(worst_sky, angle)
worst_logg = max(worst_logg, abs(x[8] - y[8]))
return dict(shared=len(keys), only_a=len(a) - len(keys),
only_b=len(b) - len(keys), terminal_mismatches=mismatches,
max_sky_angle=worst_sky, max_logg_difference=worst_logg)
@@ -0,0 +1,41 @@
#!/usr/bin/env python3
"""Record provenance without launching a render.
Usage:
record_environment.py [OUT_DIR]
OUT_DIR defaults to <repo>/local/adaptive_bounds_mesh. Writes metadata.json in
OUT_DIR. No renderer or test is run.
"""
import hashlib
import json
from pathlib import Path
import subprocess
import sys
import time
ROOT = Path(__file__).resolve().parents[2]
OUT = Path(sys.argv[1]).resolve() if len(sys.argv) > 1 else \
ROOT / 'local/adaptive_bounds_mesh'
PATHS = ['src/geodesic.c', 'src/main.c', 'src/frame.c',
'build/Release/schwarzschild_sky']
metadata = {
'recorded_utc': time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()),
'git_head': subprocess.check_output(
['git', 'rev-parse', 'HEAD'], cwd=ROOT, text=True).strip(),
'hashes': {p: hashlib.sha256((ROOT / p).read_bytes()).hexdigest()
for p in PATHS},
'compiler': subprocess.check_output(['cc', '--version'], text=True),
'cpu': subprocess.check_output(['lscpu'], text=True),
'scope': '8 bounded 320x180 candidates plus 2 tight references; no 4K here',
'threads': 8,
'build_command': 'make -j4 BUILD_TYPE=Release SPACETIME=schwarzschild backend',
'run_commands': [
'python3 benchmarks/adaptive_step_bounds_2026-10-05/run_mesh.py [OUT]',
'python3 benchmarks/adaptive_step_bounds_2026-10-05/run_mesh_reference.py [OUT]',
'bash benchmarks/adaptive_step_bounds_2026-10-05/run_failure_floor.sh [OUT]'],
}
OUT.mkdir(parents=True, exist_ok=True)
(OUT / 'metadata.json').write_text(json.dumps(metadata, indent=2) + '\n')
print(json.dumps(metadata, indent=2))
@@ -0,0 +1,16 @@
[
{
"upper": 2,
"command": "/home/wyj/Code/C/GR_4d_raytracing/build/Release/schwarzschild_sky --integrator dp54 --width 3840 --height 2160 --look-ra-deg 262.5 --look-dec-deg -30 --fov-deg 45 --observer-radius 100 --coarse-cell-pixels 16 --refine-max-level 4 --refine-jacobian-min .2 --catalog /home/wyj/Code/C/GR_4d_raytracing/local/step_bounds_4k/single_dim_star.csv --exposure 1 --tone-map reinhard --psf-min-y 1e-20 --psf-relative-tail 1e-4 --ode-min-step 1e-12 --ode-max-step 2 --verbose --lens-map-output /home/wyj/Code/C/GR_4d_raytracing/local/step_bounds_4k/r100_4k_hmax2.grlens --output /home/wyj/Code/C/GR_4d_raytracing/local/step_bounds_4k/r100_4k_hmax2.png",
"state": "completed",
"returncode": 0,
"wall_seconds": 11.389317202148959
},
{
"upper": 8,
"command": "/home/wyj/Code/C/GR_4d_raytracing/build/Release/schwarzschild_sky --integrator dp54 --width 3840 --height 2160 --look-ra-deg 262.5 --look-dec-deg -30 --fov-deg 45 --observer-radius 100 --coarse-cell-pixels 16 --refine-max-level 4 --refine-jacobian-min .2 --catalog /home/wyj/Code/C/GR_4d_raytracing/local/step_bounds_4k/single_dim_star.csv --exposure 1 --tone-map reinhard --psf-min-y 1e-20 --psf-relative-tail 1e-4 --ode-min-step 1e-12 --ode-max-step 8 --verbose --lens-map-output /home/wyj/Code/C/GR_4d_raytracing/local/step_bounds_4k/r100_4k_hmax8.grlens --output /home/wyj/Code/C/GR_4d_raytracing/local/step_bounds_4k/r100_4k_hmax8.png",
"state": "completed",
"returncode": 0,
"wall_seconds": 8.389249739935622
}
]
@@ -0,0 +1,160 @@
{
"r100_4k_hmax2": {
"vertices": 66045,
"triangles": 131338,
"outcomes": {
"0": 62419,
"1": 3626
},
"reasons": {
"0": 62419,
"1": 3626
},
"accepted": 15768677,
"rejected": 97176,
"rhs": 130409139,
"provenance": {
"atol_x": 1e-09,
"atol_Pi": 1e-09,
"atol_L": 1e-09,
"rtol": 1e-09,
"min_step": 1e-12,
"max_step": 2.0,
"max_lookback_time": 6553.6,
"retry_lookback_increment": 6553.6,
"max_total_lookback_time": 26214.4,
"integrator": 1,
"initial_step": 0.1,
"initial_max_steps": 65536,
"threshold": 8.0,
"retry_step_increment": 65536,
"max_total_steps": 262144
},
"accepted_percentiles": {
"0": 162,
"0.5": 212,
"0.9": 328,
"0.99": 404,
"1": 520
},
"rejected_percentiles": {
"0": 0,
"0.5": 1,
"0.9": 3,
"0.99": 4,
"1": 5
},
"rhs_percentiles": {
"0": 1421,
"0.5": 1799,
"0.9": 2597,
"0.99": 3129,
"1": 3941
},
"sha256": "ef7267074338192f8597b1f2a0300fd36da8e25441e064719c7ad586c481ae7a",
"triangle_sha256": "2a943902e9b1401a16bc40fc547c19077bb12e396080074393636ee30761b7b2",
"unique_film_positions": 66045,
"duplicate_film_positions": 0,
"requested_samples_including_nonpersistent_probes": 116308,
"initial_samples": 32776,
"refinement_samples_per_generation": [
32686,
7900,
13375,
24826,
4403,
261,
67,
14
],
"trace_wall_seconds": 10.774,
"wall_seconds": 11.389317202148959
},
"r100_4k_hmax8": {
"vertices": 66045,
"triangles": 131338,
"outcomes": {
"0": 62419,
"1": 3626
},
"reasons": {
"0": 62419,
"1": 3626
},
"accepted": 10183020,
"rejected": 194781,
"rhs": 91991144,
"provenance": {
"atol_x": 1e-09,
"atol_Pi": 1e-09,
"atol_L": 1e-09,
"rtol": 1e-09,
"min_step": 1e-12,
"max_step": 8.0,
"max_lookback_time": 6553.6,
"retry_lookback_increment": 6553.6,
"max_total_lookback_time": 26214.4,
"integrator": 1,
"initial_step": 0.1,
"initial_max_steps": 65536,
"threshold": 8.0,
"retry_step_increment": 65536,
"max_total_steps": 262144
},
"accepted_percentiles": {
"0": 80,
"0.5": 127,
"0.9": 259,
"0.99": 337,
"1": 432
},
"rejected_percentiles": {
"0": 0,
"0.5": 3,
"0.9": 5,
"0.99": 6,
"1": 7
},
"rhs_percentiles": {
"0": 868,
"0.5": 1204,
"0.9": 2114,
"0.99": 2646,
"1": 3318
},
"sha256": "b1897493b7fa5b88f86872e81d15ac876818a705fa0fefe8b1ee262da9de59bb",
"triangle_sha256": "2a943902e9b1401a16bc40fc547c19077bb12e396080074393636ee30761b7b2",
"unique_film_positions": 66045,
"duplicate_film_positions": 0,
"requested_samples_including_nonpersistent_probes": 116308,
"initial_samples": 32776,
"refinement_samples_per_generation": [
32686,
7900,
13375,
24826,
4403,
261,
67,
14
],
"trace_wall_seconds": 7.805000000000001,
"wall_seconds": 8.389249739935622
},
"comparison": {
"shared": 66045,
"only_a": 0,
"only_b": 0,
"terminal_mismatches": 0,
"max_sky_angle": 7.954413592847793e-07,
"max_logg_difference": 3.471110204822381e-11,
"triangle_payload_identical": true
},
"relative_reduction": {
"accepted": 0.35422483446138187,
"rhs": 0.2945958795111745,
"wall_seconds": 0.2634106513116764,
"trace_wall_seconds": 0.27557081863746047
},
"cost_scope": "accepted/rejected/RHS sums cover final persistent vertices only; nonpersistent discarded refinement probes are not in v3 map. Do not interpret them as full-render executed RHS totals."
}
@@ -0,0 +1,242 @@
OMP_NUM_THREADS=16 OMP_DYNAMIC=FALSE /home/wyj/Code/C/GR_4d_raytracing/build/Release/schwarzschild_sky --integrator dp54 --width 3840 --height 2160 --look-ra-deg 262.5 --look-dec-deg -30 --fov-deg 45 --observer-radius 100 --coarse-cell-pixels 16 --refine-max-level 4 --refine-jacobian-min .2 --catalog /home/wyj/Code/C/GR_4d_raytracing/local/step_bounds_4k/single_dim_star.csv --exposure 1 --tone-map reinhard --psf-min-y 1e-20 --psf-relative-tail 1e-4 --ode-min-step 1e-12 --ode-max-step 2 --verbose --lens-map-output /home/wyj/Code/C/GR_4d_raytracing/local/step_bounds_4k/r100_4k_hmax2.grlens --output /home/wyj/Code/C/GR_4d_raytracing/local/step_bounds_4k/r100_4k_hmax2.png
Blackbody LUT: 1024 CIE 1931 2-deg 1 nm-linear XYZ nodes, T=[670.146556, 101408.88] K, loaded assets/blackbody/cie1931_2deg_xyz_1024.grbblut in 0.000040 s; payload fnv1a64=0e39867b0e70a809
Blackbody backend: lut
PSF cache ready: 64x64 phases, radius 25 px, relative tail 1e-04, tail abs 1e-06, boundary 1e-07, build 0.250 s
Frame 0: integrator=dp54 step=0.1 max_steps=65536 threshold=8 atol=(x 1e-09, Pi 1e-09, L 1e-09) rtol=1e-09 bounds=[1e-12, 2] max_rejections=32 lookback=6553.6
Frame 0: tracing 32776 initial rays from 64800 mesh triangles...
Frame 0: initial ray trace finished in 2.562 s.
Frame 0: starting adaptive ray-trace refinement (max level 4)...
Frame 0: refinement generation 0 tracing 32686 samples from 32776 vertices and 64800 triangles.
Frame 0: refinement generation 0 finished; added 3014 vertices, now 35790 vertices and 70828 triangles.
Frame 0: refinement generation 1 tracing 7900 samples from 35790 vertices and 70828 triangles.
Frame 0: refinement generation 1 finished; added 4852 vertices, now 40642 vertices and 80532 triangles.
Frame 0: refinement generation 2 tracing 13375 samples from 40642 vertices and 80532 triangles.
Frame 0: refinement generation 2 finished; added 8889 vertices, now 49531 vertices and 98310 triangles.
Frame 0: refinement generation 3 tracing 24826 samples from 49531 vertices and 98310 triangles.
Frame 0: refinement generation 3 finished; added 16055 vertices, now 65586 vertices and 130420 triangles.
Frame 0: refinement generation 4 tracing 4403 samples from 65586 vertices and 130420 triangles.
Frame 0: refinement generation 4 finished; added 389 vertices, now 65975 vertices and 131198 triangles.
Frame 0: refinement generation 5 tracing 261 samples from 65975 vertices and 131198 triangles.
Frame 0: refinement generation 5 finished; added 58 vertices, now 66033 vertices and 131314 triangles.
Frame 0: refinement generation 6 tracing 67 samples from 66033 vertices and 131314 triangles.
Frame 0: refinement generation 6 finished; added 12 vertices, now 66045 vertices and 131338 triangles.
Frame 0: refinement generation 7 tracing 14 samples from 66045 vertices and 131338 triangles.
Frame 0: refinement generation 7 finished; added 0 vertices, now 66045 vertices and 131338 triangles.
Frame 0: adaptive ray-trace refinement finished in 8.212 s; 66045 vertices, 131338 triangles.
Boundary accounting: EEE=122456 DDD=5624 EED/EDD=3258 UUD/UDD=0 U+E=0 UUU=0 error=0; approx-black=0 (0 px^2), retries=0, budget-incomplete=0.
Frame 0 trace cost: accepted=15768677 rejected=97176 rhs=130409139 over 66045 vertices (saturated=0).
Wrote lens map: /home/wyj/Code/C/GR_4d_raytracing/local/step_bounds_4k/r100_4k_hmax2.grlens (66045 vertices, 131338 triangles)
Frame 0: traced 66045 lens vertices; starting catalog render.
Frame 0: finding and prefetching catalog tiles...
Frame 0: catalog prefetch finished (0 candidate tiles).
Frame 0: splatting 131338 lens triangles...
Frame 0: splat worker 1/16 started.
Frame 0: splat worker 1/16 reached 8 local triangles.
Frame 0: splat worker 1/16 reached 16 local triangles.
Frame 0: splat worker 1/16 reached 32 local triangles.
Frame 0: splat worker 6/16 started.
Frame 0: splat worker 8/16 started.
Frame 0: splat worker 8/16 reached 8 local triangles.
Frame 0: splat worker 8/16 reached 16 local triangles.
Frame 0: splat worker 12/16 started.
Frame 0: splat worker 13/16 started.
Frame 0: splat worker 3/16 started.
Frame 0: splat worker 6/16 reached 8 local triangles.
Frame 0: splat worker 6/16 reached 16 local triangles.
Frame 0: splat worker 9/16 started.
Frame 0: splat worker 11/16 started.
Frame 0: splat worker 7/16 started.
Frame 0: splat worker 7/16 reached 8 local triangles.
Frame 0: splat worker 13/16 reached 8 local triangles.
Frame 0: splat worker 13/16 reached 16 local triangles.
Frame 0: splat worker 13/16 reached 32 local triangles.
Frame 0: splat worker 3/16 reached 8 local triangles.
Frame 0: splat worker 3/16 reached 16 local triangles.
Frame 0: splat worker 3/16 reached 32 local triangles.
Frame 0: splat worker 15/16 started.
Frame 0: splat worker 15/16 reached 8 local triangles.
Frame 0: splat worker 15/16 reached 16 local triangles.
Frame 0: splat worker 15/16 reached 32 local triangles.
Frame 0: splat worker 10/16 started.
Frame 0: splat worker 8/16 reached 32 local triangles.
Frame 0: splat worker 9/16 reached 8 local triangles.
Frame 0: splat worker 9/16 reached 16 local triangles.
Frame 0: splat worker 12/16 reached 8 local triangles.
Frame 0: splat worker 7/16 reached 16 local triangles.
Frame 0: splat worker 14/16 started.
Frame 0: splat worker 4/16 started.
Frame 0: splat worker 13/16 reached 64 local triangles.
Frame 0: splat worker 2/16 started.
Frame 0: splat worker 5/16 started.
Frame 0: splat worker 5/16 reached 8 local triangles.
Frame 0: splat worker 5/16 reached 16 local triangles.
Frame 0: splat worker 1/16 reached 64 local triangles.
Frame 0: splat worker 16/16 started.
Frame 0: splat worker 16/16 reached 8 local triangles.
Frame 0: splat worker 16/16 reached 16 local triangles.
Frame 0: splat worker 8/16 reached 64 local triangles.
Frame 0: splat worker 11/16 reached 8 local triangles.
Frame 0: splat worker 8/16 reached 128 local triangles.
Frame 0: splat worker 12/16 reached 16 local triangles.
Frame 0: splat worker 7/16 reached 32 local triangles.
Frame 0: splat worker 14/16 reached 8 local triangles.
Frame 0: splat worker 8/16 reached 256 local triangles.
Frame 0: splat worker 13/16 reached 128 local triangles.
Frame 0: splat worker 2/16 reached 8 local triangles.
Frame 0: splat worker 2/16 reached 16 local triangles.
Frame 0: splat worker 6/16 reached 32 local triangles.
Frame 0: splat worker 5/16 reached 32 local triangles.
Frame 0: splat worker 6/16 reached 64 local triangles.
Frame 0: splat worker 1/16 reached 128 local triangles.
Frame 0: splat worker 5/16 reached 64 local triangles.
Frame 0: splat worker 10/16 reached 8 local triangles.
Frame 0: splat worker 9/16 reached 32 local triangles.
Frame 0: splat worker 6/16 reached 128 local triangles.
Frame 0: splat worker 11/16 reached 16 local triangles.
Frame 0: splat worker 5/16 reached 128 local triangles.
Frame 0: splat worker 8/16 reached 512 local triangles.
Frame 0: splat worker 7/16 reached 64 local triangles.
Frame 0: splat worker 14/16 reached 16 local triangles.
Frame 0: splat worker 4/16 reached 8 local triangles.
Frame 0: splat worker 2/16 reached 32 local triangles.
Frame 0: splat worker 15/16 reached 64 local triangles.
Frame 0: splat worker 7/16 reached 128 local triangles.
Frame 0: splat worker 13/16 reached 256 local triangles.
Frame 0: splat worker 10/16 reached 16 local triangles.
Frame 0: splat worker 5/16 reached 256 local triangles.
Frame 0: splat worker 11/16 reached 32 local triangles.
Frame 0: splat worker 9/16 reached 64 local triangles.
Frame 0: splat worker 1/16 reached 256 local triangles.
Frame 0: splat worker 11/16 reached 64 local triangles.
Frame 0: splat worker 3/16 reached 64 local triangles.
Frame 0: splat worker 14/16 reached 32 local triangles.
Frame 0: splat worker 4/16 reached 16 local triangles.
Frame 0: splat worker 6/16 reached 256 local triangles.
Frame 0: splat worker 4/16 reached 32 local triangles.
Frame 0: splat worker 14/16 reached 64 local triangles.
Frame 0: splat worker 11/16 reached 128 local triangles.
Frame 0: splat worker 3/16 reached 128 local triangles.
Frame 0: splat worker 10/16 reached 32 local triangles.
Frame 0: splat worker 4/16 reached 64 local triangles.
Frame 0: splat worker 7/16 reached 256 local triangles.
Frame 0: splat worker 10/16 reached 64 local triangles.
Frame 0: splat worker 16/16 reached 32 local triangles.
Frame 0: splat worker 4/16 reached 128 local triangles.
Frame 0: splat worker 12/16 reached 32 local triangles.
Frame 0: splat worker 5/16 reached 512 local triangles.
Frame 0: splat worker 11/16 reached 256 local triangles.
Frame 0: splat worker 16/16 reached 64 local triangles.
Frame 0: splat worker 3/16 reached 256 local triangles.
Frame 0: splat worker 12/16 reached 64 local triangles.
Frame 0: splat worker 13/16 reached 512 local triangles.
Frame 0: splat worker 15/16 reached 128 local triangles.
Frame 0: splat worker 14/16 reached 128 local triangles.
Frame 0: splat worker 9/16 reached 128 local triangles.
Frame 0: splat worker 10/16 reached 128 local triangles.
Frame 0: splat worker 2/16 reached 64 local triangles.
Frame 0: splat worker 12/16 reached 128 local triangles.
Frame 0: splat worker 16/16 reached 128 local triangles.
Frame 0: splat worker 6/16 reached 512 local triangles.
Frame 0: splat worker 4/16 reached 256 local triangles.
Frame 0: splat worker 1/16 reached 512 local triangles.
Frame 0: splat worker 8/16 reached 1024 local triangles.
Frame 0: splat worker 2/16 reached 128 local triangles.
Frame 0: splat worker 15/16 reached 256 local triangles.
Frame 0: splat worker 14/16 reached 256 local triangles.
Frame 0: splat worker 7/16 reached 512 local triangles.
Frame 0: splat worker 10/16 reached 256 local triangles.
Frame 0: splat worker 9/16 reached 256 local triangles.
Frame 0: splat worker 11/16 reached 512 local triangles.
Frame 0: splat worker 16/16 reached 256 local triangles.
Frame 0: splat worker 12/16 reached 256 local triangles.
Frame 0: splat worker 2/16 reached 256 local triangles.
Frame 0: splat worker 3/16 reached 512 local triangles.
Frame 0: splat worker 4/16 reached 512 local triangles.
Frame 0: splat worker 15/16 reached 512 local triangles.
Frame 0: splat worker 14/16 reached 512 local triangles.
Frame 0: splat worker 10/16 reached 512 local triangles.
Frame 0: splat worker 9/16 reached 512 local triangles.
Frame 0: splat worker 5/16 reached 1024 local triangles.
Frame 0: splat worker 12/16 reached 512 local triangles.
Frame 0: splat worker 16/16 reached 512 local triangles.
Frame 0: splat worker 13/16 reached 1024 local triangles.
Frame 0: splat worker 2/16 reached 512 local triangles.
Frame 0: splat worker 1/16 reached 1024 local triangles.
Frame 0: splat worker 6/16 reached 1024 local triangles.
Frame 0: splat worker 7/16 reached 1024 local triangles.
Frame 0: splat worker 11/16 reached 1024 local triangles.
Frame 0: splat worker 4/16 reached 1024 local triangles.
Frame 0: splat worker 3/16 reached 1024 local triangles.
Frame 0: splat worker 15/16 reached 1024 local triangles.
Frame 0: splat worker 14/16 reached 1024 local triangles.
Frame 0: splat worker 10/16 reached 1024 local triangles.
Frame 0: splat worker 9/16 reached 1024 local triangles.
Frame 0: splat worker 12/16 reached 1024 local triangles.
Frame 0: splat worker 16/16 reached 1024 local triangles.
Frame 0: splat worker 2/16 reached 1024 local triangles.
Frame 0: splat worker 8/16 reached 2048 local triangles.
Frame 0: splat worker 5/16 reached 2048 local triangles.
Frame 0: splat worker 13/16 reached 2048 local triangles.
Frame 0: splat worker 1/16 reached 2048 local triangles.
Frame 0: splat worker 6/16 reached 2048 local triangles.
Frame 0: splat worker 7/16 reached 2048 local triangles.
Frame 0: splat worker 11/16 reached 2048 local triangles.
Frame 0: splat worker 4/16 reached 2048 local triangles.
Frame 0: splat worker 15/16 reached 2048 local triangles.
Frame 0: splat worker 3/16 reached 2048 local triangles.
Frame 0: splat worker 14/16 reached 2048 local triangles.
Frame 0: splat worker 9/16 reached 2048 local triangles.
Frame 0: splat worker 10/16 reached 2048 local triangles.
Frame 0: splat worker 12/16 reached 2048 local triangles.
Frame 0: splat worker 2/16 reached 2048 local triangles.
Frame 0: splat worker 16/16 reached 2048 local triangles.
Frame 0: splat worker 8/16 reached 4096 local triangles.
Frame 0: splat worker 5/16 reached 4096 local triangles.
Frame 0: splat worker 13/16 reached 4096 local triangles.
Frame 0: splat worker 1/16 reached 4096 local triangles.
Frame 0: splat worker 6/16 reached 4096 local triangles.
Frame 0: splat worker 11/16 reached 4096 local triangles.
Frame 0: splat worker 4/16 reached 4096 local triangles.
Frame 0: splat worker 14/16 reached 4096 local triangles.
Frame 0: splat worker 15/16 reached 4096 local triangles.
Frame 0: splat worker 9/16 reached 4096 local triangles.
Frame 0: splat worker 10/16 reached 4096 local triangles.
Frame 0: splat worker 16/16 reached 4096 local triangles.
Frame 0: splat worker 2/16 reached 4096 local triangles.
Frame 0: splat worker 12/16 reached 4096 local triangles.
Frame 0: splat worker 3/16 reached 4096 local triangles.
Frame 0: splat worker 8/16 reached 8192 local triangles.
Frame 0: splat worker 13/16 reached 8192 local triangles.
Frame 0: splat worker 5/16 reached 8192 local triangles.
Frame 0: splat worker 12/16 reached 8192 local triangles.
Frame 0: splat worker 1/16 reached 8192 local triangles.
Frame 0: splat worker 6/16 reached 8192 local triangles.
Frame 0: splat worker 11/16 reached 8192 local triangles.
Frame 0: splat worker 14/16 reached 8192 local triangles.
Frame 0: splat worker 4/16 reached 8192 local triangles.
Frame 0: splat worker 9/16 reached 8192 local triangles.
Frame 0: splat worker 16/16 reached 8192 local triangles.
Frame 0: splat worker 10/16 reached 8192 local triangles.
Frame 0: splat worker 2/16 reached 8192 local triangles.
Frame 0: splat worker 7/16 reached 4096 local triangles.
Frame 0: splat worker 15/16 reached 8192 local triangles.
Frame 0: splat worker 6/16 finished after 8555 local triangles.
Frame 0: splat worker 16/16 finished after 8230 local triangles.
Frame 0: splat worker 15/16 finished after 8192 local triangles.
Frame 0: splat worker 11/16 finished after 8468 local triangles.
Frame 0: splat worker 10/16 finished after 8207 local triangles.
Frame 0: splat worker 5/16 finished after 8824 local triangles.
Frame 0: splat worker 4/16 finished after 8382 local triangles.
Frame 0: splat worker 3/16 finished after 8004 local triangles.
Frame 0: splat worker 8/16 finished after 9239 local triangles.
Frame 0: splat worker 1/16 finished after 8561 local triangles.
Frame 0: splat worker 12/16 finished after 8804 local triangles.
Frame 0: splat worker 14/16 finished after 8429 local triangles.
Frame 0: splat worker 7/16 finished after 4096 local triangles.
Frame 0: splat worker 2/16 finished after 8201 local triangles.
Frame 0: splat worker 9/16 finished after 8290 local triangles.
Frame 0: splat worker 13/16 finished after 8856 local triangles.
Frame 0: catalog splatting finished in 0.1 s; writing image...
Rendered 1965 images from 1 catalog stars to /home/wyj/Code/C/GR_4d_raytracing/local/step_bounds_4k/r100_4k_hmax2.png (ok)
PSF splats: cached 0, cached wing-clipped 0, direct fallbacks 0, discarded below min-Y 1965
Warning: --psf-min-y discarded one or more PSF events.
@@ -0,0 +1,233 @@
OMP_NUM_THREADS=16 OMP_DYNAMIC=FALSE /home/wyj/Code/C/GR_4d_raytracing/build/Release/schwarzschild_sky --integrator dp54 --width 3840 --height 2160 --look-ra-deg 262.5 --look-dec-deg -30 --fov-deg 45 --observer-radius 100 --coarse-cell-pixels 16 --refine-max-level 4 --refine-jacobian-min .2 --catalog /home/wyj/Code/C/GR_4d_raytracing/local/step_bounds_4k/single_dim_star.csv --exposure 1 --tone-map reinhard --psf-min-y 1e-20 --psf-relative-tail 1e-4 --ode-min-step 1e-12 --ode-max-step 8 --verbose --lens-map-output /home/wyj/Code/C/GR_4d_raytracing/local/step_bounds_4k/r100_4k_hmax8.grlens --output /home/wyj/Code/C/GR_4d_raytracing/local/step_bounds_4k/r100_4k_hmax8.png
Blackbody LUT: 1024 CIE 1931 2-deg 1 nm-linear XYZ nodes, T=[670.146556, 101408.88] K, loaded assets/blackbody/cie1931_2deg_xyz_1024.grbblut in 0.000041 s; payload fnv1a64=0e39867b0e70a809
Blackbody backend: lut
PSF cache ready: 64x64 phases, radius 25 px, relative tail 1e-04, tail abs 1e-06, boundary 1e-07, build 0.223 s
Frame 0: integrator=dp54 step=0.1 max_steps=65536 threshold=8 atol=(x 1e-09, Pi 1e-09, L 1e-09) rtol=1e-09 bounds=[1e-12, 8] max_rejections=32 lookback=6553.6
Frame 0: tracing 32776 initial rays from 64800 mesh triangles...
Frame 0: initial ray trace finished in 1.735 s.
Frame 0: starting adaptive ray-trace refinement (max level 4)...
Frame 0: refinement generation 0 tracing 32686 samples from 32776 vertices and 64800 triangles.
Frame 0: refinement generation 0 finished; added 3014 vertices, now 35790 vertices and 70828 triangles.
Frame 0: refinement generation 1 tracing 7900 samples from 35790 vertices and 70828 triangles.
Frame 0: refinement generation 1 finished; added 4852 vertices, now 40642 vertices and 80532 triangles.
Frame 0: refinement generation 2 tracing 13375 samples from 40642 vertices and 80532 triangles.
Frame 0: refinement generation 2 finished; added 8889 vertices, now 49531 vertices and 98310 triangles.
Frame 0: refinement generation 3 tracing 24826 samples from 49531 vertices and 98310 triangles.
Frame 0: refinement generation 3 finished; added 16055 vertices, now 65586 vertices and 130420 triangles.
Frame 0: refinement generation 4 tracing 4403 samples from 65586 vertices and 130420 triangles.
Frame 0: refinement generation 4 finished; added 389 vertices, now 65975 vertices and 131198 triangles.
Frame 0: refinement generation 5 tracing 261 samples from 65975 vertices and 131198 triangles.
Frame 0: refinement generation 5 finished; added 58 vertices, now 66033 vertices and 131314 triangles.
Frame 0: refinement generation 6 tracing 67 samples from 66033 vertices and 131314 triangles.
Frame 0: refinement generation 6 finished; added 12 vertices, now 66045 vertices and 131338 triangles.
Frame 0: refinement generation 7 tracing 14 samples from 66045 vertices and 131338 triangles.
Frame 0: refinement generation 7 finished; added 0 vertices, now 66045 vertices and 131338 triangles.
Frame 0: adaptive ray-trace refinement finished in 6.070 s; 66045 vertices, 131338 triangles.
Boundary accounting: EEE=122456 DDD=5624 EED/EDD=3258 UUD/UDD=0 U+E=0 UUU=0 error=0; approx-black=0 (0 px^2), retries=0, budget-incomplete=0.
Frame 0 trace cost: accepted=10183020 rejected=194781 rhs=91991144 over 66045 vertices (saturated=0).
Wrote lens map: /home/wyj/Code/C/GR_4d_raytracing/local/step_bounds_4k/r100_4k_hmax8.grlens (66045 vertices, 131338 triangles)
Frame 0: traced 66045 lens vertices; starting catalog render.
Frame 0: finding and prefetching catalog tiles...
Frame 0: catalog prefetch finished (0 candidate tiles).
Frame 0: splatting 131338 lens triangles...
Frame 0: splat worker 1/16 started.
Frame 0: splat worker 1/16 reached 8 local triangles.
Frame 0: splat worker 1/16 reached 16 local triangles.
Frame 0: splat worker 10/16 started.
Frame 0: splat worker 10/16 reached 8 local triangles.
Frame 0: splat worker 2/16 started.
Frame 0: splat worker 2/16 reached 8 local triangles.
Frame 0: splat worker 11/16 started.
Frame 0: splat worker 12/16 started.
Frame 0: splat worker 12/16 reached 8 local triangles.
Frame 0: splat worker 12/16 reached 16 local triangles.
Frame 0: splat worker 7/16 started.
Frame 0: splat worker 16/16 started.
Frame 0: splat worker 16/16 reached 8 local triangles.
Frame 0: splat worker 16/16 reached 16 local triangles.
Frame 0: splat worker 16/16 reached 32 local triangles.
Frame 0: splat worker 16/16 reached 64 local triangles.
Frame 0: splat worker 16/16 reached 128 local triangles.
Frame 0: splat worker 16/16 reached 256 local triangles.
Frame 0: splat worker 15/16 started.
Frame 0: splat worker 8/16 started.
Frame 0: splat worker 16/16 reached 512 local triangles.
Frame 0: splat worker 8/16 reached 8 local triangles.
Frame 0: splat worker 11/16 reached 8 local triangles.
Frame 0: splat worker 10/16 reached 16 local triangles.
Frame 0: splat worker 14/16 started.
Frame 0: splat worker 5/16 started.
Frame 0: splat worker 6/16 started.
Frame 0: splat worker 6/16 reached 8 local triangles.
Frame 0: splat worker 9/16 started.
Frame 0: splat worker 7/16 reached 8 local triangles.
Frame 0: splat worker 7/16 reached 16 local triangles.
Frame 0: splat worker 16/16 reached 1024 local triangles.
Frame 0: splat worker 4/16 started.
Frame 0: splat worker 3/16 started.
Frame 0: splat worker 8/16 reached 16 local triangles.
Frame 0: splat worker 3/16 reached 8 local triangles.
Frame 0: splat worker 10/16 reached 32 local triangles.
Frame 0: splat worker 16/16 reached 2048 local triangles.
Frame 0: splat worker 5/16 reached 8 local triangles.
Frame 0: splat worker 10/16 reached 64 local triangles.
Frame 0: splat worker 5/16 reached 16 local triangles.
Frame 0: splat worker 5/16 reached 32 local triangles.
Frame 0: splat worker 10/16 reached 128 local triangles.
Frame 0: splat worker 10/16 reached 256 local triangles.
Frame 0: splat worker 10/16 reached 512 local triangles.
Frame 0: splat worker 1/16 reached 32 local triangles.
Frame 0: splat worker 6/16 reached 16 local triangles.
Frame 0: splat worker 12/16 reached 32 local triangles.
Frame 0: splat worker 1/16 reached 64 local triangles.
Frame 0: splat worker 9/16 reached 8 local triangles.
Frame 0: splat worker 2/16 reached 16 local triangles.
Frame 0: splat worker 2/16 reached 32 local triangles.
Frame 0: splat worker 2/16 reached 64 local triangles.
Frame 0: splat worker 2/16 reached 128 local triangles.
Frame 0: splat worker 10/16 reached 1024 local triangles.
Frame 0: splat worker 2/16 reached 256 local triangles.
Frame 0: splat worker 4/16 reached 8 local triangles.
Frame 0: splat worker 11/16 reached 16 local triangles.
Frame 0: splat worker 4/16 reached 16 local triangles.
Frame 0: splat worker 11/16 reached 32 local triangles.
Frame 0: splat worker 4/16 reached 32 local triangles.
Frame 0: splat worker 14/16 reached 8 local triangles.
Frame 0: splat worker 5/16 reached 64 local triangles.
Frame 0: splat worker 14/16 reached 16 local triangles.
Frame 0: splat worker 7/16 reached 32 local triangles.
Frame 0: splat worker 12/16 reached 64 local triangles.
Frame 0: splat worker 5/16 reached 128 local triangles.
Frame 0: splat worker 7/16 reached 64 local triangles.
Frame 0: splat worker 12/16 reached 128 local triangles.
Frame 0: splat worker 7/16 reached 128 local triangles.
Frame 0: splat worker 5/16 reached 256 local triangles.
Frame 0: splat worker 13/16 started.
Frame 0: splat worker 8/16 reached 32 local triangles.
Frame 0: splat worker 3/16 reached 16 local triangles.
Frame 0: splat worker 2/16 reached 512 local triangles.
Frame 0: splat worker 8/16 reached 64 local triangles.
Frame 0: splat worker 5/16 reached 512 local triangles.
Frame 0: splat worker 8/16 reached 128 local triangles.
Frame 0: splat worker 4/16 reached 64 local triangles.
Frame 0: splat worker 10/16 reached 2048 local triangles.
Frame 0: splat worker 16/16 reached 4096 local triangles.
Frame 0: splat worker 14/16 reached 32 local triangles.
Frame 0: splat worker 9/16 reached 16 local triangles.
Frame 0: splat worker 4/16 reached 128 local triangles.
Frame 0: splat worker 14/16 reached 64 local triangles.
Frame 0: splat worker 7/16 reached 256 local triangles.
Frame 0: splat worker 12/16 reached 256 local triangles.
Frame 0: splat worker 3/16 reached 32 local triangles.
Frame 0: splat worker 11/16 reached 64 local triangles.
Frame 0: splat worker 3/16 reached 64 local triangles.
Frame 0: splat worker 8/16 reached 256 local triangles.
Frame 0: splat worker 6/16 reached 32 local triangles.
Frame 0: splat worker 4/16 reached 256 local triangles.
Frame 0: splat worker 9/16 reached 32 local triangles.
Frame 0: splat worker 11/16 reached 128 local triangles.
Frame 0: splat worker 3/16 reached 128 local triangles.
Frame 0: splat worker 15/16 reached 8 local triangles.
Frame 0: splat worker 5/16 reached 1024 local triangles.
Frame 0: splat worker 15/16 reached 16 local triangles.
Frame 0: splat worker 14/16 reached 128 local triangles.
Frame 0: splat worker 9/16 reached 64 local triangles.
Frame 0: splat worker 1/16 reached 128 local triangles.
Frame 0: splat worker 7/16 reached 512 local triangles.
Frame 0: splat worker 6/16 reached 64 local triangles.
Frame 0: splat worker 15/16 reached 32 local triangles.
Frame 0: splat worker 11/16 reached 256 local triangles.
Frame 0: splat worker 3/16 reached 256 local triangles.
Frame 0: splat worker 13/16 reached 8 local triangles.
Frame 0: splat worker 15/16 reached 64 local triangles.
Frame 0: splat worker 13/16 reached 16 local triangles.
Frame 0: splat worker 9/16 reached 128 local triangles.
Frame 0: splat worker 14/16 reached 256 local triangles.
Frame 0: splat worker 2/16 reached 1024 local triangles.
Frame 0: splat worker 13/16 reached 32 local triangles.
Frame 0: splat worker 15/16 reached 128 local triangles.
Frame 0: splat worker 6/16 reached 128 local triangles.
Frame 0: splat worker 12/16 reached 512 local triangles.
Frame 0: splat worker 13/16 reached 64 local triangles.
Frame 0: splat worker 4/16 reached 512 local triangles.
Frame 0: splat worker 8/16 reached 512 local triangles.
Frame 0: splat worker 1/16 reached 256 local triangles.
Frame 0: splat worker 9/16 reached 256 local triangles.
Frame 0: splat worker 13/16 reached 128 local triangles.
Frame 0: splat worker 15/16 reached 256 local triangles.
Frame 0: splat worker 6/16 reached 256 local triangles.
Frame 0: splat worker 3/16 reached 512 local triangles.
Frame 0: splat worker 11/16 reached 512 local triangles.
Frame 0: splat worker 14/16 reached 512 local triangles.
Frame 0: splat worker 13/16 reached 256 local triangles.
Frame 0: splat worker 1/16 reached 512 local triangles.
Frame 0: splat worker 9/16 reached 512 local triangles.
Frame 0: splat worker 7/16 reached 1024 local triangles.
Frame 0: splat worker 15/16 reached 512 local triangles.
Frame 0: splat worker 6/16 reached 512 local triangles.
Frame 0: splat worker 13/16 reached 512 local triangles.
Frame 0: splat worker 4/16 reached 1024 local triangles.
Frame 0: splat worker 8/16 reached 1024 local triangles.
Frame 0: splat worker 3/16 reached 1024 local triangles.
Frame 0: splat worker 11/16 reached 1024 local triangles.
Frame 0: splat worker 14/16 reached 1024 local triangles.
Frame 0: splat worker 12/16 reached 1024 local triangles.
Frame 0: splat worker 1/16 reached 1024 local triangles.
Frame 0: splat worker 5/16 reached 2048 local triangles.
Frame 0: splat worker 15/16 reached 1024 local triangles.
Frame 0: splat worker 9/16 reached 1024 local triangles.
Frame 0: splat worker 6/16 reached 1024 local triangles.
Frame 0: splat worker 13/16 reached 1024 local triangles.
Frame 0: splat worker 7/16 reached 2048 local triangles.
Frame 0: splat worker 2/16 reached 2048 local triangles.
Frame 0: splat worker 4/16 reached 2048 local triangles.
Frame 0: splat worker 8/16 reached 2048 local triangles.
Frame 0: splat worker 10/16 reached 4096 local triangles.
Frame 0: splat worker 3/16 reached 2048 local triangles.
Frame 0: splat worker 14/16 reached 2048 local triangles.
Frame 0: splat worker 11/16 reached 2048 local triangles.
Frame 0: splat worker 1/16 reached 2048 local triangles.
Frame 0: splat worker 15/16 reached 2048 local triangles.
Frame 0: splat worker 9/16 reached 2048 local triangles.
Frame 0: splat worker 6/16 reached 2048 local triangles.
Frame 0: splat worker 13/16 reached 2048 local triangles.
Frame 0: splat worker 12/16 reached 2048 local triangles.
Frame 0: splat worker 5/16 reached 4096 local triangles.
Frame 0: splat worker 7/16 reached 4096 local triangles.
Frame 0: splat worker 4/16 reached 4096 local triangles.
Frame 0: splat worker 8/16 reached 4096 local triangles.
Frame 0: splat worker 14/16 reached 4096 local triangles.
Frame 0: splat worker 16/16 reached 8192 local triangles.
Frame 0: splat worker 3/16 reached 4096 local triangles.
Frame 0: splat worker 11/16 reached 4096 local triangles.
Frame 0: splat worker 1/16 reached 4096 local triangles.
Frame 0: splat worker 15/16 reached 4096 local triangles.
Frame 0: splat worker 9/16 reached 4096 local triangles.
Frame 0: splat worker 6/16 reached 4096 local triangles.
Frame 0: splat worker 13/16 reached 4096 local triangles.
Frame 0: splat worker 2/16 reached 4096 local triangles.
Frame 0: splat worker 12/16 reached 4096 local triangles.
Frame 0: splat worker 10/16 reached 8192 local triangles.
Frame 0: splat worker 5/16 reached 8192 local triangles.
Frame 0: splat worker 14/16 reached 8192 local triangles.
Frame 0: splat worker 7/16 reached 8192 local triangles.
Frame 0: splat worker 10/16 finished after 10255 local triangles.
Frame 0: splat worker 13/16 finished after 7729 local triangles.
Frame 0: splat worker 14/16 finished after 8310 local triangles.
Frame 0: splat worker 3/16 finished after 8102 local triangles.
Frame 0: splat worker 12/16 finished after 5601 local triangles.
Frame 0: splat worker 2/16 finished after 6132 local triangles.
Frame 0: splat worker 7/16 finished after 8294 local triangles.
Frame 0: splat worker 6/16 finished after 7791 local triangles.
Frame 0: splat worker 5/16 finished after 8901 local triangles.
Frame 0: splat worker 4/16 finished after 8159 local triangles.
Frame 0: splat worker 1/16 finished after 7930 local triangles.
Frame 0: splat worker 16/16 finished after 12209 local triangles.
Frame 0: splat worker 15/16 finished after 7871 local triangles.
Frame 0: splat worker 9/16 finished after 7811 local triangles.
Frame 0: splat worker 11/16 finished after 8075 local triangles.
Frame 0: splat worker 8/16 finished after 8168 local triangles.
Frame 0: catalog splatting finished in 0.1 s; writing image...
Rendered 1954 images from 1 catalog stars to /home/wyj/Code/C/GR_4d_raytracing/local/step_bounds_4k/r100_4k_hmax8.png (ok)
PSF splats: cached 0, cached wing-clipped 0, direct fallbacks 0, discarded below min-Y 1954
Warning: --psf-min-y discarded one or more PSF events.
@@ -0,0 +1,13 @@
{
"binary_sha256": "72ba409cd60bc72a7f0ca16f736cab6be6d4ab3f42a11d00647eeb03cc7a7e2a",
"utc": "2026-10-05T22:19:23Z",
"source_sha256": {
"src/main.c": "6cf3f6354b52b5eb029e1f3605c403c4161bb3df5d4202a47b97409318358ff9",
"src/geodesic.c": "25f24a2e1c9ce49508fed230dfb3f6d9986e92bb4a5e6347bb2356ac0ee5fffa",
"src/frame.c": "317cf2a777be139a2cab6a1b9f00094b7834fbb0382389960526ee3281ca55bf"
},
"catalog": "one negligible synthetic point; no survey catalog",
"scope": "README R100 45-degree 3840x2160 coarse16 refine4 jacobian0.2",
"threads": 16,
"max_attempts": 2
}
@@ -0,0 +1,3 @@
r100_4k_hmax2 {"vertices": 66045, "triangles": 131338, "outcomes": {"0": 62419, "1": 3626}, "reasons": {"0": 62419, "1": 3626}, "accepted": 15768677, "rejected": 97176, "rhs": 130409139, "provenance": {"atol_x": 1e-09, "atol_Pi": 1e-09, "atol_L": 1e-09, "rtol": 1e-09, "min_step": 1e-12, "max_step": 2.0, "max_lookback_time": 6553.6, "retry_lookback_increment": 6553.6, "max_total_lookback_time": 26214.4, "integrator": 1, "initial_step": 0.1, "initial_max_steps": 65536, "threshold": 8.0, "retry_step_increment": 65536, "max_total_steps": 262144}, "accepted_percentiles": {"0": 162, "0.5": 212, "0.9": 328, "0.99": 404, "1": 520}, "rejected_percentiles": {"0": 0, "0.5": 1, "0.9": 3, "0.99": 4, "1": 5}, "rhs_percentiles": {"0": 1421, "0.5": 1799, "0.9": 2597, "0.99": 3129, "1": 3941}, "sha256": "ef7267074338192f8597b1f2a0300fd36da8e25441e064719c7ad586c481ae7a", "unique_film_positions": 66045, "duplicate_film_positions": 0, "wall_seconds": 11.389317202148959}
r100_4k_hmax8 {"vertices": 66045, "triangles": 131338, "outcomes": {"0": 62419, "1": 3626}, "reasons": {"0": 62419, "1": 3626}, "accepted": 10183020, "rejected": 194781, "rhs": 91991144, "provenance": {"atol_x": 1e-09, "atol_Pi": 1e-09, "atol_L": 1e-09, "rtol": 1e-09, "min_step": 1e-12, "max_step": 8.0, "max_lookback_time": 6553.6, "retry_lookback_increment": 6553.6, "max_total_lookback_time": 26214.4, "integrator": 1, "initial_step": 0.1, "initial_max_steps": 65536, "threshold": 8.0, "retry_step_increment": 65536, "max_total_steps": 262144}, "accepted_percentiles": {"0": 80, "0.5": 127, "0.9": 259, "0.99": 337, "1": 432}, "rejected_percentiles": {"0": 0, "0.5": 3, "0.9": 5, "0.99": 6, "1": 7}, "rhs_percentiles": {"0": 868, "0.5": 1204, "0.9": 2114, "0.99": 2646, "1": 3318}, "sha256": "b1897493b7fa5b88f86872e81d15ac876818a705fa0fefe8b1ee262da9de59bb", "unique_film_positions": 66045, "duplicate_film_positions": 0, "wall_seconds": 8.389249739935622}
comparison {"shared": 66045, "only_a": 0, "only_b": 0, "terminal_mismatches": 0, "max_sky_angle": 7.954413592847793e-07, "max_logg_difference": 3.471110204822381e-11}
@@ -0,0 +1,37 @@
camera,dir,theta,cfg,outcome,reason,end_id,stop_t,steps,rejected,rhs,nx,ny,nz,g,thr,L,L0
r30_inward,0,0.16812279429621627,ref_tol1e-12_h0.25,1,1,4294967295,-142.17574796859728,2081,0,14854,0,0,0,0,8.0000000000000195,8.0658840965825682,0.065884096582549498
r30_inward,0,0.16812279429621627,ref_tol1e-13_h0.125,1,1,4294967295,-142.17574845681202,3332,0,23611,0,0,0,0,8.0000000000000249,8.0658840965825735,0.065884096582549498
r30_inward,0,0.16812279429621627,tol1e-11_h2,1,1,4294967295,-142.17574172483768,1304,0,9415,0,0,0,0,8.0000000000000266,8.0658840965825753,0.065884096582549498
r30_inward,0,0.16812279429621627,tol1e-11_h8,1,1,4294967295,-142.17574172483768,1304,0,9415,0,0,0,0,8.0000000000000266,8.0658840965825753,0.065884096582549498
r30_inward,1,0.16812289429621627,ref_tol1e-12_h0.25,1,1,4294967295,-225.85293500418322,3746,0,26509,0,0,0,0,8.0000000000000338,8.0658840955358357,0.065884095535802364
r30_inward,1,0.16812289429621627,ref_tol1e-13_h0.125,1,1,4294967295,-238.90906331559862,6388,0,45003,0,0,0,0,8.000000000000016,8.065884095535818,0.065884095535802364
r30_inward,1,0.16812289429621627,tol1e-11_h2,1,1,4294967295,-212.59582609170599,2180,0,15547,0,0,0,0,8.0000000000000071,8.0658840955358091,0.065884095535802364
r30_inward,1,0.16812289429621627,tol1e-11_h8,1,1,4294967295,-212.59582609170599,2180,0,15547,0,0,0,0,8.0000000000000071,8.0658840955358091,0.065884095535802364
r30_inward,2,0.16812299429621627,ref_tol1e-12_h0.25,0,0,0,-370.18047830118235,2790,1,19831,-0.35873499147611138,-6.5068267720987458e-16,-0.9334394495041628,1.0350983390130453,nan,-0.038385896473527227,0.065884094489054593
r30_inward,2,0.16812299429621627,ref_tol1e-13_h0.125,0,0,0,-370.1804778092,4857,1,34300,-0.35873507984449715,-6.5068265353608199e-16,-0.93343941554284182,1.035098339013504,nan,-0.038385896473972475,0.065884094489054593
r30_inward,2,0.16812299429621627,tol1e-11_h2,0,0,0,-370.18048452857704,1299,1,9387,-0.3587338729380895,-6.5068297686427377e-16,-0.93343987937458439,1.0350983390054869,nan,-0.038385896466199748,0.065884094489054593
r30_inward,2,0.16812299429621627,tol1e-11_h8,0,0,0,-370.1804845285946,1257,1,9100,-0.35873387293809145,-6.5068297686427416e-16,-0.93343987937458361,1.035098339005424,nan,-0.038385896466138665,0.065884094489054593
r100_inward,0,0.051461896376274734,ref_tol1e-12_h0.25,1,1,4294967295,-211.027405235164,2252,0,16051,0,0,0,0,8.0000000000000266,8.0199767087115159,0.01997670871149013
r100_inward,0,0.051461896376274734,ref_tol1e-13_h0.125,1,1,4294967295,-211.02740538583069,3727,0,26376,0,0,0,0,8.0000000000000142,8.0199767087115035,0.01997670871149013
r100_inward,0,0.051461896376274734,tol1e-11_h2,1,1,4294967295,-211.02740354768991,1292,0,9331,0,0,0,0,8.0000000000000533,8.0199767087115426,0.01997670871149013
r100_inward,0,0.051461896376274734,tol1e-11_h8,1,1,4294967295,-211.02740354768991,1292,0,9331,0,0,0,0,8.0000000000000533,8.0199767087115426,0.01997670871149013
r100_inward,1,0.051461996376274736,ref_tol1e-12_h0.25,1,1,4294967295,-301.07740508539132,4045,0,28602,0,0,0,0,8.0000000000000018,8.0199767086106277,0.019976708610626077
r100_inward,1,0.051461996376274736,ref_tol1e-13_h0.125,1,1,4294967295,-313.77734009633099,6986,0,49189,0,0,0,0,8.0000000000000018,8.0199767086106277,0.019976708610626077
r100_inward,1,0.051461996376274736,tol1e-11_h2,1,1,4294967295,-288.21364790845217,2256,0,16079,0,0,0,0,8.0000000000000639,8.0199767086106899,0.019976708610626077
r100_inward,1,0.051461996376274736,tol1e-11_h8,1,1,4294967295,-288.21364790845217,2256,0,16079,0,0,0,0,8.0000000000000639,8.0199767086106899,0.019976708610626077
r100_inward,2,0.051462096376274739,ref_tol1e-12_h0.25,0,0,0,-439.31355204311825,2969,0,21077,-0.99110324423183982,-3.1326762563025509e-16,-0.13309530146899301,1.0101525445517785,nan,-0.013990814384499865,0.019976708509762027
r100_inward,2,0.051462096376274739,ref_tol1e-13_h0.125,0,0,0,-439.31355190087947,5265,0,37149,-0.99110324787406912,-3.132675617927086e-16,-0.13309527434688151,1.010152544552184,nan,-0.013990814384902493,0.019976708509762027
r100_inward,2,0.051462096376274739,tol1e-11_h2,0,0,0,-439.31355372933643,1291,1,9338,-0.99110320105936889,-3.1326838231562615e-16,-0.13309562295534913,1.010152544545182,nan,-0.013990814377948021,0.019976708509762027
r100_inward,2,0.051462096376274739,tol1e-11_h8,0,0,0,-439.31355372935258,1250,1,9051,-0.991103201059369,-3.1326838231562231e-16,-0.13309562295534744,1.0101525445451216,nan,-0.013990814377887956,0.019976708509762027
r2p1_outward,0,0.57037671003940071,ref_tol1e-12_h0.25,0,0,0,-333.45070432558816,2639,1,18774,-0.30976840652656795,0,-0.95081203942629522,4.5825756949539365,nan,-1.5261506795942201,-0.42981831573151047
r2p1_outward,0,0.57037671003940071,ref_tol1e-13_h0.125,0,0,0,-333.45070506871724,4589,2,32431,-0.30976827139480229,0,-0.95081208345133916,4.5825756949557528,nan,-1.5261506795946143,-0.42981831573151047
r2p1_outward,0,0.57037671003940071,tol1e-11_h2,0,0,0,-333.45070071486469,1210,2,8778,-0.30976906608911198,0,-0.95081182454483582,4.5825756949192931,nan,-1.5261506795866686,-0.42981831573151047
r2p1_outward,0,0.57037671003940071,tol1e-11_h8,0,0,0,-333.45070071488124,1169,2,8491,-0.30976906647032454,0,-0.95081182442063883,4.5825756949190151,nan,-1.5261506795866076,-0.42981831573151047
r2p1_outward,1,0.57037681003940066,ref_tol1e-12_h0.25,0,0,0,-414.94010328784253,4263,2,30149,0.3336576345143959,0,0.94269432104487505,4.5825756949507124,nan,-1.5261506795930104,-0.42981805651739968
r2p1_outward,1,0.57037681003940066,ref_tol1e-13_h0.125,0,0,0,-427.08073320404128,7560,1,53221,-0.91087431940825703,0,-0.41268386719442363,4.5825756949554943,nan,-1.5261506795940556,-0.42981805651739968
r2p1_outward,1,0.57037681003940066,tol1e-11_h2,0,0,0,-406.06581257014602,2120,2,15141,0.88820167266587602,0,-0.45945379383953305,4.58257569487448,nan,-1.5261506795763404,-0.42981805651739968
r2p1_outward,1,0.57037681003940066,tol1e-11_h8,0,0,0,-406.06581257016262,2078,2,14854,0.88817826517728515,0,-0.4594990416384655,4.5825756948742002,nan,-1.5261506795762789,-0.42981805651739968
r2p1_outward,2,0.57037691003940061,ref_tol1e-12_h0.25,1,1,4294967295,-109.41561097431348,1950,1,13944,0,0,0,0,8.0000000000000195,7.5701822026967047,-0.42981779730331554
r2p1_outward,2,0.57037691003940061,ref_tol1e-13_h0.125,1,1,4294967295,-109.41561022532379,3098,1,21980,0,0,0,0,8.000000000000016,7.5701822026967012,-0.42981779730331554
r2p1_outward,2,0.57037691003940061,tol1e-11_h2,1,1,4294967295,-109.41561457850582,1228,1,8890,0,0,0,0,8.0000000000000195,7.5701822026967038,-0.42981779730331554
r2p1_outward,2,0.57037691003940061,tol1e-11_h8,1,1,4294967295,-109.41561457850582,1228,1,8890,0,0,0,0,8.0000000000000195,7.5701822026967038,-0.42981779730331554
1 camera dir theta cfg outcome reason end_id stop_t steps rejected rhs nx ny nz g thr L L0
2 r30_inward 0 0.16812279429621627 ref_tol1e-12_h0.25 1 1 4294967295 -142.17574796859728 2081 0 14854 0 0 0 0 8.0000000000000195 8.0658840965825682 0.065884096582549498
3 r30_inward 0 0.16812279429621627 ref_tol1e-13_h0.125 1 1 4294967295 -142.17574845681202 3332 0 23611 0 0 0 0 8.0000000000000249 8.0658840965825735 0.065884096582549498
4 r30_inward 0 0.16812279429621627 tol1e-11_h2 1 1 4294967295 -142.17574172483768 1304 0 9415 0 0 0 0 8.0000000000000266 8.0658840965825753 0.065884096582549498
5 r30_inward 0 0.16812279429621627 tol1e-11_h8 1 1 4294967295 -142.17574172483768 1304 0 9415 0 0 0 0 8.0000000000000266 8.0658840965825753 0.065884096582549498
6 r30_inward 1 0.16812289429621627 ref_tol1e-12_h0.25 1 1 4294967295 -225.85293500418322 3746 0 26509 0 0 0 0 8.0000000000000338 8.0658840955358357 0.065884095535802364
7 r30_inward 1 0.16812289429621627 ref_tol1e-13_h0.125 1 1 4294967295 -238.90906331559862 6388 0 45003 0 0 0 0 8.000000000000016 8.065884095535818 0.065884095535802364
8 r30_inward 1 0.16812289429621627 tol1e-11_h2 1 1 4294967295 -212.59582609170599 2180 0 15547 0 0 0 0 8.0000000000000071 8.0658840955358091 0.065884095535802364
9 r30_inward 1 0.16812289429621627 tol1e-11_h8 1 1 4294967295 -212.59582609170599 2180 0 15547 0 0 0 0 8.0000000000000071 8.0658840955358091 0.065884095535802364
10 r30_inward 2 0.16812299429621627 ref_tol1e-12_h0.25 0 0 0 -370.18047830118235 2790 1 19831 -0.35873499147611138 -6.5068267720987458e-16 -0.9334394495041628 1.0350983390130453 nan -0.038385896473527227 0.065884094489054593
11 r30_inward 2 0.16812299429621627 ref_tol1e-13_h0.125 0 0 0 -370.1804778092 4857 1 34300 -0.35873507984449715 -6.5068265353608199e-16 -0.93343941554284182 1.035098339013504 nan -0.038385896473972475 0.065884094489054593
12 r30_inward 2 0.16812299429621627 tol1e-11_h2 0 0 0 -370.18048452857704 1299 1 9387 -0.3587338729380895 -6.5068297686427377e-16 -0.93343987937458439 1.0350983390054869 nan -0.038385896466199748 0.065884094489054593
13 r30_inward 2 0.16812299429621627 tol1e-11_h8 0 0 0 -370.1804845285946 1257 1 9100 -0.35873387293809145 -6.5068297686427416e-16 -0.93343987937458361 1.035098339005424 nan -0.038385896466138665 0.065884094489054593
14 r100_inward 0 0.051461896376274734 ref_tol1e-12_h0.25 1 1 4294967295 -211.027405235164 2252 0 16051 0 0 0 0 8.0000000000000266 8.0199767087115159 0.01997670871149013
15 r100_inward 0 0.051461896376274734 ref_tol1e-13_h0.125 1 1 4294967295 -211.02740538583069 3727 0 26376 0 0 0 0 8.0000000000000142 8.0199767087115035 0.01997670871149013
16 r100_inward 0 0.051461896376274734 tol1e-11_h2 1 1 4294967295 -211.02740354768991 1292 0 9331 0 0 0 0 8.0000000000000533 8.0199767087115426 0.01997670871149013
17 r100_inward 0 0.051461896376274734 tol1e-11_h8 1 1 4294967295 -211.02740354768991 1292 0 9331 0 0 0 0 8.0000000000000533 8.0199767087115426 0.01997670871149013
18 r100_inward 1 0.051461996376274736 ref_tol1e-12_h0.25 1 1 4294967295 -301.07740508539132 4045 0 28602 0 0 0 0 8.0000000000000018 8.0199767086106277 0.019976708610626077
19 r100_inward 1 0.051461996376274736 ref_tol1e-13_h0.125 1 1 4294967295 -313.77734009633099 6986 0 49189 0 0 0 0 8.0000000000000018 8.0199767086106277 0.019976708610626077
20 r100_inward 1 0.051461996376274736 tol1e-11_h2 1 1 4294967295 -288.21364790845217 2256 0 16079 0 0 0 0 8.0000000000000639 8.0199767086106899 0.019976708610626077
21 r100_inward 1 0.051461996376274736 tol1e-11_h8 1 1 4294967295 -288.21364790845217 2256 0 16079 0 0 0 0 8.0000000000000639 8.0199767086106899 0.019976708610626077
22 r100_inward 2 0.051462096376274739 ref_tol1e-12_h0.25 0 0 0 -439.31355204311825 2969 0 21077 -0.99110324423183982 -3.1326762563025509e-16 -0.13309530146899301 1.0101525445517785 nan -0.013990814384499865 0.019976708509762027
23 r100_inward 2 0.051462096376274739 ref_tol1e-13_h0.125 0 0 0 -439.31355190087947 5265 0 37149 -0.99110324787406912 -3.132675617927086e-16 -0.13309527434688151 1.010152544552184 nan -0.013990814384902493 0.019976708509762027
24 r100_inward 2 0.051462096376274739 tol1e-11_h2 0 0 0 -439.31355372933643 1291 1 9338 -0.99110320105936889 -3.1326838231562615e-16 -0.13309562295534913 1.010152544545182 nan -0.013990814377948021 0.019976708509762027
25 r100_inward 2 0.051462096376274739 tol1e-11_h8 0 0 0 -439.31355372935258 1250 1 9051 -0.991103201059369 -3.1326838231562231e-16 -0.13309562295534744 1.0101525445451216 nan -0.013990814377887956 0.019976708509762027
26 r2p1_outward 0 0.57037671003940071 ref_tol1e-12_h0.25 0 0 0 -333.45070432558816 2639 1 18774 -0.30976840652656795 0 -0.95081203942629522 4.5825756949539365 nan -1.5261506795942201 -0.42981831573151047
27 r2p1_outward 0 0.57037671003940071 ref_tol1e-13_h0.125 0 0 0 -333.45070506871724 4589 2 32431 -0.30976827139480229 0 -0.95081208345133916 4.5825756949557528 nan -1.5261506795946143 -0.42981831573151047
28 r2p1_outward 0 0.57037671003940071 tol1e-11_h2 0 0 0 -333.45070071486469 1210 2 8778 -0.30976906608911198 0 -0.95081182454483582 4.5825756949192931 nan -1.5261506795866686 -0.42981831573151047
29 r2p1_outward 0 0.57037671003940071 tol1e-11_h8 0 0 0 -333.45070071488124 1169 2 8491 -0.30976906647032454 0 -0.95081182442063883 4.5825756949190151 nan -1.5261506795866076 -0.42981831573151047
30 r2p1_outward 1 0.57037681003940066 ref_tol1e-12_h0.25 0 0 0 -414.94010328784253 4263 2 30149 0.3336576345143959 0 0.94269432104487505 4.5825756949507124 nan -1.5261506795930104 -0.42981805651739968
31 r2p1_outward 1 0.57037681003940066 ref_tol1e-13_h0.125 0 0 0 -427.08073320404128 7560 1 53221 -0.91087431940825703 0 -0.41268386719442363 4.5825756949554943 nan -1.5261506795940556 -0.42981805651739968
32 r2p1_outward 1 0.57037681003940066 tol1e-11_h2 0 0 0 -406.06581257014602 2120 2 15141 0.88820167266587602 0 -0.45945379383953305 4.58257569487448 nan -1.5261506795763404 -0.42981805651739968
33 r2p1_outward 1 0.57037681003940066 tol1e-11_h8 0 0 0 -406.06581257016262 2078 2 14854 0.88817826517728515 0 -0.4594990416384655 4.5825756948742002 nan -1.5261506795762789 -0.42981805651739968
34 r2p1_outward 2 0.57037691003940061 ref_tol1e-12_h0.25 1 1 4294967295 -109.41561097431348 1950 1 13944 0 0 0 0 8.0000000000000195 7.5701822026967047 -0.42981779730331554
35 r2p1_outward 2 0.57037691003940061 ref_tol1e-13_h0.125 1 1 4294967295 -109.41561022532379 3098 1 21980 0 0 0 0 8.000000000000016 7.5701822026967012 -0.42981779730331554
36 r2p1_outward 2 0.57037691003940061 tol1e-11_h2 1 1 4294967295 -109.41561457850582 1228 1 8890 0 0 0 0 8.0000000000000195 7.5701822026967038 -0.42981779730331554
37 r2p1_outward 2 0.57037691003940061 tol1e-11_h8 1 1 4294967295 -109.41561457850582 1228 1 8890 0 0 0 0 8.0000000000000195 7.5701822026967038 -0.42981779730331554
@@ -0,0 +1,96 @@
### building critical_ref_check
+ cc -std=c11 -O2 -Wall -Wextra -Wpedantic -I/home/wyj/Code/C/GR_4d_raytracing/src /home/wyj/Code/C/GR_4d_raytracing/benchmarks/adaptive_step_bounds_2026-10-05/critical_ref_check.c /home/wyj/Code/C/GR_4d_raytracing/src/geodesic.c /home/wyj/Code/C/GR_4d_raytracing/src/asymptotic.c /home/wyj/Code/C/GR_4d_raytracing/src/asymptotic_schwarzschild.c /home/wyj/Code/C/GR_4d_raytracing/src/spacetime_common.c /home/wyj/Code/C/GR_4d_raytracing/src/observer.c -lm -o /tmp/opencode/step_bounds/critical_ref_check
+ echo '### command: critical_ref_check'
### command: critical_ref_check
+ /tmp/opencode/step_bounds/critical_ref_check
RAW camera=r30_inward dir=0 theta=0.16812279429621627 cfg=ref_tol1e-12_h0.25 outcome=1 reason=1 end=4294967295 stop_t=-142.17574796859728 steps=2081 rejected=0 rhs=14854 g=0 thr=8.0000000000000195 L=8.0658840965825682 L0=0.065884096582549498 nx=0 ny=0 nz=0
RAW camera=r30_inward dir=0 theta=0.16812279429621627 cfg=ref_tol1e-13_h0.125 outcome=1 reason=1 end=4294967295 stop_t=-142.17574845681202 steps=3332 rejected=0 rhs=23611 g=0 thr=8.0000000000000249 L=8.0658840965825735 L0=0.065884096582549498 nx=0 ny=0 nz=0
RAW camera=r30_inward dir=0 theta=0.16812279429621627 cfg=tol1e-11_h2 outcome=1 reason=1 end=4294967295 stop_t=-142.17574172483768 steps=1304 rejected=0 rhs=9415 g=0 thr=8.0000000000000266 L=8.0658840965825753 L0=0.065884096582549498 nx=0 ny=0 nz=0
RAW camera=r30_inward dir=0 theta=0.16812279429621627 cfg=tol1e-11_h8 outcome=1 reason=1 end=4294967295 stop_t=-142.17574172483768 steps=1304 rejected=0 rhs=9415 g=0 thr=8.0000000000000266 L=8.0658840965825753 L0=0.065884096582549498 nx=0 ny=0 nz=0
RAW camera=r30_inward dir=1 theta=0.16812289429621627 cfg=ref_tol1e-12_h0.25 outcome=1 reason=1 end=4294967295 stop_t=-225.85293500418322 steps=3746 rejected=0 rhs=26509 g=0 thr=8.0000000000000338 L=8.0658840955358357 L0=0.065884095535802364 nx=0 ny=0 nz=0
RAW camera=r30_inward dir=1 theta=0.16812289429621627 cfg=ref_tol1e-13_h0.125 outcome=1 reason=1 end=4294967295 stop_t=-238.90906331559862 steps=6388 rejected=0 rhs=45003 g=0 thr=8.000000000000016 L=8.065884095535818 L0=0.065884095535802364 nx=0 ny=0 nz=0
RAW camera=r30_inward dir=1 theta=0.16812289429621627 cfg=tol1e-11_h2 outcome=1 reason=1 end=4294967295 stop_t=-212.59582609170599 steps=2180 rejected=0 rhs=15547 g=0 thr=8.0000000000000071 L=8.0658840955358091 L0=0.065884095535802364 nx=0 ny=0 nz=0
RAW camera=r30_inward dir=1 theta=0.16812289429621627 cfg=tol1e-11_h8 outcome=1 reason=1 end=4294967295 stop_t=-212.59582609170599 steps=2180 rejected=0 rhs=15547 g=0 thr=8.0000000000000071 L=8.0658840955358091 L0=0.065884095535802364 nx=0 ny=0 nz=0
RAW camera=r30_inward dir=2 theta=0.16812299429621627 cfg=ref_tol1e-12_h0.25 outcome=0 reason=0 end=0 stop_t=-370.18047830118235 steps=2790 rejected=1 rhs=19831 g=1.0350983390130453 thr=nan L=-0.038385896473527227 L0=0.065884094489054593 nx=-0.35873499147611138 ny=-6.5068267720987458e-16 nz=-0.9334394495041628
RAW camera=r30_inward dir=2 theta=0.16812299429621627 cfg=ref_tol1e-13_h0.125 outcome=0 reason=0 end=0 stop_t=-370.1804778092 steps=4857 rejected=1 rhs=34300 g=1.035098339013504 thr=nan L=-0.038385896473972475 L0=0.065884094489054593 nx=-0.35873507984449715 ny=-6.5068265353608199e-16 nz=-0.93343941554284182
RAW camera=r30_inward dir=2 theta=0.16812299429621627 cfg=tol1e-11_h2 outcome=0 reason=0 end=0 stop_t=-370.18048452857704 steps=1299 rejected=1 rhs=9387 g=1.0350983390054869 thr=nan L=-0.038385896466199748 L0=0.065884094489054593 nx=-0.3587338729380895 ny=-6.5068297686427377e-16 nz=-0.93343987937458439
RAW camera=r30_inward dir=2 theta=0.16812299429621627 cfg=tol1e-11_h8 outcome=0 reason=0 end=0 stop_t=-370.1804845285946 steps=1257 rejected=1 rhs=9100 g=1.035098339005424 thr=nan L=-0.038385896466138665 L0=0.065884094489054593 nx=-0.35873387293809145 ny=-6.5068297686427416e-16 nz=-0.93343987937458361
RAW camera=r100_inward dir=0 theta=0.051461896376274734 cfg=ref_tol1e-12_h0.25 outcome=1 reason=1 end=4294967295 stop_t=-211.027405235164 steps=2252 rejected=0 rhs=16051 g=0 thr=8.0000000000000266 L=8.0199767087115159 L0=0.01997670871149013 nx=0 ny=0 nz=0
RAW camera=r100_inward dir=0 theta=0.051461896376274734 cfg=ref_tol1e-13_h0.125 outcome=1 reason=1 end=4294967295 stop_t=-211.02740538583069 steps=3727 rejected=0 rhs=26376 g=0 thr=8.0000000000000142 L=8.0199767087115035 L0=0.01997670871149013 nx=0 ny=0 nz=0
RAW camera=r100_inward dir=0 theta=0.051461896376274734 cfg=tol1e-11_h2 outcome=1 reason=1 end=4294967295 stop_t=-211.02740354768991 steps=1292 rejected=0 rhs=9331 g=0 thr=8.0000000000000533 L=8.0199767087115426 L0=0.01997670871149013 nx=0 ny=0 nz=0
RAW camera=r100_inward dir=0 theta=0.051461896376274734 cfg=tol1e-11_h8 outcome=1 reason=1 end=4294967295 stop_t=-211.02740354768991 steps=1292 rejected=0 rhs=9331 g=0 thr=8.0000000000000533 L=8.0199767087115426 L0=0.01997670871149013 nx=0 ny=0 nz=0
RAW camera=r100_inward dir=1 theta=0.051461996376274736 cfg=ref_tol1e-12_h0.25 outcome=1 reason=1 end=4294967295 stop_t=-301.07740508539132 steps=4045 rejected=0 rhs=28602 g=0 thr=8.0000000000000018 L=8.0199767086106277 L0=0.019976708610626077 nx=0 ny=0 nz=0
RAW camera=r100_inward dir=1 theta=0.051461996376274736 cfg=ref_tol1e-13_h0.125 outcome=1 reason=1 end=4294967295 stop_t=-313.77734009633099 steps=6986 rejected=0 rhs=49189 g=0 thr=8.0000000000000018 L=8.0199767086106277 L0=0.019976708610626077 nx=0 ny=0 nz=0
RAW camera=r100_inward dir=1 theta=0.051461996376274736 cfg=tol1e-11_h2 outcome=1 reason=1 end=4294967295 stop_t=-288.21364790845217 steps=2256 rejected=0 rhs=16079 g=0 thr=8.0000000000000639 L=8.0199767086106899 L0=0.019976708610626077 nx=0 ny=0 nz=0
RAW camera=r100_inward dir=1 theta=0.051461996376274736 cfg=tol1e-11_h8 outcome=1 reason=1 end=4294967295 stop_t=-288.21364790845217 steps=2256 rejected=0 rhs=16079 g=0 thr=8.0000000000000639 L=8.0199767086106899 L0=0.019976708610626077 nx=0 ny=0 nz=0
RAW camera=r100_inward dir=2 theta=0.051462096376274739 cfg=ref_tol1e-12_h0.25 outcome=0 reason=0 end=0 stop_t=-439.31355204311825 steps=2969 rejected=0 rhs=21077 g=1.0101525445517785 thr=nan L=-0.013990814384499865 L0=0.019976708509762027 nx=-0.99110324423183982 ny=-3.1326762563025509e-16 nz=-0.13309530146899301
RAW camera=r100_inward dir=2 theta=0.051462096376274739 cfg=ref_tol1e-13_h0.125 outcome=0 reason=0 end=0 stop_t=-439.31355190087947 steps=5265 rejected=0 rhs=37149 g=1.010152544552184 thr=nan L=-0.013990814384902493 L0=0.019976708509762027 nx=-0.99110324787406912 ny=-3.132675617927086e-16 nz=-0.13309527434688151
RAW camera=r100_inward dir=2 theta=0.051462096376274739 cfg=tol1e-11_h2 outcome=0 reason=0 end=0 stop_t=-439.31355372933643 steps=1291 rejected=1 rhs=9338 g=1.010152544545182 thr=nan L=-0.013990814377948021 L0=0.019976708509762027 nx=-0.99110320105936889 ny=-3.1326838231562615e-16 nz=-0.13309562295534913
RAW camera=r100_inward dir=2 theta=0.051462096376274739 cfg=tol1e-11_h8 outcome=0 reason=0 end=0 stop_t=-439.31355372935258 steps=1250 rejected=1 rhs=9051 g=1.0101525445451216 thr=nan L=-0.013990814377887956 L0=0.019976708509762027 nx=-0.991103201059369 ny=-3.1326838231562231e-16 nz=-0.13309562295534744
RAW camera=r2p1_outward dir=0 theta=0.57037671003940071 cfg=ref_tol1e-12_h0.25 outcome=0 reason=0 end=0 stop_t=-333.45070432558816 steps=2639 rejected=1 rhs=18774 g=4.5825756949539365 thr=nan L=-1.5261506795942201 L0=-0.42981831573151047 nx=-0.30976840652656795 ny=0 nz=-0.95081203942629522
RAW camera=r2p1_outward dir=0 theta=0.57037671003940071 cfg=ref_tol1e-13_h0.125 outcome=0 reason=0 end=0 stop_t=-333.45070506871724 steps=4589 rejected=2 rhs=32431 g=4.5825756949557528 thr=nan L=-1.5261506795946143 L0=-0.42981831573151047 nx=-0.30976827139480229 ny=0 nz=-0.95081208345133916
RAW camera=r2p1_outward dir=0 theta=0.57037671003940071 cfg=tol1e-11_h2 outcome=0 reason=0 end=0 stop_t=-333.45070071486469 steps=1210 rejected=2 rhs=8778 g=4.5825756949192931 thr=nan L=-1.5261506795866686 L0=-0.42981831573151047 nx=-0.30976906608911198 ny=0 nz=-0.95081182454483582
RAW camera=r2p1_outward dir=0 theta=0.57037671003940071 cfg=tol1e-11_h8 outcome=0 reason=0 end=0 stop_t=-333.45070071488124 steps=1169 rejected=2 rhs=8491 g=4.5825756949190151 thr=nan L=-1.5261506795866076 L0=-0.42981831573151047 nx=-0.30976906647032454 ny=0 nz=-0.95081182442063883
RAW camera=r2p1_outward dir=1 theta=0.57037681003940066 cfg=ref_tol1e-12_h0.25 outcome=0 reason=0 end=0 stop_t=-414.94010328784253 steps=4263 rejected=2 rhs=30149 g=4.5825756949507124 thr=nan L=-1.5261506795930104 L0=-0.42981805651739968 nx=0.3336576345143959 ny=0 nz=0.94269432104487505
RAW camera=r2p1_outward dir=1 theta=0.57037681003940066 cfg=ref_tol1e-13_h0.125 outcome=0 reason=0 end=0 stop_t=-427.08073320404128 steps=7560 rejected=1 rhs=53221 g=4.5825756949554943 thr=nan L=-1.5261506795940556 L0=-0.42981805651739968 nx=-0.91087431940825703 ny=0 nz=-0.41268386719442363
RAW camera=r2p1_outward dir=1 theta=0.57037681003940066 cfg=tol1e-11_h2 outcome=0 reason=0 end=0 stop_t=-406.06581257014602 steps=2120 rejected=2 rhs=15141 g=4.58257569487448 thr=nan L=-1.5261506795763404 L0=-0.42981805651739968 nx=0.88820167266587602 ny=0 nz=-0.45945379383953305
RAW camera=r2p1_outward dir=1 theta=0.57037681003940066 cfg=tol1e-11_h8 outcome=0 reason=0 end=0 stop_t=-406.06581257016262 steps=2078 rejected=2 rhs=14854 g=4.5825756948742002 thr=nan L=-1.5261506795762789 L0=-0.42981805651739968 nx=0.88817826517728515 ny=0 nz=-0.4594990416384655
RAW camera=r2p1_outward dir=2 theta=0.57037691003940061 cfg=ref_tol1e-12_h0.25 outcome=1 reason=1 end=4294967295 stop_t=-109.41561097431348 steps=1950 rejected=1 rhs=13944 g=0 thr=8.0000000000000195 L=7.5701822026967047 L0=-0.42981779730331554 nx=0 ny=0 nz=0
RAW camera=r2p1_outward dir=2 theta=0.57037691003940061 cfg=ref_tol1e-13_h0.125 outcome=1 reason=1 end=4294967295 stop_t=-109.41561022532379 steps=3098 rejected=1 rhs=21980 g=0 thr=8.000000000000016 L=7.5701822026967012 L0=-0.42981779730331554 nx=0 ny=0 nz=0
RAW camera=r2p1_outward dir=2 theta=0.57037691003940061 cfg=tol1e-11_h2 outcome=1 reason=1 end=4294967295 stop_t=-109.41561457850582 steps=1228 rejected=1 rhs=8890 g=0 thr=8.0000000000000195 L=7.5701822026967038 L0=-0.42981779730331554 nx=0 ny=0 nz=0
RAW camera=r2p1_outward dir=2 theta=0.57037691003940061 cfg=tol1e-11_h8 outcome=1 reason=1 end=4294967295 stop_t=-109.41561457850582 steps=1228 rejected=1 rhs=8890 g=0 thr=8.0000000000000195 L=7.5701822026967038 L0=-0.42981779730331554 nx=0 ny=0 nz=0
CAPAIR camera=r30_inward dir=0 theta=0.16812279429621627 pair=ref_vs_tighter class=DARK/DARK same=1 dn_ang=nan dgrel=nan dstop_t=4.8821473797033832e-07 dthr=-5.3290705182007514e-15 end=4294967295/4294967295
CAPAIR camera=r30_inward dir=0 theta=0.16812279429621627 pair=h2_vs_h8 class=DARK/DARK same=1 dn_ang=nan dgrel=nan dstop_t=0 dthr=0 end=4294967295/4294967295
CAPAIR camera=r30_inward dir=0 theta=0.16812279429621627 pair=ref_vs_h2 class=DARK/DARK same=1 dn_ang=nan dgrel=nan dstop_t=-6.2437596000108897e-06 dthr=-7.1054273576010019e-15 end=4294967295/4294967295
CAPAIR camera=r30_inward dir=0 theta=0.16812279429621627 pair=ref_vs_h8 class=DARK/DARK same=1 dn_ang=nan dgrel=nan dstop_t=-6.2437596000108897e-06 dthr=-7.1054273576010019e-15 end=4294967295/4294967295
CAPAIR camera=r30_inward dir=1 theta=0.16812289429621627 pair=ref_vs_tighter class=DARK/DARK same=1 dn_ang=nan dgrel=nan dstop_t=13.056128311415392 dthr=1.7763568394002505e-14 end=4294967295/4294967295
CAPAIR camera=r30_inward dir=1 theta=0.16812289429621627 pair=h2_vs_h8 class=DARK/DARK same=1 dn_ang=nan dgrel=nan dstop_t=0 dthr=0 end=4294967295/4294967295
CAPAIR camera=r30_inward dir=1 theta=0.16812289429621627 pair=ref_vs_h2 class=DARK/DARK same=1 dn_ang=nan dgrel=nan dstop_t=-13.257108912477236 dthr=2.6645352591003757e-14 end=4294967295/4294967295
CAPAIR camera=r30_inward dir=1 theta=0.16812289429621627 pair=ref_vs_h8 class=DARK/DARK same=1 dn_ang=nan dgrel=nan dstop_t=-13.257108912477236 dthr=2.6645352591003757e-14 end=4294967295/4294967295
CAPAIR camera=r30_inward dir=2 theta=0.16812299429621627 pair=ref_vs_tighter class=ESC/ESC same=1 dn_ang=9.4669651562462453e-08 dgrel=4.4320103143036249e-13 dstop_t=-4.9198234819414211e-07 dthr=nan end=0/0
CAPAIR camera=r30_inward dir=2 theta=0.16812299429621627 pair=h2_vs_h8 class=ESC/ESC same=1 dn_ang=2.0539125955565396e-15 dgrel=6.0618177144533547e-14 dstop_t=1.7564616427989677e-11 dthr=nan end=0/0
CAPAIR camera=r30_inward dir=2 theta=0.16812299429621627 pair=ref_vs_h2 class=ESC/ESC same=1 dn_ang=1.198297077398556e-06 dgrel=7.3021588775645796e-12 dstop_t=6.2273946923596668e-06 dthr=nan end=0/0
CAPAIR camera=r30_inward dir=2 theta=0.16812299429621627 pair=ref_vs_h8 class=ESC/ESC same=1 dn_ang=1.1982970752891323e-06 dgrel=7.3627770547091131e-12 dstop_t=6.2274122569760948e-06 dthr=nan end=0/0
CAPAIR camera=r100_inward dir=0 theta=0.051461896376274734 pair=ref_vs_tighter class=DARK/DARK same=1 dn_ang=nan dgrel=nan dstop_t=1.5066669334373728e-07 dthr=1.2434497875801753e-14 end=4294967295/4294967295
CAPAIR camera=r100_inward dir=0 theta=0.051461896376274734 pair=h2_vs_h8 class=DARK/DARK same=1 dn_ang=nan dgrel=nan dstop_t=0 dthr=0 end=4294967295/4294967295
CAPAIR camera=r100_inward dir=0 theta=0.051461896376274734 pair=ref_vs_h2 class=DARK/DARK same=1 dn_ang=nan dgrel=nan dstop_t=-1.6874740822458989e-06 dthr=-2.6645352591003757e-14 end=4294967295/4294967295
CAPAIR camera=r100_inward dir=0 theta=0.051461896376274734 pair=ref_vs_h8 class=DARK/DARK same=1 dn_ang=nan dgrel=nan dstop_t=-1.6874740822458989e-06 dthr=-2.6645352591003757e-14 end=4294967295/4294967295
CAPAIR camera=r100_inward dir=1 theta=0.051461996376274736 pair=ref_vs_tighter class=DARK/DARK same=1 dn_ang=nan dgrel=nan dstop_t=12.699935010939669 dthr=0 end=4294967295/4294967295
CAPAIR camera=r100_inward dir=1 theta=0.051461996376274736 pair=h2_vs_h8 class=DARK/DARK same=1 dn_ang=nan dgrel=nan dstop_t=0 dthr=0 end=4294967295/4294967295
CAPAIR camera=r100_inward dir=1 theta=0.051461996376274736 pair=ref_vs_h2 class=DARK/DARK same=1 dn_ang=nan dgrel=nan dstop_t=-12.863757176939146 dthr=-6.2172489379008766e-14 end=4294967295/4294967295
CAPAIR camera=r100_inward dir=1 theta=0.051461996376274736 pair=ref_vs_h8 class=DARK/DARK same=1 dn_ang=nan dgrel=nan dstop_t=-12.863757176939146 dthr=-6.2172489379008766e-14 end=4294967295/4294967295
CAPAIR camera=r100_inward dir=2 theta=0.051462096376274739 pair=ref_vs_tighter class=ESC/ESC same=1 dn_ang=2.7365576305005138e-08 dgrel=4.0134562340199409e-13 dstop_t=-1.4223877542463015e-07 dthr=nan end=0/0
CAPAIR camera=r100_inward dir=2 theta=0.051462096376274739 pair=h2_vs_h8 class=ESC/ESC same=1 dn_ang=1.6930901125533633e-15 dgrel=5.9729998724833422e-14 dstop_t=1.6143530956469476e-11 dthr=nan end=0/0
CAPAIR camera=r100_inward dir=2 theta=0.051462096376274739 pair=ref_vs_h2 class=ESC/ESC same=1 dn_ang=3.243722235535319e-07 dgrel=6.5301097862402457e-12 dstop_t=1.6862181837495882e-06 dthr=nan end=0/0
CAPAIR camera=r100_inward dir=2 theta=0.051462096376274739 pair=ref_vs_h8 class=ESC/ESC same=1 dn_ang=3.2437222186044184e-07 dgrel=6.5900618295700042e-12 dstop_t=1.6862343272805447e-06 dthr=nan end=0/0
CAPAIR camera=r2p1_outward dir=0 theta=0.57037671003940071 pair=ref_vs_tighter class=ESC/ESC same=1 dn_ang=1.4212247739786624e-07 dgrel=3.9634961979118088e-13 dstop_t=7.4312907827334129e-07 dthr=nan end=0/0
CAPAIR camera=r2p1_outward dir=0 theta=0.57037671003940071 pair=h2_vs_h8 class=ESC/ESC same=1 dn_ang=4.0093378617100928e-10 dgrel=6.0618177144533547e-14 dstop_t=1.6541434888495132e-11 dthr=nan end=0/0
CAPAIR camera=r2p1_outward dir=0 theta=0.57037671003940071 pair=ref_vs_h2 class=ESC/ESC same=1 dn_ang=6.9368349486792499e-07 dgrel=7.5597306192776159e-12 dstop_t=-3.6107234677729139e-06 dthr=nan end=0/0
CAPAIR camera=r2p1_outward dir=0 theta=0.57037671003940071 pair=ref_vs_h8 class=ESC/ESC same=1 dn_ang=6.9408442870960736e-07 dgrel=7.6205708410270745e-12 dstop_t=-3.6107069263380254e-06 dthr=nan end=0/0
CAPAIR camera=r2p1_outward dir=1 theta=0.57037681003940066 pair=ref_vs_tighter class=ESC/ESC same=1 dn_ang=2.3363758065620761 dgrel=1.0434986208451846e-12 dstop_t=12.140629916198748 dthr=nan end=0/0
CAPAIR camera=r2p1_outward dir=1 theta=0.57037681003940066 pair=h2_vs_h8 class=ESC/ESC same=1 dn_ang=5.0943830155578499e-05 dgrel=6.106226635438361e-14 dstop_t=1.659827830735594e-11 dthr=nan end=0/0
CAPAIR camera=r2p1_outward dir=1 theta=0.57037681003940066 pair=ref_vs_h2 class=ESC/ESC same=1 dn_ang=1.7079955663413957 dgrel=1.6635359756378421e-11 dstop_t=-8.8742907176965105 dthr=nan end=0/0
CAPAIR camera=r2p1_outward dir=1 theta=0.57037681003940066 pair=ref_vs_h8 class=ESC/ESC same=1 dn_ang=1.7080465101715514 dgrel=1.6696422022732804e-11 dstop_t=-8.8742907176799122 dthr=nan end=0/0
CAPAIR camera=r2p1_outward dir=2 theta=0.57037691003940061 pair=ref_vs_tighter class=DARK/DARK same=1 dn_ang=nan dgrel=nan dstop_t=-7.4898969160130946e-07 dthr=3.5527136788005009e-15 end=4294967295/4294967295
CAPAIR camera=r2p1_outward dir=2 theta=0.57037691003940061 pair=h2_vs_h8 class=DARK/DARK same=1 dn_ang=nan dgrel=nan dstop_t=0 dthr=0 end=4294967295/4294967295
CAPAIR camera=r2p1_outward dir=2 theta=0.57037691003940061 pair=ref_vs_h2 class=DARK/DARK same=1 dn_ang=nan dgrel=nan dstop_t=3.6041923436869183e-06 dthr=0 end=4294967295/4294967295
CAPAIR camera=r2p1_outward dir=2 theta=0.57037691003940061 pair=ref_vs_h8 class=DARK/DARK same=1 dn_ang=nan dgrel=nan dstop_t=3.6041923436869183e-06 dthr=0 end=4294967295/4294967295
EXACT_CRITICAL_DIRECTION camera=r30_inward theta=0.16812289429621627 ref0=DARK/1/4294967295 ref1=DARK/1/4294967295 same=1 dn_ang=nan dstop_t=13.056128311415392
EXACT_CRITICAL_REFERENCE_NOT_CONVERGED camera=r30_inward dn_ang=nan dstop_t=13.056128311415392 class_same=1
EXACT_CRITICAL_DIRECTION camera=r100_inward theta=0.051461996376274736 ref0=DARK/1/4294967295 ref1=DARK/1/4294967295 same=1 dn_ang=nan dstop_t=12.699935010939669
EXACT_CRITICAL_REFERENCE_NOT_CONVERGED camera=r100_inward dn_ang=nan dstop_t=12.699935010939669 class_same=1
EXACT_CRITICAL_DIRECTION camera=r2p1_outward theta=0.57037681003940066 ref0=ESC/0/0 ref1=ESC/0/0 same=1 dn_ang=2.3363758065620761 dstop_t=12.140629916198748
EXACT_CRITICAL_REFERENCE_NOT_CONVERGED camera=r2p1_outward dn_ang=2.3363758065620761 dstop_t=12.140629916198748 class_same=1
NEIGHBOR_REFERENCE_CHECK camera=r30_inward dir=0 theta=0.16812279429621627 ref_class0=DARK/1/4294967295 ref_class1=DARK/1/4294967295 same=1 clean=1 stepped=1 dn_ang=nan dgrel=nan dstop_t=4.8821473797033832e-07
NEIGHBOR_REFERENCE_CHECK camera=r30_inward dir=2 theta=0.16812299429621627 ref_class0=ESC/0/0 ref_class1=ESC/0/0 same=1 clean=1 stepped=1 dn_ang=9.4669651562462453e-08 dgrel=4.4320103143036249e-13 dstop_t=-4.9198234819414211e-07
NEIGHBOR_REFERENCE_CHECK camera=r100_inward dir=0 theta=0.051461896376274734 ref_class0=DARK/1/4294967295 ref_class1=DARK/1/4294967295 same=1 clean=1 stepped=1 dn_ang=nan dgrel=nan dstop_t=1.5066669334373728e-07
NEIGHBOR_REFERENCE_CHECK camera=r100_inward dir=2 theta=0.051462096376274739 ref_class0=ESC/0/0 ref_class1=ESC/0/0 same=1 clean=1 stepped=1 dn_ang=2.7365576305005138e-08 dgrel=4.0134562340199409e-13 dstop_t=-1.4223877542463015e-07
NEIGHBOR_REFERENCE_CHECK camera=r2p1_outward dir=0 theta=0.57037671003940071 ref_class0=ESC/0/0 ref_class1=ESC/0/0 same=1 clean=1 stepped=1 dn_ang=1.4212247739786624e-07 dgrel=3.9634961979118088e-13 dstop_t=7.4312907827334129e-07
NEIGHBOR_REFERENCE_CHECK camera=r2p1_outward dir=2 theta=0.57037691003940061 ref_class0=DARK/1/4294967295 ref_class1=DARK/1/4294967295 same=1 clean=1 stepped=1 dn_ang=nan dgrel=nan dstop_t=-7.4898969160130946e-07
CRITICAL_REFERENCE_CHECK status=0
CRITICAL_REF_DONE
real 0m0.418s
user 0m0.402s
sys 0m0.016s
+ set +x
@@ -0,0 +1,271 @@
### command: a_public schwarzschild
+ /tmp/opencode/step_bounds/a_public schwarzschild
REF r30_inward dir=0 theta=0 outcome=1 reason=1 stop_t=-69.5728394 steps=531 rejected=0 rhs=4004 g=0 thr=8
REF r30_inward dir=1 theta=3.14159 outcome=0 reason=0 stop_t=-226 steps=905 rejected=0 rhs=6629 g=1.03509833901 thr=nan
REF r30_inward dir=2 theta=1.5708 outcome=0 reason=0 stop_t=-256.70592 steps=1028 rejected=0 rhs=7490 g=1.03509833901 thr=nan
REF r30_inward dir=3 theta=0.168123 outcome=1 reason=1 stop_t=-225.852935 steps=3746 rejected=0 rhs=26509 g=0 thr=8
REF r30_inward dir=4 theta=0.167123 outcome=1 reason=1 stop_t=-94.2779103 steps=1127 rejected=0 rhs=8176 g=0 thr=8
REF r30_inward dir=5 theta=0.169123 outcome=0 reason=0 stop_t=-322.366447 steps=1834 rejected=0 rhs=13132 g=1.03509833901 thr=nan
REF r30_inward dir=6 theta=0.168113 outcome=1 reason=1 stop_t=-118.245807 steps=1606 rejected=0 rhs=11529 g=0 thr=8
REF r30_inward dir=7 theta=0.168133 outcome=0 reason=0 stop_t=-346.252134 steps=2312 rejected=1 rhs=16485 g=1.03509833901 thr=nan
REF r30_inward dir=8 theta=0.168123 outcome=1 reason=1 stop_t=-142.175748 steps=2081 rejected=0 rhs=14854 g=0 thr=8
REF r30_inward dir=9 theta=0.168123 outcome=0 reason=0 stop_t=-370.180478 steps=2790 rejected=1 rhs=19831 g=1.03509833901 thr=nan
REF r30_inward dir=10 theta=0.218123 outcome=0 reason=0 stop_t=-302.584521 steps=1409 rejected=1 rhs=10164 g=1.03509833901 thr=nan
REF r30_inward dir=11 theta=0.118123 outcome=1 reason=1 stop_t=-73.7563113 steps=665 rejected=0 rhs=4942 g=0 thr=8
REF r100_inward dir=0 theta=0 outcome=1 reason=1 stop_t=-144.299101 steps=830 rejected=0 rhs=6097 g=0 thr=8
REF r100_inward dir=1 theta=3.14159 outcome=0 reason=0 stop_t=-156 steps=625 rejected=0 rhs=4669 g=1.01015254455 thr=nan
REF r100_inward dir=2 theta=1.5708 outcome=0 reason=0 stop_t=-237.678408 steps=952 rejected=0 rhs=6958 g=1.01015254455 thr=nan
REF r100_inward dir=3 theta=0.051462 outcome=1 reason=1 stop_t=-301.077405 steps=4045 rejected=0 rhs=28602 g=0 thr=8
REF r100_inward dir=4 theta=0.050462 outcome=1 reason=1 stop_t=-163.061409 steps=1292 rejected=0 rhs=9331 g=0 thr=8
REF r100_inward dir=5 theta=0.052462 outcome=0 reason=0 stop_t=-391.57927 steps=2014 rejected=1 rhs=14399 g=1.01015254455 thr=nan
REF r100_inward dir=6 theta=0.051452 outcome=1 reason=1 stop_t=-187.095903 steps=1773 rejected=0 rhs=12698 g=0 thr=8
REF r100_inward dir=7 theta=0.051472 outcome=0 reason=0 stop_t=-415.386875 steps=2493 rejected=0 rhs=17745 g=1.01015254455 thr=nan
REF r100_inward dir=8 theta=0.0514619 outcome=1 reason=1 stop_t=-211.027405 steps=2252 rejected=0 rhs=16051 g=0 thr=8
REF r100_inward dir=9 theta=0.0514621 outcome=0 reason=0 stop_t=-439.313552 steps=2969 rejected=0 rhs=21077 g=1.01015254455 thr=nan
REF r100_inward dir=10 theta=0.101462 outcome=0 reason=0 stop_t=-372.348056 steps=1571 rejected=0 rhs=11291 g=1.01015254455 thr=nan
REF r100_inward dir=11 theta=0.001462 outcome=1 reason=1 stop_t=-144.304446 steps=830 rejected=0 rhs=6097 g=0 thr=8
REF r2p1_outward dir=0 theta=0 outcome=0 reason=0 stop_t=-253.9 steps=1120 rejected=1 rhs=8141 g=4.58257569496 thr=nan
REF r2p1_outward dir=1 theta=3.14159 outcome=1 reason=1 stop_t=-32.2468847 steps=186 rejected=0 rhs=1589 g=0 thr=8
REF r2p1_outward dir=2 theta=1.5708 outcome=1 reason=1 stop_t=-32.7725901 steps=316 rejected=1 rhs=2506 g=0 thr=8
REF r2p1_outward dir=3 theta=0.570377 outcome=0 reason=0 stop_t=-414.940103 steps=4263 rejected=2 rhs=30149 g=4.58257569495 thr=nan
REF r2p1_outward dir=4 theta=0.569377 outcome=0 reason=0 stop_t=-285.577403 steps=1686 rejected=1 rhs=12103 g=4.58257569496 thr=nan
REF r2p1_outward dir=5 theta=0.571377 outcome=1 reason=1 stop_t=-61.5813727 steps=995 rejected=1 rhs=7259 g=0 thr=8
REF r2p1_outward dir=6 theta=0.570367 outcome=0 reason=0 stop_t=-309.521289 steps=2162 rejected=2 rhs=15442 g=4.58257569495 thr=nan
REF r2p1_outward dir=7 theta=0.570387 outcome=1 reason=1 stop_t=-85.4867841 steps=1474 rejected=1 rhs=10612 g=0 thr=8
REF r2p1_outward dir=8 theta=0.570377 outcome=0 reason=0 stop_t=-333.450704 steps=2639 rejected=1 rhs=18774 g=4.58257569495 thr=nan
REF r2p1_outward dir=9 theta=0.570377 outcome=1 reason=1 stop_t=-109.415611 steps=1950 rejected=1 rhs=13944 g=0 thr=8
REF r2p1_outward dir=10 theta=0.620377 outcome=1 reason=1 stop_t=-41.9883684 steps=585 rejected=1 rhs=4389 g=0 thr=8
REF r2p1_outward dir=11 theta=0.520377 outcome=0 reason=0 stop_t=-264.96182 steps=1303 rejected=2 rhs=9429 g=4.58257569496 thr=nan
REF r1p5_freefall dir=0 theta=0 outcome=1 reason=1 stop_t=-30.6580907 steps=231 rejected=1 rhs=1911 g=0 thr=8
REF r1p5_freefall dir=1 theta=1.5708 outcome=0 reason=0 stop_t=-255.249558 steps=1130 rejected=1 rhs=8211 g=1 thr=nan
REF r1p5_freefall dir=2 theta=3.14159 outcome=0 reason=0 stop_t=-254.5 steps=1143 rejected=1 rhs=8302 g=0.464101615138 thr=nan
REF r1p5_freefall dir=3 theta=2.35619 outcome=0 reason=0 stop_t=-254.612997 steps=1127 rejected=1 rhs=8190 g=0.550510257217 thr=nan
SUMMARY upper r30_inward upper=0.25 min=1e-12 n=12 mismatch=0 max_dn_ang=8.66705e-05 max_dgrel=2.2616e-09 max_dstopT=35.0811 sum_rhs=76510 esc=6 dark=6 unres=0 inc=0 wall=0.041s
SUMMARY upper r30_inward upper=0.5 min=1e-12 n=12 mismatch=0 max_dn_ang=0.00018656 max_dgrel=2.26833e-09 max_dstopT=39.0614 sum_rhs=49063 esc=6 dark=6 unres=0 inc=0 wall=0.027s
SUMMARY upper r30_inward upper=1 min=1e-12 n=12 mismatch=0 max_dn_ang=0.000280143 max_dgrel=2.2658e-09 max_dstopT=41.1726 sum_rhs=36974 esc=6 dark=6 unres=0 inc=0 wall=0.020s
SUMMARY upper r30_inward upper=2 min=1e-12 n=12 mismatch=0 max_dn_ang=0.000281866 max_dgrel=2.24374e-09 max_dstopT=41.2044 sum_rhs=31843 esc=6 dark=6 unres=0 inc=0 wall=0.017s
SUMMARY upper r30_inward upper=4 min=1e-12 n=12 mismatch=0 max_dn_ang=0.000281866 max_dgrel=2.24952e-09 max_dstopT=41.2044 sum_rhs=29617 esc=6 dark=6 unres=0 inc=0 wall=0.016s
SUMMARY upper r30_inward upper=8 min=1e-12 n=12 mismatch=0 max_dn_ang=0.000281866 max_dgrel=2.25767e-09 max_dstopT=41.2044 sum_rhs=28714 esc=6 dark=6 unres=0 inc=0 wall=0.016s
SUMMARY upper r30_inward upper=16 min=1e-12 n=12 mismatch=0 max_dn_ang=0.000281866 max_dgrel=2.26556e-09 max_dstopT=41.2044 sum_rhs=28455 esc=6 dark=6 unres=0 inc=0 wall=0.015s
SUMMARY upper r30_inward upper=32 min=1e-12 n=12 mismatch=0 max_dn_ang=0.000281866 max_dgrel=2.26605e-09 max_dstopT=41.2044 sum_rhs=28434 esc=6 dark=6 unres=0 inc=0 wall=0.015s
SUMMARY upper r30_inward upper=64 min=1e-12 n=12 mismatch=0 max_dn_ang=0.000281866 max_dgrel=2.26605e-09 max_dstopT=41.2044 sum_rhs=28434 esc=6 dark=6 unres=0 inc=0 wall=0.015s
SUMMARY upper r100_inward upper=0.25 min=1e-12 n=12 mismatch=0 max_dn_ang=2.60363e-05 max_dgrel=2.04765e-09 max_dstopT=35.2041 sum_rhs=93464 esc=6 dark=6 unres=0 inc=0 wall=0.050s
SUMMARY upper r100_inward upper=0.5 min=1e-12 n=12 mismatch=0 max_dn_ang=5.69583e-05 max_dgrel=2.05623e-09 max_dstopT=39.2691 sum_rhs=56595 esc=6 dark=6 unres=0 inc=0 wall=0.030s
SUMMARY upper r100_inward upper=1 min=1e-12 n=12 mismatch=0 max_dn_ang=8.34774e-05 max_dgrel=2.05088e-09 max_dstopT=41.2546 sum_rhs=39788 esc=6 dark=6 unres=0 inc=0 wall=0.022s
SUMMARY upper r100_inward upper=2 min=1e-12 n=12 mismatch=0 max_dn_ang=8.28604e-05 max_dgrel=2.0262e-09 max_dstopT=41.2161 sum_rhs=32389 esc=6 dark=6 unres=0 inc=0 wall=0.018s
SUMMARY upper r100_inward upper=4 min=1e-12 n=12 mismatch=0 max_dn_ang=8.08898e-05 max_dgrel=2.02847e-09 max_dstopT=41.091 sum_rhs=29393 esc=6 dark=6 unres=0 inc=0 wall=0.016s
SUMMARY upper r100_inward upper=8 min=1e-12 n=12 mismatch=0 max_dn_ang=8.03365e-05 max_dgrel=2.03431e-09 max_dstopT=41.0553 sum_rhs=28427 esc=6 dark=6 unres=0 inc=0 wall=0.015s
SUMMARY upper r100_inward upper=16 min=1e-12 n=12 mismatch=0 max_dn_ang=8.03365e-05 max_dgrel=2.04195e-09 max_dstopT=41.0553 sum_rhs=28189 esc=6 dark=6 unres=0 inc=0 wall=0.015s
SUMMARY upper r100_inward upper=32 min=1e-12 n=12 mismatch=0 max_dn_ang=8.03365e-05 max_dgrel=2.04297e-09 max_dstopT=41.0553 sum_rhs=28182 esc=6 dark=6 unres=0 inc=0 wall=0.015s
SUMMARY upper r100_inward upper=64 min=1e-12 n=12 mismatch=0 max_dn_ang=8.03365e-05 max_dgrel=2.04297e-09 max_dstopT=41.0553 sum_rhs=28182 esc=6 dark=6 unres=0 inc=0 wall=0.015s
SUMMARY upper r2p1_outward upper=0.25 min=1e-12 n=12 mismatch=1 max_dn_ang=0.000287223 max_dgrel=2.66126e-09 max_dstopT=263.145 sum_rhs=62258 esc=5 dark=7 unres=0 inc=0 wall=0.034s
SUMMARY upper r2p1_outward upper=0.5 min=1e-12 n=12 mismatch=1 max_dn_ang=0.000287221 max_dgrel=2.64443e-09 max_dstopT=263.145 sum_rhs=42490 esc=5 dark=7 unres=0 inc=0 wall=0.023s
SUMMARY upper r2p1_outward upper=1 min=1e-12 n=12 mismatch=1 max_dn_ang=0.000287221 max_dgrel=2.61531e-09 max_dstopT=263.145 sum_rhs=33544 esc=5 dark=7 unres=0 inc=0 wall=0.019s
SUMMARY upper r2p1_outward upper=2 min=1e-12 n=12 mismatch=1 max_dn_ang=0.000287222 max_dgrel=2.59243e-09 max_dstopT=263.145 sum_rhs=29421 esc=5 dark=7 unres=0 inc=0 wall=0.016s
SUMMARY upper r2p1_outward upper=4 min=1e-12 n=12 mismatch=1 max_dn_ang=0.000287221 max_dgrel=2.59774e-09 max_dstopT=263.145 sum_rhs=27580 esc=5 dark=7 unres=0 inc=0 wall=0.015s
SUMMARY upper r2p1_outward upper=8 min=1e-12 n=12 mismatch=1 max_dn_ang=0.000287221 max_dgrel=2.60591e-09 max_dstopT=263.145 sum_rhs=26831 esc=5 dark=7 unres=0 inc=0 wall=0.015s
SUMMARY upper r2p1_outward upper=16 min=1e-12 n=12 mismatch=1 max_dn_ang=0.000287223 max_dgrel=2.61374e-09 max_dstopT=263.145 sum_rhs=26614 esc=5 dark=7 unres=0 inc=0 wall=0.015s
SUMMARY upper r2p1_outward upper=32 min=1e-12 n=12 mismatch=1 max_dn_ang=0.000287221 max_dgrel=2.6148e-09 max_dstopT=263.145 sum_rhs=26607 esc=5 dark=7 unres=0 inc=0 wall=0.015s
SUMMARY upper r2p1_outward upper=64 min=1e-12 n=12 mismatch=1 max_dn_ang=0.000287221 max_dgrel=2.6148e-09 max_dstopT=263.145 sum_rhs=26607 esc=5 dark=7 unres=0 inc=0 wall=0.015s
SUMMARY upper r1p5_freefall upper=0.25 min=1e-12 n=4 mismatch=0 max_dn_ang=1.00222e-10 max_dgrel=3.16466e-10 max_dstopT=2.6078e-08 sum_rhs=23597 esc=3 dark=1 unres=0 inc=0 wall=0.014s
SUMMARY upper r1p5_freefall upper=0.5 min=1e-12 n=4 mismatch=0 max_dn_ang=8.87544e-11 max_dgrel=3.65847e-10 max_dstopT=3.08873e-08 sum_rhs=12747 esc=3 dark=1 unres=0 inc=0 wall=0.008s
SUMMARY upper r1p5_freefall upper=1 min=1e-12 n=4 mismatch=0 max_dn_ang=8.18746e-11 max_dgrel=3.81078e-10 max_dstopT=2.81655e-08 sum_rhs=7553 esc=3 dark=1 unres=0 inc=0 wall=0.005s
SUMMARY upper r1p5_freefall upper=2 min=1e-12 n=4 mismatch=0 max_dn_ang=8.165e-11 max_dgrel=3.7195e-10 max_dstopT=2.57141e-08 sum_rhs=5131 esc=3 dark=1 unres=0 inc=0 wall=0.003s
SUMMARY upper r1p5_freefall upper=4 min=1e-12 n=4 mismatch=0 max_dn_ang=8.1566e-11 max_dgrel=3.6318e-10 max_dstopT=2.34608e-08 sum_rhs=4039 esc=3 dark=1 unres=0 inc=0 wall=0.002s
SUMMARY upper r1p5_freefall upper=8 min=1e-12 n=4 mismatch=0 max_dn_ang=8.15091e-11 max_dgrel=3.54659e-10 max_dstopT=2.12507e-08 sum_rhs=3591 esc=3 dark=1 unres=0 inc=0 wall=0.002s
SUMMARY upper r1p5_freefall upper=16 min=1e-12 n=4 mismatch=0 max_dn_ang=8.14607e-11 max_dgrel=3.46852e-10 max_dstopT=1.92511e-08 sum_rhs=3465 esc=3 dark=1 unres=0 inc=0 wall=0.002s
SUMMARY upper r1p5_freefall upper=32 min=1e-12 n=4 mismatch=0 max_dn_ang=8.1455e-11 max_dgrel=3.45979e-10 max_dstopT=1.94793e-08 sum_rhs=3458 esc=3 dark=1 unres=0 inc=0 wall=0.002s
SUMMARY upper r1p5_freefall upper=64 min=1e-12 n=4 mismatch=0 max_dn_ang=8.1455e-11 max_dgrel=3.45979e-10 max_dstopT=1.94793e-08 sum_rhs=3458 esc=3 dark=1 unres=0 inc=0 wall=0.002s
SUMMARY min r30_inward upper=2 min=0.1 n=12 mismatch=0 max_dn_ang=0.000281866 max_dgrel=2.24374e-09 max_dstopT=41.2044 sum_rhs=31843 esc=6 dark=6 unres=0 inc=0 wall=0.017s
SUMMARY min r30_inward upper=2 min=0.01 n=12 mismatch=0 max_dn_ang=0.000281866 max_dgrel=2.24374e-09 max_dstopT=41.2044 sum_rhs=31843 esc=6 dark=6 unres=0 inc=0 wall=0.017s
SUMMARY min r30_inward upper=2 min=0.001 n=12 mismatch=0 max_dn_ang=0.000281866 max_dgrel=2.24374e-09 max_dstopT=41.2044 sum_rhs=31843 esc=6 dark=6 unres=0 inc=0 wall=0.017s
SUMMARY min r30_inward upper=2 min=0.0001 n=12 mismatch=0 max_dn_ang=0.000281866 max_dgrel=2.24374e-09 max_dstopT=41.2044 sum_rhs=31843 esc=6 dark=6 unres=0 inc=0 wall=0.017s
SUMMARY min r30_inward upper=2 min=1e-05 n=12 mismatch=0 max_dn_ang=0.000281866 max_dgrel=2.24374e-09 max_dstopT=41.2044 sum_rhs=31843 esc=6 dark=6 unres=0 inc=0 wall=0.017s
SUMMARY min r30_inward upper=2 min=1e-06 n=12 mismatch=0 max_dn_ang=0.000281866 max_dgrel=2.24374e-09 max_dstopT=41.2044 sum_rhs=31843 esc=6 dark=6 unres=0 inc=0 wall=0.017s
SUMMARY min r30_inward upper=2 min=1e-08 n=12 mismatch=0 max_dn_ang=0.000281866 max_dgrel=2.24374e-09 max_dstopT=41.2044 sum_rhs=31843 esc=6 dark=6 unres=0 inc=0 wall=0.017s
SUMMARY min r30_inward upper=2 min=1e-10 n=12 mismatch=0 max_dn_ang=0.000281866 max_dgrel=2.24374e-09 max_dstopT=41.2044 sum_rhs=31843 esc=6 dark=6 unres=0 inc=0 wall=0.017s
SUMMARY min r30_inward upper=2 min=1e-12 n=12 mismatch=0 max_dn_ang=0.000281866 max_dgrel=2.24374e-09 max_dstopT=41.2044 sum_rhs=31843 esc=6 dark=6 unres=0 inc=0 wall=0.017s
SUMMARY min r30_inward upper=2 min=1e-14 n=12 mismatch=0 max_dn_ang=0.000281866 max_dgrel=2.24374e-09 max_dstopT=41.2044 sum_rhs=31843 esc=6 dark=6 unres=0 inc=0 wall=0.017s
SUMMARY min r100_inward upper=2 min=0.1 n=12 mismatch=0 max_dn_ang=8.28604e-05 max_dgrel=2.0262e-09 max_dstopT=41.2161 sum_rhs=32389 esc=6 dark=6 unres=0 inc=0 wall=0.018s
SUMMARY min r100_inward upper=2 min=0.01 n=12 mismatch=0 max_dn_ang=8.28604e-05 max_dgrel=2.0262e-09 max_dstopT=41.2161 sum_rhs=32389 esc=6 dark=6 unres=0 inc=0 wall=0.018s
SUMMARY min r100_inward upper=2 min=0.001 n=12 mismatch=0 max_dn_ang=8.28604e-05 max_dgrel=2.0262e-09 max_dstopT=41.2161 sum_rhs=32389 esc=6 dark=6 unres=0 inc=0 wall=0.018s
SUMMARY min r100_inward upper=2 min=0.0001 n=12 mismatch=0 max_dn_ang=8.28604e-05 max_dgrel=2.0262e-09 max_dstopT=41.2161 sum_rhs=32389 esc=6 dark=6 unres=0 inc=0 wall=0.018s
SUMMARY min r100_inward upper=2 min=1e-05 n=12 mismatch=0 max_dn_ang=8.28604e-05 max_dgrel=2.0262e-09 max_dstopT=41.2161 sum_rhs=32389 esc=6 dark=6 unres=0 inc=0 wall=0.018s
SUMMARY min r100_inward upper=2 min=1e-06 n=12 mismatch=0 max_dn_ang=8.28604e-05 max_dgrel=2.0262e-09 max_dstopT=41.2161 sum_rhs=32389 esc=6 dark=6 unres=0 inc=0 wall=0.018s
SUMMARY min r100_inward upper=2 min=1e-08 n=12 mismatch=0 max_dn_ang=8.28604e-05 max_dgrel=2.0262e-09 max_dstopT=41.2161 sum_rhs=32389 esc=6 dark=6 unres=0 inc=0 wall=0.018s
SUMMARY min r100_inward upper=2 min=1e-10 n=12 mismatch=0 max_dn_ang=8.28604e-05 max_dgrel=2.0262e-09 max_dstopT=41.2161 sum_rhs=32389 esc=6 dark=6 unres=0 inc=0 wall=0.018s
SUMMARY min r100_inward upper=2 min=1e-12 n=12 mismatch=0 max_dn_ang=8.28604e-05 max_dgrel=2.0262e-09 max_dstopT=41.2161 sum_rhs=32389 esc=6 dark=6 unres=0 inc=0 wall=0.018s
SUMMARY min r100_inward upper=2 min=1e-14 n=12 mismatch=0 max_dn_ang=8.28604e-05 max_dgrel=2.0262e-09 max_dstopT=41.2161 sum_rhs=32389 esc=6 dark=6 unres=0 inc=0 wall=0.018s
SUMMARY min r2p1_outward upper=2 min=0.1 n=12 mismatch=1 max_dn_ang=0.000287222 max_dgrel=2.59243e-09 max_dstopT=263.145 sum_rhs=29421 esc=5 dark=7 unres=0 inc=0 wall=0.016s
SUMMARY min r2p1_outward upper=2 min=0.01 n=12 mismatch=1 max_dn_ang=0.000287222 max_dgrel=2.59243e-09 max_dstopT=263.145 sum_rhs=29421 esc=5 dark=7 unres=0 inc=0 wall=0.016s
SUMMARY min r2p1_outward upper=2 min=0.001 n=12 mismatch=1 max_dn_ang=0.000287222 max_dgrel=2.59243e-09 max_dstopT=263.145 sum_rhs=29421 esc=5 dark=7 unres=0 inc=0 wall=0.017s
SUMMARY min r2p1_outward upper=2 min=0.0001 n=12 mismatch=1 max_dn_ang=0.000287222 max_dgrel=2.59243e-09 max_dstopT=263.145 sum_rhs=29421 esc=5 dark=7 unres=0 inc=0 wall=0.017s
SUMMARY min r2p1_outward upper=2 min=1e-05 n=12 mismatch=1 max_dn_ang=0.000287222 max_dgrel=2.59243e-09 max_dstopT=263.145 sum_rhs=29421 esc=5 dark=7 unres=0 inc=0 wall=0.016s
SUMMARY min r2p1_outward upper=2 min=1e-06 n=12 mismatch=1 max_dn_ang=0.000287222 max_dgrel=2.59243e-09 max_dstopT=263.145 sum_rhs=29421 esc=5 dark=7 unres=0 inc=0 wall=0.016s
SUMMARY min r2p1_outward upper=2 min=1e-08 n=12 mismatch=1 max_dn_ang=0.000287222 max_dgrel=2.59243e-09 max_dstopT=263.145 sum_rhs=29421 esc=5 dark=7 unres=0 inc=0 wall=0.016s
SUMMARY min r2p1_outward upper=2 min=1e-10 n=12 mismatch=1 max_dn_ang=0.000287222 max_dgrel=2.59243e-09 max_dstopT=263.145 sum_rhs=29421 esc=5 dark=7 unres=0 inc=0 wall=0.016s
SUMMARY min r2p1_outward upper=2 min=1e-12 n=12 mismatch=1 max_dn_ang=0.000287222 max_dgrel=2.59243e-09 max_dstopT=263.145 sum_rhs=29421 esc=5 dark=7 unres=0 inc=0 wall=0.016s
SUMMARY min r2p1_outward upper=2 min=1e-14 n=12 mismatch=1 max_dn_ang=0.000287222 max_dgrel=2.59243e-09 max_dstopT=263.145 sum_rhs=29421 esc=5 dark=7 unres=0 inc=0 wall=0.016s
SUMMARY min r1p5_freefall upper=2 min=0.1 n=4 mismatch=1 max_dn_ang=8.165e-11 max_dgrel=3.7195e-10 max_dstopT=254.3 sum_rhs=3654 esc=2 dark=1 unres=0 inc=1 wall=0.002s
SUMMARY min r1p5_freefall upper=2 min=0.01 n=4 mismatch=0 max_dn_ang=8.165e-11 max_dgrel=3.7195e-10 max_dstopT=2.57141e-08 sum_rhs=5131 esc=3 dark=1 unres=0 inc=0 wall=0.003s
SUMMARY min r1p5_freefall upper=2 min=0.001 n=4 mismatch=0 max_dn_ang=8.165e-11 max_dgrel=3.7195e-10 max_dstopT=2.57141e-08 sum_rhs=5131 esc=3 dark=1 unres=0 inc=0 wall=0.003s
SUMMARY min r1p5_freefall upper=2 min=0.0001 n=4 mismatch=0 max_dn_ang=8.165e-11 max_dgrel=3.7195e-10 max_dstopT=2.57141e-08 sum_rhs=5131 esc=3 dark=1 unres=0 inc=0 wall=0.003s
SUMMARY min r1p5_freefall upper=2 min=1e-05 n=4 mismatch=0 max_dn_ang=8.165e-11 max_dgrel=3.7195e-10 max_dstopT=2.57141e-08 sum_rhs=5131 esc=3 dark=1 unres=0 inc=0 wall=0.003s
SUMMARY min r1p5_freefall upper=2 min=1e-06 n=4 mismatch=0 max_dn_ang=8.165e-11 max_dgrel=3.7195e-10 max_dstopT=2.57141e-08 sum_rhs=5131 esc=3 dark=1 unres=0 inc=0 wall=0.003s
SUMMARY min r1p5_freefall upper=2 min=1e-08 n=4 mismatch=0 max_dn_ang=8.165e-11 max_dgrel=3.7195e-10 max_dstopT=2.57141e-08 sum_rhs=5131 esc=3 dark=1 unres=0 inc=0 wall=0.003s
SUMMARY min r1p5_freefall upper=2 min=1e-10 n=4 mismatch=0 max_dn_ang=8.165e-11 max_dgrel=3.7195e-10 max_dstopT=2.57141e-08 sum_rhs=5131 esc=3 dark=1 unres=0 inc=0 wall=0.003s
SUMMARY min r1p5_freefall upper=2 min=1e-12 n=4 mismatch=0 max_dn_ang=8.165e-11 max_dgrel=3.7195e-10 max_dstopT=2.57141e-08 sum_rhs=5131 esc=3 dark=1 unres=0 inc=0 wall=0.003s
SUMMARY min r1p5_freefall upper=2 min=1e-14 n=4 mismatch=0 max_dn_ang=8.165e-11 max_dgrel=3.7195e-10 max_dstopT=2.57141e-08 sum_rhs=5131 esc=3 dark=1 unres=0 inc=0 wall=0.003s
RK4 r30_inward dir=8 theta=0.168123 o01=1/1 o005=1/1 hhalve_dn=nan hhalve_dgrel=nan vsdp_dn=nan vsdp_dgrel=nan vsdp_class=1
RK4 r30_inward dir=9 theta=0.168123 o01=0/0 o005=0/0 hhalve_dn=1.89584e-07 hhalve_dgrel=7.32747e-15 vsdp_dn=7.95167e-08 vsdp_dgrel=4.66294e-13 vsdp_class=1
RK4 r30_inward dir=6 theta=0.168113 o01=1/1 o005=1/1 hhalve_dn=nan hhalve_dgrel=nan vsdp_dn=nan vsdp_dgrel=nan vsdp_class=1
RK4 r30_inward dir=7 theta=0.168133 o01=0/0 o005=0/0 hhalve_dn=1.73965e-09 hhalve_dgrel=7.9492e-14 vsdp_dn=7.86496e-10 vsdp_dgrel=2.6934e-13 vsdp_class=1
RK4 r2p1_outward dir=8 theta=0.570377 o01=0/0 o005=0/0 hhalve_dn=1.03961e-05 hhalve_dgrel=1.66234e-12 vsdp_dn=8.49321e-07 vsdp_dgrel=5.46896e-13 vsdp_class=1
RK4 r2p1_outward dir=9 theta=0.570377 o01=1/1 o005=1/1 hhalve_dn=nan hhalve_dgrel=nan vsdp_dn=nan vsdp_dgrel=nan vsdp_class=1
RK4 r2p1_outward dir=6 theta=0.570367 o01=0/0 o005=0/0 hhalve_dn=1.03971e-07 hhalve_dgrel=1.61182e-12 vsdp_dn=8.47692e-09 vsdp_dgrel=3.02647e-13 vsdp_class=1
RK4 r2p1_outward dir=7 theta=0.570387 o01=1/1 o005=1/1 hhalve_dn=nan hhalve_dgrel=nan vsdp_dn=nan vsdp_dgrel=nan vsdp_class=1
TOTAL_RAYS 816
real 0m2.428s
user 0m2.423s
sys 0m0.004s
+ set +x
### command: a_public minkowski
+ /tmp/opencode/step_bounds/a_public minkowski
SUMMARY mink upper=1 min=0.1 n=6 mismatch=0 max_dn_ang=0 max_dgrel=0 sum_rhs=4886 esc=6 dark=0 unres=0 inc=0
SUMMARY mink upper=1 min=1e-06 n=6 mismatch=0 max_dn_ang=0 max_dgrel=0 sum_rhs=4886 esc=6 dark=0 unres=0 inc=0
SUMMARY mink upper=1 min=1e-12 n=6 mismatch=0 max_dn_ang=0 max_dgrel=0 sum_rhs=4886 esc=6 dark=0 unres=0 inc=0
SUMMARY mink upper=4 min=0.1 n=6 mismatch=0 max_dn_ang=0 max_dgrel=0 sum_rhs=2590 esc=6 dark=0 unres=0 inc=0
SUMMARY mink upper=4 min=1e-06 n=6 mismatch=0 max_dn_ang=0 max_dgrel=0 sum_rhs=2590 esc=6 dark=0 unres=0 inc=0
SUMMARY mink upper=4 min=1e-12 n=6 mismatch=0 max_dn_ang=0 max_dgrel=0 sum_rhs=2590 esc=6 dark=0 unres=0 inc=0
SUMMARY mink upper=16 min=0.1 n=6 mismatch=0 max_dn_ang=0 max_dgrel=0 sum_rhs=2037 esc=6 dark=0 unres=0 inc=0
SUMMARY mink upper=16 min=1e-06 n=6 mismatch=0 max_dn_ang=0 max_dgrel=0 sum_rhs=2037 esc=6 dark=0 unres=0 inc=0
SUMMARY mink upper=16 min=1e-12 n=6 mismatch=0 max_dn_ang=0 max_dgrel=0 sum_rhs=2037 esc=6 dark=0 unres=0 inc=0
SUMMARY mink upper=64 min=0.1 n=6 mismatch=0 max_dn_ang=0 max_dgrel=0 sum_rhs=1932 esc=6 dark=0 unres=0 inc=0
SUMMARY mink upper=64 min=1e-06 n=6 mismatch=0 max_dn_ang=0 max_dgrel=0 sum_rhs=1932 esc=6 dark=0 unres=0 inc=0
SUMMARY mink upper=64 min=1e-12 n=6 mismatch=0 max_dn_ang=0 max_dgrel=0 sum_rhs=1932 esc=6 dark=0 unres=0 inc=0
SUMMARY mink upper=256 min=0.1 n=6 mismatch=0 max_dn_ang=0 max_dgrel=0 sum_rhs=1932 esc=6 dark=0 unres=0 inc=0
SUMMARY mink upper=256 min=1e-06 n=6 mismatch=0 max_dn_ang=0 max_dgrel=0 sum_rhs=1932 esc=6 dark=0 unres=0 inc=0
SUMMARY mink upper=256 min=1e-12 n=6 mismatch=0 max_dn_ang=0 max_dgrel=0 sum_rhs=1932 esc=6 dark=0 unres=0 inc=0
TOTAL_RAYS 186
real 0m0.013s
user 0m0.013s
sys 0m0.000s
+ set +x
### command: a_public alcubierre
+ /tmp/opencode/step_bounds/a_public alcubierre
REF alc_v3_s1 dir=0 theta=nan outcome=0 reason=0 stop_t=-16.4338937 steps=347 rejected=1 rhs=2730 g=1.3 thr=nan
REF alc_v3_s1 dir=1 theta=nan outcome=0 reason=0 stop_t=-29.3767055 steps=597 rejected=1 rhs=4480 g=0.7 thr=nan
REF alc_v3_s1 dir=2 theta=nan outcome=0 reason=0 stop_t=-20.2190145 steps=414 rejected=0 rhs=3192 g=1 thr=nan
REF alc_v3_s1 dir=3 theta=nan outcome=0 reason=0 stop_t=-20.2190145 steps=414 rejected=0 rhs=3192 g=1 thr=nan
REF alc_v3_s1 dir=4 theta=nan outcome=0 reason=0 stop_t=-17.6897755 steps=367 rejected=0 rhs=2863 g=1.17815368048 thr=nan
REF alc_v3_s1 dir=5 theta=nan outcome=0 reason=0 stop_t=-24.329102 steps=497 rejected=1 rhs=3780 g=0.822613315483 thr=nan
REF alc_v3_s10 dir=0 theta=nan outcome=0 reason=0 stop_t=-2.53543426 steps=523 rejected=3 rhs=3976 g=1.3 thr=nan
REF alc_v3_s10 dir=1 theta=nan outcome=0 reason=0 stop_t=-3.84949982 steps=775 rejected=0 rhs=5719 g=0.7 thr=nan
REF alc_v3_s10 dir=2 theta=nan outcome=0 reason=0 stop_t=-2.94282517 steps=589 rejected=0 rhs=4417 g=1 thr=nan
REF alc_v3_s10 dir=3 theta=nan outcome=0 reason=0 stop_t=-2.94282517 steps=589 rejected=0 rhs=4417 g=1 thr=nan
REF alc_v3_s10 dir=4 theta=nan outcome=0 reason=0 stop_t=-2.67090915 steps=543 rejected=2 rhs=4109 g=1.17959135733 thr=nan
REF alc_v3_s10 dir=5 theta=nan outcome=0 reason=0 stop_t=-3.3698049 steps=677 rejected=0 rhs=5033 g=0.820579506843 thr=nan
REF alc_v9_s1 dir=0 theta=nan outcome=0 reason=0 stop_t=-11.5627491 steps=283 rejected=1 rhs=2282 g=1.9 thr=nan
REF alc_v9_s1 dir=1 theta=nan outcome=0 reason=0 stop_t=-189.149503 steps=3825 rejected=1 rhs=27069 g=0.1 thr=nan
REF alc_v9_s1 dir=2 theta=nan outcome=0 reason=0 stop_t=-16.1619146 steps=351 rejected=4 rhs=2772 g=1 thr=nan
REF alc_v9_s1 dir=3 theta=nan outcome=0 reason=0 stop_t=-16.1619146 steps=351 rejected=4 rhs=2772 g=1 thr=nan
REF alc_v9_s1 dir=4 theta=nan outcome=0 reason=0 stop_t=-13.0192142 steps=308 rejected=1 rhs=2457 g=1.5041731473 thr=nan
REF alc_v9_s1 dir=5 theta=nan outcome=0 reason=0 stop_t=-22.987334 steps=491 rejected=1 rhs=3738 g=0.54079971046 thr=nan
REF alc_v9_s10 dir=0 theta=nan outcome=0 reason=0 stop_t=-2.03742978 steps=452 rejected=5 rhs=3493 g=1.9 thr=nan
REF alc_v9_s10 dir=1 theta=nan outcome=0 reason=0 stop_t=-19.9638367 steps=4013 rejected=0 rhs=28385 g=0.1 thr=nan
REF alc_v9_s10 dir=2 theta=nan outcome=0 reason=0 stop_t=-2.62069451 steps=525 rejected=0 rhs=3969 g=1 thr=nan
REF alc_v9_s10 dir=3 theta=nan outcome=0 reason=0 stop_t=-2.62069451 steps=525 rejected=0 rhs=3969 g=1 thr=nan
REF alc_v9_s10 dir=4 theta=nan outcome=0 reason=0 stop_t=-2.20704191 steps=469 rejected=3 rhs=3598 g=1.5316539409 thr=nan
REF alc_v9_s10 dir=5 theta=nan outcome=0 reason=0 stop_t=-3.67922549 steps=742 rejected=0 rhs=5488 g=0.483265790771 thr=nan
REF alc_v9_s100 dir=0 theta=nan outcome=0 reason=0 stop_t=-1.10374298 steps=2252 rejected=5 rhs=16093 g=1.9 thr=nan
REF alc_v9_s100 dir=1 theta=nan outcome=0 reason=0 stop_t=-2.89638367 steps=5813 rejected=0 rhs=40978 g=0.1 thr=nan
REF alc_v9_s100 dir=2 theta=nan outcome=0 reason=0 stop_t=-1.18800796 steps=2377 rejected=0 rhs=16926 g=1 thr=nan
REF alc_v9_s100 dir=3 theta=nan outcome=0 reason=0 stop_t=-1.18800796 steps=2377 rejected=0 rhs=16926 g=1 thr=nan
REF alc_v9_s100 dir=4 theta=nan outcome=0 reason=0 stop_t=-1.12678339 steps=2281 rejected=2 rhs=16275 g=1.53910006629 thr=nan
REF alc_v9_s100 dir=5 theta=nan outcome=0 reason=0 stop_t=-1.36694611 steps=2740 rejected=0 rhs=19474 g=0.462933441354 thr=nan
SUMMARY upper alc_v3_s1 upper=0.05 min=0.0001 n=6 mismatch=0 max_dn_ang=7.54397e-14 max_dgrel=7.62723e-13 max_dstopT=1.98952e-13 sum_rhs=19740 esc=6 dark=0 unres=0 inc=0 wall=0.005s
SUMMARY upper alc_v3_s1 upper=0.05 min=1e-08 n=6 mismatch=0 max_dn_ang=7.54397e-14 max_dgrel=7.62723e-13 max_dstopT=1.98952e-13 sum_rhs=19740 esc=6 dark=0 unres=0 inc=0 wall=0.005s
SUMMARY upper alc_v3_s1 upper=0.05 min=1e-12 n=6 mismatch=0 max_dn_ang=7.54397e-14 max_dgrel=7.62723e-13 max_dstopT=1.98952e-13 sum_rhs=19740 esc=6 dark=0 unres=0 inc=0 wall=0.005s
SUMMARY upper alc_v3_s1 upper=0.2 min=0.0001 n=6 mismatch=0 max_dn_ang=1.2079e-10 max_dgrel=3.45157e-10 max_dstopT=4.09095e-10 sum_rhs=6475 esc=6 dark=0 unres=0 inc=0 wall=0.001s
SUMMARY upper alc_v3_s1 upper=0.2 min=1e-08 n=6 mismatch=0 max_dn_ang=1.2079e-10 max_dgrel=3.45157e-10 max_dstopT=4.09095e-10 sum_rhs=6475 esc=6 dark=0 unres=0 inc=0 wall=0.002s
SUMMARY upper alc_v3_s1 upper=0.2 min=1e-12 n=6 mismatch=0 max_dn_ang=1.2079e-10 max_dgrel=3.45157e-10 max_dstopT=4.09095e-10 sum_rhs=6475 esc=6 dark=0 unres=0 inc=0 wall=0.002s
SUMMARY upper alc_v3_s1 upper=0.4 min=0.0001 n=6 mismatch=0 max_dn_ang=1.02989e-10 max_dgrel=3.46442e-10 max_dstopT=3.14323e-10 sum_rhs=4613 esc=6 dark=0 unres=0 inc=0 wall=0.001s
SUMMARY upper alc_v3_s1 upper=0.4 min=1e-08 n=6 mismatch=0 max_dn_ang=1.02989e-10 max_dgrel=3.46442e-10 max_dstopT=3.14323e-10 sum_rhs=4613 esc=6 dark=0 unres=0 inc=0 wall=0.001s
SUMMARY upper alc_v3_s1 upper=0.4 min=1e-12 n=6 mismatch=0 max_dn_ang=1.02989e-10 max_dgrel=3.46442e-10 max_dstopT=3.14323e-10 sum_rhs=4613 esc=6 dark=0 unres=0 inc=0 wall=0.001s
SUMMARY upper alc_v3_s1 upper=0.8 min=0.0001 n=6 mismatch=0 max_dn_ang=7.88763e-11 max_dgrel=7.95433e-10 max_dstopT=7.3689e-10 sum_rhs=3794 esc=6 dark=0 unres=0 inc=0 wall=0.001s
SUMMARY upper alc_v3_s1 upper=0.8 min=1e-08 n=6 mismatch=0 max_dn_ang=7.88763e-11 max_dgrel=7.95433e-10 max_dstopT=7.3689e-10 sum_rhs=3794 esc=6 dark=0 unres=0 inc=0 wall=0.001s
SUMMARY upper alc_v3_s1 upper=0.8 min=1e-12 n=6 mismatch=0 max_dn_ang=7.88763e-11 max_dgrel=7.95433e-10 max_dstopT=7.3689e-10 sum_rhs=3794 esc=6 dark=0 unres=0 inc=0 wall=0.001s
SUMMARY upper alc_v3_s1 upper=1.6 min=0.0001 n=6 mismatch=0 max_dn_ang=1.23935e-10 max_dgrel=1.11826e-09 max_dstopT=1.14768e-09 sum_rhs=3430 esc=6 dark=0 unres=0 inc=0 wall=0.001s
SUMMARY upper alc_v3_s1 upper=1.6 min=1e-08 n=6 mismatch=0 max_dn_ang=1.23935e-10 max_dgrel=1.11826e-09 max_dstopT=1.14768e-09 sum_rhs=3430 esc=6 dark=0 unres=0 inc=0 wall=0.001s
SUMMARY upper alc_v3_s1 upper=1.6 min=1e-12 n=6 mismatch=0 max_dn_ang=1.23935e-10 max_dgrel=1.11826e-09 max_dstopT=1.14768e-09 sum_rhs=3430 esc=6 dark=0 unres=0 inc=0 wall=0.001s
SUMMARY upper alc_v3_s1 upper=3.2 min=0.0001 n=6 mismatch=0 max_dn_ang=1.54539e-10 max_dgrel=1.25996e-09 max_dstopT=1.3457e-09 sum_rhs=3290 esc=6 dark=0 unres=0 inc=0 wall=0.001s
SUMMARY upper alc_v3_s1 upper=3.2 min=1e-08 n=6 mismatch=0 max_dn_ang=1.54539e-10 max_dgrel=1.25996e-09 max_dstopT=1.3457e-09 sum_rhs=3290 esc=6 dark=0 unres=0 inc=0 wall=0.001s
SUMMARY upper alc_v3_s1 upper=3.2 min=1e-12 n=6 mismatch=0 max_dn_ang=1.54539e-10 max_dgrel=1.25996e-09 max_dstopT=1.3457e-09 sum_rhs=3290 esc=6 dark=0 unres=0 inc=0 wall=0.001s
SUMMARY upper alc_v3_s10 upper=0.005 min=0.0001 n=6 mismatch=0 max_dn_ang=5.82867e-15 max_dgrel=3.10862e-14 max_dstopT=4.66294e-14 sum_rhs=27412 esc=6 dark=0 unres=0 inc=0 wall=0.006s
SUMMARY upper alc_v3_s10 upper=0.005 min=1e-08 n=6 mismatch=0 max_dn_ang=5.82867e-15 max_dgrel=3.10862e-14 max_dstopT=4.66294e-14 sum_rhs=27412 esc=6 dark=0 unres=0 inc=0 wall=0.006s
SUMMARY upper alc_v3_s10 upper=0.005 min=1e-12 n=6 mismatch=0 max_dn_ang=5.82867e-15 max_dgrel=3.10862e-14 max_dstopT=4.66294e-14 sum_rhs=27412 esc=6 dark=0 unres=0 inc=0 wall=0.006s
SUMMARY upper alc_v3_s10 upper=0.02 min=0.0001 n=6 mismatch=0 max_dn_ang=6.29929e-12 max_dgrel=2.40162e-10 max_dstopT=1.39897e-11 sum_rhs=8337 esc=6 dark=0 unres=0 inc=0 wall=0.002s
SUMMARY upper alc_v3_s10 upper=0.02 min=1e-08 n=6 mismatch=0 max_dn_ang=6.29929e-12 max_dgrel=2.40162e-10 max_dstopT=1.39897e-11 sum_rhs=8337 esc=6 dark=0 unres=0 inc=0 wall=0.002s
SUMMARY upper alc_v3_s10 upper=0.02 min=1e-12 n=6 mismatch=0 max_dn_ang=6.29929e-12 max_dgrel=2.40162e-10 max_dstopT=1.39897e-11 sum_rhs=8337 esc=6 dark=0 unres=0 inc=0 wall=0.002s
SUMMARY upper alc_v3_s10 upper=0.04 min=0.0001 n=6 mismatch=0 max_dn_ang=3.28758e-11 max_dgrel=3.85361e-10 max_dstopT=6.29403e-11 sum_rhs=5621 esc=6 dark=0 unres=0 inc=0 wall=0.001s
SUMMARY upper alc_v3_s10 upper=0.04 min=1e-08 n=6 mismatch=0 max_dn_ang=3.28758e-11 max_dgrel=3.85361e-10 max_dstopT=6.29403e-11 sum_rhs=5621 esc=6 dark=0 unres=0 inc=0 wall=0.001s
SUMMARY upper alc_v3_s10 upper=0.04 min=1e-12 n=6 mismatch=0 max_dn_ang=3.28758e-11 max_dgrel=3.85361e-10 max_dstopT=6.29403e-11 sum_rhs=5621 esc=6 dark=0 unres=0 inc=0 wall=0.001s
SUMMARY upper alc_v3_s10 upper=0.08 min=0.0001 n=6 mismatch=0 max_dn_ang=1.04151e-11 max_dgrel=2.78111e-10 max_dstopT=8.02363e-11 sum_rhs=4648 esc=6 dark=0 unres=0 inc=0 wall=0.001s
SUMMARY upper alc_v3_s10 upper=0.08 min=1e-08 n=6 mismatch=0 max_dn_ang=1.04151e-11 max_dgrel=2.78111e-10 max_dstopT=8.02363e-11 sum_rhs=4648 esc=6 dark=0 unres=0 inc=0 wall=0.001s
SUMMARY upper alc_v3_s10 upper=0.08 min=1e-12 n=6 mismatch=0 max_dn_ang=1.04151e-11 max_dgrel=2.78111e-10 max_dstopT=8.02363e-11 sum_rhs=4648 esc=6 dark=0 unres=0 inc=0 wall=0.001s
SUMMARY upper alc_v3_s10 upper=0.16 min=0.0001 n=6 mismatch=0 max_dn_ang=1.13121e-11 max_dgrel=7.59968e-10 max_dstopT=9.53793e-11 sum_rhs=4270 esc=6 dark=0 unres=0 inc=0 wall=0.001s
SUMMARY upper alc_v3_s10 upper=0.16 min=1e-08 n=6 mismatch=0 max_dn_ang=1.13121e-11 max_dgrel=7.59968e-10 max_dstopT=9.53793e-11 sum_rhs=4270 esc=6 dark=0 unres=0 inc=0 wall=0.001s
SUMMARY upper alc_v3_s10 upper=0.16 min=1e-12 n=6 mismatch=0 max_dn_ang=1.13121e-11 max_dgrel=7.59968e-10 max_dstopT=9.53793e-11 sum_rhs=4270 esc=6 dark=0 unres=0 inc=0 wall=0.001s
SUMMARY upper alc_v3_s10 upper=0.32 min=0.0001 n=6 mismatch=0 max_dn_ang=3.13081e-11 max_dgrel=9.77839e-10 max_dstopT=1.04124e-10 sum_rhs=4116 esc=6 dark=0 unres=0 inc=0 wall=0.001s
SUMMARY upper alc_v3_s10 upper=0.32 min=1e-08 n=6 mismatch=0 max_dn_ang=3.13081e-11 max_dgrel=9.77839e-10 max_dstopT=1.04124e-10 sum_rhs=4116 esc=6 dark=0 unres=0 inc=0 wall=0.001s
SUMMARY upper alc_v3_s10 upper=0.32 min=1e-12 n=6 mismatch=0 max_dn_ang=3.13081e-11 max_dgrel=9.77839e-10 max_dstopT=1.04124e-10 sum_rhs=4116 esc=6 dark=0 unres=0 inc=0 wall=0.001s
SUMMARY upper alc_v9_s1 upper=0.05 min=0.0001 n=6 mismatch=0 max_dn_ang=6.63192e-13 max_dgrel=2.59086e-11 max_dstopT=1.38662e-11 sum_rhs=39452 esc=6 dark=0 unres=0 inc=0 wall=0.010s
SUMMARY upper alc_v9_s1 upper=0.05 min=1e-08 n=6 mismatch=0 max_dn_ang=6.63192e-13 max_dgrel=2.59086e-11 max_dstopT=1.38662e-11 sum_rhs=39452 esc=6 dark=0 unres=0 inc=0 wall=0.010s
SUMMARY upper alc_v9_s1 upper=0.05 min=1e-12 n=6 mismatch=0 max_dn_ang=6.63192e-13 max_dgrel=2.59086e-11 max_dstopT=1.38662e-11 sum_rhs=39452 esc=6 dark=0 unres=0 inc=0 wall=0.010s
SUMMARY upper alc_v9_s1 upper=0.2 min=0.0001 n=6 mismatch=0 max_dn_ang=1.07764e-10 max_dgrel=2.07282e-09 max_dstopT=6.80462e-10 sum_rhs=11690 esc=6 dark=0 unres=0 inc=0 wall=0.003s
SUMMARY upper alc_v9_s1 upper=0.2 min=1e-08 n=6 mismatch=0 max_dn_ang=1.07764e-10 max_dgrel=2.07282e-09 max_dstopT=6.80462e-10 sum_rhs=11690 esc=6 dark=0 unres=0 inc=0 wall=0.003s
SUMMARY upper alc_v9_s1 upper=0.2 min=1e-12 n=6 mismatch=0 max_dn_ang=1.07764e-10 max_dgrel=2.07282e-09 max_dstopT=6.80462e-10 sum_rhs=11690 esc=6 dark=0 unres=0 inc=0 wall=0.003s
SUMMARY upper alc_v9_s1 upper=0.4 min=0.0001 n=6 mismatch=0 max_dn_ang=6.25e-11 max_dgrel=2.46644e-09 max_dstopT=1.08309e-09 sum_rhs=7413 esc=6 dark=0 unres=0 inc=0 wall=0.002s
SUMMARY upper alc_v9_s1 upper=0.4 min=1e-08 n=6 mismatch=0 max_dn_ang=6.25e-11 max_dgrel=2.46644e-09 max_dstopT=1.08309e-09 sum_rhs=7413 esc=6 dark=0 unres=0 inc=0 wall=0.002s
SUMMARY upper alc_v9_s1 upper=0.4 min=1e-12 n=6 mismatch=0 max_dn_ang=6.25e-11 max_dgrel=2.46644e-09 max_dstopT=1.08309e-09 sum_rhs=7413 esc=6 dark=0 unres=0 inc=0 wall=0.002s
SUMMARY upper alc_v9_s1 upper=0.8 min=0.0001 n=6 mismatch=0 max_dn_ang=5.34924e-11 max_dgrel=2.71772e-09 max_dstopT=1.1859e-09 sum_rhs=5369 esc=6 dark=0 unres=0 inc=0 wall=0.001s
SUMMARY upper alc_v9_s1 upper=0.8 min=1e-08 n=6 mismatch=0 max_dn_ang=5.34924e-11 max_dgrel=2.71772e-09 max_dstopT=1.1859e-09 sum_rhs=5369 esc=6 dark=0 unres=0 inc=0 wall=0.001s
SUMMARY upper alc_v9_s1 upper=0.8 min=1e-12 n=6 mismatch=0 max_dn_ang=5.34924e-11 max_dgrel=2.71772e-09 max_dstopT=1.1859e-09 sum_rhs=5369 esc=6 dark=0 unres=0 inc=0 wall=0.001s
SUMMARY upper alc_v9_s1 upper=1.6 min=0.0001 n=6 mismatch=0 max_dn_ang=1.03537e-10 max_dgrel=2.8194e-09 max_dstopT=9.54927e-10 sum_rhs=4431 esc=6 dark=0 unres=0 inc=0 wall=0.001s
SUMMARY upper alc_v9_s1 upper=1.6 min=1e-08 n=6 mismatch=0 max_dn_ang=1.03537e-10 max_dgrel=2.8194e-09 max_dstopT=9.54927e-10 sum_rhs=4431 esc=6 dark=0 unres=0 inc=0 wall=0.001s
SUMMARY upper alc_v9_s1 upper=1.6 min=1e-12 n=6 mismatch=0 max_dn_ang=1.03537e-10 max_dgrel=2.8194e-09 max_dstopT=9.54927e-10 sum_rhs=4431 esc=6 dark=0 unres=0 inc=0 wall=0.001s
SUMMARY upper alc_v9_s1 upper=3.2 min=0.0001 n=6 mismatch=0 max_dn_ang=1.23927e-10 max_dgrel=2.82251e-09 max_dstopT=3.24928e-09 sum_rhs=3983 esc=6 dark=0 unres=0 inc=0 wall=0.001s
SUMMARY upper alc_v9_s1 upper=3.2 min=1e-08 n=6 mismatch=0 max_dn_ang=1.23927e-10 max_dgrel=2.82251e-09 max_dstopT=3.24928e-09 sum_rhs=3983 esc=6 dark=0 unres=0 inc=0 wall=0.001s
SUMMARY upper alc_v9_s1 upper=3.2 min=1e-12 n=6 mismatch=0 max_dn_ang=1.23927e-10 max_dgrel=2.82251e-09 max_dstopT=3.24928e-09 sum_rhs=3983 esc=6 dark=0 unres=0 inc=0 wall=0.001s
SUMMARY upper alc_v9_s10 upper=0.005 min=0.0001 n=6 mismatch=0 max_dn_ang=8.9373e-14 max_dgrel=1.10281e-11 max_dstopT=3.55271e-13 sum_rhs=48167 esc=6 dark=0 unres=0 inc=0 wall=0.011s
SUMMARY upper alc_v9_s10 upper=0.005 min=1e-08 n=6 mismatch=0 max_dn_ang=8.9373e-14 max_dgrel=1.10281e-11 max_dstopT=3.55271e-13 sum_rhs=48167 esc=6 dark=0 unres=0 inc=0 wall=0.011s
SUMMARY upper alc_v9_s10 upper=0.005 min=1e-12 n=6 mismatch=0 max_dn_ang=8.9373e-14 max_dgrel=1.10281e-11 max_dstopT=3.55271e-13 sum_rhs=48167 esc=6 dark=0 unres=0 inc=0 wall=0.011s
SUMMARY upper alc_v9_s10 upper=0.02 min=0.0001 n=6 mismatch=0 max_dn_ang=5.33142e-11 max_dgrel=6.90633e-10 max_dstopT=1.09151e-10 sum_rhs=13692 esc=6 dark=0 unres=0 inc=0 wall=0.003s
SUMMARY upper alc_v9_s10 upper=0.02 min=1e-08 n=6 mismatch=0 max_dn_ang=5.33142e-11 max_dgrel=6.90633e-10 max_dstopT=1.09151e-10 sum_rhs=13692 esc=6 dark=0 unres=0 inc=0 wall=0.003s
SUMMARY upper alc_v9_s10 upper=0.02 min=1e-12 n=6 mismatch=0 max_dn_ang=5.33142e-11 max_dgrel=6.90633e-10 max_dstopT=1.09151e-10 sum_rhs=13692 esc=6 dark=0 unres=0 inc=0 wall=0.003s
SUMMARY upper alc_v9_s10 upper=0.04 min=0.0001 n=6 mismatch=0 max_dn_ang=1.75204e-10 max_dgrel=9.74943e-10 max_dstopT=3.64615e-10 sum_rhs=8484 esc=6 dark=0 unres=0 inc=0 wall=0.002s
SUMMARY upper alc_v9_s10 upper=0.04 min=1e-08 n=6 mismatch=0 max_dn_ang=1.75204e-10 max_dgrel=9.74943e-10 max_dstopT=3.64615e-10 sum_rhs=8484 esc=6 dark=0 unres=0 inc=0 wall=0.002s
SUMMARY upper alc_v9_s10 upper=0.04 min=1e-12 n=6 mismatch=0 max_dn_ang=1.75204e-10 max_dgrel=9.74943e-10 max_dstopT=3.64615e-10 sum_rhs=8484 esc=6 dark=0 unres=0 inc=0 wall=0.002s
SUMMARY upper alc_v9_s10 upper=0.08 min=0.0001 n=6 mismatch=0 max_dn_ang=1.1957e-10 max_dgrel=1.45708e-09 max_dstopT=2.69508e-10 sum_rhs=6251 esc=6 dark=0 unres=0 inc=0 wall=0.001s
SUMMARY upper alc_v9_s10 upper=0.08 min=1e-08 n=6 mismatch=0 max_dn_ang=1.1957e-10 max_dgrel=1.45708e-09 max_dstopT=2.69508e-10 sum_rhs=6251 esc=6 dark=0 unres=0 inc=0 wall=0.001s
SUMMARY upper alc_v9_s10 upper=0.08 min=1e-12 n=6 mismatch=0 max_dn_ang=1.1957e-10 max_dgrel=1.45708e-09 max_dstopT=2.69508e-10 sum_rhs=6251 esc=6 dark=0 unres=0 inc=0 wall=0.001s
SUMMARY upper alc_v9_s10 upper=0.16 min=0.0001 n=6 mismatch=0 max_dn_ang=1.72733e-11 max_dgrel=1.07679e-09 max_dstopT=1.26013e-10 sum_rhs=5278 esc=6 dark=0 unres=0 inc=0 wall=0.001s
SUMMARY upper alc_v9_s10 upper=0.16 min=1e-08 n=6 mismatch=0 max_dn_ang=1.72733e-11 max_dgrel=1.07679e-09 max_dstopT=1.26013e-10 sum_rhs=5278 esc=6 dark=0 unres=0 inc=0 wall=0.001s
SUMMARY upper alc_v9_s10 upper=0.16 min=1e-12 n=6 mismatch=0 max_dn_ang=1.72733e-11 max_dgrel=1.07679e-09 max_dstopT=1.26013e-10 sum_rhs=5278 esc=6 dark=0 unres=0 inc=0 wall=0.001s
SUMMARY upper alc_v9_s10 upper=0.32 min=0.0001 n=6 mismatch=0 max_dn_ang=5.49931e-11 max_dgrel=8.67977e-10 max_dstopT=2.55501e-10 sum_rhs=4872 esc=6 dark=0 unres=0 inc=0 wall=0.001s
SUMMARY upper alc_v9_s10 upper=0.32 min=1e-08 n=6 mismatch=0 max_dn_ang=5.49931e-11 max_dgrel=8.67977e-10 max_dstopT=2.55501e-10 sum_rhs=4872 esc=6 dark=0 unres=0 inc=0 wall=0.001s
SUMMARY upper alc_v9_s10 upper=0.32 min=1e-12 n=6 mismatch=0 max_dn_ang=5.49931e-11 max_dgrel=8.67977e-10 max_dstopT=2.55501e-10 sum_rhs=4872 esc=6 dark=0 unres=0 inc=0 wall=0.001s
SUMMARY min_stress alc_v9_s100 upper=0.004 min=0.0001 n=6 mismatch=0 max_dn_ang=1.69451e-11 max_dgrel=1.33161e-09 max_dstopT=1.05733e-11 sum_rhs=18095 esc=6 dark=0 unres=0 inc=0 wall=0.004s
SUMMARY min_stress alc_v9_s100 upper=0.004 min=1e-05 n=6 mismatch=0 max_dn_ang=1.69451e-11 max_dgrel=1.33161e-09 max_dstopT=1.05733e-11 sum_rhs=18095 esc=6 dark=0 unres=0 inc=0 wall=0.004s
SUMMARY min_stress alc_v9_s100 upper=0.004 min=1e-06 n=6 mismatch=0 max_dn_ang=1.69451e-11 max_dgrel=1.33161e-09 max_dstopT=1.05733e-11 sum_rhs=18095 esc=6 dark=0 unres=0 inc=0 wall=0.004s
SUMMARY min_stress alc_v9_s100 upper=0.004 min=1e-08 n=6 mismatch=0 max_dn_ang=1.69451e-11 max_dgrel=1.33161e-09 max_dstopT=1.05733e-11 sum_rhs=18095 esc=6 dark=0 unres=0 inc=0 wall=0.004s
SUMMARY min_stress alc_v9_s100 upper=0.004 min=1e-12 n=6 mismatch=0 max_dn_ang=1.69451e-11 max_dgrel=1.33161e-09 max_dstopT=1.05733e-11 sum_rhs=18095 esc=6 dark=0 unres=0 inc=0 wall=0.004s
SUMMARY min_stress alc_v9_s100 upper=0.004 min=1e-14 n=6 mismatch=0 max_dn_ang=1.69451e-11 max_dgrel=1.33161e-09 max_dstopT=1.05733e-11 sum_rhs=18095 esc=6 dark=0 unres=0 inc=0 wall=0.004s
TOTAL_RAYS 498
real 0m0.264s
user 0m0.254s
sys 0m0.010s
+ set +x
@@ -0,0 +1,33 @@
### command: b_actual
+ /tmp/opencode/step_bounds/b_actual
B r30_inward dir=0 ref=1 refstop=-69.5728394 span=69.5728 T=20 target=-20 steps=17 reached=1 term=-1 h=[0.1,1.72575] first=0.1 last=0.839319 boundary=1 rhs=119 rej=0 nullmax=3.01e-12 final_t=-20 wall=0.0001s
B r30_inward dir=1 ref=0 refstop=-226 span=226 T=20 target=-20 steps=13 reached=1 term=-1 h=[0.1,2] first=0.1 last=0.123959 boundary=1 rhs=91 rej=0 nullmax=8.69e-12 final_t=-20 wall=0.0001s
B r30_inward dir=2 ref=1 refstop=-142.175748 span=142.176 T=20 target=-20 steps=20 reached=1 term=-1 h=[0.1,1.40832] first=0.1 last=0.637699 boundary=1 rhs=140 rej=0 nullmax=1.53e-11 final_t=-20 wall=0.0001s
B r30_inward dir=3 ref=0 refstop=-370.180478 span=370.18 T=20 target=-20 steps=20 reached=1 term=-1 h=[0.1,1.40832] first=0.1 last=0.6377 boundary=1 rhs=140 rej=0 nullmax=1.53e-11 final_t=-20 wall=0.0001s
B r30_inward dir=4 ref=1 refstop=-118.245807 span=118.246 T=20 target=-20 steps=20 reached=1 term=-1 h=[0.1,1.40833] first=0.1 last=0.637634 boundary=1 rhs=140 rej=0 nullmax=1.53e-11 final_t=-20 wall=0.0001s
B r30_inward dir=5 ref=0 refstop=-346.252134 span=346.252 T=20 target=-20 steps=20 reached=1 term=-1 h=[0.1,1.40831] first=0.1 last=0.637765 boundary=1 rhs=140 rej=0 nullmax=1.53e-11 final_t=-20 wall=0.0001s
B r100_inward dir=0 ref=1 refstop=-144.299101 span=144.299 T=20 target=-20 steps=12 reached=1 term=-1 h=[0.1,2] first=0.1 last=1.4 boundary=1 rhs=84 rej=0 nullmax=1.45e-14 final_t=-20 wall=0.0001s
B r100_inward dir=1 ref=0 refstop=-156 span=156 T=20 target=-20 steps=12 reached=1 term=-1 h=[0.1,2] first=0.1 last=1.4 boundary=1 rhs=84 rej=0 nullmax=7.66e-15 final_t=-20 wall=0.0001s
B r100_inward dir=2 ref=1 refstop=-301.077405 span=301.077 T=20 target=-20 steps=12 reached=1 term=-1 h=[0.1,2] first=0.1 last=1.4 boundary=1 rhs=84 rej=0 nullmax=1.4e-14 final_t=-20 wall=0.0001s
B r100_inward dir=3 ref=1 refstop=-211.027405 span=211.027 T=20 target=-20 steps=12 reached=1 term=-1 h=[0.1,2] first=0.1 last=1.4 boundary=1 rhs=84 rej=0 nullmax=1.32e-14 final_t=-20 wall=0.0001s
B r2p1_outward dir=0 ref=0 refstop=-253.9 span=253.9 T=20 target=-20 steps=46 reached=1 term=-1 h=[0.1,1.0708] first=0.1 last=1.06594 boundary=1 rhs=322 rej=0 nullmax=6.27e-11 final_t=-20 wall=0.0002s
B r2p1_outward dir=1 ref=0 refstop=-414.940103 span=414.94 T=20 target=-20 steps=105 reached=1 term=-1 h=[0.1,0.211533] first=0.1 last=0.192781 boundary=1 rhs=735 rej=0 nullmax=4.7e-11 final_t=-20 wall=0.0005s
B r2p1_outward dir=2 ref=0 refstop=-333.450704 span=333.451 T=20 target=-20 steps=105 reached=1 term=-1 h=[0.1,0.211533] first=0.1 last=0.192739 boundary=1 rhs=735 rej=0 nullmax=4.7e-11 final_t=-20 wall=0.0005s
B r2p1_outward dir=3 ref=1 refstop=-109.415611 span=109.416 T=20 target=-20 steps=105 reached=1 term=-1 h=[0.1,0.211532] first=0.1 last=0.192823 boundary=1 rhs=735 rej=0 nullmax=4.7e-11 final_t=-20 wall=0.0005s
B r2p1_outward dir=4 ref=1 refstop=-32.2468847 span=32.2469 T=20 target=-20 steps=37 reached=1 term=-1 h=[0.1,0.899361] first=0.1 last=0.332712 boundary=1 rhs=259 rej=0 nullmax=2.99e-11 final_t=-20 wall=0.0002s
B r1p5_freefall dir=0 ref=1 refstop=-30.6580907 span=30.6581 T=20 target=-20 steps=49 reached=1 term=-1 h=[0.1,0.695968] first=0.1 last=0.106174 boundary=1 rhs=343 rej=0 nullmax=4.94e-10 final_t=-20 wall=0.0002s
B r1p5_freefall dir=1 ref=0 refstop=-254.5 span=254.5 T=20 target=-20 steps=52 reached=1 term=-1 h=[0.0920967,1.08419] first=0.1 last=0.22147 boundary=1 rhs=364 rej=0 nullmax=4.03e-11 final_t=-20 wall=0.0002s
B mink_moving dir=0 ref=0 refstop=-51.2640254 span=51.264 T=20 target=-20 steps=3 reached=1 term=-1 h=[1,14] first=1 last=14 boundary=1 rhs=21 rej=0 nullmax=2.22e-16 final_t=-20 wall=0.0000s
B mink_moving dir=1 ref=0 refstop=-71.5846741 span=71.5847 T=20 target=-20 steps=3 reached=1 term=-1 h=[1,14] first=1 last=14 boundary=1 rhs=21 rej=0 nullmax=0 final_t=-20 wall=0.0000s
B alc_v3_s1 dir=0 ref=0 refstop=-16.4338937 span=16.4339 T=16.4339 target=-16.4339 steps=56 reached=1 term=-1 h=[0.05,0.4] first=0.05 last=0.203538 boundary=1 rhs=406 rej=2 nullmax=0 final_t=-16.4338937 wall=0.0001s
B alc_v3_s1 dir=1 ref=0 refstop=-29.3767055 span=29.3767 T=20 target=-20 steps=62 reached=1 term=-1 h=[0.05,0.4] first=0.05 last=0.27784 boundary=1 rhs=441 rej=1 nullmax=0 final_t=-20 wall=0.0001s
B alc_v9_s10 dir=0 ref=0 refstop=-2.03742978 span=2.03743 T=2.03743 target=-2.03743 steps=77 reached=1 term=-1 h=[0.005,0.04] first=0.005 last=0.0388523 boundary=1 rhs=588 rej=7 nullmax=0 final_t=-2.03742978 wall=0.0001s
B alc_v9_s10 dir=1 ref=0 refstop=-19.9638367 span=19.9638 T=19.9638 target=-19.9638 steps=521 reached=1 term=-1 h=[0.005,0.04] first=0.005 last=0.0365282 boundary=1 rhs=3682 rej=5 nullmax=2.22e-16 final_t=-19.9638367 wall=0.0009s
B alc_v9_s100 dir=0 ref=0 refstop=-1.10374298 span=1.10374 T=1.10374 target=-1.10374 steps=302 reached=1 term=-1 h=[0.0005,0.004] first=0.0005 last=0.00388798 boundary=1 rhs=2163 rej=7 nullmax=0 final_t=-1.10374298 wall=0.0005s
B alc_v9_s100 dir=1 ref=0 refstop=-2.89638367 span=2.89638 T=2.89638 target=-2.89638 steps=746 reached=1 term=-1 h=[0.0005,0.004] first=0.0005 last=0.00365282 boundary=1 rhs=5257 rej=5 nullmax=2.22e-16 final_t=-2.89638367 wall=0.0012s
TOTAL_OBSERVED_STEPS 2427
real 0m0.152s
user 0m0.145s
sys 0m0.006s
+ set +x
@@ -0,0 +1,142 @@
{
"r100_tight_reference": {
"vertices": 3158,
"triangles": 6188,
"outcomes": {
"0": 2906,
"1": 252
},
"reasons": {
"0": 2906,
"1": 252
},
"accepted": 2908050,
"rejected": 1963,
"rhs": 21294763,
"accepted_percentiles": {
"0": 659,
"0.5": 885,
"0.9": 1166,
"0.99": 1410,
"1": 1609
},
"rejected_percentiles": {
"0": 0,
"0.5": 0,
"0.9": 2,
"0.99": 3,
"1": 4
},
"rhs_percentiles": {
"0": 4900,
"0.5": 6496,
"0.9": 8463,
"0.99": 10171,
"1": 11564
},
"sha256": "a4f7def4212a7dae2b6290fd8635a5f1b087e11b0f8043e60e48fec71ecdb6e4",
"wall_seconds": 2.6278538939077407
},
"r100_ref_vs_0.5": {
"shared": 3158,
"only_a": 0,
"only_b": 0,
"terminal_mismatches": 0,
"max_sky_angle": 6.236425088081268e-08,
"max_logg_difference": 8.506182824080666e-10
},
"r100_ref_vs_2": {
"shared": 3158,
"only_a": 0,
"only_b": 0,
"terminal_mismatches": 0,
"max_sky_angle": 9.216337364737666e-08,
"max_logg_difference": 8.199086565935376e-10
},
"r100_ref_vs_8": {
"shared": 3158,
"only_a": 0,
"only_b": 0,
"terminal_mismatches": 0,
"max_sky_angle": 8.743723551600822e-08,
"max_logg_difference": 8.257244662329688e-10
},
"r100_ref_vs_32": {
"shared": 3158,
"only_a": 0,
"only_b": 0,
"terminal_mismatches": 0,
"max_sky_angle": 8.743704837242435e-08,
"max_logg_difference": 8.34318713074933e-10
},
"r2p1_tight_reference": {
"vertices": 5496,
"triangles": 10816,
"outcomes": {
"1": 1336,
"0": 4160
},
"reasons": {
"1": 1336,
"0": 4160
},
"accepted": 4515098,
"rejected": 8672,
"rhs": 33269502,
"accepted_percentiles": {
"0": 419,
"0.5": 805,
"0.9": 1034,
"0.99": 1253,
"1": 1860
},
"rejected_percentiles": {
"0": 1,
"0.5": 2,
"0.9": 2,
"0.99": 3,
"1": 3
},
"rhs_percentiles": {
"0": 3227,
"0.5": 5936,
"0.9": 7546,
"0.99": 9072,
"1": 13328
},
"sha256": "a81aac81cff2891627e21c22998ceb51d65ef779bb013922cadeb97140274cba",
"wall_seconds": 3.180392162874341
},
"r2p1_ref_vs_0.5": {
"shared": 5496,
"only_a": 0,
"only_b": 0,
"terminal_mismatches": 0,
"max_sky_angle": 1.4772143877471049e-05,
"max_logg_difference": 2.098608620215714e-09
},
"r2p1_ref_vs_2": {
"shared": 5496,
"only_a": 0,
"only_b": 0,
"terminal_mismatches": 0,
"max_sky_angle": 1.4772101910544689e-05,
"max_logg_difference": 2.0455042104572385e-09
},
"r2p1_ref_vs_8": {
"shared": 5496,
"only_a": 0,
"only_b": 0,
"terminal_mismatches": 0,
"max_sky_angle": 1.4772018211627154e-05,
"max_logg_difference": 2.059625359152051e-09
},
"r2p1_ref_vs_32": {
"shared": 5496,
"only_a": 0,
"only_b": 0,
"terminal_mismatches": 0,
"max_sky_angle": 1.4772020763985666e-05,
"max_logg_difference": 2.068350379857975e-09
}
}
@@ -0,0 +1,354 @@
{
"r100_hmax0.5": {
"vertices": 3158,
"triangles": 6188,
"outcomes": {
"0": 2906,
"1": 252
},
"reasons": {
"0": 2906,
"1": 252
},
"accepted": 2269662,
"rejected": 177,
"rhs": 16813349,
"accepted_percentiles": {
"0": 312,
"0.5": 736,
"0.9": 786,
"0.99": 848,
"1": 899
},
"rejected_percentiles": {
"0": 0,
"0.5": 0,
"0.9": 0,
"0.99": 1,
"1": 1
},
"rhs_percentiles": {
"0": 2471,
"0.5": 5446,
"0.9": 5803,
"0.99": 6223,
"1": 6587
},
"sha256": "787386a026203d846debf25a6a4f431a131b06bd3ab464f75f3e17a1d379992a",
"wall_seconds": 2.0714269301388413
},
"r100_hmax2": {
"vertices": 3158,
"triangles": 6188,
"outcomes": {
"0": 2906,
"1": 252
},
"reasons": {
"0": 2906,
"1": 252
},
"accepted": 723677,
"rejected": 5690,
"rhs": 6030185,
"accepted_percentiles": {
"0": 165,
"0.5": 220,
"0.9": 290,
"0.99": 348,
"1": 397
},
"rejected_percentiles": {
"0": 0,
"0.5": 2,
"0.9": 4,
"0.99": 5,
"1": 5
},
"rhs_percentiles": {
"0": 1442,
"0.5": 1855,
"0.9": 2324,
"0.99": 2737,
"1": 3073
},
"sha256": "187c3cbba6a18d88608cd3859da94d88b58ce9d65e0d800d84b3254a6b139151",
"wall_seconds": 0.9786226029973477
},
"r100_hmax8": {
"vertices": 3158,
"triangles": 6188,
"outcomes": {
"0": 2906,
"1": 252
},
"reasons": {
"0": 2906,
"1": 252
},
"accepted": 463004,
"rejected": 9336,
"rhs": 4230835,
"accepted_percentiles": {
"0": 80,
"0.5": 133,
"0.9": 225,
"0.99": 286,
"1": 357
},
"rejected_percentiles": {
"0": 0,
"0.5": 3,
"0.9": 5,
"0.99": 6,
"1": 7
},
"rhs_percentiles": {
"0": 868,
"0.5": 1253,
"0.9": 1869,
"0.99": 2289,
"1": 2786
},
"sha256": "4c7e9d401f2af237129a6a677ca94ed1ee41472594db57852a08647b1dc34aad",
"wall_seconds": 0.8228706018999219
},
"r100_hmax32": {
"vertices": 3158,
"triangles": 6188,
"outcomes": {
"0": 2906,
"1": 252
},
"reasons": {
"0": 2906,
"1": 252
},
"accepted": 441750,
"rejected": 9336,
"rhs": 4081609,
"accepted_percentiles": {
"0": 72,
"0.5": 126,
"0.9": 222,
"0.99": 285,
"1": 357
},
"rejected_percentiles": {
"0": 0,
"0.5": 3,
"0.9": 5,
"0.99": 6,
"1": 7
},
"rhs_percentiles": {
"0": 812,
"0.5": 1204,
"0.9": 1848,
"0.99": 2282,
"1": 2786
},
"sha256": "3742efd56893bec9e62a01668915ef7dda613d6504b2057043eb97c9b38334f3",
"wall_seconds": 0.8300854018889368
},
"r100_2_vs_0.5": {
"shared": 3158,
"only_a": 0,
"only_b": 0,
"terminal_mismatches": 0,
"max_sky_angle": 3.077090601306577e-08,
"max_logg_difference": 5.28674309191457e-11
},
"r100_2_vs_8": {
"shared": 3158,
"only_a": 0,
"only_b": 0,
"terminal_mismatches": 0,
"max_sky_angle": 7.063214322895989e-09,
"max_logg_difference": 3.2702011237839557e-11
},
"r100_2_vs_32": {
"shared": 3158,
"only_a": 0,
"only_b": 0,
"terminal_mismatches": 0,
"max_sky_angle": 7.063404202987903e-09,
"max_logg_difference": 2.477072019724247e-11
},
"r2p1_hmax0.5": {
"vertices": 5496,
"triangles": 10816,
"outcomes": {
"1": 1336,
"0": 4160
},
"reasons": {
"1": 1336,
"0": 4160
},
"accepted": 2646842,
"rejected": 830,
"rhs": 20136508,
"accepted_percentiles": {
"0": 116,
"0.5": 552,
"0.9": 618,
"0.99": 672,
"1": 823
},
"rejected_percentiles": {
"0": 0,
"0.5": 0,
"0.9": 1,
"0.99": 1,
"1": 2
},
"rhs_percentiles": {
"0": 1099,
"0.5": 4158,
"0.9": 4620,
"0.99": 4991,
"1": 6055
},
"sha256": "3a77a1e7dee3cd18d3ab8a408826aba932f912dc88749989e0c307013d6e7e81",
"wall_seconds": 1.973590083885938
},
"r2p1_hmax2": {
"vertices": 5496,
"triangles": 10816,
"outcomes": {
"1": 1336,
"0": 4160
},
"reasons": {
"1": 1336,
"0": 4160
},
"accepted": 1131104,
"rejected": 4004,
"rhs": 9548434,
"accepted_percentiles": {
"0": 109,
"0.5": 202,
"0.9": 258,
"0.99": 311,
"1": 460
},
"rejected_percentiles": {
"0": 0,
"0.5": 1,
"0.9": 1,
"0.99": 2,
"1": 2
},
"rhs_percentiles": {
"0": 1050,
"0.5": 1708,
"0.9": 2100,
"0.99": 2478,
"1": 3521
},
"sha256": "899331ea41d1612cf66bc7664b50e866f4f3bc7309e9c206440f801238ca455c",
"wall_seconds": 1.177934248931706
},
"r2p1_hmax8": {
"vertices": 5496,
"triangles": 10816,
"outcomes": {
"1": 1336,
"0": 4160
},
"reasons": {
"1": 1336,
"0": 4160
},
"accepted": 822680,
"rejected": 4004,
"rhs": 7389760,
"accepted_percentiles": {
"0": 87,
"0.5": 137,
"0.9": 212,
"0.99": 265,
"1": 386
},
"rejected_percentiles": {
"0": 0,
"0.5": 1,
"0.9": 1,
"0.99": 2,
"1": 2
},
"rhs_percentiles": {
"0": 910,
"0.5": 1260,
"0.9": 1778,
"0.99": 2142,
"1": 3003
},
"sha256": "49edfbe9dfb2948d3472e6ef6287bed103332758953756a7e4089316caa0b1b6",
"wall_seconds": 0.9704615350347012
},
"r2p1_hmax32": {
"vertices": 5496,
"triangles": 10816,
"outcomes": {
"1": 1336,
"0": 4160
},
"reasons": {
"1": 1336,
"0": 4160
},
"accepted": 795354,
"rejected": 4004,
"rhs": 7197806,
"accepted_percentiles": {
"0": 81,
"0.5": 132,
"0.9": 210,
"0.99": 261,
"1": 379
},
"rejected_percentiles": {
"0": 0,
"0.5": 1,
"0.9": 1,
"0.99": 2,
"1": 2
},
"rhs_percentiles": {
"0": 861,
"0.5": 1218,
"0.9": 1757,
"0.99": 2128,
"1": 2954
},
"sha256": "74bbb1e969644d49b7fdf5a6a7a5930ca8b962ec91628cd91feeb9df039100e7",
"wall_seconds": 0.9720155198592693
},
"r2p1_2_vs_0.5": {
"shared": 5496,
"only_a": 0,
"only_b": 0,
"terminal_mismatches": 0,
"max_sky_angle": 1.046601929593161e-10,
"max_logg_difference": 6.900746640781108e-11
},
"r2p1_2_vs_8": {
"shared": 5496,
"only_a": 0,
"only_b": 0,
"terminal_mismatches": 0,
"max_sky_angle": 8.369894549651799e-11,
"max_logg_difference": 1.9082291302652266e-11
},
"r2p1_2_vs_32": {
"shared": 5496,
"only_a": 0,
"only_b": 0,
"terminal_mismatches": 0,
"max_sky_angle": 8.114658658759588e-11,
"max_logg_difference": 2.823297151621773e-11
}
}
@@ -0,0 +1,54 @@
# Experiment A/B environment and source identity
date_utc: 2026-10-05T22:15:50Z
host: Linux ATRI-gentoo 6.18.48-gentoo #1 SMP PREEMPT_DYNAMIC Mon Aug 31 13:33:24 EDT 2026 x86_64 12th Gen Intel(R) Core(TM) i7-12700K GenuineIntel GNU/Linux
nproc: 16
cc:
cc (Gentoo 16.2.1_p20260926 p1) 16.2.1 20260926
Copyright (C) 2026 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
git_head: c894fdb11a0845558fdb67b71a9bd019a2d94ed4
git_status_short:
M Makefile
M mk/reference_images.mk
M nr_spacetime_movie_renderer_design.md
M src/asymptotic.c
M src/frame.c
M src/frame.h
M src/geodesic.c
M src/geodesic.h
M src/lens_map.c
M src/lens_map.h
M src/main.c
M src/ray.c
M src/ray.h
M tests/test_asymptotic.c
M tests/test_camera_cli.py
M tests/test_frame.c
M usage.md
?? nmesh_spacetime_output_format.md
?? tests/test_adaptive_cli.py
?? tests/test_geodesic_adaptive.c
# sha256 of the sources consumed by the harnesses
25f24a2e1c9ce49508fed230dfb3f6d9986e92bb4a5e6347bb2356ac0ee5fffa src/geodesic.c
1e43b95346f484050354631e8682f9793e618cbbd1de6338bd15e092216ae6fb src/geodesic.h
e6c0616461507adb1141ca3caa7e76f30e2497c4d53acfef092597fb60431a4a src/asymptotic.c
db029ef837f12800bbf81fc74e7bab6eb9f18e42a53ceb5f205867d8a329321d src/asymptotic.h
1792a17f432ceee32ee9c802486ecf9d64d96307c249e0d5cfa563ef4a0fefd5 src/asymptotic_schwarzschild.c
cc9ad0b04e11bd08bcf238901125e76a26ec332cd7fe72871e7c46fb7a322aec src/asymptotic_schwarzschild.h
ea0b5838bc56fbfeedec9110cd20653b892127bfc863db91a3a8cabaad4c520b src/asymptotic_gl48.h
359066d78a10f9589a220124147cdbefaff8f97028a9fdd2923b474349599752 src/spacetime.h
4c7ae6e96fe57e2a2ae2491db924bad56af59abfcc305bbb6ab2609f3b405e0d src/spacetime_common.c
690045ed715a951335dc7d654258e0f3b802e0bf6e2815ceb4858fa99cd372d7 src/spacetime_minkowski.c
e5b5935d16e24b07cedb25d184e8f7029075aec9496f54c23cb1061971914a72 src/spacetime_schwarzschild.c
75956034303fe3bab9324ecb87533e054458e35a26132a3e885233acad785767 src/spacetime_alcubierre.c
559f626983c68e06d3223d8381085a0b5ac422529a1ea656f4e24b1b3a50f51a src/observer.c
d761aabfbc277b0989bd20302578302d91f8253a9d900943de62ab3edd3f145b src/observer.h
# sha256 of the harnesses and this script
853018775c335180199aa3a0046445a5f795f83d18de0ff6a664a6368d835dbd a_public_endpoints.c
10a6558815e3ff5fa114aa8ca6dfe3c7db6e860ecb9f7cab20c59145a662d3d2 b_actual_h.c
1cfd93b0c9f187b4c45d169fbeb8c6fea7f02c3317ac24a32691723b0086548c run_experiment.sh
@@ -0,0 +1,318 @@
# Experiment A/B summary tables
## A Schwarzschild references (DP tol=1e-12, max_step=0.25)
| case | dir | theta | outcome | reason | stop_t | steps | rej | rhs | g | thr |
|---|---|---|---|---|---|---|---|---|---|---|
| r30_inward | 0 | 0 | DARK | REDSHIFT | -69.57 | 531 | 0 | 4004 | 0 | 8 |
| r30_inward | 1 | 3.142 | ESC | NONE | -226 | 905 | 0 | 6629 | 1.035 | nan |
| r30_inward | 2 | 1.571 | ESC | NONE | -256.7 | 1028 | 0 | 7490 | 1.035 | nan |
| r30_inward | 3 | 0.1681 | DARK | REDSHIFT | -225.9 | 3746 | 0 | 26509 | 0 | 8 |
| r30_inward | 4 | 0.1671 | DARK | REDSHIFT | -94.28 | 1127 | 0 | 8176 | 0 | 8 |
| r30_inward | 5 | 0.1691 | ESC | NONE | -322.4 | 1834 | 0 | 13132 | 1.035 | nan |
| r30_inward | 6 | 0.1681 | DARK | REDSHIFT | -118.2 | 1606 | 0 | 11529 | 0 | 8 |
| r30_inward | 7 | 0.1681 | ESC | NONE | -346.3 | 2312 | 1 | 16485 | 1.035 | nan |
| r30_inward | 8 | 0.1681 | DARK | REDSHIFT | -142.2 | 2081 | 0 | 14854 | 0 | 8 |
| r30_inward | 9 | 0.1681 | ESC | NONE | -370.2 | 2790 | 1 | 19831 | 1.035 | nan |
| r30_inward | 10 | 0.2181 | ESC | NONE | -302.6 | 1409 | 1 | 10164 | 1.035 | nan |
| r30_inward | 11 | 0.1181 | DARK | REDSHIFT | -73.76 | 665 | 0 | 4942 | 0 | 8 |
| r100_inward | 0 | 0 | DARK | REDSHIFT | -144.3 | 830 | 0 | 6097 | 0 | 8 |
| r100_inward | 1 | 3.142 | ESC | NONE | -156 | 625 | 0 | 4669 | 1.01 | nan |
| r100_inward | 2 | 1.571 | ESC | NONE | -237.7 | 952 | 0 | 6958 | 1.01 | nan |
| r100_inward | 3 | 0.05146 | DARK | REDSHIFT | -301.1 | 4045 | 0 | 28602 | 0 | 8 |
| r100_inward | 4 | 0.05046 | DARK | REDSHIFT | -163.1 | 1292 | 0 | 9331 | 0 | 8 |
| r100_inward | 5 | 0.05246 | ESC | NONE | -391.6 | 2014 | 1 | 14399 | 1.01 | nan |
| r100_inward | 6 | 0.05145 | DARK | REDSHIFT | -187.1 | 1773 | 0 | 12698 | 0 | 8 |
| r100_inward | 7 | 0.05147 | ESC | NONE | -415.4 | 2493 | 0 | 17745 | 1.01 | nan |
| r100_inward | 8 | 0.05146 | DARK | REDSHIFT | -211 | 2252 | 0 | 16051 | 0 | 8 |
| r100_inward | 9 | 0.05146 | ESC | NONE | -439.3 | 2969 | 0 | 21077 | 1.01 | nan |
| r100_inward | 10 | 0.1015 | ESC | NONE | -372.3 | 1571 | 0 | 11291 | 1.01 | nan |
| r100_inward | 11 | 0.001462 | DARK | REDSHIFT | -144.3 | 830 | 0 | 6097 | 0 | 8 |
| r2p1_outward | 0 | 0 | ESC | NONE | -253.9 | 1120 | 1 | 8141 | 4.583 | nan |
| r2p1_outward | 1 | 3.142 | DARK | REDSHIFT | -32.25 | 186 | 0 | 1589 | 0 | 8 |
| r2p1_outward | 2 | 1.571 | DARK | REDSHIFT | -32.77 | 316 | 1 | 2506 | 0 | 8 |
| r2p1_outward | 3 | 0.5704 | ESC | NONE | -414.9 | 4263 | 2 | 30149 | 4.583 | nan |
| r2p1_outward | 4 | 0.5694 | ESC | NONE | -285.6 | 1686 | 1 | 12103 | 4.583 | nan |
| r2p1_outward | 5 | 0.5714 | DARK | REDSHIFT | -61.58 | 995 | 1 | 7259 | 0 | 8 |
| r2p1_outward | 6 | 0.5704 | ESC | NONE | -309.5 | 2162 | 2 | 15442 | 4.583 | nan |
| r2p1_outward | 7 | 0.5704 | DARK | REDSHIFT | -85.49 | 1474 | 1 | 10612 | 0 | 8 |
| r2p1_outward | 8 | 0.5704 | ESC | NONE | -333.5 | 2639 | 1 | 18774 | 4.583 | nan |
| r2p1_outward | 9 | 0.5704 | DARK | REDSHIFT | -109.4 | 1950 | 1 | 13944 | 0 | 8 |
| r2p1_outward | 10 | 0.6204 | DARK | REDSHIFT | -41.99 | 585 | 1 | 4389 | 0 | 8 |
| r2p1_outward | 11 | 0.5204 | ESC | NONE | -265 | 1303 | 2 | 9429 | 4.583 | nan |
| r1p5_freefall | 0 | 0 | DARK | REDSHIFT | -30.66 | 231 | 1 | 1911 | 0 | 8 |
| r1p5_freefall | 1 | 1.571 | ESC | NONE | -255.2 | 1130 | 1 | 8211 | 1 | nan |
| r1p5_freefall | 2 | 3.142 | ESC | NONE | -254.5 | 1143 | 1 | 8302 | 0.4641 | nan |
| r1p5_freefall | 3 | 2.356 | ESC | NONE | -254.6 | 1127 | 1 | 8190 | 0.5505 | nan |
## A Schwarzschild upper scan (min_step=1e-12, tol=1e-9)
| case | upper | mismatch | max_dn_ang | max_dgrel | max_dstopT | sum_rhs | max_rej | ESC | DARK | UNRES | INC |
|---|---|---|---|---|---|---|---|---|---|---|---|
| r100_inward | 0.25 | 0 | 2.604e-05 | 2.048e-09 | 35.2 | 93464 | 0 | 6 | 6 | 0 | 0 |
| r100_inward | 0.5 | 0 | 5.696e-05 | 2.056e-09 | 39.27 | 56595 | 0 | 6 | 6 | 0 | 0 |
| r100_inward | 1 | 0 | 8.348e-05 | 2.051e-09 | 41.25 | 39788 | 1 | 6 | 6 | 0 | 0 |
| r100_inward | 2 | 0 | 8.286e-05 | 2.026e-09 | 41.22 | 32389 | 1 | 6 | 6 | 0 | 0 |
| r100_inward | 4 | 0 | 8.089e-05 | 2.028e-09 | 41.09 | 29393 | 2 | 6 | 6 | 0 | 0 |
| r100_inward | 8 | 0 | 8.034e-05 | 2.034e-09 | 41.06 | 28427 | 3 | 6 | 6 | 0 | 0 |
| r100_inward | 16 | 0 | 8.034e-05 | 2.042e-09 | 41.06 | 28189 | 3 | 6 | 6 | 0 | 0 |
| r100_inward | 32 | 0 | 8.034e-05 | 2.043e-09 | 41.06 | 28182 | 3 | 6 | 6 | 0 | 0 |
| r100_inward | 64 | 0 | 8.034e-05 | 2.043e-09 | 41.06 | 28182 | 3 | 6 | 6 | 0 | 0 |
| r1p5_freefall | 0.25 | 0 | 1.002e-10 | 3.165e-10 | 2.608e-08 | 23597 | 0 | 3 | 1 | 0 | 0 |
| r1p5_freefall | 0.5 | 0 | 8.875e-11 | 3.658e-10 | 3.089e-08 | 12747 | 0 | 3 | 1 | 0 | 0 |
| r1p5_freefall | 1 | 0 | 8.187e-11 | 3.811e-10 | 2.817e-08 | 7553 | 0 | 3 | 1 | 0 | 0 |
| r1p5_freefall | 2 | 0 | 8.165e-11 | 3.719e-10 | 2.571e-08 | 5131 | 0 | 3 | 1 | 0 | 0 |
| r1p5_freefall | 4 | 0 | 8.157e-11 | 3.632e-10 | 2.346e-08 | 4039 | 0 | 3 | 1 | 0 | 0 |
| r1p5_freefall | 8 | 0 | 8.151e-11 | 3.547e-10 | 2.125e-08 | 3591 | 0 | 3 | 1 | 0 | 0 |
| r1p5_freefall | 16 | 0 | 8.146e-11 | 3.469e-10 | 1.925e-08 | 3465 | 0 | 3 | 1 | 0 | 0 |
| r1p5_freefall | 32 | 0 | 8.146e-11 | 3.46e-10 | 1.948e-08 | 3458 | 0 | 3 | 1 | 0 | 0 |
| r1p5_freefall | 64 | 0 | 8.146e-11 | 3.46e-10 | 1.948e-08 | 3458 | 0 | 3 | 1 | 0 | 0 |
| r2p1_outward | 0.25 | 1 | 0.0002872 | 2.661e-09 | 263.1 | 62258 | 0 | 5 | 7 | 0 | 0 |
| r2p1_outward | 0.5 | 1 | 0.0002872 | 2.644e-09 | 263.1 | 42490 | 0 | 5 | 7 | 0 | 0 |
| r2p1_outward | 1 | 1 | 0.0002872 | 2.615e-09 | 263.1 | 33544 | 1 | 5 | 7 | 0 | 0 |
| r2p1_outward | 2 | 1 | 0.0002872 | 2.592e-09 | 263.1 | 29421 | 1 | 5 | 7 | 0 | 0 |
| r2p1_outward | 4 | 1 | 0.0002872 | 2.598e-09 | 263.1 | 27580 | 1 | 5 | 7 | 0 | 0 |
| r2p1_outward | 8 | 1 | 0.0002872 | 2.606e-09 | 263.1 | 26831 | 1 | 5 | 7 | 0 | 0 |
| r2p1_outward | 16 | 1 | 0.0002872 | 2.614e-09 | 263.1 | 26614 | 1 | 5 | 7 | 0 | 0 |
| r2p1_outward | 32 | 1 | 0.0002872 | 2.615e-09 | 263.1 | 26607 | 1 | 5 | 7 | 0 | 0 |
| r2p1_outward | 64 | 1 | 0.0002872 | 2.615e-09 | 263.1 | 26607 | 1 | 5 | 7 | 0 | 0 |
| r30_inward | 0.25 | 0 | 8.667e-05 | 2.262e-09 | 35.08 | 76510 | 0 | 6 | 6 | 0 | 0 |
| r30_inward | 0.5 | 0 | 0.0001866 | 2.268e-09 | 39.06 | 49063 | 1 | 6 | 6 | 0 | 0 |
| r30_inward | 1 | 0 | 0.0002801 | 2.266e-09 | 41.17 | 36974 | 2 | 6 | 6 | 0 | 0 |
| r30_inward | 2 | 0 | 0.0002819 | 2.244e-09 | 41.2 | 31843 | 2 | 6 | 6 | 0 | 0 |
| r30_inward | 4 | 0 | 0.0002819 | 2.25e-09 | 41.2 | 29617 | 2 | 6 | 6 | 0 | 0 |
| r30_inward | 8 | 0 | 0.0002819 | 2.258e-09 | 41.2 | 28714 | 2 | 6 | 6 | 0 | 0 |
| r30_inward | 16 | 0 | 0.0002819 | 2.266e-09 | 41.2 | 28455 | 2 | 6 | 6 | 0 | 0 |
| r30_inward | 32 | 0 | 0.0002819 | 2.266e-09 | 41.2 | 28434 | 2 | 6 | 6 | 0 | 0 |
| r30_inward | 64 | 0 | 0.0002819 | 2.266e-09 | 41.2 | 28434 | 2 | 6 | 6 | 0 | 0 |
## A Schwarzschild min scan (max_step=2, tol=1e-9)
| case | min_step | mismatch | max_dn_ang | max_dgrel | max_dstopT | sum_rhs | max_rej | ESC | DARK | UNRES | INC |
|---|---|---|---|---|---|---|---|---|---|---|---|
| r100_inward | 1e-14 | 0 | 8.286e-05 | 2.026e-09 | 41.22 | 32389 | 1 | 6 | 6 | 0 | 0 |
| r100_inward | 1e-12 | 0 | 8.286e-05 | 2.026e-09 | 41.22 | 32389 | 1 | 6 | 6 | 0 | 0 |
| r100_inward | 1e-10 | 0 | 8.286e-05 | 2.026e-09 | 41.22 | 32389 | 1 | 6 | 6 | 0 | 0 |
| r100_inward | 1e-08 | 0 | 8.286e-05 | 2.026e-09 | 41.22 | 32389 | 1 | 6 | 6 | 0 | 0 |
| r100_inward | 1e-06 | 0 | 8.286e-05 | 2.026e-09 | 41.22 | 32389 | 1 | 6 | 6 | 0 | 0 |
| r100_inward | 1e-05 | 0 | 8.286e-05 | 2.026e-09 | 41.22 | 32389 | 1 | 6 | 6 | 0 | 0 |
| r100_inward | 0.0001 | 0 | 8.286e-05 | 2.026e-09 | 41.22 | 32389 | 1 | 6 | 6 | 0 | 0 |
| r100_inward | 0.001 | 0 | 8.286e-05 | 2.026e-09 | 41.22 | 32389 | 1 | 6 | 6 | 0 | 0 |
| r100_inward | 0.01 | 0 | 8.286e-05 | 2.026e-09 | 41.22 | 32389 | 1 | 6 | 6 | 0 | 0 |
| r100_inward | 0.1 | 0 | 8.286e-05 | 2.026e-09 | 41.22 | 32389 | 1 | 6 | 6 | 0 | 0 |
| r1p5_freefall | 1e-14 | 0 | 8.165e-11 | 3.719e-10 | 2.571e-08 | 5131 | 0 | 3 | 1 | 0 | 0 |
| r1p5_freefall | 1e-12 | 0 | 8.165e-11 | 3.719e-10 | 2.571e-08 | 5131 | 0 | 3 | 1 | 0 | 0 |
| r1p5_freefall | 1e-10 | 0 | 8.165e-11 | 3.719e-10 | 2.571e-08 | 5131 | 0 | 3 | 1 | 0 | 0 |
| r1p5_freefall | 1e-08 | 0 | 8.165e-11 | 3.719e-10 | 2.571e-08 | 5131 | 0 | 3 | 1 | 0 | 0 |
| r1p5_freefall | 1e-06 | 0 | 8.165e-11 | 3.719e-10 | 2.571e-08 | 5131 | 0 | 3 | 1 | 0 | 0 |
| r1p5_freefall | 1e-05 | 0 | 8.165e-11 | 3.719e-10 | 2.571e-08 | 5131 | 0 | 3 | 1 | 0 | 0 |
| r1p5_freefall | 0.0001 | 0 | 8.165e-11 | 3.719e-10 | 2.571e-08 | 5131 | 0 | 3 | 1 | 0 | 0 |
| r1p5_freefall | 0.001 | 0 | 8.165e-11 | 3.719e-10 | 2.571e-08 | 5131 | 0 | 3 | 1 | 0 | 0 |
| r1p5_freefall | 0.01 | 0 | 8.165e-11 | 3.719e-10 | 2.571e-08 | 5131 | 0 | 3 | 1 | 0 | 0 |
| r1p5_freefall | 0.1 | 1 | 8.165e-11 | 3.719e-10 | 254.3 | 3654 | 0 | 2 | 1 | 0 | 1 |
| r2p1_outward | 1e-14 | 1 | 0.0002872 | 2.592e-09 | 263.1 | 29421 | 1 | 5 | 7 | 0 | 0 |
| r2p1_outward | 1e-12 | 1 | 0.0002872 | 2.592e-09 | 263.1 | 29421 | 1 | 5 | 7 | 0 | 0 |
| r2p1_outward | 1e-10 | 1 | 0.0002872 | 2.592e-09 | 263.1 | 29421 | 1 | 5 | 7 | 0 | 0 |
| r2p1_outward | 1e-08 | 1 | 0.0002872 | 2.592e-09 | 263.1 | 29421 | 1 | 5 | 7 | 0 | 0 |
| r2p1_outward | 1e-06 | 1 | 0.0002872 | 2.592e-09 | 263.1 | 29421 | 1 | 5 | 7 | 0 | 0 |
| r2p1_outward | 1e-05 | 1 | 0.0002872 | 2.592e-09 | 263.1 | 29421 | 1 | 5 | 7 | 0 | 0 |
| r2p1_outward | 0.0001 | 1 | 0.0002872 | 2.592e-09 | 263.1 | 29421 | 1 | 5 | 7 | 0 | 0 |
| r2p1_outward | 0.001 | 1 | 0.0002872 | 2.592e-09 | 263.1 | 29421 | 1 | 5 | 7 | 0 | 0 |
| r2p1_outward | 0.01 | 1 | 0.0002872 | 2.592e-09 | 263.1 | 29421 | 1 | 5 | 7 | 0 | 0 |
| r2p1_outward | 0.1 | 1 | 0.0002872 | 2.592e-09 | 263.1 | 29421 | 1 | 5 | 7 | 0 | 0 |
| r30_inward | 1e-14 | 0 | 0.0002819 | 2.244e-09 | 41.2 | 31843 | 2 | 6 | 6 | 0 | 0 |
| r30_inward | 1e-12 | 0 | 0.0002819 | 2.244e-09 | 41.2 | 31843 | 2 | 6 | 6 | 0 | 0 |
| r30_inward | 1e-10 | 0 | 0.0002819 | 2.244e-09 | 41.2 | 31843 | 2 | 6 | 6 | 0 | 0 |
| r30_inward | 1e-08 | 0 | 0.0002819 | 2.244e-09 | 41.2 | 31843 | 2 | 6 | 6 | 0 | 0 |
| r30_inward | 1e-06 | 0 | 0.0002819 | 2.244e-09 | 41.2 | 31843 | 2 | 6 | 6 | 0 | 0 |
| r30_inward | 1e-05 | 0 | 0.0002819 | 2.244e-09 | 41.2 | 31843 | 2 | 6 | 6 | 0 | 0 |
| r30_inward | 0.0001 | 0 | 0.0002819 | 2.244e-09 | 41.2 | 31843 | 2 | 6 | 6 | 0 | 0 |
| r30_inward | 0.001 | 0 | 0.0002819 | 2.244e-09 | 41.2 | 31843 | 2 | 6 | 6 | 0 | 0 |
| r30_inward | 0.01 | 0 | 0.0002819 | 2.244e-09 | 41.2 | 31843 | 2 | 6 | 6 | 0 | 0 |
| r30_inward | 0.1 | 0 | 0.0002819 | 2.244e-09 | 41.2 | 31843 | 2 | 6 | 6 | 0 | 0 |
## A Schwarzschild sensitive RK4 .01 vs .005
| case | dir | theta | out.01 | out.005 | hhalve_dn_ang | |dg| | rhs.01 | rhs.005 |
|---|---|---|---|---|---|---|---|---|
| r2p1_outward | 6 | 0.5704 | ESC | ESC | 1.032e-07 | 7.386e-12 | 124072 | 247880 |
| r2p1_outward | 7 | 0.5704 | DARK | DARK | nan | 0 | 34196 | 68392 |
| r2p1_outward | 8 | 0.5704 | ESC | ESC | 1.04e-05 | 7.618e-12 | 133644 | 267024 |
| r2p1_outward | 9 | 0.5704 | DARK | DARK | nan | 0 | 43768 | 87536 |
| r30_inward | 6 | 0.1681 | DARK | DARK | nan | 0 | 47300 | 94600 |
| r30_inward | 7 | 0.1681 | ESC | ESC | 0 | 8.238e-14 | 138764 | 277264 |
| r30_inward | 8 | 0.1681 | DARK | DARK | nan | 0 | 56872 | 113744 |
| r30_inward | 9 | 0.1681 | ESC | ESC | 1.891e-07 | 7.55e-15 | 148336 | 296408 |
## A Minkowski flat analytic check (n_inf and t)
| upper | min_step | n | max_dn_ang | max_dgrel | max_t_err |
|---|---|---|---|---|---|
| 1 | 1e-12 | 6 | 0 | 0 | 5.258e-13 |
| 1 | 1e-06 | 6 | 0 | 0 | 5.258e-13 |
| 1 | 0.1 | 6 | 0 | 0 | 5.258e-13 |
| 4 | 1e-12 | 6 | 0 | 0 | 1.776e-12 |
| 4 | 1e-06 | 6 | 0 | 0 | 1.776e-12 |
| 4 | 0.1 | 6 | 0 | 0 | 1.776e-12 |
| 16 | 1e-12 | 6 | 0 | 0 | 1.3e-11 |
| 16 | 1e-06 | 6 | 0 | 0 | 1.3e-11 |
| 16 | 0.1 | 6 | 0 | 0 | 1.3e-11 |
| 64 | 1e-12 | 6 | 0 | 0 | 3.894e-11 |
| 64 | 1e-06 | 6 | 0 | 0 | 3.894e-11 |
| 64 | 0.1 | 6 | 0 | 0 | 3.894e-11 |
| 256 | 1e-12 | 6 | 0 | 0 | 7.661e-11 |
| 256 | 1e-06 | 6 | 0 | 0 | 7.661e-11 |
| 256 | 0.1 | 6 | 0 | 0 | 7.661e-11 |
## A Minkowski flat analytic check (crossing x and t)
| upper | min_step | n | max_x_err | max_t_err |
|---|---|---|---|---|
| 1 | 1e-12 | 6 | 4.832e-13 | 5.258e-13 |
| 1 | 1e-06 | 6 | 4.832e-13 | 5.258e-13 |
| 1 | 0.1 | 6 | 4.832e-13 | 5.258e-13 |
| 4 | 1e-12 | 6 | 1.72e-12 | 1.776e-12 |
| 4 | 1e-06 | 6 | 1.72e-12 | 1.776e-12 |
| 4 | 0.1 | 6 | 1.72e-12 | 1.776e-12 |
| 16 | 1e-12 | 6 | 1.268e-11 | 1.3e-11 |
| 16 | 1e-06 | 6 | 1.268e-11 | 1.3e-11 |
| 16 | 0.1 | 6 | 1.268e-11 | 1.3e-11 |
| 64 | 1e-12 | 6 | 3.506e-11 | 3.894e-11 |
| 64 | 1e-06 | 6 | 3.506e-11 | 3.894e-11 |
| 64 | 0.1 | 6 | 3.506e-11 | 3.894e-11 |
| 256 | 1e-12 | 6 | 7.061e-11 | 7.661e-11 |
| 256 | 1e-06 | 6 | 7.061e-11 | 7.661e-11 |
| 256 | 0.1 | 6 | 7.061e-11 | 7.661e-11 |
## A Alcubierre alc_v3_s1 upper scan
| case | upper | mismatch | max_dn_ang | max_dgrel | max_dstopT | sum_rhs | max_rej | ESC | DARK | UNRES | INC |
|---|---|---|---|---|---|---|---|---|---|---|---|
| alc_v3_s1 | 0.05 | 0 | 7.544e-14 | 7.627e-13 | 1.99e-13 | 19740 | 0 | 6 | 0 | 0 | 0 |
| alc_v3_s1 | 0.05 | 0 | 7.544e-14 | 7.627e-13 | 1.99e-13 | 19740 | 0 | 6 | 0 | 0 | 0 |
| alc_v3_s1 | 0.05 | 0 | 7.544e-14 | 7.627e-13 | 1.99e-13 | 19740 | 0 | 6 | 0 | 0 | 0 |
| alc_v3_s1 | 0.2 | 0 | 1.208e-10 | 3.452e-10 | 4.091e-10 | 6475 | 2 | 6 | 0 | 0 | 0 |
| alc_v3_s1 | 0.2 | 0 | 1.208e-10 | 3.452e-10 | 4.091e-10 | 6475 | 2 | 6 | 0 | 0 | 0 |
| alc_v3_s1 | 0.2 | 0 | 1.208e-10 | 3.452e-10 | 4.091e-10 | 6475 | 2 | 6 | 0 | 0 | 0 |
| alc_v3_s1 | 0.4 | 0 | 1.03e-10 | 3.464e-10 | 3.143e-10 | 4613 | 2 | 6 | 0 | 0 | 0 |
| alc_v3_s1 | 0.4 | 0 | 1.03e-10 | 3.464e-10 | 3.143e-10 | 4613 | 2 | 6 | 0 | 0 | 0 |
| alc_v3_s1 | 0.4 | 0 | 1.03e-10 | 3.464e-10 | 3.143e-10 | 4613 | 2 | 6 | 0 | 0 | 0 |
| alc_v3_s1 | 0.8 | 0 | 7.888e-11 | 7.954e-10 | 7.369e-10 | 3794 | 2 | 6 | 0 | 0 | 0 |
| alc_v3_s1 | 0.8 | 0 | 7.888e-11 | 7.954e-10 | 7.369e-10 | 3794 | 2 | 6 | 0 | 0 | 0 |
| alc_v3_s1 | 0.8 | 0 | 7.888e-11 | 7.954e-10 | 7.369e-10 | 3794 | 2 | 6 | 0 | 0 | 0 |
| alc_v3_s1 | 1.6 | 0 | 1.239e-10 | 1.118e-09 | 1.148e-09 | 3430 | 2 | 6 | 0 | 0 | 0 |
| alc_v3_s1 | 1.6 | 0 | 1.239e-10 | 1.118e-09 | 1.148e-09 | 3430 | 2 | 6 | 0 | 0 | 0 |
| alc_v3_s1 | 1.6 | 0 | 1.239e-10 | 1.118e-09 | 1.148e-09 | 3430 | 2 | 6 | 0 | 0 | 0 |
| alc_v3_s1 | 3.2 | 0 | 1.545e-10 | 1.26e-09 | 1.346e-09 | 3290 | 2 | 6 | 0 | 0 | 0 |
| alc_v3_s1 | 3.2 | 0 | 1.545e-10 | 1.26e-09 | 1.346e-09 | 3290 | 2 | 6 | 0 | 0 | 0 |
| alc_v3_s1 | 3.2 | 0 | 1.545e-10 | 1.26e-09 | 1.346e-09 | 3290 | 2 | 6 | 0 | 0 | 0 |
## A Alcubierre alc_v3_s10 upper scan
| case | upper | mismatch | max_dn_ang | max_dgrel | max_dstopT | sum_rhs | max_rej | ESC | DARK | UNRES | INC |
|---|---|---|---|---|---|---|---|---|---|---|---|
| alc_v3_s10 | 0.005 | 0 | 5.829e-15 | 3.109e-14 | 4.663e-14 | 27412 | 0 | 6 | 0 | 0 | 0 |
| alc_v3_s10 | 0.005 | 0 | 5.829e-15 | 3.109e-14 | 4.663e-14 | 27412 | 0 | 6 | 0 | 0 | 0 |
| alc_v3_s10 | 0.005 | 0 | 5.829e-15 | 3.109e-14 | 4.663e-14 | 27412 | 0 | 6 | 0 | 0 | 0 |
| alc_v3_s10 | 0.02 | 0 | 6.299e-12 | 2.402e-10 | 1.399e-11 | 8337 | 4 | 6 | 0 | 0 | 0 |
| alc_v3_s10 | 0.02 | 0 | 6.299e-12 | 2.402e-10 | 1.399e-11 | 8337 | 4 | 6 | 0 | 0 | 0 |
| alc_v3_s10 | 0.02 | 0 | 6.299e-12 | 2.402e-10 | 1.399e-11 | 8337 | 4 | 6 | 0 | 0 | 0 |
| alc_v3_s10 | 0.04 | 0 | 3.288e-11 | 3.854e-10 | 6.294e-11 | 5621 | 6 | 6 | 0 | 0 | 0 |
| alc_v3_s10 | 0.04 | 0 | 3.288e-11 | 3.854e-10 | 6.294e-11 | 5621 | 6 | 6 | 0 | 0 | 0 |
| alc_v3_s10 | 0.04 | 0 | 3.288e-11 | 3.854e-10 | 6.294e-11 | 5621 | 6 | 6 | 0 | 0 | 0 |
| alc_v3_s10 | 0.08 | 0 | 1.042e-11 | 2.781e-10 | 8.024e-11 | 4648 | 12 | 6 | 0 | 0 | 0 |
| alc_v3_s10 | 0.08 | 0 | 1.042e-11 | 2.781e-10 | 8.024e-11 | 4648 | 12 | 6 | 0 | 0 | 0 |
| alc_v3_s10 | 0.08 | 0 | 1.042e-11 | 2.781e-10 | 8.024e-11 | 4648 | 12 | 6 | 0 | 0 | 0 |
| alc_v3_s10 | 0.16 | 0 | 1.131e-11 | 7.6e-10 | 9.538e-11 | 4270 | 13 | 6 | 0 | 0 | 0 |
| alc_v3_s10 | 0.16 | 0 | 1.131e-11 | 7.6e-10 | 9.538e-11 | 4270 | 13 | 6 | 0 | 0 | 0 |
| alc_v3_s10 | 0.16 | 0 | 1.131e-11 | 7.6e-10 | 9.538e-11 | 4270 | 13 | 6 | 0 | 0 | 0 |
| alc_v3_s10 | 0.32 | 0 | 3.131e-11 | 9.778e-10 | 1.041e-10 | 4116 | 13 | 6 | 0 | 0 | 0 |
| alc_v3_s10 | 0.32 | 0 | 3.131e-11 | 9.778e-10 | 1.041e-10 | 4116 | 13 | 6 | 0 | 0 | 0 |
| alc_v3_s10 | 0.32 | 0 | 3.131e-11 | 9.778e-10 | 1.041e-10 | 4116 | 13 | 6 | 0 | 0 | 0 |
## A Alcubierre alc_v9_s1 upper scan
| case | upper | mismatch | max_dn_ang | max_dgrel | max_dstopT | sum_rhs | max_rej | ESC | DARK | UNRES | INC |
|---|---|---|---|---|---|---|---|---|---|---|---|
| alc_v9_s1 | 0.05 | 0 | 6.632e-13 | 2.591e-11 | 1.387e-11 | 39452 | 0 | 6 | 0 | 0 | 0 |
| alc_v9_s1 | 0.05 | 0 | 6.632e-13 | 2.591e-11 | 1.387e-11 | 39452 | 0 | 6 | 0 | 0 | 0 |
| alc_v9_s1 | 0.05 | 0 | 6.632e-13 | 2.591e-11 | 1.387e-11 | 39452 | 0 | 6 | 0 | 0 | 0 |
| alc_v9_s1 | 0.2 | 0 | 1.078e-10 | 2.073e-09 | 6.805e-10 | 11690 | 3 | 6 | 0 | 0 | 0 |
| alc_v9_s1 | 0.2 | 0 | 1.078e-10 | 2.073e-09 | 6.805e-10 | 11690 | 3 | 6 | 0 | 0 | 0 |
| alc_v9_s1 | 0.2 | 0 | 1.078e-10 | 2.073e-09 | 6.805e-10 | 11690 | 3 | 6 | 0 | 0 | 0 |
| alc_v9_s1 | 0.4 | 0 | 6.25e-11 | 2.466e-09 | 1.083e-09 | 7413 | 3 | 6 | 0 | 0 | 0 |
| alc_v9_s1 | 0.4 | 0 | 6.25e-11 | 2.466e-09 | 1.083e-09 | 7413 | 3 | 6 | 0 | 0 | 0 |
| alc_v9_s1 | 0.4 | 0 | 6.25e-11 | 2.466e-09 | 1.083e-09 | 7413 | 3 | 6 | 0 | 0 | 0 |
| alc_v9_s1 | 0.8 | 0 | 5.349e-11 | 2.718e-09 | 1.186e-09 | 5369 | 3 | 6 | 0 | 0 | 0 |
| alc_v9_s1 | 0.8 | 0 | 5.349e-11 | 2.718e-09 | 1.186e-09 | 5369 | 3 | 6 | 0 | 0 | 0 |
| alc_v9_s1 | 0.8 | 0 | 5.349e-11 | 2.718e-09 | 1.186e-09 | 5369 | 3 | 6 | 0 | 0 | 0 |
| alc_v9_s1 | 1.6 | 0 | 1.035e-10 | 2.819e-09 | 9.549e-10 | 4431 | 3 | 6 | 0 | 0 | 0 |
| alc_v9_s1 | 1.6 | 0 | 1.035e-10 | 2.819e-09 | 9.549e-10 | 4431 | 3 | 6 | 0 | 0 | 0 |
| alc_v9_s1 | 1.6 | 0 | 1.035e-10 | 2.819e-09 | 9.549e-10 | 4431 | 3 | 6 | 0 | 0 | 0 |
| alc_v9_s1 | 3.2 | 0 | 1.239e-10 | 2.823e-09 | 3.249e-09 | 3983 | 3 | 6 | 0 | 0 | 0 |
| alc_v9_s1 | 3.2 | 0 | 1.239e-10 | 2.823e-09 | 3.249e-09 | 3983 | 3 | 6 | 0 | 0 | 0 |
| alc_v9_s1 | 3.2 | 0 | 1.239e-10 | 2.823e-09 | 3.249e-09 | 3983 | 3 | 6 | 0 | 0 | 0 |
## A Alcubierre alc_v9_s10 upper scan
| case | upper | mismatch | max_dn_ang | max_dgrel | max_dstopT | sum_rhs | max_rej | ESC | DARK | UNRES | INC |
|---|---|---|---|---|---|---|---|---|---|---|---|
| alc_v9_s10 | 0.005 | 0 | 8.937e-14 | 1.103e-11 | 3.553e-13 | 48167 | 0 | 6 | 0 | 0 | 0 |
| alc_v9_s10 | 0.005 | 0 | 8.937e-14 | 1.103e-11 | 3.553e-13 | 48167 | 0 | 6 | 0 | 0 | 0 |
| alc_v9_s10 | 0.005 | 0 | 8.937e-14 | 1.103e-11 | 3.553e-13 | 48167 | 0 | 6 | 0 | 0 | 0 |
| alc_v9_s10 | 0.02 | 0 | 5.331e-11 | 6.906e-10 | 1.092e-10 | 13692 | 6 | 6 | 0 | 0 | 0 |
| alc_v9_s10 | 0.02 | 0 | 5.331e-11 | 6.906e-10 | 1.092e-10 | 13692 | 6 | 6 | 0 | 0 | 0 |
| alc_v9_s10 | 0.02 | 0 | 5.331e-11 | 6.906e-10 | 1.092e-10 | 13692 | 6 | 6 | 0 | 0 | 0 |
| alc_v9_s10 | 0.04 | 0 | 1.752e-10 | 9.749e-10 | 3.646e-10 | 8484 | 7 | 6 | 0 | 0 | 0 |
| alc_v9_s10 | 0.04 | 0 | 1.752e-10 | 9.749e-10 | 3.646e-10 | 8484 | 7 | 6 | 0 | 0 | 0 |
| alc_v9_s10 | 0.04 | 0 | 1.752e-10 | 9.749e-10 | 3.646e-10 | 8484 | 7 | 6 | 0 | 0 | 0 |
| alc_v9_s10 | 0.08 | 0 | 1.196e-10 | 1.457e-09 | 2.695e-10 | 6251 | 12 | 6 | 0 | 0 | 0 |
| alc_v9_s10 | 0.08 | 0 | 1.196e-10 | 1.457e-09 | 2.695e-10 | 6251 | 12 | 6 | 0 | 0 | 0 |
| alc_v9_s10 | 0.08 | 0 | 1.196e-10 | 1.457e-09 | 2.695e-10 | 6251 | 12 | 6 | 0 | 0 | 0 |
| alc_v9_s10 | 0.16 | 0 | 1.727e-11 | 1.077e-09 | 1.26e-10 | 5278 | 15 | 6 | 0 | 0 | 0 |
| alc_v9_s10 | 0.16 | 0 | 1.727e-11 | 1.077e-09 | 1.26e-10 | 5278 | 15 | 6 | 0 | 0 | 0 |
| alc_v9_s10 | 0.16 | 0 | 1.727e-11 | 1.077e-09 | 1.26e-10 | 5278 | 15 | 6 | 0 | 0 | 0 |
| alc_v9_s10 | 0.32 | 0 | 5.499e-11 | 8.68e-10 | 2.555e-10 | 4872 | 14 | 6 | 0 | 0 | 0 |
| alc_v9_s10 | 0.32 | 0 | 5.499e-11 | 8.68e-10 | 2.555e-10 | 4872 | 14 | 6 | 0 | 0 | 0 |
| alc_v9_s10 | 0.32 | 0 | 5.499e-11 | 8.68e-10 | 2.555e-10 | 4872 | 14 | 6 | 0 | 0 | 0 |
## A Alcubierre v9 s100 lower-bound stress (upper=0.004)
| case | min_step | mismatch | max_dn_ang | max_dgrel | max_dstopT | sum_rhs | max_rej | ESC | DARK | UNRES | INC |
|---|---|---|---|---|---|---|---|---|---|---|---|
| alc_v9_s100 | 1e-14 | 0 | 1.695e-11 | 1.332e-09 | 1.057e-11 | 18095 | 7 | 6 | 0 | 0 | 0 |
| alc_v9_s100 | 1e-12 | 0 | 1.695e-11 | 1.332e-09 | 1.057e-11 | 18095 | 7 | 6 | 0 | 0 | 0 |
| alc_v9_s100 | 1e-08 | 0 | 1.695e-11 | 1.332e-09 | 1.057e-11 | 18095 | 7 | 6 | 0 | 0 | 0 |
| alc_v9_s100 | 1e-06 | 0 | 1.695e-11 | 1.332e-09 | 1.057e-11 | 18095 | 7 | 6 | 0 | 0 | 0 |
| alc_v9_s100 | 1e-05 | 0 | 1.695e-11 | 1.332e-09 | 1.057e-11 | 18095 | 7 | 6 | 0 | 0 | 0 |
| alc_v9_s100 | 0.0001 | 0 | 1.695e-11 | 1.332e-09 | 1.057e-11 | 18095 | 7 | 6 | 0 | 0 | 0 |
## B observed accepted step h (finite T=min(20, ref span))
| case | dir | ref | span | target | steps | reached | term | h_min | h_max | h_first | bnd | rhs | rej | null_max |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| r30_inward | 0 | DARK | 69.57 | -20 | 17 | 1 | -1 | 0.1 | 1.726 | 0.1 | 1 | 119 | 0 | 3.009e-12 |
| r30_inward | 1 | ESC | 226 | -20 | 13 | 1 | -1 | 0.1 | 2 | 0.1 | 1 | 91 | 0 | 8.69e-12 |
| r30_inward | 2 | DARK | 142.2 | -20 | 20 | 1 | -1 | 0.1 | 1.408 | 0.1 | 1 | 140 | 0 | 1.526e-11 |
| r30_inward | 3 | ESC | 370.2 | -20 | 20 | 1 | -1 | 0.1 | 1.408 | 0.1 | 1 | 140 | 0 | 1.526e-11 |
| r30_inward | 4 | DARK | 118.2 | -20 | 20 | 1 | -1 | 0.1 | 1.408 | 0.1 | 1 | 140 | 0 | 1.526e-11 |
| r30_inward | 5 | ESC | 346.3 | -20 | 20 | 1 | -1 | 0.1 | 1.408 | 0.1 | 1 | 140 | 0 | 1.526e-11 |
| r100_inward | 0 | DARK | 144.3 | -20 | 12 | 1 | -1 | 0.1 | 2 | 0.1 | 1 | 84 | 0 | 1.454e-14 |
| r100_inward | 1 | ESC | 156 | -20 | 12 | 1 | -1 | 0.1 | 2 | 0.1 | 1 | 84 | 0 | 7.661e-15 |
| r100_inward | 2 | DARK | 301.1 | -20 | 12 | 1 | -1 | 0.1 | 2 | 0.1 | 1 | 84 | 0 | 1.399e-14 |
| r100_inward | 3 | DARK | 211 | -20 | 12 | 1 | -1 | 0.1 | 2 | 0.1 | 1 | 84 | 0 | 1.321e-14 |
| r2p1_outward | 0 | ESC | 253.9 | -20 | 46 | 1 | -1 | 0.1 | 1.071 | 0.1 | 1 | 322 | 0 | 6.274e-11 |
| r2p1_outward | 1 | ESC | 414.9 | -20 | 105 | 1 | -1 | 0.1 | 0.2115 | 0.1 | 1 | 735 | 0 | 4.698e-11 |
| r2p1_outward | 2 | ESC | 333.5 | -20 | 105 | 1 | -1 | 0.1 | 0.2115 | 0.1 | 1 | 735 | 0 | 4.698e-11 |
| r2p1_outward | 3 | DARK | 109.4 | -20 | 105 | 1 | -1 | 0.1 | 0.2115 | 0.1 | 1 | 735 | 0 | 4.698e-11 |
| r2p1_outward | 4 | DARK | 32.25 | -20 | 37 | 1 | -1 | 0.1 | 0.8994 | 0.1 | 1 | 259 | 0 | 2.991e-11 |
| r1p5_freefall | 0 | DARK | 30.66 | -20 | 49 | 1 | -1 | 0.1 | 0.696 | 0.1 | 1 | 343 | 0 | 4.938e-10 |
| r1p5_freefall | 1 | ESC | 254.5 | -20 | 52 | 1 | -1 | 0.0921 | 1.084 | 0.1 | 1 | 364 | 0 | 4.034e-11 |
| mink_moving | 0 | ESC | 51.26 | -20 | 3 | 1 | -1 | 1 | 14 | 1 | 1 | 21 | 0 | 2.22e-16 |
| mink_moving | 1 | ESC | 71.58 | -20 | 3 | 1 | -1 | 1 | 14 | 1 | 1 | 21 | 0 | 0 |
| alc_v3_s1 | 0 | ESC | 16.43 | -16.43 | 56 | 1 | -1 | 0.05 | 0.4 | 0.05 | 1 | 406 | 2 | 0 |
| alc_v3_s1 | 1 | ESC | 29.38 | -20 | 62 | 1 | -1 | 0.05 | 0.4 | 0.05 | 1 | 441 | 1 | 0 |
| alc_v9_s10 | 0 | ESC | 2.037 | -2.037 | 77 | 1 | -1 | 0.005 | 0.04 | 0.005 | 1 | 588 | 7 | 0 |
| alc_v9_s10 | 1 | ESC | 19.96 | -19.96 | 521 | 1 | -1 | 0.005 | 0.04 | 0.005 | 1 | 3682 | 5 | 2.22e-16 |
| alc_v9_s100 | 0 | ESC | 1.104 | -1.104 | 302 | 1 | -1 | 0.0005 | 0.004 | 0.0005 | 1 | 2163 | 7 | 0 |
| alc_v9_s100 | 1 | ESC | 2.896 | -2.896 | 746 | 1 | -1 | 0.0005 | 0.004 | 0.0005 | 1 | 5257 | 5 | 2.22e-16 |
+124
View File
@@ -0,0 +1,124 @@
#!/usr/bin/env python3
"""One-task, at-most-two-attempt 4K comparison (R100, 3840x2160, FOV45).
This driver does NOT run as part of routine verification. It refuses to do
anything unless ``--authorize-two-4k`` is passed explicitly, and an attempt
ledger makes the task non-repeatable. Any future execution requires a fresh,
explicit user authorization for that task; authorization never extends to later
tasks or routine tests.
Usage:
run_4k_pair.py --authorize-two-4k [--out DIR]
OUT defaults to <repo>/local/adaptive_bounds_4k (distinct from any older
one-off ledger). The renderer binary is expected at
build/Release/schwarzschild_sky (see README for the make command).
"""
import argparse
import hashlib
import json
import os
from pathlib import Path
import shlex
import subprocess
import sys
import time
from mesh_maps import load, compare
ROOT = Path(__file__).resolve().parents[2]
ENV = dict(os.environ, OMP_NUM_THREADS='16', OMP_DYNAMIC='FALSE')
BUILD_CMD = 'make -j4 BUILD_TYPE=Release SPACETIME=schwarzschild backend'
REFUSAL = (
'REFUSING TO RUN: the 4K hmax 2-vs-8 pair is a one-task, at-most-two-attempt '
'experiment. Pass --authorize-two-4k only after an explicit fresh user '
'authorization for this task. It is not a routine verification target.')
def main():
ap = argparse.ArgumentParser(description=__doc__)
ap.add_argument('--authorize-two-4k', action='store_true',
help='explicit authorization for the two-attempt 4K pair')
ap.add_argument('--out', default=str(ROOT / 'local/adaptive_bounds_4k'),
help='output directory (default: local/adaptive_bounds_4k)')
args = ap.parse_args()
if not args.authorize_two_4k:
print(REFUSAL, file=sys.stderr)
return 2
out = Path(args.out).resolve()
binary = ROOT / 'build/Release/schwarzschild_sky'
if not binary.exists():
print(f'missing {binary}; build it first:\n {BUILD_CMD}',
file=sys.stderr)
return 2
out.mkdir(parents=True, exist_ok=True)
ledger = out / 'attempts.json'
attempts = json.loads(ledger.read_text()) if ledger.exists() else []
if attempts:
print('This one-task pair has already been attempted; no reruns '
'authorized.', file=sys.stderr)
return 3
catalog = out / 'single_dim_star.csv'
catalog.write_text('longitude_deg,latitude_deg,temperature_K,amplitude\n'
'262.5,-30,6000,1e-30\n')
with (out / 'cpu.txt').open('w') as f:
subprocess.run(['lscpu'], stdout=f, check=True)
metadata = {
'binary_sha256': hashlib.sha256(binary.read_bytes()).hexdigest(),
'utc': time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()),
'source_sha256': {
p: hashlib.sha256((ROOT / p).read_bytes()).hexdigest()
for p in ('src/main.c', 'src/geodesic.c', 'src/frame.c')},
'catalog': 'one negligible synthetic point; no survey catalog',
'scope': 'README R100 45-degree 3840x2160 coarse16 refine4 jacobian0.2',
'threads': 16, 'max_attempts': 2,
'authorization': 'explicit --authorize-two-4k for this task only'}
(out / 'metadata.json').write_text(json.dumps(metadata, indent=2) + '\n')
maps = {}
results = {}
for upper in (2, 8):
name = f'r100_4k_hmax{upper}'
cmd = [str(binary), '--integrator', 'dp54', '--width', '3840',
'--height', '2160', '--look-ra-deg', '262.5',
'--look-dec-deg', '-30', '--fov-deg', '45',
'--observer-radius', '100', '--coarse-cell-pixels', '16',
'--refine-max-level', '4', '--refine-jacobian-min', '.2',
'--catalog', str(catalog), '--exposure', '1',
'--tone-map', 'reinhard', '--psf-min-y', '1e-20',
'--psf-relative-tail', '1e-4', '--ode-min-step', '1e-12',
'--ode-max-step', str(upper), '--verbose',
'--lens-map-output', str(out / (name + '.grlens')),
'--output', str(out / (name + '.png'))]
attempts.append({'upper': upper, 'command': shlex.join(cmd),
'state': 'attempted'})
ledger.write_text(json.dumps(attempts, indent=2) + '\n')
start = time.perf_counter()
with (out / (name + '.log')).open('w') as log:
log.write('OMP_NUM_THREADS=16 OMP_DYNAMIC=FALSE ' +
shlex.join(cmd) + '\n')
log.flush()
result = subprocess.run(cmd, cwd=ROOT, env=ENV, stdout=log,
stderr=subprocess.STDOUT, timeout=900)
attempts[-1].update(returncode=result.returncode,
wall_seconds=time.perf_counter() - start,
state='completed')
ledger.write_text(json.dumps(attempts, indent=2) + '\n')
if result.returncode:
raise SystemExit(f'4K attempt failed: {name}; no automatic retry.')
info, maps[upper] = load(out / (name + '.grlens'))
info['wall_seconds'] = attempts[-1]['wall_seconds']
results[name] = info
print(name, json.dumps(info), flush=True)
results['comparison'] = compare(maps[2], maps[8])
p, q = (results[f'r100_4k_hmax{h}']['provenance'] for h in (2, 8))
assert p['max_step'] == 2 and q['max_step'] == 8
assert {k: v for k, v in p.items() if k != 'max_step'} == \
{k: v for k, v in q.items() if k != 'max_step'}
print('comparison', json.dumps(results['comparison']), flush=True)
(out / 'summary.json').write_text(json.dumps(results, indent=2) + '\n')
return 0
if __name__ == '__main__':
sys.exit(main())
@@ -0,0 +1,36 @@
#!/usr/bin/env bash
# Run the standalone critical-classification reference check
# (benchmarks/adaptive_step_bounds_2026-10-05/critical_ref_check.c).
#
# benchmarks/adaptive_step_bounds_2026-10-05/run_critical_ref.sh [OUT_DIR]
#
# OUT_DIR defaults to <repo>/local/adaptive_step_bounds_2026-10-05. All output
# stays under OUT_DIR; the tracked benchmark directory holds only the sources.
# This is a diagnostic probe (see the C file header); it makes no pass/fail
# claim about the exactly-critical physical outcome.
set -euo pipefail
SOURCE=$(cd "$(dirname "$0")" && pwd)
root=$(cd "$SOURCE/../.." && pwd)
OUT=${1:-$root/local/adaptive_step_bounds_2026-10-05}
BIN=/tmp/opencode/step_bounds
mkdir -p "$OUT/raw" "$OUT/logs" "$BIN"
cd "$OUT"
OUT=$PWD
{
echo "### building critical_ref_check"
set -x
cc -std=c11 -O2 -Wall -Wextra -Wpedantic -I"$root/src" \
"$SOURCE/critical_ref_check.c" "$root/src/geodesic.c" \
"$root/src/asymptotic.c" "$root/src/asymptotic_schwarzschild.c" \
"$root/src/spacetime_common.c" "$root/src/observer.c" -lm \
-o "$BIN/critical_ref_check"
echo "### command: critical_ref_check"
time "$BIN/critical_ref_check"
set +x
} > logs/critical_ref.log 2>&1
cat logs/critical_ref.log
echo "outputs under $OUT"
@@ -0,0 +1,36 @@
#!/usr/bin/env bash
# Build and run the failure-path floor diagnostic.
#
# benchmarks/adaptive_step_bounds_2026-10-05/run_failure_floor.sh [OUT_DIR]
#
# OUT_DIR defaults to <repo>/local/adaptive_bounds_mesh. All output stays
# under OUT_DIR; the tracked benchmark directory holds only the sources.
#
# failure_floor.c includes ../../tests/test_geodesic_adaptive.c (depth-2 path
# valid from this directory) and therefore requires -DGEODESIC_EVENT_TESTING
# and the production RayPool source src/ray.c. The two analytic backends are
# textually included by the regression file, so they are not linked here.
set -euo pipefail
SOURCE=$(cd "$(dirname "$0")" && pwd)
root=$(cd "$SOURCE/../.." && pwd)
OUT=${1:-$root/local/adaptive_bounds_mesh}
BIN=/tmp/opencode/step_bounds
mkdir -p "$OUT/logs" "$BIN"
cd "$OUT"
{
set -x
cc -std=c11 -O2 -Wall -Wextra -Wpedantic -fopenmp -DGEODESIC_EVENT_TESTING \
-I"$root/src" \
"$SOURCE/failure_floor.c" "$root/src/geodesic.c" "$root/src/ray.c" \
"$root/src/spacetime_common.c" "$root/src/observer.c" \
"$root/src/asymptotic.c" "$root/src/asymptotic_schwarzschild.c" -lm \
-o "$BIN/failure_floor"
"$BIN/failure_floor"
set +x
} > logs/failure_floor.log 2>&1
cat logs/failure_floor.log
echo "outputs under $OUT"
+84
View File
@@ -0,0 +1,84 @@
#!/usr/bin/env bash
# Long-term reproduction driver for the adaptive step-bound scan (Experiment A/B).
#
# benchmarks/adaptive_step_bounds_2026-10-05/run_limited.sh [OUT_DIR]
#
# OUT_DIR defaults to <repo>/local/adaptive_step_bounds_2026-10-05. All build
# and run output — env/hash record, build log, stdout logs and per-ray CSV —
# is written under OUT_DIR, never into this tracked benchmark directory.
# Scratch binaries go to /tmp/opencode/step_bounds.
set -euo pipefail
SOURCE=$(cd "$(dirname "$0")" && pwd)
root=$(cd "$SOURCE/../.." && pwd)
OUT=${1:-$root/local/adaptive_step_bounds_2026-10-05}
BIN=/tmp/opencode/step_bounds
mkdir -p "$OUT/raw" "$OUT/logs" "$BIN"
cd "$OUT"
OUT=$PWD
SRC_COMMON="$root/src/geodesic.c $root/src/asymptotic.c \
$root/src/asymptotic_schwarzschild.c $root/src/spacetime_common.c \
$root/src/observer.c"
# --- environment + source identity (full shell output) --------------------
{
echo "# adaptive_step_bounds_2026-10-05 environment and source identity"
echo "date_utc: $(date -u +%Y-%m-%dT%H:%M:%SZ)"
echo "host: $(uname -a)"
echo "nproc: $(nproc)"
echo "cc:"; cc --version
echo
echo "git_head: $(git -C "$root" rev-parse HEAD 2>/dev/null || echo none)"
echo "git_status_short:"; git -C "$root" status --short 2>/dev/null || true
echo
echo "# sha256 of the production sources consumed by the harnesses"
sha256sum $SRC_COMMON
echo
echo "# sha256 of the benchmark harnesses"
sha256sum "$SOURCE/a_public_endpoints.c" "$SOURCE/b_actual_h.c" \
"$SOURCE/summarize.py" "$SOURCE/run_limited.sh"
} > logs/env.txt 2>&1
cat logs/env.txt
# --- build ----------------------------------------------------------------
{
echo "### building a_public_endpoints"
set -x
cc -std=c11 -O2 -Wall -Wextra -Wpedantic -I"$root/src" \
"$SOURCE/a_public_endpoints.c" $SRC_COMMON -lm -o "$BIN/a_public"
echo "### building b_actual_h"
cc -std=c11 -O2 -Wall -Wextra -Wpedantic -I"$root/src" \
"$SOURCE/b_actual_h.c" "$root/src/asymptotic.c" \
"$root/src/asymptotic_schwarzschild.c" "$root/src/spacetime_common.c" \
"$root/src/observer.c" -lm -o "$BIN/b_actual"
set +x
} > logs/build.log 2>&1
cat logs/build.log
# --- Experiment A ---------------------------------------------------------
{
echo "### command: a_public schwarzschild"
set -x; time "$BIN/a_public" schwarzschild; set +x
echo "### command: a_public minkowski"
set -x; time "$BIN/a_public" minkowski; set +x
echo "### command: a_public alcubierre"
set -x; time "$BIN/a_public" alcubierre; set +x
} > logs/expA.log 2>&1
grep -E '^(SUMMARY|TOTAL_RAYS|real|user)' logs/expA.log | tail -20
# --- Experiment B ---------------------------------------------------------
{
echo "### command: b_actual"
set -x; time "$BIN/b_actual"; set +x
} > logs/expB.log 2>&1
cat logs/expB.log
# --- summary tables -------------------------------------------------------
python3 "$SOURCE/summarize.py" "$OUT"
echo
echo "rays A = $(grep '^TOTAL_RAYS' logs/expA.log | awk '{s+=$2} END{print s}')"
echo "observed steps B = $(grep '^TOTAL_OBSERVED_STEPS' logs/expB.log | awk '{print $2}')"
echo "outputs under $OUT"
+84
View File
@@ -0,0 +1,84 @@
#!/usr/bin/env python3
"""Bounded 320x180 adaptive-mesh candidates (8 cases; no 4K, no references).
Usage:
run_mesh.py [OUT_DIR]
OUT_DIR defaults to <repo>/local/adaptive_bounds_mesh. The renderer binary is
expected to already exist at build/Release/schwarzschild_sky; build it with
(make command is also in README.md):
make -j4 BUILD_TYPE=Release SPACETIME=schwarzschild backend
No survey catalog is used: the script writes a one-point synthetic dim catalog
with the required column header (longitude_deg,latitude_deg,temperature_K,
amplitude) and a 1e-30 amplitude star. No external CSV is read.
"""
import json
import os
from pathlib import Path
import shlex
import subprocess
import sys
import time
from mesh_maps import load, compare # tracked sibling module (no local/ import)
ROOT = Path(__file__).resolve().parents[2]
OUT = Path(sys.argv[1]).resolve() if len(sys.argv) > 1 else \
ROOT / 'local/adaptive_bounds_mesh'
BIN = ROOT / 'build/Release/schwarzschild_sky'
BUILD_CMD = 'make -j4 BUILD_TYPE=Release SPACETIME=schwarzschild backend'
ENV = dict(os.environ, OMP_NUM_THREADS='8', OMP_DYNAMIC='FALSE')
def scene_args(scene):
if scene == 'r100':
return ['--observer-radius', '100', '--look-ra-deg', '262.5',
'--look-dec-deg', '-30', '--fov-deg', '45',
'--refine-jacobian-min', '.2']
return ['--observer-position', '2.1', '0', '0', '--observer-velocity',
'0', '0', '0', '--look-ra-deg', '0', '--look-dec-deg', '0',
'--fov-deg', '90']
def main():
if not BIN.exists():
raise SystemExit(f'missing {BIN}; build it first:\n {BUILD_CMD}')
OUT.mkdir(parents=True, exist_ok=True)
catalog = OUT / 'single_dim_star.csv'
catalog.write_text('longitude_deg,latitude_deg,temperature_K,amplitude\n'
'262.5,-30,6000,1e-30\n')
results = {}
for scene in ('r100', 'r2p1'):
meshes = {}
for upper in (.5, 2, 8, 32):
name = f'{scene}_hmax{upper:g}'
cmd = [str(BIN), '--integrator', 'dp54',
'--width', '320', '--height', '180',
'--coarse-cell-pixels', '8', '--refine-max-level', '3',
'--catalog', str(catalog), '--exposure', '1',
'--psf-min-y', '1e-20', '--psf-relative-tail', '1e-4',
'--ode-min-step', '1e-12', '--ode-max-step', str(upper),
'--verbose', '--lens-map-output', str(OUT / (name + '.grlens')),
'--output', str(OUT / (name + '.png')), *scene_args(scene)]
start = time.perf_counter()
with (OUT / (name + '.log')).open('w') as log:
log.write('OMP_NUM_THREADS=8 OMP_DYNAMIC=FALSE ' +
shlex.join(cmd) + '\n')
log.flush()
subprocess.run(cmd, cwd=ROOT, env=ENV, stdout=log,
stderr=subprocess.STDOUT, check=True, timeout=180)
info, meshes[upper] = load(OUT / (name + '.grlens'))
info['wall_seconds'] = time.perf_counter() - start
results[name] = info
print(name, json.dumps(info), flush=True)
for upper in (.5, 8, 32):
comparison = compare(meshes[2], meshes[upper])
results[f'{scene}_2_vs_{upper:g}'] = comparison
print(f'{scene} 2 vs {upper:g}', json.dumps(comparison), flush=True)
(OUT / 'summary.json').write_text(json.dumps(results, indent=2) + '\n')
if __name__ == '__main__':
main()
@@ -0,0 +1,82 @@
#!/usr/bin/env python3
"""Two bounded tight-tolerance mesh references and comparisons (no 4K).
Usage:
run_mesh_reference.py [OUT_DIR]
OUT_DIR defaults to <repo>/local/adaptive_bounds_mesh and must already contain
the eight ``<scene>_hmax<h>.grlens`` maps from ``run_mesh.py``. The renderer
binary is expected at build/Release/schwarzschild_sky (see README for the make
command). Same one-point synthetic dim catalog, no external CSV.
"""
import json
import os
from pathlib import Path
import shlex
import subprocess
import sys
import time
from mesh_maps import load, compare
ROOT = Path(__file__).resolve().parents[2]
OUT = Path(sys.argv[1]).resolve() if len(sys.argv) > 1 else \
ROOT / 'local/adaptive_bounds_mesh'
BIN = ROOT / 'build/Release/schwarzschild_sky'
BUILD_CMD = 'make -j4 BUILD_TYPE=Release SPACETIME=schwarzschild backend'
ENV = dict(os.environ, OMP_NUM_THREADS='8', OMP_DYNAMIC='FALSE')
def scene_args(scene):
if scene == 'r100':
return ['--observer-radius', '100', '--look-ra-deg', '262.5',
'--look-dec-deg', '-30', '--fov-deg', '45',
'--refine-jacobian-min', '.2']
return ['--observer-position', '2.1', '0', '0', '--observer-velocity',
'0', '0', '0', '--look-ra-deg', '0', '--look-dec-deg', '0',
'--fov-deg', '90']
def main():
if not BIN.exists():
raise SystemExit(f'missing {BIN}; build it first:\n {BUILD_CMD}')
OUT.mkdir(parents=True, exist_ok=True)
catalog = OUT / 'single_dim_star.csv'
if not catalog.exists():
catalog.write_text('longitude_deg,latitude_deg,temperature_K,amplitude\n'
'262.5,-30,6000,1e-30\n')
results = {}
for scene in ('r100', 'r2p1'):
name = scene + '_tight_reference'
cmd = [str(BIN), '--integrator', 'dp54', '--width', '320',
'--height', '180', '--coarse-cell-pixels', '8',
'--refine-max-level', '3', '--catalog', str(catalog),
'--exposure', '1', '--psf-min-y', '1e-20',
'--psf-relative-tail', '1e-4', '--ode-min-step', '1e-12',
'--ode-max-step', '.5', '--ode-rtol', '1e-12',
'--ode-atol-x', '1e-12', '--ode-atol-pi', '1e-12',
'--ode-atol-l', '1e-12', '--verbose',
'--lens-map-output', str(OUT / (name + '.grlens')),
'--output', str(OUT / (name + '.png')), *scene_args(scene)]
start = time.perf_counter()
with (OUT / (name + '.log')).open('w') as log:
log.write('OMP_NUM_THREADS=8 OMP_DYNAMIC=FALSE ' +
shlex.join(cmd) + '\n')
log.flush()
subprocess.run(cmd, cwd=ROOT, env=ENV, stdout=log,
stderr=subprocess.STDOUT, check=True, timeout=180)
info, ref = load(OUT / (name + '.grlens'))
info['wall_seconds'] = time.perf_counter() - start
results[name] = info
print(name, json.dumps(info), flush=True)
for upper in (.5, 2, 8, 32):
_, vertices = load(OUT / f'{scene}_hmax{upper:g}.grlens')
result = compare(ref, vertices)
results[f'{scene}_ref_vs_{upper:g}'] = result
print(scene, 'ref vs', upper, json.dumps(result), flush=True)
(OUT / 'reference_summary.json').write_text(
json.dumps(results, indent=2) + '\n')
if __name__ == '__main__':
main()
@@ -0,0 +1,261 @@
#!/usr/bin/env python3
"""Summarise the adaptive-step-bounds benchmark raw CSV logs.
Usage:
summarize.py [OUT_DIR]
Reads ``OUT_DIR/raw/*.csv`` (written by ``run_limited.sh``) and writes
``OUT_DIR/logs/summary.txt``. It has no dependency on any untracked script or
on older local data.
Defaults: OUT_DIR = <repo>/local/adaptive_step_bounds_2026-10-05, derived from
this file's location (benchmarks/adaptive_step_bounds_2026-10-05/).
"""
import csv
import math
import os
import sys
HERE = os.path.dirname(os.path.abspath(__file__))
ROOT = os.path.dirname(os.path.dirname(HERE))
DEFAULT_OUT = os.path.join(ROOT, "local", "adaptive_step_bounds_2026-10-05")
OUTCOME = {0: "ESC", 1: "DARK", 2: "UNRES", 3: "INC"}
REASON = {
0: "NONE",
1: "REDSHIFT",
2: "BUDGET",
3: "TIMERANGE",
4: "DOMAIN",
5: "INVMETRIC",
6: "INTEGERR",
7: "UNSUPPORTED",
8: "PROTOCOL",
9: "IO",
}
def fnum(v):
if v is None or v == "" or v == "nan":
return math.nan
return float(v)
def fmt(v):
if isinstance(v, float):
if math.isnan(v):
return "nan"
if v == 0:
return "0"
return f"{v:.4g}"
return str(v)
def table(title, header, recs):
lines = [f"## {title}", ""]
lines.append("| " + " | ".join(header) + " |")
lines.append("|" + "|".join(["---"] * len(header)) + "|")
for r in recs:
lines.append("| " + " | ".join(fmt(x) for x in r) + " |")
lines.append("")
return lines
def ang_between(a, b):
va = [fnum(a["nx"]), fnum(a["ny"]), fnum(a["nz"])]
vb = [fnum(b["nx"]), fnum(b["ny"]), fnum(b["nz"])]
na = math.sqrt(sum(x * x for x in va))
nb = math.sqrt(sum(x * x for x in vb))
if not (na > 0 and nb > 0):
return math.nan
cross = (va[1]*vb[2]-va[2]*vb[1],
va[2]*vb[0]-va[0]*vb[2],
va[0]*vb[1]-va[1]*vb[0])
dot = sum(x * y for x, y in zip(va, vb))
return math.atan2(math.sqrt(sum(x*x for x in cross)), dot)
class Raw:
def __init__(self, out_dir):
self.raw = os.path.join(out_dir, "raw")
self.logs = os.path.join(out_dir, "logs")
def rows(self, name):
with open(os.path.join(self.raw, name)) as f:
return list(csv.DictReader(f))
def summarise_agg(raw, name, title, keycol):
recs = raw.rows(name)
out = []
for r in recs:
out.append((r["case"], float(r[keycol]), int(r["class_mismatch"]),
fnum(r["max_dn_ang"]), fnum(r["max_dgrel"]),
fnum(r["max_dstopT"]), int(r["sum_rhs"]),
int(r["max_rejected"]), int(r["escaped"]), int(r["dark"]),
int(r["unresolved"]), int(r["incomplete"])))
out.sort(key=lambda x: (x[0], x[1]))
return table(title,
["case", keycol, "mismatch", "max_dn_ang", "max_dgrel",
"max_dstopT", "sum_rhs", "max_rej", "ESC", "DARK", "UNRES",
"INC"], out)
def build(raw):
lines = ["# Adaptive-step-bounds benchmark summary tables", ""]
ref = raw.rows("a_sch_reference.csv")
recs = []
for r in ref:
recs.append((r["case"], int(r["dir"]), fnum(r["theta"]),
OUTCOME[int(r["outcome"])], REASON[int(r["reason"])],
fnum(r["stop_t"]), int(r["steps"]), int(r["rejected"]),
int(r["rhs"]), fnum(r["g"]), fnum(r["thr"])))
lines += table("A Schwarzschild references (DP tol=1e-12, max_step=0.25)",
["case", "dir", "theta", "outcome", "reason", "stop_t",
"steps", "rej", "rhs", "g", "thr"], recs)
lines += summarise_agg(raw, "a_sch_upper_summary.csv",
"A Schwarzschild upper scan (min_step=1e-12, "
"tol=1e-9)", "upper")
lines += summarise_agg(raw, "a_sch_min_summary.csv",
"A Schwarzschild min scan (max_step=2, tol=1e-9)",
"min_step")
rk = raw.rows("a_sch_rk4_sensitive.csv")
groups = {}
for r in rk:
groups.setdefault((r["case"], r["dir"]), {})[r["phase"]] = r
recs = []
for (case, d), gg in sorted(groups.items()):
a = gg.get("rk4_0.01")
b = gg.get("rk4_0.005")
if not a or not b:
continue
dn = math.nan if a["outcome"] != b["outcome"] else ang_between(a, b)
dg = (math.nan if a["outcome"] != b["outcome"]
else abs(fnum(a["g"]) - fnum(b["g"])))
recs.append((case, d, fnum(a["theta"]), OUTCOME[int(a["outcome"])],
OUTCOME[int(b["outcome"])], dn, dg, int(a["rhs"]),
int(b["rhs"])))
lines += table("A Schwarzschild sensitive RK4 .01 vs .005",
["case", "dir", "theta", "out.01", "out.005",
"hhalve_dn_ang", "|dg|", "rhs.01", "rhs.005"], recs)
mk = raw.rows("a_mink_analytic.csv")
mkx = raw.rows("a_mink_analytic_x.csv")
groups = {}
for r in mk:
groups.setdefault((r["upper"], r["min_step"]), []).append(r)
recs = []
for (u, f), g in sorted(groups.items(),
key=lambda kv: (float(kv[0][0]), float(kv[0][1]))):
recs.append((u, f, len(g), max(fnum(r["dn_ang"]) for r in g),
max(fnum(r["dgrel"]) for r in g),
max(fnum(r["t_err"]) for r in g)))
lines += table("A Minkowski flat analytic check (n_inf and t)",
["upper", "min_step", "n", "max_dn_ang", "max_dgrel",
"max_t_err"], recs)
groups = {}
for r in mkx:
groups.setdefault((r["upper"], r["min_step"]), []).append(r)
recs = []
for (u, f), g in sorted(groups.items(),
key=lambda kv: (float(kv[0][0]), float(kv[0][1]))):
maxx = max(max(fnum(r["x_err_x"]), fnum(r["x_err_y"]),
fnum(r["x_err_z"])) for r in g)
recs.append((u, f, len(g), maxx, max(fnum(r["t_err"]) for r in g)))
lines += table("A Minkowski flat analytic check (crossing x and t)",
["upper", "min_step", "n", "max_x_err", "max_t_err"], recs)
for case in ["alc_v3_s1", "alc_v3_s10", "alc_v9_s1", "alc_v9_s10"]:
lines += summarise_agg(raw, f"a_alc_{case}_upper_summary.csv",
f"A Alcubierre {case} upper scan", "upper")
lines += summarise_agg(raw, "a_alc_v9_s100_min_summary.csv",
"A Alcubierre v9 s100 lower-bound stress "
"(upper=0.004)", "min_step")
b = raw.rows("b_summary.csv")
recs = []
for r in b:
recs.append((r["case"], int(r["dir"]),
OUTCOME[int(r["ref_outcome"])], fnum(r["span"]),
fnum(r["target"]), int(r["observed_steps"]),
int(r["reached_target"]),
REASON.get(int(r["terminated_reason"]),
r["terminated_reason"]),
fnum(r["h_min"]), fnum(r["h_max"]), fnum(r["h_first"]),
int(r["boundary_steps"]), int(r["sum_rhs"]),
int(r["sum_reject"]), fnum(r["null_residual_max"])))
lines += table("B observed accepted step h (finite T=min(20, ref span))",
["case", "dir", "ref", "span", "target", "steps", "reached",
"term", "h_min", "h_max", "h_first", "bnd", "rhs", "rej",
"null_max"], recs)
if os.path.exists(os.path.join(raw.raw, "critical_ref.csv")):
lines += critical_section(raw)
return lines
def critical_section(raw):
rows = raw.rows("critical_ref.csv")
key = {}
for r in rows:
key[(r["camera"], int(r["dir"]), r["cfg"])] = r
lines = table("critical reference check (raw rows)",
["camera", "dir", "theta", "cfg", "outcome", "reason",
"end_id", "stop_t", "steps", "rejected", "rhs", "g", "thr",
"L", "L0"],
[(r["camera"], int(r["dir"]), fnum(r["theta"]), r["cfg"],
OUTCOME[int(r["outcome"])], REASON[int(r["reason"])],
r["end_id"], fnum(r["stop_t"]), int(r["steps"]),
int(r["rejected"]), int(r["rhs"]), fnum(r["g"]),
fnum(r["thr"]), fnum(r.get("L")), fnum(r.get("L0")))
for r in rows])
# pair comparisons
pairs = [("ref_tol1e-12_h0.25", "ref_tol1e-13_h0.125"),
("tol1e-11_h2", "tol1e-11_h8"),
("ref_tol1e-12_h0.25", "tol1e-11_h2"),
("ref_tol1e-12_h0.25", "tol1e-11_h8")]
for a_cfg, b_cfg in pairs:
recs = []
for (cam, di, cfg), a in sorted(key.items()):
if cfg != a_cfg:
continue
b = key.get((cam, di, b_cfg))
if not b:
continue
same = a["outcome"] == b["outcome"]
dn = ang_between(a, b) if same else math.nan
dg = (math.nan if not same else
abs(fnum(a["g"]) / fnum(b["g"]) - 1.0)
if fnum(a["g"]) > 0 and fnum(b["g"]) > 0 else math.nan)
dstop = (fnum(a["stop_t"]) - fnum(b["stop_t"])
if fnum(a["stop_t"]) == fnum(a["stop_t"]) and
fnum(b["stop_t"]) == fnum(b["stop_t"]) else math.nan)
dmar = (fnum(a["thr"]) - fnum(b["thr"])
if fnum(a["thr"]) == fnum(a["thr"]) and
fnum(b["thr"]) == fnum(b["thr"]) else math.nan)
recs.append((cam, di, fnum(a["theta"]),
OUTCOME[int(a["outcome"])], OUTCOME[int(b["outcome"])],
same, dn, dg, dstop, dmar))
lines += table(f"critical pair {a_cfg} vs {b_cfg}",
["camera", "dir", "theta", "a", "b", "same",
"dn_ang", "dgrel", "dstop_t", "dthr"], recs)
return lines
def main(argv):
out = argv[1] if len(argv) > 1 else DEFAULT_OUT
raw = Raw(out)
os.makedirs(raw.logs, exist_ok=True)
lines = build(raw)
path = os.path.join(raw.logs, "summary.txt")
with open(path, "w") as f:
f.write("\n".join(lines) + "\n")
print(f"wrote {path} ({len(lines)} lines)")
return 0
if __name__ == "__main__":
sys.exit(main(sys.argv))
+67
View File
@@ -0,0 +1,67 @@
#!/usr/bin/env python3
"""Postprocess existing 4K/limited maps only; never invokes a renderer.
Usage:
summarize_4k.py [OUT_DIR]
OUT_DIR defaults to <repo>/local/adaptive_bounds_4k and must contain
``r100_4k_hmax2.grlens``, ``r100_4k_hmax8.grlens`` and ``attempts.json``.
Writes summary.json in OUT_DIR.
Cost scope note: accepted/rejected/RHS sums cover the final persistent map
vertices only. Nonpersistent discarded refinement probes are not stored in the
v3 map, so these sums are not full-render executed RHS totals.
"""
import json
import re
from pathlib import Path
import sys
from mesh_maps import load, compare
ROOT = Path(__file__).resolve().parents[2]
OUT = Path(sys.argv[1]).resolve() if len(sys.argv) > 1 else \
ROOT / 'local/adaptive_bounds_4k'
def main():
summaries = {}
meshes = {}
for upper in (2, 8):
name = f'r100_4k_hmax{upper}'
summaries[name], meshes[upper] = load(OUT / (name + '.grlens'))
text = (OUT / (name + '.log')).read_text()
initial = int(re.search(r'tracing (\d+) initial rays', text).group(1))
generations = [int(n) for n in re.findall(
r'refinement generation \d+ tracing (\d+) samples', text)]
summaries[name]['requested_samples_including_nonpersistent_probes'] = \
initial + sum(generations)
summaries[name]['initial_samples'] = initial
summaries[name]['refinement_samples_per_generation'] = generations
summaries[name]['trace_wall_seconds'] = sum(
float(x) for x in re.findall(
r'(?:initial ray trace|adaptive ray-trace refinement) '
r'finished in ([\d.]+) s', text))
ledger = OUT / 'attempts.json'
if ledger.exists():
for entry in json.loads(ledger.read_text()):
if entry.get('state') == 'completed':
summaries[f'r100_4k_hmax{entry["upper"]}']['wall_seconds'] = \
entry['wall_seconds']
summaries['comparison'] = compare(meshes[2], meshes[8])
a, b = (summaries[f'r100_4k_hmax{upper}'] for upper in (2, 8))
summaries['relative_reduction'] = {
key: 1 - b[key] / a[key]
for key in ('accepted', 'rhs', 'wall_seconds', 'trace_wall_seconds')}
summaries['comparison']['triangle_payload_identical'] = \
a['triangle_sha256'] == b['triangle_sha256']
summaries['cost_scope'] = (
'accepted/rejected/RHS sums cover final persistent vertices only; '
'nonpersistent discarded refinement probes are not in the v3 map. '
'Do not interpret them as full-render executed RHS totals.')
(OUT / 'summary.json').write_text(json.dumps(summaries, indent=2) + '\n')
print(json.dumps(summaries, indent=2))
if __name__ == '__main__':
main()
+3 -3
View File
@@ -1,5 +1,5 @@
REFERENCE_DIR := tests/data/psf_event_sink_reference
REFERENCE_TMP_DIR := /tmp/gr_psf_event_sink_reference
REFERENCE_TMP_DIR := /tmp/opencode/gr_psf_event_sink_reference
FLOATDIFF_SCRIPT := scripts/fits_floatdiff.py
.PHONY: test-reference-images
@@ -9,8 +9,8 @@ test: test-reference-images
test-reference-images: $(FLOATDIFF_SCRIPT) $(REFERENCE_DIR)/minkowski_ra1_dec1_640x360_HDR.fits $(REFERENCE_DIR)/schwarzschild_ra1_dec1_fov60_640x360_HDR.fits
$(MAKE) SPACETIME=minkowski ENABLE_HDR=1 backend
mkdir -p $(REFERENCE_TMP_DIR)
OMP_NUM_THREADS=16 $(BUILD_DIR)/minkowski_sky --catalog assets/sky_grid_5deg.csv --output $(REFERENCE_TMP_DIR)/minkowski_ra1_dec1_640x360.$(IMAGE_EXT) --hdr-output --width 640 --height 360 --fov-deg 30 --look-ra-deg 1 --look-dec-deg 1 --exposure 0.1 --observer-radius 30 --observer-velocity 0 0 0 --psf-fwhm-pixels 2.7 --psf-moffat-beta 4.5 --max-magnification 1e300 --max-cache-psf-flux 1 --psf-relative-tail 1e-8 --psf-min-y 0 --coarse-cell-pixels 16 --refine-max-level 0 --refine-angle-abs-deg 0.001 --refine-angle-rel 0.1 --refine-jacobian-min 1e-3 --refine-min-edge-pixels 0.5 --refine-min-area-pixels2 0.25 --catalog-load-workers 4
OMP_NUM_THREADS=16 $(BUILD_DIR)/minkowski_sky --integrator rk4 --catalog assets/sky_grid_5deg.csv --output $(REFERENCE_TMP_DIR)/minkowski_ra1_dec1_640x360.$(IMAGE_EXT) --hdr-output --width 640 --height 360 --fov-deg 30 --look-ra-deg 1 --look-dec-deg 1 --exposure 0.1 --observer-radius 30 --observer-velocity 0 0 0 --psf-fwhm-pixels 2.7 --psf-moffat-beta 4.5 --max-magnification 1e300 --max-cache-psf-flux 1 --psf-relative-tail 1e-8 --psf-min-y 0 --coarse-cell-pixels 16 --refine-max-level 0 --refine-angle-abs-deg 0.001 --refine-angle-rel 0.1 --refine-jacobian-min 1e-3 --refine-min-edge-pixels 0.5 --refine-min-area-pixels2 0.25 --catalog-load-workers 4
python3 $(FLOATDIFF_SCRIPT) $(REFERENCE_DIR)/minkowski_ra1_dec1_640x360_HDR.fits $(REFERENCE_TMP_DIR)/minkowski_ra1_dec1_640x360_HDR.fits
$(MAKE) SPACETIME=schwarzschild ENABLE_HDR=1 backend
OMP_NUM_THREADS=16 $(BUILD_DIR)/schwarzschild_sky --catalog assets/sky_grid_5deg.csv --output $(REFERENCE_TMP_DIR)/schwarzschild_ra1_dec1_fov60_640x360.$(IMAGE_EXT) --hdr-output --width 640 --height 360 --fov-deg 60 --look-ra-deg 1 --look-dec-deg 1 --exposure 0.1 --observer-radius 30 --observer-velocity 0 0 0 --psf-fwhm-pixels 2.7 --psf-moffat-beta 4.5 --max-magnification 1e300 --max-cache-psf-flux 1 --psf-relative-tail 1e-8 --psf-min-y 0 --coarse-cell-pixels 16 --refine-max-level 0 --refine-angle-abs-deg 0.001 --refine-angle-rel 0.1 --refine-jacobian-min 1e-3 --refine-min-edge-pixels 0.5 --refine-min-area-pixels2 0.25 --catalog-load-workers 4
OMP_NUM_THREADS=16 $(BUILD_DIR)/schwarzschild_sky --integrator rk4 --catalog assets/sky_grid_5deg.csv --output $(REFERENCE_TMP_DIR)/schwarzschild_ra1_dec1_fov60_640x360.$(IMAGE_EXT) --hdr-output --width 640 --height 360 --fov-deg 60 --look-ra-deg 1 --look-dec-deg 1 --exposure 0.1 --observer-radius 30 --observer-velocity 0 0 0 --psf-fwhm-pixels 2.7 --psf-moffat-beta 4.5 --max-magnification 1e300 --max-cache-psf-flux 1 --psf-relative-tail 1e-8 --psf-min-y 0 --coarse-cell-pixels 16 --refine-max-level 0 --refine-angle-abs-deg 0.001 --refine-angle-rel 0.1 --refine-jacobian-min 1e-3 --refine-min-edge-pixels 0.5 --refine-min-area-pixels2 0.25 --catalog-load-workers 4
python3 $(FLOATDIFF_SCRIPT) $(REFERENCE_DIR)/schwarzschild_ra1_dec1_fov60_640x360_HDR.fits $(REFERENCE_TMP_DIR)/schwarzschild_ra1_dec1_fov60_640x360_HDR.fits
+115 -18
View File
@@ -954,35 +954,34 @@ residual、Chebyshev 表或解析主项;运行期不得建表。
- 高精度 reference evaluator 在域内不收敛;
- 表在 seam 或 grazing 区域超过误差限。
## 18A.10 当前实现状态
## 18A.10 Backend 能力与路由约束
- 公共接口(end descriptor、escape worldtube sample、canonical photon state 与
endpoint `end_id`)已实现;
- 公共接口以 end descriptor、escape worldtube sample、canonical photon state 与
endpoint `end_id` 分离远端声明、几何事件、状态转接与终态来源;
- $M=0$ 固定球与匀速移动球的 camera pre-route、directed inside -> outside
crossing、`PENDING_ENTRY` 生命周期已实现,并接入 Minkowski 与 Alcubierre;
- 单帧与 movie 共用同一 pre-route/生命周期实现;`make test` 全绿;
crossing 与 `PENDING_ENTRY` 生命周期采用同一几何约定,适用于 Minkowski 与
Alcubierre 的平直外区;
- 单帧与 movie 必须共用同一 pre-route/生命周期实现;
- $M>0$ 固定同心 Schwarzschild monopole 外区以**解析 Carlson 椭圆积分**实现
(见 18A.11),接入解析 Kerr–Schild backend;相机位于 escape 球外的
pre-route、entry 传播、directed crossing 与 infinity endpoint 均可用;
- 任意加速 worldtube 的 bracketed root driver 已实现并由 synthetic accelerated
worldtube 测试覆盖(含跨 motion-segment 与 history-exhausted failure
semantics),但尚无生产 backend 使用该路径。该 driver 只检查离散端点的
$F$ 符号,尚不能保证捕获一个步长内的窄进入;任意加速 backend 落地前需加入
段内速度/加速度界或自适应子区间搜索。
- 多渐近远端路由尚未实现:`end_id` 已进入 endpoint、`LensVertex`、
`terminal_mismatch` 与 `discrete_jacobian`,避免跨 end 插值;但
`asymptotic_route_camera` 目前对多个 end 只处理第一个,lens-map 文件格式也
未序列化 `end_id`。虫洞/最大延拓接入前需要补这些。
- worldtube 的运动能力必须显式声明。分段匀速、线性半径变化可在各段内求首次
crossing,并在段边界重新采样;任意加速运动在没有可信区间界时必须报告
`UNSUPPORTED`,不得把离散采样没有发现根解释为正常 miss。扩展加速运动能力需要
同时提供可信的段内界或专用首次 crossing 解法。
- `end_id` 贯通 endpoint、`LensVertex`、`terminal_mismatch` 与
`discrete_jacobian`,避免跨 end 插值;lens-map 保存
`end_id`。不同物理 region 的 reachable-end 路由及 catalog 绑定仍需独立定义,
不能把能够保存 end 标识解释为已支持完整解析延拓。
- 生命周期为三态:`NO_ENDS`(才允许 legacy)、`DIRECTED_READY`、
`PROTOCOL_ERROR`(descriptor 读取失败或不支持的 exterior,显式
`INVALID/UNSUPPORTED`,绝不退回 legacy)。pre-route 也先校验所有 descriptor
与 exterior kind,再判定 worldtube 内外,因此“声明了不受支持 exterior 而相机
恰在球内”不会被静默接受。
- 内区积分时若 worldtube sample 变为 `valid == 0`(历史耗尽),作为一等
terminal reason 返回 `RAY_ENDPOINT_TIME_RANGE_EXHAUSTED`、保留 `end_id`,并由
`RayPool` 记为 `RAY_POOL_TERMINATED`,与 pre-route 的耗尽语义一致。generic
accelerated driver 与内区 crossing localizer 的二分过程中任何 sample 失败都
直接传播该具体状态,不返回一个正常 entry 或普通 integration failure。
terminal reason 返回 `INCOMPLETE/TIME_RANGE_EXHAUSTED`、保留 `end_id`,并由
`RayPool` 记为 `RAY_POOL_TERMINATED`,与 pre-route 的耗尽语义一致。内区 crossing
localizer 定位过程中任何 sample 失败都直接传播具体状态,不返回一个正常 entry。
- Minkowski entry quadratic 用稳定根公式($q=-\tfrac12(b+\mathrm{copysign}
(\sqrt\Delta,b))$,取最小正根);$c=0$(相机在边界)时按 $b=\mathrm dF/
\mathrm ds$ 分类。worldtube sample 必须有限且 $R>0$,否则 `INVALID`。
@@ -1076,6 +1075,104 @@ residual、Chebyshev 表或解析主项;运行期不得建表。
---
# 18B. 自适应测地线积分
积分器负责轨迹误差控制、拒步与数值失败诊断,不承担物理终态分类。步长、计算配额与
回溯时间是独立配置;改变步长或容差不得隐式改变可追踪的时间范围。
- 生产默认采用 DP54;固定 RK4 保留为显式数值对照。CLI 默认相对容差与 `Pi/L`
绝对容差为 `1e-9`,位置绝对容差为 `1e-9` 倍 backend 长度单位(解析
Schwarzschild 为 `M`,Alcubierre 为 bubble 半径,Minkowski 为坐标单位长度)。
初始步长、最大步长及时间配额按 backend 的长度/时间单位配置,所有参数均可显式
覆盖并写入 provenance。容差控制的是局部 ODE 误差,不承诺临界光线最终方向的
全局误差界;生产精度应以独立容差与几何尺度收敛确定。
- CLI 步长下限为 `max(1e-12,16*DBL_EPSILON)` 倍 backend 长度单位(`c=1`),
是不绑定正常轨迹的数值保护,不是已测得的物理最小时间尺度。上限在解析
Schwarzschild 为 `8M`,Minkowski 为 `16` 个坐标时间单位,Alcubierre 为
初始默认步长的八倍。上限用于限制 trial 区间,不替代误差与首次事件控制;
near-critical 光线应独立收紧容差,不能只靠减小上限保证分类与方向收敛。
参数扫描与复现输入见 `benchmarks/adaptive_step_bounds_2026-10-05/`;解析 backend
的测量不外推为 NR/DG 数据的默认步长标定。
- `GeodesicTraceConfig.stepper` 区分固定 RK4 与 Dormand–Prince 5(4)。DP54 的
`atol_x/atol_Pi/atol_L/rtol`、`min_step/max_step`、
`consecutive_rejection_limit`、`max_lookback_time` 全部显式;缺省、非正、非有限或
`min_step > max_step` 直接返回 protocol error,不偷偷填默认值。初始 trial step
复用 `coordinate_time_step`。
- DP54 采用标准 Dormand–Prince 5(4)7M tableau,接受 5 阶解,误差用
`h*sum((B5-B4)*k)` 直接计算;位置误差尺度
`atol_x + rtol*max(|dx|,|h*k1.x|)`,`Pi` 用 `atol_Pi + rtol*max(|Pi_before|,|Pi_candidate|)`,
`L` 用 `atol_L + rtol*max(1,|dL|)`;控制器 safety `0.9`、factor clamp `[0.2,5]`、
exponent `1/5`,无 FSAL(每次 trial 7 次 RHS),拒步不提交状态。
- 自适应控制状态属于各条 ray:保存 `integration_start_time`(activation/entry 时间,
非相机能量参考 `L0`)、`next_step`、累计 rejected/RHS 计数与 `previous_rejected`;
`steps` 表示 accepted 步数。状态须跨 slab 与预算重试保留;endpoint 保存最后可信
轨迹及控制/成本状态,使续追无需从相机重放,也不重置 `L0`。
- 控制/成本状态在 scheduler 各层按值传递:batch 路径的 `RayPool` SoA 保存同一组
per-ray 字段,slab activation 只重置新 ray 的 accepted 步数,绝不重置 adaptive
control state;frame/movie 的续追请求以完整 resume state 重建(frame 与 movie
共用同一 helper,禁止逐字段手抄或漏字段)。endpoint 记录本次实际授予的 time
budget,供 retry 层独立累计。off-mesh witness 原地提升必须整体复制该状态,
重试回填保留连续状态与成本计数。
- 计算配额与回溯时间是两条独立、各自饱和的 retry 预算:step 增量与 time 增量互不
替代,只有"实际耗尽且仍可增长"的 quota 才允许发起 retry;任一 blocking quota 已
达硬上限时不得仅凭另一 quota 产生无效重试,直接按 budget-incomplete 处理。两个
quota 的实际配置总 grant 必须与已 spent 数量分开记录,retry 采样取实际 grant,
不得由已花费步数反推授权。"可增长"指新 quota 必须打开严格更大的可表示区间:整数
步长严格增加,time 左边界 `start - quota` 严格前移;因大 time origin 或增量过小
而 round 到同一区间的增量不算有效增长,不得发起永远无法推进的 retry。两者都到上限
后 UUU 仍为 budget-incomplete,UUD/UDD 在几何停止尺度可按 finite-resolution 近似
标黑。自动补齐的 time 重试增量由 `max_lookback_time` 指定,总上限为其四倍
(对有限数值范围饱和);这是可覆盖的 resource quota policy,不是物理终态条件。
- 每步对 slab 左边界与显式回溯预算左边界截断;DP54 不以
`coordinate_time_step * max_steps` 定义时间范围。回溯预算耗尽而轨迹仍可信
返回 `UNRESOLVED/BUDGET_EXHAUSTED`;真实的 slab data time failure 仍是
`INCOMPLETE/TIME_RANGE_EXHAUSTED`。`max_steps` 仍作为 accepted 步数额度。
各 trial 先确定可表示的目标时间,再用实际 `target-before.t` 推进状态、构造
dense interpolant 与提交时间;不得以舍入前的步长推进空间、舍入后的步长记时间。
worldtube 采样的坐标时间同样使用实际差值回推运动,不假设半步总是可表示。
Alcubierre 的默认时间范围由 `1.25*4*R_escape/(1-|v_s|)` 给出;固定步的步数
估算不能截短自适应路径的历史,也不能替代其独立资源上限。
- 初始 accepted 状态的 RHS/metric 失败直接报告具体 point reason,不缩步;后续 stage
的 `OUT_OF_DOMAIN/INVALID_METRIC` 可有界缩步重试;`TIME_UNAVAILABLE/INTERNAL_ERROR`
不靠无限缩步;最小步长或时间不可进导致失败时报告 `INCOMPLETE/INTEGRATION_ERROR`。
拒步上限耗尽时保留具体 stage 原因,或报告积分误差不可控。失败路径保证调用者
state 与 endpoint 的最后可信状态一致。
- directed inside→outside 事件的 DP 子积分定位使用同一 DP 误差控制(不混用 RK4),
成本计入 endpoint 但不伪造 accepted 主步。定位的 time bracket
停止容差取局部步长量级与真实 `nextafter` ULP 的组合(不随远端 `|t|` 放大),任何
可表示的非零 target−before 区间都实际积分,只有 target==before 才允许直接取状态;
`t+h==t` 明确失败或返回已接受可信状态,不伪造轨迹。子积分失败经独立 RayReason 透传
(`TIME_UNAVAILABLE→TIME_RANGE_EXHAUSTED`、`INVALID_METRIC`、`OUT_OF_DOMAIN`、
`INTEGRATION_ERROR`),与 worldtube descriptor 自身的 protocol/history 失败区分。
- 嵌入误差估计不保证找到全部几何事件;窄进出、first-crossing 与同一步内多个事件的
顺序必须独立验证,不能只凭步端点符号或误差通过认定事件完整。积分精度还须通过
逃逸方向、频移、null 残差及适用 backend 的独立守恒量验证;不靠反复归一化掩盖误差。
- DP54 accepted 区间的四次 dense interpolant 用于枚举事件候选。对分段匀速球形
worldtube,代入 `F=|x-center|²-radius²` 得到至多八次多项式;通过导数根划分
单调区间并隔离根,不能仅查看主步两端符号。根按过去传播方向排序,区分真正的
inside→outside crossing 与切触;motion 段边界截断主步并重新建立候选。
相机相对能量阈值的候选从同一 accepted 区间的 `L-L0` 获得。候选必须经误差受控
子积分验证,escape 与暗阈值按可信轨迹顺序处理;数值不可分辨的同时事件采用明确、
一致的优先规则,不能由 end descriptor 的排列决定终态。
`L-L0` 在动态时空中不假定单调:即使主步两端均低于阈值,也要处理步内的首次
upcrossing。阈值根按导数根分段,在局部 bracket 内确认并定位;不能用整个区间的
二分取代首次 crossing。所有候选的可信时间须在子积分后重新比较;当前不可分辨的
同时事件取 escape 优先。阈值是 `>=` 的闭事件,真正的步末 crossing 不能因根
合并到端点而丢失。无法确认事件时有界缩步,耗尽后报告积分失败,不回退为假成功。
`LOG_P0` 辅助监测策略保留 accepted-state 检查,不提供这一多项式事件定位能力;
生产 CLI 使用相机相对 `L-L0` 策略。
- lens-map 的积分 provenance 必须保存稳定的 stepper wire code、分组绝对/相对
容差、初始步长及上下限、拒步上限、初始计算/回溯配额与重试政策。render-only
replay 消费文件策略,不能由当前 CLI 默认值覆盖历史配置,也不从缺失字段推断
自适应积分来源。未知 stepper code 或无效配置须拒绝;只含固定 RK4 来源的旧文件
可按明确的固定步语义导入,但不能补成 DP54 结果。
- accepted、rejected 与实际 RHS 成本按持久 sample 身份统计;续追保存累计值,回填
替换而不是再次累加。off-mesh witness 与原地提升后的顶点是同一样本,不能重复
计费;事件子积分和失败 stage 的 RHS 也计入真实成本。渲染地图保存这些诊断值,
replay 的来源与成本统计应与原始 trace 一致。
---
# 19. 恒星 catalog 的内部表示
不要预存 RGB。
+7 -108
View File
@@ -257,106 +257,6 @@ static int solve_entry_quadratic(const double d[3], const double q[3],
return 1;
}
static double worldtube_F_frame(const double c_frame[3], double radius,
const double x_frame[3]) {
const double d[3] = {x_frame[0] - c_frame[0], x_frame[1] - c_frame[1],
x_frame[2] - c_frame[2]};
return dot3(d, d) - radius * radius;
}
/* Bracketed first-entry search for a worldtube whose motion is not constant.
* This is the future interface for accelerated worldtubes; the current
* backends always take the closed quadratic path above. */
static AsymptoticStatus minkowski_generic_first_entry(
const SpacetimeSource *source, const SpacetimeAsymptoticEnd *end,
double t0, const double x_frame[3], const double w_frame[3],
double *s_out) {
double s = 0.0;
SpacetimeEscapeWorldtubeSample sample;
AsymptoticStatus status = worldtube_sample(source, end, t0, &sample);
if (status != ASYMPTOTIC_OK)
return status;
double c_frame[3];
backend_position_to_frame(end, sample.center, c_frame);
const double f_start = worldtube_F_frame(c_frame, sample.radius, x_frame);
if (f_start < 0.0) {
*s_out = 0.0;
return ASYMPTOTIC_OK;
}
if (f_start == 0.0) {
/* On the boundary only an inward slope is an entry; outward and tangent
* rays keep searching. */
double v_frame[3], d0[3], q0[3];
backend_vector_to_frame(end, sample.velocity, v_frame);
for (int i = 0; i < 3; ++i) {
d0[i] = x_frame[i] - c_frame[i];
q0[i] = w_frame[i] + v_frame[i];
}
const double slope0 =
2.0 * dot3(d0, q0) + 2.0 * sample.radius * sample.radius_rate;
if (slope0 < 0.0) {
*s_out = 0.0;
return ASYMPTOTIC_OK;
}
}
const double speed = fabs(sample.velocity[0]) + fabs(sample.velocity[1]) +
fabs(sample.velocity[2]) + fabs(sample.radius_rate) + 1.0;
const double base_step = 0.5 * fmax(sample.radius, 1.0) / speed;
for (int iteration = 0; iteration < 1000000; ++iteration) {
double step = base_step;
const double boundary = spacetime_escape_worldtube_next_segment(
source, end->end_id, t0 - s);
if (isfinite(boundary)) {
/* The backward-integration distance to a past segment boundary. */
const double to_boundary = (t0 - boundary) - s;
if (to_boundary > 0.0)
step = fmin(step, to_boundary);
}
const double s_next = s + step;
const double t_next = t0 - s_next;
SpacetimeEscapeWorldtubeSample next;
status = worldtube_sample(source, end, t_next, &next);
if (status != ASYMPTOTIC_OK)
return status;
double c_next[3], ray_next[3];
backend_position_to_frame(end, next.center, c_next);
for (int i = 0; i < 3; ++i)
ray_next[i] = x_frame[i] + w_frame[i] * s_next;
const double f_next = worldtube_F_frame(c_next, next.radius, ray_next);
/* Only a strictly negative sample is an entry; a single touch at F == 0
* (tangent) is not. */
if (f_next < 0.0) {
double lo = s, hi = s_next;
AsymptoticStatus bisect_status = ASYMPTOTIC_OK;
for (int bisect = 0; bisect < 80; ++bisect) {
const double mid = 0.5 * (lo + hi);
SpacetimeEscapeWorldtubeSample mid_sample;
bisect_status = worldtube_sample(source, end, t0 - mid, &mid_sample);
if (bisect_status != ASYMPTOTIC_OK)
break;
double c_mid[3], ray_mid[3];
backend_position_to_frame(end, mid_sample.center, c_mid);
for (int i = 0; i < 3; ++i)
ray_mid[i] = x_frame[i] + w_frame[i] * mid;
const double f_mid =
worldtube_F_frame(c_mid, mid_sample.radius, ray_mid);
if (f_mid <= 0.0)
hi = mid;
else
lo = mid;
}
/* A sample failure inside the bracket must not be disguised as a
* normal entry. */
if (bisect_status != ASYMPTOTIC_OK)
return bisect_status;
*s_out = hi;
return ASYMPTOTIC_OK;
}
s = s_next;
}
return ASYMPTOTIC_INVALID;
}
static void minkowski_route_escaped(const SpacetimeAsymptoticEnd *end,
const double w_frame[3],
SpacetimeEndId end_id,
@@ -405,14 +305,13 @@ static AsymptoticStatus minkowski_preroute(
for (int i = 0; i < 3; ++i)
x_cur[i] = x_frame[i] + w_frame[i] * s;
if (!sample.velocity_constant) {
double s_rel;
status = minkowski_generic_first_entry(source, end, t, x_cur, w_frame,
&s_rel);
if (status != ASYMPTOTIC_OK)
return status;
minkowski_route_entry(end, t0, x_frame, w_frame, s + s_rel, end_id,
route);
return ASYMPTOTIC_OK;
/* An arbitrary accelerated worldtube has no strict interval bound on
* its relative motion, so a bracketed sign search can silently miss a
* narrow first entry. Until such bounds exist, report it explicitly
* instead of fabricating a route; piecewise-constant motion (the
* production backends) stays supported through the closed quadratic
* path below. */
return ASYMPTOTIC_UNSUPPORTED;
}
double c_frame[3], v_frame[3], d[3], q[3];
backend_position_to_frame(end, sample.center, c_frame);
+215 -29
View File
@@ -9,6 +9,7 @@
#include "optics.h"
#include <float.h>
#include <limits.h>
#include <math.h>
#include <omp.h>
@@ -102,6 +103,17 @@ int frame_lens_mesh_build_coarse(FrameLensMesh *mesh, int width, int height,
return 0;
}
/* Clamp an `unsigned long` cost counter into the persistent uint64_t field.
* On every platform this project builds for the two widths are equal, so the
* branch compiles away; it keeps a wider `unsigned long` from silently wrapping. */
static uint64_t cost_counter_u64(unsigned long value) {
#if ULONG_MAX > UINT64_MAX
return value > (unsigned long)UINT64_MAX ? UINT64_MAX : (uint64_t)value;
#else
return (uint64_t)value;
#endif
}
/* Store an endpoint into a lens vertex. For an UNRESOLVED result the last
* accepted continuous state is kept so the ray can be resumed; `granted_limit`
* is the total accepted-step budget that produced this result (0 when it is
@@ -112,6 +124,14 @@ static void store_endpoint(LensVertex *vertex, const RayEndpoint *endpoint,
vertex->reason = endpoint->reason;
vertex->end_id = endpoint->end_id;
vertex->traced = 1;
/* Real integration cost for every outcome. The endpoint already reports the
* cumulative accepted/rejected/RHS totals of a resumed trace, so this is an
* overwrite, never an accumulation. A fresh (analytically pre-routed) escape
* legitimately reports zero. */
vertex->trace_accepted_steps = (uint64_t)endpoint->accepted_steps;
vertex->trace_rejected_steps = (uint64_t)endpoint->rejected_steps;
vertex->trace_rhs_evaluations = cost_counter_u64(endpoint->rhs_evaluations);
vertex->trace_cost_saturated = 0;
if (endpoint->outcome == RAY_OUTCOME_ESCAPED) {
for (int axis = 0; axis < 3; ++axis)
vertex->n_infinity[axis] = endpoint->n_infinity[axis];
@@ -127,13 +147,60 @@ static void store_endpoint(LensVertex *vertex, const RayEndpoint *endpoint,
vertex->continuation_log_alpha_p0 = endpoint->final_log_alpha_p0;
vertex->continuation_log_alpha_p0_0 = endpoint->final_log_alpha_p0_0;
vertex->continuation_steps = endpoint->accepted_steps;
vertex->continuation_integration_start_time =
endpoint->integration_start_time;
vertex->continuation_next_step = endpoint->next_step;
vertex->continuation_rejected_steps = endpoint->rejected_steps;
vertex->continuation_rhs_evaluations = endpoint->rhs_evaluations;
vertex->continuation_previous_rejected = endpoint->previous_rejected;
/* Prefer the explicit grant of this request, then the trace's actual
* configured step budget. Only legacy/synthetic endpoints whose
* accepted_step_limit is zero fall back to the spent accepted count; real
* production must never infer a grant from steps already spent. */
const unsigned int used =
granted_limit != 0 ? granted_limit : endpoint->accepted_steps;
granted_limit != 0 ? granted_limit
: endpoint->accepted_step_limit != 0 ? endpoint->accepted_step_limit
: endpoint->accepted_steps;
if (used > vertex->continuation_limit)
vertex->continuation_limit = used;
/* The granted time quota is monotonic like the step quota: a resume must
* never silently shrink a budget that a previous generation granted. */
if (endpoint->lookback_limit > vertex->continuation_lookback_limit)
vertex->continuation_lookback_limit = endpoint->lookback_limit;
}
}
int frame_vertex_continuation_state(const LensVertex *vertex,
GeodesicRayState *state) {
if (vertex == NULL || state == NULL ||
vertex->outcome != RAY_OUTCOME_UNRESOLVED ||
!isfinite(vertex->continuation_t) ||
!isfinite(vertex->continuation_log_alpha_p0) ||
!isfinite(vertex->continuation_log_alpha_p0_0) ||
!isfinite(vertex->continuation_integration_start_time) ||
!isfinite(vertex->continuation_next_step))
return -1;
for (int axis = 0; axis < 3; ++axis)
if (!isfinite(vertex->continuation_x[axis]) ||
!isfinite(vertex->continuation_Pi[axis]))
return -1;
*state = (GeodesicRayState){
.coordinate_time = vertex->continuation_t,
.x = {vertex->continuation_x[0], vertex->continuation_x[1],
vertex->continuation_x[2]},
.Pi = {vertex->continuation_Pi[0], vertex->continuation_Pi[1],
vertex->continuation_Pi[2]},
.log_alpha_p0 = vertex->continuation_log_alpha_p0,
.log_alpha_p0_0 = vertex->continuation_log_alpha_p0_0,
.steps = vertex->continuation_steps,
.integration_start_time = vertex->continuation_integration_start_time,
.next_step = vertex->continuation_next_step,
.rejected_steps = vertex->continuation_rejected_steps,
.rhs_evaluations = vertex->continuation_rhs_evaluations,
.previous_rejected = vertex->continuation_previous_rejected};
return 0;
}
int frame_lens_mesh_trace(FrameLensMesh *mesh, const SpacetimeSource *spacetime,
const ObserverState *observer,
const GeodesicTraceConfig *trace) {
@@ -643,14 +710,77 @@ static size_t witness_find(const FrameLensMesh *mesh, size_t a, size_t b) {
return SIZE_MAX;
}
static unsigned int retry_limit(unsigned int current,
const RefinementConfig *config) {
static unsigned int retry_step_limit(unsigned int current,
const RefinementConfig *config) {
const unsigned int remaining = config->max_total_steps - current;
return current + (config->retry_step_increment < remaining
? config->retry_step_increment
: remaining);
}
static double retry_time_limit(double current,
const RefinementConfig *config) {
const double remaining = config->max_total_lookback_time - current;
return current + (config->retry_lookback_increment < remaining
? config->retry_lookback_increment
: remaining);
}
/* One local coordinate-time ULP measured with nextafter. It bounds the
* rounding of the boundary-limited final step that produced a lookback stop. */
static double retry_time_ulp(double t) {
const double up = nextafter(t, INFINITY);
const double down = nextafter(t, -INFINITY);
return fmax(fabs(up - t), fabs(t - down));
}
/* Decide whether an UNRESOLVED vertex should be retried and, if so, fill the
* independently saturated step/time budgets. A quota counts as blocking only
* when the ray actually reached it; a retry is refused when any blocking quota
* cannot open a strictly larger representable region, because growing a
* different quota cannot make progress. Returns nonzero when a retry request
* should be emitted. */
static int retry_budgets(const LensVertex *vertex,
const RefinementConfig *config,
unsigned int *step_limit, double *lookback_limit) {
const unsigned int step_current = vertex->continuation_limit;
const double time_current = vertex->continuation_lookback_limit;
const double start = vertex->continuation_integration_start_time;
/* Use the same quantity and direction as the integrator's stop test: the
* ray is time-blocked when its stop time reached the lookback left boundary
* t <= start - quota. The DP loop exits on exactly that comparison, so a
* single time ULP is allowed only for the boundary-limited final step. */
const double time_left = start - time_current;
const int time_blocked =
time_current > 0.0 &&
vertex->continuation_t <= time_left + retry_time_ulp(time_left);
const int step_blocked = vertex->continuation_steps >= step_current;
const int step_can_grow =
config->retry_step_increment > 0 && step_current < config->max_total_steps;
const int time_can_grow =
config->retry_lookback_increment > 0.0 &&
time_current < config->max_total_lookback_time;
const unsigned int step_candidate =
step_can_grow ? retry_step_limit(step_current, config) : step_current;
const double time_candidate =
time_can_grow ? retry_time_limit(time_current, config) : time_current;
/* A candidate only counts when it opens a representable region: integer
* steps must strictly increase, and the time left boundary must strictly
* move. A larger quota that rounds to the same boundary (or is unchanged at
* a large time origin) is not effective growth and must not launch a retry
* that can never progress. */
const int step_effective = step_candidate > step_current;
const int time_effective = time_candidate > time_current &&
(start - time_candidate) < (start - time_current);
if (!(step_blocked || time_blocked))
return 0;
if ((step_blocked && !step_effective) || (time_blocked && !time_effective))
return 0;
*step_limit = step_effective ? step_candidate : step_current;
*lookback_limit = time_effective ? time_candidate : time_current;
return 1;
}
static int triangle_allows_children(const FrameLensMesh *mesh,
const LensTriangle *triangle,
const RefinementConfig *config);
@@ -677,7 +807,9 @@ int frame_lens_mesh_prepare_generation(FrameLensMesh *mesh,
* as is an off-mesh unresolved witness. A witness retry carries its edge so
* the refinement decision sees the updated state; both passes share
* retry_seen so a promoted witness is never requested twice. */
if (config->retry_step_increment > 0 && mesh->vertex_count > 0) {
if ((config->retry_step_increment > 0 ||
config->retry_lookback_increment > 0.0) &&
mesh->vertex_count > 0) {
unsigned char *retry_seen = calloc(mesh->vertex_count, 1);
if (retry_seen == NULL)
return -1;
@@ -694,14 +826,14 @@ int frame_lens_mesh_prepare_generation(FrameLensMesh *mesh,
if (vertex->outcome != RAY_OUTCOME_UNRESOLVED || retry_seen[v])
continue;
retry_seen[v] = 1;
if (vertex->continuation_limit >= config->max_total_steps)
continue; /* capped: reported as budget-incomplete, not retried */
const unsigned int limit = retry_limit(vertex->continuation_limit, config);
if (limit <= vertex->continuation_limit)
continue;
unsigned int step_limit;
double time_limit;
if (!retry_budgets(vertex, config, &step_limit, &time_limit))
continue; /* blocked quota at cap: reported as budget-incomplete */
FrameSample retry = {.kind = FRAME_SAMPLE_RETRY,
.vertex_id = v,
.step_limit = limit,
.step_limit = step_limit,
.lookback_limit = time_limit,
.vertex = *vertex};
if (add_sample(mesh, &retry)) {
free(retry_seen);
@@ -716,15 +848,16 @@ int frame_lens_mesh_prepare_generation(FrameLensMesh *mesh,
if (retry_seen[v] || vertex->outcome != RAY_OUTCOME_UNRESOLVED)
continue;
retry_seen[v] = 1;
if (vertex->continuation_limit >= config->max_total_steps)
continue;
const unsigned int limit = retry_limit(vertex->continuation_limit, config);
if (limit <= vertex->continuation_limit)
unsigned int step_limit;
double time_limit;
if (!retry_budgets(vertex, config, &step_limit, &time_limit))
continue;
FrameSample retry = {.kind = FRAME_SAMPLE_RETRY, .vertex_id = v,
.edge_vertex = {vertex->probe_edge[0],
vertex->probe_edge[1]},
.step_limit = limit, .vertex = *vertex};
.step_limit = step_limit,
.lookback_limit = time_limit,
.vertex = *vertex};
if (add_sample(mesh, &retry)) {
free(retry_seen);
return -1;
@@ -994,6 +1127,43 @@ void frame_lens_mesh_boundary_stats(FrameLensMesh *mesh,
stats->retry_requests = mesh->retry_requests;
}
static uint64_t sat_add_u64(uint64_t total, uint64_t value, int *saturated) {
if (UINT64_MAX - total < value) {
*saturated = 1;
return UINT64_MAX;
}
return total + value;
}
void frame_lens_mesh_trace_stats(const FrameLensMesh *mesh,
FrameTraceStats *stats) {
if (stats == NULL)
return;
*stats = (FrameTraceStats){0};
if (mesh == NULL)
return;
int saturated = 0;
for (size_t i = 0; i < mesh->vertex_count; ++i) {
const LensVertex *vertex = &mesh->vertices[i];
if (!vertex->traced)
continue;
stats->accepted_steps =
sat_add_u64(stats->accepted_steps, vertex->trace_accepted_steps,
&saturated);
stats->rejected_steps =
sat_add_u64(stats->rejected_steps, vertex->trace_rejected_steps,
&saturated);
stats->rhs_evaluations =
sat_add_u64(stats->rhs_evaluations, vertex->trace_rhs_evaluations,
&saturated);
++stats->vertices;
if (vertex->trace_cost_saturated)
++stats->saturated_vertices;
}
if (saturated && stats->saturated_vertices == 0)
stats->saturated_vertices = 1;
}
static LensVertex midpoint_vertex(const LensVertex *a, const LensVertex *b) {
LensVertex result = {.image_x = 0.5 * (a->image_x + b->image_x),
.image_y = 0.5 * (a->image_y + b->image_y)};
@@ -1438,6 +1608,18 @@ void frame_retry_config_defaults(RefinementConfig *config,
trace->max_steps > (UINT_MAX / 4u) ? trace->max_steps
: trace->max_steps * 4u;
}
/* The coordinate-time retry budget is a resource quota owned by the
* adaptive stepper; it has no meaning for the fixed-step RK4 path, so an
* RK4 config keeps its zero time fields. */
if (trace->stepper == GEODESIC_STEPPER_DP54 &&
config->retry_lookback_increment == 0.0 &&
config->max_total_lookback_time == 0.0) {
config->retry_lookback_increment = trace->max_lookback_time;
config->max_total_lookback_time =
trace->max_lookback_time > DBL_MAX / 4.0
? DBL_MAX
: trace->max_lookback_time * 4.0;
}
}
int frame_lens_mesh_refine(FrameLensMesh *mesh,
@@ -1472,20 +1654,24 @@ int frame_lens_mesh_refine_with_progress(
if (sample->cached) continue;
RayEndpoint endpoint;
if (sample->kind == FRAME_SAMPLE_RETRY) {
const LensVertex *v = &sample->vertex;
GeodesicRayState state = {
.coordinate_time = v->continuation_t,
.x = {v->continuation_x[0], v->continuation_x[1],
v->continuation_x[2]},
.Pi = {v->continuation_Pi[0], v->continuation_Pi[1],
v->continuation_Pi[2]},
.log_alpha_p0 = v->continuation_log_alpha_p0,
.log_alpha_p0_0 = v->continuation_log_alpha_p0_0,
.steps = v->continuation_steps};
GeodesicTraceConfig retry_config = *trace;
retry_config.max_steps = sample->step_limit;
endpoint = geodesic_trace_past_from_state(spacetime, &state,
&retry_config);
GeodesicRayState state;
if (frame_vertex_continuation_state(&sample->vertex, &state)) {
/* An UNRESOLVED request without a valid resume payload is a
* protocol error, never a silent discard. */
endpoint = (RayEndpoint){.magnification = 1.0,
.end_id = SPACETIME_END_NONE,
.outcome = RAY_OUTCOME_INCOMPLETE,
.reason = RAY_REASON_PROTOCOL_ERROR,
.stop_coordinate_time = NAN,
.threshold_value = NAN};
} else {
GeodesicTraceConfig retry_config = *trace;
retry_config.max_steps = sample->step_limit;
if (sample->lookback_limit > 0.0)
retry_config.max_lookback_time = sample->lookback_limit;
endpoint = geodesic_trace_past_from_state(spacetime, &state,
&retry_config);
}
} else {
endpoint = geodesic_trace_past(spacetime, observer,
sample->vertex.camera_direction, trace);
+55
View File
@@ -8,6 +8,7 @@
#include "spacetime.h"
#include <stddef.h>
#include <stdint.h>
typedef struct {
double image_x, image_y;
@@ -29,10 +30,31 @@ typedef struct {
double continuation_log_alpha_p0_0;
unsigned int continuation_steps;
unsigned int continuation_limit;
/* Adaptive (DP54) resume control/cost state, valid with the fields above when
* outcome == RAY_OUTCOME_UNRESOLVED. An integration start time of zero is a
* legal coordinate time; the legacy RK4 path leaves these zero. */
double continuation_integration_start_time;
double continuation_next_step;
unsigned int continuation_rejected_steps;
unsigned long continuation_rhs_evaluations;
unsigned int continuation_previous_rejected;
/* Granted total coordinate-time lookback budget accumulated for this
* continuation. Zero when the legacy RK4 path supplies no time quota. */
double continuation_lookback_limit;
/* Persistent off-mesh probe witness; also participates in completion checks.
* When set, probe_edge holds the sorted edge (a,b) this witness samples. */
int diagnostic_probe;
size_t probe_edge[2];
/* Actual integration cost for this persistent vertex, copied from the
* endpoint for every outcome (not only UNRESOLVED). The endpoint already
* carries cumulative accepted/rejected/RHS counts for a resumed trace, so
* each install overwrites rather than accumulates and a witness promoted in
* place keeps one physical sample counted exactly once. `uint64_t` with the
* saturated flag records a counter that had to be capped instead of wrapping. */
uint64_t trace_accepted_steps;
uint64_t trace_rejected_steps;
uint64_t trace_rhs_evaluations;
int trace_cost_saturated;
} LensVertex;
typedef struct {
@@ -77,6 +99,13 @@ typedef struct {
* budget-incomplete instead of silently blackened. */
unsigned int retry_step_increment;
unsigned int max_total_steps;
/* Independent coordinate-time retry budget. The step and time quotas are
* saturated separately and a retry is requested only while a quota that
* actually blocked the ray can still grow. Both zero on a DP trace derive
* from trace->max_lookback_time; a zero increment with a positive cap keeps
* the time budget fixed. Ignored by the legacy RK4 path. */
double retry_lookback_increment;
double max_total_lookback_time;
} RefinementConfig;
typedef enum {
@@ -91,6 +120,9 @@ typedef struct {
size_t edge_vertex[2];
/* For FRAME_SAMPLE_RETRY: the new total accepted-step budget. */
unsigned int step_limit;
/* For FRAME_SAMPLE_RETRY: the new total coordinate-time lookback budget;
* zero keeps the trace config budget. */
double lookback_limit;
int cached;
LensVertex vertex;
} FrameSample;
@@ -116,6 +148,19 @@ typedef struct {
size_t budget_incomplete_triangles;
} FrameBoundaryStats;
/* Truthful integration cost aggregated over a finalized mesh. Every
* persistent vertex (including promoted probe witnesses) is counted exactly
* once, so summing after refinement does not double-count retries. Counters
* saturate at UINT64_MAX and increment `saturated_vertices` instead of
* wrapping. `vertices` is the number of traced vertices the cost covers. */
typedef struct {
uint64_t accepted_steps;
uint64_t rejected_steps;
uint64_t rhs_evaluations;
size_t vertices;
size_t saturated_vertices;
} FrameTraceStats;
typedef struct {
LensVertex *vertices;
LensTriangle *triangles;
@@ -188,6 +233,12 @@ const FrameSample *frame_lens_mesh_samples(const FrameLensMesh *mesh,
size_t *count);
int frame_lens_mesh_install_sample(FrameLensMesh *mesh, size_t sample_id,
const RayEndpoint *endpoint);
/* Rebuild the full adaptive resume state recorded in a vertex's continuation
* fields into `state`. Returns 0 on success and -1 when the vertex has no
* valid resume payload. The frame refinement loop and the movie scheduler
* both use this so neither can silently drop a control field. */
int frame_vertex_continuation_state(const LensVertex *vertex,
GeodesicRayState *state);
/* Applies a nonempty completed generation; callers skip frames for which
* prepare_generation returned zero. Returns the number of newly added
* vertices, zero when no topology change was made, or -1 on failure. */
@@ -209,6 +260,10 @@ int frame_lens_mesh_refine_with_progress(
void frame_lens_mesh_boundary_stats(FrameLensMesh *mesh,
const RefinementConfig *config,
FrameBoundaryStats *stats);
/* Aggregate the per-vertex integration cost of a finalized mesh. Unlike
* boundary_stats this does not modify the mesh and needs no config. */
void frame_lens_mesh_trace_stats(const FrameLensMesh *mesh,
FrameTraceStats *stats);
/* Fill retry defaults (derived from the trace step budget) when the caller
* did not configure them explicitly. A zero max_total_steps disables retry
* only if retry_step_increment is also zero. */
+1601 -68
View File
File diff suppressed because it is too large. Load diff
+79 -2
View File
@@ -56,7 +56,13 @@ typedef struct {
/* Last trusted state at termination. For an ESCAPED endpoint this is the
* (finite) numerical truncation position, not the true parameter end. */
double stop_coordinate_time;
unsigned int accepted_steps;
unsigned int accepted_steps; /* accepted (spent) steps */
/* Actual total accepted-step budget the trace was configured with, kept
* distinct from the spent `accepted_steps`. Production endpoints always
* carry it so a retry layer never has to infer a grant from the spent count;
* zero only on legacy/synthetic endpoints. For a resumed trace this is the
* new total grant, not the additional steps. */
unsigned int accepted_step_limit;
/* Last trusted continuous state, used to resume an UNRESOLVED ray from its
* last accepted step instead of replaying it from the camera. */
double final_x[3];
@@ -65,14 +71,73 @@ typedef struct {
double final_log_alpha_p0_0; /* original reference L0 for retries */
/* Monitored threshold value at termination, NAN when not applicable. */
double threshold_value;
/* Adaptive (DP54) controller state and cumulative cost at termination,
* copied from the last trusted ray state so a retry resumes without
* replaying. The controller fields below (integration window, suggested
* step, previous-rejection flag, rejected-step count) are not applicable to
* RK4 and stay zero there. `rhs_evaluations` is different: it is the real
* cumulative metric-evaluation cost for every stepper, RK4 included. */
double integration_start_time; /* adaptive window start (entry/activation) */
double next_step; /* positive trial-step magnitude, 0 if unset */
unsigned int rejected_steps; /* cumulative rejected trials (DP54 only) */
unsigned long rhs_evaluations; /* cumulative actual RHS evaluations */
unsigned int previous_rejected;/* 1 when the last trial was rejected */
/* Coordinate-time lookback budget actually granted to this trace, recorded
* so the frame/movie retry layer can accumulate the per-sample total
* independently of the accepted-step budget. Zero for the legacy RK4 path
* and for endpoints that never reached a trusted adaptive state. This is a
* resource quota, not part of the continuous resume state. */
double lookback_limit;
} RayEndpoint;
/* ODE stepper selection for the past-directed geodesic integrator. RK4 is
* the legacy fixed-step path and is value 0 so that zero-initialized configs
* keep their historical behavior. DP54 is the explicit adaptive
* Dormand-Prince 5(4) stepper; both paths share the same termination,
* lookback-budget and retry-control interfaces. */
typedef enum {
GEODESIC_STEPPER_RK4 = 0,
GEODESIC_STEPPER_DP54 = 1
} GeodesicStepper;
typedef struct {
double coordinate_time_step;
unsigned int max_steps;
/* Normal dark terminal for every backend. No backend may substitute a
* position/horizon cutoff for physical capture. */
ThresholdPolicy threshold;
/* Stepper selection (see GeodesicStepper). */
GeodesicStepper stepper;
/* DP54 tolerances. When stepper == GEODESIC_STEPPER_DP54 all four must be
* finite and strictly positive; they are never silently defaulted. Error
* scales: position uses atol_x + rtol*max(|dx|, |h*dxdt|) so a distant
* coordinate origin cannot relax the local length scale; Pi uses
* atol_Pi + rtol*max(|Pi_before|, |Pi_candidate|); L uses
* atol_L + rtol*max(1, |dL|). */
double atol_x;
double atol_Pi;
double atol_L;
double rtol;
/* DP54 trial-step magnitude bounds in coordinate time. Both finite and
* 0 < min_step <= max_step. The initial trial step and the driver
* suggestion reuse coordinate_time_step. */
double min_step;
double max_step;
/* DP54 maximum number of consecutive rejected trials before reporting an
* integration error. Must be nonzero. */
unsigned int consecutive_rejection_limit;
/* DP54 explicit coordinate-time lookback budget (length, finite and
* strictly positive). DP54 never derives its history from the legacy
* coordinate_time_step * max_steps product: reaching
* integration_start_time - max_lookback_time while the trajectory is still
* trustworthy is UNRESOLVED/BUDGET_EXHAUSTED, distinct from a source slab
* data time failure (TIME_RANGE_EXHAUSTED). */
double max_lookback_time;
} GeodesicTraceConfig;
typedef struct {
@@ -83,7 +148,19 @@ typedef struct {
/* Reference L at the start of this ray's integration, carried unchanged
* through retries so THRESHOLD_LOG_ENERGY_GROWTH stays camera-relative. */
double log_alpha_p0_0;
unsigned int steps;
unsigned int steps; /* accepted steps (all steppers) */
/* Adaptive DP54 controller state; the controller fields default to zero and
* are ignored by the RK4 path. `integration_start_time` is the inner
* activation/entry time (not the L0 camera energy); `next_step` is a
* positive trial-step magnitude and is initialized from
* config->coordinate_time_step on the first adaptive advance.
* `rhs_evaluations` is the real cumulative RHS cost for every stepper;
* `rejected_steps`/`previous_rejected` remain DP54-only. */
double integration_start_time;
double next_step;
unsigned int rejected_steps; /* cumulative rejected trials (DP54 only) */
unsigned long rhs_evaluations; /* cumulative actual RHS evaluations */
unsigned int previous_rejected;/* 1 when the last trial was rejected */
} GeodesicRayState;
typedef enum {
+119 -17
View File
@@ -11,9 +11,15 @@
* because their padding and size_t width are ABI-dependent. */
static const unsigned char lens_map_magic[8] = {'G', 'R', 'L', 'E', 'N', 'S', 1, 0};
/* Version 2 stores the two-level RayOutcome instead of the removed
* RayEndpointStatus. Version 1 files are rejected: their old captured bit
* cannot be upgraded into the new dark/unresolved/error provenance. */
enum { LENS_MAP_VERSION = 2, LENS_MAP_ENDIAN = 0x01020304u };
* RayEndpointStatus. Version 3 appends the adaptive DP54 policy and the
* per-vertex integration cost; it still supports v2 import as a legacy
* fixed-step RK4 map. Version 1 is rejected: its old captured bit cannot be
* upgraded into the new dark/unresolved/error provenance. */
enum {
LENS_MAP_VERSION = 3,
LENS_MAP_VERSION_LEGACY = 2,
LENS_MAP_ENDIAN = 0x01020304u
};
static uint32_t crc32_update(uint32_t crc, const void *data, size_t size) {
const unsigned char *bytes = data;
@@ -63,6 +69,55 @@ static int unit_vector(const double v[3]) {
const double n2 = v[0]*v[0] + v[1]*v[1] + v[2]*v[2];
return isfinite(n2) && fabs(n2 - 1.0) <= 1e-9;
}
/* Explicit switch over the stable wire codes; unknown codes are rejected
* instead of being reinterpreted as a C enum value. */
static int integrator_code_known(uint32_t code) {
switch ((GeodesicStepper)code) {
case GEODESIC_STEPPER_RK4:
case GEODESIC_STEPPER_DP54:
return 1;
default:
return 0;
}
}
/* Shared read/write schema. It deliberately does not validate mesh contents
* (valid_mesh does that per frame): it only proves the provenance is a
* complete, self-consistent policy. RK4 maps must state that no adaptive
* configuration applies; DP54 maps must carry it in full. */
static int provenance_valid(const LensMapProvenance *p) {
if (p == NULL || !isfinite(p->threshold_value) ||
p->threshold_kind > THRESHOLD_LOG_ENERGY_GROWTH ||
!isfinite(p->min_edge_pixels) || p->min_edge_pixels < 0.0 ||
!isfinite(p->min_area_pixels2) || p->min_area_pixels2 < 0.0 ||
!isfinite(p->coordinate_time_step) || p->coordinate_time_step <= 0.0 ||
!integrator_code_known(p->integrator))
return 0;
if (p->integrator == (uint32_t)GEODESIC_STEPPER_RK4) {
return p->atol_x == 0.0 && p->atol_Pi == 0.0 && p->atol_L == 0.0 &&
p->rtol == 0.0 && p->min_step == 0.0 && p->max_step == 0.0 &&
p->max_lookback_time == 0.0 && p->retry_lookback_increment == 0.0 &&
p->max_total_lookback_time == 0.0 &&
p->max_consecutive_rejections == 0;
}
/* DP54. */
return p->initial_max_steps != 0 && isfinite(p->atol_x) && p->atol_x > 0.0 &&
isfinite(p->atol_Pi) && p->atol_Pi > 0.0 &&
isfinite(p->atol_L) && p->atol_L > 0.0 && isfinite(p->rtol) &&
p->rtol > 0.0 && isfinite(p->min_step) && p->min_step > 0.0 &&
isfinite(p->max_step) && p->max_step > 0.0 &&
p->min_step <= p->max_step &&
p->coordinate_time_step >= p->min_step &&
p->coordinate_time_step <= p->max_step &&
isfinite(p->max_lookback_time) && p->max_lookback_time > 0.0 &&
p->max_consecutive_rejections != 0 &&
isfinite(p->retry_lookback_increment) &&
p->retry_lookback_increment >= 0.0 &&
isfinite(p->max_total_lookback_time) &&
p->max_total_lookback_time >= p->max_lookback_time;
}
static int valid_mesh(const FrameLensMesh *m) {
if (m == NULL || m->vertex_count == 0 || m->triangle_count == 0) return 0;
for (size_t i = 0; i < m->vertex_count; ++i) {
@@ -87,7 +142,8 @@ int lens_map_write(const char *path, int width, int height, double fov,
const LensMapFrame *frames, size_t frame_count) {
if (path == NULL || provenance == NULL || frames == NULL || width <= 0 ||
height <= 0 || !isfinite(fov) || fov <= 0.0 || fov >= 179.0 ||
frame_count == 0 || frame_count > UINT64_MAX)
frame_count == 0 || frame_count > UINT64_MAX ||
!provenance_valid(provenance))
return -1;
for (size_t i = 0; i < frame_count; ++i) if (!valid_mesh(&frames[i].mesh)) return -1;
FILE *file = fopen(path, "wb"); if (file == NULL) return -1;
@@ -105,7 +161,17 @@ int lens_map_write(const char *path, int width, int height, double fov,
write_double(file, provenance->min_edge_pixels, NULL) ||
write_double(file, provenance->min_area_pixels2, NULL) ||
write_double(file, provenance->coordinate_time_step, NULL) ||
write_u32(file, provenance->initial_max_steps, NULL);
write_u32(file, provenance->initial_max_steps, NULL) ||
write_double(file, provenance->atol_x, NULL) ||
write_double(file, provenance->atol_Pi, NULL) ||
write_double(file, provenance->atol_L, NULL) ||
write_double(file, provenance->rtol, NULL) ||
write_double(file, provenance->min_step, NULL) ||
write_double(file, provenance->max_step, NULL) ||
write_double(file, provenance->max_lookback_time, NULL) ||
write_double(file, provenance->retry_lookback_increment, NULL) ||
write_double(file, provenance->max_total_lookback_time, NULL) ||
write_u32(file, provenance->max_consecutive_rejections, NULL);
for (size_t f = 0; !failed && f < frame_count; ++f) {
const FrameLensMesh *m = &frames[f].mesh; uint32_t crc = UINT32_MAX;
failed = write_u64(file, frames[f].frame_id, NULL) ||
@@ -122,7 +188,10 @@ int lens_map_write(const char *path, int width, int height, double fov,
failed = failed || write_double(file, v->log_frequency_ratio, &crc) ||
write_u32(file, (uint32_t)v->end_id, &crc) ||
write_u32(file, (uint32_t)v->outcome, &crc) ||
write_u32(file, (uint32_t)v->reason, &crc);
write_u32(file, (uint32_t)v->reason, &crc) ||
write_u64(file, v->trace_accepted_steps, &crc) ||
write_u64(file, v->trace_rejected_steps, &crc) ||
write_u64(file, v->trace_rhs_evaluations, &crc);
}
for (size_t i = 0; !failed && i < m->triangle_count; ++i) {
for (int j = 0; j < 3; ++j) failed = failed || write_u64(file, m->triangles[i].vertex[j], &crc);
@@ -147,11 +216,20 @@ int lens_map_read(const char *path, LensMapProvenance *provenance,
*map = (LensMap){0}; FILE *file = fopen(path, "rb"); if (file == NULL) return -1;
unsigned char magic[8]; uint32_t version, endian, width, height; uint64_t count;
LensMapProvenance prov = {0};
int failed = read_bytes(file, magic, sizeof magic, NULL) || memcmp(magic, lens_map_magic, sizeof magic) ||
int failed = read_bytes(file, magic, sizeof magic, NULL) ||
memcmp(magic, lens_map_magic, sizeof magic) ||
read_u32(file, &version, NULL) || read_u32(file, &endian, NULL) ||
read_u32(file, &width, NULL) || read_u32(file, &height, NULL) ||
read_double(file, &map->horizontal_fov_deg, NULL) || read_u64(file, &count, NULL) ||
read_u32(file, &prov.threshold_kind, NULL) ||
read_double(file, &map->horizontal_fov_deg, NULL) || read_u64(file, &count, NULL);
/* Branch on the version before reading any version-specific header field, so
* a v2 file is never parsed with the v3 layout. */
if (failed)
goto done;
if (version != LENS_MAP_VERSION && version != LENS_MAP_VERSION_LEGACY) {
failed = 1;
goto done;
}
failed = read_u32(file, &prov.threshold_kind, NULL) ||
read_u32(file, &prov.threshold_policy_version, NULL) ||
read_double(file, &prov.threshold_value, NULL) ||
read_u32(file, &prov.retry_step_increment, NULL) ||
@@ -161,16 +239,35 @@ int lens_map_read(const char *path, LensMapProvenance *provenance,
read_double(file, &prov.min_edge_pixels, NULL) ||
read_double(file, &prov.min_area_pixels2, NULL) ||
read_double(file, &prov.coordinate_time_step, NULL) ||
read_u32(file, &prov.initial_max_steps, NULL) ||
version != LENS_MAP_VERSION || endian != LENS_MAP_ENDIAN || width == 0 || height == 0 ||
width > INT32_MAX || height > INT32_MAX || !isfinite(map->horizontal_fov_deg) ||
map->horizontal_fov_deg <= 0.0 || map->horizontal_fov_deg >= 179.0 || count == 0 ||
prov.threshold_kind > THRESHOLD_LOG_ENERGY_GROWTH ||
!isfinite(prov.threshold_value) ||
prov.coordinate_time_step < 0.0 || !isfinite(prov.coordinate_time_step) ||
count > SIZE_MAX / sizeof *map->frames;
read_u32(file, &prov.initial_max_steps, NULL);
if (!failed && version == LENS_MAP_VERSION) {
failed = read_double(file, &prov.atol_x, NULL) ||
read_double(file, &prov.atol_Pi, NULL) ||
read_double(file, &prov.atol_L, NULL) ||
read_double(file, &prov.rtol, NULL) ||
read_double(file, &prov.min_step, NULL) ||
read_double(file, &prov.max_step, NULL) ||
read_double(file, &prov.max_lookback_time, NULL) ||
read_double(file, &prov.retry_lookback_increment, NULL) ||
read_double(file, &prov.max_total_lookback_time, NULL) ||
read_u32(file, &prov.max_consecutive_rejections, NULL);
}
/* Legacy v2 carried no adaptive policy. Its missing fields stay zero, and
* its integrator must be RK4: a v2 DP54 claim is impossible and rejected. */
if (!failed && version == LENS_MAP_VERSION_LEGACY &&
prov.integrator != (uint32_t)GEODESIC_STEPPER_RK4)
failed = 1;
if (!failed &&
(endian != LENS_MAP_ENDIAN || width == 0 || height == 0 ||
width > INT32_MAX || height > INT32_MAX ||
!isfinite(map->horizontal_fov_deg) ||
map->horizontal_fov_deg <= 0.0 || map->horizontal_fov_deg >= 179.0 ||
count == 0 || count > SIZE_MAX / sizeof *map->frames ||
!provenance_valid(&prov)))
failed = 1;
if (failed) goto done;
map->provenance = prov;
map->file_version = version;
if (provenance != NULL)
*provenance = prov;
map->width = (int)width; map->height = (int)height; map->frame_count = (size_t)count;
@@ -204,6 +301,11 @@ int lens_map_read(const char *path, LensMapProvenance *provenance,
v->outcome = (RayOutcome)outcome;
v->reason = (RayReason)reason;
v->traced = 1;
if (version == LENS_MAP_VERSION) {
failed = failed || read_u64(file, &v->trace_accepted_steps, &crc) ||
read_u64(file, &v->trace_rejected_steps, &crc) ||
read_u64(file, &v->trace_rhs_evaluations, &crc);
}
}
for (size_t i = 0; !failed && i < frame->mesh.triangle_count; ++i) {
for (int j = 0; j < 3; ++j) {
+28 -1
View File
@@ -25,19 +25,46 @@ typedef struct {
uint32_t retry_step_increment;
uint32_t max_total_steps;
uint32_t max_level;
uint32_t integrator; /* 0 = fixed-step RK4 (transitional) */
/* Stable wire code: 0 = fixed-step RK4, 1 = adaptive Dormand-Prince 5(4).
* Never serialize the C enum directly; unknown codes are rejected. */
uint32_t integrator;
double min_edge_pixels;
double min_area_pixels2;
/* Integration source: the coordinate-time step and the initial per-ray
* accepted-step budget used for the first trace. */
double coordinate_time_step;
uint32_t initial_max_steps;
/* Adaptive DP54 configuration. For an RK4 map every field here is exactly
* zero, which makes "not applicable" explicit on the wire instead of
* implying a silently defaulted stepper. For a DP54 map the tolerances,
* step bounds and explicit lookback budget are finite and strictly positive,
* min_step <= coordinate_time_step <= max_step, and the consecutive
* rejection limit is nonzero. */
double atol_x;
double atol_Pi;
double atol_L;
double rtol;
double min_step;
double max_step;
double max_lookback_time;
/* Retry resource policy: coordinate-time increment per retry and the total
* lookback cap. `retry_lookback_increment` may be zero (fixed time budget);
* a production DP54 map requires max_total_lookback_time >=
* max_lookback_time (the initial grant). Both zero on an RK4 map. */
double retry_lookback_increment;
double max_total_lookback_time;
uint32_t max_consecutive_rejections;
} LensMapProvenance;
typedef struct {
int width, height;
double horizontal_fov_deg;
LensMapProvenance provenance;
/* On-disk format version actually read (2 for legacy RK4, 3 for the current
* adaptive-aware format). 0 when the struct was not produced by a read.
* A v2 map carries no per-vertex integration cost, so those replay as zero;
* the unknown cost is explicit through this version rather than implied. */
uint32_t file_version;
LensMapFrame *frames;
size_t frame_count;
} LensMap;
+478 -63
View File
@@ -10,6 +10,8 @@
#include "spacetime.h"
#include <errno.h>
#include <float.h>
#include <inttypes.h>
#include <limits.h>
#include <math.h>
#include <omp.h>
@@ -67,6 +69,24 @@ typedef struct {
const char *fast_fftw_wisdom_path;
const char *blackbody_table_path;
RefinementConfig refinement;
/* Integrator selection and its explicit-override flags. A flag records that
* a value was named on the command line so an explicit zero is never confused
* with "not specified, use the derived default". */
GeodesicStepper stepper;
int integrator_specified;
double ode_rtol, ode_atol_x, ode_atol_pi, ode_atol_l;
double ode_initial_step, ode_min_step, ode_max_step;
unsigned int ode_max_rejections;
unsigned int trace_max_steps;
double trace_lookback_time;
double retry_lookback_increment;
double max_total_lookback_time;
int ode_rtol_specified, ode_atol_x_specified, ode_atol_pi_specified;
int ode_atol_l_specified, ode_initial_step_specified, ode_min_step_specified;
int ode_max_step_specified, ode_max_rejections_specified;
int trace_max_steps_specified, trace_lookback_time_specified;
int retry_step_increment_specified, max_total_steps_specified;
int retry_lookback_increment_specified, max_total_lookback_time_specified;
ToneMapSettings tone_map;
PngWriteSettings png;
int sensor_bloom_enabled;
@@ -76,6 +96,15 @@ typedef struct {
double sensor_bloom_transfer;
} Settings;
/* Keep the production default independent of backend-specific units and
* resource allowances, which are populated by trace_config(). */
static GeodesicStepper default_stepper(void) { return GEODESIC_STEPPER_DP54; }
static GeodesicTraceConfig trace_config(const Settings *s);
static RefinementConfig effective_refinement(const Settings *s,
const GeodesicTraceConfig *trace);
static int validate_settings(const Settings *s);
static int parse_int(const char *text, int *value) {
char *end;
errno = 0;
@@ -162,6 +191,27 @@ static int parse_finite_positive(const char *text, double *value) {
return errno || end == text || *end || !isfinite(*value) || *value <= 0.0 ? -1 : 0;
}
/* Unsigned integer with the full 32-bit range, zero allowed. Used by retry
* quotas where an explicit zero is meaningful. */
static int parse_uint_nonnegative(const char *text, unsigned int *value) {
char *end;
errno = 0;
const unsigned long parsed = strtoul(text, &end, 10);
if (errno || end == text || *end || parsed > UINT_MAX)
return -1;
*value = (unsigned int)parsed;
return 0;
}
/* Unsigned integer that must be at least one. */
static int parse_uint_positive(const char *text, unsigned int *value) {
unsigned int parsed;
if (parse_uint_nonnegative(text, &parsed) || parsed == 0)
return -1;
*value = parsed;
return 0;
}
/* Sensor-bloom transfer coefficient: finite and in [0, 1). */
static int parse_sensor_bloom_transfer(const char *text, double *value) {
char *end;
@@ -406,6 +456,7 @@ static int parse_args(int argc, char **argv, Settings *s,
.catalog_load_workers = 4,
.movie_output_workers = 0,
.fast_fftw_plan_mode = FAST_PSF_FFTW_PLAN_ESTIMATE,
.stepper = default_stepper(),
.refinement = {.angle_absolute_rad =
1e-3 * 3.14159265358979323846 / 180.0,
.angle_relative = 0.1,
@@ -474,6 +525,60 @@ static int parse_args(int argc, char **argv, Settings *s,
!parse_positive(argv[++i], &s->exposure)) {
} else if (!strcmp(argv[i], "--dark-threshold") && i + 1 < argc &&
!parse_positive(argv[++i], &s->dark_threshold)) {
} else if (!strcmp(argv[i], "--integrator") && i + 1 < argc) {
const char *mode = argv[++i];
if (!strcmp(mode, "rk4")) {
s->stepper = GEODESIC_STEPPER_RK4;
} else if (!strcmp(mode, "dp54")) {
s->stepper = GEODESIC_STEPPER_DP54;
} else {
fprintf(stderr, "--integrator expects rk4 or dp54.\n");
return -1;
}
s->integrator_specified = 1;
} else if (!strcmp(argv[i], "--ode-rtol") && i + 1 < argc &&
!parse_finite_positive(argv[++i], &s->ode_rtol)) {
s->ode_rtol_specified = 1;
} else if (!strcmp(argv[i], "--ode-atol-x") && i + 1 < argc &&
!parse_finite_positive(argv[++i], &s->ode_atol_x)) {
s->ode_atol_x_specified = 1;
} else if (!strcmp(argv[i], "--ode-atol-pi") && i + 1 < argc &&
!parse_finite_positive(argv[++i], &s->ode_atol_pi)) {
s->ode_atol_pi_specified = 1;
} else if (!strcmp(argv[i], "--ode-atol-l") && i + 1 < argc &&
!parse_finite_positive(argv[++i], &s->ode_atol_l)) {
s->ode_atol_l_specified = 1;
} else if (!strcmp(argv[i], "--ode-initial-step") && i + 1 < argc &&
!parse_finite_positive(argv[++i], &s->ode_initial_step)) {
s->ode_initial_step_specified = 1;
} else if (!strcmp(argv[i], "--ode-min-step") && i + 1 < argc &&
!parse_finite_positive(argv[++i], &s->ode_min_step)) {
s->ode_min_step_specified = 1;
} else if (!strcmp(argv[i], "--ode-max-step") && i + 1 < argc &&
!parse_finite_positive(argv[++i], &s->ode_max_step)) {
s->ode_max_step_specified = 1;
} else if (!strcmp(argv[i], "--ode-max-rejections") && i + 1 < argc &&
!parse_uint_positive(argv[++i], &s->ode_max_rejections)) {
s->ode_max_rejections_specified = 1;
} else if (!strcmp(argv[i], "--trace-max-steps") && i + 1 < argc &&
!parse_uint_positive(argv[++i], &s->trace_max_steps)) {
s->trace_max_steps_specified = 1;
} else if (!strcmp(argv[i], "--trace-lookback-time") && i + 1 < argc &&
!parse_finite_positive(argv[++i], &s->trace_lookback_time)) {
s->trace_lookback_time_specified = 1;
} else if (!strcmp(argv[i], "--retry-step-increment") && i + 1 < argc &&
!parse_uint_nonnegative(argv[++i],
&s->refinement.retry_step_increment)) {
s->retry_step_increment_specified = 1;
} else if (!strcmp(argv[i], "--max-total-steps") && i + 1 < argc &&
!parse_uint_positive(argv[++i], &s->refinement.max_total_steps)) {
s->max_total_steps_specified = 1;
} else if (!strcmp(argv[i], "--retry-lookback-increment") && i + 1 < argc &&
!parse_nonnegative(argv[++i], &s->retry_lookback_increment)) {
s->retry_lookback_increment_specified = 1;
} else if (!strcmp(argv[i], "--max-total-lookback-time") && i + 1 < argc &&
!parse_finite_positive(argv[++i], &s->max_total_lookback_time)) {
s->max_total_lookback_time_specified = 1;
} else if (!strcmp(argv[i], "--tone-map") && i + 1 < argc) {
const char *mode = argv[++i];
if (!strcmp(mode, "softclip"))
@@ -615,7 +720,7 @@ static int parse_args(int argc, char **argv, Settings *s,
return -1;
}
s->sensor_bloom_enabled = s->sensor_bloom_limit_specified;
return 0;
return validate_settings(s);
}
static void print_help(const char *program) {
@@ -719,6 +824,26 @@ static void print_help(const char *program) {
#endif
fputs(" --allow-incomplete Publish even when unresolved/error rays remain (diagnostic; output is marked incomplete)\n",
stdout);
fputs(
"\nIntegrator (adaptive DP5(4); fixed RK4 for comparison):\n"
" --integrator rk4|dp54 Past-ray stepper (default: dp54)\n"
" --ode-rtol R DP54 relative tolerance (default: 1e-9)\n"
" --ode-atol-x A DP54 position absolute tolerance (default: 1e-9 * backend length)\n"
" --ode-atol-pi A DP54 Pi absolute tolerance (default: 1e-9)\n"
" --ode-atol-l A DP54 log-energy absolute tolerance (default: 1e-9)\n"
" --ode-initial-step H Initial trial step in coordinate time (default: backend step)\n"
" --ode-min-step H DP54 minimum step (default: max(1e-12, 16 eps) * backend unit)\n"
" --ode-max-step H DP54 maximum step (default: backend dependent)\n"
" --ode-max-rejections N Consecutive rejected trials before failure (default: 32)\n"
" --trace-max-steps N Initial accepted-step budget (default: backend budget)\n"
" --trace-lookback-time T Explicit DP54 coordinate-time lookback budget\n"
" --retry-step-increment N Accepted steps added per retry (0 disables step retries)\n"
" --max-total-steps N Hard per-ray accepted-step cap across retries\n"
" --retry-lookback-increment T Coordinate-time budget added per retry (0 keeps it fixed)\n"
" --max-total-lookback-time T Hard per-ray coordinate-time cap across retries\n"
" DP54-only options are rejected under --integrator rk4;\n"
" all tracing options are rejected with --lens-map-input.\n",
stdout);
#ifdef SPACETIME_ALCUBIERRE
fputs(
"\nAlcubierre warp bubble (moving x_s(t)=v_s*t; no capture):\n"
@@ -851,9 +976,10 @@ static void report_frame_refinement(void *context, size_t generation,
}
#ifdef SPACETIME_ALCUBIERRE
/* Upper bound on the per-ray step budget. Legal parameters whose worst-case
* near-comoving ray could need more than this are rejected at startup rather
* than silently terminating as UNRESOLVED/BUDGET_EXHAUSTED. */
/* Resource cap on the accepted-step estimate for the legacy fixed-step
* Alcubierre domain guard. It bounds a resource allowance only; the adaptive
* coordinate-time budget, not an accepted-step count, defines the trusted
* traced history and is never derived from this cap. */
#define ALCUBIERRE_MAX_TRACE_STEPS (1u << 24)
/* Safety margin over the straight-line worst case: wall-region deflection can
@@ -865,25 +991,33 @@ static double alcubierre_time_step(const Settings *s) {
return fmin(0.1, 0.05 / s->alcubierre_sigma);
}
/* Worst-case per-ray step budget, including ALCUBIERRE_BUDGET_MARGIN. 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 domain
* can take ~4*escape/(1-|v_s|) in coordinate time. trace_config() and the
* startup rejection share this single value so the configured limit always
* carries the full margin when it is accepted. */
static double alcubierre_step_budget(const Settings *s) {
/* Coordinate-time coverage a past-directed Alcubierre ray must be granted.
* 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
* 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
* geometry and the asymptotic separation rate; it is independent of the chosen
* step size and of the accepted-step count. */
static double alcubierre_trace_time_budget(const Settings *s) {
const double escape =
spacetime_alcubierre_escape_radius(s->alcubierre_radius,
s->alcubierre_sigma);
const double separation = 1.0 - fabs(s->alcubierre_vs);
return ALCUBIERRE_BUDGET_MARGIN * 4.0 * escape /
(separation * alcubierre_time_step(s));
return ALCUBIERRE_BUDGET_MARGIN * 4.0 * escape / separation;
}
/* Legacy accepted-step estimate for the same coverage, used only by the
* fixed-step RK4 comparison path and to seed the initial accepted-step resource
* allowance. The adaptive path uses alcubierre_trace_time_budget() directly
* and never derives its history from a step count. */
static double alcubierre_step_budget(const Settings *s) {
return alcubierre_trace_time_budget(s) / alcubierre_time_step(s);
}
#endif
/* Recompute approximate-black provenance and report E/D/U accounting. The
* result is diagnostic for now; production failure gating is layered on top
* of the same counters. */
/* Recompute per-triangle approximate-black provenance and aggregate the E/D/U
* accounting across the given frames. The caller uses the returned totals both
* for the verbose report and for boundary_allows_publish() production gating. */
static void report_boundary_stats(const Settings *s,
FrameLensMesh *const *meshes,
size_t frame_count,
@@ -927,10 +1061,50 @@ static void report_boundary_stats(const Settings *s,
total.approx_black_level_stops);
}
static uint64_t add_cost_u64(uint64_t total, uint64_t value, int *saturated) {
if (UINT64_MAX - total < value) {
*saturated = 1;
return UINT64_MAX;
}
return total + value;
}
/* Sum a frame's persistent-vertex cost into a running total. Every vertex
* (including promoted probe witnesses) is counted once, so this is safe to
* call after refinement and across movie frames without double-counting a
* retried sample. */
static void accumulate_trace_stats(const FrameLensMesh *mesh,
FrameTraceStats *total) {
FrameTraceStats frame;
frame_lens_mesh_trace_stats(mesh, &frame);
int saturated = 0;
total->accepted_steps =
add_cost_u64(total->accepted_steps, frame.accepted_steps, &saturated);
total->rejected_steps =
add_cost_u64(total->rejected_steps, frame.rejected_steps, &saturated);
total->rhs_evaluations =
add_cost_u64(total->rhs_evaluations, frame.rhs_evaluations, &saturated);
total->vertices += frame.vertices;
total->saturated_vertices += frame.saturated_vertices + (saturated ? 1u : 0u);
}
static void report_trace_stats(const Settings *s, const FrameTraceStats *stats,
const char *label) {
if (!s->verbose)
return;
fprintf(stderr,
"%s trace cost: accepted=%" PRIu64 " rejected=%" PRIu64
" rhs=%" PRIu64 " over %zu vertices (saturated=%zu).\n",
label, stats->accepted_steps, stats->rejected_steps,
stats->rhs_evaluations, stats->vertices, stats->saturated_vertices);
}
static LensMapProvenance lens_map_provenance(const Settings *s,
const GeodesicTraceConfig *trace) {
RefinementConfig rc = s->refinement;
frame_retry_config_defaults(&rc, trace);
const RefinementConfig rc = effective_refinement(s, trace);
/* An RK4 map advertises "no adaptive configuration" as explicit zeros; the
* DP54 adaptive policy is only stored when the DP54 stepper actually ran. */
const int dp = trace->stepper == GEODESIC_STEPPER_DP54;
return (LensMapProvenance){.threshold_kind = (uint32_t)trace->threshold.kind,
.threshold_policy_version =
trace->threshold.policy_version,
@@ -938,11 +1112,24 @@ static LensMapProvenance lens_map_provenance(const Settings *s,
.retry_step_increment = rc.retry_step_increment,
.max_total_steps = rc.max_total_steps,
.max_level = rc.max_level,
.integrator = 0,
.integrator = (uint32_t)trace->stepper,
.min_edge_pixels = rc.min_edge_pixels,
.min_area_pixels2 = rc.min_area_pixels2,
.coordinate_time_step = trace->coordinate_time_step,
.initial_max_steps = trace->max_steps};
.initial_max_steps = trace->max_steps,
.atol_x = dp ? trace->atol_x : 0.0,
.atol_Pi = dp ? trace->atol_Pi : 0.0,
.atol_L = dp ? trace->atol_L : 0.0,
.rtol = dp ? trace->rtol : 0.0,
.min_step = dp ? trace->min_step : 0.0,
.max_step = dp ? trace->max_step : 0.0,
.max_lookback_time = dp ? trace->max_lookback_time : 0.0,
.retry_lookback_increment =
dp ? rc.retry_lookback_increment : 0.0,
.max_total_lookback_time =
dp ? rc.max_total_lookback_time : 0.0,
.max_consecutive_rejections =
dp ? trace->consecutive_rejection_limit : 0u};
}
/* Refuse to publish silently on true errors or on unresolved triangles that
@@ -976,30 +1163,207 @@ static GeodesicTraceConfig trace_config(const Settings *s) {
const ThresholdPolicy threshold = {.kind = THRESHOLD_LOG_ENERGY_GROWTH,
.value = s->dark_threshold,
.policy_version = 3};
#ifdef SPACETIME_SCHWARZSCHILD
/* The directed worldtube crossing makes an escaping ray traverse the
* interior as a round trip from the entry sphere (in, turn, back out),
* rather than the old one-way stop at the first outside sample. The step
* budget must cover roughly twice the escape sphere plus margin. */
return (GeodesicTraceConfig){.coordinate_time_step = 0.1,
.max_steps = 65536,
.threshold = threshold};
/* Per-backend integration policy. `default_step` is the initial DP54 trial
* step (and RK4's fixed step on the legacy comparison path), `default_budget`
* is the initial accepted-step resource allowance, `length_unit` scales the
* position tolerance and minimum step, and `default_max_step` bounds the DP54
* trial step. `default_lookback` is the trusted coordinate-time coverage: it
* is an explicit constant or a geometry-derived physical time, never the
* product of the chosen step and the chosen step count. The directed
* worldtube crossing in Schwarzschild makes an escaping ray traverse the
* interior as a round trip, so its resource budget covers roughly twice the
* escape sphere plus margin. */
double default_step;
unsigned int default_budget;
double length_unit;
double default_max_step;
double default_lookback;
#if defined(SPACETIME_SCHWARZSCHILD)
default_step = 0.1;
default_budget = 65536;
length_unit = 1.0;
/* The far-field cap should not force small steps after the adaptive error
* controller has resolved the strong-field segment. */
default_max_step = 8.0;
/* Fixed-step physical coverage: 0.1 * 65536. */
default_lookback = 6553.6;
#elif defined(SPACETIME_ALCUBIERRE)
const double step = alcubierre_time_step(s);
const double budget = alcubierre_step_budget(s);
unsigned max_steps = ALCUBIERRE_MAX_TRACE_STEPS;
if (budget < (double)max_steps && isfinite(budget))
max_steps = (unsigned)ceil(budget);
if (max_steps < 1024u)
max_steps = 1024u;
return (GeodesicTraceConfig){.coordinate_time_step = step,
.max_steps = max_steps,
.threshold = threshold};
default_step = alcubierre_time_step(s);
{
/* Accepted-step resource estimate along the legacy fixed-step policy. It
* may be capped at ALCUBIERRE_MAX_TRACE_STEPS; the coordinate-time coverage
* below is what guarantees the physical budget, so a capped count never
* shortens the trusted history. */
const double estimated_steps = alcubierre_step_budget(s);
unsigned int budget = ALCUBIERRE_MAX_TRACE_STEPS;
if (isfinite(estimated_steps) && estimated_steps < (double)budget)
budget = (unsigned int)ceil(estimated_steps);
if (budget < 1024u)
budget = 1024u;
default_budget = budget;
}
length_unit = s->alcubierre_radius;
default_max_step = default_step * 8.0;
/* Physical coordinate-time coverage from the bubble geometry and the
* asymptotic separation rate, independent of the chosen step or step count. */
default_lookback = alcubierre_trace_time_budget(s);
#else
return (GeodesicTraceConfig){.coordinate_time_step = 1.0,
.max_steps = 2048,
.threshold = threshold};
default_step = 1.0;
default_budget = 2048;
length_unit = 1.0;
default_max_step = 16.0;
/* Fixed-step physical coverage: 1.0 * 2048. */
default_lookback = 2048.0;
#endif
double step = default_step;
unsigned int budget = default_budget;
if (s->ode_initial_step_specified)
step = s->ode_initial_step;
if (s->trace_max_steps_specified)
budget = s->trace_max_steps;
const double default_min_step =
fmax(1e-12 * length_unit, 16.0 * DBL_EPSILON * length_unit);
const double default_atol_x = 1e-9 * length_unit;
return (GeodesicTraceConfig){
.coordinate_time_step = step,
.max_steps = budget,
.threshold = threshold,
.stepper = s->stepper,
.atol_x = s->ode_atol_x_specified ? s->ode_atol_x : default_atol_x,
.atol_Pi = s->ode_atol_pi_specified ? s->ode_atol_pi : 1e-9,
.atol_L = s->ode_atol_l_specified ? s->ode_atol_l : 1e-9,
.rtol = s->ode_rtol_specified ? s->ode_rtol : 1e-9,
.min_step = s->ode_min_step_specified ? s->ode_min_step : default_min_step,
.max_step = s->ode_max_step_specified ? s->ode_max_step : default_max_step,
.consecutive_rejection_limit =
s->ode_max_rejections_specified ? s->ode_max_rejections : 32u,
.max_lookback_time = s->trace_lookback_time_specified
? s->trace_lookback_time
: default_lookback};
}
/* Resolve the retry resource policy. The flag-aware defaults keep an explicit
* zero (e.g. --retry-step-increment 0 to disable step retries) distinct from a
* value left unset, which the existing frame-level helper could not. */
static RefinementConfig effective_refinement(const Settings *s,
const GeodesicTraceConfig *trace) {
RefinementConfig rc = s->refinement;
if (rc.retry_step_increment == 0 && !s->retry_step_increment_specified)
rc.retry_step_increment = trace->max_steps;
if (rc.max_total_steps == 0 && !s->max_total_steps_specified)
rc.max_total_steps = trace->max_steps > (UINT_MAX / 4u)
? trace->max_steps
: trace->max_steps * 4u;
if (trace->stepper == GEODESIC_STEPPER_DP54) {
if (s->retry_lookback_increment_specified) {
rc.retry_lookback_increment = s->retry_lookback_increment;
} else if (rc.retry_lookback_increment == 0.0) {
rc.retry_lookback_increment = trace->max_lookback_time;
}
if (s->max_total_lookback_time_specified) {
rc.max_total_lookback_time = s->max_total_lookback_time;
} else if (rc.max_total_lookback_time == 0.0) {
rc.max_total_lookback_time = trace->max_lookback_time > DBL_MAX / 4.0
? DBL_MAX
: trace->max_lookback_time * 4.0;
}
}
return rc;
}
/* Returns the first explicitly specified option that replay cannot honor; a
* render-only import must consume the stored policy instead of overriding it. */
static const char *replay_tracing_option(const Settings *s) {
if (s->integrator_specified) return "--integrator";
if (s->ode_rtol_specified) return "--ode-rtol";
if (s->ode_atol_x_specified) return "--ode-atol-x";
if (s->ode_atol_pi_specified) return "--ode-atol-pi";
if (s->ode_atol_l_specified) return "--ode-atol-l";
if (s->ode_initial_step_specified) return "--ode-initial-step";
if (s->ode_min_step_specified) return "--ode-min-step";
if (s->ode_max_step_specified) return "--ode-max-step";
if (s->ode_max_rejections_specified) return "--ode-max-rejections";
if (s->trace_max_steps_specified) return "--trace-max-steps";
if (s->trace_lookback_time_specified) return "--trace-lookback-time";
if (s->retry_step_increment_specified) return "--retry-step-increment";
if (s->max_total_steps_specified) return "--max-total-steps";
if (s->retry_lookback_increment_specified) return "--retry-lookback-increment";
if (s->max_total_lookback_time_specified) return "--max-total-lookback-time";
return NULL;
}
/* Cross-option checks that need the resolved per-backend trace policy. */
static int validate_settings(const Settings *s) {
const GeodesicTraceConfig trace = trace_config(s);
if (trace.stepper == GEODESIC_STEPPER_RK4) {
const char *bad = NULL;
if (s->ode_rtol_specified) bad = "--ode-rtol";
else if (s->ode_atol_x_specified) bad = "--ode-atol-x";
else if (s->ode_atol_pi_specified) bad = "--ode-atol-pi";
else if (s->ode_atol_l_specified) bad = "--ode-atol-l";
else if (s->ode_min_step_specified) bad = "--ode-min-step";
else if (s->ode_max_step_specified) bad = "--ode-max-step";
else if (s->ode_max_rejections_specified) bad = "--ode-max-rejections";
else if (s->trace_lookback_time_specified) bad = "--trace-lookback-time";
else if (s->retry_lookback_increment_specified)
bad = "--retry-lookback-increment";
else if (s->max_total_lookback_time_specified)
bad = "--max-total-lookback-time";
if (bad != NULL) {
fprintf(stderr,
"%s applies only to --integrator dp54; the fixed-step RK4 path "
"has no adaptive tolerance or lookback policy.\n",
bad);
return -1;
}
return 0;
}
if (!(trace.min_step <= trace.coordinate_time_step &&
trace.coordinate_time_step <= trace.max_step)) {
fprintf(stderr,
"Adaptive step bounds must satisfy min <= initial <= max "
"(min=%.17g, initial=%.17g, max=%.17g).\n",
trace.min_step, trace.coordinate_time_step, trace.max_step);
return -1;
}
if (s->max_total_steps_specified && s->refinement.max_total_steps != 0 &&
s->refinement.max_total_steps < trace.max_steps) {
fprintf(stderr,
"--max-total-steps (%u) must be >= the initial step budget (%u).\n",
s->refinement.max_total_steps, trace.max_steps);
return -1;
}
if (s->max_total_lookback_time_specified &&
s->max_total_lookback_time < trace.max_lookback_time) {
fprintf(stderr,
"--max-total-lookback-time (%.17g) must be >= the initial lookback "
"budget (%.17g).\n",
s->max_total_lookback_time, trace.max_lookback_time);
return -1;
}
return 0;
}
static const char *stepper_name(GeodesicStepper stepper) {
return stepper == GEODESIC_STEPPER_DP54 ? "dp54" : "rk4";
}
static void report_trace_config(const Settings *s,
const GeodesicTraceConfig *trace,
const char *label) {
if (!s->verbose)
return;
fprintf(stderr,
"%s: integrator=%s step=%.8g max_steps=%u threshold=%.8g "
"atol=(x %.8g, Pi %.8g, L %.8g) rtol=%.8g bounds=[%.8g, %.8g] "
"max_rejections=%u lookback=%.8g\n",
label, stepper_name(trace->stepper), trace->coordinate_time_step,
trace->max_steps, trace->threshold.value, trace->atol_x,
trace->atol_Pi, trace->atol_L, trace->rtol, trace->min_step,
trace->max_step, trace->consecutive_rejection_limit,
trace->max_lookback_time);
}
static int resolve_camera(Settings *s) {
@@ -1098,6 +1462,8 @@ static int render_observer_frame(const Settings *s, StarCatalog *catalog,
const ObserverState *observer,
const char *output_path) {
const GeodesicTraceConfig trace = trace_config(s);
const RefinementConfig refinement_config = effective_refinement(s, &trace);
report_trace_config(s, &trace, "Frame 0");
FrameLensMesh mesh = {0};
double *hdr = calloc((size_t)s->width * s->height * 3, sizeof *hdr);
if (hdr == NULL || frame_lens_mesh_build_coarse(&mesh, s->width, s->height,
@@ -1124,7 +1490,7 @@ static int render_observer_frame(const Settings *s, StarCatalog *catalog,
s->refinement.max_level);
const double refinement_start = omp_get_wtime();
if (frame_lens_mesh_refine_with_progress(
&mesh, spacetime, observer, &trace, &s->refinement,
&mesh, spacetime, observer, &trace, &refinement_config,
s->verbose ? report_frame_refinement : NULL, (void *)s)) {
frame_lens_mesh_destroy(&mesh);
free(hdr);
@@ -1137,11 +1503,13 @@ static int render_observer_frame(const Settings *s, StarCatalog *catalog,
omp_get_wtime() - refinement_start, mesh.vertex_count,
mesh.triangle_count);
FrameBoundaryStats boundary_totals;
FrameLensMesh *frame_meshes[1] = {&mesh};
report_boundary_stats(s, frame_meshes, 1, &refinement_config,
&boundary_totals);
{
RefinementConfig boundary_config = s->refinement;
frame_retry_config_defaults(&boundary_config, &trace);
FrameLensMesh *meshes[1] = {&mesh};
report_boundary_stats(s, meshes, 1, &boundary_config, &boundary_totals);
FrameTraceStats trace_stats;
frame_lens_mesh_trace_stats(&mesh, &trace_stats);
report_trace_stats(s, &trace_stats, "Frame 0");
}
if (!boundary_allows_publish(s, &boundary_totals)) {
frame_lens_mesh_destroy(&mesh);
@@ -1230,8 +1598,7 @@ static int trace_movie_generation(Movie *movie, const Settings *s,
RayPool rays = {0};
size_t ray_count = 0;
size_t total_added = 0;
RefinementConfig effective = s->refinement;
frame_retry_config_defaults(&effective, trace);
RefinementConfig effective = effective_refinement(s, trace);
for (size_t f = 0; f < movie->frame_count; ++f) {
const int prepared = frame_lens_mesh_prepare_generation(
&movie->frames[f].mesh, &effective);
@@ -1252,12 +1619,14 @@ static int trace_movie_generation(Movie *movie, const Settings *s,
if (fs->cached) continue;
int rc;
if (fs->kind == FRAME_SAMPLE_RETRY) {
const LensVertex *v = &fs->vertex;
rc = ray_pool_append_continuation(
&rays, f, sample, v->continuation_t, v->continuation_x,
v->continuation_Pi, v->continuation_log_alpha_p0,
v->continuation_log_alpha_p0_0, v->continuation_steps,
fs->step_limit);
GeodesicRayState state;
if (frame_vertex_continuation_state(&fs->vertex, &state)) {
ray_pool_destroy(&rays);
return -1;
}
rc = ray_pool_append_continuation_state(&rays, f, sample, &state,
fs->step_limit,
fs->lookback_limit);
} else {
rc = ray_pool_append(&rays, &movie->frames[f].observer,
fs->vertex.camera_direction, f, sample);
@@ -1454,6 +1823,7 @@ static int render_movie(const Settings *s, StarCatalog *catalog,
ObserverTrack track = {0};
Movie movie = {0};
const GeodesicTraceConfig trace = trace_config(s);
report_trace_config(s, &trace, "Movie");
MovieOutputQueue output_queue;
int output_queue_ready = 0;
int result = -1;
@@ -1478,8 +1848,7 @@ static int render_movie(const Settings *s, StarCatalog *catalog,
break;
}
{
RefinementConfig boundary_config = s->refinement;
frame_retry_config_defaults(&boundary_config, &trace);
const RefinementConfig boundary_config = effective_refinement(s, &trace);
FrameLensMesh **meshes = malloc(movie.frame_count * sizeof *meshes);
if (meshes == NULL)
goto done;
@@ -1488,6 +1857,10 @@ static int render_movie(const Settings *s, StarCatalog *catalog,
FrameBoundaryStats boundary_totals;
report_boundary_stats(s, meshes, movie.frame_count, &boundary_config,
&boundary_totals);
FrameTraceStats trace_totals = {0};
for (size_t i = 0; i < movie.frame_count; ++i)
accumulate_trace_stats(&movie.frames[i].mesh, &trace_totals);
report_trace_stats(s, &trace_totals, "Movie");
free(meshes);
if (!boundary_allows_publish(s, &boundary_totals))
goto done;
@@ -1623,17 +1996,40 @@ done:
}
static int render_lens_map(const Settings *s, StarCatalog *catalog) {
const char *replay_conflict = replay_tracing_option(s);
if (replay_conflict != NULL) {
fprintf(stderr,
"%s cannot be combined with --lens-map-input; a render-only replay "
"consumes the integration and retry policy stored in the map.\n",
replay_conflict);
return -1;
}
LensMap map = {0};
if (lens_map_read(s->lens_map_input_path, NULL, &map)) {
fprintf(stderr, "Failed to read or validate lens map: %s\n", s->lens_map_input_path);
return -1;
}
if (s->verbose)
fprintf(stderr,
"Imported lens map v%u: integrator=%s step=%.8g max_steps=%u "
"lookback=%.8g retry_lookback=%.8g max_total_lookback=%.8g\n",
map.file_version,
stepper_name((GeodesicStepper)map.provenance.integrator),
map.provenance.coordinate_time_step, map.provenance.initial_max_steps,
map.provenance.max_lookback_time,
map.provenance.retry_lookback_increment,
map.provenance.max_total_lookback_time);
/* Render-only replay never retries, but the full source policy (including
* the coordinate-time retry quotas) is restored rather than replaced by the
* process's CLI defaults. */
RefinementConfig replay_config = {
.max_level = map.provenance.max_level,
.min_edge_pixels = map.provenance.min_edge_pixels,
.min_area_pixels2 = map.provenance.min_area_pixels2,
.retry_step_increment = map.provenance.retry_step_increment,
.max_total_steps = map.provenance.max_total_steps};
.max_total_steps = map.provenance.max_total_steps,
.retry_lookback_increment = map.provenance.retry_lookback_increment,
.max_total_lookback_time = map.provenance.max_total_lookback_time};
int replay_incomplete = 0;
/* Replay consumes stored decisions, not current CLI refinement defaults. */
for (size_t f = 0; f < map.frame_count; ++f) {
@@ -1644,6 +2040,12 @@ static int render_lens_map(const Settings *s, StarCatalog *catalog) {
lens_map_destroy(&map); return -1;
}
}
{
FrameTraceStats trace_totals = {0};
for (size_t f = 0; f < map.frame_count; ++f)
accumulate_trace_stats(&map.frames[f].mesh, &trace_totals);
report_trace_stats(s, &trace_totals, "Imported map");
}
if ((s->width_specified && s->width != map.width) ||
(s->height_specified && s->height != map.height) ||
(s->fov_specified && fabs(s->horizontal_fov_deg - map.horizontal_fov_deg) > 1e-12)) {
@@ -1878,6 +2280,12 @@ int main(int argc, char **argv) {
"--refine-jacobian-min J "
"--refine-min-edge-pixels P --refine-min-area-pixels2 A] "
"[--draw-mesh] [--write-catalog PATH] "
"[--integrator rk4|dp54 --ode-rtol R --ode-atol-x A "
"--ode-atol-pi A --ode-atol-l A --ode-initial-step H "
"--ode-min-step H --ode-max-step H --ode-max-rejections N "
"--trace-max-steps N --trace-lookback-time T "
"--retry-step-increment N --max-total-steps N "
"--retry-lookback-increment T --max-total-lookback-time T] "
"[--catalog-load-workers N] "
"[--observer-track PATH --frames-dir DIR --frames-prefix NAME "
"--start-time T --duration T --fps N | --movie-track-samples] "
@@ -1989,9 +2397,16 @@ int main(int argc, char **argv) {
"R > 0, sigma > 0.\n", stderr);
return 1;
}
if (alcubierre_step_budget(&settings) >
(double)ALCUBIERRE_MAX_TRACE_STEPS) {
/* The budget has a V-shaped minimum at sigma = 0.5, where the step
/* The fixed-step RK4 comparison path sizes its history from
* step * accepted-step count, so its domain guard still rejects an
* estimate that exceeds the cap. The adaptive path uses an independent
* coordinate-time budget and must not be rejected here: trustworthy quota
* exhaustion is reported as UNRESOLVED and handled by the publication
* gate, and explicit lookback/step overrides remain independent. */
if (settings.stepper == GEODESIC_STEPPER_RK4 &&
alcubierre_step_budget(&settings) >
(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. */
@@ -2001,9 +2416,9 @@ int main(int argc, char **argv) {
else if (settings.alcubierre_sigma < 0.5)
sigma_advice = "increase --alcubierre-sigma, ";
fprintf(stderr,
"Alcubierre trace budget exceeds the %u-step cap; decrease "
"--alcubierre-radius, %sor move --alcubierre-vs away from "
"+/-1.\n",
"Fixed-step RK4 Alcubierre trace budget exceeds the %u-step "
"cap; decrease --alcubierre-radius, %sor move --alcubierre-vs "
"away from +/-1, or use the adaptive default.\n",
ALCUBIERRE_MAX_TRACE_STEPS, sigma_advice);
spacetime_destroy(&spacetime);
return 2;
+81 -19
View File
@@ -16,7 +16,10 @@ int ray_pool_init(RayPool *p, size_t capacity) {
RAY_ALLOC(direction0) && RAY_ALLOC(direction1) && RAY_ALLOC(direction2) &&
RAY_ALLOC(log_alpha_p0) && RAY_ALLOC(log_alpha_p0_0) &&
RAY_ALLOC(activate_t) && RAY_ALLOC(steps) &&
RAY_ALLOC(step_limit) && RAY_ALLOC(continuation) &&
RAY_ALLOC(step_limit) && RAY_ALLOC(integration_start_time) &&
RAY_ALLOC(next_step) && RAY_ALLOC(rejected_steps) &&
RAY_ALLOC(rhs_evaluations) && RAY_ALLOC(previous_rejected) &&
RAY_ALLOC(lookback_limit) && RAY_ALLOC(continuation) &&
RAY_ALLOC(frame_id) && RAY_ALLOC(vertex_id) && RAY_ALLOC(status) &&
RAY_ALLOC(endpoint))) {
ray_pool_destroy(p);
@@ -55,30 +58,45 @@ int ray_pool_append(RayPool *p, const ObserverState *observer,
.final_log_alpha_p0_0 = NAN,
.threshold_value = NAN};
p->step_limit[i] = 0;
/* Adaptive control state starts unset; ray_pool_preroute() seeds the window
* start from the resolved activation time. Initializing here as well keeps
* a directly advanced pool slot well-defined. */
p->integration_start_time[i] = observer->coordinate_time;
p->next_step[i] = 0.0;
p->rejected_steps[i] = 0;
p->rhs_evaluations[i] = 0;
p->previous_rejected[i] = 0;
p->lookback_limit[i] = 0.0;
p->continuation[i] = 0;
p->log_alpha_p0_0[i] = 0.0;
++p->count;
return 0;
}
int ray_pool_append_continuation(RayPool *p, size_t frame_id,
size_t vertex_id, double t, const double x[3],
const double Pi[3], double log_alpha_p0,
double log_alpha_p0_0, unsigned int steps,
unsigned int limit) {
if (p == NULL || p->count == p->capacity || x == NULL || Pi == NULL)
int ray_pool_append_continuation_state(RayPool *p, size_t frame_id,
size_t vertex_id,
const GeodesicRayState *state,
unsigned int accepted_limit,
double lookback_limit) {
if (p == NULL || p->count == p->capacity || state == NULL)
return -1;
const size_t i = p->count;
p->t[i] = t;
p->activate_t[i] = t;
p->t[i] = state->coordinate_time;
p->activate_t[i] = state->coordinate_time;
p->observer[i] = NULL;
p->direction0[i] = p->direction1[i] = p->direction2[i] = 0.0;
p->x0[i] = x[0]; p->x1[i] = x[1]; p->x2[i] = x[2];
p->p0[i] = Pi[0]; p->p1[i] = Pi[1]; p->p2[i] = Pi[2];
p->log_alpha_p0[i] = log_alpha_p0;
p->log_alpha_p0_0[i] = log_alpha_p0_0;
p->steps[i] = steps;
p->step_limit[i] = limit;
p->x0[i] = state->x[0]; p->x1[i] = state->x[1]; p->x2[i] = state->x[2];
p->p0[i] = state->Pi[0]; p->p1[i] = state->Pi[1]; p->p2[i] = state->Pi[2];
p->log_alpha_p0[i] = state->log_alpha_p0;
p->log_alpha_p0_0[i] = state->log_alpha_p0_0;
p->steps[i] = state->steps;
p->step_limit[i] = accepted_limit;
p->integration_start_time[i] = state->integration_start_time;
p->next_step[i] = state->next_step;
p->rejected_steps[i] = state->rejected_steps;
p->rhs_evaluations[i] = state->rhs_evaluations;
p->previous_rejected[i] = state->previous_rejected;
p->lookback_limit[i] = lookback_limit;
p->continuation[i] = 1;
p->frame_id[i] = frame_id;
p->vertex_id[i] = vertex_id;
@@ -88,16 +106,34 @@ int ray_pool_append_continuation(RayPool *p, size_t frame_id,
.outcome = RAY_OUTCOME_INCOMPLETE,
.reason = RAY_REASON_NONE,
.stop_coordinate_time = NAN,
.accepted_steps = steps,
.accepted_steps = state->steps,
.accepted_step_limit = accepted_limit,
.final_x = {NAN, NAN, NAN},
.final_Pi = {NAN, NAN, NAN},
.final_log_alpha_p0 = NAN,
.final_log_alpha_p0_0 = log_alpha_p0_0,
.final_log_alpha_p0_0 = state->log_alpha_p0_0,
.threshold_value = NAN};
++p->count;
return 0;
}
int ray_pool_append_continuation(RayPool *p, size_t frame_id,
size_t vertex_id, double t, const double x[3],
const double Pi[3], double log_alpha_p0,
double log_alpha_p0_0, unsigned int steps,
unsigned int limit) {
if (x == NULL || Pi == NULL)
return -1;
const GeodesicRayState state = {.coordinate_time = t,
.x = {x[0], x[1], x[2]},
.Pi = {Pi[0], Pi[1], Pi[2]},
.log_alpha_p0 = log_alpha_p0,
.log_alpha_p0_0 = log_alpha_p0_0,
.steps = steps};
return ray_pool_append_continuation_state(p, frame_id, vertex_id, &state,
limit, 0.0);
}
void ray_pool_preroute(RayPool *p, const SpacetimeSource *source) {
if (p == NULL || source == NULL)
return;
@@ -151,6 +187,14 @@ void ray_pool_preroute(RayPool *p, const SpacetimeSource *source) {
continue;
}
p->activate_t[i] = route.activate_t;
/* The adaptive window starts at the resolved activation/entry state; the
* first trial step and all cost counters are freshly initialized. An
* integration start time of zero is a legal coordinate time. */
p->integration_start_time[i] = route.activate_t;
p->next_step[i] = 0.0;
p->rejected_steps[i] = 0;
p->rhs_evaluations[i] = 0;
p->previous_rejected[i] = 0;
p->x0[i] = route.x[0];
p->x1[i] = route.x[1];
p->x2[i] = route.x[2];
@@ -169,7 +213,10 @@ void ray_pool_activate_in_time_range(RayPool *p, const MetricSlab *slab) {
p->activate_t[i] <= slab->t_lo)
continue;
p->t[i] = p->activate_t[i];
/* Continuation rays keep the accepted-step count they already consumed. */
/* Continuation rays keep the accepted-step count they already consumed.
* Activation never resets adaptive control state; for a new ray it was
* already seeded by preroute, for a continuation it comes from the
* installed resume state. */
if (!p->continuation[i])
p->steps[i] = 0;
p->status[i] = RAY_POOL_ACTIVE;
@@ -191,10 +238,17 @@ void ray_pool_advance_active(RayPool *p, const MetricSlab *slab,
.Pi = {p->p0[i], p->p1[i], p->p2[i]},
.log_alpha_p0 = p->log_alpha_p0[i],
.log_alpha_p0_0 = p->log_alpha_p0_0[i],
.steps = p->steps[i]};
.steps = p->steps[i],
.integration_start_time = p->integration_start_time[i],
.next_step = p->next_step[i],
.rejected_steps = p->rejected_steps[i],
.rhs_evaluations = p->rhs_evaluations[i],
.previous_rejected = p->previous_rejected[i]};
GeodesicTraceConfig per_ray = *config;
if (p->step_limit[i] != 0)
per_ray.max_steps = p->step_limit[i];
if (p->lookback_limit[i] != 0.0)
per_ray.max_lookback_time = p->lookback_limit[i];
const GeodesicAdvanceResult result =
geodesic_advance_past_ray(slab, &s, slab->t_lo, &per_ray, &p->endpoint[i]);
p->t[i] = s.coordinate_time;
@@ -202,6 +256,11 @@ void ray_pool_advance_active(RayPool *p, const MetricSlab *slab,
p->p0[i] = s.Pi[0]; p->p1[i] = s.Pi[1]; p->p2[i] = s.Pi[2];
p->log_alpha_p0[i] = s.log_alpha_p0;
p->steps[i] = s.steps;
p->integration_start_time[i] = s.integration_start_time;
p->next_step[i] = s.next_step;
p->rejected_steps[i] = s.rejected_steps;
p->rhs_evaluations[i] = s.rhs_evaluations;
p->previous_rejected[i] = s.previous_rejected;
if (result == GEODESIC_ADVANCE_TERMINATED)
p->status[i] = p->endpoint[i].outcome == RAY_OUTCOME_UNRESOLVED
? RAY_POOL_UNRESOLVED
@@ -228,6 +287,9 @@ void ray_pool_destroy(RayPool *p) {
free(p->p0); free(p->p1); free(p->p2); free(p->log_alpha_p0);
free(p->log_alpha_p0_0);
free(p->activate_t); free(p->steps); free(p->step_limit);
free(p->integration_start_time); free(p->next_step);
free(p->rejected_steps); free(p->rhs_evaluations);
free(p->previous_rejected); free(p->lookback_limit);
free(p->continuation); free(p->frame_id); free(p->vertex_id);
free(p->status);
free(p->endpoint);
+27 -1
View File
@@ -25,6 +25,20 @@ typedef struct {
unsigned int *steps;
/* Per-ray total accepted-step limit; zero means use the trace config. */
unsigned int *step_limit;
/* Adaptive (DP54) per-ray control state, copied verbatim from the resume
* state and never reset by activation. All fields are ignored by the legacy
* RK4 path. `integration_start_time` is the activation/entry time of the
* adaptive window (not the camera energy reference), `next_step` is a
* positive trial-step magnitude (zero means "derive from the config"),
* `rejected_steps`/`rhs_evaluations` are cumulative cost and
* `previous_rejected` flags a rejected last trial. */
double *integration_start_time;
double *next_step;
unsigned int *rejected_steps;
unsigned long *rhs_evaluations;
unsigned int *previous_rejected;
/* Per-ray total lookback time budget; zero means use the trace config. */
double *lookback_limit;
/* Nonzero for a retry that resumes from a saved state instead of from the
* camera; such rays are not pre-routed. */
uint8_t *continuation;
@@ -39,7 +53,19 @@ int ray_pool_append(RayPool *pool, const ObserverState *observer,
const double direction[3],
size_t frame_id, size_t vertex_id);
/* Append a retry that resumes an UNRESOLVED ray from its last accepted state.
* `limit` is the new total accepted-step budget for this ray. */
* Every continuous and adaptive control field of `state` is copied so the
* resumed ray never replays from the camera, resets L0, or forgets its
* accumulated rejection/RHS counters. `accepted_limit` is the new total
* accepted-step budget; `lookback_limit` is the new total coordinate-time
* lookback budget (zero means use the trace config). Neither quota is part of
* the continuous state. */
int ray_pool_append_continuation_state(RayPool *pool, size_t frame_id,
size_t vertex_id,
const GeodesicRayState *state,
unsigned int accepted_limit,
double lookback_limit);
/* Legacy scattered-field wrapper around the state interface above. It builds
* an RK4-compatible state (zero adaptive control) and a zero lookback quota. */
int ray_pool_append_continuation(RayPool *pool, size_t frame_id,
size_t vertex_id, double t, const double x[3],
const double Pi[3], double log_alpha_p0,
+545
View File
@@ -0,0 +1,545 @@
#!/usr/bin/env python3
"""Exercise the production DP54 default, v3 lens-map provenance/replay and the
adaptive retry/budget policy from the CLI.
The production default is now adaptive Dormand-Prince 5(4). These checks
require that a run without --integrator exports wire code 1 and is physically
and bit-for-bit equal to an explicit --integrator dp54 run, so `make test`
truly covers the production default rather than only the explicit path.
--integrator rk4 stays available for the legacy wire code and for a
fixed-step reference convergence check. Small CPU 16x8/32x16 scenes keep the
runtime short; physical comparisons use stored lens-map endpoints, not only the
rendered PNG.
"""
import os
import re
import struct
import subprocess
import sys
import tempfile
import zlib
from pathlib import Path
# Keep scratch data inside the pre-approved OpenCode scratch directory instead
# of creating directories directly under the system temporary root.
TMP_ROOT = Path('/tmp/opencode')
TMP_ROOT.mkdir(parents=True, exist_ok=True)
BUILD = Path(sys.argv[1] if len(sys.argv) > 1 else 'build/Release').resolve()
TESTDIR = Path(sys.argv[2]).resolve() if len(sys.argv) > 2 else BUILD
ENV = dict(os.environ, OMP_NUM_THREADS='4')
# v3 wire offsets (see src/lens_map.c). The header is deliberately not part of
# the payload CRC.
VERSION_OFFSET = 8
FRAME_COUNT_OFFSET = 32
PROVENANCE_OFFSET = 40
PROVENANCE_V3_OFFSET = 100
VERTEX_COUNT_OFFSET = 200
TRIANGLE_COUNT_OFFSET = 208
VERTEX_START = 224
VERTEX_SIZE = 108
TRIANGLE_SIZE = 32
ATOL_FIELDS = ('atol_x', 'atol_Pi', 'atol_L', 'rtol', 'min_step', 'max_step',
'max_lookback_time', 'retry_lookback_increment',
'max_total_lookback_time')
# Machine-roundoff floor for comparing two adaptive integrations; below this a
# difference carries no convergence information.
ROUNDOFF_FLOOR = 1e-12
ENDPOINT_ASSERT = 1e-6
def run(binary, *args, ok=True, env=ENV):
result = subprocess.run([str(binary), *map(str, args)], env=env,
capture_output=True, text=True)
if (result.returncode == 0) != ok:
raise AssertionError(
f'{binary.name} {args}: rc={result.returncode}\n{result.stderr}')
return result
def image_payload(path):
data = path.read_bytes()
assert data[:8] == b'\x89PNG\r\n\x1a\n', f'not a PNG: {path}'
offset, compressed = 8, bytearray()
while offset < len(data):
count, kind = struct.unpack_from('>I4s', data, offset)
payload = data[offset + 8:offset + 8 + count]
if kind == b'IDAT':
compressed.extend(payload)
offset += count + 12
return bytes(zlib.decompress(compressed))
def map_provenance(path):
data = path.read_bytes()
assert data[:8] == b'GRLENS\x01\x00'
version = struct.unpack_from('<I', data, VERSION_OFFSET)[0]
frame_count = struct.unpack_from('<Q', data, FRAME_COUNT_OFFSET)[0]
base = PROVENANCE_OFFSET
prov = {}
prov['threshold_kind'], prov['threshold_policy_version'] = struct.unpack_from(
'<II', data, base)
prov['threshold_value'] = struct.unpack_from('<d', data, base + 8)[0]
(prov['retry_step_increment'], prov['max_total_steps'], prov['max_level'],
prov['integrator']) = struct.unpack_from('<IIII', data, base + 16)
prov['min_edge_pixels'] = struct.unpack_from('<d', data, base + 32)[0]
prov['min_area_pixels2'] = struct.unpack_from('<d', data, base + 40)[0]
prov['coordinate_time_step'] = struct.unpack_from('<d', data, base + 48)[0]
prov['initial_max_steps'] = struct.unpack_from('<I', data, base + 56)[0]
if version == 3:
off = PROVENANCE_V3_OFFSET
for name in ATOL_FIELDS:
prov[name] = struct.unpack_from('<d', data, off)[0]
off += 8
prov['max_consecutive_rejections'] = struct.unpack_from('<I', data, off)[0]
return version, frame_count, prov
def map_vertices(path):
data = path.read_bytes()
version = struct.unpack_from('<I', data, VERSION_OFFSET)[0]
assert version == 3, f'expected a v3 map, got v{version}'
vertices = struct.unpack_from('<Q', data, VERTEX_COUNT_OFFSET)[0]
triangles = struct.unpack_from('<Q', data, TRIANGLE_COUNT_OFFSET)[0]
offset = VERTEX_START
values = []
for _ in range(vertices):
values.append(struct.unpack_from('<9dIIIQQQ', data, offset))
offset += VERTEX_SIZE
return values, data[offset:offset + triangles * TRIANGLE_SIZE]
def endpoint_deviation(a, b):
"""(mismatched provenance count, max direction/log-g deviation).
Vertices whose end_id/outcome/reason differ are counted as mismatches and
excluded from the numeric deviation; the caller requires zero mismatches.
"""
mismatches = 0
worst = 0.0
assert len(a) == len(b)
for x, y in zip(a, b):
if x[9:12] != y[9:12]:
mismatches += 1
continue
for k in (3, 4, 5, 6, 7, 8):
worst = max(worst, abs(x[k] - y[k]))
return mismatches, worst
def trace_cost(stderr, label):
match = re.search(label + r' trace cost: accepted=(\d+) rejected=(\d+) '
r'rhs=(\d+)', stderr)
return None if match is None else match.groups()
def sum_rhs(vertices):
return sum(v[14] for v in vertices)
with tempfile.TemporaryDirectory(prefix='gr-adaptive-cli-',
dir=str(TMP_ROOT)) as directory:
tmp = Path(directory)
for backend in ('minkowski', 'schwarzschild'):
binary = BUILD / f'{backend}_sky'
if not binary.exists():
print(f'{backend}: binary absent, skipping', flush=True)
continue
help_text = run(binary, '--help').stdout
for option in ('--integrator', '--ode-rtol', '--ode-atol-x',
'--ode-atol-pi', '--ode-atol-l', '--ode-initial-step',
'--ode-min-step', '--ode-max-step',
'--ode-max-rejections', '--trace-max-steps',
'--trace-lookback-time', '--retry-step-increment',
'--max-total-steps', '--retry-lookback-increment',
'--max-total-lookback-time'):
assert option in help_text, (backend, option)
ext = 'png' if '.png' in help_text else 'ppm'
hdr_available = '--hdr-output' in help_text
hdr_args = ['--hdr-output'] if hdr_available else []
common = ['--catalog', 'assets/sky_grid_5deg.csv', '--width', 32,
'--height', 16, '--fov-deg', 80, '--exposure', 1e-3,
'--coarse-cell-pixels', 8, '--refine-max-level', 0,
'--psf-relative-tail', 1e-4]
def single(name, *options, ok=True, env=ENV, use_common=common):
out = tmp / f'{backend}_{name}.{ext}'
result = run(binary, *use_common, '--output', out, *options,
ok=ok, env=env)
return out, result
# 1) The production default (no --integrator) must be DP54 and must
# match an explicit --integrator dp54 run physically and in its
# PNG/HDR output.
dflt_map = tmp / f'{backend}_dflt.grlens'
dflt_out, dflt_run = single('dflt', *hdr_args, '--verbose',
'--lens-map-output', dflt_map)
version, frame_count, dflt_prov = map_provenance(dflt_map)
assert version == 3 and frame_count == 1
assert dflt_prov['integrator'] == 1, dflt_prov
assert dflt_prov['min_step'] == 1e-12, dflt_prov
assert dflt_prov['max_step'] == {'minkowski': 16.0,
'schwarzschild': 8.0}[backend], dflt_prov
assert dflt_prov['min_step'] <= dflt_prov['coordinate_time_step'] \
<= dflt_prov['max_step']
assert dflt_prov['atol_x'] > 0 and dflt_prov['rtol'] > 0
assert dflt_prov['max_lookback_time'] > 0
assert dflt_prov['max_consecutive_rejections'] > 0
expl_map = tmp / f'{backend}_expl.grlens'
expl_out, _ = single('expl', *hdr_args, '--integrator', 'dp54',
'--lens-map-output', expl_map)
assert dflt_map.read_bytes() == expl_map.read_bytes(), \
'default map differs from explicit dp54'
assert image_payload(dflt_out) == image_payload(expl_out)
if hdr_available:
dflt_hdr = dflt_out.with_name(dflt_out.stem + '_HDR.fits')
expl_hdr = expl_out.with_name(expl_out.stem + '_HDR.fits')
assert dflt_hdr.read_bytes() == expl_hdr.read_bytes()
dflt_vertices, _ = map_vertices(dflt_map)
# 2) The legacy RK4 wire code must be 0 and its cost counters must be
# real (nonzero RHS evaluations), not legacy zeros.
rk4_map = tmp / f'{backend}_rk4.grlens'
single('rk4', '--integrator', 'rk4', '--lens-map-output', rk4_map)
_, _, rk4_prov = map_provenance(rk4_map)
assert rk4_prov['integrator'] == 0, rk4_prov
rk4_vertices, _ = map_vertices(rk4_map)
assert sum_rhs(rk4_vertices) > 0, 'RK4 RHS cost counters are not real'
# 3) Same-camera tolerance convergence with three levels. Outcome,
# reason and end must not change between levels, and the deviation
# from the tightest reference must shrink as the tolerance tightens.
# These are local ODE tolerances, not a global sky-error bound.
tol_maps = {}
for tol in ('1e-7', '1e-9', '1e-12'):
m = tmp / f'{backend}_tol_{tol}.grlens'
single(f'tol_{tol}', '--integrator', 'dp54', '--ode-rtol', tol,
'--ode-atol-x', tol, '--ode-atol-pi', tol,
'--ode-atol-l', tol, '--lens-map-output', m)
tol_maps[tol] = map_vertices(m)[0]
mism7, err7 = endpoint_deviation(tol_maps['1e-7'], tol_maps['1e-12'])
mism9, err9 = endpoint_deviation(tol_maps['1e-9'], tol_maps['1e-12'])
assert mism7 == 0 and mism9 == 0, \
f'{backend}: tolerance levels disagree on outcome/end'
assert err9 < ENDPOINT_ASSERT, (backend, 'default vs tight', err9)
assert err7 + ROUNDOFF_FLOOR >= err9, \
f'{backend}: tightening tolerance did not reduce error ' \
f'({err7} -> {err9})'
print(f'{backend}: default==dp54, tol errors 1e-7={err7:.3g} '
f'1e-9={err9:.3g}', flush=True)
# 4) Render-only replay of the default map must be bit-identical and
# its stored statistics must equal the live trace cost.
replay_out = tmp / f'{backend}_replay.{ext}'
replay_run = run(binary, *common, *hdr_args, '--lens-map-input',
dflt_map, '--verbose', '--output', replay_out)
assert image_payload(replay_out) == image_payload(dflt_out)
if hdr_available:
replay_hdr = replay_out.with_name(replay_out.stem + '_HDR.fits')
assert dflt_out.with_name(dflt_out.stem + '_HDR.fits').read_bytes() \
== replay_hdr.read_bytes()
live_cost = trace_cost(dflt_run.stderr, 'Frame 0')
replay_cost = trace_cost(replay_run.stderr, 'Imported map')
assert live_cost is not None and replay_cost is not None
assert live_cost == replay_cost, (live_cost, replay_cost)
# 5) Explicit zeros in the retry policy must survive, not be filled in
# by the derived defaults.
zero_step_map = tmp / f'{backend}_zero_step.grlens'
single('zero_step', '--integrator', 'dp54', '--retry-step-increment',
'0', '--lens-map-output', zero_step_map)
_, _, zero_step = map_provenance(zero_step_map)
assert zero_step['retry_step_increment'] == 0, zero_step
assert zero_step['max_total_steps'] > 0, zero_step
zero_time_map = tmp / f'{backend}_zero_time.grlens'
single('zero_time', '--integrator', 'dp54',
'--retry-lookback-increment', '0', '--lens-map-output',
zero_time_map)
_, _, zero_time = map_provenance(zero_time_map)
assert zero_time['retry_lookback_increment'] == 0, zero_time
assert zero_time['max_total_lookback_time'] >= \
zero_time['max_lookback_time'], zero_time
# 6) RK4 rejects every DP-only option rather than silently ignoring it.
rk4_errors = [
(['--integrator', 'rk4', '--ode-rtol', 1e-9], 'applies only'),
(['--integrator', 'rk4', '--ode-min-step', 1e-9], 'applies only'),
(['--integrator', 'rk4', '--ode-max-step', 1e-3], 'applies only'),
(['--integrator', 'rk4', '--ode-max-rejections', 4], 'applies only'),
(['--integrator', 'rk4', '--trace-lookback-time', 1], 'applies only'),
(['--integrator', 'rk4', '--retry-lookback-increment', 1], 'applies only'),
(['--integrator', 'rk4', '--max-total-lookback-time', 2], 'applies only'),
]
# DP cross-field validation and malformed values fail immediately.
invalid = [
(['--integrator', 'bogus'], None),
(['--integrator'], None),
(['--integrator', 'dp54', '--ode-rtol', 0], None),
(['--integrator', 'dp54', '--ode-rtol', -1], None),
(['--integrator', 'dp54', '--ode-atol-x', 'nan'], None),
(['--integrator', 'dp54', '--ode-max-rejections', 0], None),
(['--integrator', 'dp54', '--trace-max-steps', 0], None),
(['--integrator', 'dp54', '--ode-initial-step', 5,
'--ode-max-step', 1], 'min <= initial <= max'),
(['--integrator', 'dp54', '--ode-min-step', 4,
'--ode-initial-step', 2], 'min <= initial <= max'),
(['--integrator', 'dp54', '--trace-lookback-time', 0], None),
(['--integrator', 'dp54', '--retry-step-increment', -1], None),
]
for options, message in rk4_errors + invalid:
missing = tmp / 'adaptive_should_not_exist.csv'
result = run(binary, '--catalog', missing, *options, ok=False)
if message:
assert message in result.stderr, (options, result.stderr)
assert not missing.exists(), result.stderr
# 7) Replay must consume the stored policy: explicit tracing options
# cannot be layered on top of --lens-map-input.
conflict = run(binary, *common, '--lens-map-input', dflt_map,
'--integrator', 'rk4', '--output',
tmp / 'conflict.png', ok=False)
assert 'cannot be combined with --lens-map-input' in conflict.stderr, \
conflict.stderr
conflict2 = run(binary, *common, '--lens-map-input', dflt_map,
'--trace-max-steps', 100, '--output',
tmp / 'conflict2.png', ok=False)
assert 'cannot be combined with --lens-map-input' in conflict2.stderr, \
conflict2.stderr
conflict3 = run(binary, *common, '--lens-map-input', dflt_map,
'--integrator', 'dp54', '--ode-rtol', 1e-9,
'--output', tmp / 'conflict3.png', ok=False)
assert 'cannot be combined with --lens-map-input' in conflict3.stderr, \
conflict3.stderr
# 8) Single vs movie: the same physical observer event must agree, and
# threads/slabs must not change the stored adaptive endpoints.
track = tmp / f'{backend}.csv'
observer_test = TESTDIR / f'test_observer_{backend}'
if observer_test.exists():
run(observer_test, track)
moving_single = tmp / f'{backend}_moving.grlens'
single('moving', '--observer-position', 3, -4, 5,
'--observer-velocity', 0.2, -0.1, 0.3,
'--look-ra-deg', 37, '--look-dec-deg', -23,
'--camera-roll-deg', 19, '--lens-map-output', moving_single)
movie_map = tmp / f'{backend}_movie.grlens'
run(binary, *common, '--observer-track', track, '--frames-dir', tmp,
'--frames-prefix', f'{backend}_movie', '--duration', 0,
'--fps', 1, '--lens-map-output', movie_map)
single_v, _ = map_vertices(moving_single)
movie_v, _ = map_vertices(movie_map)
mism, dev = endpoint_deviation(single_v, movie_v)
assert mism == 0 and dev < ENDPOINT_ASSERT, (mism, dev)
thread_maps = []
for threads in (1, 2, 4):
m = tmp / f'{backend}_movie_{threads}thr.grlens'
run(binary, *common, '--observer-track', track, '--frames-dir',
tmp, '--frames-prefix', f'{backend}_m{threads}',
'--duration', 0, '--fps', 1, '--lens-map-output', m,
env=dict(ENV, OMP_NUM_THREADS=str(threads)))
thread_maps.append(m)
reference = thread_maps[0].read_bytes()
for m in thread_maps[1:]:
assert m.read_bytes() == reference, \
f'{backend}: DP movie map changed across threads'
slab_maps = []
for slab in (2, 8, 64):
m = tmp / f'{backend}_slab_{slab}.grlens'
run(binary, *common, '--observer-track', track, '--frames-dir',
tmp, '--frames-prefix', f'{backend}_s{slab}',
'--slab-duration', slab, '--duration', 0, '--fps', 1,
'--lens-map-output', m)
slab_maps.append(m)
base_v, _ = map_vertices(slab_maps[0])
for m in slab_maps[1:]:
other_v, _ = map_vertices(m)
mism, dev = endpoint_deviation(base_v, other_v)
assert mism == 0 and dev < ENDPOINT_ASSERT, (mism, dev)
print(f'{backend}: DP movie threads/slabs agree', flush=True)
# 9) Budget: a tiny initial coordinate-time budget leaves UNRESOLVED
# rays; with no room to grow the publication gate refuses the frame,
# while retry increments that can grow resolve it.
if backend == 'schwarzschild':
refused = tmp / f'{backend}_refused.{ext}'
small = ['--integrator', 'dp54', '--trace-lookback-time', 1e-6,
'--retry-lookback-increment', 0,
'--max-total-lookback-time', 1e-6]
result = run(binary, *common, '--output', refused, *small, ok=False)
assert 'Incomplete render refused' in result.stderr, result.stderr
assert not refused.exists()
allow = tmp / f'{backend}_allow.{ext}'
run(binary, *common, '--output', allow, '--allow-incomplete', *small)
assert allow.exists()
direct = tmp / f'{backend}_direct.{ext}'
direct_result = subprocess.run(
[str(binary), *map(str, common), '--output', str(direct),
'--integrator', 'dp54', '--trace-lookback-time', '4000'],
env=ENV, capture_output=True, text=True)
if direct_result.returncode == 0:
retried = tmp / f'{backend}_retried.{ext}'
run(binary, *common, '--output', retried, '--integrator', 'dp54',
'--trace-lookback-time', 1e-6,
'--retry-lookback-increment', 25,
'--max-total-lookback-time', 4000)
assert image_payload(retried)
print('schwarzschild: retry budget resolves previously '
'unresolved rays', flush=True)
else:
print('schwarzschild: direct budget scene still unresolved; '
'retry-resolution case skipped', flush=True)
# 10) Fixed-step reference convergence on a small scene. The
# accepted budget is explicit and large so the finer step does
# not silently shorten the traced history. Both fixed-step
# levels and the DP54 default must agree below ENDPOINT_ASSERT.
# If the reference itself does not converge this must FAIL and
# be reported, never loosened into a false zero.
ref_common = ['--catalog', 'assets/sky_grid_5deg.csv', '--width',
16, '--height', 8, '--fov-deg', 80, '--exposure',
1e-3, '--coarse-cell-pixels', 8, '--refine-max-level',
0, '--psf-relative-tail', 1e-4]
def ref_map(tag, *options):
m = tmp / f'{backend}_ref_{tag}.grlens'
run(binary, *ref_common, '--output',
tmp / f'{backend}_ref_{tag}.{ext}', '--lens-map-output', m,
*options)
return map_vertices(m)[0]
rk4_04 = ref_map('rk4_04', '--integrator', 'rk4',
'--ode-initial-step', 0.04, '--trace-max-steps',
'262144')
rk4_02 = ref_map('rk4_02', '--integrator', 'rk4',
'--ode-initial-step', 0.02, '--trace-max-steps',
'262144')
dp_tight = ref_map('dp_tight', '--ode-rtol', '1e-12',
'--ode-atol-x', '1e-12', '--ode-atol-pi',
'1e-12', '--ode-atol-l', '1e-12')
assert sum(1 for v in rk4_02 if v[10] == 0) > 0, \
'reference scene has no escaped ray'
mism, ref_dev = endpoint_deviation(rk4_04, rk4_02)
assert mism == 0, 'RK4 reference levels disagree on outcome/end'
assert ref_dev < ENDPOINT_ASSERT, \
f'RK4 reference not converged: {ref_dev}; report to parent'
mism, dp_dev = endpoint_deviation(dp_tight, rk4_02)
assert mism == 0, 'DP54 default disagrees with RK4 reference'
assert dp_dev < ENDPOINT_ASSERT, (dp_dev,)
print(f'schwarzschild: RK4 ref convergence {ref_dev:.3g}, '
f'DP default vs fine RK4 {dp_dev:.3g}', flush=True)
print(f'{backend}: adaptive CLI checks passed', flush=True)
# Alcubierre production-default smoke: the DP54 default policy must
# validate and the migrated lookback budget must actually cover the warp
# bubble feature (rays integrate and escape) instead of pre-routing all
# misses. No movie/thread sweep for this third backend.
alc = BUILD / 'alcubierre_sky'
if not alc.exists():
print('alcubierre: binary absent, skipping', flush=True)
else:
alc_help = run(alc, '--help').stdout
ext = 'png' if '.png' in alc_help else 'ppm'
alc_map = tmp / 'alcubierre_default.grlens'
alc_out = tmp / f'alcubierre_default.{ext}'
run(alc, '--alcubierre-vs', 0.3, '--alcubierre-radius', 1,
'--alcubierre-sigma', 1, '--catalog', 'assets/sky_grid_5deg.csv',
'--width', 16, '--height', 8, '--fov-deg', 80, '--exposure', 1e-3,
'--coarse-cell-pixels', 8, '--refine-max-level', 0,
'--psf-relative-tail', 1e-4, '--verbose', '--output', alc_out,
'--lens-map-output', alc_map)
_, _, alc_prov = map_provenance(alc_map)
assert alc_prov['integrator'] == 1, alc_prov
assert alc_prov['min_step'] <= alc_prov['coordinate_time_step'] \
<= alc_prov['max_step']
assert alc_prov['max_lookback_time'] > 0
alc_vertices, _ = map_vertices(alc_map)
outcomes = {v[10] for v in alc_vertices}
assert 3 not in outcomes, f'Alcubierre default left INCOMPLETE rays'
assert any(v[10] == 0 for v in alc_vertices), \
'Alcubierre default produced no escaped ray'
assert sum_rhs(alc_vertices) > 0, \
'Alcubierre default pre-routed every ray; lookback misses feature'
assert image_payload(alc_out)
# The migrated coordinate-time coverage is the physical geometry budget
# margin*4*escape/(1-|v_s|) with escape = R + 20/sigma, and it is
# independent of the accepted-step count and of the chosen initial
# step. Two cheap maps with different resource overrides must keep the
# same lookback.
alc_escape = 1.0 + 20.0 / 1.0 # R + 20/sigma for R=sigma=1
alc_sep = 1.0 - abs(0.3)
alc_expected_lookback = 1.25 * 4.0 * alc_escape / alc_sep
assert abs(alc_prov['max_lookback_time'] - alc_expected_lookback) \
< 1e-12, alc_prov['max_lookback_time']
def alc_map_with(tag, *options):
m = tmp / f'alcubierre_{tag}.grlens'
run(alc, '--alcubierre-vs', 0.3, '--alcubierre-radius', 1,
'--alcubierre-sigma', 1, '--catalog', 'assets/sky_grid_5deg.csv',
'--width', 16, '--height', 8, '--fov-deg', 80, '--exposure',
1e-3, '--coarse-cell-pixels', 8, '--refine-max-level', 0,
'--psf-relative-tail', 1e-4, '--allow-incomplete',
'--output', tmp / f'alcubierre_{tag}.{ext}',
'--lens-map-output', m, *options)
return map_provenance(m)[2]
steps_override = alc_map_with('steps_override', '--trace-max-steps', 8)
assert steps_override['initial_max_steps'] == 8
assert abs(steps_override['max_lookback_time'] - alc_expected_lookback) \
< 1e-12, steps_override
step_override = alc_map_with('step_override', '--ode-initial-step', 0.02)
assert abs(step_override['coordinate_time_step'] - 0.02) < 1e-15
assert abs(step_override['max_lookback_time'] - alc_expected_lookback) \
< 1e-12, step_override
# Extreme separation (v_s = 0.99999999) where the legacy fixed-step
# estimate far exceeds the cap. The DP path must accept an explicit
# tiny coordinate-time budget instead of being rejected at startup by
# the fixed-step guard, and it must actually exercise the quota path
# (UNRESOLVED rays or real RHS work), not pre-route everything to
# escapes. --allow-incomplete publishes the diagnostic frame.
extreme = ['--alcubierre-vs', 0.99999999, '--alcubierre-radius', 1,
'--alcubierre-sigma', 1, '--catalog',
'assets/sky_grid_5deg.csv', '--width', 8, '--height', 4,
'--fov-deg', 80, '--exposure', 1e-3,
'--coarse-cell-pixels', 4, '--refine-max-level', 0,
'--psf-relative-tail', 1e-4,
'--observer-position', 0, 0, 0,
'--observer-velocity', 0.99999999, 0, 0,
'--look-ra-deg', 0, '--look-dec-deg', 0]
ext_map = tmp / 'alcubierre_extreme.grlens'
run(alc, *extreme, '--integrator', 'dp54',
'--trace-lookback-time', 1, '--trace-max-steps', 4,
'--max-total-steps', 4, '--retry-step-increment', 0,
'--max-total-lookback-time', 1, '--retry-lookback-increment', 0,
'--allow-incomplete', '--output',
tmp / f'alcubierre_extreme.{ext}', '--lens-map-output', ext_map)
_, _, ext_prov = map_provenance(ext_map)
assert ext_prov['integrator'] == 1
assert abs(ext_prov['max_lookback_time'] - 1.0) < 1e-15, ext_prov
assert ext_prov['initial_max_steps'] == 4, ext_prov
ext_vertices, _ = map_vertices(ext_map)
assert any(v[10] == 2 for v in ext_vertices) or \
sum_rhs(ext_vertices) > 0, \
'extreme Alcubierre case did not exercise the quota path'
# The same parameters without explicit DP budgets keep the legacy
# fixed-step startup guard, which still rejects the estimated domain.
rk4_extreme = run(
alc, '--integrator', 'rk4', '--alcubierre-vs', 0.99999999,
'--alcubierre-radius', 1, '--alcubierre-sigma', 1, '--catalog',
'assets/sky_grid_5deg.csv', '--width', 8, '--height', 4,
'--fov-deg', 80, '--exposure', 1e-3, '--coarse-cell-pixels', 4,
'--refine-max-level', 0, '--psf-relative-tail', 1e-4,
'--output', tmp / f'alcubierre_rk4_extreme.{ext}', ok=False)
assert rk4_extreme.returncode == 2, rk4_extreme.stderr
assert 'cap' in rk4_extreme.stderr, rk4_extreme.stderr
print('alcubierre: default DP config validated and produced escapes; '
'extreme DP quota path ok, RK4 guard still rejects', flush=True)
+52 -38
View File
@@ -307,10 +307,11 @@ static void test_boundary_semantics_minkowski(void) {
spacetime_destroy(&source);
}
static void test_boundary_semantics_generic(void) {
/* velocity_constant == 0 forces the generic bracketed driver. A finite
* history bounds the outward/tangent searches, which must not be reported
* as entries (they end as TIME_RANGE_EXHAUSTED instead). */
static void test_nonconstant_route_unsupported(void) {
/* velocity_constant == 0 signals an arbitrary accelerated worldtube. The
* camera-inside containment test still runs first; once the ray starts
* outside, the non-constant route is refused explicitly instead of being
* searched with a coarse bracket. */
SyntheticContext context = {.radius = 10.0,
.valid_t_min = -100.0,
.constant = 0};
@@ -321,15 +322,15 @@ static void test_boundary_semantics_generic(void) {
&source, &on_boundary, (double[]){-1.0, 0.0, 0.0}, &route);
CHECK(inward == ASYMPTOTIC_OK &&
route.kind == ASYMPTOTIC_ROUTE_INSIDE,
"generic on-boundary inward is inside");
"nonconstant on-boundary inward is still inside");
const AsymptoticStatus outward = asymptotic_route_camera(
&source, &on_boundary, (double[]){1.0, 0.0, 0.0}, &route);
CHECK(outward == ASYMPTOTIC_TIME_RANGE_EXHAUSTED,
"generic on-boundary outward is not an entry");
CHECK(outward == ASYMPTOTIC_UNSUPPORTED,
"nonconstant on-boundary outward is unsupported");
const AsymptoticStatus tangent = asymptotic_route_camera(
&source, &on_boundary, (double[]){0.0, 1.0, 0.0}, &route);
CHECK(tangent == ASYMPTOTIC_TIME_RANGE_EXHAUSTED,
"generic on-boundary tangent is not an entry");
CHECK(tangent == ASYMPTOTIC_UNSUPPORTED,
"nonconstant on-boundary tangent is unsupported");
}
static void test_negative_radius_root_guard(void) {
@@ -525,11 +526,12 @@ static void test_interior_crossing_bisection_failure(void) {
"interior crossing bisection propagates history exhaustion");
}
static void test_generic_bisection_failure(void) {
/* The isolated invalid window lands on a bisection midpoint while the
* bracket endpoints stay valid, so only the bisection can see it. With the
* strict F < 0 entry test, the bracket is s = 80 (F == 0) to s = 90
* (F < 0), so the first midpoint is t = -85. */
static void test_nonconstant_preroute_unsupported(void) {
/* The accelerated fixture is refused explicitly at the route entry; its
* former coarse bracketed search (and the bisection-midpoint history-hole
* case it exercised) is replaced by the unsupported contract. The history
* reason itself stays covered by the constant and piecewise-constant
* worldtube tests below. */
SyntheticContext context = {.radius = 20.0,
.valid_t_min = -1.0e30,
.constant = 0,
@@ -538,9 +540,26 @@ static void test_generic_bisection_failure(void) {
SpacetimeSource source = {.ops = &synthetic_ops, .context = &context};
const ObserverState camera = flat_observer(100.0, 0.0, 0.0);
AsymptoticRoute route;
CHECK(asymptotic_route_camera(&source, &camera, (double[]){-1.0, 0.0, 0.0},
&route) == ASYMPTOTIC_UNSUPPORTED,
"nonconstant worldtube route is unsupported");
}
static void test_piecewise_constant_history_hole(void) {
/* Piecewise-constant motion stays supported, including its history
* contract: when the segment walk steps past the valid history, the route
* reports TIME_RANGE_EXHAUSTED and never disguises it as a miss. */
SyntheticContext context = {.radius = 10.0,
.valid_t_min = -3.0,
.constant = 1,
.segment_t = -5.0,
.has_segment = 1};
SpacetimeSource source = {.ops = &synthetic_ops, .context = &context};
const ObserverState camera = flat_observer(50.0, 0.0, 0.0);
AsymptoticRoute route;
CHECK(asymptotic_route_camera(&source, &camera, (double[]){-1.0, 0.0, 0.0},
&route) == ASYMPTOTIC_TIME_RANGE_EXHAUSTED,
"generic worldtube bisection propagates sample failure");
"piecewise-constant segment history hole is exhausted");
}
static void test_interior_history_exhaustion(void) {
@@ -641,36 +660,30 @@ static void test_moving_sphere(void) {
"co-moving ray misses");
}
static void test_accelerated_worldtube(void) {
static void test_accelerated_worldtube_unsupported(void) {
/* A genuinely accelerating (non-constant velocity) worldtube has no strict
* relative-motion interval bound, so the route is explicitly unsupported.
* Its former finite-history branch is covered by the constant and
* piecewise-constant history tests instead. */
SyntheticContext context = {.vx = 0.0, .accel = 0.02, .radius = 20.0,
.valid_t_min = -1.0e30, .constant = 0};
SpacetimeSource source = {.ops = &synthetic_ops, .context = &context};
const ObserverState camera = flat_observer(100.0, 0.0, 0.0);
AsymptoticRoute route;
CHECK(asymptotic_route_camera(&source, &camera, (double[]){-1.0, 0.0, 0.0},
&route) == ASYMPTOTIC_OK &&
route.kind == ASYMPTOTIC_ROUTE_ENTRY,
"accelerated worldtube entry");
double value;
CHECK(asymptotic_worldtube_value(&source, route.end_id, route.activate_t,
route.x, &value) == 0 &&
fabs(value) <= 1e-13 * 20.0 * 20.0,
"accelerated entry lies on worldtube");
CHECK(route.activate_t < -40.0 && route.activate_t > -60.0,
"accelerated entry time in range");
context.valid_t_min = -30.0;
CHECK(asymptotic_route_camera(&source, &camera, (double[]){-1.0, 0.0, 0.0},
&route) == ASYMPTOTIC_TIME_RANGE_EXHAUSTED,
"exhausted history is not a miss");
&route) == ASYMPTOTIC_UNSUPPORTED,
"accelerated worldtube route is unsupported");
}
static void test_accelerated_segment(void) {
static void test_piecewise_segment_entry(void) {
/* The two-segment motion is piecewise constant, so velocity_constant == 1
* per segment and the closed quadratic path must still find the entry in
* the second segment (the analytic segmented first-entry case). */
SyntheticContext context = {.vx = 0.0,
.accel = 0.0,
.radius = 20.0,
.valid_t_min = -1.0e30,
.constant = 0,
.constant = 1,
.segment_t = -50.0,
.has_segment = 1};
SpacetimeSource source = {.ops = &synthetic_ops, .context = &context};
@@ -679,7 +692,7 @@ static void test_accelerated_segment(void) {
CHECK(asymptotic_route_camera(&source, &camera, (double[]){-1.0, 0.0, 0.0},
&route) == ASYMPTOTIC_OK &&
route.kind == ASYMPTOTIC_ROUTE_ENTRY,
"cross-segment entry");
"piecewise cross-segment entry");
CHECK(route.activate_t < context.segment_t,
"entry lies past the motion-segment boundary");
CHECK(fabs(route.activate_t + 65.0) < 1e-6, "cross-segment entry time");
@@ -731,16 +744,17 @@ int main(void) {
test_large_radius_quadratic();
test_round_trip();
test_moving_sphere();
test_accelerated_worldtube();
test_accelerated_segment();
test_accelerated_worldtube_unsupported();
test_piecewise_segment_entry();
test_boundary_semantics_minkowski();
test_boundary_semantics_generic();
test_nonconstant_route_unsupported();
test_negative_radius_root_guard();
test_source_finalize();
test_motion_segment_domain();
test_schwarzschild_sample_failures();
test_end_protocol_error();
test_generic_bisection_failure();
test_nonconstant_preroute_unsupported();
test_piecewise_constant_history_hole();
test_interior_history_exhaustion();
test_interior_crossing_bisection_failure();
test_ray_pool_lifecycle();
+25 -10
View File
@@ -88,19 +88,31 @@ def image_payload(path, dimensions=(64, 48), allow_black=False):
return raw
# Version 3 wire layout. The first 100 bytes are the v2 provenance; the v3
# adaptive policy appends 9 doubles and a u32 (76 bytes) so the provenance
# block ends at 176. Each frame header is 48 bytes, so vertex payload starts at
# 224. A v3 vertex keeps the 84-byte v2 record and appends three u64 cost
# counters; a triangle stays 32 bytes. The CRC covers only the vertex+triangle
# payload, never the header.
MAP_PROVENANCE_END = 176
MAP_FRAME_HEADER_START = MAP_PROVENANCE_END
MAP_VERTEX_START = MAP_PROVENANCE_END + 48
MAP_VERTEX_SIZE = 84 + 24
MAP_TRIANGLE_SIZE = 32
def map_vertices(path):
data = path.read_bytes()
assert data[:8] == b'GRLENS\x01\x00'
assert struct.unpack_from('<I', data, 8)[0] == 3, 'expected v3 lens map'
assert struct.unpack_from('<Q', data, 32)[0] == 1 # frame_count
# Header: 40 bytes, then a 60-byte provenance block, then the 48-byte
# per-frame header, so vertices start at offset 148 in the v2 format.
vertices, triangles = struct.unpack_from('<QQ', data, 124)
offset = 148
vertices, triangles = struct.unpack_from('<QQ', data, MAP_FRAME_HEADER_START + 24)
offset = MAP_VERTEX_START
values = []
for _ in range(vertices):
values.append(struct.unpack_from('<9dIII', data, offset))
offset += 84
return values, data[offset:offset + triangles * 32]
values.append(struct.unpack_from('<9dIIIQQQ', data, offset))
offset += MAP_VERTEX_SIZE
return values, data[offset:offset + triangles * MAP_TRIANGLE_SIZE]
with tempfile.TemporaryDirectory(prefix='gr-camera-cli-') as directory:
@@ -282,7 +294,10 @@ with tempfile.TemporaryDirectory(prefix='gr-camera-cli-') as directory:
assert len(a) == len(b) and ta == tb
max_error = 0
for x, y in zip(a, b):
assert x[9:] == y[9:], 'ray endpoint provenance mismatch'
# Indices 9..11 are end_id/outcome/reason; indices 12..14 are the
# per-vertex integration cost counters, which are not physical ray
# provenance and may differ between the single and movie schedulers.
assert x[9:12] == y[9:12], 'ray endpoint provenance mismatch'
max_error = max(max_error, *(abs(v - w) for v, w in zip(x[:9], y[:9])))
assert max_error < 1e-9, max_error
@@ -305,9 +320,9 @@ with tempfile.TemporaryDirectory(prefix='gr-camera-cli-') as directory:
# so this tests completion semantics rather than corruption detection.
incomplete_map = tmp / f'{backend}_incomplete.grlens'
payload = bytearray(single_map.read_bytes())
struct.pack_into('<II', payload, 148 + 76, 3, 5) # INCOMPLETE / INVALID_METRIC
struct.pack_into('<II', payload, MAP_VERTEX_START + 76, 3, 5) # INCOMPLETE / INVALID_METRIC
struct.pack_into('<I', payload, len(payload)-4,
zlib.crc32(payload[148:-4]))
zlib.crc32(payload[MAP_VERTEX_START:-4]))
incomplete_map.write_bytes(payload)
refused = tmp / f'{backend}_refused.{ext}'
failure = run(binary, *common, '--lens-map-input', incomplete_map,
+644 -2
View File
@@ -9,6 +9,107 @@
#include <string.h>
#include <unistd.h>
/* Self-contained legacy v2 lens-map fixture writer. The production writer now
* emits v3, so this serializes a real little-endian v2 map (v2 provenance, no
* per-vertex cost counters) from a live mesh to keep the import path covered by
* genuine bytes instead of a hand-maintained golden blob. */
static uint32_t v2_crc32(uint32_t crc, const void *data, size_t size) {
const unsigned char *bytes = data;
for (size_t i = 0; i < size; ++i) {
crc ^= bytes[i];
for (int bit = 0; bit < 8; ++bit)
crc = (crc >> 1) ^ (0xedb88320u & (uint32_t)-(int)(crc & 1));
}
return crc;
}
static int v2_fwrite_u32(FILE *f, uint32_t v) {
unsigned char b[4] = {(unsigned char)v, (unsigned char)(v >> 8),
(unsigned char)(v >> 16), (unsigned char)(v >> 24)};
return fwrite(b, 1, sizeof b, f) == sizeof b ? 0 : -1;
}
static int v2_fwrite_u64(FILE *f, uint64_t v) {
unsigned char b[8];
for (int i = 0; i < 8; ++i) b[i] = (unsigned char)(v >> (8 * i));
return fwrite(b, 1, sizeof b, f) == sizeof b ? 0 : -1;
}
static int v2_fwrite_double(FILE *f, double v) {
uint64_t bits; memcpy(&bits, &v, sizeof bits);
return v2_fwrite_u64(f, bits);
}
static int write_v2_lens_map(const char *path, int width, int height, double fov,
const LensMapProvenance *p,
const LensMapFrame *frame) {
static const unsigned char magic[8] = {'G', 'R', 'L', 'E', 'N', 'S', 1, 0};
FILE *f = fopen(path, "wb");
if (f == NULL) return -1;
int failed = fwrite(magic, 1, sizeof magic, f) != sizeof magic ||
v2_fwrite_u32(f, 2) || v2_fwrite_u32(f, 0x01020304u) ||
v2_fwrite_u32(f, (uint32_t)width) || v2_fwrite_u32(f, (uint32_t)height) ||
v2_fwrite_double(f, fov) || v2_fwrite_u64(f, 1) ||
v2_fwrite_u32(f, p->threshold_kind) ||
v2_fwrite_u32(f, p->threshold_policy_version) ||
v2_fwrite_double(f, p->threshold_value) ||
v2_fwrite_u32(f, p->retry_step_increment) ||
v2_fwrite_u32(f, p->max_total_steps) || v2_fwrite_u32(f, p->max_level) ||
v2_fwrite_u32(f, p->integrator) ||
v2_fwrite_double(f, p->min_edge_pixels) ||
v2_fwrite_double(f, p->min_area_pixels2) ||
v2_fwrite_double(f, p->coordinate_time_step) ||
v2_fwrite_u32(f, p->initial_max_steps);
const FrameLensMesh *m = &frame->mesh;
failed = failed || v2_fwrite_u64(f, frame->frame_id) ||
v2_fwrite_double(f, frame->coordinate_time) ||
v2_fwrite_double(f, frame->proper_time) ||
v2_fwrite_u64(f, (uint64_t)m->vertex_count) ||
v2_fwrite_u64(f, (uint64_t)m->triangle_count) ||
v2_fwrite_u64(f, (uint64_t)m->retry_requests);
/* The payload CRC covers vertices+triangles only; the header stays
* deliberately independent, exactly as in the production writers. */
uint32_t crc = UINT32_MAX;
for (size_t i = 0; i < m->vertex_count; ++i) {
const LensVertex *v = &m->vertices[i];
const double vals[9] = {v->image_x, v->image_y, v->camera_direction[0],
v->camera_direction[1], v->camera_direction[2],
v->n_infinity[0], v->n_infinity[1], v->n_infinity[2],
v->log_frequency_ratio};
const uint32_t tail[3] = {(uint32_t)v->end_id, (uint32_t)v->outcome,
(uint32_t)v->reason};
unsigned char b[84]; size_t off = 0;
for (int k = 0; k < 9; ++k) {
uint64_t bits; memcpy(&bits, &vals[k], sizeof bits);
for (int q = 0; q < 8; ++q) b[off++] = (unsigned char)(bits >> (8 * q));
}
for (int k = 0; k < 3; ++k) {
b[off++] = (unsigned char)tail[k];
b[off++] = (unsigned char)(tail[k] >> 8);
b[off++] = (unsigned char)(tail[k] >> 16);
b[off++] = (unsigned char)(tail[k] >> 24);
}
if (fwrite(b, 1, sizeof b, f) != sizeof b) failed = 1;
crc = v2_crc32(crc, b, sizeof b);
}
for (size_t i = 0; i < m->triangle_count; ++i) {
unsigned char b[32]; size_t off = 0;
for (int j = 0; j < 3; ++j) {
const uint64_t idx = (uint64_t)m->triangles[i].vertex[j];
for (int q = 0; q < 8; ++q) b[off++] = (unsigned char)(idx >> (8 * q));
}
const uint32_t tail[2] = {m->triangles[i].level,
(uint32_t)m->triangles[i].approx_black};
for (int k = 0; k < 2; ++k) {
b[off++] = (unsigned char)tail[k];
b[off++] = (unsigned char)(tail[k] >> 8);
b[off++] = (unsigned char)(tail[k] >> 16);
b[off++] = (unsigned char)(tail[k] >> 24);
}
if (fwrite(b, 1, sizeof b, f) != sizeof b) failed = 1;
crc = v2_crc32(crc, b, sizeof b);
}
if (v2_fwrite_u32(f, crc ^ UINT32_MAX)) failed = 1;
if (fclose(f)) failed = 1;
return failed ? -1 : 0;
}
static int mesh_has_hanging_vertex(const FrameLensMesh *mesh) {
for (size_t triangle = 0; triangle < mesh->triangle_count; ++triangle)
for (size_t side = 0; side < 3; ++side) {
@@ -139,6 +240,9 @@ static int review_probe_regressions(void) {
m.vertices[i].n_infinity[0]=1;
}
m.vertices[0].outcome=RAY_OUTCOME_UNRESOLVED;
/* A genuinely budget-exhausted vertex has consumed its accepted-step
* quota; the retry layer keys on that blocking quota. */
m.vertices[0].continuation_steps=10;
m.vertices[0].continuation_limit=10;
if (frame_lens_mesh_prepare_generation(&m,&c)<1 || m.retry_requests==0)
return -1; /* the retry counter is produced by real retry requests */
@@ -153,11 +257,408 @@ static int review_probe_regressions(void) {
return 0;
}
/* A budget-exhausted vertex carrying the full adaptive resume state, so the
* retry layer sees the same payload the production store_endpoint path writes. */
static LensVertex unresolved_vertex(double image_x, double t, double start,
unsigned int steps,
unsigned int step_limit,
double lookback_limit) {
LensVertex v;
memset(&v, 0, sizeof v);
v.image_x = image_x;
v.image_y = 0.0;
v.camera_direction[0] = 1.0;
v.outcome = RAY_OUTCOME_UNRESOLVED;
v.traced = 1;
v.continuation_t = t;
v.continuation_x[0] = 1.0;
v.continuation_Pi[0] = -1.0;
v.continuation_log_alpha_p0 = 0.125;
v.continuation_log_alpha_p0_0 = 0.5;
v.continuation_steps = steps;
v.continuation_limit = step_limit;
v.continuation_integration_start_time = start;
v.continuation_next_step = 0.25;
v.continuation_rejected_steps = 2;
v.continuation_rhs_evaluations = 20;
v.continuation_previous_rejected = 0;
v.continuation_lookback_limit = lookback_limit;
return v;
}
static int uuu_fixture(LensVertex vertices[3], LensTriangle *triangle) {
for (int i = 0; i < 3; ++i)
vertices[i].camera_direction[0] = 1.0;
*triangle = (LensTriangle){{0, 1, 2}, 0, 0, 0};
return 0;
}
/* Independent step/time retry budgets: a request is allowed only while every
* quota that actually blocked the ray can still grow, and the two quotas are
* saturated separately. Also verifies the resume-state round trip used by
* both the frame and movie paths. */
static int review_retry_quota_regressions(void) {
/* 1. Step-blocked, both quotas growable: the step quota advances, and the
* request records the independently saturated time quota. */
{
LensVertex vertices[3] = {
unresolved_vertex(0.0, -1.0, 0.0, 20, 20, 2.0),
unresolved_vertex(10.0, -1.0, 0.0, 20, 20, 2.0),
unresolved_vertex(0.0, -1.0, 0.0, 20, 20, 2.0)};
LensTriangle triangle;
uuu_fixture(vertices, &triangle);
FrameLensMesh m = {.vertices = vertices, .vertex_count = 3,
.triangles = &triangle, .triangle_count = 1};
RefinementConfig c = {.max_level = 0,
.retry_step_increment = 10,
.max_total_steps = 100,
.retry_lookback_increment = 1.0,
.max_total_lookback_time = 10.0};
if (frame_lens_mesh_prepare_generation(&m, &c) != 3) {
free(m.samples); free(m.probe_slots);
return -1;
}
for (size_t i = 0; i < m.sample_count; ++i)
if (m.samples[i].kind != FRAME_SAMPLE_RETRY ||
m.samples[i].step_limit != 30 ||
m.samples[i].lookback_limit != 3.0) {
free(m.samples); free(m.probe_slots);
return -1;
}
free(m.samples); free(m.probe_slots);
}
/* 2. Step quota at its cap while time can still grow: no request, because
* more time cannot buy an accepted step. The UUU triangle stays a
* budget-incomplete boundary, never blackened. */
{
LensVertex vertices[3] = {
unresolved_vertex(0.0, -1.0, 0.0, 20, 20, 2.0),
unresolved_vertex(10.0, -1.0, 0.0, 20, 20, 2.0),
unresolved_vertex(0.0, -1.0, 0.0, 20, 20, 2.0)};
LensTriangle triangle;
uuu_fixture(vertices, &triangle);
FrameLensMesh m = {.vertices = vertices, .vertex_count = 3,
.triangles = &triangle, .triangle_count = 1};
RefinementConfig c = {.max_level = 0,
.retry_step_increment = 10,
.max_total_steps = 20,
.retry_lookback_increment = 1.0,
.max_total_lookback_time = 10.0};
if (frame_lens_mesh_prepare_generation(&m, &c) != 0) {
free(m.samples); free(m.probe_slots);
return -1;
}
FrameBoundaryStats stats;
frame_lens_mesh_boundary_stats(&m, &c, &stats);
if (stats.uuu != 1 || stats.budget_incomplete_triangles == 0 ||
stats.approx_black_triangles != 0) {
free(m.samples); free(m.probe_slots);
return -1;
}
free(m.samples); free(m.probe_slots);
}
/* 3. Time-blocked with a disabled step increment: the time quota alone
* enables the retry and the step budget is left untouched. */
{
LensVertex vertices[3] = {
unresolved_vertex(0.0, -3.0, 0.0, 5, 10, 2.0),
unresolved_vertex(10.0, -3.0, 0.0, 5, 10, 2.0),
unresolved_vertex(0.0, -3.0, 0.0, 5, 10, 2.0)};
LensTriangle triangle;
uuu_fixture(vertices, &triangle);
FrameLensMesh m = {.vertices = vertices, .vertex_count = 3,
.triangles = &triangle, .triangle_count = 1};
RefinementConfig c = {.max_level = 0,
.retry_step_increment = 0,
.max_total_steps = 10,
.retry_lookback_increment = 1.0,
.max_total_lookback_time = 10.0};
if (frame_lens_mesh_prepare_generation(&m, &c) != 3) {
free(m.samples); free(m.probe_slots);
return -1;
}
for (size_t i = 0; i < m.sample_count; ++i)
if (m.samples[i].kind != FRAME_SAMPLE_RETRY ||
m.samples[i].step_limit != 10 ||
m.samples[i].lookback_limit != 3.0) {
free(m.samples); free(m.probe_slots);
return -1;
}
free(m.samples); free(m.probe_slots);
}
/* 4. Both blocking quotas at their caps: no request. */
{
LensVertex vertices[3] = {
unresolved_vertex(0.0, -6.0, 0.0, 20, 20, 5.0),
unresolved_vertex(10.0, -6.0, 0.0, 20, 20, 5.0),
unresolved_vertex(0.0, -6.0, 0.0, 20, 20, 5.0)};
LensTriangle triangle;
uuu_fixture(vertices, &triangle);
FrameLensMesh m = {.vertices = vertices, .vertex_count = 3,
.triangles = &triangle, .triangle_count = 1};
RefinementConfig c = {.max_level = 0,
.retry_step_increment = 10,
.max_total_steps = 20,
.retry_lookback_increment = 1.0,
.max_total_lookback_time = 5.0};
if (frame_lens_mesh_prepare_generation(&m, &c) != 0) {
free(m.samples); free(m.probe_slots);
return -1;
}
free(m.samples); free(m.probe_slots);
}
/* 5. Resume-state round trip: the helper reproduces every control field, and
* install back-fills the vertex with the endpoint's new state and quota. */
{
LensVertex source = unresolved_vertex(0.0, -1.0, 0.0, 20, 20, 2.0);
GeodesicRayState state;
if (frame_vertex_continuation_state(&source, &state) ||
state.coordinate_time != -1.0 || state.steps != 20 ||
state.integration_start_time != 0.0 || state.next_step != 0.25 ||
state.rejected_steps != 2 || state.rhs_evaluations != 20 ||
state.previous_rejected != 0 || state.log_alpha_p0 != 0.125 ||
state.log_alpha_p0_0 != 0.5 || state.x[0] != 1.0 ||
state.Pi[0] != -1.0)
return -1;
LensVertex target = {.image_x = 0.0, .camera_direction = {1.0, 0.0, 0.0}};
FrameLensMesh mesh = {.vertices = &target, .vertex_count = 1};
RefinementConfig plain = {.max_level = 0};
if (frame_lens_mesh_prepare_generation(&mesh, &plain) != 1) {
free(mesh.samples); free(mesh.probe_slots);
return -1;
}
const RayEndpoint endpoint = {
.magnification = 1.0,
.end_id = SPACETIME_END_NONE,
.outcome = RAY_OUTCOME_UNRESOLVED,
.reason = RAY_REASON_BUDGET_EXHAUSTED,
.stop_coordinate_time = -4.0,
.accepted_steps = 7,
.final_x = {5.0, 0.0, 0.0},
.final_Pi = {-1.0, 0.0, 0.0},
.final_log_alpha_p0 = 0.125,
.final_log_alpha_p0_0 = 0.5,
.threshold_value = NAN,
.integration_start_time = 0.0,
.next_step = 0.5,
.rejected_steps = 3,
.rhs_evaluations = 70,
.previous_rejected = 1,
.lookback_limit = 2.5};
if (frame_lens_mesh_install_sample(&mesh, 0, &endpoint) ||
target.outcome != RAY_OUTCOME_UNRESOLVED ||
target.continuation_t != -4.0 || target.continuation_steps != 7 ||
target.continuation_limit != 7 ||
target.continuation_integration_start_time != 0.0 ||
target.continuation_next_step != 0.5 ||
target.continuation_rejected_steps != 3 ||
target.continuation_rhs_evaluations != 70 ||
target.continuation_previous_rejected != 1 ||
target.continuation_lookback_limit != 2.5) {
free(mesh.samples); free(mesh.probe_slots);
return -1;
}
GeodesicRayState rebuilt;
if (frame_vertex_continuation_state(&target, &rebuilt) ||
rebuilt.coordinate_time != -4.0 || rebuilt.steps != 7 ||
rebuilt.next_step != 0.5 || rebuilt.rejected_steps != 3 ||
rebuilt.rhs_evaluations != 70 || rebuilt.previous_rejected != 1) {
free(mesh.samples); free(mesh.probe_slots);
return -1;
}
free(mesh.samples); free(mesh.probe_slots);
}
return 0;
}
static GeodesicTraceConfig frame_dp_config(double lookback) {
GeodesicTraceConfig c;
memset(&c, 0, sizeof c);
c.stepper = GEODESIC_STEPPER_DP54;
c.coordinate_time_step = 0.5;
c.max_steps = 100;
c.threshold = (ThresholdPolicy){.kind = THRESHOLD_DISABLED, .value = 0.0,
.policy_version = 0};
c.atol_x = c.atol_Pi = c.atol_L = c.rtol = 1e-9;
c.min_step = 1e-12;
c.max_step = 1e6;
c.consecutive_rejection_limit = 1000;
c.max_lookback_time = lookback;
return c;
}
/* A real production trace must record the configured step/time grant distinct
* from the spent counts, so a time-limited ray can retry with extra time while
* keeping its full step grant. A retry candidate also counts only when it
* opens a strictly larger representable region. */
static int review_real_grant_and_time_growth(void) {
SpacetimeSource source = {0};
if (spacetime_create_minkowski(&source, 10.0))
return -1;
const ObserverState observer = observer_fixed_at_origin();
int failed = 0;
/* A. Real trace at the origin, time-limited after ~2 of 100 granted steps.
* With the step increment disabled and the time increment enabled, the
* retry must keep step_limit == 100 and only grow the time budget. */
{
LensVertex vertices[3] = {{.camera_direction = {1.0, 0.0, 0.0}},
{.camera_direction = {1.0, 0.0, 0.0}},
{.camera_direction = {1.0, 0.0, 0.0}}};
LensTriangle triangle = {{0, 1, 2}, 0, 0, 0};
FrameLensMesh m = {.vertices = vertices, .vertex_count = 3,
.triangles = &triangle, .triangle_count = 1};
const GeodesicTraceConfig trace = frame_dp_config(1.0);
if (frame_lens_mesh_trace(&m, &source, &observer, &trace))
failed = 1;
for (size_t i = 0; i < 3 && !failed; ++i) {
if (vertices[i].outcome != RAY_OUTCOME_UNRESOLVED ||
vertices[i].continuation_limit != 100u ||
vertices[i].continuation_steps >= 100u ||
vertices[i].continuation_lookback_limit != 1.0)
failed = 1;
}
const RefinementConfig retry = {.max_level = 0,
.retry_step_increment = 0,
.max_total_steps = 100,
.retry_lookback_increment = 1.0,
.max_total_lookback_time = 100.0};
if (!failed && frame_lens_mesh_prepare_generation(&m, &retry) != 3)
failed = 1;
for (size_t i = 0; i < m.sample_count && !failed; ++i)
if (m.samples[i].kind != FRAME_SAMPLE_RETRY ||
m.samples[i].step_limit != 100u ||
m.samples[i].lookback_limit != 2.0)
failed = 1;
free(m.samples);
free(m.probe_slots);
}
/* B. Translated time origins (positive, negative and zero) still detect the
* lookback boundary with the integrator's own comparison and retry with a
* strictly larger time region while preserving the step grant. */
for (int origin = 0; origin < 3 && !failed; ++origin) {
const double t0 = origin == 0 ? 0.0 : origin == 1 ? 1.0e9 : -1.0e9;
ObserverState o = observer_fixed_at_origin();
o.coordinate_time = t0;
const GeodesicTraceConfig trace = frame_dp_config(0.3);
MetricData metric;
if (spacetime_eval(&source, t0, o.coordinate_position, &metric)) {
failed = 1;
break;
}
GeodesicRayState state;
if (geodesic_initialize_past_ray_metric(&metric, &o, (double[]){1, 0, 0},
&state)) {
failed = 1;
break;
}
const RayEndpoint endpoint =
geodesic_trace_past_from_state(&source, &state, &trace);
if (endpoint.outcome != RAY_OUTCOME_UNRESOLVED ||
endpoint.accepted_step_limit != 100u || endpoint.accepted_steps != 1u) {
failed = 1;
break;
}
LensVertex vertices[3];
memset(vertices, 0, sizeof vertices);
vertices[1].traced = vertices[2].traced = 1;
vertices[1].outcome = vertices[2].outcome = RAY_OUTCOME_ESCAPED;
vertices[1].n_infinity[0] = vertices[2].n_infinity[0] = 1.0;
vertices[1].end_id = vertices[2].end_id = 0;
LensTriangle triangle = {{0, 1, 2}, 0, 0, 0};
FrameLensMesh m = {.vertices = vertices, .vertex_count = 3,
.triangles = &triangle, .triangle_count = 1};
const RefinementConfig plain = {.max_level = 0};
if (frame_lens_mesh_prepare_generation(&m, &plain) != 1 ||
frame_lens_mesh_install_sample(&m, 0, &endpoint) ||
vertices[0].continuation_limit != 100u ||
vertices[0].continuation_lookback_limit != 0.3) {
free(m.samples);
free(m.probe_slots);
failed = 1;
break;
}
free(m.samples);
m.samples = NULL;
m.sample_count = m.sample_capacity = 0;
const RefinementConfig retry = {.max_level = 0,
.retry_step_increment = 0,
.max_total_steps = 100,
.retry_lookback_increment = 0.3,
.max_total_lookback_time = 10.0};
if (frame_lens_mesh_prepare_generation(&m, &retry) != 1 ||
m.samples[0].kind != FRAME_SAMPLE_RETRY ||
m.samples[0].step_limit != 100u ||
m.samples[0].lookback_limit != 0.6)
failed = 1;
free(m.samples);
free(m.probe_slots);
}
/* C. A larger quota that does not move the left boundary (increment rounds
* away, or a large time origin absorbs it) must not launch a retry. */
{
LensVertex vertices[3] = {
unresolved_vertex(0.0, -1.0e9, 0.0, 1, 100, 1.0e9),
unresolved_vertex(10.0, -1.0e9, 0.0, 1, 100, 1.0e9),
unresolved_vertex(0.0, -1.0e9, 0.0, 1, 100, 1.0e9)};
LensTriangle triangle;
uuu_fixture(vertices, &triangle);
FrameLensMesh m = {.vertices = vertices, .vertex_count = 3,
.triangles = &triangle, .triangle_count = 1};
const RefinementConfig rounded = {.max_level = 0,
.retry_step_increment = 0,
.max_total_steps = 100,
.retry_lookback_increment = 1.0e-8,
.max_total_lookback_time = 2.0e9};
if (frame_lens_mesh_prepare_generation(&m, &rounded) != 0)
failed = 1;
FrameBoundaryStats stats;
frame_lens_mesh_boundary_stats(&m, &rounded, &stats);
if (stats.uuu != 1 || stats.budget_incomplete_triangles == 0)
failed = 1;
free(m.samples);
free(m.probe_slots);
}
{
const double start = 1.0e12;
const double quota = 1.0e6;
LensVertex vertices[3] = {
unresolved_vertex(0.0, start - quota, start, 1, 100, quota),
unresolved_vertex(10.0, start - quota, start, 1, 100, quota),
unresolved_vertex(0.0, start - quota, start, 1, 100, quota)};
LensTriangle triangle;
uuu_fixture(vertices, &triangle);
FrameLensMesh m = {.vertices = vertices, .vertex_count = 3,
.triangles = &triangle, .triangle_count = 1};
const RefinementConfig absorbed = {.max_level = 0,
.retry_step_increment = 0,
.max_total_steps = 100,
.retry_lookback_increment = 1.0e-6,
.max_total_lookback_time = 2.0e6};
if (frame_lens_mesh_prepare_generation(&m, &absorbed) != 0)
failed = 1;
free(m.samples);
free(m.probe_slots);
}
spacetime_destroy(&source);
return failed ? -1 : 0;
}
int main(void) {
if (review_probe_regressions()) {
fputs("probe persistence / retry invalidation regression failed\n",stderr);
return 1;
}
if (review_retry_quota_regressions()) {
fputs("independent retry quota regression failed\n", stderr);
return 1;
}
if (review_real_grant_and_time_growth()) {
fputs("real grant / representable time growth regression failed\n", stderr);
return 1;
}
const int width = 100, height = 100;
const double test_exposure = 1e-3;
const double psf_relative_tail = 1e-8;
@@ -187,7 +688,7 @@ int main(void) {
}
/* A finalized mesh can be persisted independently of spacetime and then
* drive the exact same catalog inverse-map and PSF pass. */
const char *lens_map_path = "/tmp/gr_lens_map_test.grlens";
const char *lens_map_path = "/tmp/opencode/gr_lens_map_test.grlens";
mesh.retry_requests = 2; /* cumulative per-frame retry accounting round-trips */
const LensMapFrame saved_frame = {.frame_id = 7,
.coordinate_time = 3.0,
@@ -259,7 +760,7 @@ int main(void) {
/* A version-1 header must be rejected outright: its captured bit cannot be
* upgraded into the new dark/unresolved/error provenance. */
{
const char *legacy_path = "/tmp/gr_lens_map_v1_test.grlens";
const char *legacy_path = "/tmp/opencode/gr_lens_map_v1_test.grlens";
FILE *legacy = fopen(legacy_path, "wb");
int legacy_failed = legacy == NULL;
if (!legacy_failed) {
@@ -906,6 +1407,147 @@ int main(void) {
}
free(ud_mesh.probe_slots);
}
/* v3 DP54 round-trip: every adaptive/quota field and the per-vertex cost
* counters must survive the wire exactly. */
{
const char *dp_path = "/tmp/opencode/gr_lens_map_v3_dp_test.grlens";
LensVertex dv[3];
for (int i = 0; i < 3; ++i)
dv[i] = (LensVertex){.image_x = (double)i, .image_y = 2.0,
.camera_direction = {0.0, 0.0, -1.0},
.outcome = RAY_OUTCOME_DARK,
.reason = RAY_REASON_REDSHIFT_LIMIT,
.end_id = SPACETIME_END_NONE, .traced = 1};
dv[0].trace_accepted_steps = 11; dv[0].trace_rejected_steps = 2;
dv[0].trace_rhs_evaluations = 79;
dv[1].trace_accepted_steps = 5;
LensTriangle dt = {{0, 1, 2}, 1, 1, 0};
FrameLensMesh dm = {.vertices = dv, .triangles = &dt, .vertex_count = 3,
.vertex_capacity = 3, .triangle_count = 1,
.triangle_capacity = 1};
LensMapFrame df = {.frame_id = 3, .coordinate_time = 1.5,
.proper_time = 1.25, .mesh = dm};
const LensMapProvenance dp = {.threshold_kind = THRESHOLD_LOG_ENERGY_GROWTH,
.threshold_policy_version = 3, .threshold_value = 8.0,
.retry_step_increment = 64, .max_total_steps = 256, .max_level = 2,
.integrator = (uint32_t)GEODESIC_STEPPER_DP54,
.min_edge_pixels = 0.5, .min_area_pixels2 = 0.25,
.coordinate_time_step = 0.1, .initial_max_steps = 1024,
.atol_x = 1e-9, .atol_Pi = 1e-9, .atol_L = 1e-9, .rtol = 1e-9,
.min_step = 1e-12, .max_step = 2.0, .max_lookback_time = 102.4,
.retry_lookback_increment = 102.4, .max_total_lookback_time = 409.6,
.max_consecutive_rejections = 32};
LensMap dloaded = {0};
if (lens_map_write(dp_path, 4, 3, 30.0, &dp, &df, 1) ||
lens_map_read(dp_path, NULL, &dloaded)) {
fputs("lens-map v3 DP54 round-trip regression failed\n", stderr);
lens_map_destroy(&dloaded); unlink(dp_path); goto done;
}
const LensMapProvenance *lp = &dloaded.provenance;
if (dloaded.file_version != 3 ||
lp->integrator != (uint32_t)GEODESIC_STEPPER_DP54 ||
lp->atol_x != 1e-9 || lp->atol_Pi != 1e-9 || lp->atol_L != 1e-9 ||
lp->rtol != 1e-9 || lp->min_step != 1e-12 || lp->max_step != 2.0 ||
lp->max_lookback_time != 102.4 ||
lp->retry_lookback_increment != 102.4 ||
lp->max_total_lookback_time != 409.6 ||
lp->max_consecutive_rejections != 32 ||
lp->retry_step_increment != 64 || lp->max_total_steps != 256 ||
lp->coordinate_time_step != 0.1 || lp->initial_max_steps != 1024 ||
dloaded.frames[0].mesh.vertices[0].trace_accepted_steps != 11 ||
dloaded.frames[0].mesh.vertices[0].trace_rejected_steps != 2 ||
dloaded.frames[0].mesh.vertices[0].trace_rhs_evaluations != 79 ||
dloaded.frames[0].mesh.vertices[1].trace_accepted_steps != 5 ||
dloaded.frames[0].mesh.triangles[0].level != 1) {
fputs("lens-map v3 DP54 field round-trip regression failed\n", stderr);
lens_map_destroy(&dloaded); unlink(dp_path); goto done;
}
lens_map_destroy(&dloaded);
/* Unknown wire code and non-finite/out-of-bounds DP fields must be rejected
* by the shared schema validator, not accepted as a usable map. */
{
/* offset, is_double, double_value, u32_value */
const struct { long offset; int is_double; double dvalue; uint32_t uvalue; }
corruptions[4] = {{68, 0, 0.0, 7u}, /* unknown integrator code */
{100, 1, NAN, 0u}, /* atol_x = NaN */
{108, 1, -1.0, 0u}, /* atol_Pi below zero */
{156, 1, -1.0, 0u}}; /* max_lookback below zero */
for (size_t c = 0; c < 4; ++c) {
if (lens_map_write(dp_path, 4, 3, 30.0, &dp, &df, 1)) {
fputs("lens-map v3 corruption fixture write failed\n", stderr);
unlink(dp_path); goto done;
}
FILE *bad = fopen(dp_path, "r+b");
int bad_failed =
bad == NULL || fseek(bad, corruptions[c].offset, SEEK_SET);
if (!bad_failed) {
if (corruptions[c].is_double)
bad_failed = fwrite(&corruptions[c].dvalue,
sizeof(double), 1, bad) != 1;
else
bad_failed = fwrite(&corruptions[c].uvalue,
sizeof(uint32_t), 1, bad) != 1;
}
if (bad != NULL && fclose(bad)) bad_failed = 1;
if (bad_failed || !lens_map_read(dp_path, NULL, &dloaded)) {
fputs("lens-map v3 invalid DP54 field rejection regression failed\n",
stderr);
lens_map_destroy(&dloaded); unlink(dp_path); goto done;
}
lens_map_destroy(&dloaded);
}
}
unlink(dp_path);
}
/* Legacy v2 import: a real v2 map (no adaptive fields, no cost counters)
* must load as RK4 with an explicit zero adaptive policy and render
* identically to the live mesh. */
{
const char *v2_path = "/tmp/opencode/gr_lens_map_v2_legacy_test.grlens";
const LensMapProvenance v2p = {.threshold_kind = THRESHOLD_LOG_ALPHA_P0,
.threshold_policy_version = 1, .threshold_value = 8.0,
.retry_step_increment = 16, .max_total_steps = 64, .max_level = 2,
.integrator = (uint32_t)GEODESIC_STEPPER_RK4,
.min_edge_pixels = 0.5, .min_area_pixels2 = 0.25,
.coordinate_time_step = 0.1, .initial_max_steps = 4096};
const LensMapFrame v2f = {.frame_id = 7, .coordinate_time = 3.0,
.proper_time = 2.0, .mesh = mesh};
LensMap v2loaded = {0};
double *v2_hdr = calloc((size_t)width * height * 3, sizeof *v2_hdr);
double *live_hdr = calloc((size_t)width * height * 3, sizeof *live_hdr);
if (v2_hdr == NULL || live_hdr == NULL ||
write_v2_lens_map(v2_path, width, height, 30.0, &v2p, &v2f) ||
lens_map_read(v2_path, NULL, &v2loaded) ||
v2loaded.file_version != 2 ||
v2loaded.provenance.integrator != (uint32_t)GEODESIC_STEPPER_RK4 ||
v2loaded.provenance.atol_x != 0.0 ||
v2loaded.provenance.max_lookback_time != 0.0 ||
v2loaded.provenance.max_total_lookback_time != 0.0 ||
v2loaded.provenance.max_consecutive_rejections != 0 ||
v2loaded.frames[0].mesh.vertices[0].trace_accepted_steps != 0 ||
v2loaded.frames[0].mesh.vertices[0].trace_rhs_evaluations != 0) {
fputs("lens-map v2 legacy import regression failed\n", stderr);
free(v2_hdr); free(live_hdr); lens_map_destroy(&v2loaded);
unlink(v2_path); goto done;
}
const size_t live_images = frame_splat_catalog(
&mesh, &catalog, live_hdr, width, height, test_exposure, &psf, NULL,
INFINITY, 1.0, psf_relative_tail, 0.0, 0, 1,
FRAME_CATALOG_PREFETCH_FRAME, NULL, NULL, NULL, NULL, NULL);
const size_t v2_images = frame_splat_catalog(
&v2loaded.frames[0].mesh, &catalog, v2_hdr, width, height,
test_exposure, &psf, NULL, INFINITY, 1.0, psf_relative_tail, 0.0, 0, 1,
FRAME_CATALOG_PREFETCH_FRAME, NULL, NULL, NULL, NULL, NULL);
int render_equal = live_images == v2_images && live_images == images;
for (int k = 0; render_equal && k < width * height * 3; ++k)
if (live_hdr[k] != v2_hdr[k]) render_equal = 0;
free(v2_hdr); free(live_hdr);
lens_map_destroy(&v2loaded); unlink(v2_path);
if (!render_equal) {
fputs("lens-map v2 legacy render mismatch regression failed\n", stderr);
goto done;
}
}
result = 0;
done:
frame_lens_mesh_destroy(&mesh);
File diff suppressed because it is too large. Load diff
+60
View File
@@ -208,6 +208,59 @@ Movie rays from all frames share a newest-to-oldest coordinate-time sweep.
backends use logical slabs without metric I/O; [Nmesh](https://github.com/nmeshsource/nmesh) metric loading remains
future work.
## Geodesic integration and tracing budgets
The default `--integrator dp54` selects adaptive Dormand–Prince 5(4). Position, photon
direction/momentum, and log-energy errors have separate absolute tolerances:
```text
--ode-rtol R
--ode-atol-x X
--ode-atol-pi P
--ode-atol-l L
--ode-initial-step H
--ode-min-step HMIN
--ode-max-step HMAX
--ode-max-rejections N
```
All tolerance and step values must be finite and positive; the initial step
must lie between the step bounds. The position absolute tolerance has the
backend's coordinate-length units. Smaller tolerances control local ODE error,
not a guaranteed bound on final sky-direction or image error near critical rays.
Default minimum steps are `1e-12` in Minkowski and Schwarzschild (`M=1`), and
`1e-12 * R` in Alcubierre. This is a conservative numerical guard, not a measured
physical minimum. Default maximum steps are `16`, `8M`, and eight times
`min(0.1, 0.05/sigma)`, respectively. Schwarzschild starts at `0.1M`; increasing
the upper bound does not force large steps through strong-field regions.
Use tolerance convergence for near-critical rays rather than interpreting the
upper bound as a global accuracy guarantee. See the bounded
[step-bound experiments](benchmarks/adaptive_step_bounds_2026-10-05/README.md).
Tracing has independent accepted-step and coordinate-time budgets:
```text
--trace-max-steps N
--trace-lookback-time T
--retry-step-increment N
--max-total-steps N
--retry-lookback-increment T
--max-total-lookback-time T
```
Changing the accepted-step allowance does not change the time allowance. A
trustworthy ray that exhausts either allowance is unresolved and can be resumed
by refinement with additional resources; it is not a physical dark endpoint.
Retries keep the last accepted state and camera energy reference, without
relaxing numerical tolerances. A hard limit that prevents a required retry
blocks normal output; `--allow-incomplete` is a diagnostic override.
`--integrator rk4` retains the fixed-step comparison path. Adaptive-only
tolerances and time-budget overrides are not applicable to that path. Use an
explicit stepper when reproducing a fixed-step reference rather than relying
on the executable's default.
## Reuse a completed lens map
`--lens-map-output FILE` saves the finalized inverse-lens mesh after tracing
@@ -237,6 +290,13 @@ on import. Explicit conflicting dimensions or FOV are rejected. The reader
checks the format version, finite values, unit directions, triangle indices,
and per-frame CRCs.
The map also records the stepper, numerical tolerances and step bounds, tracing
and retry allowances, and accepted/rejected/RHS costs. Replay uses this saved
provenance; tracing-option overrides are rejected because replay does not
integrate rays. Version 3 preserves adaptive provenance. Version 2 imports as
fixed RK4 with unavailable adaptive fields and cost diagnostics; version 1 is
rejected because its capture semantics cannot be reconstructed reliably.
A movie export stores all final frame meshes in one file. To render it again,
pass `--lens-map-input FILE`, the catalog, `--frames-dir DIR`, and
`--frames-prefix NAME`; no observer track is needed on import.