#!/usr/bin/env python
# coding: utf-8
# # 4 - Hybdrid Absorbing Boundary Condition (HABC)
# # 4.1 - Introduction
#
# In this notebook we describe absorbing boundary conditions and their use combined with the *Hybdrid Absorbing Boundary Condition* (*HABC*). The common points to the previous notebooks Introduction to Acoustic Problem, Damping and PML will be used here, with brief descriptions.
# # 4.2 - Absorbing Boundary Conditions
#
# We initially describe absorbing boundary conditions, the so called A1 and A2 Clayton's conditions and
# the scheme from Higdon. These methods can be used as pure boundary conditions, designed to reduce reflections,
# or as part of the Hybrid Absorbing Boundary Condition, in which they are combined with an absorption layer in a manner to be described ahead.
#
# In the presentation of these boundary conditions we initially consider the wave equation to be solved on
# the spatial domain $\Omega=\left[x_{I},x_{F}\right] \times\left[z_{I},z_{F}\right]$ as show in the figure bellow. More details about the equation and domain definition can be found in the Introduction to Acoustic Problem notebook.
#
#
#
# ## 4.2.1 - Clayton's A1 Boundary Condition
#
# Clayton's A1 boundary condition is based on a one way wave equation (OWWE). This simple condition
# is such that outgoing waves normal to the border would leave without reflection. At the $\partial \Omega_1$ part of the boundary
# we have,
#
# - $\displaystyle\frac{\partial u(x,z,t)}{\partial t}-c(x,z)\displaystyle\frac{\partial u(x,z,t)}{\partial x}=0.$
#
# while at $\partial \Omega_3$ the condition is
#
# - $\displaystyle\frac{\partial u(x,z,t)}{\partial t}+c(x,z)\displaystyle\frac{\partial u(x,z,t)}{\partial x}=0.$
#
# and at $\partial \Omega_2$
#
# - $\displaystyle\frac{\partial u(x,z,t)}{\partial t}+c(x,z)\displaystyle\frac{\partial u(x,z,t)}{\partial z}=0.$
#
# ## 4.2.2 - Clayton's A2 Boundary Condition
#
# The A2 boundary condition aims to impose a boundary condition that would make outgoing waves leave the domain without being reflected. This condition is approximated (using a Padé approximation in the wave dispersion relation) by the following equation to be imposed on the boundary part $\partial \Omega_1$
#
# - $\displaystyle\frac{\partial^{2} u(x,z,t)}{\partial t^{2}}+c(x,z)\displaystyle\frac{\partial^{2} u(x,z,t)}{\partial x \partial t}+\frac{c^2(x,z)}{2}\displaystyle\frac{\partial^{2} u(x,z,t)}{\partial z^{2}}=0.$
#
# At $\partial \Omega_3$ we have
#
# - $\displaystyle\frac{\partial^{2} u(x,z,t)}{\partial t^{2}}-c(x,z)\displaystyle\frac{\partial^{2} u(x,z,t)}{\partial z \partial t}+\frac{c^2(x,z)}{2}\displaystyle\frac{\partial^{2} u(x,z,t)}{\partial x^{2}}=0.$
#
# while at $\partial \Omega_2$ the condition is
#
# - $\displaystyle\frac{\partial^{2} u(x,z,t)}{\partial t^{2}}-c(x,z)\displaystyle\frac{\partial^{2} u(x,z,t)}{\partial x \partial t}+\frac{c^2(x,z)}{2}\displaystyle\frac{\partial^{2} u(x,z,t)}{\partial z^{2}}=0.$
#
# At the corner points the condition is
#
# - $\displaystyle\frac{\sqrt{2}\partial u(x,z,t)}{\partial t}+c(x,z)\left(\displaystyle\frac{\partial u(x,z,t)}{\partial x}+\displaystyle\frac{\partial u(x,z,t)}{\partial z}\right)=0.$
#
# ## 4.2.3 - Higdon Boundary Condition
#
# The Higdon Boundary condition of order p is given at $\partial \Omega_1$ and $\partial \Omega_3$n by:
#
# - $\Pi_{j=1}^p(\cos(\alpha_j)\left(\displaystyle\frac{\partial }{\partial t}-c(x,z)\displaystyle\frac{\partial }{\partial x}\right)u(x,z,t)=0.$
#
# and at $\partial \Omega_2$
#
# - $\Pi_{j=1}^p(\cos(\alpha_j)\left(\displaystyle\frac{\partial}{\partial t}-c(x,z)\displaystyle\frac{\partial}{\partial z}\right)u(x,z,t)=0.$
#
# This method would make that outgoing waves with angle of incidence at the boundary equal to $\alpha_j$ would
# present no reflection. The method we use in this notebook employs order 2 ($p=2$) and angles $0$ and $\pi/4$.
#
# Observation: There are similarities between Clayton's A2 and the Higdon condition. If one chooses $p=2$ and
# both angles equal to zero in Higdon's method, this leads to the condition:
# $ u_{tt}-2cu_{xt}+c^2u_{xx}=0$. But, using the wave equation, we have that $c^2u_{xx}=u_{tt}-c^2u_{zz}$. Replacing this relation in the previous equation, we get: $2u_{tt}-2cu_{xt}-c^2u_{zz}=0$ which is Clayton's A2
# boundary condition. In this sence, Higdon's method would generalize Clayton's scheme. But the discretization of
# both methods are quite different, since in Higdon's scheme the boundary operators are unidirectional, while
# in Clayton's A2 not.
# # 4.3 - Acoustic Problem with HABC
#
# In the hybrid absorption boundary condition (HABC) scheme we will also extend the spatial domain as $\Omega=\left[x_{I}-L,x_{F}+L\right] \times\left[z_{I},z_{F}+L\right]$.
#
# We added to the target domain $\Omega_{0}=\left[x_{I},x_{F}\right]\times\left[z_{I},z_{F}\right]$ an extension zone, of length $L$ in both ends of the direction $x$ and at the end of the domain in the direction $z$, as represented in the figure bellow.
#
#
#
# The difference with respect to previous schemes, is that this extended region will now be considered as the union of several gradual extensions. As represented in the next figure, we define a region $A_M=\Omega_{0}$. The regions $A_k, k=M-1,\cdots,1$ will be defined as the previous region $A_{k+1}$ to which we add one extra grid line to the left,
# right and bottom sides of it, such that the final region $A_1=\Omega$ (we thus have $M=L+1$).
#
#
#
# We now consider the temporal evolution
# of the solution of the HABC method. Suppose that $u(x,z,t-1)$ is the solution at a given instant $t-1$ in all the
# extended $\Omega$ domain. We update it to instant $t$, using one of the absorbing boundary conditions described in the previous section (A1, A2 or Higdon) producing a preliminar new function $u(x,z,t)$. Now, call $u_{1}(x,z,t)$ the solution at instant $t$ constructed in the extended region, by applying the same absorbing boundary condition at the border of each of the domains $A_k,k=1,..,M$. The HABC solution will be constructed as a convex combination of $u(x,z,t)$ and $u_{1}(x,z,t)$:
#
# - $u(x,z,t) = (1-\omega)u(x,z,t)+\omega u_{1}(x,z,t)$.
#
# The function $u_{1}(x,z,t)$ is defined (and used) only in the extension of the domain. The function $w$ is a
# weight function growing from zero at the boundary $\partial\Omega_{0}$ to one at $\partial\Omega$. The particular weight function to be used could vary linearly, as when the scheme was first proposed by Liu and Sen. But HABC produces better results with a non-linear weight function to be described ahead.
#
# The wave equation employed here will be the same as in the previous notebooks, with same velocity model, source term and initial conditions.
#
#
# ## 4.3.1 The weight function $\omega$
#
# One can choose a *linear* weight function as
#
# \begin{equation}
# \omega_{k} = \displaystyle\frac{M-k}{M};
# \end{equation}
#
# or preferably a *non linear*
#
# \begin{equation}
# \omega_{k}=\left\{ \begin{array}{ll}
# 1, & \textrm{if $1\leq k \leq P+1$,} \\ \left(\displaystyle\frac{M-k}{M-P}\right)^{\alpha} , & \textrm{if $P+2 \leq k \leq M-1.$} \\ 0 , & \textrm{if $k=M$.}\end{array}\right.
# \label{eq:elo8}
# \end{equation}
#
# In general we take $P=2$ and we choose $\alpha$ as follows:
#
# - $\alpha = 1.5 + 0.07(npt-P)$, in the case of A1 and A2;
# - $\alpha = 1.0 + 0.15(npt-P)$, in the case of Higdon.
#
# The value *npt* designates the number of discrete points that define the length of the blue band in the direction $x$ and/or $z$.
# # 4.4 - Finite Difference Operators and Discretization of Spatial and Temporal Domains
#
# We employ the same methods as in the previous notebooks.
# # 4.5 - Standard Problem
#
# Redeeming the Standard Problem definitions discussed on the notebook Introduction to Acoustic Problem we have that:
#
# - $x_{I}$ = 0.0 Km;
# - $x_{F}$ = 1.0 Km = 1000 m;
# - $z_{I}$ = 0.0 Km;
# - $z_{F}$ = 1.0 Km = 1000 m;
#
# The spatial discretization parameters are given by:
# - $\Delta x$ = 0.01 km = 10m;
# - $\Delta z$ = 0.01 km = 10m;
#
# Let's consider a $I$ the time domain with the following limitations:
#
# - $t_{I}$ = 0 s = 0 ms;
# - $t_{F}$ = 1 s = 1000 ms;
#
# The temporal discretization parameters are given by:
#
# - $\Delta t$ $\approx$ 0.0016 s = 1.6 ms;
# - $NT$ = 626.
#
# The source term, velocity model and positioning of receivers will be as in the previous notebooks.
# # 4.6 - Numerical Simulations
#
# For the numerical simulations of this notebook we use several of the notebook codes presented in Damping e PML. The new features will be described in more detail.
#
# So, we import the following Python and Devito packages:
# In[1]:
# NBVAL_IGNORE_OUTPUT
import numpy as np
import matplotlib.pyplot as plot
import math as mt
import matplotlib.ticker as mticker
from mpl_toolkits.axes_grid1 import make_axes_locatable
from matplotlib import cm
# From Devito's library of examples we import the following structures:
# In[2]:
# NBVAL_IGNORE_OUTPUT
get_ipython().run_line_magic('matplotlib', 'inline')
from examples.seismic import TimeAxis
from examples.seismic import RickerSource
from examples.seismic import Receiver
from examples.seismic import plot_velocity
from devito import SubDomain, Grid, NODE, TimeFunction, Function, Eq, solve, Operator
# The mesh parameters that we choose define the domain $\Omega_{0}$ plus the absorption region. For this, we use the following data:
# In[3]:
nptx = 101
nptz = 101
x0 = 0.
x1 = 1000.
compx = x1-x0
z0 = 0.
z1 = 1000.
compz = z1-z0;
hxv = (x1-x0)/(nptx-1)
hzv = (z1-z0)/(nptz-1)
# As we saw previously, HABC has three approach possibilities (A1, A2 and Higdon) and two types of weights (linear and non-linear). So, we insert two control variables. The variable called *habctype* chooses the type of HABC approach and is such that:
#
# - *habctype=1* is equivalent to choosing A1;
# - *habctype=2* is equivalent to choosing A2;
# - *habctype=3* is equivalent to choosing Higdon;
#
# Regarding the weights, we will introduce the variable *habcw* that chooses the type of weight and is such that:
#
# - *habcw=1* is equivalent to linear weight;
# - *habcw=2* is equivalent to non-linear weights;
#
# In this way, we make the following choices:
# In[4]:
habctype = 3
habcw = 2
# The number of points of the absorption layer in the directions $x$ and $z$ are given, respectively, by:
# In[5]:
npmlx = 20
npmlz = 20
# The lengths $L_{x}$ and $L_{z}$ are given, respectively, by:
# In[6]:
lx = npmlx*hxv
lz = npmlz*hzv
# For the construction of the *grid* we have:
# In[7]:
nptx = nptx + 2*npmlx
nptz = nptz + 1*npmlz
x0 = x0 - hxv*npmlx
x1 = x1 + hxv*npmlx
compx = x1-x0
z0 = z0
z1 = z1 + hzv*npmlz
compz = z1-z0
origin = (x0,z0)
extent = (compx,compz)
shape = (nptx,nptz)
spacing = (hxv,hzv)
# As in the case of the acoustic equation with Damping and in the acoustic equation with PML, we can define specific regions in our domain, since the solution $u_{1}(x,z,t)$ is only calculated in the blue region. We will soon follow a similar scheme for creating *subdomains* as was done on notebooks Damping and PML.
#
# First, we define a region corresponding to the entire domain, naming this region as *d0*. In the language of *subdomains* *d0* it is written as:
# In[8]:
class d0domain(SubDomain):
name = 'd0'
def define(self, dimensions):
x, z = dimensions
return {x: x, z: z}
d0_domain = d0domain()
# The blue region will be built with 3 divisions:
#
# - *d1* represents the left range in the direction *x*, where the pairs $(x,z)$ satisfy: $x\in\{0,npmlx\}$ and $z\in\{0,nptz\}$;
# - *d2* represents the rigth range in the direction *x*, where the pairs $(x,z)$ satisfy: $x\in\{nptx-npmlx,nptx\}$ and $z\in\{0,nptz\}$;
# - *d3* represents the left range in the direction *y*, where the pairs $(x,z)$ satisfy: $x\in\{npmlx,nptx-npmlx\}$ and $z\in\{nptz-npmlz,nptz\}$;
#
# Thus, the regions *d1*, *d2* and *d3* aare described as follows in the language of *subdomains*:
# In[9]:
class d1domain(SubDomain):
name = 'd1'
def define(self, dimensions):
x, z = dimensions
return {x: ('left',npmlx), z: z}
d1_domain = d1domain()
class d2domain(SubDomain):
name = 'd2'
def define(self, dimensions):
x, z = dimensions
return {x: ('right',npmlx), z: z}
d2_domain = d2domain()
class d3domain(SubDomain):
name = 'd3'
def define(self, dimensions):
x, z = dimensions
if((habctype==3)&(habcw==1)):
return {x: x, z: ('right',npmlz)}
else:
return {x: ('middle', npmlx, npmlx), z: ('right',npmlz)}
d3_domain = d3domain()
# The figure below represents the division of domains that we did previously:
#
#
#
# After we defining the spatial parameters and constructing the *subdomains*, we then generate the *spatial grid* and set the velocity field:
# In[10]:
grid = Grid(origin=origin, extent=extent, shape=shape, subdomains=(d0_domain,d1_domain,d2_domain,d3_domain), dtype=np.float64)
# In[11]:
v0 = np.zeros((nptx,nptz))
X0 = np.linspace(x0,x1,nptx)
Z0 = np.linspace(z0,z1,nptz)
x10 = x0+lx
x11 = x1-lx
z10 = z0
z11 = z1 - lz
xm = 0.5*(x10+x11)
zm = 0.5*(z10+z11)
pxm = 0
pzm = 0
for i in range(0,nptx):
if(X0[i]==xm): pxm = i
for j in range(0,nptz):
if(Z0[j]==zm): pzm = j
p0 = 0
p1 = pzm
p2 = nptz
v0[0:nptx,p0:p1] = 1.5
v0[0:nptx,p1:p2] = 2.5
# Previously we introduce the local variables *x10,x11,z10,z11,xm,zm,pxm* and *pzm* that help us to create a specific velocity field, where we consider the whole domain (including the absorpion region). Below we include a routine to plot the velocity field.
# In[12]:
def graph2dvel(vel):
plot.figure()
plot.figure(figsize=(16,8))
fscale = 1/10**(3)
scale = np.amax(vel[npmlx:-npmlx,0:-npmlz])
extent = [fscale*(x0+lx),fscale*(x1-lx), fscale*(z1-lz), fscale*(z0)]
fig = plot.imshow(np.transpose(vel[npmlx:-npmlx,0:-npmlz]), vmin=0.,vmax=scale, cmap=cm.seismic, extent=extent)
plot.gca().xaxis.set_major_formatter(mticker.FormatStrFormatter('%.1f km'))
plot.gca().yaxis.set_major_formatter(mticker.FormatStrFormatter('%.1f km'))
plot.title('Velocity Profile')
plot.grid()
ax = plot.gca()
divider = make_axes_locatable(ax)
cax = divider.append_axes("right", size="5%", pad=0.05)
cbar = plot.colorbar(fig, cax=cax, format='%.2e')
cbar.set_label('Velocity [km/s]')
plot.show()
# Below we include the plot of velocity field.
# In[13]:
# NBVAL_IGNORE_OUTPUT
graph2dvel(v0)
# Time parameters are defined and constructed by the following sequence of commands:
# In[14]:
t0 = 0.
tn = 1000.
CFL = 0.4
vmax = np.amax(v0)
dtmax = np.float64((min(hxv,hzv)*CFL)/(vmax))
ntmax = int((tn-t0)/dtmax)+1
dt0 = np.float64((tn-t0)/ntmax)
# With the temporal parameters, we generate the time properties with *TimeAxis* as follows:
# In[15]:
time_range = TimeAxis(start=t0,stop=tn,num=ntmax+1)
nt = time_range.num - 1
# The symbolic values associated with the spatial and temporal grids that are used in the composition of the equations are given by:
# In[16]:
(hx,hz) = grid.spacing_map
(x, z) = grid.dimensions
t = grid.stepping_dim
dt = grid.stepping_dim.spacing
# We set the Ricker source:
# In[17]:
f0 = 0.01
nsource = 1
xposf = 0.5*(compx-2*npmlx*hxv)
zposf = hzv
# In[18]:
src = RickerSource(name='src',grid=grid,f0=f0,npoint=nsource,time_range=time_range,staggered=NODE,dtype=np.float64)
src.coordinates.data[:, 0] = xposf
src.coordinates.data[:, 1] = zposf
# Below we include the plot of Ricker source.
# In[19]:
# NBVAL_IGNORE_OUTPUT
src.show()
# We set the receivers:
# In[20]:
nrec = nptx
nxpos = np.linspace(x0,x1,nrec)
nzpos = hzv
# In[21]:
rec = Receiver(name='rec',grid=grid,npoint=nrec,time_range=time_range,staggered=NODE,dtype=np.float64)
rec.coordinates.data[:, 0] = nxpos
rec.coordinates.data[:, 1] = nzpos
# The displacement field *u* and the velocity *vel* are allocated:
# In[22]:
u = TimeFunction(name="u",grid=grid,time_order=2,space_order=2,staggered=NODE,dtype=np.float64)
# In[23]:
vel = Function(name="vel",grid=grid,space_order=2,staggered=NODE,dtype=np.float64)
vel.data[:,:] = v0[:,:]
# We include the source term as *src_term* using the following command:
# In[24]:
src_term = src.inject(field=u.forward,expr=src*dt**2*vel**2)
# The Receivers are again called *rec_term*:
# In[25]:
rec_term = rec.interpolate(expr=u)
# The next step is to generate the $\omega$ weights, which are selected using the *habcw* variable. Our construction approach will be in two steps: in a first step we build local vectors *weightsx* and *weightsz* that represent the weights in the directions $x$ and $z$, respectively. In a second step, with the *weightsx* and *weightsz* vectors, we distribute them in two global arrays called *Mweightsx* and *Mweightsz* that represent the distribution of these weights along the *grid* in the directions $x$ and $z$ respectively. The *generateweights* function below perform the operations listed previously:
# In[26]:
def generateweights():
weightsx = np.zeros(npmlx)
weightsz = np.zeros(npmlz)
Mweightsx = np.zeros((nptx,nptz))
Mweightsz = np.zeros((nptx,nptz))
if(habcw==1):
for i in range(0,npmlx):
weightsx[i] = (npmlx-i)/(npmlx)
for i in range(0,npmlz):
weightsz[i] = (npmlz-i)/(npmlz)
if(habcw==2):
mx = 2
mz = 2
if(habctype==3):
alphax = 1.0 + 0.15*(npmlx-mx)
alphaz = 1.0 + 0.15*(npmlz-mz)
else:
alphax = 1.5 + 0.07*(npmlx-mx)
alphaz = 1.5 + 0.07*(npmlz-mz)
for i in range(0,npmlx):
if(0<=i<=(mx)):
weightsx[i] = 1
elif((mx+1)<=i<=npmlx-1):
weightsx[i] = ((npmlx-i)/(npmlx-mx))**(alphax)
else:
weightsx[i] = 0
for i in range(0,npmlz):
if(0<=i<=(mz)):
weightsz[i] = 1
elif((mz+1)<=i<=npmlz-1):
weightsz[i] = ((npmlz-i)/(npmlz-mz))**(alphaz)
else:
weightsz[i] = 0
for k in range(0,npmlx):
ai = k
af = nptx - k - 1
bi = 0
bf = nptz - k
Mweightsx[ai,bi:bf] = weightsx[k]
Mweightsx[af,bi:bf] = weightsx[k]
for k in range(0,npmlz):
ai = k
af = nptx - k
bf = nptz - k - 1
Mweightsz[ai:af,bf] = weightsz[k]
return Mweightsx,Mweightsz
# Once the *generateweights* function has been created, we execute it with the following command:
# In[27]:
Mweightsx,Mweightsz = generateweights();
# Below we include a routine to plot the weight fields.
# In[28]:
def graph2dweight(D):
plot.figure()
plot.figure(figsize=(16,8))
fscale = 1/10**(-3)
fscale = 10**(-3)
scale = np.amax(D)
extent = [fscale*x0,fscale*x1, fscale*z1, fscale*z0]
fig = plot.imshow(np.transpose(D), vmin=0.,vmax=scale, cmap=cm.seismic, extent=extent)
plot.gca().xaxis.set_major_formatter(mticker.FormatStrFormatter('%.1f km'))
plot.gca().yaxis.set_major_formatter(mticker.FormatStrFormatter('%.1f km'))
plot.title('Weight Function')
plot.grid()
ax = plot.gca()
divider = make_axes_locatable(ax)
cax = divider.append_axes("right", size="5%", pad=0.05)
cbar = plot.colorbar(fig, cax=cax, format='%.2e')
cbar.set_label('Weights')
plot.show()
# Below we include the plot of weights field in $x$ direction.
# In[29]:
# NBVAL_IGNORE_OUTPUT
graph2dweight(Mweightsx)
# Below we include the plot of weights field in $z$ direction.
# In[30]:
# NBVAL_IGNORE_OUTPUT
graph2dweight(Mweightsz)
# Next we create the fields for the weight arrays *weightsx* and *weightsz*:
# In[31]:
weightsx = Function(name="weightsx",grid=grid,space_order=2,staggered=NODE,dtype=np.float64)
weightsx.data[:,:] = Mweightsx[:,:]
weightsz = Function(name="weightsz",grid=grid,space_order=2,staggered=NODE,dtype=np.float64)
weightsz.data[:,:] = Mweightsz[:,:]
# For the discretization of the A2 and Higdon's boundary conditions (to calculate $u_{1}(x,z,t)$) we need information from three time levels, namely $u(x,z,t-1)$, $u (x,z,t)$ and $u(x,z,t+1)$. So it is convenient to create the three fields:
# In[32]:
u1 = Function(name="u1" ,grid=grid,space_order=2,staggered=NODE,dtype=np.float64)
u2 = Function(name="u2" ,grid=grid,space_order=2,staggered=NODE,dtype=np.float64)
u3 = Function(name="u3" ,grid=grid,space_order=2,staggered=NODE,dtype=np.float64)
# We will assign to each of them the three time solutions described previously, that is,
#
# - u1(x,z) = u(x,z,t-1);
# - u2(x,z) = u(x,z,t);
# - u3(x,z) = u(x,z,t+1);
#
# These three assignments can be represented by the *stencil01* given by:
# In[33]:
stencil01 = [Eq(u1,u.backward),Eq(u2,u),Eq(u3,u.forward)]
# An update of the term *u3(x,z)* will be necessary after updating *u(x,z,t+1)* in the direction $x$, so that we can continue to apply the HABC method. This update is given by *stencil02* defined as:
# In[34]:
stencil02 = [Eq(u3,u.forward)]
# For the acoustic equation with HABC without the source term we need in $\Omega$
#
# - eq1 = u.dt2 - vel0 * vel0 * u.laplace;
#
# So the *pde* that represents this equation is given by:
# In[35]:
pde0 = Eq(u.dt2 - u.laplace*vel**2)
# And the *stencil* for *pde0* is given to:
# In[36]:
stencil0 = Eq(u.forward, solve(pde0,u.forward))
# For the blue region we will divide it into $npmlx$ layers in the $x$ direction and $npmlz$ layers in the $z$ direction. In this case, the representation is a little more complex than shown in the figures that exemplify the regions $A_{k}$ because there are intersections between the layers.
#
# **Observation:** Note that the representation of the $A_{k}$ layers that we present in our text reflects the case where $npmlx=npmlz$. However, our code includes the case illustrated in the figure, as well as situations in which $npmlx\neq npmlz$. The discretizations of the bounadry conditions A1, A2 and Higdon follow in the bibliographic references at the end. They will not be detailled here, but can be seen in the codes below.
#
# In the sequence of codes below we build the *pdes* that represent the *eqs* of the regions $B_{1}$, $B_{2}$ and $B_{3}$ and/or in the corners (red points in the case of *A2*) as represented in the following figure:
#
#
#
# In the sequence, we present the *stencils* for each of these *pdes*.
#
# So, for the A1 case we have the following *pdes* and *stencils*:
# In[37]:
if(habctype==1):
# Region B_{1}
aux1 = ((-vel[x,z]*dt+hx)*u2[x,z] + (vel[x,z]*dt+hx)*u2[x+1,z] + (vel[x,z]*dt-hx)*u3[x+1,z])/(vel[x,z]*dt+hx)
pde1 = (1-weightsx[x,z])*u3[x,z] + weightsx[x,z]*aux1
stencil1 = Eq(u.forward,pde1,subdomain = grid.subdomains['d1'])
# Region B_{3}
aux2 = ((-vel[x,z]*dt+hx)*u2[x,z] + (vel[x,z]*dt+hx)*u2[x-1,z] + (vel[x,z]*dt-hx)*u3[x-1,z])/(vel[x,z]*dt+hx)
pde2 = (1-weightsx[x,z])*u3[x,z] + weightsx[x,z]*aux2
stencil2 = Eq(u.forward,pde2,subdomain = grid.subdomains['d2'])
# Region B_{2}
aux3 = ((-vel[x,z]*dt+hz)*u2[x,z] + (vel[x,z]*dt+hz)*u2[x,z-1] + (vel[x,z]*dt-hz)*u3[x,z-1])/(vel[x,z]*dt+hz)
pde3 = (1-weightsz[x,z])*u3[x,z] + weightsz[x,z]*aux3
stencil3 = Eq(u.forward,pde3,subdomain = grid.subdomains['d3'])
# For the A2 case we have the following *pdes* and *stencils*:
# In[38]:
if(habctype==2):
# Region B_{1}
cte11 = (1/(2*dt**2)) + (1/(2*dt*hx))*vel[x,z]
cte21 = -(1/(2*dt**2)) + (1/(2*dt*hx))*vel[x,z] - (1/(2*hz**2))*vel[x,z]*vel[x,z]
cte31 = -(1/(2*dt**2)) - (1/(2*dt*hx))*vel[x,z]
cte41 = (1/(dt**2))
cte51 = (1/(4*hz**2))*vel[x,z]**2
aux1 = (cte21*(u3[x+1,z] + u1[x,z]) + cte31*u1[x+1,z] + cte41*(u2[x,z]+u2[x+1,z]) + cte51*(u3[x+1,z+1] + u3[x+1,z-1] + u1[x,z+1] + u1[x,z-1]))/cte11
pde1 = (1-weightsx[x,z])*u3[x,z] + weightsx[x,z]*aux1
stencil1 = Eq(u.forward,pde1,subdomain = grid.subdomains['d1'])
# Region B_{3}
cte12 = (1/(2*dt**2)) + (1/(2*dt*hx))*vel[x,z]
cte22 = -(1/(2*dt**2)) + (1/(2*dt*hx))*vel[x,z] - (1/(2*hz**2))*vel[x,z]**2
cte32 = -(1/(2*dt**2)) - (1/(2*dt*hx))*vel[x,z]
cte42 = (1/(dt**2))
cte52 = (1/(4*hz**2))*vel[x,z]*vel[x,z]
aux2 = (cte22*(u3[x-1,z] + u1[x,z]) + cte32*u1[x-1,z] + cte42*(u2[x,z]+u2[x-1,z]) + cte52*(u3[x-1,z+1] + u3[x-1,z-1] + u1[x,z+1] + u1[x,z-1]))/cte12
pde2 = (1-weightsx[x,z])*u3[x,z] + weightsx[x,z]*aux2
stencil2 = Eq(u.forward,pde2,subdomain = grid.subdomains['d2'])
# Region B_{2}
cte13 = (1/(2*dt**2)) + (1/(2*dt*hz))*vel[x,z]
cte23 = -(1/(2*dt**2)) + (1/(2*dt*hz))*vel[x,z] - (1/(2*hx**2))*vel[x,z]**2
cte33 = -(1/(2*dt**2)) - (1/(2*dt*hz))*vel[x,z]
cte43 = (1/(dt**2))
cte53 = (1/(4*hx**2))*vel[x,z]*vel[x,z]
aux3 = (cte23*(u3[x,z-1] + u1[x,z]) + cte33*u1[x,z-1] + cte43*(u2[x,z]+u2[x,z-1]) + cte53*(u3[x+1,z-1] + u3[x-1,z-1] + u1[x+1,z] + u1[x-1,z]))/cte13
pde3 = (1-weightsz[x,z])*u3[x,z] + weightsz[x,z]*aux3
stencil3 = Eq(u.forward,pde3,subdomain = grid.subdomains['d3'])
# Red point rigth side
stencil4 = [Eq(u[t+1,nptx-1-k,nptz-1-k],(1-weightsz[nptx-1-k,nptz-1-k])*u3[nptx-1-k,nptz-1-k] +
weightsz[nptx-1-k,nptz-1-k]*(((-(1/(4*hx)) + (1/(4*hz)) - (np.sqrt(2))/(4*vel[nptx-1-k,nptz-1-k]*dt))*u3[nptx-1-k,nptz-2-k]
+ ((1/(4*hx)) - (1/(4*hz)) - (np.sqrt(2))/(4*vel[nptx-1-k,nptz-1-k]*dt))*u3[nptx-2-k,nptz-1-k]
+ ((1/(4*hx)) + (1/(4*hz)) - (np.sqrt(2))/(4*vel[nptx-1-k,nptz-1-k]*dt))*u3[nptx-2-k,nptz-2-k]
+ (-(1/(4*hx)) - (1/(4*hz)) + (np.sqrt(2))/(4*vel[nptx-1-k,nptz-1-k]*dt))*u2[nptx-1-k,nptz-1-k]
+ (-(1/(4*hx)) + (1/(4*hz)) + (np.sqrt(2))/(4*vel[nptx-1-k,nptz-1-k]*dt))*u2[nptx-1-k,nptz-2-k]
+ ((1/(4*hx)) - (1/(4*hz)) + (np.sqrt(2))/(4*vel[nptx-1-k,nptz-1-k]*dt))*u2[nptx-2-k,nptz-1-k]
+ ((1/(4*hx)) + (1/(4*hz)) + (np.sqrt(2))/(4*vel[nptx-1-k,nptz-1-k]*dt))*u2[nptx-2-k,nptz-2-k])
/ (((1/(4*hx)) + (1/(4*hz)) + (np.sqrt(2))/(4*vel[nptx-1-k,nptz-1-k]*dt))))) for k in range(0,npmlz)]
# Red point left side
stencil5 = [Eq(u[t+1,k,nptz-1-k],(1-weightsx[k,nptz-1-k] )*u3[k,nptz-1-k]
+ weightsx[k,nptz-1-k]*(( (-(1/(4*hx)) + (1/(4*hz)) - (np.sqrt(2))/(4*vel[k,nptz-1-k]*dt))*u3[k,nptz-2-k]
+ ((1/(4*hx)) - (1/(4*hz)) - (np.sqrt(2))/(4*vel[k,nptz-1-k]*dt))*u3[k+1,nptz-1-k]
+ ((1/(4*hx)) + (1/(4*hz)) - (np.sqrt(2))/(4*vel[k,nptz-1-k]*dt))*u3[k+1,nptz-2-k]
+ (-(1/(4*hx)) - (1/(4*hz)) + (np.sqrt(2))/(4*vel[k,nptz-1-k]*dt))*u2[k,nptz-1-k]
+ (-(1/(4*hx)) + (1/(4*hz)) + (np.sqrt(2))/(4*vel[k,nptz-1-k]*dt))*u2[k,nptz-2-k]
+ ((1/(4*hx)) - (1/(4*hz)) + (np.sqrt(2))/(4*vel[k,nptz-1-k]*dt))*u2[k+1,nptz-1-k]
+ ((1/(4*hx)) + (1/(4*hz)) + (np.sqrt(2))/(4*vel[k,nptz-1-k]*dt))*u2[k+1,nptz-2-k])
/ (((1/(4*hx)) + (1/(4*hz)) + (np.sqrt(2))/(4*vel[k,nptz-1-k]*dt))))) for k in range(0,npmlx)]
# For the Higdon case we have the following *pdes* and *stencils*:
# In[39]:
if(habctype==3):
alpha1 = 0.0
alpha2 = np.pi/4
a1 = 0.5
b1 = 0.5
a2 = 0.5
b2 = 0.5
# Region B_{1}
gama111 = np.cos(alpha1)*(1-a1)*(1/dt)
gama121 = np.cos(alpha1)*(a1)*(1/dt)
gama131 = np.cos(alpha1)*(1-b1)*(1/hx)*vel[x,z]
gama141 = np.cos(alpha1)*(b1)*(1/hx)*vel[x,z]
gama211 = np.cos(alpha2)*(1-a2)*(1/dt)
gama221 = np.cos(alpha2)*(a2)*(1/dt)
gama231 = np.cos(alpha2)*(1-b2)*(1/hx)*vel[x,z]
gama241 = np.cos(alpha2)*(b2)*(1/hx)*vel[x,z]
c111 = gama111 + gama131
c121 = -gama111 + gama141
c131 = gama121 - gama131
c141 = -gama121 - gama141
c211 = gama211 + gama231
c221 = -gama211 + gama241
c231 = gama221 - gama231
c241 = -gama221 - gama241
aux1 = ( u2[x,z]*(-c111*c221-c121*c211) + u3[x+1,z]*(-c111*c231-c131*c211) + u2[x+1,z]*(-c111*c241-c121*c231-c141*c211-c131*c221)
+ u1[x,z]*(-c121*c221) + u1[x+1,z]*(-c121*c241-c141*c221) + u3[x+2,z]*(-c131*c231) +u2[x+2,z]*(-c131*c241-c141*c231)
+ u1[x+2,z]*(-c141*c241))/(c111*c211)
pde1 = (1-weightsx[x,z])*u3[x,z] + weightsx[x,z]*aux1
stencil1 = Eq(u.forward,pde1,subdomain = grid.subdomains['d1'])
# Region B_{3}
gama112 = np.cos(alpha1)*(1-a1)*(1/dt)
gama122 = np.cos(alpha1)*(a1)*(1/dt)
gama132 = np.cos(alpha1)*(1-b1)*(1/hx)*vel[x,z]
gama142 = np.cos(alpha1)*(b1)*(1/hx)*vel[x,z]
gama212 = np.cos(alpha2)*(1-a2)*(1/dt)
gama222 = np.cos(alpha2)*(a2)*(1/dt)
gama232 = np.cos(alpha2)*(1-b2)*(1/hx)*vel[x,z]
gama242 = np.cos(alpha2)*(b2)*(1/hx)*vel[x,z]
c112 = gama112 + gama132
c122 = -gama112 + gama142
c132 = gama122 - gama132
c142 = -gama122 - gama142
c212 = gama212 + gama232
c222 = -gama212 + gama242
c232 = gama222 - gama232
c242 = -gama222 - gama242
aux2 = ( u2[x,z]*(-c112*c222-c122*c212) + u3[x-1,z]*(-c112*c232-c132*c212) + u2[x-1,z]*(-c112*c242-c122*c232-c142*c212-c132*c222)
+ u1[x,z]*(-c122*c222) + u1[x-1,z]*(-c122*c242-c142*c222) + u3[x-2,z]*(-c132*c232) +u2[x-2,z]*(-c132*c242-c142*c232)
+ u1[x-2,z]*(-c142*c242))/(c112*c212)
pde2 = (1-weightsx[x,z])*u3[x,z] + weightsx[x,z]*aux2
stencil2 = Eq(u.forward,pde2,subdomain = grid.subdomains['d2'])
# Region B_{2}
gama113 = np.cos(alpha1)*(1-a1)*(1/dt)
gama123 = np.cos(alpha1)*(a1)*(1/dt)
gama133 = np.cos(alpha1)*(1-b1)*(1/hz)*vel[x,z]
gama143 = np.cos(alpha1)*(b1)*(1/hz)*vel[x,z]
gama213 = np.cos(alpha2)*(1-a2)*(1/dt)
gama223 = np.cos(alpha2)*(a2)*(1/dt)
gama233 = np.cos(alpha2)*(1-b2)*(1/hz)*vel[x,z]
gama243 = np.cos(alpha2)*(b2)*(1/hz)*vel[x,z]
c113 = gama113 + gama133
c123 = -gama113 + gama143
c133 = gama123 - gama133
c143 = -gama123 - gama143
c213 = gama213 + gama233
c223 = -gama213 + gama243
c233 = gama223 - gama233
c243 = -gama223 - gama243
aux3 = ( u2[x,z]*(-c113*c223-c123*c213) + u3[x,z-1]*(-c113*c233-c133*c213) + u2[x,z-1]*(-c113*c243-c123*c233-c143*c213-c133*c223)
+ u1[x,z]*(-c123*c223) + u1[x,z-1]*(-c123*c243-c143*c223) + u3[x,z-2]*(-c133*c233) +u2[x,z-2]*(-c133*c243-c143*c233)
+ u1[x,z-2]*(-c143*c243))/(c113*c213)
pde3 = (1-weightsz[x,z])*u3[x,z] + weightsz[x,z]*aux3
stencil3 = Eq(u.forward,pde3,subdomain = grid.subdomains['d3'])
# The surface boundary conditions of the problem are the same as in the notebook Introduction to Acoustic Problem. They are placed in the term *bc* and have the following form:
# In[40]:
bc = [Eq(u[t+1,x,0],u[t+1,x,1])]
# We will then define the operator (*op*) that will join the acoustic equation, source term, boundary conditions and receivers.
#
# - 1. The acoustic wave equation in the *d0* region: *[stencil01];*
# - 2. Source term: *src_term;*
# - 3. Updating solutions over time: *[stencil01,stencil02];*
# - 4. The acoustic wave equation in the *d1*, *d2* e *d3* regions: *[stencil1,stencil2,stencil3];*
# - 5. The equation for red points for A2 method: *[stencil5,stencil4];*
# - 6. Boundry Conditions: *bc;*
# - 7. Receivers: *rec_term;*
#
# We then define two types of *op*:
#
# - The first *op* is for the cases A1 and Higdon;
# - The second *op* is for the case A2;
#
# The *ops* are constructed by the following commands:
# In[41]:
# NBVAL_IGNORE_OUTPUT
if(habctype!=2):
op = Operator([stencil0] + src_term + [stencil01,stencil3,stencil02,stencil2,stencil1] + bc + rec_term,subs=grid.spacing_map)
else:
op = Operator([stencil0] + src_term + [stencil01,stencil3,stencil02,stencil2,stencil1,stencil02,stencil4,stencil5] + bc + rec_term,subs=grid.spacing_map)
# Initially:
# In[42]:
u.data[:] = 0.
u1.data[:] = 0.
u2.data[:] = 0.
u3.data[:] = 0.
# We assign to *op* the number of time steps it must execute and the size of the time step in the local variables *time* and *dt*, respectively.
# In[43]:
# NBVAL_IGNORE_OUTPUT
op(time=nt,dt=dt0)
# We view the result of the displacement field at the end time using the *graph2d* routine given by:
# In[44]:
def graph2d(U,i):
plot.figure()
plot.figure(figsize=(16,8))
fscale = 1/10**(3)
x0pml = x0 + npmlx*hxv
x1pml = x1 - npmlx*hxv
z0pml = z0
z1pml = z1 - npmlz*hzv
scale = np.amax(U[npmlx:-npmlx,0:-npmlz])/10.
extent = [fscale*x0pml,fscale*x1pml,fscale*z1pml,fscale*z0pml]
fig = plot.imshow(np.transpose(U[npmlx:-npmlx,0:-npmlz]),vmin=-scale, vmax=scale, cmap=cm.seismic, extent=extent)
plot.gca().xaxis.set_major_formatter(mticker.FormatStrFormatter('%.1f km'))
plot.gca().yaxis.set_major_formatter(mticker.FormatStrFormatter('%.1f km'))
plot.axis('equal')
if(i==1): plot.title('Map - Acoustic Problem with Devito - HABC A1')
if(i==2): plot.title('Map - Acoustic Problem with Devito - HABC A2')
if(i==3): plot.title('Map - Acoustic Problem with Devito - HABC Higdon')
plot.grid()
ax = plot.gca()
divider = make_axes_locatable(ax)
cax = divider.append_axes("right", size="5%", pad=0.05)
cbar = plot.colorbar(fig, cax=cax, format='%.2e')
cbar.set_label('Displacement [km]')
plot.draw()
plot.show()
# In[45]:
# NBVAL_IGNORE_OUTPUT
graph2d(u.data[0,:,:],habctype)
# We plot the Receivers shot records using the *graph2drec* routine.
# In[46]:
def graph2drec(rec,i):
plot.figure()
plot.figure(figsize=(16,8))
fscaled = 1/10**(3)
fscalet = 1/10**(3)
x0pml = x0 + npmlx*hxv
x1pml = x1 - npmlx*hxv
scale = np.amax(rec[:,npmlx:-npmlx])/10.
extent = [fscaled*x0pml,fscaled*x1pml, fscalet*tn, fscalet*t0]
fig = plot.imshow(rec[:,npmlx:-npmlx], vmin=-scale, vmax=scale, cmap=cm.seismic, extent=extent)
plot.gca().xaxis.set_major_formatter(mticker.FormatStrFormatter('%.1f km'))
plot.gca().yaxis.set_major_formatter(mticker.FormatStrFormatter('%.1f s'))
plot.axis('equal')
if(i==1): plot.title('Receivers Signal Profile - Devito with HABC A1')
if(i==2): plot.title('Receivers Signal Profile - Devito with HABC A2')
if(i==3): plot.title('Receivers Signal Profile - Devito with HABC Higdon')
ax = plot.gca()
divider = make_axes_locatable(ax)
cax = divider.append_axes("right", size="5%", pad=0.05)
cbar = plot.colorbar(fig, cax=cax, format='%.2e')
plot.show()
# In[47]:
# NBVAL_IGNORE_OUTPUT
graph2drec(rec.data,habctype)
# In[48]:
assert np.isclose(np.linalg.norm(rec.data), 990, rtol=1)
# # 4.7 - Conclusions
#
# We have presented the HABC method for the acoustic wave equation, which can be used with any of the
# absorbing boundary conditions A1, A2 or Higdon. The notebook also include the possibility of using these boundary conditions alone, without being combined with the HABC. The user has the possibilty of testing several combinations of parameters and observe the effects in the absorption of spurious reflections on computational boundaries.
#
# The relevant references for the boundary conditions are furnished next.
# ## 4.8 - References
#
# - Clayton, R., & Engquist, B. (1977). "Absorbing boundary conditions for acoustic and elastic wave equations", Bulletin of the seismological society of America, 67(6), 1529-1540. Reference Link.
#
# - Engquist, B., & Majda, A. (1979). "Radiation boundary conditions for acoustic and elastic wave calculations," Communications on pure and applied mathematics, 32(3), 313-357. DOI: 10.1137/0727049. Reference Link.
#
# - Higdon, R. L. (1987). "Absorbing boundary conditions for difference approximations to the multidimensional wave equation," Mathematics of computation, 47(176), 437-459. DOI: 10.1090/S0025-5718-1986-0856696-4. Reference Link.
#
# - Higdon, Robert L. "Numerical absorbing boundary conditions for the wave equation," Mathematics of computation, v. 49, n. 179, p. 65-90, 1987. DOI: 10.1090/S0025-5718-1987-0890254-1. Reference Link.
#
# - Liu, Y., & Sen, M. K. (2018). "An improved hybrid absorbing boundary condition for wave equation modeling," Journal of Geophysics and Engineering, 15(6), 2602-2613. DOI: 10.1088/1742-2140/aadd31. Reference Link.