Runge-Kutta算法 C++

5
以下是我编写的用于解决一阶常微分方程的四阶龙格-库塔算法。我将其与维基百科文章(点击此处)中的例子进行对比检查:
\frac{dx}{dt} = tan(x) + 1

很不幸,结果有点偏差。我已经尝试了很长时间,但是无法找到错误。答案应该是t=1.1和x=1.33786352224364362。下面的代码给出的是t=1.1和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 */

如果你将“dt”缩小,会有更好的结果吗? - The Quantum Physicist
1
“double dx_dt(double t, double x)” 看起来很奇怪,因为你在函数内部没有使用参数“t”。 - Richard Critten
1
@TheQuantumPhysicist - 我的SO测试项目将所有警告设置为最大值,因此它会标记未使用的参数等。如果我有误导之处,请见谅。 - Richard Critten
2
@PeterSM 在这个阶段,我建议你开始学习如何使用调试器,并确保每个值都是你期望的,从第一个 k1 开始。 - The Quantum Physicist
1
如果使用C ++,则不应涉及C头文件。使用带有std命名空间的C ++头文件#include<cmath> - Lutz Lehmann
显示剩余8条评论
3个回答

5
问题在于你所使用的表格与维基百科中引用代码所使用的表格不同。你正在使用的是这个:
0   |
1/2 |   1/2
1/2 |   0       1/2
1   |   0       0       1   
-------------------------------------
    |   1/6     1/3     1/3     1/6

而维基百科中使用的是

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

步长不同的表格将产生不同的结果,步长是确保达到一定精度的方法。但是,当dt -> 0时,所有的表格都是相同的。

除此之外,即使对于RK4,你的代码也是错误的。函数的第二部分应该有一半,而不是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 );

你的第二个表格是用于第二阶Ralston方法的,任务明显要求使用第一张表格的4阶经典Runge-Kutta方法。 - Lutz Lehmann
@PeterSM:你在循环中重新定义了K1,K2,K3,K4这些变量,并且K仍然没有被使用。 - AndyG

4
你在尝试过于正确地同时实现两个算法变体时犯了一个相当普遍的错误。
应该是这样的:
k2 = dt*f(t+0.5*dt, x+0.5*k1)

或者

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

另外的k相应地进行调整。

需要注意的是,在这两种情况下,斜率f只会乘以dt一次。


3
我认为您包含了过多的增量,并通过重新排列数学式子引入了问题。请尝试以下方法:
#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();
}

我预先计算了迭代的次数,这可以避免一些问题。另外,正如其他人所提到的,维基百科上的示例是算法的两阶段变体。

我已经自行更改了变量名称以匹配维基百科。一个好的提示是始终将您的参考文本的命名与工作内容匹配,直到事情起作用为止。


一个选择是保留原始代码,在最后一步添加一个截止条件 if(t_initial+dt > t_final) dt = t_final-t_initial; - Lutz Lehmann

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接