Skip to content

Commit 84756e8

Browse files
Add diffusion-implicit time stepping heat tutorial
1 parent f9e13bb commit 84756e8

File tree

2 files changed

+205
-0
lines changed

2 files changed

+205
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ SciMLTutorials.open_notebooks()
6464
- Advanced
6565
- [A 2D Cardiac Electrophysiology Model (CUDA-accelerated PDE solver)](http://tutorials.juliadiffeq.org/html/advanced/01-beeler_reuter.html)
6666
- [Solving Stiff Equations](http://tutorials.juliadiffeq.org/html/advanced/02-advanced_ODE_solving.html)
67+
- [Solving Stiff Equations (the heat equation)](http://tutorials.juliadiffeq.org/html/advanced/04-diffusion_implicit_heat_equation.html)
6768
- [Kolmogorov Backward Equations](http://tutorials.juliadiffeq.org/html/advanced/03-kolmogorov_equations.html)
6869
- Perturbation Theory
6970
- [Mixed Symbolic/Numerical Methods for Perturbation Theory - Algebraic Equations](http://tutorials.juliadiffeq.org/html/perturbation/01-perturbation_algebraic.html)
Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
---
2+
title: Solving the heat equation with diffusion-implicit time-stepping
3+
author: Charles Kawczynski
4+
---
5+
6+
In this tutorial, we'll be solving the heat equation:
7+
8+
```math
9+
∂_t T = α ∇²(T) + β \sin(γ z)
10+
```
11+
12+
with boundary conditions: ``∇T(z=a) = ∇T_{bottom}, T(z=b) = T_{top}``. We'll solve these equations numerically using Finite Difference Method on cell faces. The same exercise could easily be done on cell centers.
13+
14+
## Code loading and parameters
15+
16+
First, we'll use / import some packages:
17+
18+
```julia
19+
import Plots
20+
using LinearAlgebra
21+
using DiffEqBase
22+
using OrdinaryDiffEq: SplitODEProblem, solve, IMEXEuler
23+
import SciMLBase
24+
```
25+
26+
Next, we'll define some global problem parameters:
27+
```julia
28+
a,b, n = 0, 1, 10 # zmin, zmax, number of cells
29+
n̂_min, n̂_max = -1, 1 # Outward facing unit vectors
30+
α = 100; # thermal diffusivity, larger means more stiff
31+
β, γ = 10000, π; # source term coefficients
32+
Δt = 1000; # timestep size
33+
N_t = 10; # number of timesteps to take
34+
FT = Float64; # float type
35+
Δz = FT(b-a)/FT(n)
36+
Δz² = Δz^2;
37+
∇²_op = [1/Δz², -2/Δz², 1/Δz²]; # interior Laplacian operator
38+
∇T_bottom = 10; # Temperature gradient at the top
39+
T_top = 1; # Temperature at the bottom
40+
S(z) = β*sin(γ*z) # source term, (sin for easy integration)
41+
zf = range(a, b, length=n+1); # coordinates on cell faces
42+
```
43+
44+
## Derivation of analytic solution
45+
Here, we'll derive the analytic solution:
46+
47+
```math
48+
\frac{∂²T}{∂²z} = -S(z)/α = -β \sin(γ z)/α \\
49+
\frac{∂T}{∂z} = β \cos(γ z)/(γ α)+c_1 \\
50+
T(z) = β \sin(γ z)/(γ^2 α)+c_1 z+c_2, \qquad \text{(generic solution)}
51+
```
52+
Apply bottom boundary condition:
53+
```math
54+
\frac{∂T}{∂z}(a) = β \cos(γ a)/(γ α)+c_1 = ∇T_{bottom} \\
55+
c_1 = ∇T_{bottom}-β \cos(γ a)/(γ α)
56+
```
57+
58+
Apply top boundary condition:
59+
```math
60+
T(b) = β \sin(γ b)/(γ^2 α)+c_1 b+c_2 = T_{top} \\
61+
c_2 = T_{top}-(β \sin(γ b)/(γ^2 α)+c_1 b)
62+
```
63+
64+
And now let's define this in a julia function:
65+
```julia
66+
function T_analytic(z) # Analytic steady state solution
67+
c1 = ∇T_bottom-β*cos(γ*a)/(γ*α)
68+
c2 = T_top-(β*sin(γ*b)/(γ^2*α)+c1*b)
69+
return β*sin(γ*z)/(γ^2*α)+c1*z+c2
70+
end
71+
```
72+
73+
## Derive the temporal discretization
74+
75+
Here, we'll derivation the matrix form of the temporal discretization we wish to use (diffusion-implicit and explicit Euler):
76+
```math
77+
∂_t T = α ∇²T + S \\
78+
(T^{n+1}-T^n) = Δt (α ∇²T^{n+1} + S) \\
79+
(T^{n+1} - Δt α ∇²T^{n+1}) = T^n + Δt S \\
80+
(I - Δt α ∇²) T^{n+1} = T^n + Δt S
81+
```
82+
83+
Note that, since the ``∇²`` reaches to boundary points, we'll need to modify the stencils to account for boundary conditions.
84+
85+
## Derive the finite difference stencil
86+
87+
For the interior domain, a central and second-order finite difference stencil is simply:
88+
89+
```math
90+
∇²f = f_{i-1}/Δz² -2f_i/Δz² + f_{i+1}/Δz², \qquad \text{or} \\
91+
∇² = [1/Δz², -2/Δz², 1/Δz²] \\
92+
```
93+
94+
At the boundaries, we need to modify the stencil to account for Dirichlet and Neumann BCs. Using the following index denotion:
95+
96+
- `i` first interior index
97+
- `b` boundary index
98+
- `g` ghost index
99+
100+
the Dirichlet boundary stencil & source:
101+
```math
102+
∂_t T = α (T[i-1]+T[b]-2 T[i])/Δz² + S \\
103+
∂_t T = α (T[i-1]-2 T[i])/Δz² + S + α T[b] / Δz²
104+
```
105+
106+
and Neumann boundary stencil & source:
107+
```math
108+
∇T_{bottom} n̂ = (T[g] - T[i])/(2Δz), \qquad n̂ = [-1,1] ∈ [zmin,zmax] \\
109+
T[i] + 2 Δz ∇T_{bottom} n̂ = T[g] \\
110+
∂_t T = α (((T[i] + 2 Δz ∇T_{bottom} n̂) - T[b])/Δz - (T[b] - T[i])/Δz)/Δz + S \\
111+
∂_t T = α (((T[i]) - T[b])/Δz - (T[b] - T[i])/Δz)/Δz + S + α 2 Δz ∇T_{bottom} n̂/Δz² \\
112+
∂_t T = α (2 T[i] - 2 T[b])/Δz² + S + 2α ∇T_{bottom} n̂/Δz
113+
```
114+
115+
## Define the discrete diffusion operator
116+
```julia
117+
# Initialize interior and boundary stencils:
118+
∇² = Tridiagonal(
119+
ones(FT, n) .* ∇²_op[1],
120+
ones(FT, n+1) .* ∇²_op[2],
121+
ones(FT, n) .* ∇²_op[3]
122+
);
123+
124+
# Modify boundary stencil to account for BCs
125+
126+
∇².d[1] = -2/Δz²
127+
∇².du[1] = +2/Δz²
128+
129+
# Modify boundary stencil to account for BCs
130+
∇².du[n] = 0 # modified stencil
131+
∇².d[n+1] = 0 # to ensure `∂_t T = 0` at `z=zmax`
132+
∇².dl[n] = 0 # to ensure `∂_t T = 0` at `z=zmax`
133+
D = α .* ∇²
134+
```
135+
136+
## Define boundary source
137+
Here, we'll compute the boundary source (``α T[b] / Δz²``)
138+
```julia
139+
AT_b = zeros(FT, n+1);
140+
AT_b[1] = α*2/Δz*∇T_bottom*n̂_min;
141+
AT_b[end-1] = α*T_top/Δz²;
142+
```
143+
144+
## Set initial condition
145+
Let's just initialize the solution to `1`, and also set the top boundary condition:
146+
```julia
147+
T = zeros(FT, n+1);
148+
T .= 1;
149+
T[n+1] = T_top; # set top BC
150+
```
151+
152+
## Define right-hand side sources
153+
Here, we define the right-hand side (RHS) sources:
154+
```julia
155+
function rhs!(dT, T, params, t)
156+
n = params.n
157+
i = 1:n # interior domain
158+
dT[i] .= S.(zf[i]) .+ AT_b[i]
159+
return dT
160+
end;
161+
```
162+
163+
Next, we'll pacakge up parameters needed in the RHS function, define the ODE problem, and solve.
164+
```julia
165+
params = (;n)
166+
167+
tspan = (FT(0), N_t*FT(Δt))
168+
169+
prob = SplitODEProblem(
170+
SciMLBase.DiffEqArrayOperator(
171+
D,
172+
),
173+
rhs!,
174+
T,
175+
tspan,
176+
params
177+
)
178+
alg = IMEXEuler(linsolve=LinSolveFactorize(lu!))
179+
println("Solving...")
180+
sol = solve(
181+
prob,
182+
alg,
183+
dt = Δt,
184+
saveat = range(FT(0), N_t*FT(Δt), length=5),
185+
progress = true,
186+
progress_message = (dt, u, p, t) -> t,
187+
);
188+
```
189+
190+
# Visualizing results
191+
192+
Now, let's visualize the results of the solution and error:
193+
```julia
194+
T_end = sol.u[end]
195+
196+
p1 = Plots.plot(zf, T_analytic.(zf), label="analytic", markershape=:circle, markersize=6)
197+
p1 = Plots.plot!(p1, zf, T_end, label="numerical", markershape=:diamond)
198+
p1 = Plots.plot!(p1, title="T ∈ cell faces")
199+
200+
p2 = Plots.plot(zf, abs.(T_end .- T_analytic.(zf)), label="error", markershape=:circle, markersize=6)
201+
p2 = Plots.plot!(p2, title="T ∈ cell faces")
202+
203+
Plots.plot(p1, p2)
204+
```

0 commit comments

Comments
 (0)