Skip to content

Commit 16c942b

Browse files
committed
Add Devito-focused wave equation chapter content
Phase 2 chapter content for wave equations using Devito DSL: - wave1D_devito.qmd: Core 1D solver with Grid, TimeFunction, Operator - Symbolic derivatives (.dt2, .dx2) - Boundary conditions using Eq - Verification with standing wave solution - wave1D_features.qmd: Source terms and variable coefficients - Ricker and Gaussian source wavelets - Point sources with SparseTimeFunction - Variable velocity fields - Absorbing boundary conditions - wave2D_devito.qmd: 2D extension using .laplace - Dimension-agnostic Laplacian - 2D CFL stability condition - Visualization with surface/contour plots - wave_exercises.qmd: 10 Devito-based exercises with solutions - Standing waves, convergence rates, guitar string - Source wavelets, 2D propagation, interface reflections - Manufactured solutions, energy conservation, dispersion Updated index.qmd to include new sections.
1 parent 0df3015 commit 16c942b

6 files changed

Lines changed: 1254 additions & 3 deletions

File tree

chapters/wave/index.qmd

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
{{< include wave1D_fd1.qmd >}}
44

5+
{{< include wave1D_devito.qmd >}}
6+
7+
{{< include wave1D_features.qmd >}}
8+
59
{{< include wave1D_prog.qmd >}}
610

711
{{< include wave1D_fd2.qmd >}}
@@ -10,8 +14,12 @@
1014

1115
{{< include wave2D_fd.qmd >}}
1216

17+
{{< include wave2D_devito.qmd >}}
18+
1319
{{< include wave2D_prog.qmd >}}
1420

1521
{{< include wave_app.qmd >}}
1622

1723
{{< include wave_app_exer.qmd >}}
24+
25+
{{< include wave_exercises.qmd >}}

chapters/wave/wave1D_devito.qmd

Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
## Solving the Wave Equation with Devito {#sec-wave-devito}
2+
3+
In this section we demonstrate how to solve the wave equation using the
4+
Devito domain-specific language (DSL). Devito allows us to write the
5+
PDE symbolically and generates optimized C code automatically.
6+
7+
### From Mathematics to Devito Code
8+
9+
Recall the 1D wave equation from @sec-wave-string:
10+
$$
11+
\frac{\partial^2 u}{\partial t^2} = c^2 \frac{\partial^2 u}{\partial x^2},
12+
\quad x \in (0, L),\ t \in (0, T]
13+
$$ {#eq-wave-devito-pde}
14+
with initial conditions $u(x, 0) = I(x)$ and $\partial u/\partial t|_{t=0} = V(x)$,
15+
and boundary conditions $u(0, t) = u(L, t) = 0$.
16+
17+
In Devito, we express this PDE directly using symbolic derivatives.
18+
The key abstractions are:
19+
20+
- **Grid**: Defines the discrete domain
21+
- **TimeFunction**: A field that varies in both space and time
22+
- **Eq**: An equation relating symbolic expressions
23+
- **Operator**: Compiles equations to optimized C code
24+
25+
### The Devito Grid
26+
27+
A Devito `Grid` defines the discrete spatial domain:
28+
29+
```python
30+
from devito import Grid
31+
32+
L = 1.0 # Domain length
33+
Nx = 100 # Number of grid intervals
34+
35+
grid = Grid(shape=(Nx + 1,), extent=(L,))
36+
```
37+
38+
The `shape` is the number of grid points (including boundaries),
39+
and `extent` is the physical size of the domain.
40+
41+
### TimeFunction for the Wave Field
42+
43+
The solution $u(x, t)$ is represented by a `TimeFunction`:
44+
45+
```python
46+
from devito import TimeFunction
47+
48+
u = TimeFunction(name='u', grid=grid, time_order=2, space_order=2)
49+
```
50+
51+
The key parameters are:
52+
53+
- `time_order=2`: We need $u^{n+1}$, $u^n$, $u^{n-1}$ for the wave equation
54+
- `space_order=2`: Central difference with second-order accuracy
55+
56+
### Symbolic Derivatives
57+
58+
Devito provides symbolic access to derivatives through attribute notation:
59+
60+
| Derivative | Devito syntax | Mathematical meaning |
61+
|------------|---------------|---------------------|
62+
| First time | `u.dt` | $\partial u/\partial t$ |
63+
| Second time | `u.dt2` | $\partial^2 u/\partial t^2$ |
64+
| First space | `u.dx` | $\partial u/\partial x$ |
65+
| Second space | `u.dx2` | $\partial^2 u/\partial x^2$ |
66+
67+
### Formulating the PDE
68+
69+
We express the wave equation as a residual that should be zero:
70+
71+
```python
72+
from devito import Eq, solve, Constant
73+
74+
c_sq = Constant(name='c_sq') # Wave speed squared
75+
76+
# PDE: u_tt - c^2 * u_xx = 0
77+
pde = u.dt2 - c_sq * u.dx2
78+
```
79+
80+
The `solve` function isolates the unknown $u^{n+1}$:
81+
82+
```python
83+
stencil = Eq(u.forward, solve(pde, u.forward))
84+
```
85+
86+
Here `u.forward` represents $u^{n+1}$, the solution at the next time level.
87+
88+
### Boundary Conditions
89+
90+
For Dirichlet conditions $u(0, t) = u(L, t) = 0$, we add explicit equations:
91+
92+
```python
93+
t_dim = grid.stepping_dim # Time index dimension
94+
95+
bc_left = Eq(u[t_dim + 1, 0], 0)
96+
bc_right = Eq(u[t_dim + 1, Nx], 0)
97+
```
98+
99+
### Creating and Running the Operator
100+
101+
The `Operator` compiles all equations into optimized code:
102+
103+
```python
104+
from devito import Operator
105+
106+
op = Operator([stencil, bc_left, bc_right])
107+
```
108+
109+
To execute a time step, we call:
110+
111+
```python
112+
op.apply(time_m=1, time_M=1, dt=dt, c_sq=c**2)
113+
```
114+
115+
### Complete Solver Implementation
116+
117+
The module `src.wave` provides a complete solver that handles:
118+
119+
- Initial conditions with velocity ($u_t(x, 0) = V(x)$)
120+
- CFL stability checking
121+
- Optional history storage
122+
123+
```python
124+
from src.wave import solve_wave_1d
125+
import numpy as np
126+
127+
# Define initial condition: plucked string
128+
def I(x):
129+
return np.sin(np.pi * x)
130+
131+
# Solve
132+
result = solve_wave_1d(
133+
L=1.0, # Domain length
134+
c=1.0, # Wave speed
135+
Nx=100, # Grid points
136+
T=1.0, # Final time
137+
C=0.9, # Courant number
138+
I=I, # Initial displacement
139+
)
140+
141+
# Access results
142+
u_final = result.u # Solution at final time
143+
x = result.x # Spatial grid
144+
```
145+
146+
### The Courant Number and Stability
147+
148+
The Courant number $C = c \Delta t / \Delta x$ determines stability.
149+
For the explicit wave equation solver, we require $C \le 1$.
150+
151+
When $C = 1$ (the magic value), the numerical solution is **exact**
152+
for waves traveling in either direction. This is because the
153+
domain of dependence of the numerical scheme exactly matches
154+
the physical domain of dependence.
155+
156+
### Handling Initial Velocity
157+
158+
The first time step requires special treatment when $V(x) \ne 0$.
159+
Using the Taylor expansion:
160+
$$
161+
u^1 = u^0 + \Delta t \cdot V(x) + \frac{1}{2} \Delta t^2 c^2 u_{xx}^0
162+
$$
163+
164+
The solver implements this as:
165+
166+
```python
167+
u0 = I(x_coords)
168+
v0 = V(x_coords)
169+
u_xx_0 = np.zeros_like(u0)
170+
u_xx_0[1:-1] = (u0[2:] - 2*u0[1:-1] + u0[:-2]) / dx**2
171+
172+
u1 = u0 + dt * v0 + 0.5 * dt**2 * c**2 * u_xx_0
173+
```
174+
175+
### Verification: Standing Wave Solution
176+
177+
The standing wave with $I(x) = A \sin(\pi x / L)$ and $V = 0$ has
178+
the exact solution:
179+
$$
180+
u(x, t) = A \sin\left(\frac{\pi x}{L}\right) \cos\left(\frac{\pi c t}{L}\right)
181+
$$
182+
183+
We can verify our implementation converges at the expected rate:
184+
185+
```python
186+
from src.wave import convergence_test_wave_1d
187+
188+
grid_sizes, errors, rate = convergence_test_wave_1d(
189+
grid_sizes=[20, 40, 80, 160],
190+
T=0.5,
191+
C=0.9,
192+
)
193+
194+
print(f"Observed convergence rate: {rate:.2f}") # Should be ~2.0
195+
```
196+
197+
### Visualization
198+
199+
For time-dependent problems, animation is essential. With the
200+
history saved, we can create animations:
201+
202+
```python
203+
import matplotlib.pyplot as plt
204+
from matplotlib.animation import FuncAnimation
205+
206+
result = solve_wave_1d(
207+
L=1.0, c=1.0, Nx=100, T=2.0, C=0.9,
208+
save_history=True,
209+
)
210+
211+
fig, ax = plt.subplots()
212+
line, = ax.plot(result.x, result.u_history[0])
213+
ax.set_ylim(-1.2, 1.2)
214+
ax.set_xlabel('x')
215+
ax.set_ylabel('u')
216+
217+
def update(frame):
218+
line.set_ydata(result.u_history[frame])
219+
ax.set_title(f't = {result.t_history[frame]:.3f}')
220+
return line,
221+
222+
anim = FuncAnimation(fig, update, frames=len(result.t_history),
223+
interval=50, blit=True)
224+
```
225+
226+
### Summary: Devito vs. NumPy
227+
228+
The key advantages of using Devito for wave equations:
229+
230+
1. **Symbolic PDEs**: Write the math, not the stencils
231+
2. **Automatic optimization**: Cache-efficient loops generated automatically
232+
3. **Parallelization**: OpenMP/MPI/GPU support without code changes
233+
4. **Dimension-agnostic**: Same code pattern works for 1D, 2D, 3D
234+
235+
The explicit time-stepping loop remains visible to the user for
236+
educational purposes, but Devito handles the spatial discretization
237+
and can generate highly optimized code for the inner loop.

0 commit comments

Comments
 (0)