常微分方程的解析解(方法归纳)以及基于Python的微分方程数值解算例实现

常微分方程的解析解(方法归纳)以及基于Python的微分方程数值解算例实现

本文归纳常见常微分方程的解析解解法以及基于Python的微分方程数值解算例实现。

常微分方程的解析解

考虑常微分方程的解析解法,我们一般可以将其归纳为如下几类:

  • 可分离变量的微分方程(一阶)
  • 一阶齐次(非齐次)线性微分方程(一阶)
  • 二阶常系数微分方程(二阶)
  • 高阶常系数微分方程( n 阶)
  • 可分离变量的微分方程(一阶)

    这类微分方程可以变形成如下形式:
    两边同时积分即可解出函数,难点主要在于不定积分,是最简单的微分方程。

    某些方程看似不可分离变量,但是经过换元之后,其实还是可分离变量的,不要被这种方程迷惑。

    一阶齐次(非齐次)线性微分方程(一阶)

    的方程叫做一阶线性微分方程,若 Q(x) 为0,则方程齐次,否则称为非齐次。

    解法: (直接套公式)

    伯努利方程
    的方程称为伯努利方程,这种方程可以通过以下步骤化为一阶线性微分方程:
    y^{1-n}=u , 方程两边同时乘以 1−n ,得到
    这就将伯努利方程归结为可以套公式的一阶线性微分方程。

    二阶常系数微分方程(二阶)

    的方程称为二阶常系数微分方程,若 f(x)\equiv0 ,则方程称为齐次的,反之称为非齐次的。以下默认方程是非齐次的。

    求解此类方程分两步:

  • 求出齐次通解
  • 求出非齐次特解
  • 原方程的解 = 齐次通解 + 非齐次特解

  • 齐次通解的求法
  • 首先假设 f(x)\equiv0 .用特征方程法,写出对应的特征方程并且求解:
    解的情况分为以下三种:

    情况一:方程有两个不同的实数解

    假设两个实数解分别是 \lambda_1、\lambda_2 , 此时方程的通解是
    情况二:方程有一个二重解
    假设该解等于 \lambda ,此时方程的通解是
    情况三:方程有一对共轭复解
    假设这对解是 \alpha\pm i\beta , 此时方程的通解是

  • 非齐次特解的求法
  • 对于 f(x) 和特征根的情况,对特解的情况做如下归纳:

    f(x)=P_{m}(x) ,其中 P_{m}(x) 表示 x 的最高次数为 m 的多项式。

    若0不是方程特征解

    则方程有特解 y^{*}=Q_{m}(x)

    若0是方程的单特征解

    则方程有特解 y^{*}=xQ_{m}(x)

    若0是方程的二重特征解

    则方程有特解 y^{*}=x^{2}Q_{m}(x)

    其中 Q_{m}(x)=b_{0}+b_{1}x+…+b_{m}x^{m} , b_{i}(i = 0,1,…,m) 是需要带回原方程来确定的系数。

    \alpha 不是方程特征解

    则方程有特解 y^{*}=e^{\alpha x}Q_{m}(x)

    \alpha 是方程的单特征解

    则方程有特解 y^{*}=xe^{\alpha x}Q_{m}(x)

    \alpha 是方程的二重特征解

    则方程有特解 y^{*}=x^2e^{\alpha x}Q_{m}(x)

    \alpha\pm i\beta 不是特征解

    则方程有特解 y^{*}=e^{\alpha x}(A_{1}cos(\beta x)+A_{2}sin(\beta x))

    \alpha\pm i\beta 是特征解

    则方程有特解 y^{*}=xe^{\alpha x}(A_{1}cos(\beta x)+A_{2}sin(\beta x))

    其中 A_{1}、A_{2} 是需要带回原方程来确定的系数。

    高阶常系数微分方程(n阶)

    的方程叫做高阶常系数微分方程,若 f(x)\equiv0 ,则方程是齐次的,否则是非齐次的。下面默认方程是非齐次的。

    求解此类方程分两步:

  • 求出齐次通解
  • 求出非齐次特解
  • 原方程的解 = 齐次通解 + 非齐次特解

    齐次通解的求法 (参考二阶常系数微分方程解法) 非齐次特解的求法 (参考二阶常系数微分方程解法)

    考虑带有第三类边界条件的二阶常系数微分方程边值问题

  • 求出上述两点边值问题的解析解;
  • 通过有限差分方法算出其数值解;(取 h=\frac{1}{5} )并计算误差、绘图、输出 1.0,1.2,1.4,1.6,1.8,2.0 处的数值解.
  • 问题一:两点边值问题的解析解

    由于此方程是非齐次的,故 求解此类方程分两步:

  • 求出齐次通解
  • 求出非齐次特解
  • 原方程的解 = 齐次通解 + 非齐次特解

  • 齐次通解的求法
  • 首先假设 y''(x)+2y'(x)-3y(x)\equiv0 . 用特征方程法,写出对应的特征方程
    求解得到两个不同的实数特征根: \lambda_1=1,\lambda_2=-3 .

    此时方程的齐次通解是

  • 非齐次特解的求法
  • 由于 \lambda_1\ne0,\lambda_2\ne0 . 所以非齐次特解形式为
    将上式代入控制方程有
    求解得: a=b=-1 , 即非齐次特解为 y^{*}=-x-1 .

    原方程的解 = 齐次通解 + 非齐次特解

    于是,原方程的全解为
    因为该问题给出的是第三类边界条件,故需要求解的导函数
    将以上各式代入边界条件
    解此方程组可得: C_1=C_2=0 .

    综上所述,原两点边值问题的解为

    常微分方程的数值解

    一般二阶线性常微分方程边值问题的差分法

    对一般的二阶微分方程边值问题
    假定其解存在唯一,
    为求解的近似值, 类似于前面的做法,

  • 把区间 I=[a, b] N 等分, 即得到区间 I=[a, b] 的一个网格剖分:
    其中分点 x_{i}=a+i h(i=0,1, \cdots, N) , 步长 h=\frac{b-a}{N} .

  • 对式中的二阶导数仍用数值微分公式
    代替,而对一阶导数,为了保证略去的逼近误差为 O(h^2) ,则用 3 点数值微分公式;另外为了保证内插,在 2 个端点所用的 3 点数值微分公式与内网格点所用的公式不同,即
    略去误差,并用 y(x_i) 的近似值 y_i 代替 y(x_i) , p_i=p(x_i),q_i=q(x_i),f_i=f(x_i) 便得到差分方程组
    其中 q_{i}=q\left(x_{i}\right), p_{i}=p\left(x_{i}\right), f_{i}=f\left(x_{i}\right), i=1,2, \cdots, N-1, y_{i}y\left(x_{i}\right) 的近似值. 整理得
    特别地, 若 \alpha_{1}=1, \alpha_{2}=0, \beta_{1}=1, \beta_{2}=0 , 则原问题中的边界条件是第一类边值条件: y(a)=\alpha, y(b)=\beta ; 此时方程组为
    以上方程组是三对角方程组,用解三对角方程组的追赶法求解差分方程组,便得边值问题的差分解 .

  • 讨论差分方程组的解是否收敛到原微分方程的解,估计误差. 这里就不再详细介绍.

  • 考虑带有第三类边界条件的二阶常系数微分方程边值问题

  • 求出上述两点边值问题的解析解;
  • 通过有限差分方法算出其数值解;(取 h=\frac{1}{5} )并计算误差、绘图、输出 1.0,1.2,1.4,1.6,1.8,2.0 处的数值解.
  • 问题二:有限差分方法算出其数值解及误差
    对于 原问题 ,取步长 h=0.2 ,用 有限差分 求其 近似解 ,并将结果与 精确解y(x)=-x-1 进行比较.

    A1 = np.array([-2 * h - 3] + [b] * (NS - 1) + [3 + 2 * h]) # print("A1",A1) A2 = np.array([a] * (NS - 1) + [-4]) # print("A2", A2) A3 = np.array([4] + [c] * (NS - 1)) # print("A3", A3) A4 = np.array([-1] + [0] * (NS - 2)) # print("A4", A4) A5 = np.array([0] * (NS - 2) + [1]) # print("A5", A5) A = np.diag(A1) + np.diag(A2, -1) + np.diag(A3, 1) + np.diag(A4, 2) + np.diag(A5, -2) # print("A", A) # 矩阵b br = 2 * h ** 2 * f(x) + np.array( [2 * h - 2 * h ** 2 * f(x[0])] + [0] * (NS - 1) + [-8 * h + 2 * h ** 2 * f(x[NS])]) uh = np.linalg.solve(A, br) return uh L = 1.0 R = 2.0 NS = 5 x, h = initial(L, R, NS) a = 2 - 2 * h b = -4 - 6 * h ** 2 c = 2 + 2 * h uh = solve(NS) print("h=%f时数值解\n" % h, uh)
    h=0.200000时数值解
     [-1.48151709 -1.56543883 -1.6245972  -1.6531625  -1.64418896 -1.58929215]
    

    是不是解出数值解就完事了呢?当然不是。我们可以将问题的差分格式解与问题的真解进行比较,以得到解的可靠性。通过数学计算我们得到问题的真解为 u ( x ) = -x-1,现用范数来衡量误差的大小:

    # 真解函数
    def ture_u(x):
        ture_u = -x - 1
        return ture_u
    t_u = ture_u(x)
    print("h=%f时真解\n" % h, t_u)
    # 误差范数
    def err(ture_u, uh):
        ee = max(abs(ture_u - uh))
        e0 = np.sqrt(sum((ture_u - uh) ** 2) * h)
        return ee, e0
    ee, e0 = err(t_u, uh)
    print('L_∞(最大模)范数下的误差', ee)
    print('L_2(平方和)范数下的误差', e0)
    
    h=0.200000时真解
     [-2.  -2.2 -2.4 -2.6 -2.8 -3. ]
    L_∞(最大模)范数下的误差 1.4107078471946164
    L_2(平方和)范数下的误差 1.0483548020308784
    

    接下来绘图比较 h=0.2 时数值解与真解的差距:

    # 绘图比较
    plt.figure()
    plt.plot(x, uh, label='Numerical solution')
    plt.plot(x, t_u, label='Exact solution')
    plt.title("solution")
    plt.legend()
    plt.show()
    哎呀呀! 根据输出显示的误差,发现此时数值解的求解效果令人难以接受L_∞(最大模)范数下的误差高达1.41,从图像也能看出数值解与解析解不仅相差甚远,甚至连曲线走势都不太一致.(数值解为一条曲线,解析解为直线)

    此处首先认为解析解或者查分格式计算错了,休息了一晚上起来重新推导了一遍后并未发现明显错误. 后来在玩GTA的时候忽然想起导师之前提到过步长会影响数值解稳定性···,于是重新编写程序,修改步长后发现确实如此.

    将区间划分为 N=1280 份, 即 h=0.000781 时.

    h=0.000781时数值解
     [-1.99798816 -1.99876784 -1.99954751 ... -2.99297729 -2.99375427
     -2.99453125]
    h=0.000781时真解
     [-2.         -2.00078125 -2.0015625  ... -2.9984375  -2.99921875
     -3.        ]
    L_∞(最大模)范数下的误差 0.005468750663166322
    L_2(平方和)范数下的误差 0.0035976563635910903
    

    绘图比较 h=0.000781 时数值解与真解的差距:
    可以看到此时已经具有较高精度, 若还未达到需求精度, 可通过缩短步长h 继续提高精度.

    最后,我们还可以从数学的角度分析所采用的差分格式的一些性质。因为差分格式的误差为O(h^2), 所以理论上来说网格每加密一倍,与真解的误差大致会缩小到原来的\frac{1}{4}. 下讨论网格加密时的变化:

    # 误差与网格关系讨论
    N = [5, 10, 20, 40, 80, 160, 320, 640, 1280, 2560]
    print("网格密度加倍时误差变化情况")
    for i in range(len(N)):
        NS = N[i]
        x, h = initial(L, R, NS)
        a = 2 - 2 * h
        b = -4 - 6 * h ** 2
        c = 2 + 2 * h
        uh = solve(NS)
        t_u = ture_u(x)
        ee, e0 = err(t_u, uh)
        print('步长h=%f' % h, end='\t')
        print('最大模误差=%f' % ee)
    
    网格密度加倍时误差变化情况
    步长h=0.200000    最大模误差=1.410708
    步长h=0.100000    最大模误差=0.701417
    步长h=0.050000    最大模误差=0.350182
    步长h=0.025000    最大模误差=0.175023
    步长h=0.012500    最大模误差=0.087503
    步长h=0.006250    最大模误差=0.043750
    步长h=0.003125    最大模误差=0.021875
    步长h=0.001563    最大模误差=0.010938
    步长h=0.000781    最大模误差=0.005469
    步长h=0.000391    最大模误差=0.002734
    
    # 开发者:    Leo 刘
    # 开发环境: macOs Big Sur
    # 开发时间: 2021/10/5 6:51 下午
    # 邮箱  : 517093978@qq.com
    # @Software: PyCharm
    # ----------------------------------------------------------------------------------------------------------
    import numpy as np
    import matplotlib.pyplot as plt
    # 准备工作
    def initial(L, R, NS):
        x = np.linspace(L, R, NS + 1)
        h = (R - L) / NS
        return x, h
    # 右端函数f
    def f(x):
        return 3 * x + 1
    # 解方程
    def solve(NS):
        # 矩阵A
        A1 = np.array([-2 * h - 3] + [b] * (NS - 1) + [3 + 2 * h])
        # print("A1",A1)
        A2 = np.array([a] * (NS - 1) + [-4])
        # print("A2", A2)
        A3 = np.array([4] + [c] * (NS - 1))
        # print("A3", A3)
        A4 = np.array([-1] + [0] * (NS - 2))
        # print("A4", A4)
        A5 = np.array([0] * (NS - 2) + [1])
        # print("A5", A5)
        A = np.diag(A1) + np.diag(A2, -1) + np.diag(A3, 1) + np.diag(A4, 2) + np.diag(A5, -2)
        # print("A", A)
        # 矩阵b
        br = 2 * h ** 2 * f(x) + np.array(
            [2 * h - 2 * h ** 2 * f(x[0])] + [0] * (NS - 1) + [-8 * h + 2 * h ** 2 * f(x[NS])])
        uh = np.linalg.solve(A, br)
        return uh
    L = 1.0
    R = 2.0
    NS = 1280
    x, h = initial(L, R, NS)
    a = 2 - 2 * h
    b = -4 - 6 * h ** 2
    c = 2 + 2 * h
    uh = solve(NS)
    print("h=%f时数值解\n" % h, uh)
    # 真解函数
    def ture_u(x):
        ture_u = -x - 1
        return ture_u
    t_u = ture_u(x)
    print("h=%f时真解\n" % h, t_u)
    # 误差范数
    def err(ture_u, uh):
        ee = max(abs(ture_u - uh))
        e0 = np.sqrt(sum((ture_u - uh) ** 2) * h)
        return ee, e0
    ee, e0 = err(t_u, uh)
    print('L_∞(最大模)范数下的误差', ee)
    print('L_2(平方和)范数下的误差', e0)
    # 绘图比较
    plt.figure()
    plt.plot(x, uh, label='Numerical solution')
    plt.plot(x, t_u, label='Exact solution')
    plt.title("solution")
    plt.legend()
    plt.show()
    # 误差与网格关系讨论
    N = [5, 10, 20, 40, 80, 160, 320, 640, 1280, 2560]
    print("网格密度加倍时误差变化情况")
    for i in range(len(N)):
        NS = N[i]
        x, h = initial(L, R, NS)
        a = 2 - 2 * h
        b = -4 - 6 * h ** 2
        c = 2 + 2 * h
        uh = solve(NS)
        t_u = ture_u(x)
        ee, e0 = err(t_u, uh)
        print('步长h=%f' % h, end='\t')
        print('最大模误差=%f' % ee)