Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Ok-landscape
GitHub Repository: Ok-landscape/computational-pipeline
Path: blob/main/latex-templates/templates/numerical-methods/pde_solver.tex
51 views
unlisted
1
\documentclass[a4paper, 11pt]{report}
2
\usepackage[utf8]{inputenc}
3
\usepackage[T1]{fontenc}
4
\usepackage{amsmath, amssymb}
5
\usepackage{graphicx}
6
\usepackage{siunitx}
7
\usepackage{booktabs}
8
\usepackage{xcolor}
9
\usepackage[makestderr]{pythontex}
10
11
\definecolor{heat}{RGB}{231, 76, 60}
12
\definecolor{wave}{RGB}{46, 204, 113}
13
\definecolor{stable}{RGB}{52, 152, 219}
14
\definecolor{unstable}{RGB}{241, 196, 15}
15
16
\title{Partial Differential Equations:\\
17
Finite Difference Methods and Stability Analysis}
18
\author{Department of Computational Mathematics\\Technical Report NM-2024-002}
19
\date{\today}
20
21
\begin{document}
22
\maketitle
23
24
\begin{abstract}
25
This report presents finite difference methods for solving partial differential equations (PDEs). We implement explicit and implicit schemes for the heat equation and wave equation, analyze stability using von Neumann analysis, and demonstrate the CFL condition. Numerical examples illustrate the importance of stability constraints in time-stepping methods.
26
\end{abstract}
27
28
\tableofcontents
29
30
\chapter{Introduction}
31
32
Partial differential equations describe physical phenomena involving multiple independent variables. We focus on two canonical PDEs:
33
\begin{itemize}
34
\item \textbf{Heat equation} (parabolic): $\frac{\partial u}{\partial t} = \alpha \frac{\partial^2 u}{\partial x^2}$
35
\item \textbf{Wave equation} (hyperbolic): $\frac{\partial^2 u}{\partial t^2} = c^2 \frac{\partial^2 u}{\partial x^2}$
36
\end{itemize}
37
38
\section{Finite Difference Approximations}
39
\begin{align}
40
\frac{\partial u}{\partial t} &\approx \frac{u_i^{n+1} - u_i^n}{\Delta t} \\
41
\frac{\partial^2 u}{\partial x^2} &\approx \frac{u_{i+1}^n - 2u_i^n + u_{i-1}^n}{(\Delta x)^2}
42
\end{align}
43
44
\chapter{Heat Equation}
45
46
\section{Explicit (FTCS) Scheme}
47
Forward Time, Centered Space:
48
\begin{equation}
49
u_i^{n+1} = u_i^n + r(u_{i+1}^n - 2u_i^n + u_{i-1}^n), \quad r = \frac{\alpha \Delta t}{(\Delta x)^2}
50
\end{equation}
51
52
Stability requires: $r \leq \frac{1}{2}$
53
54
\begin{pycode}
55
import numpy as np
56
import matplotlib.pyplot as plt
57
from mpl_toolkits.mplot3d import Axes3D
58
from matplotlib import cm
59
plt.rc('text', usetex=True)
60
plt.rc('font', family='serif')
61
62
np.random.seed(42)
63
64
def heat_explicit(u0, alpha, dx, dt, num_steps):
65
r = alpha * dt / dx**2
66
u = u0.copy()
67
history = [u.copy()]
68
69
for _ in range(num_steps):
70
u_new = u.copy()
71
u_new[1:-1] = u[1:-1] + r * (u[2:] - 2*u[1:-1] + u[:-2])
72
u = u_new
73
history.append(u.copy())
74
75
return np.array(history), r
76
77
def heat_implicit(u0, alpha, dx, dt, num_steps):
78
r = alpha * dt / dx**2
79
n = len(u0)
80
u = u0.copy()
81
history = [u.copy()]
82
83
# Tridiagonal matrix
84
A = np.diag([1 + 2*r] * (n-2)) + np.diag([-r] * (n-3), 1) + np.diag([-r] * (n-3), -1)
85
86
for _ in range(num_steps):
87
b = u[1:-1].copy()
88
b[0] += r * u[0]
89
b[-1] += r * u[-1]
90
u[1:-1] = np.linalg.solve(A, b)
91
history.append(u.copy())
92
93
return np.array(history), r
94
95
def heat_crank_nicolson(u0, alpha, dx, dt, num_steps):
96
r = alpha * dt / (2 * dx**2)
97
n = len(u0)
98
u = u0.copy()
99
history = [u.copy()]
100
101
# Matrices for CN scheme
102
A = np.diag([1 + 2*r] * (n-2)) + np.diag([-r] * (n-3), 1) + np.diag([-r] * (n-3), -1)
103
B = np.diag([1 - 2*r] * (n-2)) + np.diag([r] * (n-3), 1) + np.diag([r] * (n-3), -1)
104
105
for _ in range(num_steps):
106
b = B @ u[1:-1]
107
b[0] += r * (u[0] + u[0])
108
b[-1] += r * (u[-1] + u[-1])
109
u[1:-1] = np.linalg.solve(A, b)
110
history.append(u.copy())
111
112
return np.array(history), r
113
\end{pycode}
114
115
\section{Numerical Results}
116
117
\begin{pycode}
118
# Setup
119
L = 1.0
120
nx = 51
121
dx = L / (nx - 1)
122
x = np.linspace(0, L, nx)
123
alpha = 0.01
124
125
# Initial condition: sine wave
126
u0 = np.sin(np.pi * x)
127
u0[0] = 0
128
u0[-1] = 0
129
130
# Stable explicit
131
dt_stable = 0.4 * dx**2 / alpha
132
num_steps = 200
133
history_stable, r_stable = heat_explicit(u0, alpha, dx, dt_stable, num_steps)
134
135
# Unstable explicit
136
dt_unstable = 0.6 * dx**2 / alpha
137
history_unstable, r_unstable = heat_explicit(u0, alpha, dx, dt_unstable, min(num_steps, 50))
138
139
# Implicit (unconditionally stable)
140
dt_implicit = 2 * dx**2 / alpha
141
history_implicit, r_implicit = heat_implicit(u0, alpha, dx, dt_implicit, num_steps)
142
143
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
144
145
# Stable explicit evolution
146
ax = axes[0, 0]
147
times_plot = [0, 50, 100, 150, 200]
148
for t in times_plot:
149
if t < len(history_stable):
150
ax.plot(x, history_stable[t], label=f'$t = {t*dt_stable:.3f}$')
151
ax.set_xlabel('$x$')
152
ax.set_ylabel('$u(x, t)$')
153
ax.set_title(f'Explicit (Stable): $r = {r_stable:.3f}$')
154
ax.legend()
155
ax.grid(True, alpha=0.3)
156
157
# Unstable explicit
158
ax = axes[0, 1]
159
for i, t in enumerate([0, 10, 20, 30, 40]):
160
if t < len(history_unstable):
161
ax.plot(x, history_unstable[t], label=f'$t = {t*dt_unstable:.3f}$')
162
ax.set_xlabel('$x$')
163
ax.set_ylabel('$u(x, t)$')
164
ax.set_title(f'Explicit (Unstable): $r = {r_unstable:.3f}$')
165
ax.legend()
166
ax.grid(True, alpha=0.3)
167
168
# Implicit evolution
169
ax = axes[1, 0]
170
for t in times_plot:
171
if t < len(history_implicit):
172
ax.plot(x, history_implicit[t], label=f'$t = {t*dt_implicit:.3f}$')
173
ax.set_xlabel('$x$')
174
ax.set_ylabel('$u(x, t)$')
175
ax.set_title(f'Implicit: $r = {r_implicit:.3f}$')
176
ax.legend()
177
ax.grid(True, alpha=0.3)
178
179
# Comparison at final time
180
ax = axes[1, 1]
181
t_final = 0.1
182
n_stable = int(t_final / dt_stable)
183
n_implicit = int(t_final / dt_implicit)
184
analytical = np.exp(-np.pi**2 * alpha * t_final) * np.sin(np.pi * x)
185
186
ax.plot(x, history_stable[min(n_stable, len(history_stable)-1)], 'b-', label='Explicit')
187
ax.plot(x, history_implicit[min(n_implicit, len(history_implicit)-1)], 'g--', label='Implicit')
188
ax.plot(x, analytical, 'r:', linewidth=2, label='Analytical')
189
ax.set_xlabel('$x$')
190
ax.set_ylabel('$u(x, t)$')
191
ax.set_title(f'Comparison at $t = {t_final}$')
192
ax.legend()
193
ax.grid(True, alpha=0.3)
194
195
plt.tight_layout()
196
plt.savefig('heat_equation.pdf', dpi=150, bbox_inches='tight')
197
plt.close()
198
\end{pycode}
199
200
\begin{figure}[htbp]
201
\centering
202
\includegraphics[width=0.95\textwidth]{heat_equation.pdf}
203
\caption{Heat equation solutions: (a) stable explicit, (b) unstable explicit showing oscillations, (c) implicit scheme, (d) comparison with analytical solution.}
204
\end{figure}
205
206
\chapter{Wave Equation}
207
208
\section{Explicit Scheme}
209
\begin{equation}
210
u_i^{n+1} = 2u_i^n - u_i^{n-1} + s^2(u_{i+1}^n - 2u_i^n + u_{i-1}^n), \quad s = \frac{c \Delta t}{\Delta x}
211
\end{equation}
212
213
CFL condition: $s = \frac{c \Delta t}{\Delta x} \leq 1$
214
215
\begin{pycode}
216
def wave_explicit(u0, v0, c, dx, dt, num_steps):
217
s = c * dt / dx
218
n = len(u0)
219
220
u_prev = u0.copy()
221
# First time step using initial velocity
222
u_curr = u0.copy()
223
u_curr[1:-1] = (u0[1:-1] + dt * v0[1:-1] +
224
0.5 * s**2 * (u0[2:] - 2*u0[1:-1] + u0[:-2]))
225
226
history = [u_prev.copy(), u_curr.copy()]
227
228
for _ in range(num_steps - 1):
229
u_next = np.zeros(n)
230
u_next[1:-1] = (2*u_curr[1:-1] - u_prev[1:-1] +
231
s**2 * (u_curr[2:] - 2*u_curr[1:-1] + u_curr[:-2]))
232
u_prev = u_curr.copy()
233
u_curr = u_next.copy()
234
history.append(u_curr.copy())
235
236
return np.array(history), s
237
238
# Wave equation setup
239
c = 1.0
240
nx = 101
241
L = 2.0
242
dx = L / (nx - 1)
243
x = np.linspace(0, L, nx)
244
245
# Initial condition: Gaussian pulse
246
u0_wave = np.exp(-100 * (x - 0.5)**2)
247
u0_wave[0] = 0
248
u0_wave[-1] = 0
249
v0_wave = np.zeros(nx)
250
251
# Stable CFL
252
dt_cfl_stable = 0.8 * dx / c
253
num_steps_wave = 300
254
history_wave_stable, s_stable = wave_explicit(u0_wave, v0_wave, c, dx, dt_cfl_stable, num_steps_wave)
255
256
# Unstable CFL
257
dt_cfl_unstable = 1.2 * dx / c
258
history_wave_unstable, s_unstable = wave_explicit(u0_wave, v0_wave, c, dx, dt_cfl_unstable, min(num_steps_wave, 100))
259
260
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
261
262
# Stable wave propagation
263
ax = axes[0, 0]
264
times_wave = [0, 75, 150, 225, 299]
265
for t in times_wave:
266
if t < len(history_wave_stable):
267
ax.plot(x, history_wave_stable[t], label=f'$t = {t*dt_cfl_stable:.3f}$')
268
ax.set_xlabel('$x$')
269
ax.set_ylabel('$u(x, t)$')
270
ax.set_title(f'Wave Equation (Stable): $s = {s_stable:.2f}$')
271
ax.legend()
272
ax.grid(True, alpha=0.3)
273
274
# Unstable wave
275
ax = axes[0, 1]
276
for t in [0, 20, 40, 60, 80]:
277
if t < len(history_wave_unstable):
278
ax.plot(x, history_wave_unstable[t], label=f'$t = {t*dt_cfl_unstable:.3f}$')
279
ax.set_xlabel('$x$')
280
ax.set_ylabel('$u(x, t)$')
281
ax.set_title(f'Wave Equation (Unstable): $s = {s_unstable:.2f}$')
282
ax.legend()
283
ax.grid(True, alpha=0.3)
284
285
# Space-time plot (stable)
286
ax = axes[1, 0]
287
t_arr = np.arange(len(history_wave_stable)) * dt_cfl_stable
288
X, T = np.meshgrid(x, t_arr[:200])
289
im = ax.pcolormesh(X, T, history_wave_stable[:200], cmap='RdBu', shading='auto')
290
ax.set_xlabel('$x$')
291
ax.set_ylabel('$t$')
292
ax.set_title('Space-Time Evolution')
293
plt.colorbar(im, ax=ax, label='$u(x, t)$')
294
295
# Energy conservation
296
ax = axes[1, 1]
297
energy = []
298
for i in range(len(history_wave_stable) - 1):
299
u = history_wave_stable[i]
300
u_next = history_wave_stable[i + 1]
301
kinetic = 0.5 * np.sum(((u_next[1:-1] - u[1:-1]) / dt_cfl_stable)**2) * dx
302
potential = 0.5 * c**2 * np.sum(((u[2:] - u[:-2]) / (2*dx))**2) * dx
303
energy.append(kinetic + potential)
304
305
ax.plot(np.arange(len(energy)) * dt_cfl_stable, energy, 'b-')
306
ax.set_xlabel('Time $t$')
307
ax.set_ylabel('Total Energy')
308
ax.set_title('Energy Conservation')
309
ax.grid(True, alpha=0.3)
310
311
plt.tight_layout()
312
plt.savefig('wave_equation.pdf', dpi=150, bbox_inches='tight')
313
plt.close()
314
\end{pycode}
315
316
\begin{figure}[htbp]
317
\centering
318
\includegraphics[width=0.95\textwidth]{wave_equation.pdf}
319
\caption{Wave equation: (a) stable propagation, (b) unstable CFL violation, (c) space-time diagram, (d) energy conservation.}
320
\end{figure}
321
322
\chapter{Stability Analysis}
323
324
\section{Von Neumann Analysis}
325
Assume solution of form $u_j^n = G^n e^{ik j \Delta x}$
326
327
\textbf{Heat equation (explicit)}:
328
\begin{equation}
329
G = 1 - 4r\sin^2\left(\frac{k\Delta x}{2}\right)
330
\end{equation}
331
Stability: $|G| \leq 1 \Rightarrow r \leq \frac{1}{2}$
332
333
\textbf{Wave equation}:
334
\begin{equation}
335
G = 1 - 2s^2\sin^2\left(\frac{k\Delta x}{2}\right) \pm is\sin(k\Delta x)\sqrt{1 - s^2\sin^2\left(\frac{k\Delta x}{2}\right)}
336
\end{equation}
337
Stability: $|G| = 1$ when $s \leq 1$
338
339
\begin{pycode}
340
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
341
342
# Heat equation amplification factor
343
ax = axes[0]
344
k_dx = np.linspace(0, np.pi, 100)
345
for r in [0.25, 0.5, 0.6, 0.75]:
346
G = 1 - 4*r*np.sin(k_dx/2)**2
347
ax.plot(k_dx, np.abs(G), label=f'$r = {r}$')
348
ax.axhline(1, color='black', linestyle='--', alpha=0.5)
349
ax.set_xlabel('$k \\Delta x$')
350
ax.set_ylabel('$|G|$')
351
ax.set_title('Heat Equation Amplification Factor')
352
ax.legend()
353
ax.grid(True, alpha=0.3)
354
ax.set_ylim(-0.5, 2)
355
356
# Wave equation amplification factor
357
ax = axes[1]
358
for s in [0.5, 0.8, 1.0, 1.2]:
359
sin2 = np.sin(k_dx/2)**2
360
# For wave equation |G| = 1 when stable
361
G_mag = np.sqrt((1 - 2*s**2*sin2)**2 + s**2*np.sin(k_dx)**2*(1 - s**2*sin2))
362
ax.plot(k_dx, G_mag, label=f'$s = {s}$')
363
ax.axhline(1, color='black', linestyle='--', alpha=0.5)
364
ax.set_xlabel('$k \\Delta x$')
365
ax.set_ylabel('$|G|$')
366
ax.set_title('Wave Equation Amplification Factor')
367
ax.legend()
368
ax.grid(True, alpha=0.3)
369
370
plt.tight_layout()
371
plt.savefig('stability_analysis.pdf', dpi=150, bbox_inches='tight')
372
plt.close()
373
\end{pycode}
374
375
\begin{figure}[htbp]
376
\centering
377
\includegraphics[width=0.95\textwidth]{stability_analysis.pdf}
378
\caption{Von Neumann stability analysis: amplification factors for heat (left) and wave (right) equations.}
379
\end{figure}
380
381
\chapter{Numerical Results}
382
383
\begin{pycode}
384
stability_data = [
385
('Heat Explicit', '$r \\leq 0.5$', 'Conditional'),
386
('Heat Implicit', 'None', 'Unconditional'),
387
('Heat Crank-Nicolson', 'None', 'Unconditional'),
388
('Wave Explicit', '$s \\leq 1$', 'Conditional (CFL)'),
389
]
390
\end{pycode}
391
392
\begin{table}[htbp]
393
\centering
394
\caption{Stability conditions for finite difference schemes}
395
\begin{tabular}{@{}lll@{}}
396
\toprule
397
Scheme & Stability Condition & Type \\
398
\midrule
399
\py{stability_data[0][0]} & \py{stability_data[0][1]} & \py{stability_data[0][2]} \\
400
\py{stability_data[1][0]} & \py{stability_data[1][1]} & \py{stability_data[1][2]} \\
401
\py{stability_data[2][0]} & \py{stability_data[2][1]} & \py{stability_data[2][2]} \\
402
\py{stability_data[3][0]} & \py{stability_data[3][1]} & \py{stability_data[3][2]} \\
403
\bottomrule
404
\end{tabular}
405
\end{table}
406
407
\chapter{Conclusions}
408
409
\begin{enumerate}
410
\item Explicit schemes are simple but require stability constraints
411
\item Implicit schemes are unconditionally stable but require solving linear systems
412
\item CFL condition is essential for hyperbolic PDEs
413
\item Von Neumann analysis provides stability criteria
414
\item Choice of method depends on problem type and accuracy requirements
415
\end{enumerate}
416
417
\end{document}
418
419