def setup_w_over_q(wOverQ, w, qmin, qmax, npad, sigma=0): """ Initialise spatially variable w/Q field used to implement attenuation and absorb outgoing waves at the edges of the model. Uses Devito Operator. Parameters ---------- wOverQ : Function, required The omega over Q field used to implement attenuation in the model, and the absorbing boundary condition for outgoing waves. w : float32, required center angular frequency, e.g. peak frequency of Ricker source wavelet used for modeling. qmin : float32, required Q value at the edge of the model. Typically set to 0.1 to strongly attenuate outgoing waves. qmax : float32, required Q value in the interior of the model. Typically set to 100 as a reasonable and physically meaningful Q value. npad : int, required Number of points in the absorbing boundary region. Note that we expect this to be the same on all sides of the model. sigma : float32, optional, defaults to None sigma value for call to scipy gaussian smoother, default 5. """ # sanity checks assert w > 0, "supplied w value [%f] must be positive" % (w) assert qmin > 0, "supplied qmin value [%f] must be positive" % (qmin) assert qmax > 0, "supplied qmax value [%f] must be positive" % (qmax) assert npad > 0, "supplied npad value [%f] must be positive" % (npad) for n in wOverQ.grid.shape: if n - 2 * npad < 1: raise ValueError("2 * npad must not exceed dimension size!") lqmin = np.log(qmin) lqmax = np.log(qmax) # 1. Get distance to closest boundary in all dimensions # 2. Logarithmic variation between qmin, qmax across the absorbing boundary eqs = [Eq(wOverQ, 1)] for d in wOverQ.dimensions: # left dim_l = SubDimension.left(name='abc_%s_l' % d.name, parent=d, thickness=npad) pos = Abs(dim_l - d.symbolic_min) / float(npad) eqs.append( Eq(wOverQ.subs({d: dim_l}), Min(wOverQ.subs({d: dim_l}), pos))) # right dim_r = SubDimension.right(name='abc_%s_r' % d.name, parent=d, thickness=npad) pos = Abs(d.symbolic_max - dim_r) / float(npad) eqs.append( Eq(wOverQ.subs({d: dim_r}), Min(wOverQ.subs({d: dim_r}), pos))) eqs.append(Eq(wOverQ, w / exp(lqmin + wOverQ * (lqmax - lqmin)))) # 2020.05.04 currently does not support spatial smoothing of the Q field # due to MPI weirdness in reassignment of the numpy array Operator(eqs, name='WOverQ_Operator')()
def initialize_function(function, data, nbpml): """ Initialize a `Function` with the given ``data``. ``data`` does *not* include the PML layers for the absorbing boundary conditions; these are added via padding by this function. Parameters ---------- function : Function The initialised object. data : ndarray The data array used for initialisation. nbpml : int Number of PML layers for boundary damping. """ slices = tuple([slice(nbpml, -nbpml) for _ in range(function.grid.dim)]) function.data[slices] = data eqs = [] for d in function.dimensions: dim_l = SubDimension.left(name='abc_%s_l' % d.name, parent=d, thickness=nbpml) to_copy = nbpml eqs += [Eq(function.subs({d: dim_l}), function.subs({d: to_copy}))] dim_r = SubDimension.right(name='abc_%s_r' % d.name, parent=d, thickness=nbpml) to_copy = d.symbolic_max - nbpml eqs += [Eq(function.subs({d: dim_r}), function.subs({d: to_copy}))] # TODO: Figure out why yask doesn't like it with dse/dle Operator(eqs, name='padfunc', dse='noop', dle='noop')()
def test_sub_dimension(self): """ Test that SubDimensions with same name but different attributes do not alias to the same SubDimension. Conversely, if the name and the attributes are the same, they must alias to the same SubDimension. """ x = Dimension('x') xi0 = SubDimension.middle('xi', x, 1, 1) xi1 = SubDimension.middle('xi', x, 1, 1) assert xi0 is xi1 xl0 = SubDimension.left('xl', x, 2) xl1 = SubDimension.left('xl', x, 2) assert xl0 is xl1 xl2asxi = SubDimension.left('xi', x, 2) assert xl2asxi is not xl1 assert xl2asxi is not xi1 xr0 = SubDimension.right('xr', x, 1) xr1 = SubDimension.right('xr', x, 1) assert xr0 is xr1
def test_sub_dimension_cache(): """ Test that SubDimensions with same name but different attributes do not alias to the same SubDimension. Conversely, if the name and the attributes are the same, they must alias to the same SubDimension. """ x = Dimension('x') xi0 = SubDimension.middle('xi', x, 1, 1) xi1 = SubDimension.middle('xi', x, 1, 1) assert xi0 is xi1 xl0 = SubDimension.left('xl', x, 2) xl1 = SubDimension.left('xl', x, 2) assert xl0 is xl1 xl2asxi = SubDimension.left('xi', x, 2) assert xl2asxi is not xl1 assert xl2asxi is not xi1 xr0 = SubDimension.right('xr', x, 1) xr1 = SubDimension.right('xr', x, 1) assert xr0 is xr1
def test_bcs(self): """ Tests application of an Operator consisting of multiple equations defined over different sub-regions, explicitly created through the use of :class:`SubDimension`s. """ grid = Grid(shape=(20, 20)) x, y = grid.dimensions t = grid.stepping_dim thickness = 4 u = TimeFunction(name='u', save=None, grid=grid, space_order=0, time_order=1) xleft = SubDimension.left(name='xleft', parent=x, thickness=thickness) xi = SubDimension.middle(name='xi', parent=x, thickness_left=thickness, thickness_right=thickness) xright = SubDimension.right(name='xright', parent=x, thickness=thickness) yi = SubDimension.middle(name='yi', parent=y, thickness_left=thickness, thickness_right=thickness) t_in_centre = Eq(u[t + 1, xi, yi], 1) leftbc = Eq(u[t + 1, xleft, yi], u[t + 1, xleft + 1, yi] + 1) rightbc = Eq(u[t + 1, xright, yi], u[t + 1, xright - 1, yi] + 1) op = Operator([t_in_centre, leftbc, rightbc]) op.apply(time_m=1, time_M=1) assert np.all(u.data[0, :, 0:thickness] == 0.) assert np.all(u.data[0, :, -thickness:] == 0.) assert all( np.all(u.data[0, i, thickness:-thickness] == (thickness + 1 - i)) for i in range(thickness)) assert all( np.all(u.data[0, -i, thickness:-thickness] == (thickness + 2 - i)) for i in range(1, thickness + 1)) assert np.all(u.data[0, thickness:-thickness, thickness:-thickness] == 1.)
def test_symbolic_size(self): """Check the symbolic size of all possible SubDimensions is as expected.""" grid = Grid(shape=(4,)) x, = grid.dimensions thickness = 4 xleft = SubDimension.left(name='xleft', parent=x, thickness=thickness) assert xleft.symbolic_size == xleft.thickness.left[0] xi = SubDimension.middle(name='xi', parent=x, thickness_left=thickness, thickness_right=thickness) assert xi.symbolic_size == (x.symbolic_max - x.symbolic_min - xi.thickness.left[0] - xi.thickness.right[0] + 1) xright = SubDimension.right(name='xright', parent=x, thickness=thickness) assert xright.symbolic_size == xright.thickness.right[0]
def initialize_damp(damp, padsizes, spacing, fs=False): """ Initialise damping field with an absorbing boundary layer. Includes basic constant Q setup (not interfaced yet) and assumes that the peak frequency is 1/(10 * spacing). Parameters ---------- damp : Function The damping field for absorbing boundary condition. nbl : int Number of points in the damping layer. spacing : Grid spacing coefficient. mask : bool, optional whether the dampening is a mask or layer. mask => 1 inside the domain and decreases in the layer not mask => 0 inside the domain and increase in the layer """ lqmin = np.log(.1) lqmax = np.log(100) w0 = 1 / (10 * np.max(spacing)) z = damp.dimensions[-1] eqs = [Eq(damp, 1)] for (nbl, nbr), d in zip(padsizes, damp.dimensions): if not fs or d is not z: nbl = max(5, nbl - 10) if d is z else nbl # left dim_l = SubDimension.left(name='abc_%s_l' % d.name, parent=d, thickness=nbl) pos = Abs(dim_l - d.symbolic_min) / float(nbl) eqs.append( Eq(damp.subs({d: dim_l}), Min(damp.subs({d: dim_l}), pos))) # right dim_r = SubDimension.right(name='abc_%s_r' % d.name, parent=d, thickness=nbr) pos = Abs(d.symbolic_max - dim_r) / float(nbr) eqs.append(Eq(damp.subs({d: dim_r}), Min(damp.subs({d: dim_r}), pos))) eqs.append(Eq(damp, w0 / exp(lqmin + damp * (lqmax - lqmin)))) Operator(eqs, name='initdamp')()
def test_bcs_basic(self): """ Test MPI in presence of boundary condition loops. Here, no halo exchange is expected (as there is no stencil in the computed expression) but we check that: * the left BC loop is computed by the leftmost rank only * the right BC loop is computed by the rightmost rank only """ grid = Grid(shape=(20, )) x = grid.dimensions[0] t = grid.stepping_dim thickness = 4 u = TimeFunction(name='u', grid=grid, time_order=1) xleft = SubDimension.left(name='xleft', parent=x, thickness=thickness) xi = SubDimension.middle(name='xi', parent=x, thickness_left=thickness, thickness_right=thickness) xright = SubDimension.right(name='xright', parent=x, thickness=thickness) t_in_centre = Eq(u[t + 1, xi], 1) leftbc = Eq(u[t + 1, xleft], u[t + 1, xleft + 1] + 1) rightbc = Eq(u[t + 1, xright], u[t + 1, xright - 1] + 1) op = Operator([t_in_centre, leftbc, rightbc]) op.apply(time_m=1, time_M=1) glb_pos_map = u.grid.distributor.glb_pos_map if LEFT in glb_pos_map[x]: assert np.all(u.data_ro_domain[0, thickness:] == 1.) assert np.all( u.data_ro_domain[0, :thickness] == range(thickness + 1, 1, -1)) else: assert np.all(u.data_ro_domain[0, :-thickness] == 1.) assert np.all( u.data_ro_domain[0, -thickness:] == range(2, thickness + 2))
def initialize_damp(damp, padsizes, spacing, abc_type="damp", fs=False): """ Initialize damping field with an absorbing boundary layer. Parameters ---------- damp : Function The damping field for absorbing boundary condition. nbl : int Number of points in the damping layer. spacing : Grid spacing coefficient. mask : bool, optional whether the dampening is a mask or layer. mask => 1 inside the domain and decreases in the layer not mask => 0 inside the domain and increase in the layer """ eqs = [Eq(damp, 1.0 if abc_type == "mask" else 0.0)] for (nbl, nbr), d in zip(padsizes, damp.dimensions): if not fs or d is not damp.dimensions[-1]: dampcoeff = 1.5 * np.log(1.0 / 0.001) / (nbl) # left dim_l = SubDimension.left(name='abc_%s_l' % d.name, parent=d, thickness=nbl) pos = Abs((nbl - (dim_l - d.symbolic_min) + 1) / float(nbl)) val = dampcoeff * (pos - sin(2 * np.pi * pos) / (2 * np.pi)) val = -val if abc_type == "mask" else val eqs += [Inc(damp.subs({d: dim_l}), val / d.spacing)] # right dampcoeff = 1.5 * np.log(1.0 / 0.001) / (nbr) dim_r = SubDimension.right(name='abc_%s_r' % d.name, parent=d, thickness=nbr) pos = Abs((nbr - (d.symbolic_max - dim_r) + 1) / float(nbr)) val = dampcoeff * (pos - sin(2 * np.pi * pos) / (2 * np.pi)) val = -val if abc_type == "mask" else val eqs += [Inc(damp.subs({d: dim_r}), val / d.spacing)] Operator(eqs, name='initdamp')()
def test_nofission_as_unprofitable(): """ Test there's no fission if not gonna increase number of collapsable loops. """ grid = Grid(shape=(20, 20)) x, y = grid.dimensions t = grid.stepping_dim yl = SubDimension.left(name='yl', parent=y, thickness=4) yr = SubDimension.right(name='yr', parent=y, thickness=4) u = TimeFunction(name='u', grid=grid) eqns = [ Eq(u.forward, u[t + 1, x, y + 1] + 1.).subs(y, yl), Eq(u.forward, u[t + 1, x, y - 1] + 1.).subs(y, yr) ] op = Operator(eqns, opt='fission') assert_structure(op, ['t,x,yl', 't,x,yr'], 't,x,yl,yr')
def initialize_damp(damp, nbl, spacing, mask=False): """ Initialise damping field with an absorbing boundary layer. Parameters ---------- damp : Function The damping field for absorbing boundary condition. nbl : int Number of points in the damping layer. spacing : Grid spacing coefficient. mask : bool, optional whether the dampening is a mask or layer. mask => 1 inside the domain and decreases in the layer not mask => 0 inside the domain and increase in the layer """ dampcoeff = 1.5 * np.log(1.0 / 0.001) / (40) eqs = [Eq(damp, 1.0)] if mask else [] for d in damp.dimensions: # left dim_l = SubDimension.left(name='abc_%s_l' % d.name, parent=d, thickness=nbl) pos = Abs((nbl - (dim_l - d.symbolic_min) + 1) / float(nbl)) val = dampcoeff * (pos - sin(2 * np.pi * pos) / (2 * np.pi)) val = -val if mask else val eqs += [Inc(damp.subs({d: dim_l}), val / d.spacing)] # right dim_r = SubDimension.right(name='abc_%s_r' % d.name, parent=d, thickness=nbl) pos = Abs((nbl - (d.symbolic_max - dim_r) + 1) / float(nbl)) val = dampcoeff * (pos - sin(2 * np.pi * pos) / (2 * np.pi)) val = -val if mask else val eqs += [Inc(damp.subs({d: dim_r}), val / d.spacing)] # TODO: Figure out why yask doesn't like it with dse/dle Operator(eqs, name='initdamp', dse='noop', dle='noop')()
def test_nontrivial_operator(self): """ Test MPI in a non-trivial scenario: :: * 9 processes logically organised in a 3x3 cartesian grid (as opposed to most tests in this module, which only use 2 or 4 processed); * star-like stencil expression; * non-trivial Higdon-like BCs; * simultaneous presence of TimeFunction(grid), Function(grid), and Function(dimensions) """ size_x, size_y = 9, 9 tkn = 2 # Grid and Dimensions grid = Grid(shape=( size_x, size_y, )) x, y = grid.dimensions t = grid.stepping_dim # SubDimensions to implement BCs xl, yl = [SubDimension.left('%sl' % d.name, d, tkn) for d in [x, y]] xi, yi = [ SubDimension.middle('%si' % d.name, d, tkn, tkn) for d in [x, y] ] xr, yr = [SubDimension.right('%sr' % d.name, d, tkn) for d in [x, y]] # Functions u = TimeFunction(name='f', grid=grid) m = Function(name='m', grid=grid) c = Function(name='c', grid=grid, dimensions=(x, ), shape=(size_x, )) # Data initialization u.data_with_halo[:] = 0. m.data_with_halo[:] = 1. c.data_with_halo[:] = 0. # Equations c_init = Eq(c, 1.) eqn = Eq(u[t + 1, xi, yi], u[t, xi, yi] + m[xi, yi] + c[xi] + 1.) bc_left = Eq(u[t + 1, xl, yi], u[t + 1, xl + 1, yi] + 1.) bc_right = Eq(u[t + 1, xr, yi], u[t + 1, xr - 1, yi] + 1.) bc_top = Eq(u[t + 1, xi, yl], u[t + 1, xi, yl + 1] + 1.) bc_bottom = Eq(u[t + 1, xi, yr], u[t + 1, xi, yr - 1] + 1.) op = Operator([c_init, eqn, bc_left, bc_right, bc_top, bc_bottom]) op.apply(time=0) # Expected (global view): # 0 0 5 5 5 5 5 0 0 # 0 0 4 4 4 4 4 0 0 # 5 4 3 3 3 3 3 4 5 # 5 4 3 3 3 3 3 4 5 # 5 4 3 3 3 3 3 4 5 # 5 4 3 3 3 3 3 4 5 # 0 0 4 4 4 4 4 0 0 # 0 0 5 5 5 5 5 0 0 assert np.all(u.data_ro_domain[0] == 0) # The write occures at t=1 glb_pos_map = u.grid.distributor.glb_pos_map # Check cornes if LEFT in glb_pos_map[x] and LEFT in glb_pos_map[y]: assert np.all( u.data_ro_domain[1] == [[0, 0, 5], [0, 0, 4], [5, 4, 3]]) elif LEFT in glb_pos_map[x] and RIGHT in glb_pos_map[y]: assert np.all( u.data_ro_domain[1] == [[5, 0, 0], [4, 0, 0], [3, 4, 5]]) elif RIGHT in glb_pos_map[x] and LEFT in glb_pos_map[y]: assert np.all( u.data_ro_domain[1] == [[5, 4, 3], [0, 0, 4], [0, 0, 5]]) elif RIGHT in glb_pos_map[x] and RIGHT in glb_pos_map[y]: assert np.all( u.data_ro_domain[1] == [[3, 4, 5], [4, 0, 0], [5, 0, 0]]) # Check sides if not glb_pos_map[x] and LEFT in glb_pos_map[y]: assert np.all( u.data_ro_domain[1] == [[5, 4, 3], [5, 4, 3], [5, 4, 3]]) elif not glb_pos_map[x] and RIGHT in glb_pos_map[y]: assert np.all( u.data_ro_domain[1] == [[3, 4, 5], [3, 4, 5], [3, 4, 5]]) elif LEFT in glb_pos_map[x] and not glb_pos_map[y]: assert np.all( u.data_ro_domain[1] == [[5, 5, 5], [4, 4, 4], [3, 3, 3]]) elif RIGHT in glb_pos_map[x] and not glb_pos_map[y]: assert np.all( u.data_ro_domain[1] == [[3, 3, 3], [4, 4, 4], [5, 5, 5]]) # Check center if not glb_pos_map[x] and not glb_pos_map[y]: assert np.all(u.data_ro_domain[1] == 3)