Skip to content

Commit 4a14987

Browse files
committed
examples: Add a 2D acoustic superstep example for layered and Marmousi
1 parent 6f7dffe commit 4a14987

1 file changed

Lines changed: 251 additions & 0 deletions

File tree

Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
''' Script that demonstrates the functionality of the superstep in 2D
2+
Acoustic wave equation with source injection
3+
'''
4+
import shutil
5+
import urllib.request
6+
from argparse import ArgumentParser
7+
from collections.abc import Callable
8+
from dataclasses import dataclass
9+
from pathlib import Path
10+
11+
import matplotlib.pyplot as plt
12+
import numpy as np
13+
from devito import (
14+
ConditionalDimension,
15+
Eq,
16+
Function,
17+
Grid,
18+
Operator,
19+
SparseTimeFunction,
20+
TimeFunction,
21+
solve,
22+
)
23+
from devito.timestepping.superstep import superstep_generator
24+
from scipy.interpolate import interpn
25+
26+
27+
@dataclass
28+
class model:
29+
name: str
30+
velocity: Callable
31+
# Spatial Domain
32+
shape: tuple[int]
33+
origin: tuple[float]
34+
extent: tuple[float]
35+
# Souce Location
36+
source: tuple[float]
37+
# Time Domain
38+
t0: float
39+
t1: float
40+
t2: float
41+
critical_dt: float
42+
# Plotting
43+
zlim: float
44+
45+
46+
def layered_velocity(grid, step=0):
47+
velocity = Function(name="layered", grid=grid, space_order=(2, step, step))
48+
q = grid.shape[1]//4
49+
velocity.data[:] = 1500
50+
velocity.data[:, q:2*q] = 2000
51+
velocity.data[:, 2*q:3*q] = 2500
52+
velocity.data[:, 3*q:] = 3000
53+
return velocity
54+
55+
def marmousi(grid, step=0):
56+
# Grab dataset from pwd or download
57+
url = 'https://github.com/devitocodes/data/raw/refs/heads/master/Simple2D/vp_marmousi_bi' # noqa: E501
58+
filename = Path('marmousi.np')
59+
shape = (1601, 401)
60+
if not filename.exists():
61+
with urllib.request.urlopen(url) as response, open(filename, 'wb') as fh:
62+
shutil.copyfileobj(response, fh)
63+
data = np.fromfile(filename, dtype=np.float32, sep='').reshape(*shape)
64+
cropped = data[650:1051, 35:]
65+
66+
xs = np.linspace(0, 1, cropped.shape[0])
67+
ys = np.linspace(0, 1, cropped.shape[1])
68+
69+
xd = np.linspace(0, 1, grid.shape[0])
70+
yd = np.linspace(0, 1, grid.shape[1])
71+
72+
velocity = Function(name="marmousi", grid=grid, space_order=(2, step, step))
73+
velocity.data[:] = 1000*interpn(
74+
(xs, ys), cropped, np.meshgrid(xd, yd), method='nearest'
75+
).T
76+
return velocity
77+
78+
79+
def ricker(t, f=10, A=1):
80+
'''The Ricker wavelet
81+
f - freq in Hz
82+
A - amplitude
83+
'''
84+
trm = (np.pi * f * (t - 1 / f)) ** 2
85+
return A * (1 - 2 * trm) * np.exp(-trm)
86+
87+
88+
def acoustic_model(model, step=1, snapshots=1):
89+
# Construct 2D Grid
90+
grid = Grid(shape=model.shape, extent=model.extent)
91+
x, y = grid.dimensions
92+
93+
t0, t1, t2 = model.t0, model.t1, model.t2
94+
95+
velocity = model.velocity(grid, step)
96+
u = TimeFunction(name="u", grid=grid, time_order=2, space_order=2)
97+
98+
pde = (1/velocity**2)*u.dt2 - u.laplace # + damp*u.dt
99+
stencil = Eq(u.forward, solve(pde, u.forward))
100+
101+
tn1 = int(np.ceil((t1 - t0)/model.critical_dt))
102+
dt = (t1 - t0)/tn1
103+
104+
# Source
105+
t = np.linspace(t0, t1, tn1)
106+
rick = ricker(t)
107+
source = SparseTimeFunction(
108+
name="ricker", npoint=1, coordinates=[model.source], nt=tn1, grid=grid,
109+
time_order=2, space_order=4
110+
)
111+
source.data[:, 0] = rick
112+
src_term = source.inject(field=u.forward, expr=source*velocity*velocity*dt*dt)
113+
114+
op1 = Operator([stencil] + src_term)
115+
op1(time=tn1 - 1, dt=dt)
116+
117+
# Stencil and operator
118+
idx = tn1 % 3
119+
if step == 1:
120+
# Non-superstep case
121+
new_u = TimeFunction(name="new_u", grid=grid, time_order=2, space_order=2)
122+
stencil = [stencil.subs(
123+
{u.forward: new_u.forward, u: new_u, u.backward: new_u.backward}
124+
)]
125+
new_u.data[0, :] = u.data[idx - 2]
126+
new_u.data[1, :] = u.data[idx - 1]
127+
new_u.data[2, :] = u.data[idx]
128+
else:
129+
new_u, new_u_p, *stencil = superstep_generator(u, stencil.rhs, step)
130+
131+
new_u.data[0, :] = u.data[idx - 1]
132+
new_u.data[1, :] = u.data[idx]
133+
new_u_p.data[0, :] = u.data[idx - 2]
134+
new_u_p.data[1, :] = u.data[idx - 1]
135+
136+
tn2 = int(np.ceil((t2 - t1)/model.critical_dt))
137+
dt = (t2 - t1)/tn2
138+
139+
# Snapshot the solution
140+
factor = int(np.ceil(tn2/(snapshots + 1)))
141+
t_sub = ConditionalDimension('t_sub', parent=grid.time_dim, factor=factor)
142+
u_save = TimeFunction(
143+
name='usave', grid=grid,
144+
time_order=0, space_order=2,
145+
save=snapshots//step + 1, time_dim=t_sub
146+
)
147+
save = Eq(u_save, new_u)
148+
149+
op = Operator([*stencil, save])
150+
op(dt=dt)
151+
152+
if step == 1:
153+
u_save.data[0, :, :] = u.data[idx]
154+
155+
return u_save.data
156+
157+
158+
layered_model = model(
159+
name='layered',
160+
velocity=layered_velocity,
161+
shape = (101, 101),
162+
origin = (0., 0.),
163+
extent = (1000, 1000), # 1kmx1km
164+
source = (500, 20),
165+
t0 = 0,
166+
t1 = 0.2,
167+
t2 = 0.65,
168+
critical_dt = 0.002357,
169+
zlim = 30
170+
)
171+
172+
marmousi_model = model(
173+
name='marmousi',
174+
velocity=marmousi,
175+
shape = (274, 301),
176+
origin = (4875., 262.5),
177+
extent = (3000, 2737.5), # 3kmx2.7km
178+
source = (1000, 1200),
179+
t0 = 0,
180+
t1 = 0.2,
181+
t2 = 0.5,
182+
critical_dt = 0.0013728,
183+
zlim = 20
184+
)
185+
186+
187+
if __name__ == '__main__':
188+
parser = ArgumentParser()
189+
parser.add_argument('--model', default='layered', choices=['layered', 'marmousi'])
190+
args = parser.parse_args()
191+
192+
model = layered_model if args.model == 'layered' else marmousi_model
193+
194+
# Supersteps
195+
k = [1, 4]
196+
# Snapshots
197+
m = 13
198+
fig, axes = plt.subplots(len(k), m)
199+
200+
# Velocity model
201+
grid = Grid(shape=model.shape, extent=model.extent)
202+
v = model.velocity(grid)
203+
plot_extent = [
204+
model.origin[0],
205+
model.origin[0] + model.extent[0],
206+
model.origin[1] + model.extent[1],
207+
model.origin[1]
208+
]
209+
210+
for step, ax_row in zip(k, axes, strict=True):
211+
data = acoustic_model(model, step=step, snapshots=m)
212+
time = np.linspace(model.t1, model.t2, (m - 1)//step + 1)
213+
idx = 0
214+
for ii, ax in enumerate(ax_row):
215+
if ii % step == 0:
216+
ax.imshow(
217+
data[idx, :, :].T,
218+
extent=plot_extent,
219+
vmin=-model.zlim, vmax=model.zlim,
220+
cmap='seismic'
221+
)
222+
ax.imshow(v.data.T, cmap='grey', extent=plot_extent, alpha=0.2)
223+
ax.set_title(f't = {time[idx]:0.3f}')
224+
idx += 1
225+
if ii > 0:
226+
ax.set_xticks([])
227+
ax.set_yticks([])
228+
else:
229+
xticks = ax.get_xticks()
230+
ax.set_xticks(np.array((
231+
model.origin[0],
232+
round(model.origin[0] + model.extent[0]/2, -3),
233+
model.origin[0] + model.extent[0]
234+
)))
235+
ax.set_xlim(model.origin[0], model.origin[0] + model.extent[0])
236+
yticks = ax.get_yticks()
237+
ax.set_yticks(np.concat(((model.origin[1],), yticks[2::2])))
238+
ax.set_ylim(model.origin[1] + model.extent[1], model.origin[1])
239+
else:
240+
ax.remove()
241+
242+
fig.set_size_inches(16, 3.5)
243+
fig.subplots_adjust(
244+
left=0.05,
245+
bottom=0.025,
246+
right=0.99,
247+
top=0.97,
248+
wspace=0.06,
249+
hspace=0.06
250+
)
251+
fig.savefig(f'{model.name}.png', dpi=300)

0 commit comments

Comments
 (0)