/home/runner/work/kynema/kynema/kynema/src/math/interpolation.hpp Source File

Kynema API: /home/runner/work/kynema/kynema/kynema/src/math/interpolation.hpp Source File
Kynema API
A flexible multibody structural dynamics code for wind turbines
Loading...
Searching...
No Matches
interpolation.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <cmath>
4#include <numbers>
5#include <numeric>
6#include <ranges>
7#include <span>
8#include <stdexcept>
9#include <string>
10#include <vector>
11
12namespace kynema::math {
13
21inline void LinearInterpWeights(double x, std::span<const double> xs, std::vector<double>& weights) {
22 const auto n = xs.size();
23 weights.assign(n, 0.);
24
25 const auto lower = std::lower_bound(xs.begin(), xs.end(), x);
26 // If x is less than the first node, first weight -> 1 and our work is done
27 if (lower == xs.begin()) {
28 weights.front() = 1.;
29 return;
30 }
31 // If x is greater than the last node, last weight -> 1 and our work is done
32 if (lower == xs.end()) {
33 weights.back() = 1.;
34 return;
35 }
36 // x is between two nodes, so compute weights for closest nodes
37 const auto index = static_cast<unsigned>(std::distance(xs.begin(), lower));
38 const auto lower_loc = xs[index - 1];
39 const auto upper_loc = xs[index];
40 const auto weight_upper = (x - lower_loc) / (upper_loc - lower_loc);
41 weights[index - 1] = 1. - weight_upper;
42 weights[index] = weight_upper;
43}
44
53inline double LinearInterp(double x, std::span<const double> xs, std::span<const double> values) {
54 std::vector<double> weights(xs.size());
55 LinearInterpWeights(x, xs, weights);
56 return std::transform_reduce(
57 std::begin(weights), std::end(weights), std::begin(values), 0., std::plus<>(),
58 std::multiplies<>()
59 );
60}
61
70 double x, std::span<const double> xs, std::vector<double>& weights
71) {
72 const auto n = xs.size();
73 weights.assign(n, 1.);
74
75 // Pre-compute (x - xs[m]) terms to avoid repeated calculations
76 std::vector<double> x_minus_xm(n);
77 for (auto m : std::views::iota(0U, n)) {
78 x_minus_xm[m] = x - xs[m];
79 }
80
81 for (auto j : std::views::iota(0U, n)) {
82 for (auto m : std::views::iota(0U, n)) {
83 if (j != m) {
84 weights[j] *= x_minus_xm[m] / (xs[j] - xs[m]);
85 }
86 }
87 }
88}
89
98 double x, std::span<const double> xs, std::vector<double>& weights
99) {
100 const auto n = xs.size();
101 weights.assign(n, 0.);
102
103 for (auto i : std::views::iota(0U, n)) {
104 auto xi = xs[i];
105 auto weight = 0.;
106 for (auto j : std::views::iota(0U, n) | std::views::filter([i](unsigned j) {
107 return j != i;
108 })) {
109 auto range = std::views::iota(0U, n) | std::views::filter([i, j](unsigned k) {
110 return k != i && k != j;
111 }) |
112 std::views::common;
113 auto prod = std::transform_reduce(
114 std::begin(range), std::end(range), 1., std::multiplies<>(),
115 [&xs, x, xi](auto k) {
116 return (x - xs[k]) / (xi - xs[k]);
117 }
118 );
119 weight += prod / (xi - xs[j]);
120 }
121 weights[i] = weight;
122 }
123}
124
136inline double LegendrePolynomial(const size_t n, const double x) {
137 // Base cases
138 if (n == 0) {
139 return 1.;
140 }
141 if (n == 1) {
142 return x;
143 }
144
145 // Compute the nth Legendre polynomial iteratively
146 double p_n_minus_2{1.}; // P_{n-2}(x)
147 double p_n_minus_1{x}; // P_{n-1}(x)
148 double p_n{0.}; // P_n(x)
149 for (auto i : std::views::iota(2U, n + 1)) {
150 const auto i_double = static_cast<double>(i);
151 p_n = ((2. * i_double - 1.) * x * p_n_minus_1 - (i_double - 1.) * p_n_minus_2) / i_double;
152 p_n_minus_2 = p_n_minus_1;
153 p_n_minus_1 = p_n;
154 }
155 return p_n;
156}
157
169inline std::vector<double> GenerateGLLPoints(const size_t order) {
170 constexpr auto max_iterations = 1000U;
171 constexpr auto convergence_tolerance = std::numeric_limits<double>::epsilon();
172
173 if (order < 1) {
174 throw std::invalid_argument("Polynomial order must be >= 1");
175 }
176
177 const size_t n_nodes = order + 1;
178 auto gll_points = std::vector<double>(n_nodes, 0.);
179 gll_points.resize(n_nodes);
180
181 // Set the endpoints fixed at [-1, 1]
182 gll_points.front() = -1.;
183 gll_points.back() = 1.;
184
185 // Find interior GLL points (1, ..., order - 1) using Newton-Raphson iteration
186 std::vector<double> legendre_poly(n_nodes, 0.);
187 for (auto i : std::views::iota(1U, order)) {
188 // Initial guess using Chebyshev-Gauss-Lobatto nodes
189 auto x_it =
190 -std::cos(static_cast<double>(i) * std::numbers::pi / static_cast<double>(order));
191
192 bool converged{false};
193 for ([[maybe_unused]] auto iteration : std::views::iota(0U, max_iterations)) {
194 const auto x_old = x_it;
195
196 // Compute Legendre polynomials up to order n
197 for (auto k : std::views::iota(0U, n_nodes)) {
198 legendre_poly[k] = LegendrePolynomial(k, x_it);
199 }
200
201 // Newton update: x_{n+1} = x_n - f(x_n)/f'(x_n)
202 const auto numerator = x_it * legendre_poly[n_nodes - 1] - legendre_poly[n_nodes - 2];
203 const auto denominator = static_cast<double>(n_nodes) * legendre_poly[n_nodes - 1];
204 x_it -= numerator / denominator;
205
206 // Check for convergence
207 if (std::abs(x_it - x_old) <= convergence_tolerance) {
208 converged = true;
209 break;
210 }
211 }
212
213 if (!converged) {
214 throw std::runtime_error(
215 "Newton-Raphson iteration failed to converge for GLL point index " +
216 std::to_string(i)
217 );
218 }
219
220 gll_points[i] = x_it;
221 }
222
223 return gll_points;
224}
225
226} // namespace kynema::math
Definition gll_quadrature.hpp:7
std::vector< double > GenerateGLLPoints(const size_t order)
Generates Gauss-Lobatto-Legendre (GLL) points for spectral element discretization.
Definition interpolation.hpp:169
void LagrangePolynomialInterpWeights(double x, std::span< const double > xs, std::vector< double > &weights)
Computes weights for Lagrange polynomial interpolation.
Definition interpolation.hpp:69
double LegendrePolynomial(const size_t n, const double x)
Calculates the value of Legendre polynomial of order n at point x.
Definition interpolation.hpp:136
void LinearInterpWeights(double x, std::span< const double > xs, std::vector< double > &weights)
Computes weights for linear interpolation.
Definition interpolation.hpp:21
void LagrangePolynomialDerivWeights(double x, std::span< const double > xs, std::vector< double > &weights)
Computes weights for Lagrange polynomial derivative interpolation.
Definition interpolation.hpp:97
double LinearInterp(double x, std::span< const double > xs, std::span< const double > values)
Computes linear interpolation.
Definition interpolation.hpp:53