#ifndef MEDUSA_BITS_INTEGRATORS_ADAMSBASHFORTH_FWD_HPP_ #define MEDUSA_BITS_INTEGRATORS_ADAMSBASHFORTH_FWD_HPP_ /** * @file * Adams-Bashforth integrator declarations. * * @example test/integrators/AdamsBashforth_test.cpp */ #include #include #include #include "RKExplicit_fwd.hpp" #include namespace mm { /** * Class representing an AdamsBashforth method, an explicit linear multistep method. * @tparam Scalar Type of scalars to use for calculations, e.g.\ `double`. * @tparam num_steps Number of previous steps the method uses. * * Usage example: * @snippet integrators/AdamsBashforth_test.cpp Adams Bashforth usage example * @ingroup integrators */ template class AdamsBashforth { public: typedef Scalar scalar_t; ///< floating point type enum { steps = num_steps ///< number of steps of the multistep method }; private: Eigen::Matrix b_; ///< coefficients of the multistep method public: /** * Construct an `num_steps`-step Adams Bashforth method with coefficients `b`, given as * @f$y_{n+s} = y_{n+s-1} + h \sum_{j=0}^{s-1} b_j f(t_j, y_j)@f$. * @param b Vector of size `num_steps` representing the coefficients of the method. */ AdamsBashforth(const Eigen::Matrix& b) : b_(b) { assert_msg(std::abs(b_.sum() - 1.0) < 1e-15, "AB method coefficients do not sum up to 1, " "but %.16f instead.", b_.sum()); } /** * Integrator class for AB methods. * @tparam starting_method_t Types of the underlying starting method, required to get first * `num_steps` values. * @warning * You should choose a method that provides same order of accuracy, is a single step method and * has a fixed time step. A default choice is the `num_steps` stage Runge Kutta method, * which might not always be correct. Predefined methods that are called by name, such * as ExplicitMultistep::AB5(), have correct initial methods. */ template > class Integrator { const initial_method_t initial_method_; ///< method used for first `num_steps` steps const AdamsBashforth method_; ///< method used for all next steps scalar_t t0_; ///< start time scalar_t dt_; ///< time step int max_steps; ///< number of steps Eigen::VectorXd y0_; ///< initial value const func_t& func_; ///< function to integrate public: /** * Constructs stepper with given initial method. It is recommended to use the * AdamsBashforth::solve() factory method to construct this object. */ Integrator(const AdamsBashforth& method, const initial_method_t& initial_method, const func_t& func, scalar_t t0, scalar_t t_max, scalar_t dt, Eigen::VectorXd y0) : initial_method_(initial_method), method_(method), t0_(t0), dt_(dt), max_steps(iceil((t_max - t0) / dt)), y0_(std::move(y0)), func_(func) {} /** * Class representing a step in the integration process. This class satisfies * `std::forward_iterator` requirements and can therefore be used with STL-type algorithms. */ class IterationStep : public std::iterator< std::forward_iterator_tag, // iterator_category IterationStep, // value_type int, // difference_type const IterationStep*, // pointer IterationStep // reference > { const Integrator& integrator; ///< reference to underlying integrator scalar_t t; ///< current time Eigen::Matrix last_ys; ///< current value int cur_step; ///< current step public: /// Construct an iterator at the initial values of the equation. explicit IterationStep(const Integrator& integrator) : integrator(integrator), t(integrator.t0_), last_ys(integrator.y0_.size(), static_cast(steps)), cur_step(0) { last_ys.col(steps-1) = integrator.y0_; } /// Construct an (invalid) iterator pointing past the last step IterationStep(const Integrator& integrator, int) : integrator(integrator), t(), last_ys(), cur_step(integrator.max_steps + 1) {} /// Advance the stepper for one time step, returning the new value. IterationStep& operator++(); /** * Advance the stepper for one time step, returning the old value. * @warning usually the prefix increment `++step` is preferred, due to not having a * temporary variable. */ IterationStep operator++(int) { IterationStep retval = *this; ++(*this); return retval; } /// Compare two steppers if they are on the same step. bool operator==(const IterationStep& other) const { return cur_step == other.cur_step; } /// Negation of IterationStep::operator==. bool operator!=(const IterationStep& other) const { return !(*this == other); } /// Noop, used to conform to `std::iterator` requirements IterationStep& operator*() { return *this; } /// const version of IterationStep::operator* const IterationStep& operator*() const { return *this; } /// Returns `false` if integrator went past the last step and `true` otherwise. explicit operator bool() { return cur_step <= integrator.max_steps; } /// Returns true if integrator just completed its last step. bool is_last() const { return cur_step == integrator.max_steps; } /// Get current time. scalar_t time() const { return t; } /// Get current value. typename Eigen::Matrix::ConstColXpr value() const { return last_ys.col(steps-1); } /// Read-write access to current time scalar_t& time() { return t; } /// Read-write access to current value. typename Eigen::Matrix::ColXpr value() { return last_ys.col(steps-1); } /// Output current state of the stepper friend std::ostream& operator<<(std::ostream& os, const IterationStep& step) { return os << "t = " << step.t << "; y = " << step.last_ys.col(steps-1); } }; typedef IterationStep iterator; ///< iterator type typedef IterationStep const_iterator; ///< const iterator type /// Creates stepper at positioned at initial value. iterator begin() { return IterationStep(*this); } /// Same as Integrator::begin(). const_iterator cbegin() { return IterationStep(*this); } /// Creates an invalid stepper at positioned past the last step, meant to be used for /// comparison `it == end()` only. iterator end() { return IterationStep(*this, 0); } /// Same as Integrator::end(). const_iterator cend() { return IterationStep(*this, 0); } /// Output information about this integrator. friend std::ostream& operator<<(std::ostream& os, const Integrator& integrator) { return os << "Multistep integrator using " << integrator.method_ << " method from " << "time " << integrator.t0_ << " with step " << integrator.dt_ << " for " << integrator.max_steps << " steps."; } }; /** * Returns a solver using this method. * @param func Function to integrate. * @param t0 Start time. * @param t_max End time. * @param dt Time step. * @param y0 Initial value. * @warning Last step might not finish exactly on `t_max`. The number of steps is calculated as * `std::ceil((t_max - t0)/dt)`. * @return A solver object. */ template Integrator solve(const func_t& func, scalar_t t0, scalar_t t_max, scalar_t dt, const Eigen::VectorXd& y0) const { return Integrator>( *this, integrators::Explicit::of_order(), func, t0, t_max, dt, y0); } /// Same as AdamsBashforth::solve, but offers the option of supplying your own initial method. template Integrator solve(const initial_method_t& method, const func_t& func, scalar_t t0, scalar_t t_max, scalar_t dt, const Eigen::VectorXd& y0) const { return Integrator(*this, method, func, t0, t_max, dt, y0); } private: /// Returns next value template Eigen::VectorXd step(const func_t& func, scalar_t t, const Eigen::Matrix& last_ys, scalar_t dt) const { Eigen::VectorXd result = last_ys.col(steps-1); for (int i = 0; i < steps; ++i) { result += dt * b_(i) * func(t - i*dt, last_ys.col(i)); } return result; } public: /// Output the method's tableau for debugging. template friend std::ostream& operator<<(std::ostream& os, const AdamsBashforth&); }; namespace integrators { /** * Namespace containing factory functions for explicit linear multistep integrators. * * Usage example: * @snippet AdamsBashforth_test.cpp Adams Bashforth usage example */ namespace ExplicitMultistep { /// Standard Euler's method. template static AdamsBashforth AB1() { Eigen::Matrix b(1); b << 1.0; return {b}; } /// Two step AB method. template static AdamsBashforth AB2() { Eigen::Matrix b(2); b << -1./2., 3./2.; return {b}; } /// Three step AB method. template static AdamsBashforth AB3() { Eigen::Matrix b(3); b << 5./12., -4./3., 23./12.; return {b}; } /// Four step AB method. template static AdamsBashforth AB4() { Eigen::Matrix b(4); b << -3./8., 37./24., -59./24., 55./24.; return {b}; } /// Five step AB method. template static AdamsBashforth AB5() { Eigen::Matrix b(5); b << 251./720., -637./360., 109./30., -1387./360., 1901./720.; return {b}; } // Internal namespace containing implementation details for Explicit::of_order function. /// @cond namespace of_order_internal { template struct _of_order_impl { static AdamsBashforth of_order(); }; } // namespace of_order_internal /// @endcond /** * Returns Adams-Bashforth explicit method of requested order with given floating point type. */ template static AdamsBashforth of_order() { return of_order_internal::_of_order_impl::of_order(); } } // namespace ExplicitMultistep } // namespace integrators } // namespace mm #endif // MEDUSA_BITS_INTEGRATORS_ADAMSBASHFORTH_FWD_HPP_