<jupyter_start><jupyter_text>**Viscoelastic wave equation implementation on a staggered grid**This is a first attempt at implementing the viscoelastic wave equation as described in [1]. See also the FDELMODC implementation by <NAME> [2]. In the following example, a three dimensional toy problem will be introduced consisting of a single Ricker source located at (100, 50, 35) in a 200 m $\times$ 100 m $\times$ 100 *m* domain.<jupyter_code># Required imports:
import numpy as np
import sympy as sp

from devito import *
from examples.seismic.source import RickerSource, TimeAxis
from examples.seismic import ModelViscoelastic, plot_image<jupyter_output><empty_output><jupyter_text>The model domain is now constructed. It consists of an upper layer of water, 50 m in depth, and a lower rock layer separated by a 4 m thick sediment layer.<jupyter_code># Domain size:
extent = (200., 100., 100.) # 200 x 100 x 100 m domain
h = 1.0 # Desired grid spacing
shape = (int(extent[0]/h+1), int(extent[1]/h+1), int(extent[2]/h+1))

# Model physical parameters:
vp = np.zeros(shape)
qp = np.zeros(shape)
vs = np.zeros(shape)
qs = np.zeros(shape)
rho = np.zeros(shape)

# Set up three horizontally separated layers:
vp[:,:,:int(0.5*shape[2])+1] = 1.52
qp[:,:,:int(0.5*shape[2])+1] = 10000.
vs[:,:,:int(0.5*shape[2])+1] = 0.
qs[:,:,:int(0.5*shape[2])+1] = 0.
rho[:,:,:int(0.5*shape[2])+1] = 1.05

vp[:,:,int(0.5*shape[2])+1:int(0.5*shape[2])+1+int(4/h)] = 1.6
qp[:,:,int(0.5*shape[2])+1:int(0.5*shape[2])+1+int(4/h)] = 40.
vs[:,:,int(0.5*shape[2])+1:int(0.5*shape[2])+1+int(4/h)] = 0.4
qs[:,:,int(0.5*shape[2])+1:int(0.5*shape[2])+1+int(4/h)] = 30.
rho[:,:,int(0.5*shape[2])+1:int(0.5*shape[2])+1+int(4/h)] = 1.3

vp[:,:,int(0.5*shape[2])+1+int(4/h):] = 2.2
qp[:,:,int(0.5*shape[2])+1+int(4/h):] = 100.
vs[:,:,int(0.5*shape[2])+1+int(4/h):] = 1.2
qs[:,:,int(0.5*shape[2])+1+int(4/h):] = 70.
rho[:,:,int(0.5*shape[2])+1+int(4/h):] = 2.<jupyter_output><empty_output><jupyter_text>Now create a Devito vsicoelastic model generating an appropriate computational grid along with absorbing boundary layers:<jupyter_code># Create model
origin = (0, 0, 0)
spacing = (h, h, h)
so = 4 # FD space order (Note that the time order is by default 1).
nbl = 20 # Number of absorbing boundary layers cells
model = ModelViscoelastic(space_order=so, vp=vp, qp=qp, vs=vs, qs=qs,
                          rho=rho, origin=origin, shape=shape, spacing=spacing,
                          nbl=nbl)<jupyter_output><empty_output><jupyter_text>The source frequency is now set along with the required model parameters:<jupyter_code># Source freq. in MHz (note that the source is defined below):
f0 = 0.12

# Thorbecke's parameter notation
cp2 = model.vp*model.vp
cs2 = model.vs*model.vs
ro = 1./model.rho

mu = cs2*model.rho
l = cp2*model.rho - 2*mu
k = 1.0/(l + 2*mu)
pi = l + 2*mu

t_s = (sp.sqrt(1.+1./model.qp**2)-1./model.qp)/f0
t_ep = 1./(f0**2*t_s)
t_es = (1.+f0*model.qs*t_s)/(f0*model.qs-f0**2*t_s)
# Time step in ms and time range:
t0, tn = 0., 30.
dt = model.critical_dt
time_range = TimeAxis(start=t0, stop=tn, step=dt)<jupyter_output><empty_output><jupyter_text>Generate Devito time functions for the velocity, stress and memory variables appearing in the viscoelastic model equations. By default, the initial data of each field will be set to zero.<jupyter_code># PDE fn's:
x, y, z = model.grid.dimensions
damp = model.damp

# Staggered grid setup:

# Velocity terms:
vx= TimeFunction(name='vx', grid=model.grid, staggered=x, space_order=so)
vy= TimeFunction(name='vy', grid=model.grid, staggered=y, space_order=so)
vz = TimeFunction(name='vz', grid=model.grid, staggered=z, space_order=so)

# Stress terms:
sxx = TimeFunction(name='sxx', grid=model.grid, staggered=NODE, space_order=so)
syy = TimeFunction(name='syy', grid=model.grid, staggered=NODE, space_order=so)
szz = TimeFunction(name='szz', grid=model.grid, staggered=NODE, space_order=so)
sxz = TimeFunction(name='sxz', grid=model.grid, staggered=(x, z), space_order=so)
sxy = TimeFunction(name='sxy', grid=model.grid, staggered=(x, y), space_order=so)
syz = TimeFunction(name='syz', grid=model.grid, staggered=(y, z), space_order=so)

# Memory variables:
rxx = TimeFunction(name='rxx', grid=model.grid, staggered=NODE, space_order=so)
ryy = TimeFunction(name='ryy', grid=model.grid, staggered=NODE, space_order=so)
rzz = TimeFunction(name='rzz', grid=model.grid, staggered=NODE, space_order=so)
rxz = TimeFunction(name='rxz', grid=model.grid, staggered=(x, z), space_order=so)
rxy = TimeFunction(name='rxy', grid=model.grid, staggered=(x, y), space_order=so)
ryz = TimeFunction(name='ryz', grid=model.grid, staggered=(y, z), space_order=so)

s = model.grid.stepping_dim.spacing # Symbolic representation of the model grid spacing<jupyter_output><empty_output><jupyter_text>And now the source and PDE's are constructed:<jupyter_code># Source
src = RickerSource(name='src', grid=model.grid, f0=f0, time_range=time_range)
src.coordinates.data[:] = np.array([100., 50., 35.])

# The source injection term
src_xx = src.inject(field=sxx.forward, expr=src*s)
src_yy = src.inject(field=syy.forward, expr=src*s)
src_zz = src.inject(field=szz.forward, expr=src*s)

# The horizontal particle velocities (x, y) and the vertical particle velocity (z):
eq_vx = Eq(vx.forward, damp*vx + damp*ro*s*(sxx.dx + sxy.dy + sxz.dz))
eq_vy = Eq(vy.forward, damp*vy + damp*ro*s*(sxy.dx + syy.dy + syz.dz))
eq_vz = Eq(vz.forward, damp*vz + damp*ro*s*(sxz.dx + syz.dy + szz.dz))

# Horizontal stress equations:
eq_sxx = Eq(sxx.forward, damp*sxx \
    + damp*s*pi*t_ep/t_s*(vx.forward.dx+vy.forward.dy+vz.forward.dz) \
        - damp*2.*s*mu*t_es/t_s*(vy.forward.dy+vz.forward.dz) \
            + damp*s*rxx.forward)
eq_syy = Eq(syy.forward, damp*syy \
    + damp*s*pi*t_ep/t_s*(vx.forward.dx+vy.forward.dy+vz.forward.dz) \
        - damp*2.*s*mu*t_es/t_s*(vx.forward.dx+vz.forward.dz) \
            + damp*s*ryy.forward)
# Vertical stress equation:
eq_szz = Eq(szz.forward, damp*szz \
    + damp*s*pi*t_ep/t_s*(vx.forward.dx+vy.forward.dy+vz.forward.dz) \
        - damp*2.*s*mu*t_es/t_s*(vx.forward.dx+vy.forward.dy) \
            + damp*s*rzz.forward)
# Diagonal stress equations:
eq_sxy = Eq(sxy.forward, damp*sxy \
    + damp*s*mu*t_es/t_s*(vx.forward.dy+vy.forward.dx) + damp*s*rxy.forward)
eq_sxz = Eq(sxz.forward, damp*sxz \
    + damp*s*mu*t_es/t_s*(vx.forward.dz+vz.forward.dx) + damp*s*rxz.forward)
eq_syz = Eq(syz.forward, damp*syz \
    + damp*s*mu*t_es/t_s*(vy.forward.dz+vz.forward.dy) + damp*s*ryz.forward)

# Horizontal memory variable equations:
eq_rxx = Eq(rxx.forward, damp*rxx \
    - damp*s*1./t_s*(rxx+pi*(t_ep/t_s-1)*(vx.forward.dx+vy.forward.dy+vz.forward.dz) \
        - 2*mu*(t_es/t_s-1)*(vz.forward.dz+vy.forward.dy)))
eq_ryy = Eq(ryy.forward, damp*ryy \
    - damp*s*1./t_s*(ryy+pi*(t_ep/t_s-1)*(vx.forward.dx+vy.forward.dy+vz.forward.dz) \
        - 2*mu*(t_es/t_s-1)*(vx.forward.dx+vy.forward.dy)))
# Vertical memory variable equation:
eq_rzz = Eq(rzz.forward, damp*rzz \
    - damp*s*1./t_s*(rzz+pi*(t_ep/t_s-1)*(vx.forward.dx+vy.forward.dy+vz.forward.dz) \
        - 2*mu*(t_es/t_s-1)*(vx.forward.dx+vy.forward.dy)))
# Diagonal memory variable equations:
eq_rxy = Eq(rxy.forward, damp*rxy \
    - damp*s*1/t_s*(rxy+mu*(t_es/t_s-1)*(vx.forward.dy+vy.forward.dx)))
eq_rxz = Eq(rxz.forward, damp*rxz \
    - damp*s*1/t_s*(rxz+mu*(t_es/t_s-1)*(vx.forward.dz+vz.forward.dx)))
eq_ryz = Eq(ryz.forward, ryz \
    - damp*s*1/t_s*(ryz+mu*(t_es/t_s-1)*(vy.forward.dz+vz.forward.dy)))<jupyter_output><empty_output><jupyter_text>We now create and then run the operator:<jupyter_code># Create the operator:
op = Operator([eq_vx, eq_vy, eq_vz, eq_rxx, eq_ryy, eq_rzz, eq_rxy, eq_rxz, eq_ryz,
               eq_sxx, eq_syy, eq_szz, eq_sxy, eq_sxz, eq_syz] + src_xx + src_yy + src_zz,
               subs=model.spacing_map)
#NBVAL_IGNORE_OUTPUT

# Execute the operator:
op(dt=dt)<jupyter_output>Operator `Kernel` run in 36.88 s<jupyter_text>Before plotting some results, let us first look at the shape of the data stored in one of our time functions:<jupyter_code>vx.data.shape<jupyter_output><empty_output><jupyter_text>Since our functions are first order in time, the time dimension is of length 2. The spatial extent of the data includes the absorbing boundary layers in each dimension (i.e. each spatial dimension is padded by 20 grid points to the left and to the right).The total number of instances in time considered is obtained from:<jupyter_code>time_range.num<jupyter_output><empty_output><jupyter_text>Hence 223 time steps were executed. Thus the final time step will be stored in index given by:<jupyter_code>np.mod(223,2)<jupyter_output><empty_output><jupyter_text>Now, let us plot some 2D slices of the fields `vx` and `szz` at the final time step:<jupyter_code>#NBVAL_SKIP

# Mid-points:
mid_x = int(0.5*(vx.data.shape[1]-1))+1
mid_y = int(0.5*(vx.data.shape[2]-1))+1

# Plot some selected results:
plot_image(vx.data[1, :, mid_y, :], cmap="seismic")
plot_image(vx.data[1, mid_x, :, :], cmap="seismic")

plot_image(szz.data[1, :, mid_y, :], cmap="seismic")
plot_image(szz.data[1, mid_x, :, :], cmap="seismic")
#NBVAL_IGNORE_OUTPUT

assert np.isclose(np.linalg.norm(vx.data[1]), 0.072813906, atol=1e-4, rtol=0)<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.17378180250107805, 'fraction_numerical': 0.03040103492884864, 'mean_word_length': 5.063398692810457, 'pattern_counts': {'":': 0, '<': 45, '<?xml version=': 0, '>': 45, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '11', 'dir': 'jupyter-structured-clean-dedup', 'id': '249589', 'license': 'MIT', 'n_tokens_mistral': 3994, 'n_tokens_neox': 3783, 'n_words': 766, 'path': 'examples/seismic/tutorials/09_viscoelastic.ipynb', 'repo_name': 'beterraba/devito', 'size': '9283'}