






Study with the several resources on Docsity
Earn points by helping other students or get them with a premium plan
Prepare for your exams
Study with the several resources on Docsity
Earn points to download
Earn points by helping other students or get them with a premium plan
The principles of coding and numerically integrating ordinary differential equations (odes) using the fourth order runge kutta method. It covers the importance of well-defined problems, divide and conquer strategy, top-down design, optimal data structures, and conceptual simplicity. The document also provides a pseudo-code for implementing the main program and subroutines.
Typology: Assignments
1 / 11
This page cannot be seen from the preview
Don't miss anything!







Translating algorithms into code takes art, patience, and hard work. The process can be greatly shortened by following a few rules to implement the algorithm. The sources of the algorithm are not of concern here although in practice we should be familiar with the intent and limitations of any algorithm, particularly when it relates to numerical computations. We will leave these issues for later as we are mainly concerned with programming.
This section lists a few rules of thumbs to improve the coding of algorithms, and help in the homework problem.
Understand what is required in the computations, define the input, the output and the computations to be performed. Making sure the problem is well-defined goes a long way in determining what kind of input data and what sort of output is required.
Dividing the problem into functionally separate tasks helps in making large cod- ing projects manageable. Identify the repetitive tasks and consider isolating them into well-designed subroutines and functions. This subdivision is critical for the remainder of the programming tasks as it: divides into manageable units, allows
validation to proceed in stages, and simplify code reuse. The details of the compu- tations will then be boxed into compartments and a higher level abstraction about the algorithm is then possible. Most numerical tasks follow the plan:
Each of these tasks may contain a long list of substasks but the divide and conquer strategy can be applied to those recursively.
Start designing the problem from the top starting with the main program and filling in the subroutine as you go. This will help you clarify the flow of the code to yourself. Write shell routines in the initial go and start filling them and validating them in groups. Postpone the details of each routine as long as possible.
Describe in pseudo code (i.e. programming language independent) the steps needed in the algorithms.
In most numerical computations this will simply arrays as they are the most effi- cient data structures for many types of computations.
Avoid using obfuscated code even though it may seem great at the moment. Ob- fuscated code can be hard to read, and even the implementors would have a hard time remembering what it does 3 months after they coded it.
It is important to gain assurances that parts of the code are working properly in order to catch coding and (more ominously) algorithmic mistakes. It is then imperative that subroutines are validated as one is developing the code. If an error is introduced later it will useful to restrict its tracking to the untested portions of the code. Validation can take many forms:
stages to predict the value of the unknown function at the next time level starting from the known initial conditions. All that is needed is the repeated evaluation of the function F~ on the right hand side of equation 11.1. The steps of the algorithms can be described as follows: Given the solution at w~n^ and the time step ∆t:
F^ ~ (1)^ = F~ ( w~n, tn) , w~(1)^ = w~n^ + ∆t 2
( w ~(1), tn +
∆t 2
) , w~(2)^ = w~n^ +
∆t 2
( w ~(2), tn +
∆t 2
) , w~(3)^ = w~n^ + ∆t F~ (3)^ (11.9)
( w ~(3), tn + ∆t
) , w~n+1^ = w~n^ + ∆t
11.5 Analysis
Here we will attempt to define the problem and analyzing the algorithm so as to start sketching out the design of our code.
11.6 Problem definition
We need to integrate the function w~ in time given its initial conditions w~ 0.
The parameters of the problem we wish to compute consist of
These parameters are intrinsic to the problem and do not depend on the numerical scheme. The numerical parameters consist of:
The solution w~ need to be saved as the computations proceed. Diagnostics about the computations should be helpful in telling us whether we are proceeding on the right track or not. One possible diagnostics is the energy contained in the system. Multiplying the equations by u and v respectively and adding them up it is evident that
u
du dt
dv dt
d dt
( u^2 + v^2 2
) = 0 (11.11)
and hence the energy (u^2 + v^2 )/2 should remain constant at all times. It is thus a good idea to monitor the energy as the calculation proceeds.
One can make the following remarks about the algorithm:
routine Initialize routine save-solution routine diagnostics routine rk calculate rhs of equation and temporary value for each stage end routine rk routine rhs set physical parameter f find the slope for a given w and time routine rhs
11.7 The Coding and Incremental Validation
A first implementation of the main program, may look as follows
program inert implicit none integer, parameter :: nd=2! dimension of solution vector real :: w(nd)! solution vector integer :: Ntimes, isnap! number of time steps and save frequency
print *,’Enter dt, Ntimes, isnap’ read *, dt, Ntimes, isnap
call Initialize(w,nd) call PrintOut(w,nd) call Diagnostics(w,nd) do it = 1,Ntimes call Rk4(w,time,dt,nd) if (mod(it,isnap)==0) then call Printout(w,nd) call Diagnostics(w,nd) endif enddo
stop end program inert
There are a few variable declarations missing but the compiler will help us find those pretty easily because the implicit none will require that all variables be declared. Now we begin the task of writing the subroutines. A first cut at the initialize subroutine would look like:
subroutine Initialize(w,nd) implicit none integer, intent(in) :: nd real, intent(out0 :: w(nd) w(1) = 1. w(2) = 0. return end subroutine Initialize
The subroutine is quite simple and it is unlikely that we have introduced a bug. Nevertheless, we could probably produce an executable and run the code to check it out. To do that however, we would need to provide the remainder of the sub- routines. At this point all we need is the declaration for the computer to produce the executables. These would look like:
!*********************************************************************** subroutine PrintOut(w,nd) implicit none integer, intent(in) :: nd real, intent(in) :: w(nd) return end subroutine PrintOut !*********************************************************************** subroutine Diagnostics(w,nd) implicit none integer, intent(in) :: nd real, intent(in) :: w(nd) return end subroutine Diagnostics !*********************************************************************** subroutine Rk4(w,time,dt,nd) implicit none integer, intent(in) :: nd! dimension of solution vector real, intent(in) :: time,dt! time level and time step real, intent(inout) :: w(nd)! solution vector return end subroutine Rk
These routines contain only variable declarations and no executable statetements at this stage. They are enough now to create an executable and run the code to initialize the vector w. At this point we can ascertain that the code runs but we have no way of verifying the output; we need to write out the solution and the routine PrintOut is then necessary. It is probably best if we write the solution to a file that we can examine after the run is done. Here it is:
call rhs(r1,w,time,nd)! stage 1 wt = w + 0.5dtr call rhs(r2, wt, time+0.5dt,nd)! stage 2 wt = w + 0.5dtr call rhs(r3, wt, time+0.5dt,nd)! stage 3 wt = w + dtr call rhs(r4, wt, time+dt, nd)! stage 4 w = w + dt * ( r1 + 2.0(r2+r3) + r4 ) / 6.
return end subroutine Rk
Validating this code is relative simple provided we give it a trivial forcing func- tion. The solution should not change for example if the right hand side is zero. The right hand side subroutine for our case however would be as follows:
subroutine rhs(r,w,time,nd) implicit none integer, intent(in) :: nd real, intent(in) :: time, w(nd) real, intent(out) :: r(nd) real, parameter :: f = 1 r(1) = f * w(2) r(2) =-f * w(1) return end subroutine rhs
Notice that we chose to declare the Coriolis parameter locally since it is not needed by any of the other routines.
11.8 Run Plotting and Validation
Once the bugs and compiler errors are corrected, the code can be run for a short run. Initial runs should proceed with optimization parameters off, and debuggers parameters on. These should include
Once the code has been validated for a small test run, the optimization can be removed since they tend to slow execution severely.
The plotting in matlab is usually a breeze. Simply launch matlab and read in the 2 columns with the load matlab command. The two columns will contain the evolution of the solution caught at every isnap time steps.