Design of an ODE Solver Environment

Design of an ODE Solver Environment
System of ODEs
Consider the system of ODEs:
dyi
 f i ( y1 , , yn , t ),
dt
yi (0)  yi0 ,
i  1, , n
Typically solved by forward Euler or 4th order Runge-Kutta
Traditional Approach
Traditionally solved by library routines:
SUBROUTINE RK4 (Y,T,F,WORK1,N,TSTEP,TOL1,TOL2,…)
Y:
current value of y
T:
current value for time t
F:
external function defining f
WORK1:
work array
N:
length of array
TSTEP:
time step value
TOL1,TOL2:algorithmic parameters
Model Problem
y  c1 ( y  c2 y y )  c3 ( y  c4 y 3 )  sin t
Define
y1  y2 , y 2  y
and you get:
f 2  c1 ( y2  c2 y2 y2 )  c3 ( y1  c4 y13 )  sin t
f1  y2
Traditional Interface
That is, the function F has the following interface:
SUBROUTINE F (YDOT,Y,T,C1,C2,C3,C4,OMEGA)
NB! Problem-dependent interface!!
Solvers like RK4 wants:
SUBROUTINE F (YDOT,Y,T)
(Bad) Solution: COMMON block (global data)
The Object-Oriented Way
• User program can access base class (solver unknown)
• Class hierarchy of solvers
• Embedding of problem-specific parameters
• Uniform interface for function f
Suggested Design
ForwardEuler
RungeKutta2
ODEProblem
ODESolver
RungeKutta4
RungeKutta4A
Oscillator
...
...
Class ODESolver
class ODESolver
{
protected:
// members only visible in subclasses
ODEProblem* eqdef;
// definition of the ODE in user's class
public:
// members visible also outside the class
ODESolver (ODEProblem* eqdef_)
{ eqdef = eqdef_; }
virtual ~ODESolver () {} // always needed, does nothing here...
virtual void init();
// initialize solver data structures
virtual void advance (Vec(real)& y, real& t, real& dt);
};
Class ODEProblem
class ODEProblem
{
protected:
ODESolver* solver;
// some ODE solver
Vec(real) y, y0;
// solution (y) and initial condition (y0)
real
t, dt, T; // time loop parameters
public:
ODEProblem () {}
virtual ~ODEProblem ();
virtual void timeLoop ();
virtual void equation (Vec(real)& f, const Vec(real)& y, real t);
virtual int size (); // no of equations in the ODE system
virtual void scan ();
virtual void print (Os os);
};
The Time Loop
void ODEProblem:: timeLoop ()
{
Os outfile (aform("%s.y",casename.c_str()), NEWFILE);
t = 0; y = y0;
outfile << t << " "; y.print(outfile); outfile << '\n';
while (t <= T) {
solver->advance (y, t, dt); // update y, t, and dt
outfile << t << " "; y.print(outfile); outfile << '\n';
}
}
Class Oscillator
class Oscillator : public ODEProblem
{
protected:
real c1,c2,c3,c4,omega; // problem dependent paramters
public:
Oscillator () {}
virtual void equation (Vec(real)& f, const Vec(real)& y, real t);
virtual int size () { return 2; } // 2x2 system of ODEs
virtual void scan ();
virtual void print (Os os);
};
Defining the ODE
void Oscillator::equation (Vec(real)& f, const Vec(real)& y_, real t_)
{
f(1) = y_(2);
f(2) = -c1*(y_(2)+c2*y_(2)*abs(y_(2))) - c3*(y_(1)+c4*pow3(y_(1)))
+ sin(omega*t_);
}
Class ForwardEuler
class ForwardEuler : public ODESolver
{
Vec(real) scratch1;
// needed in the algorithm
public:
ForwardEuler (ODEProblem* eqdef_);
virtual void init (); // for allocating scratch1
virtual void advance (Vec(real)& y, real& t, real& dt);
};
Stepping the Solver
void ForwardEuler:: advance (Vec(real)& y, real& t, real& dt)
{
eqdef->equation (scratch1, y, t); // evaluate scratch1 (as f)
const int n = y.size();
for (int i = 1; i <= n; i++)
y(i) += dt * scratch1(i);
t += dt;
}
yil 1  yil  tf ( y1l ,, ynl , n, t m )
Parameter Classes
class ODESolver_prm
{
public:
String
method;
// name of subclass in ODESolver hierarchy
ODEProblem* problem;
// pointer to user's problem class
ODESolver*
create (); // create correct subclass of ODESolver
};
ODESolver* ODESolver_prm:: create ()
{
ODESolver* ptr = NULL;
if (method == "ForwardEuler")
ptr = new ForwardEuler (problem);
else if (method == "RungeKutta4")
ptr = new RungeKutta4 (problem);
else
errorFP("ODESolver_prm::create",
"Method \"%s\" is not available",method.c_str());
return ptr;
}
Problem Input
void ODEProblem:: scan ()
{
const int n = size(); // call size in actual subclass
y.redim(n); y0.redim(n);
s_o << "Give " << n << " initial conditions: ";
y0.scan(s_i);
s_o << "Give time step: ";
s_i >> dt;
s_o << "Give final time T: "; s_i >> T;
ODESolver_prm solver_prm;
s_o << "Give name of ODE solver: ";
s_i >> solver_prm.method;
solver_prm.problem = this;
solver = solver_prm.create();
solver->init();
// more reading in user's subclass
}
Problem Input
void Oscillator:: scan ()
{
// first we need to do everything that ODEProblem::scan does:
ODEProblem::scan();
// additional reading here:
s_o << "Give c1, c2, c3, c4, and omega: ";
s_i >> c1 >> c2 >> c3 >> c4 >> omega;
print(s_o); // convenient check for the user
}
The Main Program
int main (int argc, const char* argv[])
{
initDiffpack (argc, argv);
Oscillator problem;
problem.scan();
// read input data and initialize
problem.timeLoop(); // solve problem
return 0;
}
What Has Been Gained?
• Some “disturbance” of basic numerics
• Flexible library
• Faster to define problem (f) than hardcoding solver
• Simple example, ideas transfer to very complicated scenarios
Exercise: Extending the ODE Environment
Use the ODE environment to solve the problem
u’(t) = -u(t)
u(0) = 1
The analytical solution to this problem is u(t)=e-t.