예제 #1
0
    def __init__(self,
                 model,
                 src,
                 damp,
                 data,
                 time_order=2,
                 spc_order=6,
                 save=False,
                 **kwargs):
        nrec, nt = data.shape
        dt = model.get_critical_dt()
        u = TimeData(name="u",
                     shape=model.get_shape_comp(),
                     time_dim=nt,
                     time_order=time_order,
                     space_order=spc_order,
                     save=save,
                     dtype=damp.dtype)
        m = DenseData(name="m", shape=model.get_shape_comp(), dtype=damp.dtype)
        m.data[:] = model.padm()
        u.pad_time = save
        rec = SourceLike(name="rec",
                         npoint=nrec,
                         nt=nt,
                         dt=dt,
                         h=model.get_spacing(),
                         coordinates=data.receiver_coords,
                         ndim=len(damp.shape),
                         dtype=damp.dtype,
                         nbpml=model.nbpml)
        # Derive stencil from symbolic equation
        eqn = m * u.dt2 - u.laplace + damp * u.dt
        stencil = solve(eqn, u.forward)[0]
        # Add substitutions for spacing (temporal and spatial)
        s, h = symbols('s h')
        subs = {s: dt, h: model.get_spacing()}
        super(ForwardOperator, self).__init__(nt,
                                              m.shape,
                                              stencils=Eq(u.forward, stencil),
                                              subs=subs,
                                              spc_border=spc_order / 2,
                                              time_order=time_order,
                                              forward=True,
                                              dtype=m.dtype,
                                              **kwargs)

        # Insert source and receiver terms post-hoc
        self.input_params += [src, src.coordinates, rec, rec.coordinates]
        self.output_params += [rec]
        self.propagator.time_loop_stencils_a = src.add(m, u) + rec.read(u)
        self.propagator.add_devito_param(src)
        self.propagator.add_devito_param(src.coordinates)
        self.propagator.add_devito_param(rec)
        self.propagator.add_devito_param(rec.coordinates)
예제 #2
0
    def born(self, dmin, src=None, rec=None, u=None, U=None, m=None, **kwargs):
        """
        Linearized Born modelling function that creates the necessary
        data objects for running an adjoint modelling operator.

        :param src: Symbol with time series data for the injected source term
        :param rec: Symbol to store interpolated receiver data
        :param u: (Optional) Symbol to store the computed wavefield
        :param U: (Optional) Symbol to store the computed wavefield
        :param m: (Optional) Symbol for the time-constant square slowness
        """
        # Source term is read-only, so re-use the default
        if src is None:
            src = self.source
        # Create a new receiver object to store the result
        if rec is None:
            rec = rec or Receiver(name='rec',
                                  ntime=self.receiver.nt,
                                  coordinates=self.receiver.coordinates.data)

        # Create the forward wavefields u and U if not provided
        if u is None:
            u = TimeData(name='u',
                         shape=self.model.shape_domain,
                         save=False,
                         time_order=self.time_order,
                         space_order=self.space_order,
                         dtype=self.model.dtype)
        if U is None:
            U = TimeData(name='U',
                         shape=self.model.shape_domain,
                         save=False,
                         time_order=self.time_order,
                         space_order=self.space_order,
                         dtype=self.model.dtype)

        # Pick m from model unless explicitly provided
        if m is None:
            m = self.model.m

        # Execute operator and return wavefield and receiver data
        summary = self.op_born.apply(dm=dmin,
                                     u=u,
                                     U=U,
                                     src=src,
                                     rec=rec,
                                     m=m,
                                     **kwargs)
        return rec, u, U, summary
예제 #3
0
    def __init__(self, m, rec, damp, srca, time_order=4, spc_order=12):
        assert(m.shape == damp.shape)

        input_params = [m, rec, damp, srca]
        v = TimeData("v", m.shape, rec.nt, time_order=time_order,
                     save=True, dtype=m.dtype)
        output_params = [v]
        dim = len(m.shape)
        total_dim = self.total_dim(dim)
        space_dim = self.space_dim(dim)
        lhs = v.indexed[total_dim]
        stencil, subs = self._init_taylor(dim, time_order, spc_order)[1]
        stencil = self.smart_sympy_replace(dim, time_order, stencil,
                                           Function('p'), v, fw=False)
        main_stencil = Eq(lhs, stencil)
        stencil_args = [m.indexed[space_dim], rec.dt, rec.h,
                        damp.indexed[space_dim]]
        stencils = [main_stencil]
        substitutions = [dict(zip(subs, stencil_args))]

        super(AdjointOperator, self).__init__(rec.nt, m.shape,
                                              stencils=stencils,
                                              subs=substitutions,
                                              spc_border=spc_order/2,
                                              time_order=time_order,
                                              forward=False, dtype=m.dtype,
                                              input_params=input_params,
                                              output_params=output_params)

        # Insert source and receiver terms post-hoc
        self.propagator.time_loop_stencils_a = rec.add(m, v) + srca.read(v)
예제 #4
0
def run_simulation(save=False, dx=0.01, dy=0.01, a=0.5, timesteps=100):
    nx, ny = int(1 / dx), int(1 / dy)
    dx2, dy2 = dx**2, dy**2
    dt = dx2 * dy2 / (2 * a * (dx2 + dy2))

    u = TimeData(name='u',
                 shape=(nx, ny),
                 time_dim=timesteps,
                 initializer=initializer,
                 time_order=1,
                 space_order=2,
                 save=save,
                 pad_time=save)

    a, h, s = symbols('a h s')
    eqn = Eq(u.dt, a * (u.dx2 + u.dy2))
    stencil = solve(eqn, u.forward)[0]
    op = Operator(stencils=Eq(u.forward, stencil),
                  subs={
                      a: 0.5,
                      h: dx,
                      s: dt
                  },
                  nt=timesteps,
                  shape=(nx, ny),
                  spc_border=1,
                  time_order=1)
    op.apply()

    if save:
        return u.data[timesteps - 1, :]
    else:
        return u.data[timesteps % 2, :]
예제 #5
0
    def __init__(self, dm, m, src, damp, rec, time_order=4, spc_order=12):
        assert(m.shape == damp.shape)

        input_params = [dm, m, src, damp, rec]
        u = TimeData("u", m.shape, src.nt, time_order=time_order,
                     save=False, dtype=m.dtype)
        U = TimeData("U", m.shape, src.nt, time_order=time_order,
                     save=False, dtype=m.dtype)
        output_params = [u, U]
        dim = len(m.shape)
        total_dim = self.total_dim(dim)
        space_dim = self.space_dim(dim)
        dt = src.dt
        h = src.h
        stencil, subs = self._init_taylor(dim, time_order, spc_order)[0]
        first_stencil = self.smart_sympy_replace(dim, time_order, stencil, Function('p'),
                                                 u, fw=True)
        second_stencil = self.smart_sympy_replace(dim, time_order, stencil, Function('p'),
                                                  U, fw=True)
        first_stencil_args = [m.indexed[space_dim], dt, h, damp.indexed[space_dim]]
        first_update = Eq(u.indexed[total_dim], u.indexed[total_dim]+first_stencil)
        src2 = (-(dt**-2)*(u.indexed[total_dim]-2*u.indexed[tuple((t - 1,) + space_dim)] +
                u.indexed[tuple((t - 2,) + space_dim)])*dm.indexed[space_dim])
        second_stencil_args = [m.indexed[space_dim], dt, h, damp.indexed[space_dim]]
        second_update = Eq(U.indexed[total_dim], second_stencil)
        insert_second_source = Eq(U.indexed[total_dim], U.indexed[total_dim] +
                                  (dt*dt)/m.indexed[space_dim]*src2)
        reset_u = Eq(u.indexed[tuple((t - 2,) + space_dim)], 0)
        stencils = [first_update, second_update, insert_second_source, reset_u]
        substitutions = [dict(zip(subs, first_stencil_args)),
                         dict(zip(subs, second_stencil_args)), {}, {}]

        super(BornOperator, self).__init__(src.nt, m.shape,
                                           stencils=stencils,
                                           subs=substitutions,
                                           spc_border=spc_order/2,
                                           time_order=time_order,
                                           forward=True,
                                           dtype=m.dtype,
                                           input_params=input_params,
                                           output_params=output_params)

        # Insert source and receiver terms post-hoc
        self.propagator.time_loop_stencils_b = src.add(m, u)
        self.propagator.time_loop_stencils_a = rec.read(U)
예제 #6
0
    def forward(self,
                src=None,
                rec=None,
                u=None,
                m=None,
                save=False,
                **kwargs):
        """
        Forward modelling function that creates the necessary
        data objects for running a forward modelling operator.

        :param src: Symbol with time series data for the injected source term
        :param rec: Symbol to store interpolated receiver data
        :param u: (Optional) Symbol to store the computed wavefield
        :param m: (Optional) Symbol for the time-constant square slowness
        :param save: Option to store the entire (unrolled) wavefield

        :returns: Receiver, wavefield and performance summary
        """
        # Source term is read-only, so re-use the default
        if src is None:
            src = self.source
        # Create a new receiver object to store the result
        if rec is None:
            rec = Receiver(name='rec',
                           ntime=self.receiver.nt,
                           coordinates=self.receiver.coordinates.data)

        # Create the forward wavefield if not provided
        if u is None:
            u = TimeData(name='u',
                         shape=self.model.shape_domain,
                         save=save,
                         time_dim=self.source.nt,
                         time_order=self.time_order,
                         space_order=self.space_order,
                         dtype=self.model.dtype)

        # Pick m from model unless explicitly provided
        if m is None:
            m = m or self.model.m

        # Execute operator and return wavefield and receiver data
        if save:
            summary = self.op_fwd_save.apply(src=src,
                                             rec=rec,
                                             u=u,
                                             m=m,
                                             **kwargs)
        else:
            summary = self.op_fwd.apply(src=src, rec=rec, u=u, m=m, **kwargs)
        return rec, u, summary
예제 #7
0
    def gradient(self, rec, u, v=None, grad=None, m=None, **kwargs):
        """
        Gradient modelling function for computing the adjoint of the
        Linearized Born modelling function, ie. the action of the
        Jacobian adjoint on an input data.

        :param recin: Receiver data as a numpy array
        :param u: Symbol for full wavefield `u` (created with save=True)
        :param v: (Optional) Symbol to store the computed wavefield
        :param grad: (Optional) Symbol to store the gradient field

        :returns: Gradient field and performance summary
        """

        # Gradient symbol
        if grad is None:
            grad = DenseData(name='grad',
                             shape=self.model.shape_domain,
                             dtype=self.model.dtype)

        # Create the forward wavefield
        if v is None:
            v = TimeData(name='v',
                         shape=self.model.shape_domain,
                         save=False,
                         time_dim=self.source.nt,
                         time_order=self.time_order,
                         space_order=self.space_order,
                         dtype=self.model.dtype)

        # Pick m from model unless explicitly provided
        if m is None:
            m = m or self.model.m

        summary = self.op_grad.apply(rec=rec,
                                     grad=grad,
                                     v=v,
                                     u=u,
                                     m=m,
                                     **kwargs)
        return grad, summary
예제 #8
0
    def __init__(self, u, m, rec, damp, time_order=4, spc_order=12):
        assert(m.shape == damp.shape)

        input_params = [u, m, rec, damp]
        v = TimeData("v", m.shape, rec.nt, time_order=time_order,
                     save=False, dtype=m.dtype)
        grad = DenseData("grad", m.shape, dtype=m.dtype)
        output_params = [grad, v]
        dim = len(m.shape)
        total_dim = self.total_dim(dim)
        space_dim = self.space_dim(dim)
        lhs = v.indexed[total_dim]
        stencil, subs = self._init_taylor(dim, time_order, spc_order)[1]
        stencil = self.smart_sympy_replace(dim, time_order, stencil,
                                           Function('p'), v, fw=False)
        stencil_args = [m.indexed[space_dim], rec.dt, rec.h,
                        damp.indexed[space_dim]]
        main_stencil = Eq(lhs, lhs + stencil)
        gradient_update = Eq(grad.indexed[space_dim],
                             grad.indexed[space_dim] -
                             (v.indexed[total_dim] - 2 *
                              v.indexed[tuple((t + 1,) + space_dim)] +
                              v.indexed[tuple((t + 2,) + space_dim)]) *
                             u.indexed[total_dim])
        reset_v = Eq(v.indexed[tuple((t + 2,) + space_dim)], 0)
        stencils = [main_stencil, gradient_update, reset_v]
        substitutions = [dict(zip(subs, stencil_args)), {}, {}]

        super(GradientOperator, self).__init__(rec.nt, m.shape,
                                               stencils=stencils,
                                               subs=substitutions,
                                               spc_border=spc_order/2,
                                               time_order=time_order,
                                               forward=False, dtype=m.dtype,
                                               input_params=input_params,
                                               output_params=output_params)

        # Insert source and receiver terms post-hoc
        self.propagator.time_loop_stencils_b = rec.add(m, v)
예제 #9
0
def _new_operator2(shape, time_order, **kwargs):
    infield = TimeData(name='in',
                       shape=shape,
                       time_order=time_order,
                       dtype=np.int32)
    infield.data[:] = np.arange(reduce(mul, shape),
                                dtype=np.int32).reshape(shape)

    outfield = TimeData(name='out',
                        shape=shape,
                        time_order=time_order,
                        dtype=np.int32)

    stencil = Eq(outfield.forward.indexify(),
                 outfield.indexify() + infield.indexify() * 3.0)

    # Run the operator
    Operator(stencil, **kwargs)(infield, outfield, t=10)

    return outfield
예제 #10
0
    def adjoint(self, rec, srca=None, v=None, m=None, **kwargs):
        """
        Adjoint modelling function that creates the necessary
        data objects for running an adjoint modelling operator.

        :param rec: Symbol with stored receiver data. Please note that
                    these act as the source term in the adjoint run.
        :param srca: Symbol to store the resulting data for the
                     interpolated at the original source location.
        :param v: (Optional) Symbol to store the computed wavefield
        :param m: (Optional) Symbol for the time-constant square slowness

        :returns: Adjoint source, wavefield and performance summary
        """
        # Create a new adjoint source and receiver symbol
        if srca is None:
            srca = PointSource(name='srca',
                               ntime=self.source.nt,
                               coordinates=self.source.coordinates.data)

        # Create the adjoint wavefield if not provided
        if v is None:
            v = TimeData(name='v',
                         shape=self.model.shape_domain,
                         save=False,
                         time_order=self.time_order,
                         space_order=self.space_order,
                         dtype=self.model.dtype)

        # Pick m from model unless explicitly provided
        if m is None:
            m = self.model.m

        # Execute operator and return wavefield and receiver data
        summary = self.op_adj.apply(srca=srca, rec=rec, v=v, m=m, **kwargs)
        return srca, v, summary
예제 #11
0
    def __init__(self,
                 model,
                 damp,
                 data,
                 recin,
                 time_order=2,
                 spc_order=6,
                 **kwargs):
        nrec, nt = data.shape
        dt = model.get_critical_dt()
        v = TimeData(name="v",
                     shape=model.get_shape_comp(),
                     time_dim=nt,
                     time_order=time_order,
                     space_order=spc_order,
                     save=False,
                     dtype=damp.dtype)
        m = DenseData(name="m", shape=model.get_shape_comp(), dtype=damp.dtype)
        m.data[:] = model.padm()
        v.pad_time = False
        srca = SourceLike(
            name="srca",
            npoint=1,
            nt=nt,
            dt=dt,
            h=model.get_spacing(),
            coordinates=np.array(data.source_coords,
                                 dtype=damp.dtype)[np.newaxis, :],
            ndim=len(damp.shape),
            dtype=damp.dtype,
            nbpml=model.nbpml)
        rec = SourceLike(name="rec",
                         npoint=nrec,
                         nt=nt,
                         dt=dt,
                         h=model.get_spacing(),
                         coordinates=data.receiver_coords,
                         ndim=len(damp.shape),
                         dtype=damp.dtype,
                         nbpml=model.nbpml)
        rec.data[:] = recin[:]
        # Derive stencil from symbolic equation
        eqn = m * v.dt2 - v.laplace - damp * v.dt
        stencil = solve(eqn, v.backward)[0]

        # Add substitutions for spacing (temporal and spatial)
        s, h = symbols('s h')
        subs = {s: model.get_critical_dt(), h: model.get_spacing()}
        super(AdjointOperator, self).__init__(nt,
                                              m.shape,
                                              stencils=Eq(v.backward, stencil),
                                              subs=subs,
                                              spc_border=spc_order / 2,
                                              time_order=time_order,
                                              forward=False,
                                              dtype=m.dtype,
                                              **kwargs)

        # Insert source and receiver terms post-hoc
        self.input_params += [srca, srca.coordinates, rec, rec.coordinates]
        self.propagator.time_loop_stencils_a = rec.add(m, v) + srca.read(v)
        self.output_params = [srca]
        self.propagator.add_devito_param(srca)
        self.propagator.add_devito_param(srca.coordinates)
        self.propagator.add_devito_param(rec)
        self.propagator.add_devito_param(rec.coordinates)
예제 #12
0
    def __init__(self,
                 model,
                 src,
                 damp,
                 data,
                 dmin,
                 time_order=2,
                 spc_order=6,
                 **kwargs):
        nrec, nt = data.shape
        dt = model.get_critical_dt()
        u = TimeData(name="u",
                     shape=model.get_shape_comp(),
                     time_dim=nt,
                     time_order=time_order,
                     space_order=spc_order,
                     save=False,
                     dtype=damp.dtype)
        U = TimeData(name="U",
                     shape=model.get_shape_comp(),
                     time_dim=nt,
                     time_order=time_order,
                     space_order=spc_order,
                     save=False,
                     dtype=damp.dtype)
        m = DenseData(name="m", shape=model.get_shape_comp(), dtype=damp.dtype)
        m.data[:] = model.padm()

        dm = DenseData(name="dm",
                       shape=model.get_shape_comp(),
                       dtype=damp.dtype)
        dm.data[:] = model.pad(dmin)

        rec = SourceLike(name="rec",
                         npoint=nrec,
                         nt=nt,
                         dt=dt,
                         h=model.get_spacing(),
                         coordinates=data.receiver_coords,
                         ndim=len(damp.shape),
                         dtype=damp.dtype,
                         nbpml=model.nbpml)

        # Derive stencils from symbolic equation
        first_eqn = m * u.dt2 - u.laplace - damp * u.dt
        first_stencil = solve(first_eqn, u.forward)[0]
        second_eqn = m * U.dt2 - U.laplace - damp * U.dt
        second_stencil = solve(second_eqn, U.forward)[0]

        # Add substitutions for spacing (temporal and spatial)
        s, h = symbols('s h')
        subs = {s: src.dt, h: src.h}

        # Add Born-specific updates and resets
        src2 = -(src.dt**-2) * (-2 * u + u.forward + u.backward) * dm
        insert_second_source = Eq(U, U + (src.dt * src.dt) / m * src2)
        stencils = [
            Eq(u.forward, first_stencil),
            Eq(U.forward, second_stencil), insert_second_source
        ]
        super(BornOperator, self).__init__(src.nt,
                                           m.shape,
                                           stencils=stencils,
                                           subs=[subs, subs, {}, {}],
                                           spc_border=spc_order / 2,
                                           time_order=time_order,
                                           forward=True,
                                           dtype=m.dtype,
                                           **kwargs)

        # Insert source and receiver terms post-hoc
        self.input_params += [
            dm, src, src.coordinates, rec, rec.coordinates, U
        ]
        self.output_params = [rec]
        self.propagator.time_loop_stencils_b = src.add(m, u, t - 1)
        self.propagator.time_loop_stencils_a = rec.read(U)
        self.propagator.add_devito_param(dm)
        self.propagator.add_devito_param(src)
        self.propagator.add_devito_param(src.coordinates)
        self.propagator.add_devito_param(rec)
        self.propagator.add_devito_param(rec.coordinates)
        self.propagator.add_devito_param(U)
예제 #13
0
    def __init__(self,
                 model,
                 damp,
                 data,
                 recin,
                 u,
                 time_order=2,
                 spc_order=6,
                 **kwargs):
        nrec, nt = data.shape
        dt = model.get_critical_dt()
        v = TimeData(name="v",
                     shape=model.get_shape_comp(),
                     time_dim=nt,
                     time_order=time_order,
                     space_order=spc_order,
                     save=False,
                     dtype=damp.dtype)
        m = DenseData(name="m", shape=model.get_shape_comp(), dtype=damp.dtype)
        m.data[:] = model.padm()
        v.pad_time = False
        rec = SourceLike(name="rec",
                         npoint=nrec,
                         nt=nt,
                         dt=dt,
                         h=model.get_spacing(),
                         coordinates=data.receiver_coords,
                         ndim=len(damp.shape),
                         dtype=damp.dtype,
                         nbpml=model.nbpml)
        rec.data[:] = recin
        grad = DenseData(name="grad", shape=m.shape, dtype=m.dtype)

        # Derive stencil from symbolic equation
        eqn = m * v.dt2 - v.laplace - damp * v.dt
        stencil = solve(eqn, v.backward)[0]

        # Add substitutions for spacing (temporal and spatial)
        s, h = symbols('s h')
        subs = {s: model.get_critical_dt(), h: model.get_spacing()}
        # Add Gradient-specific updates. The dt2 is currently hacky
        #  as it has to match the cyclic indices
        gradient_update = Eq(
            grad,
            grad - s**-2 * (v + v.forward - 2 * v.forward.forward) * u.forward)
        stencils = [gradient_update, Eq(v.backward, stencil)]
        super(GradientOperator, self).__init__(rec.nt - 1,
                                               m.shape,
                                               stencils=stencils,
                                               subs=[subs, subs, {}],
                                               spc_border=spc_order / 2,
                                               time_order=time_order,
                                               forward=False,
                                               dtype=m.dtype,
                                               input_params=[m, v, damp, u],
                                               **kwargs)
        # Insert receiver term post-hoc
        self.input_params += [grad, rec, rec.coordinates]
        self.output_params = [grad]
        self.propagator.time_loop_stencils_b = rec.add(m, v, t + 1)
        self.propagator.add_devito_param(rec)
        self.propagator.add_devito_param(rec.coordinates)
예제 #14
0
def ForwardOperator(model,
                    u,
                    src,
                    rec,
                    data,
                    q,
                    time_order=2,
                    spc_order=6,
                    save=False,
                    tsave=4.0,
                    free_surface=False,
                    **kwargs):
    nt = data.shape[0]
    dt = model.critical_dt
    s = t.spacing
    m, damp, rho = model.m, model.damp, model.rho
    Lap, rho = acoustic_laplacian(u, rho)
    # Derive stencil from symbolic equation
    eqn = m / rho * u.dt2 - Lap + damp * u.dt + q
    # stencil = solve(eqn, u.forward)[0]
    stencil = solve(eqn, u.forward, rational=False)[0]
    # Add substitutions for spacing (temporal and spatial)
    subs = dict([(s, dt)] +
                [(i.spacing, model.get_spacing()[j])
                 for i, j in zip(u.indices[1:], range(len(model.shape)))])
    stencils = [Eq(u.forward, stencil)]
    # Create stencil expressions for operator, source and receivers
    ti = u.indices[0]
    src_term = src.inject(field=u.forward,
                          offset=model.nbpml,
                          expr=rho * src * dt**2 / m)
    # Create interpolation expression for receivers
    rec_term = rec.interpolate(expr=u, offset=model.nbpml)
    stencils = stencils + src_term + rec_term
    if save:
        nsave = int(nt / (tsave / dt) + 1)
        rate = int(nt / nsave) + 1
        usave = TimeData(name="usave",
                         shape=model.shape_domain,
                         time_dim=nt,
                         time_order=2,
                         space_order=spc_order,
                         save=True,
                         dtype=model.dtype)
        stencils += [
            Eq(usave.subs(usave.indices[0],
                          Function('INT')(time / rate)), u)
        ]

    if free_surface:
        fs = Dimension(name="fs", size=model.nbpml)
        stencils += [
            Eq(u.forward.subs({u.indices[-1]: fs}),
               -u.forward.subs({u.indices[-1]: 2 * model.nbpml - fs}))
        ]

    dse = kwargs.get('dse', 'advanced')
    dle = kwargs.get('dle', 'advanced')
    op = Operator(stencils,
                  subs=subs,
                  dse=dse,
                  dle=dle,
                  time_axis=Forward,
                  name="Forward%s" % randint(1e5),
                  profiler=False,
                  external=True)

    return op
예제 #15
0
    def __init__(self, model, src, damp, data, time_order=2, spc_order=4, save=False,
                 **kwargs):
        nrec, nt = data.shape
        dt = model.get_critical_dt()
        u = TimeData(name="u", shape=model.get_shape_comp(),
                     time_dim=nt, time_order=time_order,
                     space_order=spc_order,
                     save=save, dtype=damp.dtype)
        v = TimeData(name="v", shape=model.get_shape_comp(),
                     time_dim=nt, time_order=time_order,
                     space_order=spc_order,
                     save=save, dtype=damp.dtype)
        m = DenseData(name="m", shape=model.get_shape_comp(),
                      dtype=damp.dtype)
        m.data[:] = model.padm()

        if model.epsilon is not None:
            epsilon = DenseData(name="epsilon", shape=model.get_shape_comp(),
                                dtype=damp.dtype)
            epsilon.data[:] = model.pad(model.epsilon)
        else:
            epsilon = 1.0

        if model.delta is not None:
            delta = DenseData(name="delta", shape=model.get_shape_comp(),
                              dtype=damp.dtype)
            delta.data[:] = model.pad(model.delta)
        else:
            delta = 1.0
        if model.theta is not None:
            theta = DenseData(name="theta", shape=model.get_shape_comp(),
                              dtype=damp.dtype)
            theta.data[:] = model.pad(model.theta)
        else:
            theta = 0

        if len(model.get_shape_comp()) == 3:
            if model.phi is not None:
                phi = DenseData(name="phi", shape=model.get_shape_comp(),
                                dtype=damp.dtype)
                phi.data[:] = model.pad(model.phi)
            else:
                phi = 0

        u.pad_time = save
        v.pad_time = save
        rec = SourceLike(name="rec", npoint=nrec, nt=nt, dt=dt,
                         h=model.get_spacing(),
                         coordinates=data.receiver_coords,
                         ndim=len(damp.shape),
                         dtype=damp.dtype,
                         nbpml=model.nbpml)

        def Bhaskarasin(angle):
            if angle == 0:
                return 0
            else:
                return (16.0 * angle * (3.1416 - abs(angle)) /
                        (49.3483 - 4.0 * abs(angle) * (3.1416 - abs(angle))))

        def Bhaskaracos(angle):
            if angle == 0:
                return 1.0
            else:
                return Bhaskarasin(angle + 1.5708)

        Hp, Hzr = symbols('Hp Hzr')
        if len(m.shape) == 3:
            ang0 = Function('ang0')(x, y, z)
            ang1 = Function('ang1')(x, y, z)
            ang2 = Function('ang2')(x, y, z)
            ang3 = Function('ang3')(x, y, z)
        else:
            ang0 = Function('ang0')(x, y)
            ang1 = Function('ang1')(x, y)

        s, h = symbols('s h')

        ang0 = Bhaskaracos(theta)
        ang1 = Bhaskarasin(theta)
        spc_brd = spc_order / 2
        # Derive stencil from symbolic equation
        if len(m.shape) == 3:
            ang2 = Bhaskaracos(phi)
            ang3 = Bhaskarasin(phi)

            Gy1p = (ang3 * u.dxl - ang2 * u.dyl)
            Gyy1 = (first_derivative(Gy1p, ang3, dim=x, side=right, order=spc_brd) -
                    first_derivative(Gy1p, ang2, dim=y, side=right, order=spc_brd))

            Gy2p = (ang3 * u.dxr - ang2 * u.dyr)
            Gyy2 = (first_derivative(Gy2p, ang3, dim=x, side=left, order=spc_brd) -
                    first_derivative(Gy2p, ang2, dim=y, side=left, order=spc_brd))

            Gx1p = (ang0 * ang2 * u.dxl + ang0 * ang3 * u.dyl - ang1 * u.dzl)
            Gz1r = (ang1 * ang2 * v.dxl + ang1 * ang3 * v.dyl + ang0 * v.dzl)
            Gxx1 = (first_derivative(Gx1p, ang0, ang2,
                                     dim=x, side=right, order=spc_brd) +
                    first_derivative(Gx1p, ang0, ang3,
                                     dim=y, side=right, order=spc_brd) -
                    first_derivative(Gx1p, ang1, dim=z, side=right, order=spc_brd))
            Gzz1 = (first_derivative(Gz1r, ang1, ang2,
                                     dim=x, side=right, order=spc_brd) +
                    first_derivative(Gz1r, ang1, ang3,
                                     dim=y, side=right, order=spc_brd) +
                    first_derivative(Gz1r, ang0, dim=z, side=right, order=spc_brd))

            Gx2p = (ang0 * ang2 * u.dxr + ang0 * ang3 * u.dyr - ang1 * u.dzr)
            Gz2r = (ang1 * ang2 * v.dxr + ang1 * ang3 * v.dyr + ang0 * v.dzr)
            Gxx2 = (first_derivative(Gx2p, ang0, ang2,
                                     dim=x, side=left, order=spc_brd) +
                    first_derivative(Gx2p, ang0, ang3,
                                     dim=y, side=left, order=spc_brd) -
                    first_derivative(Gx2p, ang1, dim=z, side=left, order=spc_brd))
            Gzz2 = (first_derivative(Gz2r, ang1, ang2,
                                     dim=x, side=left, order=spc_brd) +
                    first_derivative(Gz2r, ang1, ang3,
                                     dim=y, side=left, order=spc_brd) +
                    first_derivative(Gz2r, ang0,
                                     dim=z, side=left, order=spc_brd))
            parm = [m, damp, epsilon, delta, theta, phi, u, v]
        else:
            Gyy2 = 0
            Gyy1 = 0
            parm = [m, damp, epsilon, delta, theta, u, v]
            Gx1p = (ang0 * u.dxr - ang1 * u.dy)
            Gz1r = (ang1 * v.dxr + ang0 * v.dy)
            Gxx1 = (first_derivative(Gx1p * ang0, dim=x,
                                     side=left, order=spc_brd) -
                    first_derivative(Gx1p * ang1, dim=y,
                                     side=centered, order=spc_brd))
            Gzz1 = (first_derivative(Gz1r * ang1, dim=x,
                                     side=left, order=spc_brd) +
                    first_derivative(Gz1r * ang0, dim=y,
                                     side=centered, order=spc_brd))
            Gx2p = (ang0 * u.dx - ang1 * u.dyr)
            Gz2r = (ang1 * v.dx + ang0 * v.dyr)
            Gxx2 = (first_derivative(Gx2p * ang0, dim=x,
                                     side=centered, order=spc_brd) -
                    first_derivative(Gx2p * ang1, dim=y,
                                     side=left, order=spc_brd))
            Gzz2 = (first_derivative(Gz2r * ang1, dim=x,
                                     side=centered, order=spc_brd) +
                    first_derivative(Gz2r * ang0, dim=y,
                                     side=left, order=spc_brd))

        stencilp = 1.0 / (2.0 * m + s * damp) * \
            (4.0 * m * u + (s * damp - 2.0 * m) *
             u.backward + 2.0 * s**2 * (epsilon * Hp + delta * Hzr))
        stencilr = 1.0 / (2.0 * m + s * damp) * \
            (4.0 * m * v + (s * damp - 2.0 * m) *
             v.backward + 2.0 * s**2 * (delta * Hp + Hzr))

        Hp_val = -(.5 * Gxx1 + .5 * Gxx2 + .5 * Gyy1 + .5 * Gyy2)
        Hzr_val = -(.5 * Gzz1 + .5 * Gzz2)
        factorized = {Hp: Hp_val, Hzr: Hzr_val}
        # Add substitutions for spacing (temporal and spatial)
        subs = [{s: src.dt, h: src.h}, {s: src.dt, h: src.h}]
        first_stencil = Eq(u.forward, stencilp.xreplace(factorized))
        second_stencil = Eq(v.forward, stencilr.xreplace(factorized))
        stencils = [first_stencil, second_stencil]
        super(ForwardOperator, self).__init__(src.nt, m.shape,
                                              stencils=stencils,
                                              subs=subs,
                                              spc_border=spc_order/2 + 2,
                                              time_order=time_order,
                                              forward=True,
                                              dtype=m.dtype,
                                              input_params=parm,
                                              **kwargs)

        # Insert source and receiver terms post-hoc
        self.input_params += [src, src.coordinates, rec, rec.coordinates]
        self.output_params += [v, rec]
        self.propagator.time_loop_stencils_a = (src.add(m, u) + src.add(m, v) +
                                                rec.read2(u, v))
        self.propagator.add_devito_param(src)
        self.propagator.add_devito_param(src.coordinates)
        self.propagator.add_devito_param(rec)
        self.propagator.add_devito_param(rec.coordinates)