Update ODESolver to work with ImplicitSolve for stage-state, u, and stage-slope, du/dt
1096 lines
32 KiB
C++
1096 lines
32 KiB
C++
// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
|
||
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
|
||
// LICENSE and NOTICE for details. LLNL-CODE-806117.
|
||
//
|
||
// This file is part of the MFEM library. For more information and source code
|
||
// availability visit https://mfem.org.
|
||
//
|
||
// MFEM is free software; you can redistribute it and/or modify it under the
|
||
// terms of the BSD-3 license. We welcome feedback and contributions, see file
|
||
// CONTRIBUTING.md for details.
|
||
|
||
#ifndef MFEM_ODE
|
||
#define MFEM_ODE
|
||
|
||
#include "../general/communication.hpp"
|
||
#include "../config/config.hpp"
|
||
#include "operator.hpp"
|
||
#include <vector>
|
||
#include <memory>
|
||
|
||
namespace mfem
|
||
{
|
||
|
||
/// An interface for storing state of previous timesteps
|
||
class ODEStateData
|
||
{
|
||
public:
|
||
/// Get the maximum number of stored stages
|
||
virtual int MaxSize() const = 0;
|
||
|
||
/// Get the current number of stored stages
|
||
virtual int Size() const = 0;
|
||
|
||
/// Get the ith state vector
|
||
virtual const Vector &Get(int i) const = 0;
|
||
|
||
/// Get the ith state vector - non-const version
|
||
virtual Vector &Get(int i) = 0;
|
||
|
||
/// Get the ith state vector - with a copy
|
||
virtual void Get(int i, Vector &vec) const = 0;
|
||
|
||
/// Set the ith state vector
|
||
virtual void Set(int i, Vector &state) = 0;
|
||
|
||
/// Add state vector and increment state size
|
||
virtual void Append(Vector &state) = 0;
|
||
|
||
/// Virtual destructor
|
||
virtual ~ODEStateData() = default;
|
||
};
|
||
|
||
/// An implementation of ODEStateData that stores states in an std::vector<Vector>
|
||
class ODEStateDataVector : public ODEStateData
|
||
{
|
||
private:
|
||
MemoryType mem_type;
|
||
int ss, smax;
|
||
std::vector<Vector> data;
|
||
Array<int> idx;
|
||
|
||
public:
|
||
ODEStateDataVector (int smax): smax(smax)
|
||
{
|
||
data.resize(smax);
|
||
idx.SetSize(smax);
|
||
ss = 0;
|
||
};
|
||
|
||
/// Set the number of stages and the size of the vectors
|
||
void SetSize(int vsize, MemoryType mem_type);
|
||
|
||
/// Shift the stage counter for the next timestep
|
||
inline void ShiftStages()
|
||
{
|
||
for (int i = 0; i < smax; i++) { idx[i] = (++idx[i])%smax; }
|
||
};
|
||
|
||
/// Increment the stage counter
|
||
void Increment() { ss++; ss = std::min(ss,smax); };
|
||
|
||
/// Reset the stage counter
|
||
void Reset() { ss = 0; };
|
||
|
||
/// Reference access to the ith vector.
|
||
inline Vector & operator[](int i) { return data[idx[i]]; };
|
||
|
||
/// Const reference access to the ith vector.
|
||
inline const Vector &operator[](int i) const { return data[idx[i]]; };
|
||
|
||
/// Print state data
|
||
void Print(std::ostream &os = mfem::out) const ;
|
||
|
||
int MaxSize() const override { return smax; };
|
||
|
||
int Size() const override { return ss; };
|
||
|
||
const Vector &Get(int i) const override;
|
||
Vector &Get(int i) override;
|
||
void Get(int i, Vector &vec) const override;
|
||
|
||
void Set(int i, Vector &state) override;
|
||
|
||
void Append(Vector &state) override;
|
||
};
|
||
|
||
|
||
/// Abstract class for solving systems of ODEs: dx/dt = f(x,t)
|
||
/** For systems of split ODEs:
|
||
$$ M dx/dt = f_1(x,t) + f_2(x,t) $$
|
||
where $ M^{-1} f_1 $ and $ M^{-1} f_2 $ are treated differently (e.g.,
|
||
explicitly and implicitly), the solver class expects a
|
||
TimeDependentOperator with split functionality. Setting
|
||
TimeDependentOperator::EvalMode = TimeDependentOperator::ADDITIVE_TERM_1
|
||
and calling TimeDependentOperator::Mult() should return
|
||
$ k_1=M^{-1} f_1(x,t) $. Setting TimeDependentOperator::EvalMode =
|
||
TimeDependentOperator::ADDITIVE_TERM_2 and calling
|
||
TimeDependentOperator::ImplicitSolve() should solve
|
||
$ M k_2 = f_2(x+\gamma k_2,t) $. */
|
||
class ODESolver
|
||
{
|
||
protected:
|
||
using ImplicitVariableType = TimeDependentOperator::ImplicitVariableType;
|
||
/// Pointer to the associated TimeDependentOperator.
|
||
TimeDependentOperator *f; // f(.,t) : R^n --> R^n
|
||
MemoryType mem_type;
|
||
|
||
public:
|
||
ODESolver() : f(NULL) { mem_type = Device::GetHostMemoryType(); }
|
||
|
||
/// Associate a TimeDependentOperator with the ODE solver.
|
||
/** This method has to be called:
|
||
- Before the first call to Step().
|
||
- When the dimensions of the associated TimeDependentOperator change.
|
||
- When a time stepping sequence has to be restarted.
|
||
- To change the associated TimeDependentOperator. */
|
||
virtual void Init(TimeDependentOperator &f_);
|
||
|
||
/** @brief Perform a time step from time @a t [in] to time @a t [out] based
|
||
on the requested step size @a dt [in]. */
|
||
/** @param[in,out] x Approximate solution.
|
||
@param[in,out] t Time associated with the approximate solution @a x.
|
||
@param[in,out] dt Time step size.
|
||
|
||
The following rules describe the common behavior of the method:
|
||
- The input @a x [in] is the approximate solution for the input time
|
||
@a t [in].
|
||
- The input @a dt [in] is the desired time step size, defining the desired
|
||
target time: t [target] = @a t [in] + @a dt [in].
|
||
- The output @a x [out] is the approximate solution for the output time
|
||
@a t [out].
|
||
- The output @a dt [out] is the last time step taken by the method which
|
||
may be smaller or larger than the input @a dt [in] value, e.g. because
|
||
of time step control.
|
||
- The method may perform more than one time step internally; in this case
|
||
@a dt [out] is the last internal time step size.
|
||
- The output value of @a t [out] may be smaller or larger than
|
||
t [target], however, it is not smaller than @a t [in] + @a dt [out], if
|
||
at least one internal time step was performed.
|
||
- The value @a x [out] may be obtained by interpolation using internally
|
||
stored data.
|
||
- In some cases, the contents of @a x [in] may not be used, e.g. when
|
||
@a x [out] from a previous Step() call was obtained by interpolation.
|
||
- In consecutive calls to this method, the output @a t [out] of one
|
||
Step() call has to be the same as the input @a t [in] to the next
|
||
Step() call.
|
||
- If the previous rule has to be broken, e.g. to restart a time stepping
|
||
sequence, then the ODE solver must be re-initialized by calling Init()
|
||
between the two Step() calls. */
|
||
virtual void Step(Vector &x, real_t &t, real_t &dt) = 0;
|
||
|
||
/// Perform time integration from time @a t [in] to time @a tf [in].
|
||
/** @param[in,out] x Approximate solution.
|
||
@param[in,out] t Time associated with the approximate solution @a x.
|
||
@param[in,out] dt Time step size.
|
||
@param[in] tf Requested final time.
|
||
|
||
The default implementation makes consecutive calls to Step() until
|
||
reaching @a tf.
|
||
The following rules describe the common behavior of the method:
|
||
- The input @a x [in] is the approximate solution for the input time
|
||
@a t [in].
|
||
- The input @a dt [in] is the initial time step size.
|
||
- The output @a dt [out] is the last time step taken by the method which
|
||
may be smaller or larger than the input @a dt [in] value, e.g. because
|
||
of time step control.
|
||
- The output value of @a t [out] is not smaller than @a tf [in]. */
|
||
virtual void Run(Vector &x, real_t &t, real_t &dt, real_t tf)
|
||
{
|
||
while (t < tf) { Step(x, t, dt); }
|
||
}
|
||
|
||
/// Returns how many State vectors the ODE requires
|
||
virtual int GetStateSize() { return 0; };
|
||
|
||
///@brief Returns @a true if the ODESolver supports the given
|
||
/// #ImplicitVariableType, @a var, and returns @a false otherwise.
|
||
///@note Should be overriden in ODESolver that calls TimeDependentOperator::ImplicitSolve().
|
||
virtual bool SupportsImplicitVariableType(ImplicitVariableType var) const
|
||
{ return false; };
|
||
|
||
/** @brief Compute the finite-difference slope, @a $\frac{du}{dt} \approx \frac{u(t+dt)-u(t)}{dt}$,
|
||
* and store it in @a k.
|
||
* @param [in] dt Finite difference step size.
|
||
* @param [in] u state vector, @a u(t).
|
||
* @param [in,out] k On input, @a k contains the state vector, @a u( @a t+ @a dt).
|
||
* On output, @a k contains the computed slope, @a du/dt.
|
||
* */
|
||
virtual void ComputeSlopeFromState(const real_t dt, const Vector &u,
|
||
Vector &k);
|
||
|
||
// Help info for ODESolver options
|
||
static MFEM_EXPORT std::string ExplicitTypes;
|
||
static MFEM_EXPORT std::string ImplicitTypes;
|
||
static MFEM_EXPORT std::string IMEXTypes;
|
||
static MFEM_EXPORT std::string Types;
|
||
|
||
/// Function for selecting the desired ODESolver (Explicit and Implicit)
|
||
/// Returns an ODESolver pointer based on an type
|
||
/// Caller gets ownership of the object and is responsible for its deletion
|
||
static MFEM_EXPORT std::unique_ptr<ODESolver> Select(const int ode_solver_type);
|
||
|
||
/// Function for selecting the desired Explicit ODESolver
|
||
/// Returns an ODESolver pointer based on an type
|
||
/// Caller gets ownership of the object and is responsible for its deletion
|
||
static MFEM_EXPORT std::unique_ptr<ODESolver> SelectExplicit(
|
||
const int ode_solver_type);
|
||
|
||
/// Function for selecting the desired Implicit ODESolver
|
||
/// Returns an ODESolver pointer based on an type
|
||
/// Caller gets ownership of the object and is responsible for its deletion
|
||
static MFEM_EXPORT std::unique_ptr<ODESolver> SelectImplicit(
|
||
const int ode_solver_type);
|
||
|
||
|
||
/// Function for selecting the desired IMEX ODESolver
|
||
/// Returns an ODESolver pointer based on an type
|
||
/// Caller gets ownership of the object and is responsible for its deletion
|
||
static MFEM_EXPORT std::unique_ptr<ODESolver> SelectIMEX(
|
||
const int ode_solver_type);
|
||
|
||
virtual ~ODESolver() { }
|
||
};
|
||
|
||
/// Abstract class for an ODESolver that has state history implemented as ODEStateData
|
||
class ODESolverWithStates : public ODESolver
|
||
{
|
||
public:
|
||
/// Returns the StateData
|
||
virtual ODEStateData& GetState() = 0;
|
||
|
||
/// Returns the StateData
|
||
virtual const ODEStateData& GetState() const = 0;
|
||
|
||
/// Returns how many State vectors the ODE requires
|
||
virtual int GetStateSize() { return GetState().MaxSize(); };
|
||
};
|
||
|
||
|
||
/// The classical forward Euler method
|
||
class ForwardEulerSolver : public ODESolver
|
||
{
|
||
private:
|
||
Vector dxdt;
|
||
|
||
public:
|
||
void Init(TimeDependentOperator &f_) override;
|
||
|
||
void Step(Vector &x, real_t &t, real_t &dt) override;
|
||
};
|
||
|
||
|
||
/** A family of explicit second-order RK2 methods. Some choices for the
|
||
parameter 'a' are:
|
||
a = 1/2 - the midpoint method
|
||
a = 1 - Heun's method
|
||
a = 2/3 - default, has minimal truncation error. */
|
||
class RK2Solver : public ODESolver
|
||
{
|
||
private:
|
||
real_t a;
|
||
Vector dxdt, x1;
|
||
|
||
public:
|
||
RK2Solver(const real_t a_ = 2./3.) : a(a_) { }
|
||
|
||
void Init(TimeDependentOperator &f_) override;
|
||
|
||
void Step(Vector &x, real_t &t, real_t &dt) override;
|
||
};
|
||
|
||
|
||
/// Third-order, strong stability preserving (SSP) Runge-Kutta method
|
||
class RK3SSPSolver : public ODESolver
|
||
{
|
||
private:
|
||
Vector y, k;
|
||
|
||
public:
|
||
void Init(TimeDependentOperator &f_) override;
|
||
|
||
void Step(Vector &x, real_t &t, real_t &dt) override;
|
||
};
|
||
|
||
|
||
/// The classical explicit forth-order Runge-Kutta method, RK4
|
||
class RK4Solver : public ODESolver
|
||
{
|
||
private:
|
||
Vector y, k, z;
|
||
|
||
public:
|
||
void Init(TimeDependentOperator &f_) override;
|
||
|
||
void Step(Vector &x, real_t &t, real_t &dt) override;
|
||
};
|
||
|
||
|
||
/** An explicit Runge-Kutta method corresponding to a general Butcher tableau
|
||
+--------+----------------------+
|
||
| c[0] | a[0] |
|
||
| c[1] | a[1] a[2] |
|
||
| ... | ... |
|
||
| c[s-2] | ... a[s(s-1)/2-1] |
|
||
+--------+----------------------+
|
||
| | b[0] b[1] ... b[s-1] |
|
||
+--------+----------------------+ */
|
||
class ExplicitRKSolver : public ODESolver
|
||
{
|
||
private:
|
||
int s;
|
||
const real_t *a, *b, *c;
|
||
Vector y, *k;
|
||
|
||
public:
|
||
ExplicitRKSolver(int s_, const real_t *a_, const real_t *b_,
|
||
const real_t *c_);
|
||
|
||
void Init(TimeDependentOperator &f_) override;
|
||
|
||
void Step(Vector &x, real_t &t, real_t &dt) override;
|
||
|
||
virtual ~ExplicitRKSolver();
|
||
};
|
||
|
||
|
||
/** An 8-stage, 6th order RK method. From Verner's "efficient" 9-stage 6(5)
|
||
pair. */
|
||
class RK6Solver : public ExplicitRKSolver
|
||
{
|
||
private:
|
||
static MFEM_EXPORT const real_t a[28], b[8], c[7];
|
||
|
||
public:
|
||
RK6Solver() : ExplicitRKSolver(8, a, b, c) { }
|
||
};
|
||
|
||
|
||
/** A 12-stage, 8th order RK method. From Verner's "efficient" 13-stage 8(7)
|
||
pair. */
|
||
class RK8Solver : public ExplicitRKSolver
|
||
{
|
||
private:
|
||
static MFEM_EXPORT const real_t a[66], b[12], c[11];
|
||
|
||
public:
|
||
RK8Solver() : ExplicitRKSolver(12, a, b, c) { }
|
||
};
|
||
|
||
|
||
/// Backward Euler ODE solver. L-stable.
|
||
class BackwardEulerSolver : public ODESolver
|
||
{
|
||
protected:
|
||
Vector k;
|
||
|
||
public:
|
||
void Init(TimeDependentOperator &f_) override;
|
||
|
||
void Step(Vector &x, real_t &t, real_t &dt) override;
|
||
|
||
bool SupportsImplicitVariableType(ImplicitVariableType var) const override
|
||
{
|
||
return (var == ImplicitVariableType::STATE ||
|
||
var == ImplicitVariableType::SLOPE);
|
||
}
|
||
};
|
||
|
||
|
||
/// Implicit midpoint method. A-stable, not L-stable.
|
||
class ImplicitMidpointSolver : public ODESolver
|
||
{
|
||
protected:
|
||
Vector k;
|
||
|
||
public:
|
||
void Init(TimeDependentOperator &f_) override;
|
||
|
||
void Step(Vector &x, real_t &t, real_t &dt) override;
|
||
|
||
bool SupportsImplicitVariableType(ImplicitVariableType var) const override
|
||
{
|
||
return (var == ImplicitVariableType::STATE ||
|
||
var == ImplicitVariableType::SLOPE);
|
||
}
|
||
};
|
||
|
||
|
||
/** Two stage, singly diagonal implicit Runge-Kutta (SDIRK) methods;
|
||
the choices for gamma_opt are:
|
||
0 - 3rd order method, not A-stable
|
||
1 - 3rd order method, A-stable, not L-stable (default)
|
||
2 - 2nd order method, L-stable
|
||
3 - 2nd order method, L-stable (has solves outside [t,t+dt]). */
|
||
class SDIRK23Solver : public ODESolver
|
||
{
|
||
protected:
|
||
real_t gamma;
|
||
Vector k, y;
|
||
|
||
public:
|
||
SDIRK23Solver(int gamma_opt = 1);
|
||
|
||
void Init(TimeDependentOperator &f_) override;
|
||
|
||
void Step(Vector &x, real_t &t, real_t &dt) override;
|
||
|
||
bool SupportsImplicitVariableType(ImplicitVariableType var) const override
|
||
{
|
||
return (var == ImplicitVariableType::STATE ||
|
||
var == ImplicitVariableType::SLOPE);
|
||
}
|
||
};
|
||
|
||
|
||
/** Three stage, singly diagonal implicit Runge-Kutta (SDIRK) method of
|
||
order 4. A-stable, not L-stable. */
|
||
class SDIRK34Solver : public ODESolver
|
||
{
|
||
protected:
|
||
Vector k, y, z;
|
||
|
||
public:
|
||
void Init(TimeDependentOperator &f_) override;
|
||
|
||
void Step(Vector &x, real_t &t, real_t &dt) override;
|
||
|
||
bool SupportsImplicitVariableType(ImplicitVariableType var) const override
|
||
{
|
||
return (var == ImplicitVariableType::STATE ||
|
||
var == ImplicitVariableType::SLOPE);
|
||
}
|
||
};
|
||
|
||
|
||
/** Three stage, singly diagonal implicit Runge-Kutta (SDIRK) method of
|
||
order 3. L-stable. */
|
||
class SDIRK33Solver : public ODESolver
|
||
{
|
||
protected:
|
||
Vector k, y;
|
||
|
||
public:
|
||
void Init(TimeDependentOperator &f_) override;
|
||
|
||
void Step(Vector &x, real_t &t, real_t &dt) override;
|
||
|
||
bool SupportsImplicitVariableType(ImplicitVariableType var) const override
|
||
{
|
||
return (var == ImplicitVariableType::STATE ||
|
||
var == ImplicitVariableType::SLOPE);
|
||
}
|
||
};
|
||
|
||
|
||
/** Two stage, explicit singly diagonal implicit Runge-Kutta (ESDIRK) method
|
||
of order 2. A-stable. */
|
||
class TrapezoidalRuleSolver : public ODESolver
|
||
{
|
||
protected:
|
||
Vector k, y;
|
||
|
||
public:
|
||
void Init(TimeDependentOperator &f_) override;
|
||
|
||
void Step(Vector &x, real_t &t, real_t &dt) override;
|
||
|
||
bool SupportsImplicitVariableType(ImplicitVariableType var) const override
|
||
{
|
||
return (var == ImplicitVariableType::STATE ||
|
||
var == ImplicitVariableType::SLOPE);
|
||
}
|
||
};
|
||
|
||
|
||
/** Three stage, explicit singly diagonal implicit Runge-Kutta (ESDIRK) method
|
||
of order 2. L-stable. */
|
||
class ESDIRK32Solver : public ODESolver
|
||
{
|
||
protected:
|
||
Vector k, y, z;
|
||
|
||
public:
|
||
void Init(TimeDependentOperator &f_) override;
|
||
|
||
void Step(Vector &x, real_t &t, real_t &dt) override;
|
||
|
||
bool SupportsImplicitVariableType(ImplicitVariableType var) const override
|
||
{
|
||
return (var == ImplicitVariableType::STATE ||
|
||
var == ImplicitVariableType::SLOPE);
|
||
}
|
||
};
|
||
|
||
|
||
/** Three stage, explicit singly diagonal implicit Runge-Kutta (ESDIRK) method
|
||
of order 3. A-stable. */
|
||
class ESDIRK33Solver : public ODESolver
|
||
{
|
||
protected:
|
||
Vector k, y, z;
|
||
|
||
public:
|
||
void Init(TimeDependentOperator &f_) override;
|
||
|
||
void Step(Vector &x, real_t &t, real_t &dt) override;
|
||
|
||
bool SupportsImplicitVariableType(ImplicitVariableType var) const override
|
||
{
|
||
return (var == ImplicitVariableType::STATE ||
|
||
var == ImplicitVariableType::SLOPE);
|
||
}
|
||
};
|
||
|
||
|
||
/// Generalized-alpha ODE solver from "A generalized-α method for integrating
|
||
/// the filtered Navier-Stokes equations with a stabilized finite element
|
||
/// method" by K.E. Jansen, C.H. Whiting and G.M. Hulbert.
|
||
class GeneralizedAlphaSolver : public ODESolverWithStates
|
||
{
|
||
ODEStateDataVector state;
|
||
|
||
protected:
|
||
|
||
mutable Vector k,y;
|
||
real_t alpha_f, alpha_m, gamma;
|
||
|
||
void SetRhoInf(real_t rho_inf);
|
||
void PrintProperties(std::ostream &os = mfem::out);
|
||
public:
|
||
|
||
GeneralizedAlphaSolver(real_t rho = 1.0) : state(1) { SetRhoInf(rho); };
|
||
void Init(TimeDependentOperator &f_) override;
|
||
void Step(Vector &x, real_t &t, real_t &dt) override;
|
||
|
||
ODEStateData& GetState() override { return state; }
|
||
const ODEStateData& GetState() const override { return state; }
|
||
|
||
bool SupportsImplicitVariableType(ImplicitVariableType var) const override
|
||
{
|
||
return (var == ImplicitVariableType::STATE ||
|
||
var == ImplicitVariableType::SLOPE);
|
||
}
|
||
};
|
||
|
||
|
||
/** An explicit Adams-Bashforth method. */
|
||
class AdamsBashforthSolver : public ODESolverWithStates
|
||
{
|
||
private:
|
||
const real_t *a;
|
||
const int stages;
|
||
real_t dt_;
|
||
ODEStateDataVector state;
|
||
|
||
protected:
|
||
std::unique_ptr<ODESolver> RKsolver;
|
||
|
||
inline bool print()
|
||
{
|
||
#ifdef MFEM_USE_MPI
|
||
return Mpi::IsInitialized() ? Mpi::Root() : true;
|
||
#else
|
||
return true;
|
||
#endif
|
||
}
|
||
|
||
void CheckTimestep(real_t dt);
|
||
|
||
public:
|
||
AdamsBashforthSolver(int s_, const real_t *a_);
|
||
void Init(TimeDependentOperator &f_) override;
|
||
void Step(Vector &x, real_t &t, real_t &dt) override;
|
||
|
||
ODEStateData& GetState() override { return state; }
|
||
const ODEStateData& GetState() const override { return state; }
|
||
};
|
||
|
||
/** A 1-stage, 1st order AB method. */
|
||
class AB1Solver : public AdamsBashforthSolver
|
||
{
|
||
private:
|
||
static MFEM_EXPORT const real_t a[1];
|
||
|
||
public:
|
||
AB1Solver() : AdamsBashforthSolver(1, a) { }
|
||
};
|
||
|
||
/** A 2-stage, 2nd order AB method. */
|
||
class AB2Solver : public AdamsBashforthSolver
|
||
{
|
||
private:
|
||
static MFEM_EXPORT const real_t a[2];
|
||
|
||
public:
|
||
AB2Solver() : AdamsBashforthSolver(2, a) { RKsolver.reset(new RK2Solver()); }
|
||
};
|
||
|
||
/** A 3-stage, 3rd order AB method. */
|
||
class AB3Solver : public AdamsBashforthSolver
|
||
{
|
||
private:
|
||
static MFEM_EXPORT const real_t a[3];
|
||
|
||
public:
|
||
AB3Solver() : AdamsBashforthSolver(3, a) { RKsolver.reset(new RK3SSPSolver()); }
|
||
};
|
||
|
||
/** A 4-stage, 4th order AB method. */
|
||
class AB4Solver : public AdamsBashforthSolver
|
||
{
|
||
private:
|
||
static MFEM_EXPORT const real_t a[4];
|
||
|
||
public:
|
||
AB4Solver() : AdamsBashforthSolver(4, a) { RKsolver.reset(new RK4Solver()); }
|
||
};
|
||
|
||
/** A 5-stage, 5th order AB method. */
|
||
class AB5Solver : public AdamsBashforthSolver
|
||
{
|
||
private:
|
||
static MFEM_EXPORT const real_t a[5];
|
||
|
||
public:
|
||
AB5Solver() : AdamsBashforthSolver(5, a) { RKsolver.reset(new RK6Solver()); }
|
||
};
|
||
|
||
|
||
/** An implicit Adams-Moulton method. */
|
||
class AdamsMoultonSolver : public ODESolverWithStates
|
||
{
|
||
private:
|
||
const real_t *a;
|
||
const int stages;
|
||
real_t dt_;
|
||
ODEStateDataVector state;
|
||
|
||
protected:
|
||
std::unique_ptr<ODESolver> RKsolver;
|
||
|
||
inline bool print()
|
||
{
|
||
#ifdef MFEM_USE_MPI
|
||
return Mpi::IsInitialized() ? Mpi::Root() : true;
|
||
#else
|
||
return true;
|
||
#endif
|
||
}
|
||
|
||
void CheckTimestep(real_t dt);
|
||
|
||
public:
|
||
AdamsMoultonSolver(int s_, const real_t *a_);
|
||
void Init(TimeDependentOperator &f_) override;
|
||
void Step(Vector &x, real_t &t, real_t &dt) override;
|
||
|
||
ODEStateData& GetState() override { return state; }
|
||
const ODEStateData& GetState() const override { return state; }
|
||
bool SupportsImplicitVariableType(ImplicitVariableType var) const override
|
||
{
|
||
return (var == ImplicitVariableType::STATE ||
|
||
var == ImplicitVariableType::SLOPE);
|
||
}
|
||
};
|
||
|
||
/** A 1-stage, 2nd order AM method. */
|
||
class AM1Solver : public AdamsMoultonSolver
|
||
{
|
||
private:
|
||
static MFEM_EXPORT const real_t a[2];
|
||
|
||
public:
|
||
AM1Solver() : AdamsMoultonSolver(1, a) { RKsolver.reset(new SDIRK23Solver()); }
|
||
};
|
||
|
||
/** A 2-stage, 3rd order AM method. */
|
||
class AM2Solver : public AdamsMoultonSolver
|
||
{
|
||
private:
|
||
static MFEM_EXPORT const real_t a[3];
|
||
|
||
public:
|
||
AM2Solver() : AdamsMoultonSolver(2, a) { RKsolver.reset(new SDIRK23Solver()); }
|
||
};
|
||
|
||
/** A 3-stage, 4th order AM method. */
|
||
class AM3Solver : public AdamsMoultonSolver
|
||
{
|
||
private:
|
||
static MFEM_EXPORT const real_t a[4];
|
||
|
||
public:
|
||
AM3Solver() : AdamsMoultonSolver(3, a) { RKsolver.reset(new SDIRK23Solver()); }
|
||
};
|
||
|
||
/** A 4-stage, 5th order AM method. */
|
||
class AM4Solver : public AdamsMoultonSolver
|
||
{
|
||
private:
|
||
static MFEM_EXPORT const real_t a[5];
|
||
|
||
public:
|
||
AM4Solver() : AdamsMoultonSolver(4, a) { RKsolver.reset(new SDIRK34Solver()); }
|
||
};
|
||
|
||
/// The SIASolver class is based on the Symplectic Integration Algorithm
|
||
/// described in "A Symplectic Integration Algorithm for Separable Hamiltonian
|
||
/// Functions" by J. Candy and W. Rozmus, Journal of Computational Physics,
|
||
/// Vol. 92, pages 230-256 (1991).
|
||
|
||
/** The Symplectic Integration Algorithm (SIA) is designed for systems of first
|
||
order ODEs derived from a Hamiltonian.
|
||
H(q,p,t) = T(p) + V(q,t)
|
||
Which leads to the equations:
|
||
dq/dt = dT/dp
|
||
dp/dt = -dV/dq
|
||
In the integrator the operators P and F are defined to be:
|
||
P = dT/dp
|
||
F = -dV/dq
|
||
*/
|
||
class SIASolver
|
||
{
|
||
public:
|
||
SIASolver() : F_(NULL), P_(NULL) {}
|
||
|
||
virtual void Init(Operator &P, TimeDependentOperator & F);
|
||
|
||
virtual void Step(Vector &q, Vector &p, real_t &t, real_t &dt) = 0;
|
||
|
||
virtual void Run(Vector &q, Vector &p, real_t &t, real_t &dt, real_t tf)
|
||
{
|
||
while (t < tf) { Step(q, p, t, dt); }
|
||
}
|
||
|
||
virtual ~SIASolver() {}
|
||
|
||
protected:
|
||
TimeDependentOperator * F_; // p_{i+1} = p_{i} + dt F(q_{i})
|
||
Operator * P_; // q_{i+1} = q_{i} + dt P(p_{i+1})
|
||
|
||
mutable Vector dp_;
|
||
mutable Vector dq_;
|
||
};
|
||
|
||
/// First Order Symplectic Integration Algorithm
|
||
class SIA1Solver : public SIASolver
|
||
{
|
||
public:
|
||
SIA1Solver() {}
|
||
void Step(Vector &q, Vector &p, real_t &t, real_t &dt) override;
|
||
};
|
||
|
||
/// Second Order Symplectic Integration Algorithm
|
||
class SIA2Solver : public SIASolver
|
||
{
|
||
public:
|
||
SIA2Solver() {}
|
||
void Step(Vector &q, Vector &p, real_t &t, real_t &dt) override;
|
||
};
|
||
|
||
/// Variable order Symplectic Integration Algorithm (orders 1-4)
|
||
class SIAVSolver : public SIASolver
|
||
{
|
||
public:
|
||
SIAVSolver(int order);
|
||
void Step(Vector &q, Vector &p, real_t &t, real_t &dt) override;
|
||
|
||
private:
|
||
int order_;
|
||
|
||
Array<real_t> a_;
|
||
Array<real_t> b_;
|
||
};
|
||
|
||
|
||
|
||
/// Abstract class for solving systems of ODEs: d2x/dt2 = f(x,dx/dt,t)
|
||
class SecondOrderODESolver
|
||
{
|
||
protected:
|
||
/// Pointer to the associated TimeDependentOperator.
|
||
SecondOrderTimeDependentOperator *f; // f(.,.,t) : R^n x R^n --> R^n
|
||
MemoryType mem_type;
|
||
ODEStateDataVector state;
|
||
|
||
public:
|
||
SecondOrderODESolver() : f(NULL), state(1) { mem_type = MemoryType::HOST; }
|
||
|
||
/// Associate a TimeDependentOperator with the ODE solver.
|
||
/** This method has to be called:
|
||
- Before the first call to Step().
|
||
- When the dimensions of the associated TimeDependentOperator change.
|
||
- When a time stepping sequence has to be restarted.
|
||
- To change the associated TimeDependentOperator. */
|
||
virtual void Init(SecondOrderTimeDependentOperator &f);
|
||
|
||
/** @brief Perform a time step from time @a t [in] to time @a t [out] based
|
||
on the requested step size @a dt [in]. */
|
||
/** @param[in,out] x Approximate solution.
|
||
@param[in,out] dxdt Approximate rate.
|
||
@param[in,out] t Time associated with the
|
||
approximate solution @a x and rate @ dxdt
|
||
@param[in,out] dt Time step size.
|
||
|
||
The following rules describe the common behavior of the method:
|
||
- The input @a x [in] is the approximate solution for the input time
|
||
@a t [in].
|
||
- The input @a dxdt [in] is the approximate rate for the input time
|
||
@a t [in].
|
||
- The input @a dt [in] is the desired time step size, defining the desired
|
||
target time: t [target] = @a t [in] + @a dt [in].
|
||
- The output @a x [out] is the approximate solution for the output time
|
||
@a t [out].
|
||
- The output @a dxdt [out] is the approximate rate for the output time
|
||
@a t [out].
|
||
- The output @a dt [out] is the last time step taken by the method which
|
||
may be smaller or larger than the input @a dt [in] value, e.g. because
|
||
of time step control.
|
||
- The method may perform more than one time step internally; in this case
|
||
@a dt [out] is the last internal time step size.
|
||
- The output value of @a t [out] may be smaller or larger than
|
||
t [target], however, it is not smaller than @a t [in] + @a dt [out], if
|
||
at least one internal time step was performed.
|
||
- The value @a x [out] may be obtained by interpolation using internally
|
||
stored data.
|
||
- In some cases, the contents of @a x [in] may not be used, e.g. when
|
||
@a x [out] from a previous Step() call was obtained by interpolation.
|
||
- In consecutive calls to this method, the output @a t [out] of one
|
||
Step() call has to be the same as the input @a t [in] to the next
|
||
Step() call.
|
||
- If the previous rule has to be broken, e.g. to restart a time stepping
|
||
sequence, then the ODE solver must be re-initialized by calling Init()
|
||
between the two Step() calls. */
|
||
virtual void Step(Vector &x, Vector &dxdt, real_t &t, real_t &dt) = 0;
|
||
void EulerStep(Vector &x, Vector &dxdt, real_t &t, real_t &dt);
|
||
void MidPointStep(Vector &x, Vector &dxdt, real_t &t, real_t &dt);
|
||
|
||
/// Perform time integration from time @a t [in] to time @a tf [in].
|
||
/** @param[in,out] x Approximate solution.
|
||
@param[in,out] dxdt Approximate rate.
|
||
@param[in,out] t Time associated with the approximate solution @a x.
|
||
@param[in,out] dt Time step size.
|
||
@param[in] tf Requested final time.
|
||
|
||
The default implementation makes consecutive calls to Step() until
|
||
reaching @a tf.
|
||
The following rules describe the common behavior of the method:
|
||
- The input @a x [in] is the approximate solution for the input time
|
||
@a t [in].
|
||
- The input @a dxdt [in] is the approximate rate for the input time
|
||
@a t [in].
|
||
- The input @a dt [in] is the initial time step size.
|
||
- The output @a dt [out] is the last time step taken by the method which
|
||
may be smaller or larger than the input @a dt [in] value, e.g. because
|
||
of time step control.
|
||
- The output value of @a t [out] is not smaller than @a tf [in]. */
|
||
virtual void Run(Vector &x, Vector &dxdt, real_t &t, real_t &dt, real_t tf)
|
||
{
|
||
while (t < tf) { Step(x, dxdt, t, dt); }
|
||
}
|
||
|
||
/// Functions for getting the state vectors
|
||
ODEStateData& GetState() { return state; }
|
||
const ODEStateData& GetState() const { return state; }
|
||
|
||
/// Returns how many State vectors the ODE requires
|
||
int GetStateSize() { return GetState().MaxSize(); };
|
||
|
||
/// Help info for SecondOrderODESolver options
|
||
static MFEM_EXPORT std::string Types;
|
||
|
||
/// Function selecting the desired SecondOrderODESolver
|
||
static MFEM_EXPORT SecondOrderODESolver *Select(const int ode_solver_type);
|
||
|
||
virtual ~SecondOrderODESolver() { }
|
||
};
|
||
|
||
/// The classical newmark method.
|
||
/// Newmark, N. M. (1959) A method of computation for structural dynamics.
|
||
/// Journal of Engineering Mechanics, ASCE, 85 (EM3) 67-94.
|
||
class NewmarkSolver : public SecondOrderODESolver
|
||
{
|
||
private:
|
||
real_t beta, gamma;
|
||
bool no_mult;
|
||
|
||
public:
|
||
NewmarkSolver(real_t beta_ = 0.25, real_t gamma_ = 0.5, bool no_mult_ = false)
|
||
{
|
||
beta = beta_;
|
||
gamma = gamma_;
|
||
no_mult = no_mult_;
|
||
};
|
||
|
||
void PrintProperties(std::ostream &os = mfem::out);
|
||
|
||
void Step(Vector &x, Vector &dxdt, real_t &t, real_t &dt) override;
|
||
};
|
||
|
||
class LinearAccelerationSolver : public NewmarkSolver
|
||
{
|
||
public:
|
||
LinearAccelerationSolver() : NewmarkSolver(1.0/6.0, 0.5) { };
|
||
};
|
||
|
||
class CentralDifferenceSolver : public NewmarkSolver
|
||
{
|
||
public:
|
||
CentralDifferenceSolver() : NewmarkSolver(0.0, 0.5) { };
|
||
};
|
||
|
||
class FoxGoodwinSolver : public NewmarkSolver
|
||
{
|
||
public:
|
||
FoxGoodwinSolver() : NewmarkSolver(1.0/12.0, 0.5) { };
|
||
};
|
||
|
||
/// Generalized-alpha ODE solver
|
||
/// A Time Integration Algorithm for Structural Dynamics With Improved
|
||
/// Numerical Dissipation: The Generalized-α Method
|
||
/// J.Chung and G.M. Hulbert, J. Appl. Mech 60(2), 371-375, 1993
|
||
/// https://doi.org/10.1115/1.2900803
|
||
/// rho_inf in [0,1]
|
||
class GeneralizedAlpha2Solver : public SecondOrderODESolver
|
||
{
|
||
protected:
|
||
Vector xa,va,aa;
|
||
real_t alpha_f, alpha_m, beta, gamma;
|
||
bool no_mult;
|
||
|
||
public:
|
||
GeneralizedAlpha2Solver(real_t rho_inf = 1.0, bool no_mult_ = false)
|
||
{
|
||
no_mult = no_mult_;
|
||
rho_inf = (rho_inf > 1.0) ? 1.0 : rho_inf;
|
||
rho_inf = (rho_inf < 0.0) ? 0.0 : rho_inf;
|
||
|
||
alpha_m = (2.0 - rho_inf)/(1.0 + rho_inf);
|
||
alpha_f = 1.0/(1.0 + rho_inf);
|
||
beta = 0.25*pow(1.0 + alpha_m - alpha_f,2);
|
||
gamma = 0.5 + alpha_m - alpha_f;
|
||
};
|
||
|
||
void PrintProperties(std::ostream &os = mfem::out);
|
||
|
||
void Init(SecondOrderTimeDependentOperator &f_) override;
|
||
|
||
void Step(Vector &x, Vector &dxdt, real_t &t, real_t &dt) override;
|
||
|
||
};
|
||
|
||
/// The classical midpoint method.
|
||
class AverageAccelerationSolver : public GeneralizedAlpha2Solver
|
||
{
|
||
public:
|
||
AverageAccelerationSolver()
|
||
{
|
||
alpha_m = 0.5;
|
||
alpha_f = 0.5;
|
||
beta = 0.25;
|
||
gamma = 0.5;
|
||
};
|
||
};
|
||
|
||
/// HHT-alpha ODE solver
|
||
/// Improved numerical dissipation for time integration algorithms
|
||
/// in structural dynamics
|
||
/// H.M. Hilber, T.J.R. Hughes and R.L. Taylor 1977
|
||
/// https://doi.org/10.1002/eqe.4290050306
|
||
/// alpha in [2/3,1] --> Defined differently than in paper.
|
||
class HHTAlphaSolver : public GeneralizedAlpha2Solver
|
||
{
|
||
public:
|
||
HHTAlphaSolver(real_t alpha = 1.0)
|
||
{
|
||
alpha = (alpha > 1.0) ? 1.0 : alpha;
|
||
alpha = (alpha < 2.0/3.0) ? 2.0/3.0 : alpha;
|
||
|
||
alpha_m = 1.0;
|
||
alpha_f = alpha;
|
||
beta = (2-alpha)*(2-alpha)/4;
|
||
gamma = 0.5 + alpha_m - alpha_f;
|
||
};
|
||
|
||
};
|
||
|
||
/// WBZ-alpha ODE solver
|
||
/// An alpha modification of Newmark's method
|
||
/// W.L. Wood, M. Bossak and O.C. Zienkiewicz 1980
|
||
/// https://doi.org/10.1002/nme.1620151011
|
||
/// rho_inf in [0,1]
|
||
class WBZAlphaSolver : public GeneralizedAlpha2Solver
|
||
{
|
||
public:
|
||
WBZAlphaSolver(real_t rho_inf = 1.0)
|
||
{
|
||
rho_inf = (rho_inf > 1.0) ? 1.0 : rho_inf;
|
||
rho_inf = (rho_inf < 0.0) ? 0.0 : rho_inf;
|
||
|
||
alpha_f = 1.0;
|
||
alpha_m = 2.0/(1.0 + rho_inf);
|
||
beta = 0.25*pow(1.0 + alpha_m - alpha_f,2);
|
||
gamma = 0.5 + alpha_m - alpha_f;
|
||
};
|
||
|
||
};
|
||
|
||
/// Forward-backward Euler method
|
||
class IMEXExpImplEuler : public ODESolver
|
||
{
|
||
private:
|
||
Vector k1; Vector k2;
|
||
public:
|
||
void Init(TimeDependentOperator &f_) override;
|
||
|
||
void Step(Vector &x, real_t &t, real_t &dt) override;
|
||
};
|
||
|
||
/// Second order, two-stage implicit-explicit (IMEX) Runge-Kutta (RK) method
|
||
/** L-stable IMEX RK2 method adopted from "On the Stability of IMEX Upwind gSBP
|
||
Schemes for 1D Linear Advection‑Difusion Equations" by Sigrun Ortleb. Same
|
||
as (2,2,2) from "Implicit-explicit Runge-Kutta methods for time-dependent
|
||
partial differential equations" by Ascher, Ruuth and Spiteri, Applied
|
||
Numerical Mathematics (1997). */
|
||
class IMEXRK2 : public ODESolver
|
||
{
|
||
private:
|
||
Vector k1_exp; Vector k2_exp; Vector k_imp;
|
||
//helper vector
|
||
Vector y;
|
||
public:
|
||
void Init(TimeDependentOperator &f_) override;
|
||
|
||
void Step(Vector &x, real_t &t, real_t &dt) override;
|
||
};
|
||
|
||
/// Second order, 2/3-stage implicit-explicit (IMEX) Runge-Kutta (RK) method
|
||
/** L-stable method (2,3,2) from "Implicit-explicit Runge-Kutta methods for
|
||
time-dependent partial differential equations" by Ascher, Ruuth and
|
||
Spiteri, Applied Numerical Mathematics (1997). */
|
||
class IMEXRK2_3StageExplicit : public ODESolver
|
||
{
|
||
private:
|
||
Vector k1_exp; Vector k2_exp; Vector k3_exp;
|
||
Vector k_imp;
|
||
//helper vectors
|
||
Vector y;
|
||
public:
|
||
void Init(TimeDependentOperator &f_) override;
|
||
|
||
void Step(Vector &x, real_t &t, real_t &dt) override;
|
||
};
|
||
|
||
/// Third order, 3/4-stage implicit-explicit (IMEX) Runge-Kutta (RK) method
|
||
/** L-stable method (3,4,3) from "Implicit-explicit Runge-Kutta methods for
|
||
time-dependent partial differential equations" by Ascher, Ruuth and
|
||
Spiteri, Applied Numerical Mathematics (1997). */
|
||
class IMEX_DIRK_RK3 : public ODESolver
|
||
{
|
||
private:
|
||
Vector k1_exp; Vector k2_exp; Vector k3_exp; Vector k4_exp;
|
||
Vector k2_imp; Vector k3_imp;
|
||
//helper vectors
|
||
Vector y;
|
||
public:
|
||
void Init(TimeDependentOperator &f_) override;
|
||
|
||
void Step(Vector &x, real_t &t, real_t &dt) override;
|
||
};
|
||
|
||
|
||
}
|
||
|
||
#endif
|