コード例 #1
0
ファイル: wavesolver.py プロジェクト: tccw/devito
    def forward(self,
                src=None,
                rec=None,
                u=None,
                v=None,
                m=None,
                epsilon=None,
                delta=None,
                theta=None,
                phi=None,
                save=False,
                kernel='centered',
                **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 (u+v)
        :param u: (Optional) Symbol to store the computed wavefield first component
        :param v: (Optional) Symbol to store the computed wavefield second component
        :param m: (Optional) Symbol for the time-constant square slowness
        :param epsilon: (Optional) Symbol for the time-constant first Thomsen parameter
        :param delta: (Optional) Symbol for the time-constant second Thomsen parameter
        :param theta: (Optional) Symbol for the time-constant Dip angle (radians)
        :param phi: (Optional) Symbol for the time-constant Azimuth angle (radians)
        :param save: Option to store the entire (unrolled) wavefield
        :param kernel: type of discretization, centered or shifted

        :returns: Receiver, wavefield and performance summary
        """

        # Space order needs to be halved in the shifted case to have an
        # overall space_order discretization
        self.space_order = self.space_order // 2 if kernel == 'shifted' \
            else self.space_order

        time_order = 1 if kernel == 'staggered' else 2
        # Source term is read-only, so re-use the default
        src = src or self.source
        # Create a new receiver object to store the result
        rec = rec or Receiver(name='rec',
                              grid=self.model.grid,
                              time_range=self.receiver.time_range,
                              coordinates=self.receiver.coordinates.data)

        # Create the forward wavefield if not provided
        u = u or TimeFunction(name='u',
                              grid=self.model.grid,
                              save=self.source.nt if save else None,
                              time_order=time_order,
                              space_order=self.space_order)
        # Create the forward wavefield if not provided
        v = v or TimeFunction(name='v',
                              grid=self.model.grid,
                              save=self.source.nt if save else None,
                              time_order=time_order,
                              space_order=self.space_order)

        if kernel == 'staggered':
            vx, vz, vy = particle_velocity_fields(self.model, self.space_order)
            kwargs["vx"] = vx
            kwargs["vz"] = vz
            if vy is not None:
                kwargs["vy"] = vy

        # Pick m from model unless explicitly provided
        kwargs.update(
            self.model.physical_params(m=m,
                                       epsilon=epsilon,
                                       delta=delta,
                                       theta=theta,
                                       phi=phi))
        # Execute operator and return wavefield and receiver data
        op = self.op_fwd(kernel, save)
        summary = op.apply(src=src,
                           rec=rec,
                           u=u,
                           v=v,
                           dt=kwargs.pop('dt', self.dt),
                           **kwargs)
        return rec, u, v, summary
コード例 #2
0
    def forward(self, src=None, rec=None, u=None, v=None, vp=None,
                epsilon=None, delta=None, theta=None, phi=None,
                save=False, kernel='centered', **kwargs):
        """
        Forward modelling function that creates the necessary
        data objects for running a forward modelling operator.

        Parameters
        ----------
        geometry : AcquisitionGeometry
            Geometry object that contains the source (SparseTimeFunction) and
            receivers (SparseTimeFunction) and their position.
        u : TimeFunction, optional
            The computed wavefield first component.
        v : TimeFunction, optional
            The computed wavefield second component.
        vp : Function or float, optional
            The time-constant velocity.
        epsilon : Function or float, optional
            The time-constant first Thomsen parameter.
        delta : Function or float, optional
            The time-constant second Thomsen parameter.
        theta : Function or float, optional
            The time-constant Dip angle (radians).
        phi : Function or float, optional
            The time-constant Azimuth angle (radians).
        save : int or Buffer
            Option to store the entire (unrolled) wavefield.
        kernel : str, optional
            Type of discretization, centered or shifted.

        Returns
        -------
        Receiver, wavefield and performance summary.
        """
        if kernel == 'staggered':
            time_order = 1
            dims = self.model.space_dimensions
            stagg_u = (-dims[-1])
            stagg_v = (-dims[0], -dims[1]) if self.model.grid.dim == 3 else (-dims[0])
        else:
            time_order = 2
            stagg_u = stagg_v = None
        # Source term is read-only, so re-use the default
        src = src or self.geometry.src
        # Create a new receiver object to store the result
        rec = rec or Receiver(name='rec', grid=self.model.grid,
                              time_range=self.geometry.time_axis,
                              coordinates=self.geometry.rec_positions)

        # Create the forward wavefield if not provided
        if u is None:
            u = TimeFunction(name='u', grid=self.model.grid, staggered=stagg_u,
                             save=self.geometry.nt if save else None,
                             time_order=time_order,
                             space_order=self.space_order)
        # Create the forward wavefield if not provided
        if v is None:
            v = TimeFunction(name='v', grid=self.model.grid, staggered=stagg_v,
                             save=self.geometry.nt if save else None,
                             time_order=time_order,
                             space_order=self.space_order)

        if kernel == 'staggered':
            vx, vz, vy = particle_velocity_fields(self.model, self.space_order)
            kwargs["vx"] = vx
            kwargs["vz"] = vz
            if vy is not None:
                kwargs["vy"] = vy

        # Pick vp and Thomsen parameters from model unless explicitly provided
        kwargs.update(self.model.physical_params(
            vp=vp, epsilon=epsilon, delta=delta, theta=theta, phi=phi)
        )
        if self.model.dim < 3:
            kwargs.pop('phi', None)
        # Execute operator and return wavefield and receiver data
        op = self.op_fwd(kernel, save)
        summary = op.apply(src=src, rec=rec, u=u, v=v,
                           dt=kwargs.pop('dt', self.dt), **kwargs)
        return rec, u, v, summary
コード例 #3
0
ファイル: wavesolver.py プロジェクト: georgebisbas/devito
    def forward(self,
                src=None,
                rec=None,
                u=None,
                v=None,
                vp=None,
                epsilon=None,
                delta=None,
                theta=None,
                phi=None,
                save=False,
                kernel='centered',
                **kwargs):
        """
        Forward modelling function that creates the necessary
        data objects for running a forward modelling operator.

        Parameters
        ----------
        geometry : AcquisitionGeometry
            Geometry object that contains the source (SparseTimeFunction) and
            receivers (SparseTimeFunction) and their position.
        u : TimeFunction, optional
            The computed wavefield first component.
        v : TimeFunction, optional
            The computed wavefield second component.
        vp : Function or float, optional
            The time-constant velocity.
        epsilon : Function or float, optional
            The time-constant first Thomsen parameter.
        delta : Function or float, optional
            The time-constant second Thomsen parameter.
        theta : Function or float, optional
            The time-constant Dip angle (radians).
        phi : Function or float, optional
            The time-constant Azimuth angle (radians).
        save : bool, optional
            Whether or not to save the entire (unrolled) wavefield.
        kernel : str, optional
            Type of discretization, centered or shifted.

        Returns
        -------
        Receiver, wavefield and performance summary.
        """
        if kernel == 'staggered':
            time_order = 1
            dims = self.model.space_dimensions
            stagg_u = (-dims[-1])
            stagg_v = (-dims[0],
                       -dims[1]) if self.model.grid.dim == 3 else (-dims[0])
        else:
            time_order = 2
            stagg_u = stagg_v = None
        # Source term is read-only, so re-use the default
        src = src or self.geometry.src
        # Create a new receiver object to store the result
        rec = rec or Receiver(name='rec',
                              grid=self.model.grid,
                              time_range=self.geometry.time_axis,
                              coordinates=self.geometry.rec_positions)
        # Create the forward wavefield if not provided

        if u is None:
            u = TimeFunction(name='u',
                             grid=self.model.grid,
                             staggered=stagg_u,
                             save=self.geometry.nt if save else None,
                             time_order=time_order,
                             space_order=self.space_order)
        # Create the forward wavefield if not provided
        if v is None:
            v = TimeFunction(name='v',
                             grid=self.model.grid,
                             staggered=stagg_v,
                             save=self.geometry.nt if save else None,
                             time_order=time_order,
                             space_order=self.space_order)

        print("Initial Norm u", norm(u))
        print("Initial Norm v", norm(v))

        if kernel == 'staggered':
            vx, vz, vy = particle_velocity_fields(self.model, self.space_order)
            kwargs["vx"] = vx
            kwargs["vz"] = vz
            if vy is not None:
                kwargs["vy"] = vy

        # Pick vp and Thomsen parameters from model unless explicitly provided
        kwargs.update(
            self.model.physical_params(vp=vp,
                                       epsilon=epsilon,
                                       delta=delta,
                                       theta=theta,
                                       phi=phi))
        if self.model.dim < 3:
            kwargs.pop('phi', None)
        # Execute operator and return wavefield and receiver data

        op = self.op_fwd(kernel, save)
        print(kwargs)
        summary = op.apply(src=src,
                           u=u,
                           v=v,
                           dt=kwargs.pop('dt', self.dt),
                           **kwargs)

        regnormu = norm(u)
        regnormv = norm(v)
        print("Norm u:", regnormu)
        print("Norm v:", regnormv)

        if 0:
            cmap = plt.cm.get_cmap("viridis")
            values = u.data[0, :, :, :]
            vistagrid = pv.UniformGrid()
            vistagrid.dimensions = np.array(values.shape) + 1
            vistagrid.spacing = (1, 1, 1)
            vistagrid.origin = (0, 0, 0
                                )  # The bottom left corner of the data set
            vistagrid.cell_arrays["values"] = values.flatten(order="F")
            vistaslices = vistagrid.slice_orthogonal()
            vistagrid.plot(show_edges=True)
            vistaslices.plot(cmap=cmap)

        print("=========================================")

        s_u = TimeFunction(name='s_u',
                           grid=self.model.grid,
                           space_order=self.space_order,
                           time_order=1)
        s_v = TimeFunction(name='s_v',
                           grid=self.model.grid,
                           space_order=self.space_order,
                           time_order=1)

        src_u = src.inject(field=s_u.forward,
                           expr=src * self.model.grid.time_dim.spacing**2 /
                           self.model.m)
        src_v = src.inject(field=s_v.forward,
                           expr=src * self.model.grid.time_dim.spacing**2 /
                           self.model.m)

        op_f = Operator([src_u, src_v])
        op_f.apply(src=src, dt=kwargs.pop('dt', self.dt))

        print("Norm s_u", norm(s_u))
        print("Norm s_v", norm(s_v))

        # Get the nonzero indices
        nzinds = np.nonzero(s_u.data[0])  # nzinds is a tuple
        assert len(nzinds) == len(self.model.grid.shape)
        shape = self.model.grid.shape
        x, y, z = self.model.grid.dimensions
        time = self.model.grid.time_dim
        t = self.model.grid.stepping_dim

        source_mask = Function(name='source_mask',
                               shape=self.model.grid.shape,
                               dimensions=(x, y, z),
                               space_order=0,
                               dtype=np.int32)
        source_id = Function(name='source_id',
                             shape=shape,
                             dimensions=(x, y, z),
                             space_order=0,
                             dtype=np.int32)
        print("source_id data indexes start from 0 now !!!")

        # source_id.data[nzinds[0], nzinds[1], nzinds[2]] = tuple(np.arange(1, len(nzinds[0])+1))
        source_id.data[nzinds[0], nzinds[1],
                       nzinds[2]] = tuple(np.arange(len(nzinds[0])))

        source_mask.data[nzinds[0], nzinds[1], nzinds[2]] = 1
        # plot3d(source_mask.data, model)
        # import pdb; pdb.set_trace()

        print("Number of unique affected points is: %d", len(nzinds[0]))

        # Assert that first and last index are as expected
        assert (source_id.data[nzinds[0][0], nzinds[1][0], nzinds[2][0]] == 0)
        assert (source_id.data[nzinds[0][-1], nzinds[1][-1],
                               nzinds[2][-1]] == len(nzinds[0]) - 1)
        assert (source_id.data[nzinds[0][len(nzinds[0]) - 1],
                               nzinds[1][len(nzinds[0]) - 1],
                               nzinds[2][len(nzinds[0]) -
                                         1]] == len(nzinds[0]) - 1)

        assert (np.all(np.nonzero(source_id.data)) == np.all(
            np.nonzero(source_mask.data)))
        assert (np.all(np.nonzero(source_id.data)) == np.all(
            np.nonzero(s_u.data[0])))

        print(
            "-At this point source_mask and source_id have been popoulated correctly-"
        )

        nnz_shape = (self.model.grid.shape[0], self.model.grid.shape[1])

        nnz_sp_source_mask = Function(name='nnz_sp_source_mask',
                                      shape=(list(nnz_shape)),
                                      dimensions=(x, y),
                                      space_order=0,
                                      dtype=np.int32)

        nnz_sp_source_mask.data[:, :] = source_mask.data[:, :, :].sum(2)
        inds = np.where(source_mask.data == 1.)
        print("Grid - source positions:", inds)
        maxz = len(np.unique(inds[-1]))
        # Change only 3rd dim
        sparse_shape = (self.model.grid.shape[0], self.model.grid.shape[1],
                        maxz)

        assert (len(
            nnz_sp_source_mask.dimensions) == (len(source_mask.dimensions) -
                                               1))

        # Note : sparse_source_id is not needed as long as sparse info is kept in mask
        # sp_source_id.data[inds[0],inds[1],:] = inds[2][:maxz]

        id_dim = Dimension(name='id_dim')
        b_dim = Dimension(name='b_dim')

        save_src_u = TimeFunction(name='save_src_u',
                                  shape=(src.shape[0], nzinds[1].shape[0]),
                                  dimensions=(src.dimensions[0], id_dim))
        save_src_v = TimeFunction(name='save_src_v',
                                  shape=(src.shape[0], nzinds[1].shape[0]),
                                  dimensions=(src.dimensions[0], id_dim))

        save_src_u_term = src.inject(
            field=save_src_u[src.dimensions[0], source_id],
            expr=src * self.model.grid.time_dim.spacing**2 / self.model.m)
        save_src_v_term = src.inject(
            field=save_src_v[src.dimensions[0], source_id],
            expr=src * self.model.grid.time_dim.spacing**2 / self.model.m)

        print("Injecting to empty grids")
        op1 = Operator([save_src_u_term, save_src_v_term])
        op1.apply(src=src, dt=kwargs.pop('dt', self.dt))
        print("Injecting to empty grids finished")
        sp_zi = Dimension(name='sp_zi')

        sp_source_mask = Function(name='sp_source_mask',
                                  shape=(list(sparse_shape)),
                                  dimensions=(x, y, sp_zi),
                                  space_order=0,
                                  dtype=np.int32)

        # Now holds IDs
        sp_source_mask.data[inds[0], inds[1], :] = tuple(
            inds[-1][:len(np.unique(inds[-1]))])

        assert (np.count_nonzero(sp_source_mask.data) == len(nzinds[0]))
        assert (len(sp_source_mask.dimensions) == 3)

        # import pdb; pdb.set_trace()         .

        zind = Scalar(name='zind', dtype=np.int32)
        xb_size = Scalar(name='xb_size', dtype=np.int32)
        yb_size = Scalar(name='yb_size', dtype=np.int32)
        x0_blk0_size = Scalar(name='x0_blk0_size', dtype=np.int32)
        y0_blk0_size = Scalar(name='y0_blk0_size', dtype=np.int32)

        block_sizes = Function(name='block_sizes',
                               shape=(4, ),
                               dimensions=(b_dim, ),
                               space_order=0,
                               dtype=np.int32)

        bsizes = (8, 8, 32, 32)
        block_sizes.data[:] = bsizes

        # eqxb = Eq(xb_size, block_sizes[0])
        # eqyb = Eq(yb_size, block_sizes[1])
        # eqxb2 = Eq(x0_blk0_size, block_sizes[2])
        # eqyb2 = Eq(y0_blk0_size, block_sizes[3])

        eq0 = Eq(sp_zi.symbolic_max,
                 nnz_sp_source_mask[x, y] - 1,
                 implicit_dims=(time, x, y))
        # eq1 = Eq(zind, sp_source_mask[x, sp_zi], implicit_dims=(time, x, sp_zi))
        eq1 = Eq(zind,
                 sp_source_mask[x, y, sp_zi],
                 implicit_dims=(time, x, y, sp_zi))

        inj_u = source_mask[x, y, zind] * save_src_u[time, source_id[x, y,
                                                                     zind]]
        inj_v = source_mask[x, y, zind] * save_src_v[time, source_id[x, y,
                                                                     zind]]

        eq_u = Inc(u.forward[t + 1, x, y, zind],
                   inj_u,
                   implicit_dims=(time, x, y, sp_zi))
        eq_v = Inc(v.forward[t + 1, x, y, zind],
                   inj_v,
                   implicit_dims=(time, x, y, sp_zi))

        # The additional time-tiling equations
        # tteqs = (eqxb, eqyb, eqxb2, eqyb2, eq0, eq1, eq_u, eq_v)

        performance_map = np.array([[0, 0, 0, 0, 0]])

        bxstart = 4
        bxend = 17
        bystart = 4
        byend = 17
        bstep = 16

        txstart = 8
        txend = 9
        tystart = 8
        tyend = 9

        tstep = 16
        # Temporal autotuning
        for tx in range(txstart, txend, tstep):
            # import pdb; pdb.set_trace()
            for ty in range(tystart, tyend, tstep):
                for bx in range(bxstart, bxend, bstep):
                    for by in range(bystart, byend, bstep):

                        block_sizes.data[:] = [tx, ty, bx, by]

                        eqxb = Eq(xb_size, block_sizes[0])
                        eqyb = Eq(yb_size, block_sizes[1])
                        eqxb2 = Eq(x0_blk0_size, block_sizes[2])
                        eqyb2 = Eq(y0_blk0_size, block_sizes[3])

                        u.data[:] = 0
                        v.data[:] = 0
                        print("-----")
                        tteqs = (eqxb, eqyb, eqxb2, eqyb2, eq0, eq1, eq_u,
                                 eq_v)

                        op_tt = self.op_fwd(kernel, save, tteqs)
                        summary_tt = op_tt.apply(u=u,
                                                 v=v,
                                                 dt=kwargs.pop('dt', self.dt),
                                                 **kwargs)
                        norm_tt_u = norm(u)
                        norm_tt_v = norm(v)
                        print("Norm u:", regnormu)
                        print("Norm v:", regnormv)
                        print("Norm(tt_u):", norm_tt_u)
                        print("Norm(tt_v):", norm_tt_v)

                        print(
                            "===Temporal blocking======================================"
                        )

                        performance_map = np.append(performance_map, [[
                            tx, ty, bx, by,
                            summary_tt.globals['fdlike'].gflopss
                        ]], 0)

                print(performance_map)
                # tids = np.unique(performance_map[:, 0])

                #for tid in tids:
                bids = np.where((performance_map[:, 0] == tx)
                                & (performance_map[:, 1] == ty))
                bx_data = np.unique(performance_map[bids, 2])
                by_data = np.unique(performance_map[bids, 3])
                gptss_data = performance_map[bids, 4]
                gptss_data = gptss_data.reshape(len(bx_data), len(by_data))

                fig, ax = plt.subplots()
                im = ax.imshow(gptss_data)
                pause(2)

                # We want to show all ticks...
                ax.set_xticks(np.arange(len(bx_data)))
                ax.set_yticks(np.arange(len(by_data)))
                # ... and label them with the respective list entries
                ax.set_xticklabels(bx_data)
                ax.set_yticklabels(by_data)

                ax.set_title(
                    "Gpts/s for fixed tile size. (Sweeping block sizes)")
                fig.tight_layout()

                fig.colorbar(im, ax=ax)
                # ax = sns.heatmap(gptss_data, linewidth=0.5)
                plt.savefig(
                    str(shape[0]) + str(np.int32(tx)) + str(np.int32(ty)) +
                    ".pdf")

        if 0:
            cmap = plt.cm.get_cmap("viridis")
            values = u.data[0, :, :, :]
            vistagrid = pv.UniformGrid()
            vistagrid.dimensions = np.array(values.shape) + 1
            vistagrid.spacing = (1, 1, 1)
            vistagrid.origin = (0, 0, 0
                                )  # The bottom left corner of the data set
            vistagrid.cell_arrays["values"] = values.flatten(order="F")
            vistaslices = vistagrid.slice_orthogonal()
            vistagrid.plot(show_edges=True)
            vistaslices.plot(cmap=cmap)

        return rec, u, v, summary
コード例 #4
0
    def adjoint(self,
                rec,
                srca=None,
                p=None,
                r=None,
                model=None,
                save=None,
                **kwargs):
        """
        Adjoint modelling function that creates the necessary
        data objects for running an adjoint modelling operator.

        Parameters
        ----------
        geometry : AcquisitionGeometry
            Geometry object that contains the source (SparseTimeFunction) and
            receivers (SparseTimeFunction) and their position.
        p : TimeFunction, optional
            The computed wavefield first component.
        r : TimeFunction, optional
            The computed wavefield second component.
        model : Model, optional
            Object containing the physical parameters.
        vp : Function or float, optional
            The time-constant velocity.
        epsilon : Function or float, optional
            The time-constant first Thomsen parameter.
        delta : Function or float, optional
            The time-constant second Thomsen parameter.
        theta : Function or float, optional
            The time-constant Dip angle (radians).
        phi : Function or float, optional
            The time-constant Azimuth angle (radians).

        Returns
        -------
        Adjoint source, wavefield and performance summary.
        """
        if self.kernel == 'staggered':
            time_order = 1
            dims = self.model.space_dimensions
            stagg_p = (-dims[-1])
            stagg_r = (-dims[0],
                       -dims[1]) if self.model.grid.dim == 3 else (-dims[0])
        else:
            time_order = 2
            stagg_p = stagg_r = None

        # Source term is read-only, so re-use the default
        srca = srca or self.geometry.new_src(name='srca', src_type=None)

        # Create the wavefield if not provided
        if p is None:
            p = TimeFunction(name='p',
                             grid=self.model.grid,
                             staggered=stagg_p,
                             time_order=time_order,
                             space_order=self.space_order)
        # Create the wavefield if not provided
        if r is None:
            r = TimeFunction(name='r',
                             grid=self.model.grid,
                             staggered=stagg_r,
                             time_order=time_order,
                             space_order=self.space_order)

        if self.kernel == 'staggered':
            vx, vz, vy = particle_velocity_fields(self.model, self.space_order)
            kwargs["vx"] = vx
            kwargs["vz"] = vz
            if vy is not None:
                kwargs["vy"] = vy

        model = model or self.model
        # Pick vp and Thomsen parameters from model unless explicitly provided
        kwargs.update(model.physical_params(**kwargs))
        if self.model.dim < 3:
            kwargs.pop('phi', None)
        # Execute operator and return wavefield and receiver data
        summary = self.op_adj().apply(srca=srca,
                                      rec=rec,
                                      p=p,
                                      r=r,
                                      dt=kwargs.pop('dt', self.dt),
                                      time_m=0 if time_order == 1 else None,
                                      **kwargs)
        return srca, p, r, summary
コード例 #5
0
ファイル: wavesolver.py プロジェクト: opesci/devito
    def forward(self, src=None, rec=None, u=None, v=None, m=None,
                epsilon=None, delta=None, theta=None, phi=None,
                save=False, kernel='centered', **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 (u+v)
        :param u: (Optional) Symbol to store the computed wavefield first component
        :param v: (Optional) Symbol to store the computed wavefield second component
        :param m: (Optional) Symbol for the time-constant square slowness
        :param epsilon: (Optional) Symbol for the time-constant first Thomsen parameter
        :param delta: (Optional) Symbol for the time-constant second Thomsen parameter
        :param theta: (Optional) Symbol for the time-constant Dip angle (radians)
        :param phi: (Optional) Symbol for the time-constant Azimuth angle (radians)
        :param save: Option to store the entire (unrolled) wavefield
        :param kernel: type of discretization, centered or shifted

        :returns: Receiver, wavefield and performance summary
        """

        if kernel == 'staggered':
            time_order = 1
            dims = self.model.space_dimensions
            stagg_u = (-dims[-1])
            stagg_v = (-dims[0], -dims[1]) if self.model.grid.dim == 3 else (-dims[0])
        else:
            time_order = 2
            stagg_u = stagg_v = None
        # Source term is read-only, so re-use the default
        src = src or self.geometry.src
        # Create a new receiver object to store the result
        rec = rec or Receiver(name='rec', grid=self.model.grid,
                              time_range=self.geometry.time_axis,
                              coordinates=self.geometry.rec_positions)

        # Create the forward wavefield if not provided
        if u is None:
            u = TimeFunction(name='u', grid=self.model.grid, staggered=stagg_u,
                             save=self.geometry.nt if save else None,
                             time_order=time_order, space_order=self.space_order)
        # Create the forward wavefield if not provided
        if v is None:
            v = TimeFunction(name='v', grid=self.model.grid, staggered=stagg_v,
                             save=self.geometry.nt if save else None,
                             time_order=time_order, space_order=self.space_order)

        if kernel == 'staggered':
            vx, vz, vy = particle_velocity_fields(self.model, self.space_order)
            kwargs["vx"] = vx
            kwargs["vz"] = vz
            if vy is not None:
                kwargs["vy"] = vy

        # Pick m from model unless explicitly provided
        kwargs.update(self.model.physical_params(m=m, epsilon=epsilon, delta=delta,
                                                 theta=theta, phi=phi))
        # Execute operator and return wavefield and receiver data
        op = self.op_fwd(kernel, save)
        summary = op.apply(src=src, rec=rec, u=u, v=v,
                           dt=kwargs.pop('dt', self.dt), **kwargs)
        return rec, u, v, summary