1515
1616The most direct approach adds equations that set boundary values:
1717
18- ``` python
19- from devito import Grid, TimeFunction, Eq, Operator
20-
21- grid = Grid(shape = (101 ,), extent = (1.0 ,))
22- u = TimeFunction(name = ' u' , grid = grid, time_order = 2 , space_order = 2 )
23-
24- # Get the time dimension for indexing
25- t = grid.stepping_dim
26-
27- # Interior update (wave equation)
28- update = Eq(u.forward, 2 * u - u.backward + dt** 2 * c** 2 * u.dx2)
29-
30- # Boundary conditions: u = 0 at both ends
31- bc_left = Eq(u[t+ 1 , 0 ], 0 )
32- bc_right = Eq(u[t+ 1 , 100 ], 0 )
33-
34- # Include all equations in the operator
35- op = Operator([update, bc_left, bc_right])
36- ```
18+ {{< include snippets/boundary_dirichlet_wave.qmd >}}
3719
3820** Method 2: Using subdomain**
3921
40- For interior-only updates, use ` subdomain=grid.interior ` :
41-
42- ``` python
43- # Update only interior points (automatically excludes boundaries)
44- update = Eq(u.forward, 2 * u - u.backward + dt** 2 * c** 2 * u.dx2,
45- subdomain = grid.interior)
46-
47- # Set boundaries explicitly
48- bc_left = Eq(u[t+ 1 , 0 ], 0 )
49- bc_right = Eq(u[t+ 1 , 100 ], 0 )
50-
51- op = Operator([update, bc_left, bc_right])
52- ```
22+ The snippet above already uses ` subdomain=grid.interior ` to keep the interior PDE update separate from boundary treatment.
5323
5424The ` subdomain=grid.interior ` approach is often cleaner because it
5525explicitly separates the physics (interior PDE) from the boundary treatment.
7141
7242This gives $u_ {-1} = u_1$, which we substitute into the interior equation:
7343
74- ``` python
75- grid = Grid(shape = (101 ,), extent = (1.0 ,))
76- u = TimeFunction(name = ' u' , grid = grid, time_order = 1 , space_order = 2 )
77- x = grid.dimensions[0 ]
78- t = grid.stepping_dim
79-
80- # Interior update (diffusion equation)
81- update = Eq(u.forward, u + alpha * dt * u.dx2, subdomain = grid.interior)
82-
83- # Neumann BC at left (du/dx = 0): use one-sided update
84- # u_new[0] = u[0] + alpha*dt * 2*(u[1] - u[0])/dx^2
85- dx = grid.spacing[0 ]
86- bc_left = Eq(u[t+ 1 , 0 ], u[t, 0 ] + alpha * dt * 2 * (u[t, 1 ] - u[t, 0 ]) / dx** 2 )
87-
88- # Neumann BC at right (du/dx = 0)
89- bc_right = Eq(u[t+ 1 , 100 ], u[t, 100 ] + alpha * dt * 2 * (u[t, 99 ] - u[t, 100 ]) / dx** 2 )
90-
91- op = Operator([update, bc_left, bc_right])
92- ```
44+ {{< include snippets/neumann_bc_diffusion_1d.qmd >}}
9345
9446### Mixed Boundary Conditions
9547
9648Often we have different conditions on different boundaries:
9749
98- ``` python
99- # Dirichlet on left, Neumann on right
100- bc_left = Eq(u[t+ 1 , 0 ], 0 ) # u(0,t) = 0
101- bc_right = Eq(u[t+ 1 , 100 ], u[t+ 1 , 99 ]) # du/dx(L,t) = 0 (copy from interior)
102-
103- op = Operator([update, bc_left, bc_right])
104- ```
50+ {{< include snippets/mixed_bc_diffusion_1d.qmd >}}
10551
10652### 2D Boundary Conditions
10753
10854For 2D problems, boundary conditions apply to all four edges:
10955
110- ``` python
111- grid = Grid(shape = (101 , 101 ), extent = (1.0 , 1.0 ))
112- u = TimeFunction(name = ' u' , grid = grid, time_order = 2 , space_order = 2 )
113-
114- x, y = grid.dimensions
115- t = grid.stepping_dim
116- Nx, Ny = 100 , 100
117-
118- # Interior update
119- update = Eq(u.forward, 2 * u - u.backward + dt** 2 * c** 2 * u.laplace,
120- subdomain = grid.interior)
121-
122- # Dirichlet BCs on all four edges
123- bc_left = Eq(u[t+ 1 , 0 , y], 0 )
124- bc_right = Eq(u[t+ 1 , Nx, y], 0 )
125- bc_bottom = Eq(u[t+ 1 , x, 0 ], 0 )
126- bc_top = Eq(u[t+ 1 , x, Ny], 0 )
127-
128- op = Operator([update, bc_left, bc_right, bc_bottom, bc_top])
129- ```
56+ {{< include snippets/bc_2d_dirichlet_wave.qmd >}}
13057
13158### Time-Dependent Boundary Conditions
13259
13360For boundaries that vary in time, use the time index:
13461
135- ``` python
136- from devito import Constant
137-
138- # Time-varying amplitude
139- A = Constant(name = ' A' )
140-
141- # Sinusoidal forcing at left boundary
142- # u(0, t) = A * sin(omega * t)
143- import sympy as sp
144- omega = 2 * sp.pi # Angular frequency
145-
146- # The time value at step n
147- t_val = t * dt # Symbolic time value
148-
149- bc_left = Eq(u[t+ 1 , 0 ], A * sp.sin(omega * t_val))
150-
151- # Set the amplitude before running
152- op = Operator([update, bc_left, bc_right])
153- op(time = Nt, dt = dt, A = 1.0 ) # Pass A as keyword argument
154- ```
62+ {{< include snippets/time_dependent_bc_sine.qmd >}}
15563
15664### Absorbing Boundary Conditions
15765
16371
16472This can be discretized as:
16573
166- ``` python
167- # Absorbing BC at right boundary (waves traveling right)
168- dx = grid.spacing[0 ]
169- bc_right_absorbing = Eq(
170- u[t+ 1 , Nx],
171- u[t, Nx] - c * dt / dx * (u[t, Nx] - u[t, Nx- 1 ])
172- )
173- ```
74+ {{< include snippets/absorbing_bc_right_wave.qmd >}}
17475
17576More sophisticated absorbing conditions use damping layers (sponges)
17677near the boundaries. This is covered in detail in @sec-wave-1d-absorbing .
18586Devito doesn't directly support periodic BCs, but they can be implemented
18687by copying values:
18788
188- ``` python
189- # Periodic BCs: u[0] = u[Nx-1], u[Nx] = u[1]
190- bc_periodic_left = Eq(u[t+ 1 , 0 ], u[t+ 1 , Nx- 1 ])
191- bc_periodic_right = Eq(u[t+ 1 , Nx], u[t+ 1 , 1 ])
192- ```
89+ {{< include snippets/periodic_bc_advection_1d.qmd >}}
19390
19491Note: The order of equations matters. Update the interior first, then
19592copy for periodicity.
@@ -212,42 +109,7 @@ copy for periodicity.
212109
213110Here's a complete example combining interior updates with boundary conditions:
214111
215- ``` python
216- from devito import Grid, TimeFunction, Eq, Operator
217- import numpy as np
218-
219- # Setup
220- L, c, T = 1.0 , 1.0 , 2.0
221- Nx = 100
222- C = 0.9 # Courant number
223- dx = L / Nx
224- dt = C * dx / c
225- Nt = int (T / dt)
226-
227- # Grid and field
228- grid = Grid(shape = (Nx + 1 ,), extent = (L,))
229- u = TimeFunction(name = ' u' , grid = grid, time_order = 2 , space_order = 2 )
230- t = grid.stepping_dim
231-
232- # Initial condition: plucked string
233- x_vals = np.linspace(0 , L, Nx + 1 )
234- u.data[0 , :] = np.sin(np.pi * x_vals)
235- u.data[1 , :] = u.data[0 , :] # Zero initial velocity
236-
237- # Equations
238- update = Eq(u.forward, 2 * u - u.backward + (c* dt)** 2 * u.dx2,
239- subdomain = grid.interior)
240- bc_left = Eq(u[t+ 1 , 0 ], 0 )
241- bc_right = Eq(u[t+ 1 , Nx], 0 )
242-
243- # Solve
244- op = Operator([update, bc_left, bc_right])
245- op(time = Nt, dt = dt)
246-
247- # Verify: solution should return to initial shape at t = 2L/c
248- print (f " Initial max: { np.max(u.data[1 , :]):.6f } " )
249- print (f " Final max: { np.max(u.data[0 , :]):.6f } " )
250- ```
112+ {{< include snippets/boundary_dirichlet_wave.qmd >}}
251113
252114For a string with fixed ends and initial shape $\sin(\pi x)$, the solution
253115oscillates with period $2L/c$. After one period, it should return to the
0 commit comments