Runge-Kutta算法学习

文章目录

    • 原理
    • 欧拉方法
    • 改进欧拉方法
    • 遇到的问题
    • MATLAB与C语言程序

原理

此方法主要用来求取微分方程或者微分方程组的数值解,主要思想是将微分方程化为差分方程,然后迭代解出差分方程在一系列点上的值

因此求解步骤主要分为两步:

  • 离散:将微分方程离散化,建立差分方程。而离散化的方法主要有差商法、泰勒级数法与数值积分法(利用矩形或者梯形的面积来计算积分)。
  • 递推:由已知的y(0)逐步计算出解在一系列点上的值。

欧拉方法

其中差商法的基本公式为:
d y d x = f ( x , y ) \frac{dy}{dx}=f(x,y) dxdy=f(x,y)
也就是说
y i + 1 − y i h ≈ f ( x i , y i ) \frac{y_{i+1}-y_i}{h}\approx f(x_i,y_i) hyi+1yif(xi,yi)
所以,欧拉公式可以表示为:
y i + 1 = y i + h f ( x i , y i ) y_{i+1}=y_i+hf(x_i,y_i) yi+1=yi+hf(xi,yi)
这样利用简单的差商法可以得到一般的欧拉方法。但是欧拉方法的局部截断误差会在后面的迭代计算中逐步累积,计算结果和实际数值相差较大。属于一阶精度的计算方法。

对于欧拉方法,减小步长h可以提高计算精度,但是同时也会增大计算负担。该方法尽管效率不高,但是简单易用是他的优势。

为了进一步提高欧拉方法的计算精度,可以改变做差的方式。使用向后差商公式或者中心差商公式。这两种做差方法可以多利用迭代中前一次或者前前一次的数值信息,参与本次数值的计算,从而提高计算精度。因此这种方法也称为两步法,这是一种二阶方法。

其中采用了中心差商的欧拉方法公式如下:
y ′ ( x i ) = y ( x i + 1 ) − y ( x i − 1 ) 2 h − h 2 6 y ( 3 ) ( ξ i ) y'(x_{i})= \frac{y(x_{i+1})-y(x_{i-1})}{2h}-\frac{h^{2}}{6}{y^{(3)}}(\xi_i) y(xi)=2hy(xi+1)y(xi1)6h2y(3)(ξi)
可得
y ( x i + 1 ) = y ( x i − 1 ) + 2 h f ( x i , y ( x i ) ) + h 3 3 y ( 3 ) ( ξ i ) y(x_{i+1})=y(x_{i-1})+2hf(x_i,y(x_i))+\frac{h^3}{3}y^{(3)}(\xi_i) y(xi+1)=y(xi1)+2hf(xi,y(xi))+3h3y(3)(ξi)
因此得到差分方程如下
y i + 1 = y i − 1 + 2 h f ( x i , y i ) y_{i+1}=y_{i-1}+2hf(x_i,y_i) yi+1=yi1+2hf(xi,yi)
同理也容易计算出向后差商的欧拉公式:
y i + 1 = y i + h f ( x i + 1 , y i + 1 ) y_{i+1}=y_i+hf(x_{i+1},y_{i+1}) yi+1=yi+hf(xi+1,yi+1)

改进欧拉方法

前面说过,微分方程离散化的方法除了差商法还有数值积分法。
因为
y ( x i + 1 ) = y ( x i ) + ∫ x i x i + 1 f ( x , y ( x ) ) d x y(x_{i+1})=y(x_i)+\int_{x_i}^{x_{i+1}}f(x,y(x))dx y(xi+1)=y(xi)+xixi+1f(x,y(x))dx
而后面这一部分积分可以采用矩形面积或者梯形面积来替代,并且使用梯形面积来计算的精度较高。由此可以得到一个二阶精度梯形公式:
y i + 1 = y i + h 2 [ f ( x i , y i ) + f ( x i + 1 , y i + 1 ) ] y_{i+1}=y_i+\frac{h}{2}[f(x_i,y_i)+f(x_{i+1},y_{i+1})] yi+1=yi+2h[f(xi,yi)+f(xi+1,yi+1)]
改进后的梯形公式虽然精度较高,但是公式两边都有 y i + 1 y_{i+1} yi+1,需要迭代求解,计算量大。

为了在保证精度的基础上减小计算量,可以将梯形公式和普通欧拉方法结合起来。
先利用欧拉方法得到初步的近似值,记为 y ˉ i + 1 \bar{y}_{i+1} yˉi+1,也称为预测值。利用这个预测值代替梯形公式中的 y i + 1 y_{i+1} yi+1,再计算得到进一步的校正值 y i + 1 y_{i+1} yi+1。利用这种预测-校正的方法,可以很大的减小的计算量。感觉这也是改进一种复杂算法的一种很好的方式。当发现一个算法很好的时候,但是计算量比较大,可以将该算法进行步骤分解,看看有没有哪种更简单的方法可以近似替代原算法当中的步骤,从而慢慢改进某一个算法。
经过上面的分析,可以得到改进欧拉算法,可以写成下面三个公式
y i + 1 = y i + 1 2 ( k 1 + k 2 ) y_{i+1}=y_i+\frac{1}{2}(k_1+k_2) yi+1=yi+21(k1+k2)
k 1 = h f ( x i , y i ) k_1=hf(x_i,y_i) k1=hf(xi,yi)
k 2 = h f ( x i + h , y i + k 1 ) k_2=hf(x_i+h,y_i+k_1) k2=hf(xi+h,yi+k1)
这也是二阶龙格-库塔法的一个公式。可以利用这个公式计算微分方程(组)的数值解。

遇到的问题

上面的公式是求取单个微分方程的公式,当遇到高阶的微分方程,可以利用变量代换将其转换为n个一阶的微分方程组。然后对该微分方程组进行求解。但是在利用二阶龙格-库塔法求解微分方程组的时候,涉及多个函数变量,需要注意的是每个函数变量需要同步进行计算。这一点可以从四阶龙格-库塔法中找到规律:
Runge-Kutta算法学习_第1张图片

MATLAB与C语言程序

在MATLAB中可以借助ode23或者ode45函数来求解微分方程的数值解,使用较为简单。这里主要介绍我编写的C语言程序求解一个微分方程组。方程组解析式较为复杂,但是可以在程序中看出。

#include 
#include 
#include 

//需要迭代的次数
#define iterationNumber     500
//求解区间
const double tLow = 0.0;
const double tUp = 5.0;
//步长,注意步长的选择决定着计算结果是收敛还是发散
//一开始我步长设置为0.1计算结果会发散
const double h = 0.01;
//物理常量
const double PI = 3.14;
const double G = 9.8;
//初始条件
const double q0 = 0.5;
const double q0_d = 0;
const double z0 = 15;
//系统参数
const double ko = 50;
const double kp = 10;
const double kv = 5;
//龙格库塔法计算时需要的中间变量
typedef struct{
    double k1, k2;
    double yArray[iterationNumber+1];
}structSolve;
//需要求解的3个函数值
structSolve y1_d, y2_d, y3_d;

//给定参考轨迹及其各阶导数
double qd(double t)
{
    double qdVal = 0.1*cos(5*PI*(t));
    return qdVal;
}
double qd_d(double t)
{
    double qd_dVal = -0.5*PI*sin(5*PI*(t));
    return qd_dVal;
}
double qd_dd(double t)
{
    double qd_ddVal = -2.5*PI*PI*cos(5*PI*(t));
    return qd_ddVal;
}
//系统拉格朗日方程中的参数
double m(double q)
{
    double mVal = 1 + cos(q)*cos(q);
    return mVal;
}
double c(double q)
{
    double cVal = -0.5*sin(2*(q));
    return cVal;
}
double g(double q)
{
    double gVal = G*sin(q);
    return gVal;
}
//控制器
double tau(double q, double z, double t)
{
    double temp1 = m(q)*qd_dd(t),\
           temp2 = c(q)*qd_d(t)*qd_d(t),\
           temp3 = g(q),\
           temp4 = kp*((q) - qd(t)),\
           temp5 = (kv - c(q)*qd_d(t)/sqrt(m(q)))*((z) + ko*((q)-qd(t)));

    return (temp1+temp2+temp3-temp4-temp5);
}
double z_d(double q, double z, double t)
{
    double temp1 = (-z)*(ko+kv)/sqrt(m(q)),\
           temp2 = ko*(ko+kp/ko+kv)*((q)-qd(t))/sqrt(m(q));

    return (temp1-temp2);
}

//利用变量代换将高阶微分方程化为微分方程组
//其中,令:y1=q, y2=q_d, y3=z
//得到如下三个微分方程
//方程1:dy1/dt=y2
double ODE1(double y2)
{
    return y2;
}
//方程2:dy2/dt=(tau-g*sin(y1)-c(q)*y2*y2)/m(q)
double ODE2(double y1, double y2, double y3, double t)
{
    double temp = tau(y1,y3,t);
    double ans = (temp - g(y1) - c(y1)*(y2)*(y2))/m(y1);
    return ans;
}
//方程3:dy3/dt=z_d
double ODE3(double y1, double y3, double t)
{
    double ans = z_d(y1,y3,t);
    return ans;
}

/*
@function :二阶龙格库塔法实现微分返程组的求解
@parameter :方程组的初始条件
*/
void systemODE(double qInit, double DqInit, double zInit)
{
    int i;
    double t;
    //初始状态赋值
    t = tLow;
    y1_d.yArray[0] = qInit;
    y2_d.yArray[0] = DqInit;
    y3_d.yArray[0] = zInit;

    //龙格库塔法进行迭代计算
    for(i=0; i<iterationNumber; i++)
    {
        //依次计算微分方程组中每个函数的k1、k2等
        y1_d.k1 = h*ODE1(y2_d.yArray[i]);
        y2_d.k1 = h*ODE2(y1_d.yArray[i], y2_d.yArray[i], y3_d.yArray[i], t);
        y3_d.k1 = h*ODE3(y1_d.yArray[i], y3_d.yArray[i], t);

        y1_d.k2 = h*ODE1(y2_d.yArray[i]+0.5*y2_d.k1);
        y2_d.k2 = h*ODE2(y1_d.yArray[i]+0.5*y1_d.k1, y2_d.yArray[i]+0.5*y2_d.k1, y3_d.yArray[i]+0.5*y3_d.k1, t+0.5*h);
        y3_d.k2 = h*ODE3(y1_d.yArray[i]+0.5*y1_d.k1, y3_d.yArray[i]+0.5*y3_d.k1, t+0.5*h);

        y1_d.yArray[i+1] = y1_d.yArray[i] + y1_d.k2;
        y2_d.yArray[i+1] = y2_d.yArray[i] + y2_d.k2;
        y3_d.yArray[i+1] = y3_d.yArray[i] + y3_d.k2;

        t+= h;
    }
}

int main()
{
    //给定初始值进行求解
    systemODE(q0, q0_d, z0);

    //输出结果
    int i;
    for(i=0; i<iterationNumber; i++)
    {
        printf("y1=%3.5lf\ty2=%3.5lf\ty3=%3.5lf\n", y1_d.yArray[i],y2_d.yArray[i],y3_d.yArray[i]);
    }
    return 0;
}

你可能感兴趣的:(控制工程)