前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >数值计算方法 Chapter8. 常微分方程的数值解

数值计算方法 Chapter8. 常微分方程的数值解

作者头像
codename_cys
发布2022-08-23 11:25:21
2.7K0
发布2022-08-23 11:25:21
举报
文章被收录于专栏:我的充电站

0. 问题描述

这一章节考察的问题如标题所述,即常微分方程的数值求解:

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

1. Euler公式

1. 向前Euler公式

Euler公式算是一个求解常微分方程数值解问题的一个比较直接的思路:

\frac{dy}{dx} = \frac{\delta y}{\delta x} = f(x, y)

从而有:

y+\delta y = f(x, y) \cdot \delta x

由此,我们不断地求解就能够近似的得到各个x取值下的y值。

y_{n+1} = y_n + h\cdot f(x_n, y_n)

给出python伪代码实现如下:

代码语言:javascript
复制
def fwd_euler_fn(f, x0, y0, step=1e-3):
    def fn(x):
        h = step if x >= x0 else -step
        n = math.ceil((x - x0) / h)
        x, y = x0, y0
        for _ in range(n):
            y += h * f(x, y)
            x += h
        return y
    return fn

2. 向后Euler公式

向后Euler公式和向前Euler公式并无本质区别,不过微调公式为:

y_{n+1} = y_n + h\cdot f(x_{n+1}, y_{n+1})

但是显然的在计算y_{n+1} 时事实上无法得知f(x_{n+1}, y_{n+1}) 的值,因此这里需要叠加上迭代的思路:

y_{n+1}^{(k+1)} = y_n^{(k+1)} + h\cdot f(x_{n+1}, y_{n+1}^{(k)})

上述迭代公式称为Picard迭代。

可以证明,当h足够小时,Picard迭代收敛。

给出python伪代码如下:

代码语言:javascript
复制
def bwd_euler_fn(f, x0, y0, step=1e-3):
    def fn(x, epsilon = 1e-6):
        h = step if x >= x0 else -step
        n = math.ceil((x - x0) / h)
        xlist = [x0 + i*h for i in range(n)]
        ylist = [y0 for i in range(n)]
        for i in range(n-1):
            ylist[i+1] = ylist[i] + h * f(xlist[i], ylist[i])
        while True:
            zlist = deepcopy(ylist)
            for i in range(n-1):
                zlist[i+1] = zlist[i] + h * f(xlist[i+1], ylist[i+1])
            delta = abs(zlist[-1] - ylist[-1])
            ylist = zlist
            if delta < epsilon:
                break
        return zlist[-1]
    return fn

3. 梯形公式

梯形公式本质上依然还是基于微分差商,不过不同于之前直接使用微分的形式,这里更加严格的使用了积分的表达,即:

y_{n+1} = y_n + \int_{x_{n}}^{x_{n+1}}f(x, y)dx

然后,这里使用梯形公式来近似掉其中的积分过程,有:

y_{n+1} = y_n + \frac{h}{2}(f(x_n, y_n) + f(x_{n+1}, y_{n+1}))

这里同样会涉及到等号右侧的y_{n+1} 的求解问题,不过,不同于向后Euler公式中的纯迭代思路,这里只使用一次迭代来近似,即:

y_{n+1} = y_n + \frac{h}{2}(f(x_n, y_n) + f(x_{n+1}, y_{n} + h\cdot f(x_n, y_n)))

同样,我们给出python伪代码实现如下:

代码语言:javascript
复制
def tapezoid_fn(f, x0, y0, step=1e-3):
    def fn(x):
        h = step if x >= x0 else -step
        n = math.ceil((x - x0) / h)
        x, y = x0, y0
        for _ in range(n):
            y += h/2 * (f(x, y) + f(x+h, y+h*f(x, y)))
            x += h
        return y
    return fn

2. Runge-Kutta方法

1. 二阶Runge-Kutta方法

Runge-Kutta方法较之之前的Euler公式是一个相对而言精度更高的方法。

Euler公式来源于微分的定义,而Runge-Kutta方法则考察了Taylor展开,有:

\begin{aligned} y_{n+1} &= y_n + \sum_{i} \frac{h^i}{i!} y^{(i)}(x_n) \\ &= y_n + h \cdot y'(x_n) + \frac{h^2}{2!} \cdot y''(x_n) + ... \end{aligned}

我们保留二阶项即有:

\begin{aligned} y_{n+1} &= y_n + h \cdot y'(x_n) + \frac{h^2}{2} y''(x_n) \\ &= y_n + h \cdot f(x_n, y_n) + \frac{h^2}{2}(\frac{\partial f}{\partial x}(x_n, y_n) + \frac{\partial f}{\partial y}(x_n, y_n) \cdot f(x_n, y_n)) \end{aligned}

但是,这里的一个问题在于说两个偏导函数事实上也不一定好解,因此上述表达式没有办法直接调用。

而Runge-Kutta方法则是使用一个近似的位移公式来对其进行估计,使得二者在二阶导范围内没有误差。

具体而言:

\begin{aligned} \Delta &= y_{n+} - y_n \\ &= h \cdot y'(x_n) + \frac{h^2}{2} y''(x_n) \\ &= h \cdot f(x_n, y_n) + \frac{h^2}{2}(\frac{\partial f}{\partial x}(x_n, y_n) + \frac{\partial f}{\partial y}(x_n, y_n) \cdot f(x_n, y_n)) \\ &= c_1\cdot h\cdot f(x_n, y_n) + c_2 \cdot h \cdot f(x_n + ah, y_n + bhf(x_n, y_n)) \\ &= c_1 \cdot h \cdot f(x_n, y_n) + c_2 \cdot h \cdot [f(x_n, y_n) + \frac{\partial f}{\partial x}(x_n, y_n) \cdot ah + \frac{\partial f}{\partial y}(x_n, y_n) \cdot bhf(x_n, y_n)] \end{aligned}

或者说,给出参数

c_1, c_2, a, b

使得满足如下条件:

\left\{ \begin{aligned} y_{n+1} &= y_n + h(c_1 \cdot k_1 + c_2 \cdot k_2) \\ k_1 &= f(x_n, y_n) \\ k_2 &= f(x_n + ah, y_n + bhk_1) \end{aligned} \right.

使得y_{n+1} 在二阶导数范围内没有误差。

亦即:

\left\{ \begin{aligned} (c_1 + c_2)h \cdot f(x_n, y_n) &= h \\ c_2 a h^2 \cdot \frac{\partial f}{\partial x}(x_n, y_n) &= \frac{h^2}{2} \frac{\partial f}{\partial x}(x_n, y_n) \\ c_2 b h^2 \cdot \frac{\partial f}{\partial y}(x_n, y_n) &= \frac{h^2}{2} \frac{\partial f}{\partial y}(x_n, y_n) \end{aligned} \right.

简化上式即可得到:

\left\{ \begin{aligned} c_1 + c_2 &= 1 \\ 2c_2 a &= 1 \\ 2c_2 b &= 1 \end{aligned} \right.

只要满足上述方程组,对应的参数a,b,c_1,c_2 均可以令上两式在二阶导范围内没有误差。

给出两组常见的二阶Runge-Kutta公式如下:

c_1=\frac{1}{2}, c_2=\frac{1}{2}, a=1, b=1
\left\{ \begin{aligned} k_1 &= f(x_n, y_n) \\ k_2 &= f(x_n + h, y_n + hk_1) \\ y_{n+1} &= y_n + \frac{h}{2}(k_1 + k_2) \end{aligned} \right.
c_1=0, c_2=1, a=\frac{1}{2}, b=\frac{1}{2}
\left\{ \begin{aligned} k_1 &= f(x_n, y_n) \\ k_2 &= f(x_n + \frac{h}{2}, y_n + \frac{h}{2}k_1) \\ y_{n+1} &= y_n + hk_2 \end{aligned} \right.

2. 高阶Runge-Kutta方法

同样的,我们仿照上述的思路,给出一般情况下的高阶Runge-Kutta方法的表达式如下:

\left\{ \begin{aligned} y_{n+1} &= y_n + h(c_1 \cdot k_1 + c_2 \cdot k_2 + ... + c_m \cdot k_m) \\ k_1 &= f(x_n, y_n) \\ k_2 &= f(x_n + a_{1} h + y_n + b_{1,1} h \cdot k_1) \\ &... \\ k_m &= f(x_n + a_{m-1} h + y_n + \sum_{i=1}^{m-1}b_{m-1,i} h \cdot k_{i}) \end{aligned} \right.

参数a,b,c 可以使得y_n 具有O(h^{m+1}) 阶精度。

亦即,Taylor展开直到h^{m} 都有两侧的展开系数相同。

1. 三阶Runge-Kutta方法

我们给出三阶Runge-Kutta方法的两组典型的系数如下:

  1. 系数组合(一)
\left\{ \begin{aligned} y_{n+1} &= y_n + \frac{h}{6}(k_1 + 4k_2 + k_3) \\ k_1 &= f(x_n, y_n) \\ k_2 &= f(x_n + \frac{1}{2}h, y_n + \frac{1}{2}hk_1) \\ k_3 &= f(x_n + h, y_n - hk_1 + 2hk_2) \end{aligned} \right.
  1. 系数组合(二)
\left\{ \begin{aligned} y_{n+1} &= y_n + \frac{h}{4}(k_1 + 3k_3) \\ k_1 &= f(x_n, y_n) \\ k_2 &= f(x_n + \frac{1}{3}h, y_n + \frac{1}{3}hk_1) \\ k_3 &= f(x_n + \frac{2}{3}h, y_n + \frac{2}{3}hk_2) \end{aligned} \right.
  1. 系数组合(三)
\left\{ \begin{aligned} y_{n+1} &= y_n + \frac{h}{9}(2k_1 + 3k_2 + 4k_3) \\ k_1 &= f(x_n, y_n) \\ k_2 &= f(x_n + \frac{1}{2}h, y_n + \frac{1}{2}hk_1) \\ k_3 &= f(x_n + \frac{3}{4}h, y_n + \frac{3}{4}hk_2) \end{aligned} \right.
2. 四阶Runge-Kutta方法

同样的,我们可以给出两组典型的四阶Runge-Kutta公式如下:

  1. 系数组合(一)
\left\{ \begin{aligned} y_{n+1} &= y_n + \frac{h}{6}(k_1 + 2k_2 + 2k_3 + k_4) \\ k_1 &= f(x_n, y_n) \\ k_2 &= f(x_n + \frac{1}{2}h, y_n + \frac{1}{2}hk_1) \\ k_3 &= f(x_n + \frac{1}{2}h, y_n + \frac{1}{2}hk_2) \\ k_4 &= f(x_n + h, y_n + hk_3) \end{aligned} \right.
  1. 系数组合(二)
\left\{ \begin{aligned} y_{n+1} &= y_n + \frac{h}{8}(k_1 + 3k_2 + 3k_3 + k_4) \\ k_1 &= f(x_n, y_n) \\ k_2 &= f(x_n + \frac{1}{3}h, y_n + \frac{1}{3}hk_1) \\ k_3 &= f(x_n + \frac{2}{3}h, y_n + \frac{1}{3}hk_1 + hk_2) \\ k_4 &= f(x_n + h, y_n + hk_1 - hk_2 + hk_3) \end{aligned} \right.

3. python伪代码实现

下面,我们来给出Runge-Kutta方法的python伪代码实现如下:

代码语言:javascript
复制
def runge_kutta_fn(f, x0, y0, a, b, c, step=1e-3):
    def fn(x):
        h = step if x >= x0 else -step
        n = math.ceil((x - x0) / h)
        m = len(c)
        assert(len(a) == m-1 and all(len(b[i]) == i+1 for i in range(m-1)))
        k = [0 for _ in range(m)]
        x, y = x0, y0
        for _ in range(n):
            k[0] = f(x, y)
            for i in range(m-1):
                xi = x + a[i] * h
                yi = y
                for j in range(i+1):
                    yi += h * b[i][j] * k[j]
                k[i+1] = f(xi, yi)
            x += h
            y += sum(ci * ki * h for ci, ki in zip(c, k))
        return y
    return fn

3. 线性多步法

1. 基本思路

线性多步法的思路来源依然还是积分公式:

y(x) = y(x_0) + \int_{x_0}^{x}y'(t)dt

之前,Euler公式和Runge-Kutta方法都是直接对积分的值进行估计。

其中,Euler公式是直接令

\int_{x}^{x+h}y'(t)dt = y'(x)h

,而Runge-Kutta方法则是通过引入一系列的偏移量使得y(x) 在n阶Taylor展开当中没有误差。

而线性多步法的近似思路则是用采用之前的插值公式的思路,来对y'(x) 来进行拟合,然后用这个拟合函数来计算后面这个积分值。

给出书中的描述如下:

若用积分节点x_n, x_{n-1}, ..., x_{n-q} 构造插值多项式近似y'(x) ,在区间[x_{n-p}, x_{n+1}] 上计算数值积分

\int_{x_{n-p}}^{x_{n+1}}y'(x)dx

,则称构造计算y_{n+1} 的方法为线性多步法。

特别的:

  • 如果使用x_{n}, x_{n-1}, ..., x_{n-q}构造插值多项式,则称拟合函数为显式Adams公式;
  • 如果使用x_{n+1}, x_{n}, ..., x_{n+1-q} 构造插值多项式,则称拟合函数为隐式Admas公式;

2. Adams公式

这里,我们舍去推导,直接给出一些常见的Adams公式如下:

  1. 二阶显式Adams公式
y_{n+1} = y_{n} + \frac{h}{2}(3f(x_{n}, y_{n}) - f(x_{n-1}, y_{n-1}))
  1. 二阶隐式Adams公式
y_{n+1} = y_{n} + \frac{h}{2}(f(x_{n}, y_{n}) + f(x_{n+1}, y_{n+1}))
  1. 三阶显示Adams公式
y_{n+1} = y_{n} + \frac{h}{12}(23f(x_{n}, y_{n}) - 16f(x_{n-1}, y_{n-1}) + 5f(x_{n-2}, y_{n-2}))
  1. 三阶隐示Adams公式
y_{n+1} = y_{n} + \frac{h}{12}(5f(x_{n+1}, y_{n+1} + 8f(x_{n}, y_{n}) - f(x_{n-1}, y_{n-1}))
  1. 四阶显式Adams公式
y_{n+1} = y_{n} + \frac{h}{24}(55f(x_{n}, y_{n}) - 59f(x_{n-1}, y_{n-1}) + 37f(x_{n-2}, y_{n-2}) - 9f(x_{n-3}, y_{n-3}))
  1. 四阶隐式Adams公式
y_{n+1} = y_{n} + \frac{h}{24}(9f(x_{n+1}, y_{n+1} + 19f(x_{n}, y_{n}) - 5f(x_{n-1}, y_{n-1}) + f(x_{n-2}, y_{n-2}))

4. 常微分方程组的数值解法

1. 一阶常微分方程组的数值解法

我们给出一阶常微分方程的初值问题表达如下:

\left\{ \begin{aligned} \frac{dy_{1}}{dx} &= f_1(x, y_{1}, y_{2}, ..., y_{m}) \\ \frac{dy_{2}}{dx} &= f_2(x, y_{1}, y_{2}, ..., y_{m}) \\ &... \\ \frac{dy_{m}}{dx} &= f_m(x, y_{1}, y_{2}, ..., y_{m}) \\ y_1(x_0) &= \eta_1 \\ y_2(x_0) &= \eta_2 \\ &... \\ y_m(x_0) &= \eta_m \\ \end{aligned} \right.

这类方程组的解法问题其实完全可以原模原样照搬常微分方程的数值解法,倒是也没啥必要详细展开就是了。

我们直接以二元方程组为例,给出一些常见的解:

\left\{ \begin{aligned} \frac{dy}{dx} &= f(x, y, z) \\ \frac{dz}{dx} &= g(x, y, z) \\ y(x_0) &= y_0 \\ z(x_0) &= z_0 \end{aligned} \right.
  1. 前向Euler公式
\left\{ \begin{aligned} y_{n+1} &= y_n + hf(x_n, y_n, z_n) \\ z_{n+1} &= z_n + hg(x_n, y_n, z_n) \end{aligned} \right.
  1. 梯形公式
\left\{ \begin{aligned} \hat{y_{n+1}} &= y_n + hf(x_n, y_n, z_n) \\ \hat{z_{n+1}} &= z_n + hg(x_n, y_n, z_n) \\ y_{n+1} &= y_n + \frac{h}{2}(f(x_n, y_n, z_n) + f(x_{n+1}, \hat{y_{n+1}}, \hat{z_{n+1}})) \\ z_{n+1} &= z_n + \frac{h}{2}(g(x_n, y_n, z_n) + g(x_{n+1}, \hat{y_{n+1}}, \hat{z_{n+1}})) \end{aligned} \right.
  1. 四阶Runge-Kutta方法
\left\{ \begin{aligned} y_{n+1} &= y_n + \frac{h}{6}(k_{11} + 2k_{12} + 2k_{13} + k_{14}) \\ z_{n+1} &= z_n + \frac{h}{6}(k_{21} + 2k_{22} + 2k_{23} + k_{24}) \\ k_{11} &= f(x_n, y_n, z_n) \\ k_{21} &= g(x_n, y_n, z_n) \\ k_{12} &= f(x_n + \frac{h}{2}, y_n + \frac{h}{2}k_{11}, z_n + \frac{h}{2}k_{21}) \\ k_{22} &= g(x_n + \frac{h}{2}, y_n + \frac{h}{2}k_{11}, z_n + \frac{h}{2}k_{21}) \\ k_{13} &= f(x_n + \frac{h}{2}, y_n + \frac{h}{2}k_{12}, z_n + \frac{h}{2}k_{22}) \\ k_{23} &= g(x_n + \frac{h}{2}, y_n + \frac{h}{2}k_{12}, z_n + \frac{h}{2}k_{22}) \\ k_{14} &= f(x_n + h, y_n + hk_{13}, z_n + hk_{23}) \\ k_{24} &= g(x_n + h, y_n + hk_{13}, z_n + hk_{23}) \end{aligned} \right.

2. 高阶微分方程数值方法

这里,我们再来考察一下一元高阶微分方程的数值解法。

\left\{ \begin{aligned} \frac{d^{m}y(x)}{dx^{m}} &= f(x, y, y', ..., y^{(m-1)}) \\ y(x_0) &= \eta_0 \\ y'(x_0) &= \eta_1 \\ & ... \\ y^{(m-1)}(x_0) &= \eta_{m-1} \end{aligned} \right.

这一类问题事实上可以作为上述一阶常微分方程组的一个应用实例,我们只需要做如下变换就可以将问题完全转换为一个一阶常微分方程组,然后就可以运用之前的一阶常微分方程组的数值解法进行求解了。

\left\{ \begin{aligned} \frac{dy_0}{dx} &= y_1(x) \\ \frac{dy_1}{dx} &= y_2(x) \\ &... \\ \frac{dy_{m-2}}{dx} &= y_{m-1}(x) \\ \frac{dy_{m-1}}{dx} &= f(x, y_0, y_1, ..., y_{m-1}) \\ y_{0}(x_0) &= \eta_0 \\ y_{1}(x_0) &= \eta_1 \\ & ... \\ y_{m-1}(x_0) &= \eta_{m-1} \end{aligned} \right.
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-07-03,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 0. 问题描述
  • 1. Euler公式
    • 1. 向前Euler公式
      • 2. 向后Euler公式
        • 3. 梯形公式
        • 2. Runge-Kutta方法
          • 1. 二阶Runge-Kutta方法
            • 2. 高阶Runge-Kutta方法
              • 1. 三阶Runge-Kutta方法
              • 2. 四阶Runge-Kutta方法
            • 3. python伪代码实现
            • 3. 线性多步法
              • 1. 基本思路
                • 2. Adams公式
                • 4. 常微分方程组的数值解法
                  • 1. 一阶常微分方程组的数值解法
                    • 2. 高阶微分方程数值方法
                    领券
                    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档