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. # built for a different backend.
TEST_OUT_DIR := $(OBJECT_DIR)/$(HDR_BUILD_TAG) TEST_OUT_DIR := $(OBJECT_DIR)/$(HDR_BUILD_TAG)
TEST_TARGET := $(TEST_OUT_DIR)/test_geodesic 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_TEST_TARGET := $(TEST_OUT_DIR)/test_asymptotic
ASYMPTOTIC_SCHWARZSCHILD_TEST_TARGET := $(TEST_OUT_DIR)/test_asymptotic_schwarzschild ASYMPTOTIC_SCHWARZSCHILD_TEST_TARGET := $(TEST_OUT_DIR)/test_asymptotic_schwarzschild
TERMINATION_ORACLE_TEST_TARGET := $(TEST_OUT_DIR)/test_termination_oracle 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) $(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 $@ $(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) $(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 $@ $(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 := FAST_PSF_FFTW_TEST_RUN :=
endif 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_minkowski
$(TEST_OUT_DIR)/test_observer_schwarzschild $(TEST_OUT_DIR)/test_observer_schwarzschild
$(TEST_TARGET) $(TEST_TARGET)
$(ADAPTIVE_GEODESIC_TEST_TARGET)
$(ASYMPTOTIC_TEST_TARGET) $(ASYMPTOTIC_TEST_TARGET)
$(ASYMPTOTIC_SCHWARZSCHILD_TEST_TARGET) $(ASYMPTOTIC_SCHWARZSCHILD_TEST_TARGET)
$(TERMINATION_ORACLE_TEST_TARGET) $(TERMINATION_ORACLE_TEST_TARGET)
@@ -290,6 +298,7 @@ test: $(CAMERA_TEST_TARGETS) $(TEST_TARGET) $(ASYMPTOTIC_TEST_TARGET) $(ASYMPTOT
$(MOVIE_OUTPUT_TEST_TARGET) $(MOVIE_OUTPUT_TEST_TARGET)
$(SENSOR_BLOOM_TEST_TARGET) $(SENSOR_BLOOM_TEST_TARGET)
python3 tests/test_camera_cli.py $(BUILD_DIR) $(TEST_OUT_DIR) 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: $(TONE_MAP_TEST_TARGET)
$(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_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 FLOATDIFF_SCRIPT := scripts/fits_floatdiff.py
.PHONY: test-reference-images .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 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 $(MAKE) SPACETIME=minkowski ENABLE_HDR=1 backend
mkdir -p $(REFERENCE_TMP_DIR) 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 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 $(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 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 在域内不收敛; - 高精度 reference evaluator 在域内不收敛;
- 表在 seam 或 grazing 区域超过误差限。 - 表在 seam 或 grazing 区域超过误差限。
## 18A.10 当前实现状态 ## 18A.10 Backend 能力与路由约束
- 公共接口(end descriptor、escape worldtube sample、canonical photon state 与 - 公共接口以 end descriptor、escape worldtube sample、canonical photon state 与
endpoint `end_id`)已实现; endpoint `end_id` 分离远端声明、几何事件、状态转接与终态来源;
- $M=0$ 固定球与匀速移动球的 camera pre-route、directed inside -> outside - $M=0$ 固定球与匀速移动球的 camera pre-route、directed inside -> outside
crossing、`PENDING_ENTRY` 生命周期已实现,并接入 Minkowski 与 Alcubierre; crossing 与 `PENDING_ENTRY` 生命周期采用同一几何约定,适用于 Minkowski 与
- 单帧与 movie 共用同一 pre-route/生命周期实现;`make test` 全绿; Alcubierre 的平直外区;
- 单帧与 movie 必须共用同一 pre-route/生命周期实现;
- $M>0$ 固定同心 Schwarzschild monopole 外区以**解析 Carlson 椭圆积分**实现 - $M>0$ 固定同心 Schwarzschild monopole 外区以**解析 Carlson 椭圆积分**实现
(见 18A.11),接入解析 Kerr–Schild backend;相机位于 escape 球外的 (见 18A.11),接入解析 Kerr–Schild backend;相机位于 escape 球外的
pre-route、entry 传播、directed crossing 与 infinity endpoint 均可用; pre-route、entry 传播、directed crossing 与 infinity endpoint 均可用;
- 任意加速 worldtube 的 bracketed root driver 已实现并由 synthetic accelerated - worldtube 的运动能力必须显式声明。分段匀速、线性半径变化可在各段内求首次
worldtube 测试覆盖(含跨 motion-segment 与 history-exhausted failure crossing,并在段边界重新采样;任意加速运动在没有可信区间界时必须报告
semantics),但尚无生产 backend 使用该路径。该 driver 只检查离散端点的 `UNSUPPORTED`,不得把离散采样没有发现根解释为正常 miss。扩展加速运动能力需要
$F$ 符号,尚不能保证捕获一个步长内的窄进入;任意加速 backend 落地前需加入 同时提供可信的段内界或专用首次 crossing 解法。
段内速度/加速度界或自适应子区间搜索。 - `end_id` 贯通 endpoint、`LensVertex`、`terminal_mismatch` 与
- 多渐近远端路由尚未实现:`end_id` 已进入 endpoint、`LensVertex`、 `discrete_jacobian`,避免跨 end 插值;lens-map 保存
`terminal_mismatch` 与 `discrete_jacobian`,避免跨 end 插值;但 `end_id`。不同物理 region 的 reachable-end 路由及 catalog 绑定仍需独立定义,
`asymptotic_route_camera` 目前对多个 end 只处理第一个,lens-map 文件格式也 不能把能够保存 end 标识解释为已支持完整解析延拓。
未序列化 `end_id`。虫洞/最大延拓接入前需要补这些。
- 生命周期为三态:`NO_ENDS`(才允许 legacy)、`DIRECTED_READY`、 - 生命周期为三态:`NO_ENDS`(才允许 legacy)、`DIRECTED_READY`、
`PROTOCOL_ERROR`(descriptor 读取失败或不支持的 exterior,显式 `PROTOCOL_ERROR`(descriptor 读取失败或不支持的 exterior,显式
`INVALID/UNSUPPORTED`,绝不退回 legacy)。pre-route 也先校验所有 descriptor `INVALID/UNSUPPORTED`,绝不退回 legacy)。pre-route 也先校验所有 descriptor
与 exterior kind,再判定 worldtube 内外,因此“声明了不受支持 exterior 而相机 与 exterior kind,再判定 worldtube 内外,因此“声明了不受支持 exterior 而相机
恰在球内”不会被静默接受。 恰在球内”不会被静默接受。
- 内区积分时若 worldtube sample 变为 `valid == 0`(历史耗尽),作为一等 - 内区积分时若 worldtube sample 变为 `valid == 0`(历史耗尽),作为一等
terminal reason 返回 `RAY_ENDPOINT_TIME_RANGE_EXHAUSTED`、保留 `end_id`,并由 terminal reason 返回 `INCOMPLETE/TIME_RANGE_EXHAUSTED`、保留 `end_id`,并由
`RayPool` 记为 `RAY_POOL_TERMINATED`,与 pre-route 的耗尽语义一致。generic `RayPool` 记为 `RAY_POOL_TERMINATED`,与 pre-route 的耗尽语义一致。内区 crossing
accelerated driver 与内区 crossing localizer 的二分过程中任何 sample 失败都 localizer 定位过程中任何 sample 失败都直接传播具体状态,不返回一个正常 entry。
直接传播该具体状态,不返回一个正常 entry 或普通 integration failure。
- Minkowski entry quadratic 用稳定根公式($q=-\tfrac12(b+\mathrm{copysign} - Minkowski entry quadratic 用稳定根公式($q=-\tfrac12(b+\mathrm{copysign}
(\sqrt\Delta,b))$,取最小正根);$c=0$(相机在边界)时按 $b=\mathrm dF/ (\sqrt\Delta,b))$,取最小正根);$c=0$(相机在边界)时按 $b=\mathrm dF/
\mathrm ds$ 分类。worldtube sample 必须有限且 $R>0$,否则 `INVALID`。 \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 的内部表示 # 19. 恒星 catalog 的内部表示
不要预存 RGB。 不要预存 RGB。
+7 -108
View File
@@ -257,106 +257,6 @@ static int solve_entry_quadratic(const double d[3], const double q[3],
return 1; 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, static void minkowski_route_escaped(const SpacetimeAsymptoticEnd *end,
const double w_frame[3], const double w_frame[3],
SpacetimeEndId end_id, SpacetimeEndId end_id,
@@ -405,14 +305,13 @@ static AsymptoticStatus minkowski_preroute(
for (int i = 0; i < 3; ++i) for (int i = 0; i < 3; ++i)
x_cur[i] = x_frame[i] + w_frame[i] * s; x_cur[i] = x_frame[i] + w_frame[i] * s;
if (!sample.velocity_constant) { if (!sample.velocity_constant) {
double s_rel; /* An arbitrary accelerated worldtube has no strict interval bound on
status = minkowski_generic_first_entry(source, end, t, x_cur, w_frame, * its relative motion, so a bracketed sign search can silently miss a
&s_rel); * narrow first entry. Until such bounds exist, report it explicitly
if (status != ASYMPTOTIC_OK) * instead of fabricating a route; piecewise-constant motion (the
return status; * production backends) stays supported through the closed quadratic
minkowski_route_entry(end, t0, x_frame, w_frame, s + s_rel, end_id, * path below. */
route); return ASYMPTOTIC_UNSUPPORTED;
return ASYMPTOTIC_OK;
} }
double c_frame[3], v_frame[3], d[3], q[3]; double c_frame[3], v_frame[3], d[3], q[3];
backend_position_to_frame(end, sample.center, c_frame); backend_position_to_frame(end, sample.center, c_frame);
+215 -29
View File
@@ -9,6 +9,7 @@
#include "optics.h" #include "optics.h"
#include <float.h>
#include <limits.h> #include <limits.h>
#include <math.h> #include <math.h>
#include <omp.h> #include <omp.h>
@@ -102,6 +103,17 @@ int frame_lens_mesh_build_coarse(FrameLensMesh *mesh, int width, int height,
return 0; 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 /* 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` * 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 * 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->reason = endpoint->reason;
vertex->end_id = endpoint->end_id; vertex->end_id = endpoint->end_id;
vertex->traced = 1; 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) { if (endpoint->outcome == RAY_OUTCOME_ESCAPED) {
for (int axis = 0; axis < 3; ++axis) for (int axis = 0; axis < 3; ++axis)
vertex->n_infinity[axis] = endpoint->n_infinity[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 = endpoint->final_log_alpha_p0;
vertex->continuation_log_alpha_p0_0 = endpoint->final_log_alpha_p0_0; vertex->continuation_log_alpha_p0_0 = endpoint->final_log_alpha_p0_0;
vertex->continuation_steps = endpoint->accepted_steps; 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 = 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) if (used > vertex->continuation_limit)
vertex->continuation_limit = used; 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, int frame_lens_mesh_trace(FrameLensMesh *mesh, const SpacetimeSource *spacetime,
const ObserverState *observer, const ObserverState *observer,
const GeodesicTraceConfig *trace) { const GeodesicTraceConfig *trace) {
@@ -643,14 +710,77 @@ static size_t witness_find(const FrameLensMesh *mesh, size_t a, size_t b) {
return SIZE_MAX; return SIZE_MAX;
} }
static unsigned int retry_limit(unsigned int current, static unsigned int retry_step_limit(unsigned int current,
const RefinementConfig *config) { const RefinementConfig *config) {
const unsigned int remaining = config->max_total_steps - current; const unsigned int remaining = config->max_total_steps - current;
return current + (config->retry_step_increment < remaining return current + (config->retry_step_increment < remaining
? config->retry_step_increment ? config->retry_step_increment
: remaining); : 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, static int triangle_allows_children(const FrameLensMesh *mesh,
const LensTriangle *triangle, const LensTriangle *triangle,
const RefinementConfig *config); 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 * as is an off-mesh unresolved witness. A witness retry carries its edge so
* the refinement decision sees the updated state; both passes share * the refinement decision sees the updated state; both passes share
* retry_seen so a promoted witness is never requested twice. */ * 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); unsigned char *retry_seen = calloc(mesh->vertex_count, 1);
if (retry_seen == NULL) if (retry_seen == NULL)
return -1; return -1;
@@ -694,14 +826,14 @@ int frame_lens_mesh_prepare_generation(FrameLensMesh *mesh,
if (vertex->outcome != RAY_OUTCOME_UNRESOLVED || retry_seen[v]) if (vertex->outcome != RAY_OUTCOME_UNRESOLVED || retry_seen[v])
continue; continue;
retry_seen[v] = 1; retry_seen[v] = 1;
if (vertex->continuation_limit >= config->max_total_steps) unsigned int step_limit;
continue; /* capped: reported as budget-incomplete, not retried */ double time_limit;
const unsigned int limit = retry_limit(vertex->continuation_limit, config); if (!retry_budgets(vertex, config, &step_limit, &time_limit))
if (limit <= vertex->continuation_limit) continue; /* blocked quota at cap: reported as budget-incomplete */
continue;
FrameSample retry = {.kind = FRAME_SAMPLE_RETRY, FrameSample retry = {.kind = FRAME_SAMPLE_RETRY,
.vertex_id = v, .vertex_id = v,
.step_limit = limit, .step_limit = step_limit,
.lookback_limit = time_limit,
.vertex = *vertex}; .vertex = *vertex};
if (add_sample(mesh, &retry)) { if (add_sample(mesh, &retry)) {
free(retry_seen); free(retry_seen);
@@ -716,15 +848,16 @@ int frame_lens_mesh_prepare_generation(FrameLensMesh *mesh,
if (retry_seen[v] || vertex->outcome != RAY_OUTCOME_UNRESOLVED) if (retry_seen[v] || vertex->outcome != RAY_OUTCOME_UNRESOLVED)
continue; continue;
retry_seen[v] = 1; retry_seen[v] = 1;
if (vertex->continuation_limit >= config->max_total_steps) unsigned int step_limit;
continue; double time_limit;
const unsigned int limit = retry_limit(vertex->continuation_limit, config); if (!retry_budgets(vertex, config, &step_limit, &time_limit))
if (limit <= vertex->continuation_limit)
continue; continue;
FrameSample retry = {.kind = FRAME_SAMPLE_RETRY, .vertex_id = v, FrameSample retry = {.kind = FRAME_SAMPLE_RETRY, .vertex_id = v,
.edge_vertex = {vertex->probe_edge[0], .edge_vertex = {vertex->probe_edge[0],
vertex->probe_edge[1]}, vertex->probe_edge[1]},
.step_limit = limit, .vertex = *vertex}; .step_limit = step_limit,
.lookback_limit = time_limit,
.vertex = *vertex};
if (add_sample(mesh, &retry)) { if (add_sample(mesh, &retry)) {
free(retry_seen); free(retry_seen);
return -1; return -1;
@@ -994,6 +1127,43 @@ void frame_lens_mesh_boundary_stats(FrameLensMesh *mesh,
stats->retry_requests = mesh->retry_requests; 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) { static LensVertex midpoint_vertex(const LensVertex *a, const LensVertex *b) {
LensVertex result = {.image_x = 0.5 * (a->image_x + b->image_x), LensVertex result = {.image_x = 0.5 * (a->image_x + b->image_x),
.image_y = 0.5 * (a->image_y + b->image_y)}; .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 > (UINT_MAX / 4u) ? trace->max_steps
: trace->max_steps * 4u; : 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, int frame_lens_mesh_refine(FrameLensMesh *mesh,
@@ -1472,20 +1654,24 @@ int frame_lens_mesh_refine_with_progress(
if (sample->cached) continue; if (sample->cached) continue;
RayEndpoint endpoint; RayEndpoint endpoint;
if (sample->kind == FRAME_SAMPLE_RETRY) { if (sample->kind == FRAME_SAMPLE_RETRY) {
const LensVertex *v = &sample->vertex; GeodesicRayState state;
GeodesicRayState state = { if (frame_vertex_continuation_state(&sample->vertex, &state)) {
.coordinate_time = v->continuation_t, /* An UNRESOLVED request without a valid resume payload is a
.x = {v->continuation_x[0], v->continuation_x[1], * protocol error, never a silent discard. */
v->continuation_x[2]}, endpoint = (RayEndpoint){.magnification = 1.0,
.Pi = {v->continuation_Pi[0], v->continuation_Pi[1], .end_id = SPACETIME_END_NONE,
v->continuation_Pi[2]}, .outcome = RAY_OUTCOME_INCOMPLETE,
.log_alpha_p0 = v->continuation_log_alpha_p0, .reason = RAY_REASON_PROTOCOL_ERROR,
.log_alpha_p0_0 = v->continuation_log_alpha_p0_0, .stop_coordinate_time = NAN,
.steps = v->continuation_steps}; .threshold_value = NAN};
GeodesicTraceConfig retry_config = *trace; } else {
retry_config.max_steps = sample->step_limit; GeodesicTraceConfig retry_config = *trace;
endpoint = geodesic_trace_past_from_state(spacetime, &state, retry_config.max_steps = sample->step_limit;
&retry_config); 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 { } else {
endpoint = geodesic_trace_past(spacetime, observer, endpoint = geodesic_trace_past(spacetime, observer,
sample->vertex.camera_direction, trace); sample->vertex.camera_direction, trace);
+55
View File
@@ -8,6 +8,7 @@
#include "spacetime.h" #include "spacetime.h"
#include <stddef.h> #include <stddef.h>
#include <stdint.h>
typedef struct { typedef struct {
double image_x, image_y; double image_x, image_y;
@@ -29,10 +30,31 @@ typedef struct {
double continuation_log_alpha_p0_0; double continuation_log_alpha_p0_0;
unsigned int continuation_steps; unsigned int continuation_steps;
unsigned int continuation_limit; 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. /* Persistent off-mesh probe witness; also participates in completion checks.
* When set, probe_edge holds the sorted edge (a,b) this witness samples. */ * When set, probe_edge holds the sorted edge (a,b) this witness samples. */
int diagnostic_probe; int diagnostic_probe;
size_t probe_edge[2]; 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; } LensVertex;
typedef struct { typedef struct {
@@ -77,6 +99,13 @@ typedef struct {
* budget-incomplete instead of silently blackened. */ * budget-incomplete instead of silently blackened. */
unsigned int retry_step_increment; unsigned int retry_step_increment;
unsigned int max_total_steps; 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; } RefinementConfig;
typedef enum { typedef enum {
@@ -91,6 +120,9 @@ typedef struct {
size_t edge_vertex[2]; size_t edge_vertex[2];
/* For FRAME_SAMPLE_RETRY: the new total accepted-step budget. */ /* For FRAME_SAMPLE_RETRY: the new total accepted-step budget. */
unsigned int step_limit; 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; int cached;
LensVertex vertex; LensVertex vertex;
} FrameSample; } FrameSample;
@@ -116,6 +148,19 @@ typedef struct {
size_t budget_incomplete_triangles; size_t budget_incomplete_triangles;
} FrameBoundaryStats; } 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 { typedef struct {
LensVertex *vertices; LensVertex *vertices;
LensTriangle *triangles; LensTriangle *triangles;
@@ -188,6 +233,12 @@ const FrameSample *frame_lens_mesh_samples(const FrameLensMesh *mesh,
size_t *count); size_t *count);
int frame_lens_mesh_install_sample(FrameLensMesh *mesh, size_t sample_id, int frame_lens_mesh_install_sample(FrameLensMesh *mesh, size_t sample_id,
const RayEndpoint *endpoint); 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 /* Applies a nonempty completed generation; callers skip frames for which
* prepare_generation returned zero. Returns the number of newly added * prepare_generation returned zero. Returns the number of newly added
* vertices, zero when no topology change was made, or -1 on failure. */ * 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, void frame_lens_mesh_boundary_stats(FrameLensMesh *mesh,
const RefinementConfig *config, const RefinementConfig *config,
FrameBoundaryStats *stats); 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 /* Fill retry defaults (derived from the trace step budget) when the caller
* did not configure them explicitly. A zero max_total_steps disables retry * did not configure them explicitly. A zero max_total_steps disables retry
* only if retry_step_increment is also zero. */ * 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 /* Last trusted state at termination. For an ESCAPED endpoint this is the
* (finite) numerical truncation position, not the true parameter end. */ * (finite) numerical truncation position, not the true parameter end. */
double stop_coordinate_time; 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 trusted continuous state, used to resume an UNRESOLVED ray from its
* last accepted step instead of replaying it from the camera. */ * last accepted step instead of replaying it from the camera. */
double final_x[3]; double final_x[3];
@@ -65,14 +71,73 @@ typedef struct {
double final_log_alpha_p0_0; /* original reference L0 for retries */ double final_log_alpha_p0_0; /* original reference L0 for retries */
/* Monitored threshold value at termination, NAN when not applicable. */ /* Monitored threshold value at termination, NAN when not applicable. */
double threshold_value; 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; } 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 { typedef struct {
double coordinate_time_step; double coordinate_time_step;
unsigned int max_steps; unsigned int max_steps;
/* Normal dark terminal for every backend. No backend may substitute a /* Normal dark terminal for every backend. No backend may substitute a
* position/horizon cutoff for physical capture. */ * position/horizon cutoff for physical capture. */
ThresholdPolicy threshold; 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; } GeodesicTraceConfig;
typedef struct { typedef struct {
@@ -83,7 +148,19 @@ typedef struct {
/* Reference L at the start of this ray's integration, carried unchanged /* Reference L at the start of this ray's integration, carried unchanged
* through retries so THRESHOLD_LOG_ENERGY_GROWTH stays camera-relative. */ * through retries so THRESHOLD_LOG_ENERGY_GROWTH stays camera-relative. */
double log_alpha_p0_0; 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; } GeodesicRayState;
typedef enum { typedef enum {
+119 -17
View File
@@ -11,9 +11,15 @@
* because their padding and size_t width are ABI-dependent. */ * 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}; 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 /* Version 2 stores the two-level RayOutcome instead of the removed
* RayEndpointStatus. Version 1 files are rejected: their old captured bit * RayEndpointStatus. Version 3 appends the adaptive DP54 policy and the
* cannot be upgraded into the new dark/unresolved/error provenance. */ * per-vertex integration cost; it still supports v2 import as a legacy
enum { LENS_MAP_VERSION = 2, LENS_MAP_ENDIAN = 0x01020304u }; * 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) { static uint32_t crc32_update(uint32_t crc, const void *data, size_t size) {
const unsigned char *bytes = data; 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]; const double n2 = v[0]*v[0] + v[1]*v[1] + v[2]*v[2];
return isfinite(n2) && fabs(n2 - 1.0) <= 1e-9; 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) { static int valid_mesh(const FrameLensMesh *m) {
if (m == NULL || m->vertex_count == 0 || m->triangle_count == 0) return 0; if (m == NULL || m->vertex_count == 0 || m->triangle_count == 0) return 0;
for (size_t i = 0; i < m->vertex_count; ++i) { 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) { const LensMapFrame *frames, size_t frame_count) {
if (path == NULL || provenance == NULL || frames == NULL || width <= 0 || if (path == NULL || provenance == NULL || frames == NULL || width <= 0 ||
height <= 0 || !isfinite(fov) || fov <= 0.0 || fov >= 179.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; return -1;
for (size_t i = 0; i < frame_count; ++i) if (!valid_mesh(&frames[i].mesh)) 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; 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_edge_pixels, NULL) ||
write_double(file, provenance->min_area_pixels2, NULL) || write_double(file, provenance->min_area_pixels2, NULL) ||
write_double(file, provenance->coordinate_time_step, 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) { for (size_t f = 0; !failed && f < frame_count; ++f) {
const FrameLensMesh *m = &frames[f].mesh; uint32_t crc = UINT32_MAX; const FrameLensMesh *m = &frames[f].mesh; uint32_t crc = UINT32_MAX;
failed = write_u64(file, frames[f].frame_id, NULL) || 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) || 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->end_id, &crc) ||
write_u32(file, (uint32_t)v->outcome, &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 (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); 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; *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; unsigned char magic[8]; uint32_t version, endian, width, height; uint64_t count;
LensMapProvenance prov = {0}; 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, &version, NULL) || read_u32(file, &endian, NULL) ||
read_u32(file, &width, NULL) || read_u32(file, &height, 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_double(file, &map->horizontal_fov_deg, NULL) || read_u64(file, &count, NULL);
read_u32(file, &prov.threshold_kind, 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_u32(file, &prov.threshold_policy_version, NULL) ||
read_double(file, &prov.threshold_value, NULL) || read_double(file, &prov.threshold_value, NULL) ||
read_u32(file, &prov.retry_step_increment, 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_edge_pixels, NULL) ||
read_double(file, &prov.min_area_pixels2, NULL) || read_double(file, &prov.min_area_pixels2, NULL) ||
read_double(file, &prov.coordinate_time_step, NULL) || read_double(file, &prov.coordinate_time_step, NULL) ||
read_u32(file, &prov.initial_max_steps, NULL) || read_u32(file, &prov.initial_max_steps, NULL);
version != LENS_MAP_VERSION || endian != LENS_MAP_ENDIAN || width == 0 || height == 0 || if (!failed && version == LENS_MAP_VERSION) {
width > INT32_MAX || height > INT32_MAX || !isfinite(map->horizontal_fov_deg) || failed = read_double(file, &prov.atol_x, NULL) ||
map->horizontal_fov_deg <= 0.0 || map->horizontal_fov_deg >= 179.0 || count == 0 || read_double(file, &prov.atol_Pi, NULL) ||
prov.threshold_kind > THRESHOLD_LOG_ENERGY_GROWTH || read_double(file, &prov.atol_L, NULL) ||
!isfinite(prov.threshold_value) || read_double(file, &prov.rtol, NULL) ||
prov.coordinate_time_step < 0.0 || !isfinite(prov.coordinate_time_step) || read_double(file, &prov.min_step, NULL) ||
count > SIZE_MAX / sizeof *map->frames; 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; if (failed) goto done;
map->provenance = prov; map->provenance = prov;
map->file_version = version;
if (provenance != NULL) if (provenance != NULL)
*provenance = prov; *provenance = prov;
map->width = (int)width; map->height = (int)height; map->frame_count = (size_t)count; 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->outcome = (RayOutcome)outcome;
v->reason = (RayReason)reason; v->reason = (RayReason)reason;
v->traced = 1; 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 (size_t i = 0; !failed && i < frame->mesh.triangle_count; ++i) {
for (int j = 0; j < 3; ++j) { for (int j = 0; j < 3; ++j) {
+28 -1
View File
@@ -25,19 +25,46 @@ typedef struct {
uint32_t retry_step_increment; uint32_t retry_step_increment;
uint32_t max_total_steps; uint32_t max_total_steps;
uint32_t max_level; 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_edge_pixels;
double min_area_pixels2; double min_area_pixels2;
/* Integration source: the coordinate-time step and the initial per-ray /* Integration source: the coordinate-time step and the initial per-ray
* accepted-step budget used for the first trace. */ * accepted-step budget used for the first trace. */
double coordinate_time_step; double coordinate_time_step;
uint32_t initial_max_steps; 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; } LensMapProvenance;
typedef struct { typedef struct {
int width, height; int width, height;
double horizontal_fov_deg; double horizontal_fov_deg;
LensMapProvenance provenance; 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; LensMapFrame *frames;
size_t frame_count; size_t frame_count;
} LensMap; } LensMap;
+478 -63
View File
@@ -10,6 +10,8 @@
#include "spacetime.h" #include "spacetime.h"
#include <errno.h> #include <errno.h>
#include <float.h>
#include <inttypes.h>
#include <limits.h> #include <limits.h>
#include <math.h> #include <math.h>
#include <omp.h> #include <omp.h>
@@ -67,6 +69,24 @@ typedef struct {
const char *fast_fftw_wisdom_path; const char *fast_fftw_wisdom_path;
const char *blackbody_table_path; const char *blackbody_table_path;
RefinementConfig refinement; 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; ToneMapSettings tone_map;
PngWriteSettings png; PngWriteSettings png;
int sensor_bloom_enabled; int sensor_bloom_enabled;
@@ -76,6 +96,15 @@ typedef struct {
double sensor_bloom_transfer; double sensor_bloom_transfer;
} Settings; } 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) { static int parse_int(const char *text, int *value) {
char *end; char *end;
errno = 0; 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; 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). */ /* Sensor-bloom transfer coefficient: finite and in [0, 1). */
static int parse_sensor_bloom_transfer(const char *text, double *value) { static int parse_sensor_bloom_transfer(const char *text, double *value) {
char *end; char *end;
@@ -406,6 +456,7 @@ static int parse_args(int argc, char **argv, Settings *s,
.catalog_load_workers = 4, .catalog_load_workers = 4,
.movie_output_workers = 0, .movie_output_workers = 0,
.fast_fftw_plan_mode = FAST_PSF_FFTW_PLAN_ESTIMATE, .fast_fftw_plan_mode = FAST_PSF_FFTW_PLAN_ESTIMATE,
.stepper = default_stepper(),
.refinement = {.angle_absolute_rad = .refinement = {.angle_absolute_rad =
1e-3 * 3.14159265358979323846 / 180.0, 1e-3 * 3.14159265358979323846 / 180.0,
.angle_relative = 0.1, .angle_relative = 0.1,
@@ -474,6 +525,60 @@ static int parse_args(int argc, char **argv, Settings *s,
!parse_positive(argv[++i], &s->exposure)) { !parse_positive(argv[++i], &s->exposure)) {
} else if (!strcmp(argv[i], "--dark-threshold") && i + 1 < argc && } else if (!strcmp(argv[i], "--dark-threshold") && i + 1 < argc &&
!parse_positive(argv[++i], &s->dark_threshold)) { !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) { } else if (!strcmp(argv[i], "--tone-map") && i + 1 < argc) {
const char *mode = argv[++i]; const char *mode = argv[++i];
if (!strcmp(mode, "softclip")) if (!strcmp(mode, "softclip"))
@@ -615,7 +720,7 @@ static int parse_args(int argc, char **argv, Settings *s,
return -1; return -1;
} }
s->sensor_bloom_enabled = s->sensor_bloom_limit_specified; s->sensor_bloom_enabled = s->sensor_bloom_limit_specified;
return 0; return validate_settings(s);
} }
static void print_help(const char *program) { static void print_help(const char *program) {
@@ -719,6 +824,26 @@ static void print_help(const char *program) {
#endif #endif
fputs(" --allow-incomplete Publish even when unresolved/error rays remain (diagnostic; output is marked incomplete)\n", fputs(" --allow-incomplete Publish even when unresolved/error rays remain (diagnostic; output is marked incomplete)\n",
stdout); 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 #ifdef SPACETIME_ALCUBIERRE
fputs( fputs(
"\nAlcubierre warp bubble (moving x_s(t)=v_s*t; no capture):\n" "\nAlcubierre warp bubble (moving x_s(t)=v_s*t; no capture):\n"
@@ -851,9 +976,10 @@ static void report_frame_refinement(void *context, size_t generation,
} }
#ifdef SPACETIME_ALCUBIERRE #ifdef SPACETIME_ALCUBIERRE
/* Upper bound on the per-ray step budget. Legal parameters whose worst-case /* Resource cap on the accepted-step estimate for the legacy fixed-step
* near-comoving ray could need more than this are rejected at startup rather * Alcubierre domain guard. It bounds a resource allowance only; the adaptive
* than silently terminating as UNRESOLVED/BUDGET_EXHAUSTED. */ * 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) #define ALCUBIERRE_MAX_TRACE_STEPS (1u << 24)
/* Safety margin over the straight-line worst case: wall-region deflection can /* 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); return fmin(0.1, 0.05 / s->alcubierre_sigma);
} }
/* Worst-case per-ray step budget, including ALCUBIERRE_BUDGET_MARGIN. A ray /* Coordinate-time coverage a past-directed Alcubierre ray must be granted.
* that is nearly comoving with the bubble separates from its center in the * A ray that is nearly comoving with the bubble separates from its center in
* propagation direction at only ~1 - |v_s|, so crossing the ~4*escape domain * the propagation direction at only ~1 - |v_s|, so crossing the ~4*escape
* can take ~4*escape/(1-|v_s|) in coordinate time. trace_config() and the * domain takes up to ~4*escape/(1 - |v_s|), scaled by ALCUBIERRE_BUDGET_MARGIN
* startup rejection share this single value so the configured limit always * for lingering in the wall. This is a physical budget derived from the bubble
* carries the full margin when it is accepted. */ * geometry and the asymptotic separation rate; it is independent of the chosen
static double alcubierre_step_budget(const Settings *s) { * step size and of the accepted-step count. */
static double alcubierre_trace_time_budget(const Settings *s) {
const double escape = const double escape =
spacetime_alcubierre_escape_radius(s->alcubierre_radius, spacetime_alcubierre_escape_radius(s->alcubierre_radius,
s->alcubierre_sigma); s->alcubierre_sigma);
const double separation = 1.0 - fabs(s->alcubierre_vs); const double separation = 1.0 - fabs(s->alcubierre_vs);
return ALCUBIERRE_BUDGET_MARGIN * 4.0 * escape / return ALCUBIERRE_BUDGET_MARGIN * 4.0 * escape / separation;
(separation * alcubierre_time_step(s)); }
/* 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 #endif
/* Recompute approximate-black provenance and report E/D/U accounting. The /* Recompute per-triangle approximate-black provenance and aggregate the E/D/U
* result is diagnostic for now; production failure gating is layered on top * accounting across the given frames. The caller uses the returned totals both
* of the same counters. */ * for the verbose report and for boundary_allows_publish() production gating. */
static void report_boundary_stats(const Settings *s, static void report_boundary_stats(const Settings *s,
FrameLensMesh *const *meshes, FrameLensMesh *const *meshes,
size_t frame_count, size_t frame_count,
@@ -927,10 +1061,50 @@ static void report_boundary_stats(const Settings *s,
total.approx_black_level_stops); 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, static LensMapProvenance lens_map_provenance(const Settings *s,
const GeodesicTraceConfig *trace) { const GeodesicTraceConfig *trace) {
RefinementConfig rc = s->refinement; const RefinementConfig rc = effective_refinement(s, trace);
frame_retry_config_defaults(&rc, 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, return (LensMapProvenance){.threshold_kind = (uint32_t)trace->threshold.kind,
.threshold_policy_version = .threshold_policy_version =
trace->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, .retry_step_increment = rc.retry_step_increment,
.max_total_steps = rc.max_total_steps, .max_total_steps = rc.max_total_steps,
.max_level = rc.max_level, .max_level = rc.max_level,
.integrator = 0, .integrator = (uint32_t)trace->stepper,
.min_edge_pixels = rc.min_edge_pixels, .min_edge_pixels = rc.min_edge_pixels,
.min_area_pixels2 = rc.min_area_pixels2, .min_area_pixels2 = rc.min_area_pixels2,
.coordinate_time_step = trace->coordinate_time_step, .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 /* 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, const ThresholdPolicy threshold = {.kind = THRESHOLD_LOG_ENERGY_GROWTH,
.value = s->dark_threshold, .value = s->dark_threshold,
.policy_version = 3}; .policy_version = 3};
#ifdef SPACETIME_SCHWARZSCHILD /* Per-backend integration policy. `default_step` is the initial DP54 trial
/* The directed worldtube crossing makes an escaping ray traverse the * step (and RK4's fixed step on the legacy comparison path), `default_budget`
* interior as a round trip from the entry sphere (in, turn, back out), * is the initial accepted-step resource allowance, `length_unit` scales the
* rather than the old one-way stop at the first outside sample. The step * position tolerance and minimum step, and `default_max_step` bounds the DP54
* budget must cover roughly twice the escape sphere plus margin. */ * trial step. `default_lookback` is the trusted coordinate-time coverage: it
return (GeodesicTraceConfig){.coordinate_time_step = 0.1, * is an explicit constant or a geometry-derived physical time, never the
.max_steps = 65536, * product of the chosen step and the chosen step count. The directed
.threshold = threshold}; * 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) #elif defined(SPACETIME_ALCUBIERRE)
const double step = alcubierre_time_step(s); default_step = alcubierre_time_step(s);
const double budget = alcubierre_step_budget(s); {
unsigned max_steps = ALCUBIERRE_MAX_TRACE_STEPS; /* Accepted-step resource estimate along the legacy fixed-step policy. It
if (budget < (double)max_steps && isfinite(budget)) * may be capped at ALCUBIERRE_MAX_TRACE_STEPS; the coordinate-time coverage
max_steps = (unsigned)ceil(budget); * below is what guarantees the physical budget, so a capped count never
if (max_steps < 1024u) * shortens the trusted history. */
max_steps = 1024u; const double estimated_steps = alcubierre_step_budget(s);
return (GeodesicTraceConfig){.coordinate_time_step = step, unsigned int budget = ALCUBIERRE_MAX_TRACE_STEPS;
.max_steps = max_steps, if (isfinite(estimated_steps) && estimated_steps < (double)budget)
.threshold = threshold}; 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 #else
return (GeodesicTraceConfig){.coordinate_time_step = 1.0, default_step = 1.0;
.max_steps = 2048, default_budget = 2048;
.threshold = threshold}; length_unit = 1.0;
default_max_step = 16.0;
/* Fixed-step physical coverage: 1.0 * 2048. */
default_lookback = 2048.0;
#endif #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) { static int resolve_camera(Settings *s) {
@@ -1098,6 +1462,8 @@ static int render_observer_frame(const Settings *s, StarCatalog *catalog,
const ObserverState *observer, const ObserverState *observer,
const char *output_path) { const char *output_path) {
const GeodesicTraceConfig trace = trace_config(s); const GeodesicTraceConfig trace = trace_config(s);
const RefinementConfig refinement_config = effective_refinement(s, &trace);
report_trace_config(s, &trace, "Frame 0");
FrameLensMesh mesh = {0}; FrameLensMesh mesh = {0};
double *hdr = calloc((size_t)s->width * s->height * 3, sizeof *hdr); 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, 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); s->refinement.max_level);
const double refinement_start = omp_get_wtime(); const double refinement_start = omp_get_wtime();
if (frame_lens_mesh_refine_with_progress( 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)) { s->verbose ? report_frame_refinement : NULL, (void *)s)) {
frame_lens_mesh_destroy(&mesh); frame_lens_mesh_destroy(&mesh);
free(hdr); free(hdr);
@@ -1137,11 +1503,13 @@ static int render_observer_frame(const Settings *s, StarCatalog *catalog,
omp_get_wtime() - refinement_start, mesh.vertex_count, omp_get_wtime() - refinement_start, mesh.vertex_count,
mesh.triangle_count); mesh.triangle_count);
FrameBoundaryStats boundary_totals; FrameBoundaryStats boundary_totals;
FrameLensMesh *frame_meshes[1] = {&mesh};
report_boundary_stats(s, frame_meshes, 1, &refinement_config,
&boundary_totals);
{ {
RefinementConfig boundary_config = s->refinement; FrameTraceStats trace_stats;
frame_retry_config_defaults(&boundary_config, &trace); frame_lens_mesh_trace_stats(&mesh, &trace_stats);
FrameLensMesh *meshes[1] = {&mesh}; report_trace_stats(s, &trace_stats, "Frame 0");
report_boundary_stats(s, meshes, 1, &boundary_config, &boundary_totals);
} }
if (!boundary_allows_publish(s, &boundary_totals)) { if (!boundary_allows_publish(s, &boundary_totals)) {
frame_lens_mesh_destroy(&mesh); frame_lens_mesh_destroy(&mesh);
@@ -1230,8 +1598,7 @@ static int trace_movie_generation(Movie *movie, const Settings *s,
RayPool rays = {0}; RayPool rays = {0};
size_t ray_count = 0; size_t ray_count = 0;
size_t total_added = 0; size_t total_added = 0;
RefinementConfig effective = s->refinement; RefinementConfig effective = effective_refinement(s, trace);
frame_retry_config_defaults(&effective, trace);
for (size_t f = 0; f < movie->frame_count; ++f) { for (size_t f = 0; f < movie->frame_count; ++f) {
const int prepared = frame_lens_mesh_prepare_generation( const int prepared = frame_lens_mesh_prepare_generation(
&movie->frames[f].mesh, &effective); &movie->frames[f].mesh, &effective);
@@ -1252,12 +1619,14 @@ static int trace_movie_generation(Movie *movie, const Settings *s,
if (fs->cached) continue; if (fs->cached) continue;
int rc; int rc;
if (fs->kind == FRAME_SAMPLE_RETRY) { if (fs->kind == FRAME_SAMPLE_RETRY) {
const LensVertex *v = &fs->vertex; GeodesicRayState state;
rc = ray_pool_append_continuation( if (frame_vertex_continuation_state(&fs->vertex, &state)) {
&rays, f, sample, v->continuation_t, v->continuation_x, ray_pool_destroy(&rays);
v->continuation_Pi, v->continuation_log_alpha_p0, return -1;
v->continuation_log_alpha_p0_0, v->continuation_steps, }
fs->step_limit); rc = ray_pool_append_continuation_state(&rays, f, sample, &state,
fs->step_limit,
fs->lookback_limit);
} else { } else {
rc = ray_pool_append(&rays, &movie->frames[f].observer, rc = ray_pool_append(&rays, &movie->frames[f].observer,
fs->vertex.camera_direction, f, sample); fs->vertex.camera_direction, f, sample);
@@ -1454,6 +1823,7 @@ static int render_movie(const Settings *s, StarCatalog *catalog,
ObserverTrack track = {0}; ObserverTrack track = {0};
Movie movie = {0}; Movie movie = {0};
const GeodesicTraceConfig trace = trace_config(s); const GeodesicTraceConfig trace = trace_config(s);
report_trace_config(s, &trace, "Movie");
MovieOutputQueue output_queue; MovieOutputQueue output_queue;
int output_queue_ready = 0; int output_queue_ready = 0;
int result = -1; int result = -1;
@@ -1478,8 +1848,7 @@ static int render_movie(const Settings *s, StarCatalog *catalog,
break; break;
} }
{ {
RefinementConfig boundary_config = s->refinement; const RefinementConfig boundary_config = effective_refinement(s, &trace);
frame_retry_config_defaults(&boundary_config, &trace);
FrameLensMesh **meshes = malloc(movie.frame_count * sizeof *meshes); FrameLensMesh **meshes = malloc(movie.frame_count * sizeof *meshes);
if (meshes == NULL) if (meshes == NULL)
goto done; goto done;
@@ -1488,6 +1857,10 @@ static int render_movie(const Settings *s, StarCatalog *catalog,
FrameBoundaryStats boundary_totals; FrameBoundaryStats boundary_totals;
report_boundary_stats(s, meshes, movie.frame_count, &boundary_config, report_boundary_stats(s, meshes, movie.frame_count, &boundary_config,
&boundary_totals); &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); free(meshes);
if (!boundary_allows_publish(s, &boundary_totals)) if (!boundary_allows_publish(s, &boundary_totals))
goto done; goto done;
@@ -1623,17 +1996,40 @@ done:
} }
static int render_lens_map(const Settings *s, StarCatalog *catalog) { 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}; LensMap map = {0};
if (lens_map_read(s->lens_map_input_path, NULL, &map)) { 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); fprintf(stderr, "Failed to read or validate lens map: %s\n", s->lens_map_input_path);
return -1; 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 = { RefinementConfig replay_config = {
.max_level = map.provenance.max_level, .max_level = map.provenance.max_level,
.min_edge_pixels = map.provenance.min_edge_pixels, .min_edge_pixels = map.provenance.min_edge_pixels,
.min_area_pixels2 = map.provenance.min_area_pixels2, .min_area_pixels2 = map.provenance.min_area_pixels2,
.retry_step_increment = map.provenance.retry_step_increment, .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; int replay_incomplete = 0;
/* Replay consumes stored decisions, not current CLI refinement defaults. */ /* Replay consumes stored decisions, not current CLI refinement defaults. */
for (size_t f = 0; f < map.frame_count; ++f) { 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; 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) || if ((s->width_specified && s->width != map.width) ||
(s->height_specified && s->height != map.height) || (s->height_specified && s->height != map.height) ||
(s->fov_specified && fabs(s->horizontal_fov_deg - map.horizontal_fov_deg) > 1e-12)) { (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-jacobian-min J "
"--refine-min-edge-pixels P --refine-min-area-pixels2 A] " "--refine-min-edge-pixels P --refine-min-area-pixels2 A] "
"[--draw-mesh] [--write-catalog PATH] " "[--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] " "[--catalog-load-workers N] "
"[--observer-track PATH --frames-dir DIR --frames-prefix NAME " "[--observer-track PATH --frames-dir DIR --frames-prefix NAME "
"--start-time T --duration T --fps N | --movie-track-samples] " "--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); "R > 0, sigma > 0.\n", stderr);
return 1; return 1;
} }
if (alcubierre_step_budget(&settings) > /* The fixed-step RK4 comparison path sizes its history from
(double)ALCUBIERRE_MAX_TRACE_STEPS) { * step * accepted-step count, so its domain guard still rejects an
/* The budget has a V-shaped minimum at sigma = 0.5, where the step * 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 * stops being capped: below it the 20/sigma term dominates (increase
* sigma helps), above it the step scales as 1/sigma (decrease sigma * sigma helps), above it the step scales as 1/sigma (decrease sigma
* helps), and at exactly 0.5 neither direction improves anything. */ * 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) else if (settings.alcubierre_sigma < 0.5)
sigma_advice = "increase --alcubierre-sigma, "; sigma_advice = "increase --alcubierre-sigma, ";
fprintf(stderr, fprintf(stderr,
"Alcubierre trace budget exceeds the %u-step cap; decrease " "Fixed-step RK4 Alcubierre trace budget exceeds the %u-step "
"--alcubierre-radius, %sor move --alcubierre-vs away from " "cap; decrease --alcubierre-radius, %sor move --alcubierre-vs "
"+/-1.\n", "away from +/-1, or use the adaptive default.\n",
ALCUBIERRE_MAX_TRACE_STEPS, sigma_advice); ALCUBIERRE_MAX_TRACE_STEPS, sigma_advice);
spacetime_destroy(&spacetime); spacetime_destroy(&spacetime);
return 2; 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(direction0) && RAY_ALLOC(direction1) && RAY_ALLOC(direction2) &&
RAY_ALLOC(log_alpha_p0) && RAY_ALLOC(log_alpha_p0_0) && RAY_ALLOC(log_alpha_p0) && RAY_ALLOC(log_alpha_p0_0) &&
RAY_ALLOC(activate_t) && RAY_ALLOC(steps) && 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(frame_id) && RAY_ALLOC(vertex_id) && RAY_ALLOC(status) &&
RAY_ALLOC(endpoint))) { RAY_ALLOC(endpoint))) {
ray_pool_destroy(p); ray_pool_destroy(p);
@@ -55,30 +58,45 @@ int ray_pool_append(RayPool *p, const ObserverState *observer,
.final_log_alpha_p0_0 = NAN, .final_log_alpha_p0_0 = NAN,
.threshold_value = NAN}; .threshold_value = NAN};
p->step_limit[i] = 0; 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->continuation[i] = 0;
p->log_alpha_p0_0[i] = 0.0; p->log_alpha_p0_0[i] = 0.0;
++p->count; ++p->count;
return 0; return 0;
} }
int ray_pool_append_continuation(RayPool *p, size_t frame_id, int ray_pool_append_continuation_state(RayPool *p, size_t frame_id,
size_t vertex_id, double t, const double x[3], size_t vertex_id,
const double Pi[3], double log_alpha_p0, const GeodesicRayState *state,
double log_alpha_p0_0, unsigned int steps, unsigned int accepted_limit,
unsigned int limit) { double lookback_limit) {
if (p == NULL || p->count == p->capacity || x == NULL || Pi == NULL) if (p == NULL || p->count == p->capacity || state == NULL)
return -1; return -1;
const size_t i = p->count; const size_t i = p->count;
p->t[i] = t; p->t[i] = state->coordinate_time;
p->activate_t[i] = t; p->activate_t[i] = state->coordinate_time;
p->observer[i] = NULL; p->observer[i] = NULL;
p->direction0[i] = p->direction1[i] = p->direction2[i] = 0.0; 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->x0[i] = state->x[0]; p->x1[i] = state->x[1]; p->x2[i] = state->x[2];
p->p0[i] = Pi[0]; p->p1[i] = Pi[1]; p->p2[i] = Pi[2]; p->p0[i] = state->Pi[0]; p->p1[i] = state->Pi[1]; p->p2[i] = state->Pi[2];
p->log_alpha_p0[i] = log_alpha_p0; p->log_alpha_p0[i] = state->log_alpha_p0;
p->log_alpha_p0_0[i] = log_alpha_p0_0; p->log_alpha_p0_0[i] = state->log_alpha_p0_0;
p->steps[i] = steps; p->steps[i] = state->steps;
p->step_limit[i] = limit; 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->continuation[i] = 1;
p->frame_id[i] = frame_id; p->frame_id[i] = frame_id;
p->vertex_id[i] = vertex_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, .outcome = RAY_OUTCOME_INCOMPLETE,
.reason = RAY_REASON_NONE, .reason = RAY_REASON_NONE,
.stop_coordinate_time = NAN, .stop_coordinate_time = NAN,
.accepted_steps = steps, .accepted_steps = state->steps,
.accepted_step_limit = accepted_limit,
.final_x = {NAN, NAN, NAN}, .final_x = {NAN, NAN, NAN},
.final_Pi = {NAN, NAN, NAN}, .final_Pi = {NAN, NAN, NAN},
.final_log_alpha_p0 = 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}; .threshold_value = NAN};
++p->count; ++p->count;
return 0; 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) { void ray_pool_preroute(RayPool *p, const SpacetimeSource *source) {
if (p == NULL || source == NULL) if (p == NULL || source == NULL)
return; return;
@@ -151,6 +187,14 @@ void ray_pool_preroute(RayPool *p, const SpacetimeSource *source) {
continue; continue;
} }
p->activate_t[i] = route.activate_t; 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->x0[i] = route.x[0];
p->x1[i] = route.x[1]; p->x1[i] = route.x[1];
p->x2[i] = route.x[2]; 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) p->activate_t[i] <= slab->t_lo)
continue; continue;
p->t[i] = p->activate_t[i]; 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]) if (!p->continuation[i])
p->steps[i] = 0; p->steps[i] = 0;
p->status[i] = RAY_POOL_ACTIVE; 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]}, .Pi = {p->p0[i], p->p1[i], p->p2[i]},
.log_alpha_p0 = p->log_alpha_p0[i], .log_alpha_p0 = p->log_alpha_p0[i],
.log_alpha_p0_0 = p->log_alpha_p0_0[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; GeodesicTraceConfig per_ray = *config;
if (p->step_limit[i] != 0) if (p->step_limit[i] != 0)
per_ray.max_steps = p->step_limit[i]; 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 = const GeodesicAdvanceResult result =
geodesic_advance_past_ray(slab, &s, slab->t_lo, &per_ray, &p->endpoint[i]); geodesic_advance_past_ray(slab, &s, slab->t_lo, &per_ray, &p->endpoint[i]);
p->t[i] = s.coordinate_time; 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->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->log_alpha_p0[i] = s.log_alpha_p0;
p->steps[i] = s.steps; 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) if (result == GEODESIC_ADVANCE_TERMINATED)
p->status[i] = p->endpoint[i].outcome == RAY_OUTCOME_UNRESOLVED p->status[i] = p->endpoint[i].outcome == RAY_OUTCOME_UNRESOLVED
? RAY_POOL_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->p0); free(p->p1); free(p->p2); free(p->log_alpha_p0);
free(p->log_alpha_p0_0); free(p->log_alpha_p0_0);
free(p->activate_t); free(p->steps); free(p->step_limit); 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->continuation); free(p->frame_id); free(p->vertex_id);
free(p->status); free(p->status);
free(p->endpoint); free(p->endpoint);
+27 -1
View File
@@ -25,6 +25,20 @@ typedef struct {
unsigned int *steps; unsigned int *steps;
/* Per-ray total accepted-step limit; zero means use the trace config. */ /* Per-ray total accepted-step limit; zero means use the trace config. */
unsigned int *step_limit; 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 /* Nonzero for a retry that resumes from a saved state instead of from the
* camera; such rays are not pre-routed. */ * camera; such rays are not pre-routed. */
uint8_t *continuation; uint8_t *continuation;
@@ -39,7 +53,19 @@ int ray_pool_append(RayPool *pool, const ObserverState *observer,
const double direction[3], const double direction[3],
size_t frame_id, size_t vertex_id); size_t frame_id, size_t vertex_id);
/* Append a retry that resumes an UNRESOLVED ray from its last accepted state. /* 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, int ray_pool_append_continuation(RayPool *pool, size_t frame_id,
size_t vertex_id, double t, const double x[3], size_t vertex_id, double t, const double x[3],
const double Pi[3], double log_alpha_p0, 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); spacetime_destroy(&source);
} }
static void test_boundary_semantics_generic(void) { static void test_nonconstant_route_unsupported(void) {
/* velocity_constant == 0 forces the generic bracketed driver. A finite /* velocity_constant == 0 signals an arbitrary accelerated worldtube. The
* history bounds the outward/tangent searches, which must not be reported * camera-inside containment test still runs first; once the ray starts
* as entries (they end as TIME_RANGE_EXHAUSTED instead). */ * outside, the non-constant route is refused explicitly instead of being
* searched with a coarse bracket. */
SyntheticContext context = {.radius = 10.0, SyntheticContext context = {.radius = 10.0,
.valid_t_min = -100.0, .valid_t_min = -100.0,
.constant = 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); &source, &on_boundary, (double[]){-1.0, 0.0, 0.0}, &route);
CHECK(inward == ASYMPTOTIC_OK && CHECK(inward == ASYMPTOTIC_OK &&
route.kind == ASYMPTOTIC_ROUTE_INSIDE, route.kind == ASYMPTOTIC_ROUTE_INSIDE,
"generic on-boundary inward is inside"); "nonconstant on-boundary inward is still inside");
const AsymptoticStatus outward = asymptotic_route_camera( const AsymptoticStatus outward = asymptotic_route_camera(
&source, &on_boundary, (double[]){1.0, 0.0, 0.0}, &route); &source, &on_boundary, (double[]){1.0, 0.0, 0.0}, &route);
CHECK(outward == ASYMPTOTIC_TIME_RANGE_EXHAUSTED, CHECK(outward == ASYMPTOTIC_UNSUPPORTED,
"generic on-boundary outward is not an entry"); "nonconstant on-boundary outward is unsupported");
const AsymptoticStatus tangent = asymptotic_route_camera( const AsymptoticStatus tangent = asymptotic_route_camera(
&source, &on_boundary, (double[]){0.0, 1.0, 0.0}, &route); &source, &on_boundary, (double[]){0.0, 1.0, 0.0}, &route);
CHECK(tangent == ASYMPTOTIC_TIME_RANGE_EXHAUSTED, CHECK(tangent == ASYMPTOTIC_UNSUPPORTED,
"generic on-boundary tangent is not an entry"); "nonconstant on-boundary tangent is unsupported");
} }
static void test_negative_radius_root_guard(void) { 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"); "interior crossing bisection propagates history exhaustion");
} }
static void test_generic_bisection_failure(void) { static void test_nonconstant_preroute_unsupported(void) {
/* The isolated invalid window lands on a bisection midpoint while the /* The accelerated fixture is refused explicitly at the route entry; its
* bracket endpoints stay valid, so only the bisection can see it. With the * former coarse bracketed search (and the bisection-midpoint history-hole
* strict F < 0 entry test, the bracket is s = 80 (F == 0) to s = 90 * case it exercised) is replaced by the unsupported contract. The history
* (F < 0), so the first midpoint is t = -85. */ * reason itself stays covered by the constant and piecewise-constant
* worldtube tests below. */
SyntheticContext context = {.radius = 20.0, SyntheticContext context = {.radius = 20.0,
.valid_t_min = -1.0e30, .valid_t_min = -1.0e30,
.constant = 0, .constant = 0,
@@ -538,9 +540,26 @@ static void test_generic_bisection_failure(void) {
SpacetimeSource source = {.ops = &synthetic_ops, .context = &context}; SpacetimeSource source = {.ops = &synthetic_ops, .context = &context};
const ObserverState camera = flat_observer(100.0, 0.0, 0.0); const ObserverState camera = flat_observer(100.0, 0.0, 0.0);
AsymptoticRoute route; 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}, CHECK(asymptotic_route_camera(&source, &camera, (double[]){-1.0, 0.0, 0.0},
&route) == ASYMPTOTIC_TIME_RANGE_EXHAUSTED, &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) { static void test_interior_history_exhaustion(void) {
@@ -641,36 +660,30 @@ static void test_moving_sphere(void) {
"co-moving ray misses"); "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, SyntheticContext context = {.vx = 0.0, .accel = 0.02, .radius = 20.0,
.valid_t_min = -1.0e30, .constant = 0}; .valid_t_min = -1.0e30, .constant = 0};
SpacetimeSource source = {.ops = &synthetic_ops, .context = &context}; SpacetimeSource source = {.ops = &synthetic_ops, .context = &context};
const ObserverState camera = flat_observer(100.0, 0.0, 0.0); const ObserverState camera = flat_observer(100.0, 0.0, 0.0);
AsymptoticRoute route; AsymptoticRoute route;
CHECK(asymptotic_route_camera(&source, &camera, (double[]){-1.0, 0.0, 0.0}, CHECK(asymptotic_route_camera(&source, &camera, (double[]){-1.0, 0.0, 0.0},
&route) == ASYMPTOTIC_OK && &route) == ASYMPTOTIC_UNSUPPORTED,
route.kind == ASYMPTOTIC_ROUTE_ENTRY, "accelerated worldtube route is unsupported");
"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");
} }
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, SyntheticContext context = {.vx = 0.0,
.accel = 0.0, .accel = 0.0,
.radius = 20.0, .radius = 20.0,
.valid_t_min = -1.0e30, .valid_t_min = -1.0e30,
.constant = 0, .constant = 1,
.segment_t = -50.0, .segment_t = -50.0,
.has_segment = 1}; .has_segment = 1};
SpacetimeSource source = {.ops = &synthetic_ops, .context = &context}; 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}, CHECK(asymptotic_route_camera(&source, &camera, (double[]){-1.0, 0.0, 0.0},
&route) == ASYMPTOTIC_OK && &route) == ASYMPTOTIC_OK &&
route.kind == ASYMPTOTIC_ROUTE_ENTRY, route.kind == ASYMPTOTIC_ROUTE_ENTRY,
"cross-segment entry"); "piecewise cross-segment entry");
CHECK(route.activate_t < context.segment_t, CHECK(route.activate_t < context.segment_t,
"entry lies past the motion-segment boundary"); "entry lies past the motion-segment boundary");
CHECK(fabs(route.activate_t + 65.0) < 1e-6, "cross-segment entry time"); 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_large_radius_quadratic();
test_round_trip(); test_round_trip();
test_moving_sphere(); test_moving_sphere();
test_accelerated_worldtube(); test_accelerated_worldtube_unsupported();
test_accelerated_segment(); test_piecewise_segment_entry();
test_boundary_semantics_minkowski(); test_boundary_semantics_minkowski();
test_boundary_semantics_generic(); test_nonconstant_route_unsupported();
test_negative_radius_root_guard(); test_negative_radius_root_guard();
test_source_finalize(); test_source_finalize();
test_motion_segment_domain(); test_motion_segment_domain();
test_schwarzschild_sample_failures(); test_schwarzschild_sample_failures();
test_end_protocol_error(); test_end_protocol_error();
test_generic_bisection_failure(); test_nonconstant_preroute_unsupported();
test_piecewise_constant_history_hole();
test_interior_history_exhaustion(); test_interior_history_exhaustion();
test_interior_crossing_bisection_failure(); test_interior_crossing_bisection_failure();
test_ray_pool_lifecycle(); test_ray_pool_lifecycle();
+25 -10
View File
@@ -88,19 +88,31 @@ def image_payload(path, dimensions=(64, 48), allow_black=False):
return raw 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): def map_vertices(path):
data = path.read_bytes() data = path.read_bytes()
assert data[:8] == b'GRLENS\x01\x00' 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 assert struct.unpack_from('<Q', data, 32)[0] == 1 # frame_count
# Header: 40 bytes, then a 60-byte provenance block, then the 48-byte vertices, triangles = struct.unpack_from('<QQ', data, MAP_FRAME_HEADER_START + 24)
# per-frame header, so vertices start at offset 148 in the v2 format. offset = MAP_VERTEX_START
vertices, triangles = struct.unpack_from('<QQ', data, 124)
offset = 148
values = [] values = []
for _ in range(vertices): for _ in range(vertices):
values.append(struct.unpack_from('<9dIII', data, offset)) values.append(struct.unpack_from('<9dIIIQQQ', data, offset))
offset += 84 offset += MAP_VERTEX_SIZE
return values, data[offset:offset + triangles * 32] return values, data[offset:offset + triangles * MAP_TRIANGLE_SIZE]
with tempfile.TemporaryDirectory(prefix='gr-camera-cli-') as directory: 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 assert len(a) == len(b) and ta == tb
max_error = 0 max_error = 0
for x, y in zip(a, b): 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]))) max_error = max(max_error, *(abs(v - w) for v, w in zip(x[:9], y[:9])))
assert max_error < 1e-9, max_error 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. # so this tests completion semantics rather than corruption detection.
incomplete_map = tmp / f'{backend}_incomplete.grlens' incomplete_map = tmp / f'{backend}_incomplete.grlens'
payload = bytearray(single_map.read_bytes()) 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, 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) incomplete_map.write_bytes(payload)
refused = tmp / f'{backend}_refused.{ext}' refused = tmp / f'{backend}_refused.{ext}'
failure = run(binary, *common, '--lens-map-input', incomplete_map, failure = run(binary, *common, '--lens-map-input', incomplete_map,
+644 -2
View File
@@ -9,6 +9,107 @@
#include <string.h> #include <string.h>
#include <unistd.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) { static int mesh_has_hanging_vertex(const FrameLensMesh *mesh) {
for (size_t triangle = 0; triangle < mesh->triangle_count; ++triangle) for (size_t triangle = 0; triangle < mesh->triangle_count; ++triangle)
for (size_t side = 0; side < 3; ++side) { 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[i].n_infinity[0]=1;
} }
m.vertices[0].outcome=RAY_OUTCOME_UNRESOLVED; 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; m.vertices[0].continuation_limit=10;
if (frame_lens_mesh_prepare_generation(&m,&c)<1 || m.retry_requests==0) if (frame_lens_mesh_prepare_generation(&m,&c)<1 || m.retry_requests==0)
return -1; /* the retry counter is produced by real retry requests */ return -1; /* the retry counter is produced by real retry requests */
@@ -153,11 +257,408 @@ static int review_probe_regressions(void) {
return 0; 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) { int main(void) {
if (review_probe_regressions()) { if (review_probe_regressions()) {
fputs("probe persistence / retry invalidation regression failed\n",stderr); fputs("probe persistence / retry invalidation regression failed\n",stderr);
return 1; 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 int width = 100, height = 100;
const double test_exposure = 1e-3; const double test_exposure = 1e-3;
const double psf_relative_tail = 1e-8; 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 /* A finalized mesh can be persisted independently of spacetime and then
* drive the exact same catalog inverse-map and PSF pass. */ * 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 */ mesh.retry_requests = 2; /* cumulative per-frame retry accounting round-trips */
const LensMapFrame saved_frame = {.frame_id = 7, const LensMapFrame saved_frame = {.frame_id = 7,
.coordinate_time = 3.0, .coordinate_time = 3.0,
@@ -259,7 +760,7 @@ int main(void) {
/* A version-1 header must be rejected outright: its captured bit cannot be /* A version-1 header must be rejected outright: its captured bit cannot be
* upgraded into the new dark/unresolved/error provenance. */ * 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"); FILE *legacy = fopen(legacy_path, "wb");
int legacy_failed = legacy == NULL; int legacy_failed = legacy == NULL;
if (!legacy_failed) { if (!legacy_failed) {
@@ -906,6 +1407,147 @@ int main(void) {
} }
free(ud_mesh.probe_slots); 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; result = 0;
done: done:
frame_lens_mesh_destroy(&mesh); 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 backends use logical slabs without metric I/O; [Nmesh](https://github.com/nmeshsource/nmesh) metric loading remains
future work. 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 ## Reuse a completed lens map
`--lens-map-output FILE` saves the finalized inverse-lens mesh after tracing `--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, checks the format version, finite values, unit directions, triangle indices,
and per-frame CRCs. 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, 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 pass `--lens-map-input FILE`, the catalog, `--frames-dir DIR`, and
`--frames-prefix NAME`; no observer track is needed on import. `--frames-prefix NAME`; no observer track is needed on import.