zoukankan      html  css  js  c++  java
  • 求简单的常微分方程

      求常微分方程的原理(懒得重新打一遍。。于是把我知乎上的一个相关回答搬过来):

      这里先介绍一种方法,叫欧拉法,比如,形如:

    $$ left{ egin{aligned} frac{dy}{dx} &= f(x,y) \ y(x_0) &= y_0 end{aligned} ight. $$

      的一阶微分方程(注:用数值方法求解时,默认有解且唯一)。

      通过初始条件: $$ y(x_0)=y_0 $$

      可以计算出: $$ y'(x_0)=f(x_0,y_0) $$

      假设 $ x_1 - x_0 = h $ 充分小,则近似的有:

    $$ frac{y(x_1)-y(x_0)}{h} approx y'(x_0)=f(x_0,y_0) quad $$

      记 $$ y_i=y(x_i) quad i=0,1,...,n $$

      取 $$ y_1=y_0+hf(x_0,y_0) $$

      同样的方法,计算出

    $$ y_2=y_1+hf(x_1,y_1) $$

      于是得到递推公式:

    $$ y_{i+1}=y_i+hf(x_i,y_i) ,h为步长 $$

      参考:常微分方程组的数值算法解法 P1~2

      p.s.可看到实际上与牛顿切线法原理类似。。。牛顿法:

    $$ x_{i+1}=x_i-frac{f(x_i)}{f'(x_i)} $$

      例题1:求一阶微分方程

    $$ left{ egin{aligned} frac{dy}{dx} &= y \ y(0) &= 1 end{aligned} ight. \ 步长h=0.01,x=0.10时的值 $$

      下面是代码实现:

    #include<bits/stdc++.h>
    int main()
    {
        // 欧拉法:yi+1 = yi + h * f(x,y)
        // 
        double h = 0.01, x = 0.10, y = 1.00;
        for(double i = 0.00; i < x; i += h)
            y += h*y;
        printf("%.4f
    ", y);
        return 0;
    }

      Output:

    1.1157
    
    Process returned 0 (0x0)   execution time : 0.420 s
    Press any key to continue.
    

      精确结果:

    e^0.1
    1.1051709180756
    

      当h=0.001时:

    1.1051
    
    Process returned 0 (0x0)   execution time : 0.401 s
    Press any key to continue.
    

      看起来还是不错的,但精度还是不够,一些方程还需要更高的精度且x越大则会导致计算效率非常差。。。

    改进欧拉法

      考虑下面积分方程:

    $$ y(x)=y_0+ int _{x_0}^x f(t,y(t))dt $$

      等式右边第二项利用积分中值定理有:

    $$ int _{x_0}^x f(t,y(t))dt approx f(x_0,y(x_0))(x-x_0) $$

    $$ 当 x=x_1时,有int _{x_0}^{x_1} f(t,y(t))dt approx f(x_0,y(x_0))(x_1-x_0) $$

    $$ 取x_1-x_0=h,于是有y1=y0+hf(x_0,y(x_0)) $$

      这个形式与前面欧拉法递推公式相同,所以我们可以理解为什么欧拉法精度上会有不足,因为等式右边相当于利用黎曼求和法进行了函数的矩形面积求和,所以我们可以考虑利用梯形法求和提高精度,利用梯形公式有:

    $$ int _{x_0}^{x_1} f(t,y(t))dt approx frac{1}{2}[f(x_0,y(x_0))+f(x_1,y(x_1))](x_1-x_0) $$

    $$ 取x_1-x_0=h,有: $$

    $$ y_1=y_0+frac{h}{2}[(f(x_0,y_0)+f(x_1,y_1)] $$

      于是递推公式变为:

    $$ y_{i+1}=y_i+frac{h}{2}[(f(x_i,y_i)+f(x_{i+1},y_{i+1})] $$

      由于利用该公式进行求解时需要求解含有yi+1的方程,这常常是困难的,所以实际计算时,常常与欧拉法结合使用: 

    $$ left { egin{aligned} y_{i+1}^{(0)} &= y_i+hf(x_i,y_i) \ quad y_{i+1}^{(k+1)} &= y_i+frac{h}{2}[(f(x_i,y_i)+f(x_{i+1},y_{i+1}^{(k)})] quad k=0,1,2... end{aligned} ight. $$

      通常将上式称为预报校正公式,其中第一式叫预报公式,第二式叫校正公式。这个公式还可写为:

    $$ left { egin{aligned} y(x_{n+1}) &= y_n + frac{1}{2}k_1 + frac{1}{2}k_2 \ k_1 &= hf(x_n, y_n) \ k2 &= hf(x_n + h, y_n + k_1) end{aligned} ight. $$

      利用这个方法测试一下例题1:

      Output:

    [darkchii@localhost 文档]$ ./test
    Euler:1.1157 ReEuler:1.1163

      emmm...貌似我的写法有问题?

      例题2:求一阶微分方程

    $$ left{ egin{aligned} frac{dy}{dx} &= y-frac{2x}{y} \ y(0) &= 1 end{aligned} ight. $$

    $$ 当h=0.1,x=1.5时的值 $$

      代码实现:

    #include<bits/stdc++.h>
    int main()
    {
    	double h = 0.1, x = 1.50, y = 1.00, result = y;
    
    	// y_{i+1} = y_i + h * f(x_i,y_i)
    	// y_{i+1} = y_i + h/2 * (f(x_i,y_i) + f(x_{i+1},y_{i+1}))
    	
    	for(double i = 0.00; i < x; i += h)
    	{
    		y += h*(y-((2*i)/y));
    		result += (h/2.0)*(result-((2*i)/result) + y-((2*(i+h))/y));
    	}
    	printf("Euler:%.4f ReEuler:%.4f
    ", y, result);
    	return 0;
    }
    

      Output:

    [darkchii@localhost 文档]$ ./test
    Euler:2.1201 ReEuler:2.0819
    

      改进的欧拉法结果与书上例题给出的结果没对上,我的写法可能有问题。。。但按书上的意思是改进欧拉法只使用第一次欧拉法迭代一次的结果,但我觉得只用一次貌似没有意义...

      现在来考察两个公式的截断误差:即y(xn+1)-yn+1有多大?这里假定前一步得的结果yn=y(xn)是准确的。

        (1)欧拉法的截断误差

          将y(xn+1)用泰勒展开:

    $$ egin{aligned} y(x_{n+1}) &= y(x_n + h) \ &= y(x_n) + hy'(x_n) + frac{h^2}{2!}y''(x_n) + cdot cdot cdot quad (1) end{aligned} $$

          由欧拉法得:

    $$ y_{n+1} = y_n + hf(x_n, y_n) = y(x_n) + hy'(x_n) quad (2) $$

          将(1)、(2)两式相减有:

    $$ y(x_{n+1}) - y_{n+1} = frac{h^2}{2} y''(x_n) + cdot cdot cdot = O(h^2) $$

          即欧拉法的截断误差为O(h2),当h→0时,它与h2是同阶无穷小量。

        (2)改进欧拉法的截断误差
          以迭代一次的预报校正公式来说明,因为

    $$ egin{aligned} k_1 &= hf(x_n, y_n) = hf(x_n, y(x_n)) \ &= hy'(x_n) \ k_2 &= hf(x_n + h, y_n + k_1) \ &= hfegin{bmatrix} x_n + h, y(x_n) + k_1 end{bmatrix} \ &= h egin{Bmatrix} fegin{bmatrix}x_n, y(x_n)end{bmatrix} + h frac{partial}{partial x} fegin{bmatrix} x_n, y(x_n)end{bmatrix} + k_1 frac{partial}{partial y} fegin{bmatrix} x_n, y(x_n)end{bmatrix} + cdot cdot cdot end{Bmatrix} \ &=hfegin{bmatrix} x_n, y(x_n)end{bmatrix} + h^2 egin{Bmatrix} frac{partial}{partial x} fegin{bmatrix} x_n, y(x_n)end{bmatrix} + y'(x_n) frac{partial}{partial y} fegin{bmatrix}x_n, y(x_n)end{bmatrix} + cdot cdot cdot end{Bmatrix} \ &= hy'(x_n) + h^2 y''(x_n) + cdot cdot cdot end{aligned} $$

          将k1、k2代入预报校正公式得:

    $$ egin{aligned} y_{n+1} &= y_n + frac{h}{2} y'(x_n) + frac{h}{2} y'(x_n) + frac{h^2}{2} y''(x_n) + cdot cdot cdot \ &= y(x_n) + hy'(x_n) + frac{h^2}{2}y''(x_n) + cdot cdot cdot quad (3) end{aligned} $$

          再将(1)、(3)两式相减有:

    $$ y(x_{n+1}) - y_{n+1} = O(h^3) $$

          即改进欧拉法比欧拉法的阶提高了。

    泰勒级数法:

       如果初值问题

    $$ left{ egin{aligned} frac{dy}{dx} &= f(x,y) \ y(x_0) &= y_0 end{aligned} ight. $$

      的精确解y(x)在[x,x]上存在k+1阶导数且连续,那么由泰勒公式

    $$ y(x_{n+1}) = y(x_n) + hy'(x_n) + cdot cdot cdot + frac{h^k}{k!} y^{k}(x_n) + R_k $$

      其中截断误差为
    $$ egin{aligned} R_k= frac{h^{k+1}}{(k+1)!} y^{k+1}(xi) = O(h^{k+1}) \ x_n < xi < x_{n+1} end{aligned} $$

      略去截断误差,并用近似值yn(k)代替真值y(k)(xn)则得

    $$ y_{n+1} = y_n + hy'_n + frac{h^2}{2}y''_n + cdot cdot cdot + frac{h^k}{k!}y_n ^{(k)} $$

      做数值计算时一般取k=3阶就足够了,这时的截断误差为

    $$ R_3= frac{h^{4}}{(4)!} y^{4}(xi) = O(h^{4}) \ x_n < xi < x_{n+1} $$

    龙格-库塔法

      

  • 相关阅读:
    [Micropython]发光二极管制作炫彩跑马灯
    如何在MicroPython TPYBoard 添加自定义类库
    micropython TPYBoard v202 超声波测距
    简易排水简车的制作 TurnipBit 系列教程
    TPYBoard v102 驱动28BYJ-48步进电机
    使用mksdcard管理虚拟SD卡
    使用 DX 编译 Android应用
    常用ADB的用法
    在命令行创建、删除和浏览AVD、使用android模拟器
    Android-SDK下目录结构
  • 原文地址:https://www.cnblogs.com/darkchii/p/9175716.html
Copyright © 2011-2022 走看看