Janus 2.0.0
High-performance C++20 dual-mode numerical framework
Loading...
Searching...
No Matches
Calculus.hpp
Go to the documentation of this file.
1#pragma once
7
11#include <Eigen/Dense>
12#include <optional>
13#include <type_traits>
14#include <utility>
15
16namespace janus {
17
18// --- diff(vector) ---
27template <typename Derived> auto diff(const Eigen::MatrixBase<Derived> &v) {
28 // Return expression template for efficiency
29 return (v.tail(v.size() - 1) - v.head(v.size() - 1));
30}
31
32// --- trapz(y, x) ---
43template <typename DerivedY, typename DerivedX>
44auto trapz(const Eigen::MatrixBase<DerivedY> &y, const Eigen::MatrixBase<DerivedX> &x) {
45 auto dx = (x.tail(x.size() - 1) - x.head(x.size() - 1));
46 auto mean_y = 0.5 * (y.tail(y.size() - 1) + y.head(y.size() - 1));
47
48 // Sum of element-wise product
49 return (mean_y.array() * dx.array()).sum();
50}
51
52// --- cumtrapz(y, x) ---
65template <typename DerivedY, typename DerivedX>
66auto cumtrapz(const Eigen::MatrixBase<DerivedY> &y, const Eigen::MatrixBase<DerivedX> &x) {
67 using Scalar = std::decay_t<decltype(0.5 *
68 (std::declval<typename DerivedY::Scalar>() +
69 std::declval<typename DerivedY::Scalar>()) *
70 (std::declval<typename DerivedX::Scalar>() -
71 std::declval<typename DerivedX::Scalar>()))>;
72 JanusVector<Scalar> result(y.size());
73
74 if (y.size() != x.size()) {
75 throw InvalidArgument("cumtrapz: y and x must have the same size");
76 }
77
78 if (y.size() == 0) {
79 return result;
80 }
81
82 result(0) = Scalar(0.0);
83 for (Eigen::Index i = 1; i < y.size(); ++i) {
84 const auto interval = 0.5 * (y(i - 1) + y(i)) * (x(i) - x(i - 1));
85 result(i) = result(i - 1) + interval;
86 }
87
88 return result;
89}
90
103template <typename DerivedY, JanusScalar Spacing = double>
104auto cumtrapz(const Eigen::MatrixBase<DerivedY> &y, const Spacing &dx = 1.0) {
105 using Scalar = std::decay_t<decltype(0.5 *
106 (std::declval<typename DerivedY::Scalar>() +
107 std::declval<typename DerivedY::Scalar>()) *
108 std::declval<Spacing>())>;
109 JanusVector<Scalar> result(y.size());
110
111 if (y.size() == 0) {
112 return result;
113 }
114
115 result(0) = Scalar(0.0);
116 for (Eigen::Index i = 1; i < y.size(); ++i) {
117 const auto interval = 0.5 * (y(i - 1) + y(i)) * dx;
118 result(i) = result(i - 1) + interval;
119 }
120
121 return result;
122}
123
124// --- gradient_1d(y, x) ---
138template <typename DerivedY, typename DerivedX>
139auto gradient_1d(const Eigen::MatrixBase<DerivedY> &y, const Eigen::MatrixBase<DerivedX> &x) {
140 Eigen::Index n = y.size();
141 typename DerivedY::PlainObject grad(n);
142
143 if (n < 2) {
144 if (n > 0)
145 grad.setZero();
146 return grad;
147 }
148
149 // Interior points: (y[i+1] - y[i-1]) / (x[i+1] - x[i-1])
150 if (n > 2) {
151 grad.segment(1, n - 2) =
152 (y.tail(n - 2) - y.head(n - 2)).array() / (x.tail(n - 2) - x.head(n - 2)).array();
153 }
154
155 // Boundaries (Forward/Backward difference)
156 // Forward at 0
157 grad(0) = (y(1) - y(0)) / (x(1) - x(0));
158 // Backward at n-1
159 grad(n - 1) = (y(n - 1) - y(n - 2)) / (x(n - 1) - x(n - 2));
160
161 return grad;
162}
163
177template <typename DerivedF, typename Spacing = double>
178auto gradient(const Eigen::MatrixBase<DerivedF> &f, const Spacing &dx = 1.0, int edge_order = 1,
179 int n = 1) {
180 using Scalar = typename DerivedF::Scalar;
181 using Vector = JanusVector<Scalar>;
182
183 Eigen::Index N = f.size();
184 Vector grad(N);
185
186 if (N < 2) {
187 if (N > 0)
188 grad.setZero();
189 return grad;
190 }
191
192 // Handle spacing - convert scalar to vector if needed
193 Vector dx_vec(N - 1);
194 if constexpr (std::is_arithmetic_v<Spacing>) {
195 dx_vec.setConstant(dx);
196 } else {
197 // dx is already a vector - compute differences
198 if (dx.size() == N) {
199 // dx are grid points, compute spacing
200 dx_vec = (dx.tail(N - 1) - dx.head(N - 1));
201 } else if (dx.size() == N - 1) {
202 // dx are already spacings
203 dx_vec = dx;
204 } else {
205 throw InvalidArgument("gradient: dx must be scalar, size N, or size N-1");
206 }
207 }
208
209 if (N == 2) {
210 // Special case: only 2 points
211 grad(0) = (f(1) - f(0)) / dx_vec(0);
212 grad(1) = grad(0);
213 return grad;
214 }
215
216 // Extract spacing arrays for interior calculation
217 // hm[i] = spacing before point i+1
218 // hp[i] = spacing after point i+1
219 Vector hm = dx_vec.head(N - 2); // dx[0] to dx[N-3]
220 Vector hp = dx_vec.tail(N - 2); // dx[1] to dx[N-2]
221
222 // Extract function differences
223 // dfp[i] = f[i+2] - f[i+1]
224 // dfm[i] = f[i+1] - f[i]
225 Vector dfp = f.tail(N - 2) - f.segment(1, N - 2); // f[2:] - f[1:-1]
226 Vector dfm = f.segment(1, N - 2) - f.head(N - 2); // f[1:-1] - f[:-2]
227
228 if (n == 1) {
229 // First derivative using second-order accurate formula
230 // Interior points: weighted average based on spacing
231 grad.segment(1, N - 2) =
232 (hm.array().square() * dfp.array() + hp.array().square() * dfm.array()) /
233 (hm.array() * hp.array() * (hm.array() + hp.array()));
234
235 if (edge_order == 1) {
236 // First-order accurate boundaries (forward/backward difference)
237 grad(0) = dfm(0) / hm(0);
238 grad(N - 1) = dfp(N - 3) / hp(N - 3);
239
240 } else if (edge_order == 2) {
241 // Second-order accurate boundaries
242 // First point
243 Scalar dfm_0 = dfm(0);
244 Scalar dfp_0 = dfp(0);
245 Scalar hm_0 = hm(0);
246 Scalar hp_0 = hp(0);
247 grad(0) = (2.0 * dfm_0 * hm_0 * hp_0 + dfm_0 * hp_0 * hp_0 - dfp_0 * hm_0 * hm_0) /
248 (hm_0 * hp_0 * (hm_0 + hp_0));
249
250 // Last point
251 Scalar dfm_N = dfm(N - 3);
252 Scalar dfp_N = dfp(N - 3);
253 Scalar hm_N = hm(N - 3);
254 Scalar hp_N = hp(N - 3);
255 grad(N - 1) = (-dfm_N * hp_N * hp_N + dfp_N * hm_N * hm_N + 2.0 * dfp_N * hm_N * hp_N) /
256 (hm_N * hp_N * (hm_N + hp_N));
257 } else {
258 throw InvalidArgument("gradient: edge_order must be 1 or 2");
259 }
260
261 } else if (n == 2) {
262 // Second derivative
263 grad.segment(1, N - 2) =
264 2.0 / (hm.array() + hp.array()) * (dfp.array() / hp.array() - dfm.array() / hm.array());
265
266 // For second derivative, replicate edge values
267 grad(0) = grad(1);
268 grad(N - 1) = grad(N - 2);
269
270 } else {
271 throw InvalidArgument("gradient: derivative order (n) must be 1 or 2");
272 }
273
274 return grad;
275}
276
294template <typename DerivedF, typename Spacing = double, typename Period>
295auto gradient_periodic(const Eigen::MatrixBase<DerivedF> &f, const Spacing &dx,
296 const Period &period, int edge_order = 1, int n = 1) {
297 using Scalar = typename DerivedF::Scalar;
298 using Vector = JanusVector<Scalar>;
299
300 const Eigen::Index N = f.size();
301 Vector grad(N);
302
303 if (edge_order != 1 && edge_order != 2) {
304 throw InvalidArgument("gradient_periodic: edge_order must be 1 or 2");
305 }
306 if (n != 1 && n != 2) {
307 throw InvalidArgument("gradient_periodic: derivative order (n) must be 1 or 2");
308 }
309
310 if (N < 2) {
311 if (N > 0)
312 grad.setZero();
313 return grad;
314 }
315
316 Eigen::VectorXd dx_vec(N - 1);
317 if constexpr (std::is_arithmetic_v<Spacing>) {
318 dx_vec.setConstant(static_cast<double>(dx));
319 } else {
320 if (dx.size() == N) {
321 for (Eigen::Index i = 0; i < N - 1; ++i) {
322 dx_vec(i) = static_cast<double>(dx(i + 1) - dx(i));
323 }
324 } else if (dx.size() == N - 1) {
325 for (Eigen::Index i = 0; i < N - 1; ++i) {
326 dx_vec(i) = static_cast<double>(dx(i));
327 }
328 } else {
329 throw InvalidArgument("gradient_periodic: dx must be scalar, size N, or size N-1");
330 }
331 }
332
333 if (N == 2) {
334 if (n == 1) {
335 grad(0) = (f(1) - f(0)) / dx_vec(0);
336 grad(1) = grad(0);
337 } else {
338 grad.setZero();
339 }
340 return grad;
341 }
342
343 const double wrap_dx = static_cast<double>(period) - dx_vec.sum();
344 if (wrap_dx <= 0.0) {
345 throw InvalidArgument("gradient_periodic: period must exceed the covered span. Exclude "
346 "duplicate endpoint samples.");
347 }
348
349 for (Eigen::Index i = 0; i < N; ++i) {
350 const Eigen::Index prev = (i == 0) ? N - 1 : i - 1;
351 const Eigen::Index next = (i == N - 1) ? 0 : i + 1;
352
353 const double hm = (i == 0) ? wrap_dx : dx_vec(i - 1);
354 const double hp = (i == N - 1) ? wrap_dx : dx_vec(i);
355
356 const auto dfm = f(i) - f(prev);
357 const auto dfp = f(next) - f(i);
358
359 if (n == 1) {
360 grad(i) = (hm * hm * dfp + hp * hp * dfm) / (hm * hp * (hm + hp));
361 } else {
362 grad(i) = 2.0 / (hm + hp) * (dfp / hp - dfm / hm);
363 }
364 }
365
366 return grad;
367}
368
369} // namespace janus
Scalar and element-wise arithmetic functions (abs, sqrt, pow, exp, log, etc.).
C++20 concepts constraining valid Janus scalar types.
Custom exception hierarchy for Janus framework.
Input validation failed (e.g., mismatched sizes, invalid parameters).
Definition JanusError.hpp:31
Definition Diagnostics.hpp:19
auto gradient(const Eigen::MatrixBase< DerivedF > &f, const Spacing &dx=1.0, int edge_order=1, int n=1)
Computes gradient using second-order accurate central differences.
Definition Calculus.hpp:178
auto gradient_1d(const Eigen::MatrixBase< DerivedY > &y, const Eigen::MatrixBase< DerivedX > &x)
Computes gradient of 1D data using central differences.
Definition Calculus.hpp:139
T square(const T &x)
Computes x^2 (more efficient than pow(x, 2)).
Definition Arithmetic.hpp:739
auto gradient_periodic(const Eigen::MatrixBase< DerivedF > &f, const Spacing &dx, const Period &period, int edge_order=1, int n=1)
Computes gradient with periodic boundary conditions.
Definition Calculus.hpp:295
auto diff(const Eigen::MatrixBase< Derived > &v)
Computes adjacent differences of a vector Returns a vector of size N-1 where out[i] = v[i+1] - v[i].
Definition Calculus.hpp:27
auto trapz(const Eigen::MatrixBase< DerivedY > &y, const Eigen::MatrixBase< DerivedX > &x)
Computes trapezoidal integration Approximation of integral of y(x) using trapezoidal rule.
Definition Calculus.hpp:44
Eigen::Matrix< Scalar, Eigen::Dynamic, 1 > JanusVector
Dynamic-size column vector for both numeric and symbolic backends.
Definition JanusTypes.hpp:49
auto cumtrapz(const Eigen::MatrixBase< DerivedY > &y, const Eigen::MatrixBase< DerivedX > &x)
Computes the cumulative trapezoidal integral.
Definition Calculus.hpp:66