#include "geodesic.h" #include "observer_track.h" #include #include #include static int nearly_equal(double a, double b) { return fabs(a - b) < 1e-12; } /* Every enumerator must have a stable label, and an out-of-range value must be * reported as UNKNOWN rather than indexing past a table. */ static int check_reason_names(void) { const RayReason reasons[] = { RAY_REASON_NONE, RAY_REASON_REDSHIFT_LIMIT, RAY_REASON_BUDGET_EXHAUSTED, RAY_REASON_TIME_RANGE_EXHAUSTED, RAY_REASON_OUT_OF_DOMAIN, RAY_REASON_INVALID_METRIC, RAY_REASON_INTEGRATION_ERROR, RAY_REASON_UNSUPPORTED, RAY_REASON_PROTOCOL_ERROR, RAY_REASON_IO_ERROR}; int failed = 0; for (size_t i = 0; i < sizeof reasons / sizeof reasons[0]; ++i) { const char *name = ray_reason_name(reasons[i]); if (name == NULL || name[0] == '\0' || strcmp(name, "UNKNOWN") == 0) { fprintf(stderr, "reason %d has no stable label\n", (int)reasons[i]); failed = 1; } } const int unknown = (int)RAY_REASON_IO_ERROR + 7; if (strcmp(ray_reason_name((RayReason)unknown), "UNKNOWN") != 0) { fputs("out-of-range reason is not UNKNOWN\n", stderr); failed = 1; } return failed; } static int check_ray(const SpacetimeSource *source, const ObserverState *observer, const double local_direction[3], const double expected[3]) { const GeodesicTraceConfig config = {.coordinate_time_step = 0.25, .max_steps = 100}; RayEndpoint ray = geodesic_trace_past(source, observer, local_direction, &config); if (ray.outcome != RAY_OUTCOME_ESCAPED || !nearly_equal(ray.frequency_ratio, 1.0) || !nearly_equal(ray.n_infinity[0], expected[0]) || !nearly_equal(ray.n_infinity[1], expected[1]) || !nearly_equal(ray.n_infinity[2], expected[2])) { fprintf(stderr, "flat-space geodesic regression failed\n"); return 1; } return 0; } int main(void) { SpacetimeSource source = {0}; MetricSlab *slab = NULL; MetricData metric; const ObserverState observer = observer_fixed_at_origin(); if (spacetime_create_minkowski(&source, 10.0) || spacetime_load_slab(&source, 0.0, -1.0, &slab) || spacetime_slab_eval(slab, -0.5, (double[]){0.0, 0.0, 0.0}, &metric) || metric.alpha != 1.0 || !spacetime_slab_eval(slab, 0.25, (double[]){0.0, 0.0, 0.0}, &metric)) return 1; int result = check_reason_names() || check_ray(&source, &observer, (double[]){1.0, 0.0, 0.0}, (double[]){0.0, 0.0, -1.0}) || check_ray(&source, &observer, (double[]){0.0, 0.0, 1.0}, (double[]){1.0, 0.0, 0.0}); const ObserverState look_at_ra_zero = observer_fixed_at_origin_look_at(0.0, 0.0); result = result || check_ray(&source, &look_at_ra_zero, (double[]){1.0, 0.0, 0.0}, (double[]){1.0, 0.0, 0.0}); /* Standard ICRS has +Z at the north celestial pole and +Y at increasing * RA. A right-handed north-up camera consequently has west to its right. */ result = result || check_ray(&source, &look_at_ra_zero, (double[]){0.0, 1.0, 0.0}, (double[]){0.0, 0.0, 1.0}) || check_ray(&source, &look_at_ra_zero, (double[]){0.0, 0.0, 1.0}, (double[]){0.0, -1.0, 0.0}); ObserverTrack accelerated = {0}; ObserverState final_observer; if (observer_track_generate_minkowski_acceleration(&accelerated, 1.52, 2.0, 1.0 / 30.0) || observer_track_interpolate(&accelerated, 2.0, &final_observer, NULL)) { result = 1; } else { const RayEndpoint forward = geodesic_trace_past( &source, &final_observer, (double[]){1.0, 0.0, 0.0}, &(GeodesicTraceConfig){.coordinate_time_step = 0.25, .max_steps = 100}); const double expected_g = sqrt(1.0 + 3.04 * 3.04) + 3.04; if (forward.outcome != RAY_OUTCOME_ESCAPED || !nearly_equal(forward.frequency_ratio, expected_g)) { fputs("accelerated-observer Doppler regression failed\n", stderr); result = 1; } } observer_track_destroy(&accelerated); spacetime_free_slab(slab); spacetime_destroy(&source); return result; }