Special Relativity in Financial Modeling 1.0.0
Lorentz transforms, spacetime classification, and geodesic price paths for quantitative finance
Loading...
Searching...
No Matches
minkowski_momentum.cpp
Go to the documentation of this file.
1/**
2 * @file src/minkowski_momentum.cpp
3 * @brief Implementation of the Minkowski Momentum module — Round 6.
4 *
5 * See include/srfm/minkowski_momentum.hpp for the full API contract,
6 * concept description, and formula derivations.
7 */
8
10
11#include <algorithm>
12#include <array>
13#include <cmath>
14#include <numeric>
15#include <span>
16#include <vector>
17
19
20// ─── MinkowskiMomentum ────────────────────────────────────────────────────────
21
23 return p.energy * p.energy
24 - p.px * p.px
25 - p.py * p.py
26 - p.pz * p.pz;
27}
28
29std::optional<double>
31 if (!std::isfinite(p.energy) ||
32 !std::isfinite(p.px) ||
33 !std::isfinite(p.py) ||
34 !std::isfinite(p.pz)) {
35 return std::nullopt;
36 }
37
38 const double m2 = invariant_mass_sq(p);
39 // Return signed sqrt: positive for time-like (m2 > 0), negative for space-like.
40 if (m2 >= 0.0) {
41 return std::sqrt(m2);
42 } else {
43 return -std::sqrt(-m2);
44 }
45}
46
47std::optional<double>
49 if (!std::isfinite(p.energy) || !std::isfinite(p.px)) {
50 return std::nullopt;
51 }
52
53 const double num = p.energy + p.px;
54 const double den = p.energy - p.px;
55
56 if (den <= 0.0 || num <= 0.0) {
57 // Rapidity undefined when |p_x| >= E
58 return std::nullopt;
59 }
60
61 return 0.5 * std::log(num / den);
62}
63
65 return std::sqrt(p.py * p.py + p.pz * p.pz);
66}
67
69 return std::sqrt(p.px * p.px + p.py * p.py + p.pz * p.pz);
70}
71
72// ─── FourMomentumConservation ─────────────────────────────────────────────────
73
75 std::span<const FourMomentum> momenta) noexcept
76{
77 FourMomentum result{0.0, 0.0, 0.0, 0.0};
78 for (const auto& p : momenta) {
79 result.energy += p.energy;
80 result.px += p.px;
81 result.py += p.py;
82 result.pz += p.pz;
83 }
84 return result;
85}
86
88 std::span<const FourMomentum> trades,
89 const FourMomentum& reference,
90 double tolerance) noexcept
91{
92 const FourMomentum net = sum(trades);
93 return std::abs(net.energy - reference.energy) <= tolerance &&
94 std::abs(net.px - reference.px) <= tolerance &&
95 std::abs(net.py - reference.py) <= tolerance &&
96 std::abs(net.pz - reference.pz) <= tolerance;
97}
98
99// ─── MomentumPortfolioOptimizer ───────────────────────────────────────────────
100
101FourMomentum MomentumPortfolioOptimizer::_portfolio_momentum(
102 std::span<const double> weights,
103 std::span<const double> returns,
104 std::span<const std::array<double,3>> exposures) noexcept
105{
106 FourMomentum p{0.0, 0.0, 0.0, 0.0};
107 const std::size_t n = weights.size();
108 for (std::size_t i = 0; i < n; ++i) {
109 const double w = weights[i];
110 p.energy += w * returns[i];
111 p.px += w * exposures[i][0];
112 p.py += w * exposures[i][1];
113 p.pz += w * exposures[i][2];
114 }
115 return p;
116}
117
118std::optional<MomentumPortfolioOptimizer::Result>
120 std::span<const double> returns,
121 std::span<const std::array<double,3>> exposures,
122 const Config& cfg) noexcept
123{
124 const std::size_t n = returns.size();
125 if (n == 0 || n != exposures.size()) {
126 return std::nullopt;
127 }
128 if (cfg.max_iterations <= 0 || cfg.learning_rate <= 0.0) {
129 return std::nullopt;
130 }
131
132 // Initialise to equal weights.
133 std::vector<double> w(n, 1.0 / static_cast<double>(n));
134
135 auto clamp_and_normalise = [&]() {
136 // Clamp each weight to [min_weight, max_weight].
137 for (auto& wi : w) {
138 wi = std::clamp(wi, cfg.min_weight, cfg.max_weight);
139 }
140 // Re-normalise to sum to 1.
141 const double total = std::accumulate(w.begin(), w.end(), 0.0);
142 if (total <= 0.0) {
143 std::fill(w.begin(), w.end(), 1.0 / static_cast<double>(n));
144 } else {
145 for (auto& wi : w) wi /= total;
146 }
147 };
148
149 clamp_and_normalise();
150
152 _portfolio_momentum(w, returns, exposures));
153
154 int iters = 0;
155 bool converged = false;
156
157 for (int iter = 0; iter < cfg.max_iterations; ++iter) {
158 ++iters;
159
160 // Numerical gradient of m² with respect to each weight w_i.
161 // Gradient_i ~ [m²(w + delta_i*e_i) - m²(w)] / delta_i
162 // using central difference for accuracy.
163 constexpr double DELTA = 1e-6;
164 std::vector<double> grad(n, 0.0);
165
166 for (std::size_t i = 0; i < n; ++i) {
167 // Forward perturb
168 w[i] += DELTA;
169 const double m2_fwd = MinkowskiMomentum::invariant_mass_sq(
170 _portfolio_momentum(w, returns, exposures));
171 w[i] -= 2.0 * DELTA;
172 const double m2_bwd = MinkowskiMomentum::invariant_mass_sq(
173 _portfolio_momentum(w, returns, exposures));
174 w[i] += DELTA; // restore
175
176 grad[i] = (m2_fwd - m2_bwd) / (2.0 * DELTA);
177 }
178
179 // Gradient ascent step.
180 for (std::size_t i = 0; i < n; ++i) {
181 w[i] += cfg.learning_rate * grad[i];
182 }
183
184 clamp_and_normalise();
185
186 const double new_m2 = MinkowskiMomentum::invariant_mass_sq(
187 _portfolio_momentum(w, returns, exposures));
188
189 if (std::abs(new_m2 - prev_m2) < cfg.tolerance) {
190 prev_m2 = new_m2;
191 converged = true;
192 break;
193 }
194 prev_m2 = new_m2;
195 }
196
197 return Result{
198 std::move(w),
199 prev_m2,
200 iters,
201 converged,
202 };
203}
204
205} // namespace srfm::minkowski_momentum
static bool conserves(std::span< const FourMomentum > trades, const FourMomentum &reference, double tolerance=1e-9) noexcept
static FourMomentum sum(std::span< const FourMomentum > momenta) noexcept
static double invariant_mass_sq(const FourMomentum &p) noexcept
static double spatial_magnitude(const FourMomentum &p) noexcept
static double transverse_momentum(const FourMomentum &p) noexcept
static std::optional< double > rapidity(const FourMomentum &p) noexcept
static std::optional< double > invariant_mass(const FourMomentum &p) noexcept
static std::optional< Result > optimize(std::span< const double > returns, std::span< const std::array< double, 3 > > exposures, const Config &cfg={}) noexcept
Minkowski Momentum — Round 6 public API.
double energy
E — portfolio return (time-like component)
double pz
p_z — commodity exposure