IN229/Simulation: Lecture 3 Knut–Andreas Lie Department of Informatics University of Oslo

advertisement
'
$
IN229/Simulation: Lecture 3
Knut–Andreas Lie
Department of Informatics
University of Oslo
February 2003
&
%
'
$
IN229/Simulation: Lecture 3
Ordinary Differential Equations (ODEs)
What is an ODE?
An equation relating a function to its
derivatives (in such a way that the
function itself can be determined).
Very simple example from high school physics:
Consider the motion of a body with mass m
under constant force f , which is initially at rest at
position x0.
Newton’s second law reads
f = ma = mx00(t)
where x(t) is the position of the body at time t.
The corresponding ODE then reads
f = mx00(t),
x(0) = x0, x0(0) = 0
This equation is easily integrated
f 2
t.
x(t) = x0 + 2m
&
1
%
'
$
IN229/Simulation: Lecture 3
ODEs contd.
An ODE model from modern research,
describing the dynamics of HIV-1 infection in
vivo (Perelson& Nelson, SIAM Review 41/1, 1999) :
The rate of change of uninfected cells T ,
productively infected cells T ∗, and virus V :
dT
= s + pT 1 − T /Tmax − dT T − kV T
dt
dT ∗
= kV T − δT ∗
dt
dV
= N δT ∗ − cV.
dt
Here:
dT – death rate of uninfected cells
δ – death rate of infected cells
p – rate of proliferation
N – virus production per infected cell
c – clearance rate
&
2
%
&
y(a) = α,
a≤t≤b
i = 0, . . . , N − 1
This gives an explicit formula for each y(ti+1) once y0 is known.
y(ti+1) = y(ti) + hf (y(ti), ti),
• apply forward differences to the equation
• generate a mesh: ti = a + ih, for each i = 0, . . . , N
h = (b − a)/N is called stepsize
Obvious solution – use finite differences:
y 0 = f (y, t),
We wish to solve the equation:
Numerical solution of ODEs – Euler’s method
'
$
IN229/Simulation: Lecture 3
3
%
&
0
2
4
6
8
10
12
14
16
18
20
0
0.1
Exact
h=1/4
h=1/16
h=1/64
0.2
0.3
0.4
0.5
0.6
0.7
0.8
0.9
1
Graphical illustration of Euler’s method: u0 = 3u
'
$
IN229/Simulation: Lecture 3
4
%
&
y(a) = α,
a≤t≤b
i = 0, . . . , N − 1
This gives an equation for each y(ti+1) once y0 is known.
y(ti+1) = y(ti) + hf (y(ti+1), ti+1),
This time we apply backward differences
and introduce a mesh: ti = a + ih, for each i = 0, . . . , N
y 0 = f (y, t),
Once again we consider:
Another Euler method – backward Euler
'
$
IN229/Simulation: Lecture 3
5
%
&
the new value is given by an algebraic equation
y(ti+1) = y(ti) + hf (y(ti+1), ti+1)
• backward Euler: implicit method
the new value is given by a formula
y(ti+1) = y(ti) + hf (y(ti), ti)
• forward Euler: explicit method
Two different methods
'
$
IN229/Simulation: Lecture 3
6
%
&
−1
−0.8
−0.6
−0.4
−0.2
0
0.2
0.4
0.6
0.8
1
0
0.1
0.2
0.3
Exact
Forward Euler
Backward Euler
0.4
u(xi) = u(xi−1) − 4π sin(4πxi−1)
0.5
0.6
0.7
0.8
0.9
1
u(xi) = u(xi−1) − 4π sin(4πxi)
The two Euler methods: u0 = −4π sin(4πx) u(0) = 1
'
$
IN229/Simulation: Lecture 3
7
%
&
error = ÿ(τ )h/2 =
d
f (y(τ ))h/2
dt
We say that forward Euler is a first-order method.
for ti ≤ τ ≤ ti+1. Now since ẏ(ti) = f (y(ti)),
y(ti) + hẏ(ti) + ÿ(τ )h2/2 − y(ti) = hf (y(ti)) + h · error
y(ti+1) − y(ti)
= f (y(ti)) + error
h
Expanding y(ti+1) by a Taylor polynomial:
Consider ẏ = f (y). When using forward Euler, we make an error,
Truncation errors – the error we make at each point
'
$
IN229/Simulation: Lecture 3
8
%
&
yi+1 = yi + hf (yi + hf (yi)/2)
We have a new method:
This means that β = hf (yi)/2.
f (yi) + f 0(yi)f (yi)h/2 = f (yi + b) = f (yi) + βf 0(yi) + β 2 + . . .
T aylor
Assume now that error = f¨(τ )h2/6. Then
f (yi)h + f˙(yi)h2/2 + f¨(τ )h3/6 = hf (yi + β) + h · error
Consider the forward Euler method: yi+1 = yi + hf (yi).
What if we evaluate f () at some other point?
Higher order methods – Runge–Kutta
'
$
IN229/Simulation: Lecture 3
9
%
&
For higher-order methods we use more intermediate steps.
• Combine yi, w, f (yi), f (w) to get a more accurate solution yi+1.
• Approximate the solution at a point ti ≤ τ ≤ ti+1 by the
intermediate step w = yi + (τ − ti)f (yi)
The methods are all on the form:
There are other alternatives also, e.g.,
yi+1 = yi + (h/2) f (yi) + f (yi + hf (yi))
Runge-Kutta methods....
'
$
IN229/Simulation: Lecture 3
10
%
'
$
IN229/Simulation: Lecture 3
ODE Solver Environment
The purpose of this example:
• OO-design for a simple problem
• Continue the overview of C++ features
• Principles apply to advanced simulations
Mathematical problem:
dyi
= fi(y1, . . . , yn, t),
dt
yi(0) = yi0, i = 1, . . . , n
• A system of ordinary differential equations
(ODEs).
• A plenitude of numerical methods exist.
• Occurs frequently in numerics1
1
You will encounter solution of ODEs later when streamlines are taught in the
visualization part.
&
11
%
&
• TOL1, TOL2,.. are various parameters
• TSTEP is the current time step
• WORK1 is a work array (since runtime allocation is not allowed
in F77)
• F is a function defining the fi’s
• T is the value of t
• Y is current y
Here:
SUBROUTINE RK4(Y,T,F,WORK1,N,TSTEP,TOL1,TOL2,...)
Typical interface in FORTRAN77:
Traditional procedural solution
'
$
IN229/Simulation: Lecture 3
12
%
'
$
IN229/Simulation: Lecture 3
FORTRAN 77 contd.
For a specific ODE:
ÿ + c1(ẏ + c2ẏ|ẏ|) + c3(y + c4y 3) = sin ωt
Written as a system (ẏ1 = y2, ẏ2 = ÿ)
f1 = y2,
f2 = −c1(y2 + c2y2|y2|) − c3(y1 + c4y13) + sin ωt
Possible FORTRAN function
SUBROUTINE F(YDOT,Y,T,C1,C2,C3,C4,OMEGA)
Unfortunately this is problem dependent and
cannot be used. Instead,
SUBROUTINE F(YDOT,Y,T)
with C1,C2,C3,C4,OMEGA transferred in
COMMON blocks.
=⇒ dangerous side effects and possible hidden
bugs
&
13
%
'
$
IN229/Simulation: Lecture 3
OO Design
Our software problem consists of
• Different ODE solvers
• Different problems (fi’s)
Design: introduce two class hierarchies
• ODESolver (e.g., forward Euler, Runge–Kutta , ..)
Base class with:
• initialisation of solver
• virtual function advance for one time step
• ODEProblem (e.g., Newton’s 2.law, HIV-1 virus, ..)
Base class with:
• the generic system - virtual function
equation
• a driver function timeLoop
• common data: ∆t, T , name of solver,..
ODESolver must access ODEProblem and
vice versa
&
14
%
&
// members only visible in subclasses
// definition of the ODE in user’s class
// tell C++ that this class name exists
Notice: protected means public access for derived classes
and private access for others.
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 (MyArray<double>& y,
double& t, double& dt);
};
class ODESolver
{
protected:
ODEProblem* eqdef;
class ODEProblem;
Implementation in C++
'
$
IN229/Simulation: Lecture 3
15
%
&
// tell C++ that this class name exists
• timeLoop can be written in base class
• scan and print extended in subclasses
• equation and size only in subclasses
class ODEProblem
{
protected:
ODESolver*
solver;
// some ODE solver
MyArray<double> y, y0;
// solution (y) and initial cond. (y0)
double
t, dt, T; // time loop parameters
public:
ODEProblem () {}
virtual ~ODEProblem ();
virtual void timeLoop ();
virtual void equation (MyArray<double>& f,
const MyArray<double>& y, double t);
virtual int size (); // no of equations in the ODE system
virtual void scan ();
virtual void print (ostream& os);
};
class ODESolver;
The ODEProblem class
'
$
IN229/Simulation: Lecture 3
16
%
&
This function is generic and can be coded in the base class.
void ODEProblem:: timeLoop ()
{
ofstream outfile("y.out");
t = 0; y = y0;
outfile << t << " "; y.print(outfile); outfile << endl;
while (t <= T) {
solver->advance (y, t, dt);
outfile << t << " "; y.print(outfile); outfile << endl;
}
}
The timeLoop function
'
$
IN229/Simulation: Lecture 3
17
%
&
f2 = −c1(y2 + c2y2|y2|) − c3(y1 + c4y13) + sin ωt
We inherit y, y0, t, dt, T and functions from ODEProblem.
timeLoop can be reused and does not appear explicitly
class Oscillator : public ODEProblem
{
protected:
double c1,c2,c3,c4,omega; // problem dependent paramters
public:
Oscillator () {}
virtual void equation (MyArray<double>& f,
const MyArray<double>& y, double t);
virtual int size () { return 2; } // 2x2 system of ODEs
virtual void scan ();
virtual void print (ostream& os);
};
f1 = y2,
Right-hand side of ẏ(t) = f (y)
One specific problem — Oscillator
'
$
IN229/Simulation: Lecture 3
18
%
&
void Oscillator::equation(MyArray<double>& f,
const MyArray<double>& y, double t)
{
f(1) = y(2);
f(2) = -c1*(y(2)+c2*y(2)*fabs(y(2))) - c3*y(1)*(1.0+c4*y(1)*y(1))
+ sin(omega*t);
}
Oscillator contd.
'
$
IN229/Simulation: Lecture 3
19
%
&
void ForwardEuler::advance(MyArray<double>& y, double& t, double& 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;
}
class ForwardEuler : public ODESolver
{
MyArray<double> scratch1;
// needed in the algorithm
public:
ForwardEuler (ODEProblem* eqdef_);
virtual void init (); // for allocating scratch1
virtual void advance (MyArray<double>& y, double& t, double& dt);
};
yi`+1 = yi` + ∆tf (y1` , . . . , y `, n, tm)
Forward Euler is the simplest possible scheme
A specific solver — ForwardEuler
'
$
IN229/Simulation: Lecture 3
20
%
&
∆t
f (y ` , tm )
2
∆t
`+1/4
= yi
+
f (y `+1/4 , tm + ∆t/2)
2
∆t
= yi` + ∆tf (y `+2/4 , tm +
)
2
∆t h `
∆t
`
= yi +
f (y , tm ) + 2f (y `+1/4 , tm +
)
6
2
i
∆t
`+2/4
`+3/4
+2f (y
, tm +
) + f (y
, tm + ∆t)
2
= yi` +
class RungeKutta4 : public ODESolver
{
MyArray<double> scratch1, scratch2, scratch3; // needed in algorithm
public:
RungeKutta4 (ODEProblem* eqdef_);
virtual void init ();
virtual void advance (MyArray<double>& y, double& t, double& dt);
};
yi`+1
yi
`+3/4
yi
`+2/4
`+1/4
yi
Another solver – RungeKutta4
'
$
IN229/Simulation: Lecture 3
21
%
&
}
for (i = 1; i <= n; i++)
y(i) = y(i) + dt6*(scratch1(i) + scratch2(i) + 2*scratch3(i));
t += dt;
void RungeKutta4:: advance (MyArray<double>& y, double& t, double& dt)
{
const double dt2 = 0.5*dt;
const double dt6 = dt/6.0;
const int n = y.size();
eqdef->equation (scratch1, y, t);
int i;
for (i = 1; i <= n; i++)
scratch2(i) = y(i) + dt2 * scratch1(i);
eqdef->equation (scratch1, scratch2, t+dt2);
for (i = 1; i <= n; i++)
scratch2(i) = y(i) + dt2 * scratch1(i);
eqdef->equation (scratch3, scratch2, t+dt2);
for (i = 1; i <= n; i++)
{
scratch2(i) = y(i)
+ dt * scratch3(i);
scratch3(i) = scratch1(i) + scratch3(i);
}
eqdef->equation (scratch1, scratch2, t+dt);
eqdef->equation (scratch2, y, t);
RungeKutta4 contd.
'
$
IN229/Simulation: Lecture 3
22
%
&
Oscillator
ODEProblem
ODESolver
The class hierarchy
........
RungeKutta4A
RungeKutta4
RungeKutta2
ForwardEuler
'
$
IN229/Simulation: Lecture 3
23
%
&
// name of subclass in ODESolver hierarchy
// pointer to user’s problem class
// create correct subclass of ODESolver
ODESolver* ODESolver_prm::create ()
{
ODESolver* ptr = NULL;
if
(strcmp(method, "ForwardEuler") == 0)
ptr = new ForwardEuler (problem);
else if (strcmp(method, "RungeKutta4") == 0)
ptr = new RungeKutta4 (problem);
else {
cout << "\n\nODESolver_prm::create:\n\t"
<< "Method " << method << " is not available\n\\n";
exit(1);
}
return ptr;
}
class ODESolver_prm
{
public:
char
method[30];
ODEProblem* problem;
ODESolver*
create ();
};
How to connect to initialize?
'
$
IN229/Simulation: Lecture 3
24
%
&
}
ODESolver_prm solver_prm;
cout << "Give name of ODE solver: ";
cin >> solver_prm.method;
solver_prm.problem = this;
solver = solver_prm.create();
solver->init();
// more reading in user’s subclass
cout << "Give time step: ";
cin >> dt;
cout << "Give final time T: "; cin >> T;
cout << "Give " << n << " initial conditions: ";
// y0.scan(cin);
int i;
for(i=1; (i<=n) && (cin>>y0(i)); i++);
cout << "Read " << i-1 << "elements";
y0.print(cout); cout<<endl;
void ODEProblem:: scan ()
{
const int n = size(); // call size in actual subclass
y.redim(n); y0.redim(n);
Initialization of ODEProblem
'
$
IN229/Simulation: Lecture 3
25
%
&
void Oscillator:: scan ()
{
// first we need to do everything that ODEProblem::scan does:
ODEProblem::scan();
// additional reading here:
cout << "Give c1, c2, c3, c4, and omega: ";
cin >> c1 >> c2 >> c3 >> c4 >> omega;
print(cout); // convenient check for the user
}
Initialization of a specific problem
'
$
IN229/Simulation: Lecture 3
26
%
&
• faster start for the next ODE we wish to solve....
• introduction to general code design principles
• a flexible ODE library
Conclusion:
int main (int argc, const char* argv[])
{
Oscillator problem;
problem.scan();
// read input data and initialize
problem.timeLoop(); // solve problem
}
#include "Oscillator.h"
... and finally the main program
'
$
IN229/Simulation: Lecture 3
27
%
&
$(OBJ)
$(CXX) $(CXXFLAGS) $(OBJ) -o $@
main.o
ForwardEuler.o: ForwardEuler.h ODESolver.h MyArray.h
:
:
# DO NOT DELETE THIS LINE -- make depend depends on it.
depend :; makedepend -f Makefile -- *.C
clean:; rm -f $(OBJ) $(EXE)
.PHONY: clean depend all
$(EXE):
all: depend $(EXE)
EXE = myprog
OBJ = ForwardEuler.o ODEProblem.o ODESolver.o \
ODESolver_prm.o RungeKutta4.o Oscillator.o
CXXFLAGS = -Wall -g
CXX
= g++
And while we are at it - Makefile
'
$
IN229/Simulation: Lecture 3
28
%
&
#
#
#
#
#
y0
dt
final time
method
c1 c2 c3 c4 omega
y(0) = 0, ẏ(0) = 1
And run the program: myprog < data.in
The comments “# ...” will not work with the current implementation.
0.0 1.0
0.001
30
RungeKutta4
0 0 1 0 0.5
Create an input file:
ÿ + y = sin(0.5t),
Consider the following equation:
An example
'
$
IN229/Simulation: Lecture 3
29
%
&
−1.5
−1
−0.5
0
0.5
1
1.5
0
5
y1=y
y2=dy/dt
10
15
20
Plot of the solution
25
30
'
$
IN229/Simulation: Lecture 3
30
%
Download