riccaticpp
Loading...
Searching...
No Matches
step.hpp
Go to the documentation of this file.
1#ifndef INCLUDE_RICCATI_STEP_HPP
2#define INCLUDE_RICCATI_STEP_HPP
3
5#include <Eigen/Dense>
6#include <complex>
7#include <cmath>
8#include <tuple>
9
10namespace riccati {
11
49template <typename SolverInfo, typename Scalar, typename YScalar>
50inline auto nonosc_step(SolverInfo &&info, Scalar x0, Scalar h, YScalar y0,
51 YScalar dy0, Scalar epsres) {
52 using complex_t = std::complex<Scalar>;
53
54 Scalar maxerr = 10 * epsres;
55 auto N = info.nini_;
56 auto Nmax = info.nmax_;
57 auto cheby = spectral_chebyshev(info, x0, h, y0, dy0, 0);
58 auto yprev = std::get<0>(cheby);
59 auto dyprev = std::get<1>(cheby);
60 auto xprev = std::get<2>(cheby);
61 int iter = 0;
62 while (maxerr > epsres) {
63 iter++;
64 N *= 2;
65 if (N > Nmax) {
66 return std::make_tuple(false, complex_t(0.0, 0.0), complex_t(0.0, 0.0),
67 maxerr, yprev, dyprev, iter);
68 }
69 auto cheb_num = static_cast<int>(std::log2(N / info.nini_));
70 auto cheby2 = spectral_chebyshev(info, x0, h, y0, dy0, cheb_num);
71 auto y = std::get<0>(std::move(cheby2));
72 auto dy = std::get<1>(std::move(cheby2));
73 auto x = std::get<2>(std::move(cheby2));
74 maxerr = std::abs((yprev(0) - y(0)) / y(0));
75 if (std::isnan(maxerr)) {
76 maxerr = std::numeric_limits<Scalar>::infinity();
77 }
78 yprev = std::move(y);
79 dyprev = std::move(dy);
80 xprev = std::move(x);
81 }
82 return std::make_tuple(true, yprev(0), dyprev(0), maxerr, yprev, dyprev,
83 iter);
84}
85
136template <bool DenseOut, typename SolverInfo, typename OmegaVec,
137 typename GammaVec, typename Scalar, typename YScalar>
138inline auto osc_step(SolverInfo &&info, OmegaVec &&omega_s, GammaVec &&gamma_s,
139 Scalar x0, Scalar h, YScalar y0, YScalar dy0,
140 Scalar epsres) {
141 using complex_t = std::complex<Scalar>;
142 using vectorc_t = vector_t<complex_t>;
143 bool success = true;
144 auto &&Dn = info.Dn();
145 auto y = eval(info.alloc_, complex_t(0.0, 1.0) * omega_s);
146 auto delta = [&](const auto &r, const auto &y) {
147 return (-r.array() / (Scalar{2.0} * (y.array() + gamma_s.array())))
148 .matrix()
149 .eval();
150 };
151 auto R = [&](const auto &d) {
152 return Scalar{2.0} / h * (Dn * d) + d.array().square().matrix();
153 };
154 auto Ry
155 = (complex_t(0.0, 1.0) * Scalar{2.0}
156 * (Scalar{1.0} / h * (Dn * omega_s) + gamma_s.cwiseProduct(omega_s)))
157 .eval();
158 Scalar maxerr = Ry.array().abs().maxCoeff();
159
160 arena_matrix<vectorc_t> deltay(info.alloc_, Ry.size(), 1);
161 Scalar prev_err = std::numeric_limits<Scalar>::infinity();
162 while (maxerr > epsres) {
163 deltay = delta(Ry, y);
164 y += deltay;
165 Ry = R(deltay);
166 maxerr = Ry.array().abs().maxCoeff();
167 if (maxerr >= (Scalar{2.0} * prev_err)) {
168 success = false;
169 break;
170 }
171 prev_err = maxerr;
172 }
173 if constexpr (DenseOut) {
174 auto u1
175 = eval(info.alloc_, h / Scalar{2.0} * (info.integration_matrix_ * y));
176 auto f1 = eval(info.alloc_, (u1).array().exp().matrix());
177 auto f2 = eval(info.alloc_, f1.conjugate());
178 auto du2 = eval(info.alloc_, y.conjugate());
179 auto ap_top = (dy0 - y0 * du2(du2.size() - 1));
180 auto ap_bottom = (y(y.size() - 1) - du2(du2.size() - 1));
181 auto ap = ap_top / ap_bottom;
182 auto am = (dy0 - y0 * y(y.size() - 1))
183 / (du2(du2.size() - 1) - y(y.size() - 1));
184 auto y1 = eval(info.alloc_, ap * f1 + am * f2);
185 auto dy1 = eval(info.alloc_,
186 ap * y.cwiseProduct(f1) + am * du2.cwiseProduct(f2));
187 Scalar phase = std::imag(f1(0));
188 return std::make_tuple(success, y1(0), dy1(0), maxerr, phase, u1, y,
189 std::make_pair(ap, am));
190 } else {
191 complex_t f1 = std::exp(h / Scalar{2.0} * (info.quadwts_.dot(y)));
192 auto f2 = std::conj(f1);
193 auto du2 = y.conjugate().eval();
194 auto ap_top = (dy0 - y0 * du2(du2.size() - 1));
195 auto ap_bottom = (y(y.size() - 1) - du2(du2.size() - 1));
196 auto ap = ap_top / ap_bottom;
197 auto am = (dy0 - y0 * y(y.size() - 1))
198 / (du2(du2.size() - 1) - y(y.size() - 1));
199 auto y1 = (ap * f1 + am * f2);
200 auto dy1 = (ap * y * f1 + am * du2 * f2).eval();
201 Scalar phase = std::imag(f1);
202 return std::make_tuple(success, y1, dy1(0), maxerr, phase,
203 arena_matrix<vectorc_t>(info.alloc_, y.size()),
204 arena_matrix<vectorc_t>(info.alloc_, y.size()),
205 std::make_pair(ap, am));
206 }
207}
208
272template <typename SolverInfo, typename Scalar, typename Vec>
273inline auto step(SolverInfo &info, Scalar xi, Scalar xf,
274 std::complex<Scalar> yi, std::complex<Scalar> dyi, Scalar eps,
275 Scalar epsilon_h, Scalar init_stepsize, Vec &&x_eval,
276 bool hard_stop = false) {
277 using vectord_t = vector_t<Scalar>;
278 using complex_t = std::complex<Scalar>;
279 using vectorc_t = vector_t<complex_t>;
280 Scalar direction = init_stepsize > 0 ? 1 : -1;
281 if (init_stepsize * (xf - xi) < 0) {
282 throw std::domain_error(
283 "Direction of integration does not match stepsize sign,"
284 " adjusting it so that integration happens from xi to xf.");
285 }
286 // Check that yeval and x_eval are right size
287 constexpr bool dense_output = compile_size_v<Vec> != 0;
288 if constexpr (dense_output) {
289 if (!x_eval.size()) {
290 throw std::domain_error("Dense output requested but x_eval is size 0!");
291 }
292 // TODO: Better error messages
293 auto x_eval_max = (direction * x_eval.maxCoeff());
294 auto x_eval_min = (direction * x_eval.minCoeff());
295 auto xi_intdir = direction * xi;
296 auto xf_intdir = direction * xf;
297 const bool high_range_err = xf_intdir < x_eval_max;
298 const bool low_range_err = xi_intdir > x_eval_min;
299 if (high_range_err || low_range_err) {
300 if (high_range_err && low_range_err) {
301 throw std::out_of_range(
302 std::string{"The max and min of the output points ("}
303 + std::to_string(x_eval_min) + std::string{", "}
304 + std::to_string(x_eval_max)
305 + ") lie outside the high and low of the integration range ("
306 + std::to_string(xi_intdir) + std::string{", "}
307 + std::to_string(xf_intdir) + ")!");
308 }
309 if (high_range_err) {
310 throw std::out_of_range(
311 std::string{"The max of the output points ("}
312 + std::to_string(x_eval_max)
313 + ") lies outside the high of the integration range ("
314 + std::to_string(xf_intdir) + ")!");
315 }
316 if (low_range_err) {
317 throw std::out_of_range(
318 std::string{"The min of the output points ("}
319 + std::to_string(x_eval_min)
320 + ") lies outside the low of the integration range ("
321 + std::to_string(xi_intdir) + ")!");
322 }
323 }
324 }
325
326 // Initialize vectors for storing results
327 Scalar xs;
328 complex_t ys;
329 complex_t dys;
330 int successes;
331 int steptypes;
332 Scalar phases;
333 Eigen::Matrix<complex_t, -1, 1> yeval; //(x_eval.size());
334 Eigen::Matrix<complex_t, -1, 1> dyeval; //(x_eval.size());
335
336 complex_t y = yi;
337 complex_t dy = dyi;
338 complex_t yprev = y;
339 complex_t dyprev = dy;
340 auto scale_xi = scale(info.xp().array(), xi, init_stepsize).eval();
341 auto omega_is = omega(info, scale_xi).eval();
342 auto gamma_is = gamma(info, scale_xi).eval();
343 Scalar wi = omega_is.mean();
344 Scalar gi = gamma_is.mean();
345 Scalar dwi = (2.0 / init_stepsize * (info.Dn() * omega_is)).mean();
346 Scalar dgi = (2.0 / init_stepsize * (info.Dn() * gamma_is)).mean();
347 Scalar hslo_ini = direction
348 * std::min(static_cast<Scalar>(1e8),
349 static_cast<Scalar>(std::abs(1.0 / wi)));
350 Scalar hosc_ini
351 = direction
352 * std::min(std::min(static_cast<Scalar>(1e8),
353 static_cast<Scalar>(std::abs(wi / dwi))),
354 std::abs(gi / dgi));
355
356 if (hard_stop) {
357 if (direction * (xi + hosc_ini) > direction * xf) {
358 hosc_ini = xf - xi;
359 }
360 if (direction * (xi + hslo_ini) > direction * xf) {
361 hslo_ini = xf - xi;
362 }
363 }
364 auto hslo = choose_nonosc_stepsize(info, xi, hslo_ini, Scalar(0.2));
365 // o and g written here
366 auto osc_step_tup = choose_osc_stepsize(info, xi, hosc_ini, epsilon_h);
367 auto hosc = std::get<0>(osc_step_tup);
368 // NOTE: Calling choose_osc_stepsize will update these values
369 auto &&omega_n = std::get<1>(osc_step_tup);
370 auto &&gamma_n = std::get<2>(osc_step_tup);
371 Scalar xcurrent = xi;
372 Scalar wnext = wi;
373 using matrixc_t = matrix_t<complex_t>;
374 matrixc_t y_eval;
375 matrixc_t dy_eval;
376 arena_matrix<vectorc_t> un(info.alloc_, omega_n.size(), 1);
377 arena_matrix<vectorc_t> d_un(info.alloc_, omega_n.size(), 1);
378 std::pair<complex_t, complex_t> a_pair;
379 Scalar phase{0.0};
380 bool success = false;
381 bool steptype = true;
382 Scalar err;
383 int cheb_N = 0;
384 if ((direction * hosc > direction * hslo * 5.0)
385 && (direction * hosc * wnext / (2.0 * pi<Scalar>()) > 1.0)) {
386 if (hard_stop) {
387 if (direction * (xcurrent + hosc) > direction * xf) {
388 hosc = xf - xcurrent;
389 auto xp_scaled = scale(info.xp().array(), xcurrent, hosc).eval();
390 omega_n = omega(info, xp_scaled);
391 gamma_n = gamma(info, xp_scaled);
392 }
393 if (direction * (xcurrent + hslo) > direction * xf) {
394 hslo = xf - xcurrent;
395 }
396 }
397 // o and g read here
398 std::tie(success, y, dy, err, phase, un, d_un, a_pair)
399 = osc_step<dense_output>(info, omega_n, gamma_n, xcurrent, hosc, yprev,
400 dyprev, eps);
401 steptype = 1;
402 }
403 while (!success) {
404 std::tie(success, y, dy, err, y_eval, dy_eval, cheb_N)
405 = nonosc_step(info, xcurrent, hslo, yprev, dyprev, eps);
406 steptype = 0;
407 if (!success) {
408 hslo *= Scalar{0.5};
409 }
410 if (direction * hslo < 1e-16) {
411 throw std::domain_error("Stepsize became to small error");
412 }
413 }
414 auto h = steptype ? hosc : hslo;
415 if constexpr (dense_output) {
416 Eigen::Index dense_size = 0;
417 Eigen::Index dense_start = 0;
418 // Assuming x_eval is sorted we just want start and size
419 std::tie(dense_start, dense_size)
420 = get_slice(x_eval, direction * xcurrent, direction * (xcurrent + h));
421 yeval = Eigen::Matrix<complex_t, -1, 1>(dense_size);
422 dyeval = Eigen::Matrix<complex_t, -1, 1>(dense_size);
423 if (dense_size != 0) {
424 auto x_eval_map
425 = Eigen::Map<vectord_t>(x_eval.data() + dense_start, dense_size);
426 auto y_eval_map
427 = Eigen::Map<vectorc_t>(yeval.data() + dense_start, dense_size);
428 auto dy_eval_map
429 = Eigen::Map<vectorc_t>(dyeval.data() + dense_start, dense_size);
430 if (steptype) {
431 auto x_eval_scaled
432 = eval(info.alloc_,
433 (2.0 / h * (x_eval_map.array() - xcurrent) - 1.0).matrix());
434 auto Linterp = interpolate(info.xn(), x_eval_scaled, info.alloc_);
435 auto fdense = eval(info.alloc_, (Linterp * un).array().exp().matrix());
436 y_eval_map = a_pair.first * fdense + a_pair.second * fdense.conjugate();
437 auto du_dense = eval(info.alloc_, (Linterp * d_un));
438 dy_eval_map
439 = a_pair.first * (du_dense.array() * fdense.array())
440 + a_pair.second * (du_dense.array() * fdense.array()).conjugate();
441 } else {
442 auto xc_scaled = eval(
443 info.alloc_,
444 scale(std::get<2>(info.chebyshev_[cheb_N]), xcurrent, h).matrix());
445 auto Linterp = interpolate(xc_scaled, x_eval_map, info.alloc_);
446 y_eval_map = Linterp * y_eval;
447 }
448 }
449 }
450 // Finish appending and ending conditions
451 ys = y;
452 dys = dy;
453 xs = xcurrent + h;
454 phases = phase;
455 steptypes = steptype;
456 successes = success;
457 Scalar dwnext;
458 Scalar gnext;
459 Scalar dgnext;
460 if (steptype) {
461 wnext = omega_n[0];
462 gnext = gamma_n[0];
463 dwnext = 2.0 / h * info.Dn().row(0).dot(omega_n);
464 dgnext = 2.0 / h * info.Dn().row(0).dot(gamma_n);
465 } else {
466 wnext = omega(info, xcurrent + h);
467 gnext = gamma(info, xcurrent + h);
468 auto xn_scaled = scale(info.xn().array(), xcurrent, h).eval();
469 dwnext = 2.0 / h * info.Dn().row(0).dot(omega(info, xn_scaled).matrix());
470 dgnext = 2.0 / h * info.Dn().row(0).dot(gamma(info, (xn_scaled).matrix()));
471 }
472 xcurrent += h;
473 if (direction * xcurrent < direction * xf) {
474 hslo_ini = direction * std::min(Scalar{1e8}, std::abs(Scalar{1.0} / wnext));
475 hosc_ini = direction
476 * std::min(std::min(Scalar{1e8}, std::abs(wnext / dwnext)),
477 std::abs(gnext / dgnext));
478 if (hard_stop) {
479 if (direction * (xcurrent + hosc_ini) > direction * xf) {
480 hosc_ini = xf - xcurrent;
481 }
482 if (direction * (xcurrent + hslo_ini) > direction * xf) {
483 hslo_ini = xf - xcurrent;
484 }
485 }
486 // o and g written here
487 osc_step_tup = choose_osc_stepsize(info, xcurrent, hosc_ini, epsilon_h);
488 hosc = std::get<0>(osc_step_tup);
489 hslo = choose_nonosc_stepsize(info, xcurrent, hslo_ini, Scalar{0.2});
490 yprev = y;
491 dyprev = dy;
492 }
493 info.alloc_.recover_memory();
494 return std::make_tuple(xs, ys, dys, hosc, hslo, successes, phases, steptypes,
495 yeval);
496}
497
498} // namespace riccati
499
500#endif
std::vector< std::tuple< Integral, matrixd_t, vectord_t > > chebyshev_
Definition solver.hpp:59
RICCATI_ALWAYS_INLINE const auto & xp() const noexcept
Definition solver.hpp:237
Allocator alloc_
Definition solver.hpp:55
RICCATI_ALWAYS_INLINE const auto & Dn() const noexcept
Definition solver.hpp:214
RICCATI_ALWAYS_INLINE const auto & xn() const noexcept
Definition solver.hpp:227
auto step(SolverInfo &info, Scalar xi, Scalar xf, std::complex< Scalar > yi, std::complex< Scalar > dyi, Scalar eps, Scalar epsilon_h, Scalar init_stepsize, Vec &&x_eval, bool hard_stop=false)
Solves the differential equation y'' + 2gy' + w^2y = 0 over a given interval.
Definition step.hpp:273
constexpr Eigen::Index compile_size_v
Definition utils.hpp:14
FloatingPoint choose_nonosc_stepsize(SolverInfo &&info, FloatingPoint x0, FloatingPoint h, FloatingPoint epsilon_h)
Definition stepsize.hpp:27
auto eval(arena_allocator< T, arena_alloc > &arena, const Expr &expr) noexcept
RICCATI_ALWAYS_INLINE auto spectral_chebyshev(SolverInfo &&info, Scalar x0, Scalar h, YScalar y0, YScalar dy0, Integral niter)
Applies a spectral collocation method based on Chebyshev nodes for solving differential equations.
Eigen::Matrix< Scalar, -1, -1 > matrix_t
Definition utils.hpp:48
auto nonosc_step(SolverInfo &&info, Scalar x0, Scalar h, YScalar y0, YScalar dy0, Scalar epsres)
Performs a single Chebyshev step with adaptive node count for solving differential equations.
Definition step.hpp:50
auto get_slice(T &&x_eval, Scalar start, Scalar end)
Definition utils.hpp:113
auto gamma(SolverInfo &&info, const Scalar &x)
Definition solver.hpp:25
auto choose_osc_stepsize(SolverInfo &&info, FloatingPoint x0, FloatingPoint h, FloatingPoint epsilon_h)
Chooses an appropriate step size for the Riccati step based on the accuracy of Chebyshev interpolatio...
Definition stepsize.hpp:64
auto omega(SolverInfo &&info, const Scalar &x)
Definition solver.hpp:33
auto scale(Vector &&x, Scalar x0, Scalar h)
Scales and shifts a vector of Chebyshev nodes.
Definition utils.hpp:40
Eigen::Matrix< Scalar, -1, 1 > vector_t
Definition utils.hpp:50
constexpr T pi()
Definition utils.hpp:63
auto osc_step(SolverInfo &&info, OmegaVec &&omega_s, GammaVec &&gamma_s, Scalar x0, Scalar h, YScalar y0, YScalar dy0, Scalar epsres)
Performs a single Riccati step for solving differential equations with oscillatory behavior.
Definition step.hpp:138
auto matrix(T x)
Definition utils.hpp:211
RICCATI_ALWAYS_INLINE auto interpolate(Vec1 &&s, Vec2 &&t, Allocator &&alloc)
Creates an interpolation matrix from an array of source nodes to target nodes.