29 for (
int i = 0; i < DIM; ++i) {
30 if (!std::isfinite(
x[i]))
return false;
31 if (!std::isfinite(
u[i]))
return false;
41std::array<double, DIM>
42geodesic_acceleration(
const std::array<double, NUM_CHRISTOFFEL>& gamma,
43 const std::array<double, DIM>& u)
noexcept {
44 std::array<double, DIM> accel{};
46 for (
int lambda = 0; lambda < DIM; ++lambda) {
48 for (
int mu = 0; mu < DIM; ++mu) {
49 for (
int nu = 0; nu < DIM; ++nu) {
50 const int idx = christoffel_index(lambda, mu, nu);
51 sum += gamma[
static_cast<std::size_t
>(idx)] * u[mu] * u[nu];
54 accel[
static_cast<std::size_t
>(lambda)] = -sum;
60std::optional<GeodesicState>
61rk4_step(
const GeodesicState& s,
62 const std::array<double, NUM_CHRISTOFFEL>& christoffel,
65 const auto a1 = geodesic_acceleration(christoffel, s.u);
67 for (
int i = 0; i <
DIM; ++i) {
68 s2.x[
static_cast<std::size_t
>(i)] = s.x[
static_cast<std::size_t
>(i)] + 0.5 * dt * s.u[
static_cast<std::size_t
>(i)];
69 s2.u[
static_cast<std::size_t
>(i)] = s.u[
static_cast<std::size_t
>(i)] + 0.5 * dt * a1[
static_cast<std::size_t
>(i)];
73 const auto a2 = geodesic_acceleration(christoffel, s2.u);
75 for (
int i = 0; i <
DIM; ++i) {
76 s3.x[
static_cast<std::size_t
>(i)] = s.x[
static_cast<std::size_t
>(i)] + 0.5 * dt * s2.u[
static_cast<std::size_t
>(i)];
77 s3.u[
static_cast<std::size_t
>(i)] = s.u[
static_cast<std::size_t
>(i)] + 0.5 * dt * a2[
static_cast<std::size_t
>(i)];
81 const auto a3 = geodesic_acceleration(christoffel, s3.u);
83 for (
int i = 0; i <
DIM; ++i) {
84 s4.x[
static_cast<std::size_t
>(i)] = s.x[
static_cast<std::size_t
>(i)] + dt * s3.u[
static_cast<std::size_t
>(i)];
85 s4.u[
static_cast<std::size_t
>(i)] = s.u[
static_cast<std::size_t
>(i)] + dt * a3[
static_cast<std::size_t
>(i)];
89 const auto a4 = geodesic_acceleration(christoffel, s4.u);
93 for (std::size_t i = 0; i < static_cast<std::size_t>(DIM); ++i) {
94 out.x[i] = s.x[i] + (dt / 6.0) * (s.u[i] + 2.0 * s2.u[i] + 2.0 * s3.u[i] + s4.u[i]);
95 out.u[i] = s.u[i] + (dt / 6.0) * (a1[i] + 2.0 * a2[i] + 2.0 * a3[i] + a4[i]);
97 if (!std::isfinite(out.x[i]) || !std::isfinite(out.u[i])) {
108std::optional<GeodesicState>
112 double dt)
const noexcept {
113 SRFM_LOG_TRACE(
"GeodesicSolver::solve entry: steps={}, dt={}, x0=[{},{},{},{}]",
114 steps, dt, initial.x[0], initial.x[1], initial.x[2], initial.x[3]);
117 if (!initial.is_finite()) {
118 SRFM_LOG_WARN(
"GeodesicSolver::solve: initial state is not finite — returning nullopt");
123 const int clamped_steps = std::clamp(steps, 1, 100'000);
124 const double clamped_dt = std::clamp(dt, 1e-8, 1.0);
126 if (clamped_steps != steps) {
127 SRFM_LOG_WARN(
"GeodesicSolver::solve: steps={} clamped to {}", steps, clamped_steps);
129 if (clamped_dt != dt) {
130 SRFM_LOG_WARN(
"GeodesicSolver::solve: dt={} clamped to {}", dt, clamped_dt);
134 if (!metric.is_valid()) {
135 SRFM_LOG_WARN(
"GeodesicSolver::solve: metric is not valid — returning nullopt");
144 for (
int step = 0; step < clamped_steps; ++step) {
145 auto next = rk4_step(state, christoffel, clamped_dt);
147 SRFM_LOG_WARN(
"GeodesicSolver::solve: NaN detected during step {} — returning nullopt", step);
153 SRFM_LOG_DEBUG(
"GeodesicSolver::solve: completed {} steps, final x=[{},{},{},{}]",
154 clamped_steps, state.
x[0], state.
x[1], state.
x[2], state.
x[3]);
std::optional< GeodesicState > solve(const GeodesicState &initial, const MetricTensor &metric, int steps, double dt) const noexcept
Integrate the geodesic equation for steps RK4 steps.