You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
301 lines
11 KiB
301 lines
11 KiB
#ifndef MEDUSA_BITS_INTEGRATORS_RKEXPLICIT_FWD_HPP_
|
|
#define MEDUSA_BITS_INTEGRATORS_RKEXPLICIT_FWD_HPP_
|
|
|
|
/**
|
|
* @file
|
|
* Runge-Kutta integrator declarations.
|
|
*
|
|
* @example test/integrators/RKExplicit_test.cpp
|
|
*/
|
|
|
|
#include <medusa/Config.hpp>
|
|
#include <Eigen/Core>
|
|
#include <medusa/bits/utils/numutils.hpp>
|
|
#include "RKExplicit_fwd.hpp"
|
|
#include <medusa/bits/utils/assert.hpp>
|
|
|
|
namespace mm {
|
|
|
|
template <typename Scalar, int num_steps>
|
|
class AdamsBashforth;
|
|
|
|
/**
|
|
* Class representing an explicit Runge-Kutta method.
|
|
* @tparam Scalar type of scalars to use for calculations, e.g.\ double
|
|
* @tparam num_stages number of stages of the method
|
|
*
|
|
* Usage example:
|
|
* @snippet integrators/RKExplicit_test.cpp Runge Kutta usage example
|
|
* @ingroup integrators
|
|
*/
|
|
template <typename Scalar, int num_stages>
|
|
class RKExplicit {
|
|
public:
|
|
typedef Scalar scalar_t; ///< Floating point type used in the computations.
|
|
enum { /** number of stages */ stages = num_stages };
|
|
|
|
private:
|
|
Eigen::Matrix<scalar_t, stages, 1> alpha_; ///< Left row of the tableau.
|
|
Eigen::Matrix<scalar_t, stages, stages> beta_; ///< Central matrix of the tableau.
|
|
Eigen::Matrix<scalar_t, stages, 1> gamma_; ///< Bottom row of the tableau.
|
|
|
|
public:
|
|
/**
|
|
* Construct a Runge-Kutta method with tableau
|
|
* @f$
|
|
* \begin{array}{l|l}
|
|
* \alpha & \beta \\ \hline
|
|
* & \gamma^\mathsf{T}
|
|
* \end{array}
|
|
* @f$
|
|
* @param alpha Vector of size `stages`.
|
|
* @param beta Matrix of size `stages` times `stages`. Only strictly lower triangular part is
|
|
* used.
|
|
* @param gamma Vector of size `stages`.
|
|
*/
|
|
RKExplicit(const Eigen::Matrix<scalar_t, stages, 1>& alpha,
|
|
const Eigen::Matrix<scalar_t, stages, stages>& beta,
|
|
const Eigen::Matrix<scalar_t, stages, 1>& gamma)
|
|
: alpha_(alpha), beta_(beta), gamma_(gamma) {}
|
|
|
|
/**
|
|
* Integrator class for RK methods.
|
|
*/
|
|
template <typename func_t>
|
|
class Integrator {
|
|
const RKExplicit<scalar_t, num_stages> method_; ///< Method used in this integrator.
|
|
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. It is recommended to use the RKExplicit::solve() factory method
|
|
* to construct this object.
|
|
*/
|
|
Integrator(const RKExplicit<scalar_t, num_stages>& method, const func_t& func, scalar_t t0,
|
|
scalar_t t_max, scalar_t dt, Eigen::VectorXd y0) :
|
|
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::VectorXd y; ///< 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_), y(integrator.y0_), cur_step(0) {}
|
|
|
|
/// Construct an (invalid) iterator pointing past the last step
|
|
IterationStep(const Integrator& integrator, int) : integrator(integrator), t(), y(),
|
|
cur_step(integrator.max_steps + 1) {}
|
|
|
|
/// Advance the stepper for one time step, returning the new value.
|
|
IterationStep& operator++() {
|
|
y = integrator.method_.step(integrator.func_, t, y, integrator.dt_);
|
|
t += integrator.dt_;
|
|
cur_step++;
|
|
return *this;
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
Eigen::VectorXd value() const { return y; }
|
|
|
|
/// Read-write access to current time.
|
|
scalar_t& time() { return t; }
|
|
|
|
/// Read-write access to current value.
|
|
Eigen::VectorXd& value() { return y; }
|
|
|
|
/// Output current state of the stepper.
|
|
friend std::ostream& operator<<(std::ostream& os, const IterationStep& step) {
|
|
return os << "t = " << step.t << "; y = " << step.y;
|
|
}
|
|
};
|
|
|
|
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 << "Explicit RK 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 <typename func_t>
|
|
Integrator<func_t> solve(const func_t& func, scalar_t t0, scalar_t t_max, scalar_t dt,
|
|
const Eigen::VectorXd& y0) const {
|
|
return Integrator<func_t>(*this, func, t0, t_max, dt, y0);
|
|
}
|
|
|
|
private:
|
|
/// Returns next value.
|
|
template <typename func_t>
|
|
Eigen::VectorXd step(const func_t& func, scalar_t t, const Eigen::VectorXd& y,
|
|
scalar_t dt) const {
|
|
Eigen::Matrix<scalar_t, Eigen::Dynamic, stages> k(y.size(), static_cast<int>(stages));
|
|
k.col(0) = func(t + alpha_(0) * dt, y);
|
|
for (int i = 1; i < stages; ++i) {
|
|
k.col(i) = func(t + alpha_(i) * dt,
|
|
y + dt * k.leftCols(i) * beta_.row(i).head(i).transpose());
|
|
}
|
|
return y + dt * k * gamma_;
|
|
}
|
|
|
|
public:
|
|
/// Output the method's tableau for debugging.
|
|
template <typename scalar_t, int stages>
|
|
friend std::ostream& operator<<(std::ostream& os, const RKExplicit<scalar_t, stages>&);
|
|
|
|
/// Implements Adams Bashforth multistep methods.
|
|
template <typename Scalar2, int num_steps2>
|
|
friend class AdamsBashforth;
|
|
};
|
|
|
|
/// Output the method's tableau for debugging.
|
|
template <typename scalar_t, int num_stages>
|
|
std::ostream& operator<<(std::ostream& os, const RKExplicit<scalar_t, num_stages>& method) {
|
|
os << "RKExplicit with " << num_stages << " stages and "
|
|
<< "alpha = " << method.alpha_ << ", "
|
|
<< "beta = " << method.beta_ << ", "
|
|
<< "gamma = " << method.gamma_;
|
|
return os;
|
|
}
|
|
|
|
/**
|
|
* Namespace containing most known methods for integrating ODEs.
|
|
*/
|
|
namespace integrators {
|
|
|
|
/**
|
|
* Namespace containing factory functions for explicit single step integrators.
|
|
* Factory functions are provided for the most common ones, such as Euler's method, Midpoint rule,
|
|
* RK4, ...
|
|
*
|
|
* Usage example:
|
|
* @snippet RKExplicit_test.cpp Runge Kutta usage example
|
|
*/
|
|
namespace Explicit {
|
|
|
|
/// Standard RK4 4th order method
|
|
template <class scalar_t = double>
|
|
RKExplicit<scalar_t, 4> RK4();
|
|
|
|
/// 3/8 rule 4th order method
|
|
template <class scalar_t = double>
|
|
RKExplicit<scalar_t, 4> RK38();
|
|
|
|
/// Explicit Euler's method, 1st order
|
|
template <class scalar_t = double>
|
|
RKExplicit<scalar_t, 1> Euler();
|
|
|
|
/// Explicit midpoint rule, 2nd order
|
|
template <class scalar_t = double>
|
|
RKExplicit<scalar_t, 2> Midpoint();
|
|
|
|
/// Kutta's third order method
|
|
template <class scalar_t = double>
|
|
RKExplicit<scalar_t, 3> RK3();
|
|
|
|
/// Fifth order method appearing in Fehlberg's method
|
|
template <class scalar_t = double>
|
|
RKExplicit<scalar_t, 6> Fehlberg5();
|
|
|
|
/// @cond
|
|
namespace of_order_internal {
|
|
template <int order, class scalar_t>
|
|
struct _of_order_impl { static RKExplicit<scalar_t, order> of_order(); };
|
|
}
|
|
/// @endcond
|
|
|
|
/**
|
|
* Returns Runge Kutta explicit method of requested order with given floating point type.
|
|
*/
|
|
template <int order, class scalar_t = double>
|
|
static RKExplicit<scalar_t, order> of_order() {
|
|
return of_order_internal::_of_order_impl<order, scalar_t>::of_order();
|
|
}
|
|
|
|
} // namespace Explicit
|
|
} // namespace integrators
|
|
} // namespace mm
|
|
|
|
#endif // MEDUSA_BITS_INTEGRATORS_RKEXPLICIT_FWD_HPP_
|
|
|