riccaticpp
Loading...
Searching...
No Matches
evolve.hpp
Go to the documentation of this file.
1#ifndef INCLUDE_RICCATI_EVOLVE_HPP
2#define INCLUDE_RICCATI_EVOLVE_HPP
3
5#include <riccati/step.hpp>
7#include <riccati/utils.hpp>
8#include <complex>
9#include <type_traits>
10#include <tuple>
11
12namespace riccati {
13
68template <typename SolverInfo, typename Scalar, typename Vec>
69inline auto osc_evolve(SolverInfo &&info, Scalar xi, Scalar xf,
70 std::complex<Scalar> yi, std::complex<Scalar> dyi,
71 Scalar eps, Scalar epsilon_h, Scalar init_stepsize,
72 Vec &&x_eval, bool hard_stop = false) {
73 int sign = init_stepsize > 0 ? 1 : -1;
74 using complex_t = std::complex<Scalar>;
75 using vectorc_t = vector_t<complex_t>;
76 auto xi_scaled
77 = eval(info.alloc_, scale(info.xn().array(), xi, init_stepsize).matrix());
78 // Frequency and friction functions evaluated at n+1 Chebyshev nodes
79 auto omega_n = eval(info.alloc_, omega(info, xi_scaled));
80 auto gamma_n = eval(info.alloc_, gamma(info, xi_scaled));
81 vectorc_t yeval;
82 vectorc_t dyeval;
83 // TODO: Add this check to regular evolve
84 if (sign * (xi + init_stepsize) > sign * xf) {
85 throw std::out_of_range(
86 std::string("Stepsize (") + std::to_string(init_stepsize)
87 + std::string(") is too large for integration range (")
88 + std::to_string(xi) + std::string(", ") + std::to_string(xf)
89 + std::string(")!"));
90 }
91 // o and g read here
92 constexpr bool dense_output = compile_size_v<Vec> != 0;
93 auto osc_ret = osc_step<dense_output>(info, omega_n, gamma_n, xi,
94 init_stepsize, yi, dyi, eps);
95 if (std::get<0>(osc_ret) == 0) {
96 return std::make_tuple(false, xi, init_stepsize, osc_ret, vectorc_t(0),
97 vectorc_t(0), static_cast<Eigen::Index>(0),
98 static_cast<Eigen::Index>(0));
99 } else {
100 Eigen::Index dense_size = 0;
101 Eigen::Index dense_start = 0;
102 if constexpr (dense_output) {
103 // Assuming x_eval is sorted we just want start and size
104 std::tie(dense_start, dense_size)
105 = get_slice(x_eval, sign * xi, sign * (xi + init_stepsize));
106 if (dense_size != 0) {
107 auto x_eval_map = x_eval.segment(dense_start, dense_size);
108 auto x_eval_scaled = eval(
109 info.alloc_,
110 (2.0 / init_stepsize * (x_eval_map.array() - xi) - 1.0).matrix());
111 auto Linterp = interpolate(info.xn(), x_eval_scaled, info.alloc_);
112 auto fdense
113 = eval(info.alloc_,
114 (Linterp * std::get<5>(osc_ret)).array().exp().matrix());
115 yeval = std::get<7>(osc_ret).first * fdense
116 + std::get<7>(osc_ret).second * fdense.conjugate();
117 auto du_dense
118 = eval(info.alloc_, (Linterp * std::get<6>(osc_ret))).array();
119 dyeval
120 = std::get<7>(osc_ret).first * (du_dense.array() * fdense.array())
121 + std::get<7>(osc_ret).second
122 * (du_dense.array() * fdense.array()).conjugate();
123 }
124 }
125 auto x_next = xi + init_stepsize;
126 // o and g read here
127 auto wnext = omega_n[0];
128 auto gnext = gamma_n[0];
129 auto dwnext = 2.0 / init_stepsize * info.Dn().row(0).dot(omega_n);
130 auto dgnext = 2.0 / init_stepsize * info.Dn().row(0).dot(gamma_n);
131 auto hosc_ini = sign
132 * std::min(std::min(1e8, std::abs(wnext / dwnext)),
133 std::abs(gnext / dgnext));
134 if (sign * (x_next + hosc_ini) > sign * xf) {
135 hosc_ini = xf - x_next;
136 }
137 // o and g written here
138 auto h_next = choose_osc_stepsize(info, x_next, hosc_ini, epsilon_h);
139 // TODO: CANNOT RETURN ARENA MATRIX FROM OSC_RET
140 return std::make_tuple(true, x_next, std::get<0>(h_next), osc_ret, yeval,
141 dyeval, dense_start, dense_size);
142 }
143}
144
198template <typename SolverInfo, typename Scalar, typename Vec>
199inline auto nonosc_evolve(SolverInfo &&info, Scalar xi, Scalar xf,
200 std::complex<Scalar> yi, std::complex<Scalar> dyi,
201 Scalar eps, Scalar epsilon_h, Scalar init_stepsize,
202 Vec &&x_eval, bool hard_stop = false) {
203 using complex_t = std::complex<Scalar>;
204 using vectorc_t = vector_t<complex_t>;
205 vectorc_t yeval;
206 vectorc_t dyeval;
207 // TODO: Add this check to regular evolve
208 const int sign = init_stepsize > 0 ? 1 : -1;
209 if (sign * (xi + init_stepsize) > sign * xf) {
210 throw std::out_of_range(
211 std::string("Stepsize (") + std::to_string(init_stepsize)
212 + std::string(") is too large for integration range (")
213 + std::to_string(xi) + std::string(", ") + std::to_string(xf)
214 + std::string(")!"));
215 }
216 auto nonosc_ret = nonosc_step(info, xi, init_stepsize, yi, dyi, eps);
217 if (!std::get<0>(nonosc_ret)) {
218 return std::make_tuple(false, xi, init_stepsize, nonosc_ret, vectorc_t(0),
219 vectorc_t(0), static_cast<Eigen::Index>(0),
220 static_cast<Eigen::Index>(0));
221 } else {
222 Eigen::Index dense_size = 0;
223 Eigen::Index dense_start = 0;
224 if constexpr (compile_size_v<Vec> != 0) {
225 // Assuming x_eval is sorted we just want start and size
226 std::tie(dense_start, dense_size)
227 = get_slice(x_eval, sign * xi, sign * (xi + init_stepsize));
228 if (dense_size != 0) {
229 auto x_eval_map = x_eval.segment(dense_start, dense_size);
230
231 auto xi_scaled
232 = (xi + init_stepsize / 2
233 + (init_stepsize / 2)
234 * std::get<2>(info.chebyshev_[std::get<6>(nonosc_ret)])
235 .array())
236 .matrix()
237 .eval();
238 auto Linterp = interpolate(xi_scaled, x_eval_map, info.alloc_);
239 yeval = Linterp * std::get<4>(nonosc_ret);
240 dyeval = Linterp * std::get<5>(nonosc_ret);
241 }
242 }
243 auto x_next = xi + init_stepsize;
244 auto wnext = omega(info, xi + init_stepsize);
245 auto hslo_ini = sign * std::min(1e8, std::abs(1.0 / wnext));
246 if (sign * (x_next + hslo_ini) > sign * xf) {
247 hslo_ini = xf - x_next;
248 }
249 auto h_next = choose_nonosc_stepsize(info, x_next, hslo_ini, epsilon_h);
250 return std::make_tuple(true, x_next, h_next, nonosc_ret, yeval, dyeval,
251 dense_start, dense_size);
252 }
253}
254
318template <typename SolverInfo, typename Scalar, typename Vec>
319inline auto evolve(SolverInfo &info, Scalar xi, Scalar xf,
320 std::complex<Scalar> yi, std::complex<Scalar> dyi,
321 Scalar eps, Scalar epsilon_h, Scalar init_stepsize,
322 Vec &&x_eval, bool hard_stop = false) {
323 using vectord_t = vector_t<Scalar>;
324 Scalar direction = init_stepsize > 0 ? 1 : -1;
325 if (init_stepsize * (xf - xi) < 0) {
326 throw std::domain_error(
327 "Direction of integration does not match stepsize sign,"
328 " adjusting it so that integration happens from xi to xf.");
329 }
330 // Check that yeval and x_eval are right size
331 constexpr bool dense_output = compile_size_v<Vec> != 0;
332 if constexpr (dense_output) {
333 if (!x_eval.size()) {
334 throw std::domain_error("Dense output requested but x_eval is size 0!");
335 }
336 // TODO: Better error messages
337 auto x_eval_max = (direction * x_eval.maxCoeff());
338 auto x_eval_min = (direction * x_eval.minCoeff());
339 auto xi_intdir = direction * xi;
340 auto xf_intdir = direction * xf;
341 const bool high_range_err = xf_intdir < x_eval_max;
342 const bool low_range_err = xi_intdir > x_eval_min;
343 if (high_range_err || low_range_err) {
344 if (high_range_err && low_range_err) {
345 throw std::out_of_range(
346 std::string{"The max and min of the output points ("}
347 + std::to_string(x_eval_min) + std::string{", "}
348 + std::to_string(x_eval_max)
349 + ") lie outside the high and low of the integration range ("
350 + std::to_string(xi_intdir) + std::string{", "}
351 + std::to_string(xf_intdir) + ")!");
352 }
353 if (high_range_err) {
354 throw std::out_of_range(
355 std::string{"The max of the output points ("}
356 + std::to_string(x_eval_max)
357 + ") lies outside the high of the integration range ("
358 + std::to_string(xf_intdir) + ")!");
359 }
360 if (low_range_err) {
361 throw std::out_of_range(
362 std::string{"The min of the output points ("}
363 + std::to_string(x_eval_min)
364 + ") lies outside the low of the integration range ("
365 + std::to_string(xi_intdir) + ")!");
366 }
367 }
368 }
369
370 // Initialize vectors for storing results
371 std::size_t output_size = 100;
372 using stdvecd_t = std::vector<Scalar>;
373 stdvecd_t xs;
374 xs.reserve(output_size);
375 using complex_t = std::complex<Scalar>;
376 using stdvecc_t = std::vector<complex_t>;
377 stdvecc_t ys;
378 ys.reserve(output_size);
379 stdvecc_t dys;
380 dys.reserve(output_size);
381 std::vector<int> successes;
382 successes.reserve(output_size);
383 std::vector<int> steptypes;
384 steptypes.reserve(output_size);
385 stdvecd_t phases;
386 phases.reserve(output_size);
387 using vectorc_t = vector_t<complex_t>;
388 vectorc_t yeval(x_eval.size());
389 vectorc_t dyeval(x_eval.size());
390
391 complex_t y = yi;
392 complex_t dy = dyi;
393 complex_t yprev = y;
394 complex_t dyprev = dy;
395 auto scale_xi = scale(info.xp().array(), xi, init_stepsize).eval();
396 auto omega_is = omega(info, scale_xi).eval();
397 auto gamma_is = gamma(info, scale_xi).eval();
398 Scalar wi = omega_is.mean();
399 Scalar gi = gamma_is.mean();
400 Scalar dwi = (2.0 / init_stepsize * (info.Dn() * omega_is)).mean();
401 Scalar dgi = (2.0 / init_stepsize * (info.Dn() * gamma_is)).mean();
402 Scalar hslo_ini = direction
403 * std::min(static_cast<Scalar>(1e8),
404 static_cast<Scalar>(std::abs(1.0 / wi)));
405 Scalar hosc_ini
406 = direction
407 * std::min(std::min(static_cast<Scalar>(1e8),
408 static_cast<Scalar>(std::abs(wi / dwi))),
409 std::abs(gi / dgi));
410
411 if (hard_stop) {
412 if (direction * (xi + hosc_ini) > direction * xf) {
413 hosc_ini = xf - xi;
414 }
415 if (direction * (xi + hslo_ini) > direction * xf) {
416 hslo_ini = xf - xi;
417 }
418 }
419 auto hslo = choose_nonosc_stepsize(info, xi, hslo_ini, Scalar(0.2));
420 // o and g written here
421 auto osc_step_tup = choose_osc_stepsize(info, xi, hosc_ini, epsilon_h);
422 auto hosc = std::get<0>(osc_step_tup);
423 // NOTE: Calling choose_osc_stepsize will update these values
424 auto &&omega_n = std::get<1>(osc_step_tup);
425 auto &&gamma_n = std::get<2>(osc_step_tup);
426 Scalar xcurrent = xi;
427 Scalar wnext = wi;
428 using matrixc_t = matrix_t<complex_t>;
429 matrixc_t y_eval;
430 matrixc_t dy_eval;
431 arena_matrix<vectorc_t> un(info.alloc_, omega_n.size(), 1);
432 arena_matrix<vectorc_t> d_un(info.alloc_, omega_n.size(), 1);
433 std::pair<complex_t, complex_t> a_pair;
434 while (std::abs(xcurrent - xf) > Scalar(1e-8)
435 && direction * xcurrent < direction * xf) {
436 Scalar phase{0.0};
437 bool success = false;
438 bool steptype = true;
439 Scalar err;
440 int cheb_N = 0;
441 if ((direction * hosc > direction * hslo * 5.0)
442 && (direction * hosc * wnext / (2.0 * pi<Scalar>()) > 1.0)) {
443 if (hard_stop) {
444 if (direction * (xcurrent + hosc) > direction * xf) {
445 hosc = xf - xcurrent;
446 auto xp_scaled = scale(info.xp().array(), xcurrent, hosc).eval();
447 omega_n = omega(info, xp_scaled);
448 gamma_n = gamma(info, xp_scaled);
449 }
450 if (direction * (xcurrent + hslo) > direction * xf) {
451 hslo = xf - xcurrent;
452 }
453 }
454 // o and g read here
455 std::tie(success, y, dy, err, phase, un, d_un, a_pair)
456 = osc_step<dense_output>(info, omega_n, gamma_n, xcurrent, hosc,
457 yprev, dyprev, eps);
458 steptype = 1;
459 }
460 while (!success) {
461 std::tie(success, y, dy, err, y_eval, dy_eval, cheb_N)
462 = nonosc_step(info, xcurrent, hslo, yprev, dyprev, eps);
463 steptype = 0;
464 if (!success) {
465 hslo *= Scalar{0.5};
466 }
467 if (direction * hslo < 1e-16) {
468 throw std::domain_error("Stepsize became to small error");
469 }
470 }
471 auto h = steptype ? hosc : hslo;
472 if constexpr (dense_output) {
473 Eigen::Index dense_size = 0;
474 Eigen::Index dense_start = 0;
475 // Assuming x_eval is sorted we just want start and size
476 std::tie(dense_start, dense_size)
477 = get_slice(x_eval, xcurrent, (xcurrent + h));
478 if (dense_size != 0) {
479 auto x_eval_map
480 = Eigen::Map<vectord_t>(x_eval.data() + dense_start, dense_size);
481 auto y_eval_map
482 = Eigen::Map<vectorc_t>(yeval.data() + dense_start, dense_size);
483 auto dy_eval_map
484 = Eigen::Map<vectorc_t>(dyeval.data() + dense_start, dense_size);
485 if (steptype) {
486 auto x_eval_scaled = eval(
487 info.alloc_,
488 (2.0 / h * (x_eval_map.array() - xcurrent) - 1.0).matrix());
489 auto Linterp = interpolate(info.xn(), x_eval_scaled, info.alloc_);
490 auto fdense
491 = eval(info.alloc_, (Linterp * un).array().exp().matrix());
492 y_eval_map
493 = a_pair.first * fdense + a_pair.second * fdense.conjugate();
494 auto du_dense = eval(info.alloc_, (Linterp * d_un));
495 dy_eval_map = a_pair.first * (du_dense.array() * fdense.array())
496 + a_pair.second
497 * (du_dense.array() * fdense.array()).conjugate();
498 } else {
499 auto xc_scaled
500 = eval(info.alloc_,
501 scale(std::get<2>(info.chebyshev_[cheb_N]), xcurrent, h)
502 .matrix());
503 auto Linterp = interpolate(xc_scaled, x_eval_map, info.alloc_);
504 y_eval_map = Linterp * y_eval;
505 dy_eval_map = Linterp * dy_eval;
506 }
507 }
508 }
509 // Finish appending and ending conditions
510 ys.push_back(y);
511 dys.push_back(dy);
512 xs.push_back(xcurrent + h);
513 phases.push_back(phase);
514 steptypes.push_back(steptype);
515 successes.push_back(success);
516 Scalar dwnext;
517 Scalar gnext;
518 Scalar dgnext;
519 if (steptype) {
520 wnext = omega_n[0];
521 gnext = gamma_n[0];
522 dwnext = 2.0 / h * info.Dn().row(0).dot(omega_n);
523 dgnext = 2.0 / h * info.Dn().row(0).dot(gamma_n);
524 } else {
525 wnext = omega(info, xcurrent + h);
526 gnext = gamma(info, xcurrent + h);
527 auto xn_scaled = scale(info.xn().array(), xcurrent, h).eval();
528 dwnext = 2.0 / h * info.Dn().row(0).dot(omega(info, xn_scaled).matrix());
529 dgnext
530 = 2.0 / h * info.Dn().row(0).dot(gamma(info, (xn_scaled).matrix()));
531 }
532 xcurrent += h;
533 if (direction * xcurrent < direction * xf) {
534 hslo_ini
535 = direction * std::min(Scalar{1e8}, std::abs(Scalar{1.0} / wnext));
536 hosc_ini = direction
537 * std::min(std::min(Scalar{1e8}, std::abs(wnext / dwnext)),
538 std::abs(gnext / dgnext));
539 if (hard_stop) {
540 if (direction * (xcurrent + hosc_ini) > direction * xf) {
541 hosc_ini = xf - xcurrent;
542 }
543 if (direction * (xcurrent + hslo_ini) > direction * xf) {
544 hslo_ini = xf - xcurrent;
545 }
546 }
547 // o and g written here
548 osc_step_tup = choose_osc_stepsize(info, xcurrent, hosc_ini, epsilon_h);
549 hosc = std::get<0>(osc_step_tup);
550 hslo = choose_nonosc_stepsize(info, xcurrent, hslo_ini, Scalar{0.2});
551 yprev = y;
552 dyprev = dy;
553 }
554 info.alloc_.recover_memory();
555 }
556 return std::make_tuple(xs, ys, dys, successes, phases, steptypes, yeval,
557 dyeval);
558}
559
560} // namespace riccati
561
562#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
constexpr Eigen::Index compile_size_v
Definition utils.hpp:14
auto evolve(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 evolve.hpp:319
auto osc_evolve(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 evolve.hpp:69
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
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.
auto nonosc_evolve(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 evolve.hpp:199