Special Relativity in Financial Modeling 1.0.0
Lorentz transforms, spacetime classification, and geodesic price paths for quantitative finance
Loading...
Searching...
No Matches
christoffel_n.cpp
Go to the documentation of this file.
1/**
2 * @file christoffel_n.cpp
3 * @brief Implementation of ChristoffelN.
4 *
5 * See include/srfm/tensor/christoffel_n.hpp for the public API contract.
6 *
7 * ## Algorithm
8 * Christoffel symbols of the second kind:
9 *
10 * Γ^λ_μν = ½ g^λσ (∂_μ g_νσ + ∂_ν g_μσ - ∂_σ g_μν)
11 *
12 * Metric derivatives are evaluated by central finite differences:
13 *
14 * ∂_α g_μν ≈ [g_μν(x + h·e_α) - g_μν(x - h·e_α)] / (2h)
15 *
16 * For a constant metric (NAssetManifold base case) all derivatives vanish,
17 * so all Christoffel symbols are identically zero.
18 */
19
20#include "../../include/srfm/tensor/christoffel_n.hpp"
21
22#include <cmath>
23
24namespace srfm::tensor {
25
26// ── Constructor ───────────────────────────────────────────────────────────────
27
29 : manifold_(manifold)
30{}
31
32// ── Internal helpers ──────────────────────────────────────────────────────────
33
34std::optional<double>
35ChristoffelN::metric_deriv(int alpha, int mu, int nu,
36 const Eigen::VectorXd& x) const noexcept {
37 const int D = manifold_.dim();
38
39 if (alpha < 0 || alpha >= D) { return std::nullopt; }
40 if (mu < 0 || mu >= D) { return std::nullopt; }
41 if (nu < 0 || nu >= D) { return std::nullopt; }
42 if (x.size() != D) { return std::nullopt; }
43
44 // Forward perturb.
45 Eigen::VectorXd xp = x;
46 xp(alpha) += FD_STEP;
47 auto gp = manifold_.metric_at(xp);
48 if (!gp) { return std::nullopt; }
49
50 // Backward perturb.
51 Eigen::VectorXd xm = x;
52 xm(alpha) -= FD_STEP;
53 auto gm = manifold_.metric_at(xm);
54 if (!gm) { return std::nullopt; }
55
56 return ((*gp)(mu, nu) - (*gm)(mu, nu)) / (2.0 * FD_STEP);
57}
58
59std::optional<Eigen::MatrixXd>
60ChristoffelN::inv_metric_at(const Eigen::VectorXd& x) const noexcept {
61 return manifold_.inverse_metric_at(x);
62}
63
64// ── Public methods ────────────────────────────────────────────────────────────
65
66std::optional<double>
67ChristoffelN::symbol(int lambda, int mu, int nu,
68 const Eigen::VectorXd& x) const noexcept {
69 const int D = manifold_.dim();
70
71 // Validate indices.
72 if (lambda < 0 || lambda >= D) { return std::nullopt; }
73 if (mu < 0 || mu >= D) { return std::nullopt; }
74 if (nu < 0 || nu >= D) { return std::nullopt; }
75 if (x.size() != D) { return std::nullopt; }
76
77 // Obtain inverse metric.
78 auto g_inv_opt = inv_metric_at(x);
79 if (!g_inv_opt) { return std::nullopt; }
80 const Eigen::MatrixXd& g_inv = *g_inv_opt;
81
82 // Γ^λ_μν = ½ Σ_σ g^λσ (∂_μ g_νσ + ∂_ν g_μσ - ∂_σ g_μν)
83 double result = 0.0;
84 for (int sigma = 0; sigma < D; ++sigma) {
85 auto d_mu_g_nu_sigma = metric_deriv(mu, nu, sigma, x);
86 auto d_nu_g_mu_sigma = metric_deriv(nu, mu, sigma, x);
87 auto d_sigma_g_mu_nu = metric_deriv(sigma, mu, nu, x);
88
89 if (!d_mu_g_nu_sigma || !d_nu_g_mu_sigma || !d_sigma_g_mu_nu) {
90 return std::nullopt;
91 }
92
93 double bracket = *d_mu_g_nu_sigma + *d_nu_g_mu_sigma - *d_sigma_g_mu_nu;
94 result += g_inv(lambda, sigma) * bracket;
95 }
96
97 return 0.5 * result;
98}
99
100std::optional<std::vector<std::vector<std::vector<double>>>>
101ChristoffelN::all_symbols(const Eigen::VectorXd& x) const noexcept {
102 const int D = manifold_.dim();
103 if (x.size() != D) { return std::nullopt; }
104
105 // Obtain inverse metric once.
106 auto g_inv_opt = inv_metric_at(x);
107 if (!g_inv_opt) { return std::nullopt; }
108 const Eigen::MatrixXd& g_inv = *g_inv_opt;
109
110 // Pre-compute all metric derivatives: deriv[alpha][mu][nu] = ∂_alpha g_mu_nu.
111 // Use a flat 3D array stored as vector-of-vector-of-vector.
112 std::vector<std::vector<std::vector<double>>> dg(
113 D, std::vector<std::vector<double>>(D, std::vector<double>(D, 0.0)));
114
115 // Same central difference as metric_deriv(), but the two perturbed
116 // metrics are evaluated once per alpha instead of once per (alpha, mu, nu):
117 // 2*D metric evaluations rather than 2*D^3, which made D = 51 unusable.
118 for (int alpha = 0; alpha < D; ++alpha) {
119 Eigen::VectorXd xp = x;
120 xp(alpha) += FD_STEP;
121 auto gp = manifold_.metric_at(xp);
122 if (!gp) { return std::nullopt; }
123
124 Eigen::VectorXd xm = x;
125 xm(alpha) -= FD_STEP;
126 auto gm = manifold_.metric_at(xm);
127 if (!gm) { return std::nullopt; }
128
129 for (int mu = 0; mu < D; ++mu) {
130 for (int nu = 0; nu < D; ++nu) {
131 dg[alpha][mu][nu] =
132 ((*gp)(mu, nu) - (*gm)(mu, nu)) / (2.0 * FD_STEP);
133 }
134 }
135 }
136
137 // Compute full Christoffel tensor.
138 std::vector<std::vector<std::vector<double>>> gamma(
139 D, std::vector<std::vector<double>>(D, std::vector<double>(D, 0.0)));
140
141 for (int lambda = 0; lambda < D; ++lambda) {
142 for (int mu = 0; mu < D; ++mu) {
143 for (int nu = 0; nu < D; ++nu) {
144 double val = 0.0;
145 for (int sigma = 0; sigma < D; ++sigma) {
146 double bracket = dg[mu][nu][sigma]
147 + dg[nu][mu][sigma]
148 - dg[sigma][mu][nu];
149 val += g_inv(lambda, sigma) * bracket;
150 }
151 gamma[lambda][mu][nu] = 0.5 * val;
152 }
153 }
154 }
155
156 return gamma;
157}
158
159bool ChristoffelN::verify_symmetry(const Eigen::VectorXd& x,
160 double tol) const noexcept {
161 const int D = manifold_.dim();
162 if (x.size() != D) { return false; }
163
164 for (int lambda = 0; lambda < D; ++lambda) {
165 for (int mu = 0; mu < D; ++mu) {
166 for (int nu = mu + 1; nu < D; ++nu) {
167 auto s_mn = symbol(lambda, mu, nu, x);
168 auto s_nm = symbol(lambda, nu, mu, x);
169 if (!s_mn || !s_nm) { return false; }
170 if (std::abs(*s_mn - *s_nm) > tol) { return false; }
171 }
172 }
173 }
174 return true;
175}
176
177} // namespace srfm::tensor
bool verify_symmetry(const Eigen::VectorXd &x, double tol=1e-10) const noexcept
Verify that Γ^λ_μν = Γ^λ_νμ for all indices at point x.
std::optional< std::vector< std::vector< std::vector< double > > > > all_symbols(const Eigen::VectorXd &x) const noexcept
Compute all Christoffel symbols at point x.
ChristoffelN(const NAssetManifold &manifold) noexcept
Construct from a manifold reference.
std::optional< double > symbol(int lambda, int mu, int nu, const Eigen::VectorXd &x) const noexcept
Compute a single Christoffel symbol Γ^lambda_mu_nu at point x.
(N+1)-dimensional Lorentzian manifold for N financial assets.