本项目旨在为 JuliaDiffEq 使用 有限差分法(FDM) 方法构建一个 PDE 求解器。我们选择 FDM 方法而不是 FEM 和 FVM,因为 FEM 和 FVM 已经有很多现成的工具箱,而 FDM 则不然。此外,在很多情况下,问题的几何形状非常简单,可以使用 FDM 方法进行求解,这些方法由于能够避免矩阵乘法的瓶颈步骤而快得多,而是使用线性变换来模拟矩阵乘法的效果。由于矩阵乘法本质上将一个向量元素转换为相邻元素的加权和,因此这可以很容易地通过一个特殊函数来实现,该函数以最优的 时间对向量进行操作。
结果是名为 DiffEqOperators.jl 的新软件包,它创建了偏微分算子的高效离散化,从而将 PDE 转换为 ODE,可以通过现有的 ODE 求解器进行高效地求解。DerivativeOperator
基于中心差分方案,用于近似某个点的导数,而 UpwindOperators
基于单边差分方案,其中解通常是一个在特定方向上传播的波。该软件包还支持各种边界条件,如 Dirichlet、Neumann、周期 和 Robin 边界条件。
有限差分法的基本思想是生成与微分算子相对应的有限差分权重,以允许一定程度的近似。时间和空间变量被划分成一个网格,其中 和 对于 和 和 对于 .
离散未知数是标量 对于上面给出的i和j的值,希望 是 的近似值。该方程的右侧通过设置 进行离散化。我们也使用符号
来表示时间 在空间网格上的近似值向量。
对于二阶空间导数,通过结合前向差分商和后向差分商,我们得到了中心逼近
因此,对应二阶偏导数的权重为 。有限差分法通过用离散未知数代替网格点上解的精确值来模仿这些逼近。
在这个特定情况下,我们最终得到了以下方案
这是将模板重写为递归式。用向量形式写出来,我们得到:-
当我们要将此运算符应用于向量 时,权重向量就变成了一个称为变换矩阵的矩阵
但矩阵乘法成本很高,因此最好使用二阶偏导数的线性算子,而不是变换矩阵。 它看起来像这样:-
function double_partial(x,dx) for i in 2:length(dx)-1 dx[i] = -1*x[i-1] + 2*x[i] + -1*x[i+1] end dx[1] = 2*x[1] + -1*x[2] dx[end] = -1*x[end-1] + 2*x[end] end
此函数对向量进行操作,具有最佳的 时间复杂度,与矩阵乘法的低效 时间复杂度相比,同时避免了稀疏矩阵的开销。
为了将偏微分方程转换为常微分方程,我们在空间上进行离散化,但在时间上不进行离散化。然后,这个常微分方程可以通过现有的求解器有效地求解。我们的半线性热方程,也称为反应扩散方程,转换为以下常微分方程: 其中 是线性算子,而不是变换矩阵。因此,我们需要使 **DifferentialEquations.jl** 的常微分方程求解器也兼容线性算子。
由于手动计算泰勒系数很繁琐,Fornberg 给出了一种 算法 ,可以高效地计算任意导数和逼近阶的泰勒系数。这些模板可以通过对相邻点的适当加权求和来有效地计算任意点的导数。例如, 是用于计算某一点的二阶导数的二阶模板。
在 **DiffEqOperators.jl** 中,我们可以轻松地从算子中提取任意导数和逼近阶的模板。例如。
# Define A as a DerivativeOperator of 4th order and of 2nd order of accuracy
julia> A = DerivativeOperator{Float64}(4,2,1.0,10,:Dirichlet0,:Dirichlet0)
julia> A.stencil_coefs
7-element SVector{7,Float64}:
-0.166667
2.0
-6.5
9.33333
-6.5
2.0
-0.166667
如果我们想将算子作为矩阵乘法(稀疏或稠密)应用,我们可以通过提取线性算子的变换矩阵来轻松实现,它看起来像这样:-
julia> full(A)
10×10 Array{Float64,2}:
9.33333 -6.5 2.0 … 0.0 0.0 0.0
-6.5 9.33333 -6.5 0.0 0.0 0.0
2.0 -6.5 9.33333 0.0 0.0 0.0
-0.166667 2.0 -6.5 0.0 0.0 0.0
0.0 -0.166667 2.0 -0.166667 0.0 0.0
0.0 0.0 -0.166667 … 2.0 -0.166667 0.0
0.0 0.0 0.0 -6.5 2.0 -0.166667
0.0 0.0 0.0 9.33333 -6.5 2.0
0.0 0.0 0.0 -6.5 9.33333 -6.5
0.0 0.0 0.0 2.0 -6.5 9.33333
julia> sparse(A)
10×10 SparseMatrixCSC{Float64,Int64} with 58 stored entries:
[1 , 1] = 9.33333
[2 , 1] = -6.5
[3 , 1] = 2.0
[4 , 1] = -0.166667
[1 , 2] = -6.5
[2 , 2] = 9.33333
[3 , 2] = -6.5
⋮
[7 , 9] = 2.0
[8 , 9] = -6.5
[9 , 9] = 9.33333
[10, 9] = -6.5
[7 , 10] = -0.166667
[8 , 10] = 2.0
[9 , 10] = -6.5
[10, 10] = 9.33333
模板乘法是极度并行的,**DiffEqOperators.jl** 已经考虑到了这一点。
现在让我们使用二维 space x time
网格上的显式离散化来求解著名的热方程。热方程为:-
对于这个例子,我们考虑一个狄利克雷边界条件,初始分布为抛物线。由于我们在边界上固定了值(在本例中相等),经过很长时间后,我们预计一维杆将以线性方式被加热。
julia> using DiffEqOperators, DifferentialEquations, Plots
julia> x = -pi : 2pi/511 : pi;
julia> u0 = -(x - 0.5).^2 + 1/12;
julia> A = DerivativeOperator{Float64}(2,2,2pi/511,512,:Dirichlet,:Dirichlet;BC=(u0[1],u0[end]));
这是设置问题的代码。首先,我们定义域,它只是一条直线,被分成 512
段。然后,我们定义初始条件,它是一个关于 x 坐标的抛物线函数。
最后,我们初始化二阶导数和二阶逼近阶的 DerivativeOperator
。我们告诉网格步长值、域的总长度和两端的边界条件。请注意,由于我们在这里应用狄利克雷边界条件,我们需要告诉边界上的值,它以元组的形式作为最后一个参数给出。
现在将方程作为常微分方程求解,我们有:-
julia> prob1 = ODEProblem(A, u0, (0.,10.));
julia> sol1 = solve(prob1, dense=false, tstops=0:0.01:10);
# try to plot the solution at different time points using
julia> plot(x, [sol1(i) for i in 0:1:10])
请注意热分布如何随着时间的推移“变平”,正如预期的那样,最终趋于从左端到右端线性增加。
并非所有偏微分方程都可以用中心差分法求解,例如KdV 波方程。经过常微分方程求解器的几次迭代后,波开始分裂,即它很快变得不稳定。
对于这些非常特殊的情况,已经设计出了 迎风格式。它表示一类用于求解双曲型偏微分方程的数值离散化方法。它们试图通过使用根据特征速度符号确定的方向进行偏差的差分来离散化双曲型偏微分方程。例如一维线性对流方程
描述了一个沿 x 轴以速度 传播的波。如果 为正,则上述方程的传播波解向右传播,左侧称为迎风侧,右侧称为背风侧。如果空间导数 的有限差分格式包含迎风侧的更多点,则该格式称为 **迎风格式**。考虑二阶迎风格式的情况,定义