常微分方程的解析解(方法归纳)以及基于Python的微分方程数值解算例实现
本文归纳常见常微分方程的解析解解法以及基于Python的微分方程数值解算例实现。
常微分方程的解析解
考虑常微分方程的解析解法,我们一般可以将其归纳为如下几类:
可分离变量的微分方程(一阶)
这类微分方程可以变形成如下形式:
两边同时积分即可解出函数,难点主要在于不定积分,是最简单的微分方程。
某些方程看似不可分离变量,但是经过换元之后,其实还是可分离变量的,不要被这种方程迷惑。
一阶齐次(非齐次)线性微分方程(一阶)
的方程叫做一阶线性微分方程,若 为0,则方程齐次,否则称为非齐次。
解法:
(直接套公式)
伯努利方程
的方程称为伯努利方程,这种方程可以通过以下步骤化为一阶线性微分方程:
令
, 方程两边同时乘以
,得到
这就将伯努利方程归结为可以套公式的一阶线性微分方程。
二阶常系数微分方程(二阶)
的方程称为二阶常系数微分方程,若 ,则方程称为齐次的,反之称为非齐次的。以下默认方程是非齐次的。
求解此类方程分两步:
原方程的解
=
齐次通解
+
非齐次特解
首先假设
.用特征方程法,写出对应的特征方程并且求解:
解的情况分为以下三种:
情况一:方程有两个不同的实数解
假设两个实数解分别是
, 此时方程的通解是
情况二:方程有一个二重解
假设该解等于
,此时方程的通解是
情况三:方程有一对共轭复解
假设这对解是
, 此时方程的通解是
对于 和特征根的情况,对特解的情况做如下归纳:
,其中 表示 的最高次数为 的多项式。
若0不是方程特征解
则方程有特解
若0是方程的单特征解
则方程有特解
若0是方程的二重特征解
则方程有特解
其中 , 是需要带回原方程来确定的系数。
若 不是方程特征解
则方程有特解
若 是方程的单特征解
则方程有特解
若 是方程的二重特征解
则方程有特解
若 不是特征解
则方程有特解
若 是特征解
则方程有特解
其中 是需要带回原方程来确定的系数。
高阶常系数微分方程(n阶)
的方程叫做高阶常系数微分方程,若 ,则方程是齐次的,否则是非齐次的。下面默认方程是非齐次的。
求解此类方程分两步:
原方程的解
=
齐次通解
+
非齐次特解
考虑带有第三类边界条件的二阶常系数微分方程边值问题
问题一:两点边值问题的解析解
由于此方程是非齐次的,故 求解此类方程分两步:
原方程的解
=
齐次通解
+
非齐次特解
首先假设
. 用特征方程法,写出对应的特征方程
求解得到两个不同的实数特征根:
.
此时方程的齐次通解是
由于
. 所以非齐次特解形式为
将上式代入控制方程有
求解得:
, 即非齐次特解为
.
原方程的解
=
齐次通解
+
非齐次特解
于是,原方程的全解为
因为该问题给出的是第三类边界条件,故需要求解的导函数
将以上各式代入边界条件
解此方程组可得:
.
综上所述,原两点边值问题的解为
常微分方程的数值解
一般二阶线性常微分方程边值问题的差分法
对一般的二阶微分方程边值问题
假定其解存在唯一,
为求解的近似值, 类似于前面的做法,
把区间
等分, 即得到区间
的一个网格剖分:
其中分点
, 步长
.
对式中的二阶导数仍用数值微分公式
代替,而对一阶导数,为了保证略去的逼近误差为
,则用 3 点数值微分公式;另外为了保证内插,在 2 个端点所用的 3 点数值微分公式与内网格点所用的公式不同,即
略去误差,并用
的近似值
代替
,
便得到差分方程组
其中
是
的近似值. 整理得
特别地, 若
, 则原问题中的边界条件是第一类边值条件:
; 此时方程组为
以上方程组是三对角方程组,用解三对角方程组的追赶法求解差分方程组,便得边值问题的差分解 .
讨论差分方程组的解是否收敛到原微分方程的解,估计误差. 这里就不再详细介绍.
考虑带有第三类边界条件的二阶常系数微分方程边值问题
问题二:有限差分方法算出其数值解及误差
对于
原问题
,取步长
h=0.2
,用
有限差分
求其
近似解
,并将结果与
精确解y(x)=-x-1
进行比较.
h=0.200000时数值解
[-1.48151709 -1.56543883 -1.6245972 -1.6531625 -1.64418896 -1.58929215]
是不是解出数值解就完事了呢?当然不是。我们可以将问题的差分格式解与问题的真解进行比较,以得到解的可靠性。通过数学计算我们得到问题的真解为 ,现用范数来衡量误差的大小:
# 真解函数
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
接下来绘图比较 时数值解与真解的差距:
# 绘图比较
plt.figure()
plt.plot(x, uh, label='Numerical solution')
plt.plot(x, t_u, label='Exact solution')
plt.title("solution")
plt.legend()
plt.show()
哎呀呀! 根据输出显示的误差,发现此时数值解的求解效果令人难以接受(最大模)范数下的误差高达1.41,从图像也能看出数值解与解析解不仅相差甚远,甚至连曲线走势都不太一致.(数值解为一条曲线,解析解为直线)
此处首先认为解析解或者查分格式计算错了,休息了一晚上起来重新推导了一遍后并未发现明显错误. 后来在玩GTA的时候忽然想起导师之前提到过步长会影响数值解稳定性···,于是重新编写程序,修改步长后发现确实如此.
将区间划分为 份, 即 时.
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
绘图比较 时数值解与真解的差距:
可以看到此时已经具有较高精度, 若还未达到需求精度, 可通过缩短步长 继续提高精度.
最后,我们还可以从数学的角度分析所采用的差分格式的一些性质。因为差分格式的误差为, 所以理论上来说网格每加密一倍,与真解的误差大致会缩小到原来的. 下讨论网格加密时的变化:
# 误差与网格关系讨论
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)