Differential Equations
Mathwrist C++ API Series

Copyright ©Mathwrist LLC 2023
(October 5, 2023)
Abstract

This document details the C++ programming interface to solve differential equations in Mathwrist’s Numerical Programming Library (NPL). We target on the initial value problems of ordinary differential equations (ODE) and initial/boundary value problems of linear parabolic partial differential equations (PDE). Please refer to our differential equation white paper for technical overview.

1 Ordinary Differential Equation

1.1 General ODE Interface

The common interface class ODE of n-dimensional ODE system

y(t)=f(t,y(t)),t[t0,T] (1)

is defined in header file “ode.h”. Users need supply a derived class under the ODE interface and implement the following pure virtual functions. This interface doesn’t assume linearity on an actual ODE problem. Users can choose an explicit method to solve this general form of problems.

1.1.1 Dimension Size

1 virtual size_t size() const = 0;

Description:

It returns the dimension size n of the ODE system, which is the number of unknown variables y(t) to be solved simultaneously.

1.1.2 Start Time and End Time

1 virtual double start_time() const = 0;
2 virtual double end_time() const = 0;

Description:

It returns the ODE system start time t0 and end time T respectively.

1.1.3 Compute y(t)

1 virtual void y_prime(
2    double t,
3    const double* _y,
4    double* _fty) const = 0;

Description:

It computes the derivative y(t) as of time t, given the values of y(t). Both parameters _y and _fty are pointers to n-element buffer that are managed by NPL ODE solvers. Pointer _y stores the values of y(t). On return, parameter _fty holds the values of f(t,y(t)).

1.2 Linear ODE Interface

A system of ODE is linear if it has the form y(t)=A(t)y(t)+s(t), where A(t) is a n×n matrix that doesn’t depend on y(t). s(t) is the external source function. A homogeneous ODE system has s(t)=0.

Linear ODEs can be solved using implicit methods in NPL for better numerical stability. Essentially, we solve a linear system (2) at each time step, see our differential equation white paper for details. In order to use implicit methods, users need supply a concrete class derived from the ODE_Linear interface class and implement its pure virtual functions. Class “ODE_Linear” is also defined in header file “ode.h”.

[𝐈-h𝐀(t)]𝐲=𝐫 (2)

1.2.1 Homogeneity

1 virtual bool homogeneous() const = 0;

Description:

Return true if the ODE is homogeneous, s(t)=0.

1.2.2 Compact Size of A(t)

1 virtual size_t size_A() const = 0;

Description:

A(t) very often is a sparse matrix. Users may choose a compact storage to represent A(t) and specify the total number of elements of A(t) in their compact form. NPL solvers then use this information to allocate enough memory to store A(t). If A(t) is constant, a derived class may return the size of A(t) to be 0 and cache the constant matrix A(t) internally.

1.2.3 Compute A(t) and s(t)

1 virtual void update(double t, double* _A, double* _s) const = 0;

Description:

This function evaluates A(t) and s(t) as of time t and stores the result to provided buffer pointers _A and _s.

Parameters _A points to a memory buffer managed by NPL solvers. _A is allocated to store the number of elements specified by size_A() function. When size_A() returns 0, pointer _A will be a null pointer. In which case, NPL assumes A(t) is handled internally in a derived class.

When the ODE is homogeneous, parameter _s is passed as a null pointer and can be ignored. Otherwise, it points to a memory buffer managed by NPL solvers. It should store n-element of source function s(t) values on return.

1.2.4 Compute y(t)=A(t)y(t)+s(t)

1 virtual void Ays(
2    const double* _A,
3    const double* _y,
4    const double* _s,
5    double* _yp) const = 0;

Description:

This function asks the concrete linear ODE class to compute y(t)=A(t)y(t)+s(t) with given A(t), y(t) and s(t). Parameters _A and _s are same as that of the update(t, _A, _s) function. Parameter _y points to a buffer that stores n-element of y(t) values. Parameter _yp is pointer to n-element buffer that stores the result y(t) on return.

1.2.5 Solve Linear System (2)

1 virtual void solve(
2    const double* _A,
3    double t,
4    double h,
5    double* _y) const = 0;

Description:

This function requires a concrete linear ODE derived class to solve linear system (2) using its compact storage A(t) that is passed from parameter _A. Parameter t is the time variable value. Parameter h is ODE solver’s time marching step length. Parameter _y holds the n-element right hand side vector 𝐫 on call. It should store the solution vector 𝐲 on return.

1.3 IDeC Solver

Mathwrist NPL uses an Iterative Deferred Correction (IDeC) framework to solve all ODE problems. ODE_IDeC solver class is defined in header file “ode_idec.h”.

1.3.1 Method of Choice

1 class ODE_IDeC
2 {
3 public:
4    enum Method
5    {
6       O1_CLASSIC_EXPLICIT,
7       O2_CLASSIC_EXPLICIT,
8       O1_SPECTRAL_EXPLICIT,
9       O2_SPECTRAL_EXPLICIT,
10       O1_CLASSIC_IMPLICIT,
11       O1_SPECTRAL_IMPLICIT
12    };
14    ...

Description

This ODE_IDeC::Method enumeration list is a member of class ODE_IDeC It gives users different combination choices of base methods and IDeC methods. A base method starts the process with an first-order or second order approximation of y(t). IDeC methods then raise the accuracy order at each error correction iteration.

Table 1: IDeC Method of Choices
Enumeration Base Method IDeC ODE Class
O1_CLASSIC_EXPLICIT 𝒪(h) explicit Euler classic General
O2_CLASSIC_EXPLICIT 𝒪(h2) Adams-Bashforth 3-step explicit classic General
O1_SPECTRAL_EXPLICIT 𝒪(h) explicit Euler spectral General
O2_SPECTRAL_EXPLICIT 𝒪(h2) Adams-Bashforth 3-step explicit spectral General
O1_CLASSIC_IMPLICIT 𝒪(h) implicit Euler classic Linear
O1_SPECTRAL_IMPLICIT 𝒪(h) implicit Euler spectral Linear

1.3.2 Constructor

1 ODE_IDeC(Method m, int p, int q);

Description:

Construct an IDeC solver object given a method of choice, degree of interpolation/approximation polynomial and number of error correction iterations.

Parameter m is one of the ODE_IDeC::Method enumeration item. It specifies what base method and IDeC method are used.

Parameter p specifies the degree of interpolation polynomial used in class IDeC method or the degree of Chebyshev approximation polynomial in spectral IDeC. REQUIRED: 3<p<7 in classic IDeC and 3<p<12 in spectral IDeC.

Parameter q specifies the number of error correction iterations. q=0 implies no error correction is applied, in which case the approximation result from the base method is immediately returned. REQUIRED: q<p.

1.3.3 Solver Operator

1 void operator()(const ODE& o,int N,const double* y0,double* yT);

Description:

Solve an ODE problem by iterative deferred correction method.

Parameter o is an object of concrete ODE problems supplied by the user. It is derived from the ODE base class for problems to be solved with explicit methods, or derived from ODE_Linear class if implicit methods are desired.

Parameter N is the suggested number of time discretization steps. For problems to be solved with explicit methods, time marching step size needs be fine enough to retain numerical stability.

Parameter y0 is a n-elment buffer managed by the caller to provide initial values of y(0).

Parameter yT is a n-element buffer managed by the caller. It stores the numerical solution y(T) on return.

2 Parabolic Partial Differential Equation

2.1 Parabolic PDE Interface

We consider 1-d linear parabolic PDE initial and boundary value problems in the following form,

vt=f(t,x)2vx2+g(t,x)vx+h(t,x)v+s(t,x),t[t0,T],x[a,b] (3)
v(t0,x)=u(x) (4)
α1v(t,a)+β1vx(t,a)=u1(t) (5)
α2v(t,b)+β2vx(t,b)=u2(t) (6)

, where the boundary conditions (6) are expressed as the general Robin boundary conditions. The exact behavior of linear parabolic PDEs are abstracted to a set of interfaces defined in class PDEParabolic1D in “pde_parabolic.h” header file.

Multiple problems that are governed by the same PDE (3) but subject to different initial value (4) and boundary conditions (6) or different source function s(t,x) in (3) can be solved simultaneously.

2.1.1 Boundary Conditions

1 class PDEParabolic1D
2 {
3 public:
4    class BoundaryCondition
5    {
6    public:
7       double alpha() const;
8       double beta() const;
9       virtual double eval(double t, double x, size_t ip) const=0;
10    };
11    ...

Description:

The nested class PDEParabolic1D::BoundaryCondition represents boundary conditions in the general Robin boundary condition form. For Dirichlet boundary conditions, α=1, β=0. For Neumann boundary conditions, α=0 and β=1.

Users need implement the eval() pure virtual function in a derived class. Parameter t is the time variable. Parameter x is the boundary state value either a or b. Parameter ip is the index ID of the i-th problem when multiple problems are solved together.

2.1.2 Initial Values

1 class PDEParabolic1D
2 {
3 public:
4    struct InitialValueFunct
5    {
6       virtual void eval(
7          const ftl::Buffer<double>& x,
8          double t,
9          int event_id,
10          Matrix& v_x) const = 0;
11    };
12    ...
13 };

Description:

The nested struct PDEParabolic1D::InitialValueFunct is designed to serve as both the initial value function v(t0,x) in (4) and the update function to support events and resets. We will describe handling events and resets in a separate section.

Users need implement the eval() pure virtual function in a derived class, where

  • Parameter x is a n-element buffer of discretized spatial states that are ordered from lower boundary a to high boundary b.

  • Parameter t is the current time t. If t is greater than the PDE start time t0, parameter v_x holds the PDE solution v(t,x) computed by a solver up to this current time. If the actual problem requires v(t,x) to be updated at events or resets, v_x should be updated on the return of this function call. The updated v(t,x) then serves as the new initial values for on-going process.

  • Parameter event_id indicates if the current time t is on one of the events specified by the user. If so, t==event_time[event_id] in a user supplied event time array event_time. Otherwise, event_id is passed as a negative value.

  • Parameter v_x is a m×n matrix in-out parameter, where m is the number of problems to be solved simultaneously subject to the same PDE, n is the number of spatial discretization states. On call, it holds the PDE solution v(t,x) computed by a solver up to time t. On return, it receives updated initial values.

2.1.3 Source Function

1 class PDEParabolic1D
2 {
3 public:
4    struct SourceFunct
5    {
6       virtual void add(
7          const double* x,
8          size_t n,
9          double t,
10          size_t ip,
11          double c,
12          double* r) const = 0;
13    };
14    ...
15 };

Description:

The nested struct PDEParabolic1D::SourceFunct represents the external source function s(t,x) in (3). Users need implement the add() pure virtual function in a derived class with the following parameters.

  • Parameter x is a pointer to the n-element buffer of discretized spatial state variable x.

  • Parameter n specifies the number of spatial states.

  • Parameter t is the current time t to evaluate source function s(t,x).

  • Parameter ip indicates the source function is to be evaluated for the i-th problem when multiple problems governed by the same PDE are solved together.

  • Parameter c is a scalar quantity to be multiplied to s(t,x).

  • Parameter r is a pointer to a n-element vector 𝐫 where the source function vector 𝐬=s(t,x) are scaled by c and then added to 𝐫, NOT assigned. In other words, vector 𝐫=𝐫+c×𝐬 on function return.

2.1.4 Specification

1 class PDEParabolic1D
2 {
3 public:
4    struct Specification
5    {
6       ftl::Buffer<double> event_t;
7       size_t n_problems;
8       double t_smooth_initial_values;
9       bool reset_initial_value_at_events;
10       bool reset_initial_value_at_steps;
11       bool stop_at_last_event;
12    };
13    ...
14 };

Description:

The nested struct PDEParabolic::Specification groups a list of control setting to specify how the PDE should be solved.

Data member event_t holds important event time points ti(t0,T) that we want a PDE solver to make a step at ti.

Data member n_problems is the number of problems to be solved together. These problems all follow the same PDE and differ from each other on initial values, boundary conditions and source functions.

Data member t_smooth_initial_values is a time s, t0<s<T, where t0 and T are the PDE start and end time respectively. If such a time s is provided, we will solve the problem in two stages. In an initial smoothing stage t(t0,s), a 4-th order accurate finite difference method is used to smooth out initial values from t0 to s. A PDE solver then takes v(s,x) as the initial value for the subsequent stage t(s,T).

Boolean data member reset_initial_value_at_events is true if we want the solver to reset initial values at those event times. At an event time t, the initial value function supplied by a user is called by the PDE solver. Inside the initial value function, users may update the solution v(t,x), which becomes the new initial value to continue the process.

Boolean data member reset_initial_value_at_steps is true if we want the solver to reset initial values at each discretization time step, which effectively treats each step as a new initial value problem. At each time discretization step t, user supplied initial value function is called to update v(t,x).

Boolean data member stop_at_last_event instructs a PDE solver to stop at the last event time. Otherwise the solver will run all the way to PDE end time T.

2.1.5 Observation

1 class PDEParabolic1D
2 {
3 public:
4    struct Observation
5    {
6       double t;
7       double x;
8       Bound range_v;
9       Observation() : t(0), x(0) {}
10    };
11    ...
12 };

Description:

The nested struct PDEParabolic1D::Observation is a helper struct for users and NPL internally to mark observation points on PDE mesh grids, i.e. used in model calibration. Data members t and x are the observation time value t and state variable value x. Data member range_v allows users to specify the observed value range.

2.1.6 Control Function

1 class PDEParabolic1D
2 {
3 public:
4    struct Control : public VtrValueFunctionND
5    {
6       virtual void set_observations(
7          const Observation* obs,
8          size_t n)=0;
9    };
10    ...
11 };

Description:

The nest struct PDEParabolic1D::Control is an interface for advanced use of PDE solvers. Function v(t,x) could be based on a mathematical model with model parameter θ. Given some observation data v¯(ti,xj), we could calibrate model parameter θ by formulating an optimal control problem, for example,

argminθi,jv¯(ti,xj)-v(t,x;θ)22s.t. (7)
vt=𝒜(v)+s(t,x) (8)

, where 𝒜(v) is the partial derivative operator at the right hand side of (3). In this formulation, PDE constraint (8) translates to a system of nonlinear equality constraints, i.e. a system of difference equations in finite difference methods.

2.1.7 Domain Query

1 double state_lo() const;
2 double state_hi() const;
3 double start_time() const;
4 double end_time() const;

Description:

The rectangular domain [t0,T]×[a,b] of function v(t,x) can be queried by the following PDEParabolic1D class member functions:

  1. 1.

    Returns the lower state boundary a.

  2. 2.

    Returns the upper state boundary a.

  3. 3.

    Returns the PDE start time t0.

  4. 4.

    Returns the PDE end time T.

2.1.8 Coefficient Function

1 virtual void coefficient(
2    double t,
3    const double* x,
4    size_t n_x,
5    double* _ftx,
6    double* _gtx,
7    double* _htx) const = 0;

Description:

Users need derive from PDEParabolic1D interface class and implement this coefficient() pure virtual member function to evaluate coefficient functions f(t,x), g(t,x) and h(t,x) that appears in (3).

Parameter t is the value of time variable t. Parameter x is a pointer to n-element buffer of discretized states. Parameter n_x specifies the number n of states in the discretization buffer x.

Parameter _ftx, _gtx and _htx each is a pointer to n-element buffer to store f(t,x), g(t,x) and h(t,x) on function return.

For efficiency, an concrete derived class doesn’t have to fill the content of _ftx, _gtx and _htx by zero if the corresponding differential term doesn’t exist in (3). Instead, it could leave the content not touched.

2.2 Crank-Nicolson Method

Solver class PDEParabolic_CN implements Crank-Nicolson finite difference method in “pde_parabolio_cn.h” header file.

2.2.1 Constructor

1 PDEParabolic_CN(const PDEParabolic1D& pde, double dx, double dt);

Description:

Create a solver object with given discretization settings. Parameter pde is an object of user derived PDE class under PDEParabolic1D base class. Parameter dx suggests the spatial discretization step size. Parameter dt suggests the time dimension discretization step size.

2.2.2 Query Functions

1 void set_implicitness(double phi);
2 double get_implicitness() const;
3 const PDEParabolic1D& pde() const;
4 const ftl::Buffer<double>& states() const;
5 double dt() const;

Description:

  1. 1.

    Set the implicitness parameter 0ϕ1 in the general different equation form that is documented in Mathwrist’s white paper. Crank-Nicolson method should set ϕ=0.5. ϕ=0 or 1 is effectively a fully explicit or a fully implicit method respectively.

  2. 2.

    Return the implicitness parameter ϕ used by the solver.

  3. 3.

    Return an object reference to the PDE problem to be solved.

  4. 4.

    Return a buffer of discretized spatial states that is used by the solver.

  5. 5.

    Return the time step size.

2.2.3 Solver Operator

1 void operator()(
2    const PDEParabolic1D::InitialValueFunct& f_t,
3    const PDEParabolic1D::BoundaryCondition& bc_lo,
4    const PDEParabolic1D::BoundaryCondition& bc_hi,
5    const PDEParabolic1D::SourceFunct* s_tx,
6    const PDEParabolic1D::Specification& spec,
7    Matrix& V);

Description:

Solve m number of initial value problems simultaneously using Crank Nicolson method.

Parameter f_t is the initial value funtion. It also serves as the callback function to update v(t,x) at events or resets.

Parameter bc_lo and bc_hi specify the lower and upper boundary conditions respectively.

Parameter s_tx is a pointer to the external source function. Pass nullptr if the PDE is homogeneous.

Parameter spec is a problem specification.

Parameter V a m×n matrix to hold the numerical solutions, where m is the number of problems in specification and n is the number of discretized states.

2.3 Spectral Collocation Method

Solver class PDEParabolic_SC implements spectral collocation method in header file “pde_parabolio_spectral.h”.

2.3.1 Constructor

1 PDEParabolic_SC(const PDEParabolic1D& pde,int n,int q,double dt);

Description:

Parameter pde is an object of user derived PDE class under PDEParabolic1D base class. Parameter n is the degree of Chebyshev approximation polynomial. Parameter q is the number of error correction iterations used in implicit IDeC, REQUIRED: 0q<n. Parameter dt suggests the time dimension step size used in implicit IDeC.

2.3.2 Query Functions

1 const PDEParabolic1D& pde() const;
2 const ftl::Buffer<double>& states() const;
3 double dt() const;
4 int err_iters() const;

Description:

  1. 1.

    Return an object reference to the PDE problem to be solved.

  2. 2.

    Return the collocation points in physical states. The number of collocation points is equal to the degree of Chebyshev approximation polynomial + 1.

  3. 3.

    Return the time step size.

  4. 4.

    Return the number of error correction iterations.

2.3.3 Solver Operator

1 void operator()(
2    const PDEParabolic1D::InitialValueFunct& f_t,
3    const PDEParabolic1D::BoundaryCondition& bc_lo,
4    const PDEParabolic1D::BoundaryCondition& bc_hi,
5    const PDEParabolic1D::SourceFunct* s_tx,
6    const PDEParabolic1D::Specification& spec,
7    Matrix& C);

Description:

Solve m number of initial value problems simultaneously using spectral collocation method.

Parameter f_t is the initial value funtion. It also serves as the callback function to update v(t,x) at events or resets.

Parameter bc_lo and bc_hi specify the lower and upper boundary conditions respectively.

Parameter s_tx is a pointer to the external source function. Pass nullptr if the PDE is homogeneous.

Parameter spec is a problem specification.

Parameter C is a m×(n+1) coefficient matrix on return of the function. It holds Chebyshev expansion coefficients to approximate the solutions, where m is the number of problems in specification and n is the degree of Chebyshev approximation polynomial. The numerical solution of each problem can be recovered by creating a ChebyshevCurve object with a row of coefficients of C.

2.4 Method of Lines

Solver class PDEParabolic_MoL implements method of lines (finite difference) in header file “pde_parabolio_mol.h”.

2.4.1 Finite Difference Accuracy Order

1 enum Order
2 {
3    O2,   // 2nd order accurate finite difference method
4    O4    // 4th order accurate finite difference method
5 };

Description:

Enumeration item PDEParabolic_MoL::O2 and PDEParabolic_MoL::O4 instruct a solver to approximate the partial derivative operator 𝒜(v) by a 2nd or 4th order accurate finite difference scheme respectively.

2.4.2 Constructor

1 PDEParabolic_MoL(
2    const PDEParabolic1D& pde,
3    Order o,
4    int q,
5    double dx,
6    double dt);

Description:

Parameter pde is an object of user derived PDE class under PDEParabolic1D base class. Parameter o is the order of finite difference scheme to be used. Parameter q is the number of error correction iterations used in implicit IDeC, REQUIRED: 0q<6. Parameter dx suggests the finite difference discretization step size in states. Parameter dt suggests the time dimension step size used in implicit IDeC.

2.4.3 Query Functions

1 const PDEParabolic1D& pde() const;
2 Order order() const;
3 const ftl::Buffer<double>& states() const;
4 double dt() const;
5 int err_iters() const;

Description:

  1. 1.

    Return an object reference to the PDE problem to be solved.

  2. 2.

    Return the order of finite difference scheme.

  3. 3.

    Return a buffer of discretized spatial states that is used by the solver.

  4. 4.

    Return the time step size.

  5. 5.

    Return the number of error correction iterations.

2.4.4 Solver Operator

1 void operator()(
2    const PDEParabolic1D::InitialValueFunct& f_t,
3    const PDEParabolic1D::BoundaryCondition& bc_lo,
4    const PDEParabolic1D::BoundaryCondition& bc_hi,
5    const PDEParabolic1D::SourceFunct* s_tx,
6    const PDEParabolic1D::Specification& spec,
7    Matrix& C);

Description:

Solve m number of initial value problems simultaneously using method of lines.

Parameter f_t is the initial value funtion. It also serves as the callback function to update v(t,x) at events or resets.

Parameter bc_lo and bc_hi specify the lower and upper boundary conditions respectively.

Parameter s_tx is a pointer to the external source function. Pass nullptr if the PDE is homogeneous.

Parameter spec is a problem specification.

Parameter V a m×n matrix to hold the numerical solutions, where m is the number of problems in specification and n is the number of discretized states.