Linear Reaction-Diffusion Equations
Next, we write a specialised solver for solving linear reaction-diffusion equations. What we produce in this section can also be accessed in FiniteVolumeMethod.LinearReactionDiffusionEquation
.
Mathematical Details
To start, let's give the mathematical details. The problems we will be solving take the form
We want to turn this into an equation of the form
Implementation
Let us now implement the solver. For constructing FiniteVolumeMethod.triangle_contributions!
as in the previous sections, but we will need an extra function to add apply_dirichlet_conditions!
, apply_dudt_conditions
, and boundary_edge_contributions!
from the diffusion equation example. Here is our implementation.
using FiniteVolumeMethod, SparseArrays, OrdinaryDiffEq, LinearAlgebra
const FVM = FiniteVolumeMethod
function linear_source_contributions!(A, mesh, conditions, source_function, source_parameters)
for i in each_solid_vertex(mesh.triangulation)
if !FVM.has_condition(conditions, i)
x, y = get_point(mesh, i)
A[i, i] += source_function(x, y, source_parameters)
end
end
end
function linear_reaction_diffusion_equation(mesh::FVMGeometry,
BCs::BoundaryConditions,
ICs::InternalConditions=InternalConditions();
diffusion_function,
diffusion_parameters=nothing,
source_function,
source_parameters=nothing,
initial_condition,
initial_time=0.0,
final_time)
conditions = Conditions(mesh, BCs, ICs)
n = DelaunayTriangulation.num_solid_vertices(mesh.triangulation)
Afull = zeros(n + 1, n + 1)
A = @views Afull[begin:end-1, begin:end-1]
b = @views Afull[begin:end-1, end]
_ic = vcat(initial_condition, 1)
FVM.triangle_contributions!(A, mesh, conditions, diffusion_function, diffusion_parameters)
FVM.boundary_edge_contributions!(A, b, mesh, conditions, diffusion_function, diffusion_parameters)
linear_source_contributions!(A, mesh, conditions, source_function, source_parameters)
FVM.apply_dudt_conditions!(b, mesh, conditions)
FVM.apply_dirichlet_conditions!(_ic, mesh, conditions)
FVM.fix_missing_vertices!(A, b, mesh)
Af = sparse(Afull)
prob = ODEProblem(MatrixOperator(Af), _ic, (initial_time, final_time))
return prob
end
linear_reaction_diffusion_equation (generic function with 2 methods)
If you go and look back at the diffusion_equation
function from the diffusion equation example, you will see that this is essentially the same function except we now have linear_source_contributions!
and source_function
and source_parameters
arguments.
Let's now test this function. We consider the problem
with
using DelaunayTriangulation
tri = triangulate_rectangle(0, 1, 0, 1, 150, 150, single_boundary=true)
mesh = FVMGeometry(tri)
BCs = BoundaryConditions(mesh, (x, y, t, u, p) -> one(x), Neumann)
diffusion_function = (x, y, p) -> p.D * x^2 * y
diffusion_parameters = (D=1e-3,)
source_function = (x, y, p) -> (x - 1) * (y - 1)
initial_condition = [x^2 + y^2 for (x, y) in DelaunayTriangulation.each_point(tri)]
final_time = 8.0
prob = linear_reaction_diffusion_equation(mesh, BCs;
diffusion_function, diffusion_parameters,
source_function, initial_condition, final_time)
ODEProblem with uType Vector{Float64} and tType Float64. In-place: true
timespan: (0.0, 8.0)
u0: 22501-element Vector{Float64}:
0.0
4.504301608035674e-5
0.00018017206432142696
0.00040538714472321063
0.0007206882572857079
⋮
1.9601369307688843
1.9733345344804287
1.986622224224134
2.0
1.0
sol = solve(prob, Tsit5(); saveat=2)
retcode: Success
Interpolation: 1st order linear
t: 5-element Vector{Float64}:
0.0
2.0
4.0
6.0
8.0
u: 5-element Vector{Vector{Float64}}:
[0.0, 4.504301608035674e-5, 0.00018017206432142696, 0.00040538714472321063, 0.0007206882572857079, 0.0011260754020089186, 0.0016215485788928425, 0.0022071077879374803, 0.0028827530291428314, 0.0036484843025088956 … 1.8955002026935723, 1.9082473762443133, 1.921084635827215, 1.9340119814422772, 1.9470294130895003, 1.9601369307688843, 1.9733345344804287, 1.986622224224134, 2.0, 1.0]
[5.392972715241143e-10, 0.0003283969313676684, 0.0012960759990657792, 0.002877290944573626, 0.005046982038098889, 0.007780761845897448, 0.011054900287088444, 0.014846309987847438, 0.019132531927487165, 0.023891721371031467 … 1.8576012589880762, 1.8669187520861235, 1.8756974896164818, 1.884763887437326, 1.8917905879259957, 1.902881675506313, 1.9023588034293868, 1.9299909658674865, 1.8825037936504285, 1.0]
[7.916735417539211e-9, 0.002394255277378063, 0.009323370071034556, 0.02042192008254055, 0.03534389913584281, 0.05376187635010653, 0.07536617230272592, 0.09986406790939065, 0.1269790448138673, 0.15645005612239932 … 1.8509136105071604, 1.858591600062336, 1.8664895899891367, 1.8731272794777616, 1.8825835384209586, 1.884116496741108, 1.905311415327742, 1.8762173442191878, 1.978490952695312, 1.0]
[8.716291359760346e-8, 0.017455872288248277, 0.06706791816304723, 0.14494671157302366, 0.24751160642335782, 0.37147055211194413, 0.5138009456452572, 0.6717316202228347, 0.8427259076096069, 1.0244657148949718 … 1.856052705661518, 1.8629729344642054, 1.869629885146697, 1.8768276952756373, 1.8824053751208059, 1.892111089683071, 1.8912408569476635, 1.9171352613990147, 1.8728687859751985, 1.0]
[8.530436664276854e-7, 0.1272660020066086, 0.48245430288397767, 1.0287720968254588, 1.7333054836766988, 2.5666820729791873, 3.5027585194801247, 4.5183326883131265, 5.592878646428236, 6.708302802132749 … 1.870052628652889, 1.8761847240187424, 1.8825065062351822, 1.8884430769360512, 1.8956204930445033, 1.8997921589345663, 1.9118930626122232, 1.9042189119217956, 1.9487418307409279, 1.0]
using CairoMakie
fig = Figure(fontsize=38)
for j in eachindex(sol)
ax = Axis(fig[1, j], width=600, height=600,
xlabel="x", ylabel="y",
title="t = $(sol.t[j])")
tricontourf!(ax, tri, sol.u[j], levels=0:0.1:1, extendlow=:auto, extendhigh=:auto, colormap=:turbo)
tightlimits!(ax)
end
resize_to_layout!(fig)
fig

Here is how we could convert this into an FVMProblem
. Note that the Neumann boundary conditions are expressed as FVMProblem
we need them in the form
_BCs = BoundaryConditions(mesh, (x, y, t, u, p) -> -p.D(x, y, p.Dp), Neumann;
parameters=(D=diffusion_function, Dp=diffusion_parameters))
fvm_prob = FVMProblem(
mesh,
_BCs;
diffusion_function=let D=diffusion_function
(x, y, t, u, p) -> D(x, y, p)
end,
diffusion_parameters=diffusion_parameters,
source_function=let S=source_function
(x, y, t, u, p) -> S(x, y, p) * u
end,
final_time=final_time,
initial_condition=initial_condition
)
fvm_sol = solve(fvm_prob, Tsit5(), saveat=2.0)
retcode: Success
Interpolation: 1st order linear
t: 5-element Vector{Float64}:
0.0
2.0
4.0
6.0
8.0
u: 5-element Vector{Vector{Float64}}:
[0.0, 4.504301608035674e-5, 0.00018017206432142696, 0.00040538714472321063, 0.0007206882572857079, 0.0011260754020089186, 0.0016215485788928425, 0.0022071077879374803, 0.0028827530291428314, 0.0036484843025088956 … 1.882843115174992, 1.8955002026935723, 1.9082473762443133, 1.921084635827215, 1.9340119814422772, 1.9470294130895003, 1.9601369307688843, 1.9733345344804287, 1.986622224224134, 2.0]
[5.392972715241146e-10, 0.00032839693136766865, 0.0012960759990657787, 0.002877290944573627, 0.0050469820380988906, 0.00778076184589744, 0.01105490028708845, 0.014846309987847429, 0.01913253192748716, 0.02389172137103148 … 1.8480635912090653, 1.8576012582625914, 1.8669187542720502, 1.8756974833133426, 1.8847639048460716, 1.8917905417452556, 1.902881793954016, 1.9023585053824088, 1.9299917234623845, 1.8825017475754324]
[7.916735417539227e-9, 0.0023942552773780694, 0.009323370071034566, 0.020421920082540564, 0.03534389913584279, 0.0537618763501065, 0.07536617230272606, 0.09986406790939072, 0.12697904481386746, 0.15645005612239948 … 1.8429322427231227, 1.8509136099818753, 1.8585916016450483, 1.866489585425376, 1.8731272920825013, 1.8825835049839645, 1.884116582502732, 1.9053111995279264, 1.8762178927529864, 1.9784894712422374]
[8.716291359760358e-8, 0.017455872288248315, 0.06706791816304726, 0.14494671157302375, 0.24751160642335737, 0.37147055211194385, 0.5138009456452574, 0.671731620222835, 0.8427259076096064, 1.0244657148949723 … 1.8491360538216242, 1.8560527070463413, 1.8629729302916793, 1.8696298971782281, 1.8768276620455648, 1.8824054632712968, 1.8921108635883268, 1.8912414258641945, 1.9171338152905444, 1.8728726915537697]
[8.530436664276897e-7, 0.12726600200660945, 0.4824543028839793, 1.0287720968254626, 1.7333054836767012, 2.566682072979191, 3.5027585194801385, 4.518332688313142, 5.59287864642825, 6.708302802132766 … 1.8638903357449816, 1.8700526280569478, 1.8761847258143487, 1.8825065010575315, 1.8884430912362884, 1.8956204551098073, 1.8997922562322205, 1.9118928177845889, 1.9042195342403745, 1.9487401500135615]
Using the Provided Template
The above code is implemented in LinearReactionDiffusionEquation
in FiniteVolumeMethod.jl.
prob = LinearReactionDiffusionEquation(mesh, BCs;
diffusion_function, diffusion_parameters,
source_function, initial_condition, final_time)
sol = solve(prob, Tsit5(); saveat=2)
retcode: Success
Interpolation: 1st order linear
t: 5-element Vector{Float64}:
0.0
2.0
4.0
6.0
8.0
u: 5-element Vector{Vector{Float64}}:
[0.0, 4.504301608035674e-5, 0.00018017206432142696, 0.00040538714472321063, 0.0007206882572857079, 0.0011260754020089186, 0.0016215485788928425, 0.0022071077879374803, 0.0028827530291428314, 0.0036484843025088956 … 1.8955002026935723, 1.9082473762443133, 1.921084635827215, 1.9340119814422772, 1.9470294130895003, 1.9601369307688843, 1.9733345344804287, 1.986622224224134, 2.0, 1.0]
[5.392972715241143e-10, 0.0003283969313676684, 0.0012960759990657792, 0.002877290944573626, 0.005046982038098889, 0.007780761845897448, 0.011054900287088444, 0.014846309987847438, 0.019132531927487165, 0.023891721371031467 … 1.8576012589880762, 1.8669187520861235, 1.8756974896164818, 1.884763887437326, 1.8917905879259957, 1.902881675506313, 1.9023588034293868, 1.9299909658674865, 1.8825037936504285, 1.0]
[7.916735417539211e-9, 0.002394255277378063, 0.009323370071034556, 0.02042192008254055, 0.03534389913584281, 0.05376187635010653, 0.07536617230272592, 0.09986406790939065, 0.1269790448138673, 0.15645005612239932 … 1.8509136105071604, 1.858591600062336, 1.8664895899891367, 1.8731272794777616, 1.8825835384209586, 1.884116496741108, 1.905311415327742, 1.8762173442191878, 1.978490952695312, 1.0]
[8.716291359760346e-8, 0.017455872288248277, 0.06706791816304723, 0.14494671157302366, 0.24751160642335782, 0.37147055211194413, 0.5138009456452572, 0.6717316202228347, 0.8427259076096069, 1.0244657148949718 … 1.856052705661518, 1.8629729344642054, 1.869629885146697, 1.8768276952756373, 1.8824053751208059, 1.892111089683071, 1.8912408569476635, 1.9171352613990147, 1.8728687859751985, 1.0]
[8.530436664276854e-7, 0.1272660020066086, 0.48245430288397767, 1.0287720968254588, 1.7333054836766988, 2.5666820729791873, 3.5027585194801247, 4.5183326883131265, 5.592878646428236, 6.708302802132749 … 1.870052628652889, 1.8761847240187424, 1.8825065062351822, 1.8884430769360512, 1.8956204930445033, 1.8997921589345663, 1.9118930626122232, 1.9042189119217956, 1.9487418307409279, 1.0]
Here is a benchmark comparison of LinearReactionDiffusionEquation
versus FVMProblem
.
using BenchmarkTools
using Sundials
@btime solve($prob, $CVODE_BDF(linear_solver=:GMRES); saveat=$2);
48.360 ms (1087 allocations: 1.58 MiB)
@btime solve($fvm_prob, $CVODE_BDF(linear_solver=:GMRES); saveat=$2);
163.686 ms (83267 allocations: 90.84 MiB)
Just the code
An uncommented version of this example is given below. You can view the source code for this file here.
using FiniteVolumeMethod, SparseArrays, OrdinaryDiffEq, LinearAlgebra
const FVM = FiniteVolumeMethod
function linear_source_contributions!(A, mesh, conditions, source_function, source_parameters)
for i in each_solid_vertex(mesh.triangulation)
if !FVM.has_condition(conditions, i)
x, y = get_point(mesh, i)
A[i, i] += source_function(x, y, source_parameters)
end
end
end
function linear_reaction_diffusion_equation(mesh::FVMGeometry,
BCs::BoundaryConditions,
ICs::InternalConditions=InternalConditions();
diffusion_function,
diffusion_parameters=nothing,
source_function,
source_parameters=nothing,
initial_condition,
initial_time=0.0,
final_time)
conditions = Conditions(mesh, BCs, ICs)
n = DelaunayTriangulation.num_solid_vertices(mesh.triangulation)
Afull = zeros(n + 1, n + 1)
A = @views Afull[begin:end-1, begin:end-1]
b = @views Afull[begin:end-1, end]
_ic = vcat(initial_condition, 1)
FVM.triangle_contributions!(A, mesh, conditions, diffusion_function, diffusion_parameters)
FVM.boundary_edge_contributions!(A, b, mesh, conditions, diffusion_function, diffusion_parameters)
linear_source_contributions!(A, mesh, conditions, source_function, source_parameters)
FVM.apply_dudt_conditions!(b, mesh, conditions)
FVM.apply_dirichlet_conditions!(_ic, mesh, conditions)
FVM.fix_missing_vertices!(A, b, mesh)
Af = sparse(Afull)
prob = ODEProblem(MatrixOperator(Af), _ic, (initial_time, final_time))
return prob
end
using DelaunayTriangulation
tri = triangulate_rectangle(0, 1, 0, 1, 150, 150, single_boundary=true)
mesh = FVMGeometry(tri)
BCs = BoundaryConditions(mesh, (x, y, t, u, p) -> one(x), Neumann)
diffusion_function = (x, y, p) -> p.D * x^2 * y
diffusion_parameters = (D=1e-3,)
source_function = (x, y, p) -> (x - 1) * (y - 1)
initial_condition = [x^2 + y^2 for (x, y) in DelaunayTriangulation.each_point(tri)]
final_time = 8.0
prob = linear_reaction_diffusion_equation(mesh, BCs;
diffusion_function, diffusion_parameters,
source_function, initial_condition, final_time)
sol = solve(prob, Tsit5(); saveat=2)
using CairoMakie
fig = Figure(fontsize=38)
for j in eachindex(sol)
ax = Axis(fig[1, j], width=600, height=600,
xlabel="x", ylabel="y",
title="t = $(sol.t[j])")
tricontourf!(ax, tri, sol.u[j], levels=0:0.1:1, extendlow=:auto, extendhigh=:auto, colormap=:turbo)
tightlimits!(ax)
end
resize_to_layout!(fig)
fig
_BCs = BoundaryConditions(mesh, (x, y, t, u, p) -> -p.D(x, y, p.Dp), Neumann;
parameters=(D=diffusion_function, Dp=diffusion_parameters))
fvm_prob = FVMProblem(
mesh,
_BCs;
diffusion_function=let D=diffusion_function
(x, y, t, u, p) -> D(x, y, p)
end,
diffusion_parameters=diffusion_parameters,
source_function=let S=source_function
(x, y, t, u, p) -> S(x, y, p) * u
end,
final_time=final_time,
initial_condition=initial_condition
)
fvm_sol = solve(fvm_prob, Tsit5(), saveat=2.0)
prob = LinearReactionDiffusionEquation(mesh, BCs;
diffusion_function, diffusion_parameters,
source_function, initial_condition, final_time)
sol = solve(prob, Tsit5(); saveat=2)
This page was generated using Literate.jl.