Special Relativity in Financial Modeling 1.0.0
Lorentz transforms, spacetime classification, and geodesic price paths for quantitative finance
Loading...
Searching...
No Matches
geodesic_solver.cpp
Go to the documentation of this file.
1/**
2 * @file geodesic_solver.cpp
3 * @brief RK4 geodesic integrator implementation (AGT-13 / SRFM).
4 *
5 * See geodesic_solver.hpp for the full module contract.
6 */
7
8#include "geodesic_solver.hpp"
9
10#include <algorithm>
11#include <cmath>
12
13#if defined(SRFM_HAS_SPDLOG) && SRFM_HAS_SPDLOG
14# include <spdlog/spdlog.h>
15# define SRFM_LOG_TRACE(...) spdlog::trace(__VA_ARGS__)
16# define SRFM_LOG_DEBUG(...) spdlog::debug(__VA_ARGS__)
17# define SRFM_LOG_WARN(...) spdlog::warn(__VA_ARGS__)
18#else
19# define SRFM_LOG_TRACE(...) (void)0
20# define SRFM_LOG_DEBUG(...) (void)0
21# define SRFM_LOG_WARN(...) (void)0
22#endif
23
24namespace srfm::geodesic {
25
26// ── GeodesicState ─────────────────────────────────────────────────────────────
27
28bool GeodesicState::is_finite() const noexcept {
29 for (int i = 0; i < DIM; ++i) {
30 if (!std::isfinite(x[i])) return false;
31 if (!std::isfinite(u[i])) return false;
32 }
33 return true;
34}
35
36// ── Internal RK4 helpers ──────────────────────────────────────────────────────
37
38namespace {
39
40/// Compute Christoffel acceleration: a^λ = −Γ^λ_μν u^μ u^ν
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{};
45 accel.fill(0.0);
46 for (int lambda = 0; lambda < DIM; ++lambda) {
47 double sum = 0.0;
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];
52 }
53 }
54 accel[static_cast<std::size_t>(lambda)] = -sum;
55 }
56 return accel;
57}
58
59/// Single RK4 step. Returns nullopt if any component becomes non-finite.
60std::optional<GeodesicState>
61rk4_step(const GeodesicState& s,
62 const std::array<double, NUM_CHRISTOFFEL>& christoffel,
63 double dt) noexcept {
64 // k1
65 const auto a1 = geodesic_acceleration(christoffel, s.u);
66 GeodesicState s2{};
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)];
70 }
71
72 // k2
73 const auto a2 = geodesic_acceleration(christoffel, s2.u);
74 GeodesicState s3{};
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)];
78 }
79
80 // k3
81 const auto a3 = geodesic_acceleration(christoffel, s3.u);
82 GeodesicState s4{};
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)];
86 }
87
88 // k4
89 const auto a4 = geodesic_acceleration(christoffel, s4.u);
90
91 // Combine
92 GeodesicState out{};
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]);
96
97 if (!std::isfinite(out.x[i]) || !std::isfinite(out.u[i])) {
98 return std::nullopt;
99 }
100 }
101 return out;
102}
103
104} // namespace
105
106// ── GeodesicSolver::solve ─────────────────────────────────────────────────────
107
108std::optional<GeodesicState>
110 const MetricTensor& metric,
111 int steps,
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]);
115
116 // Validate initial state
117 if (!initial.is_finite()) {
118 SRFM_LOG_WARN("GeodesicSolver::solve: initial state is not finite — returning nullopt");
119 return std::nullopt;
120 }
121
122 // Clamp integration parameters to safe ranges
123 const int clamped_steps = std::clamp(steps, 1, 100'000);
124 const double clamped_dt = std::clamp(dt, 1e-8, 1.0);
125
126 if (clamped_steps != steps) {
127 SRFM_LOG_WARN("GeodesicSolver::solve: steps={} clamped to {}", steps, clamped_steps);
128 }
129 if (clamped_dt != dt) {
130 SRFM_LOG_WARN("GeodesicSolver::solve: dt={} clamped to {}", dt, clamped_dt);
131 }
132
133 // Validate metric
134 if (!metric.is_valid()) {
135 SRFM_LOG_WARN("GeodesicSolver::solve: metric is not valid — returning nullopt");
136 return std::nullopt;
137 }
138
139 // Compute Christoffel symbols (constant for flat metric)
141 const auto christoffel = mfld.christoffelSymbols(metric);
142
143 GeodesicState state = initial;
144 for (int step = 0; step < clamped_steps; ++step) {
145 auto next = rk4_step(state, christoffel, clamped_dt);
146 if (!next) {
147 SRFM_LOG_WARN("GeodesicSolver::solve: NaN detected during step {} — returning nullopt", step);
148 return std::nullopt;
149 }
150 state = *next;
151 }
152
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]);
155
156 return state;
157}
158
159} // namespace srfm::geodesic
std::optional< GeodesicState > solve(const GeodesicState &initial, const MetricTensor &metric, int steps, double dt) const noexcept
Integrate the geodesic equation for steps RK4 steps.
std::array< double, NUM_CHRISTOFFEL > christoffelSymbols(const MetricTensor &metric) const noexcept
Compute all 64 Christoffel symbols Γ^λ_μν via finite differences.
#define SRFM_LOG_WARN(...)
#define SRFM_LOG_DEBUG(...)
#define SRFM_LOG_TRACE(...)
RK4 geodesic integrator on a spacetime manifold (AGT-13 / SRFM)
constexpr int DIM
Number of spacetime dimensions.
State of a particle on a geodesic: position x^μ and 4-velocity u^μ.
std::array< double, DIM > u
4-velocity u^μ = dx^μ/dτ
bool is_finite() const noexcept
True iff all position and velocity components are finite.
std::array< double, DIM > x
Position x^μ (μ = 0…3)
Symmetric 4×4 spacetime metric tensor g_{μν}.