Diffusion Equation on an Annulus
In this tutorial, we consider a diffusion equation on an annulus:
\[\begin{equation} \begin{aligned} \pdv{u(\vb x, t)}{t} &= \grad^2 u(\vb x, t) & \vb x \in \Omega, \\ \grad u(\vb x, t) \vdot \vu n(\vb x) &= 0 & \vb x \in \mathcal D(0, 1), \\ u(\vb x, t) &= c(t) & \vb x \in \mathcal D(0,0.2), \\ u(\vb x, t) &= u_0(\vb x), \end{aligned} \end{equation}\]
demonstrating how we can solve PDEs over multiply-connected domains. Here, $\mathcal D(0, r)$ is a circle of radius $r$ centred at the origin, $\Omega$ is the annulus between $\mathcal D(0,0.2)$ and $\mathcal D(0, 1)$, $c(t) = 50[1-\mathrm{e}^{-t/2}]$, and
\[u_0(x) = 10\mathrm{e}^{-25\left[\left(x+\frac12\right)^2+\left(y+\frac12\right)^2\right]} - 10\mathrm{e}^{-45\left[\left(x-\frac12\right)^2+\left(y-\frac12\right)^2\right]} - 5\mathrm{e}^{-50\left[\left(x+\frac{3}{10}\right)^2+\left(y+\frac12\right)^2\right]}.\]
For the mesh, we use two CircularArcs to define the annulus.
using DelaunayTriangulation, FiniteVolumeMethod, CairoMakie
R₁, R₂ = 0.2, 1.0
inner = CircularArc((R₁, 0.0), (R₁, 0.0), (0.0, 0.0), positive = false)
outer = CircularArc((R₂, 0.0), (R₂, 0.0), (0.0, 0.0))
boundary_nodes = [[[outer]], [[inner]]]
points = NTuple{2, Float64}[]
tri = triangulate(points; boundary_nodes)
A = get_area(tri)
refine!(tri; max_area = 1e-4A)
triplot(tri)
mesh = FVMGeometry(tri)FVMGeometry with 8906 control volumes, 17494 triangles, and 26400 edgesNow let us define the boundary conditions. Remember, the order of the boundary conditions follows the order of the boundaries in the mesh. The outer boundary came first, and then came the inner boundary. We can verify that this is the order of the boundary indices as follows:
fig = Figure()
ax = Axis(fig[1, 1])
outer = [get_point(tri, i) for i in get_neighbours(tri, -1)]
inner = [get_point(tri, i) for i in get_neighbours(tri, -2)]
triplot!(ax, tri)
scatter!(ax, outer, color = :red)
scatter!(ax, inner, color = :blue)
fig
So, the boundary conditions are:
outer_bc = (x, y, t, u, p) -> zero(u)
inner_bc = (x, y, t, u, p) -> oftype(u, 50(1 - exp(-t / 2)))
types = (Neumann, Dirichlet)
BCs = BoundaryConditions(mesh, (outer_bc, inner_bc), types)BoundaryConditions with 2 boundary conditions with types (Neumann, Dirichlet)Finally, let's define the problem and solve it.
initial_condition_f = (x,
y) -> begin
10 * exp(-25 * ((x + 0.5) * (x + 0.5) + (y + 0.5) * (y + 0.5))) -
5 * exp(-50 * ((x + 0.3) * (x + 0.3) + (y + 0.5) * (y + 0.5))) -
10 * exp(-45 * ((x - 0.5) * (x - 0.5) + (y - 0.5) * (y - 0.5)))
end
diffusion_function = (x, y, t, u, p) -> one(u)
initial_condition = [initial_condition_f(x, y)
for (x, y) in DelaunayTriangulation.each_point(tri)]
final_time = 2.0
prob = FVMProblem(mesh, BCs;
diffusion_function,
final_time,
initial_condition)FVMProblem with 8906 nodes and time span (0.0, 2.0)using OrdinaryDiffEq, OrdinaryDiffEqSDIRK, LinearSolve
sol = solve(prob, TRBDF2(linsolve = KLUFactorization()), saveat = 0.2)retcode: Success
Interpolation: 1st order linear
t: 11-element Vector{Float64}:
0.0
0.2
0.4
0.6
0.8
1.0
1.2
1.4
1.6
1.8
2.0
u: 11-element Vector{Vector{Float64}}:
[-1.6918979226151304e-9, -2.1738750708653327e-6, 3.726653172035993e-5, -1.6918979226151364e-9, 3.705953483484758e-5, -0.2105979106615764, 5.175555005801385e-16, 1.1709299175694265, 5.1755544523350865e-16, 0.0020233820430370225 … 2.394098931922989, 3.0526059888466146, 0.0032498829004540485, -3.181038960460887e-7, 4.540399417365942e-13, -1.2713505119834172e-16, 7.579579600965964e-7, -1.900972936035572e-16, -7.16487470974605e-18, 8.862645326321719e-10]
[0.040938166463417296, 4.700606018620402, 0.8991046130437262, 0.05097549640543214, 0.8305441374413417, -0.1676189890552742, 0.44471071987096206, 1.1469980651675926, 0.4026039427139896, 4.700606018620402 … 1.1950503637916765, 1.224129069339804, 0.9790783620468809, 0.249323853870975, 0.5100584338722027, 0.32347158760539513, 0.7835108497860879, 0.2837297195042144, 0.2886671435698269, 0.6025938984270272]
[1.8421027544648614, 8.36621276142342, 2.250694305896309, 1.8532289774307824, 2.230804250793645, 1.7692155549108175, 2.0501449758079042, 2.3291457386184247, 2.0284178014294953, 8.36621276142342 … 2.5615032365398918, 2.636041413415823, 2.3742514850781187, 2.1629582740589384, 2.2131754544632543, 2.0517998986902133, 2.5070100255467596, 2.0029823304118657, 2.004759319802877, 2.2855104560820316]
[4.360562843559459, 12.948629557787312, 4.5450393846345, 4.366524113513842, 4.537749967380308, 4.327501816398227, 4.456519312289492, 4.579019413210773, 4.446951835802833, 12.948629557787312 … 4.901937339899625, 5.000985312572518, 4.699352732176257, 4.74147766753295, 4.676304276072871, 4.531844344650306, 4.980073326212344, 4.478112933675547, 4.482629122323663, 4.733456829016822]
[7.2569659194863005, 15.509024737817361, 7.3396958428290455, 7.259467989858852, 7.3369636124758575, 7.241857257241041, 7.300126169403523, 7.355048380196289, 7.296193380703318, 15.509024737817361 … 7.721058674449442, 7.83186853299042, 7.509237263151189, 7.666439689805441, 7.547322561960606, 7.409896227475177, 7.857389404166011, 7.353625356008506, 7.36009267141736, 7.597710976371302]
[10.311678795248195, 19.542068482091885, 10.348523170289294, 10.312434179152174, 10.347697634057242, 10.304735759471125, 10.330738965314978, 10.355615112356062, 10.329381039007828, 19.542068482091885 … 10.735020120425029, 10.84911947588807, 10.522343107856255, 10.727297630894554, 10.586373166281573, 10.454080691467368, 10.894153771345666, 10.3975074141688, 10.40486547455962, 10.633048577885269]
[13.376063391947941, 22.31236602576464, 13.392231594963361, 13.376006368917231, 13.392242661781186, 13.372810807388182, 13.38422059049925, 13.395617851290444, 13.384030840780925, 22.31236602576464 … 13.771522323747991, 13.884233561600547, 13.563666489158551, 13.784133565385359, 13.63726004175254, 13.51040750850389, 13.936526184260115, 13.455081998500713, 13.462681199212177, 13.681218030719574]
[16.358101638904706, 25.100911290797047, 16.36497342895707, 16.357686660130398, 16.365347631838493, 16.356513913529007, 16.36135481125154, 16.36668820660317, 16.361683606299533, 25.100911290797047 … 16.728758051081762, 16.83719229584065, 16.529762923729, 16.749556049420377, 16.604978147497878, 16.484597442038176, 16.89085398228687, 16.431619365331308, 16.439089001848075, 16.646358889029152]
[19.199607111147095, 26.491208788293957, 19.2023177708756, 19.19904948422525, 19.202837112348224, 19.198773382946577, 19.200673316929638, 19.203271690335665, 19.201217458447886, 26.491208788293957 … 19.547141819240096, 19.650220965359328, 19.358590276604858, 19.5707131800158, 19.431926462668404, 19.31838723815064, 19.70273990488181, 19.26823249103038, 19.275396496491155, 19.47083165784839]
[21.869673054789605, 29.227608779876476, 21.870540517864875, 21.869074130173132, 21.871104267812186, 21.8691833009201, 21.869781434686313, 21.871142697902094, 21.87040068588059, 29.227608779876476 … 22.190808787342323, 22.286328283458964, 22.015915765825813, 22.214286532352894, 22.084889404053524, 21.979679763122213, 22.335579580157532, 21.933068405917854, 21.93977886782677, 22.120859333013602]
[24.362270544301143, 30.565395008173336, 24.362336320486264, 24.36167639541735, 24.362896705102692, 24.361944012373307, 24.361975747899116, 24.36276828460437, 24.36260118001772, 30.565395008173336 … 24.665344119269108, 24.756067387077824, 24.499710207378122, 24.6883939335329, 24.565394863865425, 24.46596521217103, 24.803194992984608, 24.421927106278794, 24.428279397480416, 24.599392359797672]fig = Figure(fontsize = 38)
for (i, j) in zip(1:3, (1, 6, 11))
local ax
ax = Axis(fig[1, i], width = 600, height = 600,
xlabel = "x", ylabel = "y",
title = "t = $(sol.t[j])",
titlealign = :left)
tricontourf!(ax, tri, sol.u[j], levels = -10:2:40, colormap = :matter)
tightlimits!(ax)
end
resize_to_layout!(fig)
fig
To finish this example, let us consider how natural neighbour interpolation can be applied here. The application is more complicated for this problem since the mesh has holes. Before we do that, though, let us show how we could use pl_interpolate, which could be useful if we did not need a higher quality interpolant. Let us interpolate the solution at $t = 1$, which is sol.t[6]. For this, we need to put the ghost triangles back into tri so that we can safely apply jump_and_march. This is done with add_ghost_triangles!.
add_ghost_triangles!(tri)Delaunay Triangulation.
Number of vertices: 8906
Number of triangles: 17494
Number of edges: 26400
Has boundary nodes: true
Has ghost triangles: true
Curve-bounded: true
Weighted: false
Constrained: true(Actually, tri already had these ghost triangles, but we are just showing how you would add them back in if needed.)
Now let's interpolate.
x = LinRange(-R₂, R₂, 400)
y = LinRange(-R₂, R₂, 400)
interp_vals = zeros(length(x), length(y))
u = sol.u[6]
last_triangle = Ref((1, 1, 1))
for (j, _y) in enumerate(y)
for (i, _x) in enumerate(x)
T = jump_and_march(tri, (_x, _y), try_points = last_triangle[])
last_triangle[] = triangle_vertices(T) # used to accelerate jump_and_march, since the points we're looking for are close to each other
if DelaunayTriangulation.is_ghost_triangle(T) # don't extrapolate
interp_vals[i, j] = NaN
else
interp_vals[i, j] = pl_interpolate(prob, T, sol.u[6], _x, _y)
end
end
end
fig, ax, sc = contourf(x, y, interp_vals, levels = -10:2:40, colormap = :matter)
fig
Let's now consider applying NaturalNeighbours.jl. We apply it naively first to highlight some complications.
using NaturalNeighbours
_x = vec([x for x in x, y in y]) # NaturalNeighbours.jl needs vector data
_y = vec([y for x in x, y in y])
itp = interpolate(tri, u, derivatives = true)Natural Neighbour Interpolant
z: [10.311678795248195, 19.542068482091885, 10.348523170289294, 10.312434179152174, 10.347697634057242, 10.304735759471125, 10.330738965314978, 10.355615112356062, 10.329381039007828, 19.542068482091885 … 10.735020120425029, 10.84911947588807, 10.522343107856255, 10.727297630894554, 10.586373166281573, 10.454080691467368, 10.894153771345666, 10.3975074141688, 10.40486547455962, 10.633048577885269]
∇: [(0.0021193332660897567, -0.0109681343436805), (4758.722040691395, -34.99760381270306), (-0.021841740804556194, -0.018611352253622076), (-0.02365570054116361, -0.01184026694590588), (-0.020911719402055415, -0.0001983240994332534), (0.0016294914221730206, 0.002770272496367241), (-0.025438576037677445, -0.02027756929087996), (-0.0030122931419357198, -0.007125407891847634), (-0.035564648783518886, -0.004177867933217493), (-3076.6426071587243, -417.73121870240715) … (3.2912278807315327, 1.7236159024237898), (3.7825339767123256, 2.045905946849367), (2.422130846927594, 0.13977149264110275), (0.25049535402526774, -3.8859016350907107), (2.0038993829525227, -2.233185681423754), (0.9146830887363427, -1.8969659652284838), (-1.7973619878401506, 4.2117392241281975), (0.6186395361747663, -1.4680473800768465), (-1.4886278795660726, 0.736563544127938), (-1.8143961731517133, 2.7015955427832536)]
H: [(15.96316294392874, 0.028383303411738208, 0.06939679645369881), (-440506.66630047973, 22574.3618709472, -2907.665386423836), (17.276678888723165, -0.16587811819024914, -0.09219380849584778), (-0.11183932756757817, 13.687637314012191, -0.17079200986985985), (-0.07043258017695231, 15.388605436360532, -0.1116271442301115), (7.6270082080577835, 7.8920209025968715, 7.8074128331275565), (7.367992082413287, 6.788207346176093, -7.285748432957614), (8.583811682543539, 7.969416247748044, 8.328565897489893), (6.631494163869983, 6.996128669181593, -6.787577173685071), (-241612.42742820925, 17780.539636934423, 986.3066018391113) … (14.859120384373782, 0.7183393923452498, 10.257475201899505), (14.93402115809741, 0.35224802569260916, 11.076227596042513), (17.96643019005438, -2.704099913138065, 1.3452401169278265), (-5.2462031888604, 20.064967924590466, -1.663520688295872), (6.6052129938801665, 8.842691294965908, -11.24129682891313), (1.485079511698496, 13.999671203089322, -7.915252329849441), (-1.933161480642981, 17.390661989953152, -9.724621356952266), (1.560410270792376, 14.370533909588127, -6.953714457712088), (13.145079482250344, 1.9429894595319228, -7.672056152743646), (3.204545932921749, 12.198034771706038, -10.704344417886254)]itp_vals = itp(_x, _y; method = Farin())160000-element Vector{Float64}:
10.355617250853154
10.35561574149549
10.355614232137825
10.355612722780162
10.355611213422497
⋮
10.304709321099189
10.3047195559387
10.30472979077821
10.30474002561772
10.304750260457231fig, ax,
sc = contourf(
x, y, reshape(itp_vals, length(x), length(y)), colormap = :matter, levels = -10:2:40)
fig
The issue here is that the interpolant is trying to extrapolate inside the hole and outside of the annulus. To avoid this, you need to pass project=false.
itp_vals = itp(_x, _y; method = Farin(), project = false)160000-element Vector{Float64}:
Inf
Inf
Inf
Inf
Inf
⋮
Inf
Inf
Inf
Inf
Inffig, ax,
sc = contourf(
x, y, reshape(itp_vals, length(x), length(y)), colormap = :matter, levels = -10:2:40)
fig
Just the code
An uncommented version of this example is given below. You can view the source code for this file here.
using DelaunayTriangulation, FiniteVolumeMethod, CairoMakie
R₁, R₂ = 0.2, 1.0
inner = CircularArc((R₁, 0.0), (R₁, 0.0), (0.0, 0.0), positive = false)
outer = CircularArc((R₂, 0.0), (R₂, 0.0), (0.0, 0.0))
boundary_nodes = [[[outer]], [[inner]]]
points = NTuple{2, Float64}[]
tri = triangulate(points; boundary_nodes)
A = get_area(tri)
refine!(tri; max_area = 1e-4A)
triplot(tri)
mesh = FVMGeometry(tri)
fig = Figure()
ax = Axis(fig[1, 1])
outer = [get_point(tri, i) for i in get_neighbours(tri, -1)]
inner = [get_point(tri, i) for i in get_neighbours(tri, -2)]
triplot!(ax, tri)
scatter!(ax, outer, color = :red)
scatter!(ax, inner, color = :blue)
fig
outer_bc = (x, y, t, u, p) -> zero(u)
inner_bc = (x, y, t, u, p) -> oftype(u, 50(1 - exp(-t / 2)))
types = (Neumann, Dirichlet)
BCs = BoundaryConditions(mesh, (outer_bc, inner_bc), types)
initial_condition_f = (x,
y) -> begin
10 * exp(-25 * ((x + 0.5) * (x + 0.5) + (y + 0.5) * (y + 0.5))) -
5 * exp(-50 * ((x + 0.3) * (x + 0.3) + (y + 0.5) * (y + 0.5))) -
10 * exp(-45 * ((x - 0.5) * (x - 0.5) + (y - 0.5) * (y - 0.5)))
end
diffusion_function = (x, y, t, u, p) -> one(u)
initial_condition = [initial_condition_f(x, y)
for (x, y) in DelaunayTriangulation.each_point(tri)]
final_time = 2.0
prob = FVMProblem(mesh, BCs;
diffusion_function,
final_time,
initial_condition)
using OrdinaryDiffEq, OrdinaryDiffEqSDIRK, LinearSolve
sol = solve(prob, TRBDF2(linsolve = KLUFactorization()), saveat = 0.2)
fig = Figure(fontsize = 38)
for (i, j) in zip(1:3, (1, 6, 11))
local ax
ax = Axis(fig[1, i], width = 600, height = 600,
xlabel = "x", ylabel = "y",
title = "t = $(sol.t[j])",
titlealign = :left)
tricontourf!(ax, tri, sol.u[j], levels = -10:2:40, colormap = :matter)
tightlimits!(ax)
end
resize_to_layout!(fig)
fig
add_ghost_triangles!(tri)
x = LinRange(-R₂, R₂, 400)
y = LinRange(-R₂, R₂, 400)
interp_vals = zeros(length(x), length(y))
u = sol.u[6]
last_triangle = Ref((1, 1, 1))
for (j, _y) in enumerate(y)
for (i, _x) in enumerate(x)
T = jump_and_march(tri, (_x, _y), try_points = last_triangle[])
last_triangle[] = triangle_vertices(T) # used to accelerate jump_and_march, since the points we're looking for are close to each other
if DelaunayTriangulation.is_ghost_triangle(T) # don't extrapolate
interp_vals[i, j] = NaN
else
interp_vals[i, j] = pl_interpolate(prob, T, sol.u[6], _x, _y)
end
end
end
fig, ax, sc = contourf(x, y, interp_vals, levels = -10:2:40, colormap = :matter)
fig
using NaturalNeighbours
_x = vec([x for x in x, y in y]) # NaturalNeighbours.jl needs vector data
_y = vec([y for x in x, y in y])
itp = interpolate(tri, u, derivatives = true)
itp_vals = itp(_x, _y; method = Farin())
fig, ax,
sc = contourf(
x, y, reshape(itp_vals, length(x), length(y)), colormap = :matter, levels = -10:2:40)
fig
itp_vals = itp(_x, _y; method = Farin(), project = false)
fig, ax,
sc = contourf(
x, y, reshape(itp_vals, length(x), length(y)), colormap = :matter, levels = -10:2:40)
figThis page was generated using Literate.jl.