|
| 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