34 if (
weights.empty())
return false;
36 if (!std::isfinite(w))
return false;
43 for (
double w :
weights) s += w;
50double GeodesicSolver::trajectory(
59 return w_start + (w_end - w_start) * t_norm;
62 const double sin_omega = std::sin(omega);
63 if (std::fabs(sin_omega) < 1e-12) {
66 return w_start + (w_end - w_start) * t_norm;
70 const double A = w_start;
71 const double B = (w_end - A * std::cos(omega)) / sin_omega;
73 return A * std::cos(omega * t_norm) + B * std::sin(omega * t_norm);
84 throw std::invalid_argument(
85 "GeodesicSolver::solve — n_steps must be >= 1, got " +
86 std::to_string(n_steps)
89 if (start.
dim() != end.
dim()) {
90 throw std::invalid_argument(
91 "GeodesicSolver::solve — start and end must have the same dimension ("
92 + std::to_string(start.
dim()) +
" vs " + std::to_string(end.
dim()) +
")"
96 throw std::invalid_argument(
97 "GeodesicSolver::solve — portfolio dimension must be >= 1"
101 throw std::invalid_argument(
102 "GeodesicSolver::solve — lambda must be >= 0, got " +
103 std::to_string(lambda)
107 const int dim = start.
dim();
108 const double omega = std::sqrt(2.0 * lambda);
111 const int n_points = n_steps + 1;
118 result.
states.reserve(
static_cast<std::size_t
>(n_points));
120 for (
int step = 0; step < n_points; ++step) {
121 const double t_norm = (n_steps == 0)
123 :
static_cast<double>(step) /
static_cast<double>(n_steps);
126 state.
weights.resize(
static_cast<std::size_t
>(dim));
128 std::round(
static_cast<double>(t1 - t0) * t_norm)
131 for (
int i = 0; i < dim; ++i) {
132 state.
weights[
static_cast<std::size_t
>(i)] = trajectory(
133 start.
weights[
static_cast<std::size_t
>(i)],
134 end.
weights[
static_cast<std::size_t
>(i)],
140 result.
states.push_back(std::move(state));
153 if (a.weights.size() != b.weights.size())
return 0.0;
155 for (std::size_t i = 0; i < a.weights.size(); ++i) {
156 const double diff = a.weights[i] - b.weights[i];
157 sum_sq += diff * diff;
159 return std::sqrt(sum_sq);
164 if (geodesic.states.size() < 2)
return 0.0;
165 const double effective_dt = (dt <= 0.0) ? 1.0 : dt;
168 for (std::size_t i = 1; i < geodesic.states.size(); ++i) {
173 const double seg = distance(geodesic.states[i - 1], geodesic.states[i]);
static double compute(const Geodesic &geodesic, double dt=1.0) noexcept
static double distance(const PortfolioState &a, const PortfolioState &b) noexcept
Compute the Euclidean distance between two PortfolioState weight vectors.
static Geodesic solve(const PortfolioState &start, const PortfolioState &end, int n_steps, double lambda)
Geodesic Portfolio Path — Round 5 public API.
std::vector< PortfolioState > states
Ordered from start to end.
A point in portfolio space + time.
int64_t timestamp_ms
Wall-clock time in milliseconds.
std::vector< double > weights
Portfolio weights (any length >= 1)
bool is_valid() const noexcept
Returns true if weights is non-empty and all elements are finite.
int dim() const noexcept
Dimension of the portfolio (number of assets).
double sum_weights() const noexcept
Sum of all weights.