Runge-Kutta algorithm C++
Asked Answered
K

3

5

Below is my 4th order Runge-Kutta algorithm to solve a first order ODE. I am checking it against the wikipedia example found here to solve:

\frac{dx}{dt} = tan(x) + 1

Unfortunately it is out by a little bit. I have toyed around for a long while, but I can't find the error. The answer should be t = 1.1 and x = 1.33786352224364362. The below code gives t = 1.1 and x = 1.42223.

/*

This code is a 1D classical Runge-Kutta method. Compare to the Wikipedia page.

*/

#include <math.h> 
#include <iostream>
#include <iomanip>

double x,t,K,K1,K2,K3,K4;

const double sixth = 1.0 / 6.0;

static double dx_dt(double t, double x){
    return tan(x) + 1;
}

int main(int argc, const char * argv[]) {

/*======================================================================*/
/*===================== Runge-Kutta Method for ODE =====================*/
/*======================================================================*/

double t_initial = 1.0;// initial time
double x_initial = 1.0;// initial x position

double t_final = 1.1;// value of t wish to know x 
double dt = 0.025;// time interval for updates
double halfdt = 0.5*dt;

/*======================================================================*/

while(t_initial < t_final){

    /*============================ Runge-Kutta increments =================================*/ 

    double K1 = dt*dx_dt( t_initial, x_initial );
    double K2 = dt*dx_dt( t_initial + halfdt, x_initial + halfdt*K1 );
    double K3 = dt*dx_dt( t_initial + halfdt, x_initial + halfdt*K2 );
    double K4 = dt*dx_dt( t_initial + dt, x_initial + dt*K3 );

    x_initial += sixth*(K1 + 2*(K2 + K3) + K4);

    /*============================ prints =================================*/

    std::cout << t_initial << std::setw(16) << x_initial << "\n";

    /*============================ re-setting update conditions =================================*/

    t_initial += dt;

    /*======================================================================*/
}

std::cout<<"----------------------------------------------\n";
std::cout << "t =  "<< t_initial << ",  x =  "<< x_initial << std::endl; 


}/* main */
Kotick answered 17/3, 2017 at 11:24 Comment(13)
Does it get better if you make dt smaller?Tarry
you are off at the very first step by 0.064. At t0 = 1 the y0 should be 1 according with the wiki page that you linkedPlacentation
double dx_dt(double t, double x) looks funny as you do not use argument t inside the functionLai
@RichardCritten That's actually common in Mathematical solvers, because generally it can be a function of x or t or neither or both.Tarry
@TheQuantumPhysicist Ah! It does improve when dt is set to 0.00000025. Why does the online one work however with this step size?Kotick
@TheQuantumPhysicist - my SO test project has all warning set to max so it's flagging unused parameters etc. Sorry if I was misleading.Lai
@Kotick Actually this doesn't necessarily mean that you don't have anything wrong with your code. On the other hand, if making dt smaller doesn't help, then you definitely have a mistake. Btw, out of curiosity, why don't you use one of the solvers available for you for free online out there?Tarry
@TheQuantumPhysicist Thanks, I have tried the following site too and it is in agreement with wikipedia keisan.casio.com/exec/system/1222997077Kotick
@Kotick At this point, I recommend that you start learning how to use a debugger, and make sure that every value is what you expect it's, starting from the first k1.Tarry
Calls to dx_dt(...) are varying its first parameter t (which is being ignored) this must be wrong.Lai
Possible duplicate of Integration using the Trapezium Rule in C giving wrong answer for certain valuesCar
Other questions exhibiting the same problem: #35071893, #29803842, #34160453Respiration
If using C++, there should be no C-headers involved. Use #include<cmath> for the C++ header with std namespace.Respiration
T
5

The problem is that the tableau used for your code is different than the one for the code you cited in wikipedia. The one you're using is this:

0   |
1/2 |   1/2
1/2 |   0       1/2
1   |   0       0       1   
-------------------------------------
    |   1/6     1/3     1/3     1/6

And the one used in wikipedia is

0   |
2/3 |   2/3     
---------------------
    |   1/4     3/4

Different tableaus will yield different results depending on the step-size, which is the way used to make sure that the step-size is good enough for a certain accuracy. However, when dt -> 0, then all tableaus are the same.

Besides all this, your code is wrong anyway even for RK4. The second part of the function should have halves, not 0.5*dt:

double K1 = dt*dx_dt( t_initial, x_initial );
double K2 = dt*dx_dt( t_initial + halfdt, x_initial + 0.5*K1 );
double K3 = dt*dx_dt( t_initial + halfdt, x_initial + 0.5*K2 );
double K4 = dt*dx_dt( t_initial + dt, x_initial + K3 );
Tarry answered 17/3, 2017 at 12:3 Comment(2)
Your second tableau is for the second order Ralston method, the task apparently asked for the 4th order classical Runge-Kutta method of the first tableau.Respiration
@PeterSM: You also redefine K1,K2,K3,K4 within the loop from the above variables, and K remains unused.Portrait
R
4

You are making a rather usual mistake in trying to be overly correct and implement the two variants of the algorithm at once.

It should either be

k2 = dt*f(t+0.5*dt, x+0.5*k1)

or

k2 = f(t+0.5*dt, x+0.5*dt*k1)

the other ks accordingly.

Note that in both cases the slope fonly gets multiplied with dt once.

Respiration answered 17/3, 2017 at 12:5 Comment(0)
P
3

I think you are including one too many increments and have introduced problems by rearranging the mathematics. Try this:

#include <math.h> 
#include <iostream>
#include <iomanip>

static double dx_dt(double t, double x)
{
    return tan(x) + 1;
}

int main(int argc, const char * argv[])
{
    double t = 1.0;
    double t_end = 1.1;

    double y = 1.0;
    double h = 0.025;

    std::cout << std::setprecision(16);

    int n = static_cast<int>((t_end - t) / h);

    for (int i = 0; i < n; i++)
    {
        double k1 = dx_dt(t, y);
        double k2 = dx_dt(t + h / 2.0, y + h*k1 / 2.0);
        double k3 = dx_dt(t + h / 2.0, y + h*k2 / 2.0);
        double k4 = dx_dt(t + h, y + h*k3);

        y += (k1 + 2 * k2 + 2 * k3 + k4) * h / 6.0;

        std::cout << t << ": " << y << std::endl;

        t += h;
    }

    std::cout << "----------------------------------------------\n";
    std::cout << "t =  " << t << ",  x =  " << y << std::endl;

    std::getchar();
}

I precalculate how many times to do the iteration, this avoids a few different issues. Also as others have mentioned, the worked example on wikipedia is for a two stage variant of the algorithm.

I've taken the liberty of changing the variable names to match wikipedia. A good tip is always match the naming of your reference text until the thing works.

Prolamine answered 17/3, 2017 at 12:7 Comment(1)
One could also stay with the original code and add a cut-off if(t_initial+dt > t_final) dt = t_final-t_initial; for the last step.Respiration

© 2022 - 2024 — McMap. All rights reserved.