This directory consists of the following files:
TimeIntegrationScheme.h | TimeIntegrationScheme.cpp |
TimeIntegrationWrapper.h | TimeIntegrationWrapper.cpp |
The original time-stepping classes (found in TimeIntegrationScheme) where implemented by Vos et al. [62] and a detailed explanation of the mathematics and computer science concepts are provided there. After the original implementation, Cantwell et al. extended Vos’ original work by adding the time-stepping routines into a factory pattern (found in TimeIntegrationWrapper).
General linear methods (GLMs) can be considered as the generalization of a broad range of different numerical methods for ordinary differential equations. They were introduced by Butcher and they provide a unified formulation for traditional methods such as the Runge-Kutta methods and the linear multi-step methods such as Adams-Bashforth. From an implementation point of view, this means that all these numerical methods can be abstracted in a similar way. As this allows a high level of generality, it is chosen in Nektar++ to cast all time integration schemes in the framework of general linear methods.
For background information about general linear methods, please consult [14].
The standard initial value problem can written in the form
![]() |
where y is a vector containing the variable (or an array of array containing the variables).
In the formulation of general linear methods, it is more convenient to consider the ODE in autonomous form, i.e.
![]() |
Suppose the governing differential equation is given in autonomous form, the nth step of the GLM comprising
is formulated as:
Y i | = Δt∑ j=0s-1a ijFj + ∑ j=0r-1u ij^ y j[n-1], i = 0,1,…,s - 1 | ||
^y i[n] | = Δt∑ j=0s-1b ijFj + ∑ j=0r-1v ij^ y j[n-1], i = 0,1,…,r - 1 |
where Y i are referred to as the stage values and Fj as the stage derivatives. Both quantities are related by the differential equation:
Fi = ^ f (Y i). |
The matrices A = [aij], U = [uij], B = [bij], V = [vij] are characteristic of a specific method. Each scheme can then be uniquely defined by a partitioned (s + r) × (s + r) matrix
![]() |
Adopting the notation:
^y [n-1] = ![]() ![]() ![]() ![]() |
the general linear method can be written more compactly in the following form:
![]() ![]() ![]() |
where IN is the identity matrix of dimension N × N.
Although the GLM is essentially presented for ODE’s in its autonomous form, in Nektar++ it will be used to solve ODE’s formulated in non-autonomous form. Given the ODE,
![]() |
a single step of GLM can then be evaluated in the following way:
y[n-1], i.e. the r subvectors comprising yi[n-1] - t[n-1], i.e. the equivalent of y[n-1] for the time variable t.
For a detailed description of the formulation and a deeper insight of the numerical method see [62].
Nektar++ contains various classes and methods which implement the concept of GLMs. This toolbox is capable of numerically solving the generalised ODE using a broad range of different time-stepping methods. We distinguish the following types of general linear methods:
The aim in Nektar++ is to fully support the first three types of GLMs. Fully implicit methods are currently not implemented.
The goal of abstracting the concept of general linear methods is to provide users with a single interface for time-stepping, independent of the chosen method. The classes tree allows the user to numerically integrate ODE’s using high-order complex schemes, as if it was done using the Forward-Euler method. Switching between time-stepping schemes is as easy as changing a parameter in an input file.
In the already implemented solvers the time-integration schemes have been set up according to the nature of the equations. For example the incompressible Navier-Stokes equations solver allows the use of three different Implicit-Explicit time-schemes if solving the equations with a splitting-scheme. This is because this kind of scheme has an explicit and an implicit operator that combined solve the ODE’s system.
Once aware of the problem’s nature and implementation, the user can easily switch between some (depending on the problem) of the following time-integration schemes:
AdamsBashforthOrder1 | Adams-Bashforth Forward multi-step scheme of order 1 |
AdamsBashforthOrder2 | Adams-Bashforth Forward multi-step scheme of order 2 |
AdamsBashforthOrder3 | Adams-Bashforth Forward multi-step scheme of order 3 |
AdamsMoultonOrder1 | Adams-Moulton Forward multi-step scheme of order 1 |
AdamsMoultonOrder2 | Adams-Moulton Forward multi-step scheme of order 2 |
BDFImplicitOrder1 | BDF multi-step scheme of order 1 (implicit) |
BDFImplicitOrder2 | BDF multi-step scheme of order 2 (implicit) |
ClassicalRungeKutta4 | Runge-Kutta multi-stage scheme 4th order explicit (old name) |
RungeKutta4 | Runge-Kutta multi-stage scheme 4th order explicit (new name for ClassicalRungeKutta4) |
RungeKutta3_SSP | Nonlinear SSP RungeKutta3 explicit |
RungeKutta2_ImprovedEuler | Improved RungeKutta2 explicit (old name meaning Heun’s method) |
RungeKutta2_SSP | Nonlinear SSP RungeKutta2 explicit (surrogate for RungeKutta2_ImprovedEuler) |
ForwardEuler | Forward-Euler scheme |
BackwardEuler | Backward Euler scheme |
IMEXOrder1 | IMEX 1st order scheme using Euler Backwards Euler Forwards |
IMEXOrder2 | IMEX 2nd order scheme using Backward Different Formula & Extrapolation |
IMEXOrder3 | IMEX 3rd order scheme using Backward Different Formula & Extrapolation |
Midpoint | Midpoint method (old name) |
RungeKutta2 | Classical RungeKutta2 method (new name for Midpoint) |
DIRKOrder2 | Diagonally Implicit Runge-Kutta scheme of order 2 |
DIRKOrder3 | Diagonally Implicit Runge-Kutta scheme of order 3 |
CNAB | Crank-Nicolson-Adams-Bashforth Order 2 (CNAB) |
IMEXGear | IMEX Gear Order 2 |
MCNAB | Modified Crank-Nicolson-Adams-Bashforth Order 2 (MCNAB) |
IMEXdirk_1_1_1 | Forward-Backward Euler IMEX DIRK(1,1,1) |
IMEXdirk_1_2_1 | Forward-Backward Euler IMEX DIRK(1,2,1) |
IMEXdirk_1_2_2 | Implicit-Explicit Midpoint IMEX DIRK(1,2,2) |
IMEXdirk_2_2_2 | L-stable, two stage, second order IMEX DIRK(2,2,2) |
IMEXdirk_2_3_2 | L-stable, three stage, third order IMEX DIRK(3,4,3) |
IMEXdirk_2_3_3 | L-stable, two stage, third order IMEX DIRK(2,3,3) |
IMEXdirk_3_4_3 | L-stable, three stage, third order IMEX DIRK(3,4,3) |
IMEXdirk_4_4_3 | L-stable, four stage, third order IMEX DIRK(4,4,3) |
Nektar++ input file for your problem will ask you just the string corresponding the
time-stepping scheme you want to use (between quotation marks in the previous list), and few
parameters to define your integration in time (time-step and number of steps or final time).
For example:
In order to implement a new solver which takes advantage of the time-integration class in Nektar++, two main ingredients are required:
Your pseudo-main file, where you actually loop over the time steps, will look like
We can distinguish three different sections in the code above
In this section you define the basic parameters (like time-step, initial time, etc.) and the
time-integration objects. The operators are not all required, it depends on the nature of your
problem and on the type of time integration schemes you want to use. In this case, the
problem has been set up to work just with Forward-Euler, then for sure you will not need the
implicit operator. An object named equation
has been initialized, is an object of type
YourClass
, where your spatial discretization and the functions which actually represent your
operators are implemented. An example of this class will be shown later in this
page.
The second part consists in the scheme initialization. In this example we set up just Forward-Euler, but we can set up more then one time-integration scheme and quickly switch between them from the input file. Forward-Euler does not require any other scheme for the start-up procedure. High order multi-step schemes may need lower-order schemes for the start up.
The last step is the typical time-loop, where you iterate in time to get your new solution at
each time-level. The solution at time tn+1 is stored into vector U
(you need to properly
initialize this vector). U
is an Array of Arrays, where the first dimension corresponds to the
number of variables (eg. u,v,w) and the second dimension corresponds to the variables size
(e.g. the number of modes or the number of physical points).
The variable ODE
is an object which contains the methods. A class representing a PDE
equation (or a system of equations) must have a series of functions representing the
implicit/explicit part of the method, which represents the reduction of the PDE’s to a system
of ODE’s. The spatial discretization and the definition of this method should be
implemented in YourClass
. &YourClass::YourExplicitOperatorFunction
is a
functor, i.e. a pointer to a function where the method is implemented. equation
is a
pointer to the object, i.e. the class, where the function/method is implemented. Here
a pseudo-example of the .h file of your hypothetical class representing the set of
equations. The implementation of the functions is meant to be in the related .cpp
file.
Dirichlet boundary conditions can be strongly imposed by lifting the known Dirichlet solution. This is equivalent to decompose the approximate solution y into an known part, yD, which satisfies the Dirichlet boundary conditions, and an unknown part, yH, which is zero on the Dirichlet boundaries, i.e.
y = yD + yH |
In a Finite Element discretisation, this corresponds to splitting the solution vector of coefficients y into the known Dirichlet degrees of freedom yD and the unknown homogeneous degrees of freedom yH. Ordering the known coefficients first, this corresponds to:
y = ![]() |
The generalised formulation of the general linear method (i.e. the introduction of a left hand side operator) allows for an easier treatment of these types of boundary conditions. To better appreciate this, consider the equation for the stage values for an explicit general linear method where both the left and right hand side operator are linear operators, i.e. they can be represented by a matrix.
MY i = Δt∑ j=0i-1a ijLY j + ∑ j=0r-1u ijyj[n-1], i = 0,1,…,s - 1 |
In case of a lifted known solution, this can be written as:
![]() ![]() ![]() ![]() ![]() | ||
i = 0,1,…,s - 1 |
In order to calculate the stage values correctly, the explicit operator should now be implemented to do the following:
![]() ![]() ![]() |
Note that only the homogeneous part bH will be used to calculate the stage values. This
means essentially that only the bottom part of the operation above, i.e. LHDyD + LHHyH is
required. However, sometimes it might be more convenient to use/implement routines for the
explicit operator that also calculate bD.
An implicit method should solve the system:
![]() ![]() ![]() ![]() ![]() |
for the unknown vector y. This can be done in three steps:
To add a new time integration scheme, follow the steps below:
TimeIntegrationMethod
enum
list.
TimeIntegrationScheme
constructor with
the coefficients of the new method.
InitializeScheme
to select (or implement) a proper
initialisation strategy for the method.
Here we show some examples time-stepping schemes implemented in Nektar++, to give an idea of what is required to add one of them.
![]() ![]() ![]() ![]() |
![]() ![]() ![]() ![]() |
![]() ![]() ![]() ![]() |
![]() ![]() ![]() ![]() |
![]() ![]() |
with
y[n] = ![]() ![]() |
![]() ![]() |
with
y[n] = ![]() ![]() |
![]() ![]() ![]() ![]() |
![]() ![]() ![]() ![]() |
![]() ![]() ![]() ![]() |
![]() ![]() ![]() ![]() ![]() |
![]() ![]() |
with
λ = 0.4358665215, y[n] = ![]() ![]() |
![]() | |||
![]() |
with
λ = 0.4358665215, y[n] = ![]() ![]() |