Janus 2.0.0
High-performance C++20 dual-mode numerical framework
Loading...
Searching...
No Matches
OrthogonalPolynomials.hpp
Go to the documentation of this file.
1#pragma once
7
10#include <cmath>
11#include <limits>
12#include <utility>
13
14namespace janus {
15
16std::pair<double, double> legendre_poly(int n, double x);
17
18namespace detail {
19
21constexpr double polynomial_root_tolerance = 1e-15;
22
23inline std::pair<NumericVector, NumericVector> gauss_legendre_rule(int n) {
24 if (n < 1) {
25 throw InvalidArgument("gauss_legendre_rule: n must be >= 1");
26 }
27
28 NumericVector nodes(n);
29 NumericVector weights(n);
30
31 const int m = (n + 1) / 2;
32 const double pi = std::acos(-1.0);
33 const double tol = 64.0 * std::numeric_limits<double>::epsilon();
34
35 for (int i = 0; i < m; ++i) {
36 double z = std::cos(pi * (static_cast<double>(i) + 0.75) / (static_cast<double>(n) + 0.5));
37
38 for (int iter = 0; iter < 100; ++iter) {
39 const auto [pn, dpn] = legendre_poly(n, z);
40 const double delta = pn / dpn;
41 z -= delta;
42 if (std::abs(delta) < tol) {
43 break;
44 }
45 }
46
47 const auto [pn, dpn] = legendre_poly(n, z);
48 (void)pn;
49 const double w = 2.0 / ((1.0 - z * z) * dpn * dpn);
50
51 nodes(i) = -z;
52 nodes(n - 1 - i) = z;
53 weights(i) = w;
54 weights(n - 1 - i) = w;
55 }
56
57 return {nodes, weights};
58}
59
60} // namespace detail
61
67inline std::pair<NumericVector, NumericVector> gauss_legendre_rule(int n) {
69}
70
77inline std::pair<double, double> legendre_poly(int n, double x) {
78 if (n < 0) {
79 throw InvalidArgument("legendre_poly: n must be >= 0");
80 }
81
82 if (n == 0) {
83 return {1.0, 0.0};
84 }
85 if (n == 1) {
86 return {x, 1.0};
87 }
88
89 double p_nm1 = 1.0;
90 double p_n = x;
91 for (int k = 1; k < n; ++k) {
92 const double p_np1 =
93 ((2.0 * static_cast<double>(k) + 1.0) * x * p_n - static_cast<double>(k) * p_nm1) /
94 (static_cast<double>(k) + 1.0);
95 p_nm1 = p_n;
96 p_n = p_np1;
97 }
98
99 const double nn = static_cast<double>(n);
100 const double endpoint_tol = 64.0 * std::numeric_limits<double>::epsilon();
101 const double one_minus_abs_x = std::abs(1.0 - std::abs(x));
102
103 double dp_n = 0.0;
104 if (one_minus_abs_x < endpoint_tol) {
105 const double endpoint = 0.5 * nn * (nn + 1.0);
106 if (x >= 0.0) {
107 dp_n = endpoint;
108 } else {
109 const double sign = ((n + 1) % 2 == 0) ? 1.0 : -1.0; // (-1)^(n+1)
110 dp_n = sign * endpoint;
111 }
112 } else {
113 dp_n = nn * (x * p_n - p_nm1) / (x * x - 1.0);
114 }
115
116 return {p_n, dp_n};
117}
118
126 NumericVector out(x.size());
127 for (Eigen::Index i = 0; i < x.size(); ++i) {
128 out(i) = legendre_poly(n, x(i)).first;
129 }
130 return out;
131}
132
139 if (N < 2) {
140 throw InvalidArgument("lgl_nodes: N must be >= 2");
141 }
142
143 NumericVector nodes(N);
144 nodes(0) = -1.0;
145 nodes(N - 1) = 1.0;
146 if (N == 2) {
147 return nodes;
148 }
149
150 const int n = N - 1;
151 const double pi = std::acos(-1.0);
152 const double tol = 32.0 * std::numeric_limits<double>::epsilon();
153
154 for (int j = 1; j < N - 1; ++j) {
155 double x = -std::cos(pi * static_cast<double>(j) / static_cast<double>(N - 1));
156
157 for (int iter = 0; iter < 100; ++iter) {
158 const auto [pn, dpn] = legendre_poly(n, x);
159 const double denom = 1.0 - x * x;
160 const double ddpn = (2.0 * x * dpn - static_cast<double>(n) * (n + 1) * pn) / denom;
161
162 const double delta = dpn / ddpn;
163 x -= delta;
164
165 if (std::abs(delta) < tol) {
166 break;
167 }
168 }
169
170 nodes(j) = x;
171 }
172
173 return nodes;
174}
175
182 if (N < 2) {
183 throw InvalidArgument("cgl_nodes: N must be >= 2");
184 }
185
186 NumericVector nodes(N);
187 const double pi = std::acos(-1.0);
188 for (int j = 0; j < N; ++j) {
189 nodes(j) = -std::cos(pi * static_cast<double>(j) / static_cast<double>(N - 1));
190 }
191 return nodes;
192}
193
200inline NumericVector lgl_weights(int N, const NumericVector &nodes) {
201 if (N < 2) {
202 throw InvalidArgument("lgl_weights: N must be >= 2");
203 }
204 if (nodes.size() != N) {
205 throw InvalidArgument("lgl_weights: nodes size mismatch");
206 }
207
208 NumericVector w(N);
209 const double scale = 2.0 / (static_cast<double>(N) * static_cast<double>(N - 1));
210 const int n = N - 1;
211 for (int i = 0; i < N; ++i) {
212 const double pn = legendre_poly(n, nodes(i)).first;
213 w(i) = scale / (pn * pn);
214 }
215 return w;
216}
217
224inline NumericVector cgl_weights(int N, const NumericVector &nodes) {
225 if (N < 2) {
226 throw InvalidArgument("cgl_weights: N must be >= 2");
227 }
228 if (nodes.size() != N) {
229 throw InvalidArgument("cgl_weights: nodes size mismatch");
230 }
231
232 if (N == 2) {
233 NumericVector w(2);
234 w(0) = 1.0;
235 w(1) = 1.0;
236 return w;
237 }
238
239 const int n = N - 1;
240 const double pi = std::acos(-1.0);
241 NumericVector theta(N);
242 for (int j = 0; j < N; ++j) {
243 theta(j) = pi * static_cast<double>(j) / static_cast<double>(n);
244 }
245
246 NumericVector w = NumericVector::Zero(N);
247 NumericVector v = NumericVector::Ones(N - 2);
248
249 if (n % 2 == 0) {
250 const double w0 = 1.0 / (static_cast<double>(n) * static_cast<double>(n) - 1.0);
251 w(0) = w0;
252 w(N - 1) = w0;
253
254 for (int k = 1; k < n / 2; ++k) {
255 const double denom = 4.0 * static_cast<double>(k) * static_cast<double>(k) - 1.0;
256 for (int j = 1; j < N - 1; ++j) {
257 v(j - 1) -= 2.0 * std::cos(2.0 * static_cast<double>(k) * theta(j)) / denom;
258 }
259 }
260
261 const double denom = static_cast<double>(n) * static_cast<double>(n) - 1.0;
262 for (int j = 1; j < N - 1; ++j) {
263 v(j - 1) -= std::cos(static_cast<double>(n) * theta(j)) / denom;
264 }
265 } else {
266 const double w0 = 1.0 / (static_cast<double>(n) * static_cast<double>(n));
267 w(0) = w0;
268 w(N - 1) = w0;
269
270 for (int k = 1; k <= (n - 1) / 2; ++k) {
271 const double denom = 4.0 * static_cast<double>(k) * static_cast<double>(k) - 1.0;
272 for (int j = 1; j < N - 1; ++j) {
273 v(j - 1) -= 2.0 * std::cos(2.0 * static_cast<double>(k) * theta(j)) / denom;
274 }
275 }
276 }
277
278 for (int j = 1; j < N - 1; ++j) {
279 w(j) = 2.0 * v(j - 1) / static_cast<double>(n);
280 }
281
282 return w;
283}
284
291 const int N = static_cast<int>(nodes.size());
292 if (N < 2) {
293 throw InvalidArgument("barycentric_weights: need at least 2 nodes");
294 }
295
296 NumericVector lambda(N);
297 for (int j = 0; j < N; ++j) {
298 double prod = 1.0;
299 for (int k = 0; k < N; ++k) {
300 if (k == j) {
301 continue;
302 }
303 prod *= (nodes(j) - nodes(k));
304 }
305 lambda(j) = 1.0 / prod;
306 }
307 return lambda;
308}
309
318inline double barycentric_basis_eval(const NumericVector &nodes, const NumericVector &bary_w, int j,
319 double s) {
320 const int N = static_cast<int>(nodes.size());
321 if (N < 2) {
322 throw InvalidArgument("barycentric_basis_eval: need at least 2 nodes");
323 }
324 if (bary_w.size() != N) {
325 throw InvalidArgument("barycentric_basis_eval: barycentric weight size mismatch");
326 }
327 if (j < 0 || j >= N) {
328 throw InvalidArgument("barycentric_basis_eval: basis index out of range");
329 }
330
331 const double tol = 128.0 * std::numeric_limits<double>::epsilon() * (1.0 + std::abs(s));
332 for (int k = 0; k < N; ++k) {
333 if (std::abs(s - nodes(k)) <= tol) {
334 return (k == j) ? 1.0 : 0.0;
335 }
336 }
337
338 double denom = 0.0;
339 for (int k = 0; k < N; ++k) {
340 denom += bary_w(k) / (s - nodes(k));
341 }
342 return (bary_w(j) / (s - nodes(j))) / denom;
343}
344
355 const int N = static_cast<int>(nodes.size());
356 if (N < 2) {
357 throw InvalidArgument("birkhoff_integration_matrix: need at least 2 nodes");
358 }
359
360 const NumericVector bary_w = barycentric_weights(nodes);
361 const int n_quad = std::max(8, (N + 1) / 2 + 2);
362 const auto [quad_x, quad_w] = detail::gauss_legendre_rule(n_quad);
363
364 NumericMatrix B = NumericMatrix::Zero(N, N);
365 const double a = nodes(0);
366
367 for (int i = 0; i < N; ++i) {
368 const double b = nodes(i);
369 const double half = 0.5 * (b - a);
370 const double mid = 0.5 * (b + a);
371 if (std::abs(half) < detail::polynomial_root_tolerance) {
372 continue; // first row stays zero
373 }
374
375 for (int q = 0; q < n_quad; ++q) {
376 const double s = mid + half * quad_x(q);
377 const double wq = half * quad_w(q);
378
379 int node_match = -1;
380 const double tol = 128.0 * std::numeric_limits<double>::epsilon() * (1.0 + std::abs(s));
381 for (int k = 0; k < N; ++k) {
382 if (std::abs(s - nodes(k)) <= tol) {
383 node_match = k;
384 break;
385 }
386 }
387
388 if (node_match >= 0) {
389 B(i, node_match) += wq;
390 continue;
391 }
392
393 double denom = 0.0;
394 for (int k = 0; k < N; ++k) {
395 denom += bary_w(k) / (s - nodes(k));
396 }
397 for (int j = 0; j < N; ++j) {
398 const double ell_j = (bary_w(j) / (s - nodes(j))) / denom;
399 B(i, j) += wq * ell_j;
400 }
401 }
402 }
403
404 return B;
405}
406
413 const int N = static_cast<int>(nodes.size());
414 if (N < 2) {
415 throw InvalidArgument("spectral_diff_matrix: need at least 2 nodes");
416 }
417
418 const NumericVector lambda = barycentric_weights(nodes);
419
420 NumericMatrix D = NumericMatrix::Zero(N, N);
421 for (int i = 0; i < N; ++i) {
422 for (int j = 0; j < N; ++j) {
423 if (i == j) {
424 continue;
425 }
426 D(i, j) = (lambda(j) / lambda(i)) / (nodes(i) - nodes(j));
427 }
428 }
429
430 for (int i = 0; i < N; ++i) {
431 D(i, i) = -D.row(i).sum();
432 }
433
434 return D;
435}
436
437} // namespace janus
Custom exception hierarchy for Janus framework.
Core type aliases for numeric and symbolic Eigen/CasADi interop.
Input validation failed (e.g., mismatched sizes, invalid parameters).
Definition JanusError.hpp:31
Smooth approximation of ReLU function: softplus(x) = (1/beta) * log(1 + exp(beta * x)).
Definition Diagnostics.hpp:131
std::pair< NumericVector, NumericVector > gauss_legendre_rule(int n)
Definition OrthogonalPolynomials.hpp:23
constexpr double polynomial_root_tolerance
Tolerance for zero-length integration intervals in Birkhoff matrix.
Definition OrthogonalPolynomials.hpp:21
Definition Diagnostics.hpp:19
NumericMatrix birkhoff_integration_matrix(const NumericVector &nodes)
Compute Birkhoff integration matrix B^a for the node set.
Definition OrthogonalPolynomials.hpp:354
NumericVector legendre_poly_vec(int n, const NumericVector &x)
Evaluate P_n(x) at each x in vector.
Definition OrthogonalPolynomials.hpp:125
NumericVector cgl_weights(int N, const NumericVector &nodes)
CGL (Clenshaw-Curtis) quadrature weights on [-1, 1].
Definition OrthogonalPolynomials.hpp:224
JanusMatrix< NumericScalar > NumericMatrix
Eigen::MatrixXd equivalent.
Definition JanusTypes.hpp:66
NumericVector lgl_weights(int N, const NumericVector &nodes)
LGL quadrature weights.
Definition OrthogonalPolynomials.hpp:200
JanusVector< NumericScalar > NumericVector
Eigen::VectorXd equivalent.
Definition JanusTypes.hpp:67
std::pair< NumericVector, NumericVector > gauss_legendre_rule(int n)
Gauss-Legendre nodes and weights on [-1, 1].
Definition OrthogonalPolynomials.hpp:67
T sign(const T &x)
Computes sign of x.
Definition Arithmetic.hpp:326
double barycentric_basis_eval(const NumericVector &nodes, const NumericVector &bary_w, int j, double s)
Evaluate the j-th Lagrange basis polynomial at s using barycentric form.
Definition OrthogonalPolynomials.hpp:318
NumericMatrix spectral_diff_matrix(const NumericVector &nodes)
Spectral differentiation matrix using barycentric weights.
Definition OrthogonalPolynomials.hpp:412
NumericVector barycentric_weights(const NumericVector &nodes)
Compute barycentric interpolation weights for the given nodes.
Definition OrthogonalPolynomials.hpp:290
NumericVector lgl_nodes(int N)
Compute Legendre-Gauss-Lobatto nodes on [-1, 1].
Definition OrthogonalPolynomials.hpp:138
NumericVector cgl_nodes(int N)
Compute Chebyshev-Gauss-Lobatto nodes on [-1, 1].
Definition OrthogonalPolynomials.hpp:181
std::pair< double, double > legendre_poly(int n, double x)
Evaluate Legendre polynomial P_n(x) and derivative P'_n(x).
Definition OrthogonalPolynomials.hpp:77