Example #1
0
def cross_derivative(*args, **kwargs):
    """Derives corss derivative for a product of given functions.

    :param \*args: All positional arguments must be fully qualified
       function objects, eg. `f(x, y)` or `g(t, x, y, z)`.
    :param dims: 2-tuple of symbols defining the dimension wrt. which
       to differentiate, eg. `x`, `y`, `z` or `t`.
    :param diff: Finite Difference symbol to insert, default `h`.
    :returns: The cross derivative

    Example: Deriving the cross-derivative of f(x, y)*g(x, y) wrt. x and y via:
       ``cross_derivative(f(x, y), g(x, y), dims=(x, y))``
       results in:
       ``0.5*(-2.0*f(x, y)*g(x, y) + f(x, -h + y)*g(x, -h + y) +``
       ``f(x, h + y)*g(x, h + y) + f(-h + x, y)*g(-h + x, y) -``
       ``f(-h + x, h + y)*g(-h + x, h + y) + f(h + x, y)*g(h + x, y) -``
       ``f(h + x, -h + y)*g(h + x, -h + y)) / h**2``
    """
    dims = kwargs.get('dims', (x, y))
    diff = kwargs.get('diff', h)
    order = kwargs.get('order', 1)

    assert(isinstance(dims, tuple) and len(dims) == 2)
    deriv = 0

    # Stencil positions for non-symmetric cross-derivatives with symmetric averaging
    ind1r = [(dims[0] + i * diff)
             for i in range(-int(order / 2) + 1 - (order < 4),
                            int((order + 1) / 2) + 2 - (order < 4))]
    ind2r = [(dims[1] + i * diff)
             for i in range(-int(order / 2) + 1 - (order < 4),
                            int((order + 1) / 2) + 2 - (order < 4))]
    ind1l = [(dims[0] - i * diff)
             for i in range(-int(order / 2) + 1 - (order < 4),
                            int((order + 1) / 2) + 2 - (order < 4))]
    ind2l = [(dims[1] - i * diff)
             for i in range(-int(order / 2) + 1 - (order < 4),
                            int((order + 1) / 2) + 2 - (order < 4))]

    # Finite difference weights from Taylor approximation with this positions
    c1 = finite_diff_weights(1, ind1r, dims[0])
    c1 = c1[-1][-1]
    c2 = finite_diff_weights(1, ind1l, dims[0])
    c2 = c2[-1][-1]

    # Diagonal elements
    for i in range(0, len(ind1r)):
        for j in range(0, len(ind2r)):
            var1 = [a.subs({dims[0]: ind1r[i], dims[1]: ind2r[j]}) for a in args]
            var2 = [a.subs({dims[0]: ind1l[i], dims[1]: ind2l[j]}) for a in args]
            deriv += (.5 * c1[i] * c1[j] * reduce(mul, var1, 1) +
                      .5 * c2[-(j+1)] * c2[-(i+1)] * reduce(mul, var2, 1))

    return -deriv
Example #2
0
    def generate_subs(deriv_order, function, index):
        dim = retrieve_dimensions(index)[0]

        if dim.is_Time:
            fd_order = function.time_order
        elif dim.is_Space:
            fd_order = function.space_order
        else:
            # Shouldn't arrive here
            raise TypeError("Dimension type not recognised")

        subs = {}

        mapper = {dim: index}

        indices, x0 = generate_indices(function, dim,
                                       fd_order, side=None, x0=mapper)

        coeffs = sympy.finite_diff_weights(deriv_order, indices, x0)[-1][-1]

        for j in range(len(coeffs)):
            subs.update({function._coeff_symbol
                        (indices[j], deriv_order, function, index): coeffs[j]})

        return subs
Example #3
0
    def generate_subs(deriv_order, function, dim):

        if dim.is_Time:
            fd_order = function.time_order
        elif dim.is_Space:
            fd_order = function.space_order
        else:
            # Shouldn't arrive here
            raise TypeError("Dimension type not recognised")

        side = form_side((dim, ), function)
        stagger = side.get(dim)

        subs = {}

        indices, x0 = generate_indices(function,
                                       dim,
                                       dim.spacing,
                                       fd_order,
                                       side=None,
                                       stagger=stagger)

        coeffs = sympy.finite_diff_weights(deriv_order, indices, x0)[-1][-1]

        for j in range(len(coeffs)):
            subs.update({
                function._coeff_symbol(indices[j], deriv_order, function, dim):
                coeffs[j]
            })

        return subs
Example #4
0
def second_derivative(*args, **kwargs):
    """Derives second derivative for a product of given functions.

    :param \*args: All positional arguments must be fully qualified
       function objects, eg. `f(x, y)` or `g(t, x, y, z)`.
    :param dim: Symbol defininf the dimension wrt. which to
       differentiate, eg. `x`, `y`, `z` or `t`.
    :param diff: Finite Difference symbol to insert, default `h`.
    :param order: Discretisation order of the stencil to create.
    :returns: The second derivative

    Example: Deriving the second derivative of f(x, y)*g(x, y) wrt. x via
       ``second_derivative(f(x, y), g(x, y), order=2, dim=x)``
       results in ``(-2.0*f(x, y)*g(x, y) + 1.0*f(-h + x, y)*g(-h + x, y) +
       1.0*f(h + x, y)*g(h + x, y)) / h**2``.
    """
    order = kwargs.get('order', 2)
    dim = kwargs.get('dim')
    diff = kwargs.get('diff', dim.spacing)

    ind = [(dim + i * diff) for i in range(-int(order / 2),
                                           int(order / 2) + 1)]

    coeffs = finite_diff_weights(2, ind, dim)[-1][-1]
    deriv = 0

    for i in range(0, len(ind)):
        var = [a.subs({dim: ind[i]}) for a in args]
        deriv += coeffs[i] * reduce(mul, var, 1)
    return deriv
Example #5
0
def generic_derivative(expr, deriv_order, dim, fd_order, **kwargs):
    """
    Create generic arbitrary order derivative expression from a
    single :class:`Function` object. This methods is essentially a
    dedicated wrapper around SymPy's `as_finite_diff` utility for
    :class:`devito.Function` objects.

    :param function: The symbol representing a function.
    :param deriv_order: Derivative order, eg. 2 for a second derivative.
    :param dim: The dimension for which to take the derivative.
    :param fd_order: Order of the coefficient discretization and thus
                     the width of the resulting stencil expression.
    """
    indices = [(dim + i * dim.spacing)
               for i in range(-fd_order // 2, fd_order // 2 + 1)]
    if fd_order == 1:
        indices = [dim, dim + dim.spacing]
    c = finite_diff_weights(deriv_order, indices, dim)[-1][-1]
    deriv = 0
    all_dims = tuple(
        set((dim, ) + tuple([i for i in expr.indices if i.root == dim])))
    for i in range(0, len(indices)):
        subs = dict([(d, indices[i].subs({dim: d})) for d in all_dims])
        deriv += expr.subs(subs) * c[i]

    return deriv.evalf(_PRECISION)
Example #6
0
def staggered_diff(expr, deriv_order, dim, fd_order, stagger=centered):
    """
    Utility to generate staggered derivatives.

    :param expr: A :class:`Function` object.
    :param dims: The :class:`Dimension`s w.r.t. the derivative is computed.
    :param order: Order of the discretization coefficient (note: this impacts
                  the width of the resulting stencil expression).
    :param stagger: (Optional) shift for the FD, `left`, `right` or `centered`.
    """

    if stagger == left:
        off = -.5
    elif stagger == right:
        off = .5
    else:
        off = 0
    diff = dim.spacing
    idx = list(
        set([(dim + int(i + .5 + off) * diff)
             for i in range(-int(fd_order / 2), int(fd_order / 2))]))
    if fd_order // 2 == 1:
        idx = [dim + diff, dim] if stagger == right else [dim - diff, dim]
    c = finite_diff_weights(deriv_order, idx, dim + off * dim.spacing)[-1][-1]
    deriv = 0
    for i in range(0, len(idx)):
        deriv += expr.subs({dim: idx[i]}) * c[i]

    return deriv.evalf(_PRECISION)
Example #7
0
def second_derivative(expr, **kwargs):
    """Derives second derivative for a product of given functions.

    :param \*args: All positional arguments must be fully qualified
       function objects, eg. `f(x, y)` or `g(t, x, y, z)`.
    :param dim: Symbol defininf the dimension wrt. which to
       differentiate, eg. `x`, `y`, `z` or `t`.
    :param diff: Finite Difference symbol to insert, default `h`.
    :param order: Discretisation order of the stencil to create.
    :returns: The second derivative

    Example: Deriving the second derivative of f(x, y)*g(x, y) wrt. x via
       ``second_derivative(f(x, y), g(x, y), order=2, dim=x)``
       results in ``(-2.0*f(x, y)*g(x, y) + 1.0*f(-h + x, y)*g(-h + x, y) +
       1.0*f(h + x, y)*g(h + x, y)) / h**2``.
    """

    order = kwargs.get('order', 2)
    dim = kwargs.get('dim')
    diff = kwargs.get('diff', dim.spacing)

    ind = [(dim + i * diff) for i in range(-int(order / 2),
                                           int(order / 2) + 1)]

    coeffs = finite_diff_weights(2, ind, dim)[-1][-1]
    deriv = 0
    all_dims = tuple(
        set((dim, ) + tuple([i for i in expr.indices if i.root == dim])))
    for i in range(0, len(ind)):
        subs = dict([(d, ind[i].subs({dim: d})) for d in all_dims])
        deriv += coeffs[i] * expr.subs(subs)
    return deriv.evalf(_PRECISION)
Example #8
0
def generic_derivative(expr, dim, fd_order, deriv_order, stagger=None):
    """
    Arbitrary-order derivative of a given expression.

    Parameters
    ----------
    expr : expr-like
        Expression for which the derivative is produced.
    dim : Dimension
        The Dimension w.r.t. which to differentiate.
    fd_order : int
        Coefficient discretization order. Note: this impacts the width of
        the resulting stencil.
    deriv_order : int
        Derivative order, e.g. 2 for a second-order derivative.
    stagger : Side, optional
        Shift of the finite-difference approximation.

    Returns
    -------
    expr-like
        ``deriv-order`` derivative of ``expr``.
    """

    diff = dim.spacing

    if stagger == left or not expr.is_Staggered:
        off = -.5
    elif stagger == right:
        off = .5
    else:
        off = 0

    if expr.is_Staggered:
        indices = list(
            set([(dim + int(i + .5 + off) * dim.spacing)
                 for i in range(-fd_order // 2, fd_order // 2)]))
        x0 = (dim + off * diff)
        if fd_order < 2:
            indices = [dim +
                       diff, dim] if stagger == right else [dim - diff, dim]

    else:
        indices = [(dim + i * dim.spacing)
                   for i in range(-fd_order // 2, fd_order // 2 + 1)]
        x0 = dim
        if fd_order < 2:
            indices = [dim, dim + diff]

    c = finite_diff_weights(deriv_order, indices, x0)[-1][-1]

    deriv = 0
    all_dims = tuple(
        set((dim, ) + tuple([i for i in expr.indices if i.root == dim])))
    for i in range(0, len(indices)):
        subs = dict([(d, indices[i].subs({dim: d})) for d in all_dims])
        deriv += expr.subs(subs) * c[i]

    return deriv.evalf(_PRECISION)
Example #9
0
def generic_derivative(expr,
                       dim,
                       fd_order,
                       deriv_order,
                       stagger=None,
                       symbolic=False,
                       matvec=direct):
    """
    Arbitrary-order derivative of a given expression.

    Parameters
    ----------
    expr : expr-like
        Expression for which the derivative is produced.
    dim : Dimension
        The Dimension w.r.t. which to differentiate.
    fd_order : int
        Coefficient discretization order. Note: this impacts the width of
        the resulting stencil.
    deriv_order : int
        Derivative order, e.g. 2 for a second-order derivative.
    stagger : Side, optional
        Shift of the finite-difference approximation.

    Returns
    -------
    expr-like
        ``deriv-order`` derivative of ``expr``.
    """
    diff = dim.spacing
    adjoint_val = matvec.val**deriv_order

    # Stencil positions
    indices, x0 = generate_indices(expr, dim, diff, fd_order, stagger=stagger)

    # Finite difference weights from Taylor approximation with these positions
    if symbolic:
        c = symbolic_weights(expr, deriv_order, indices, x0)
    else:
        c = finite_diff_weights(deriv_order, indices, x0)[-1][-1]

    # Loop through positions
    deriv = 0
    all_dims = tuple(
        set((dim, ) + tuple(i for i in expr.indices if i.root == dim)))
    for i in range(len(indices)):
        subs = dict((d, indices[i].subs({dim: d})) for d in all_dims)
        deriv += expr.subs(subs) * c[i]

    # Evaluate up to _PRECISION digits
    deriv = (adjoint_val * deriv).evalf(_PRECISION)

    return deriv
Example #10
0
def generic_derivative(expr,
                       dim,
                       fd_order,
                       deriv_order,
                       stagger=None,
                       symbolic=False):
    """
    Arbitrary-order derivative of a given expression.

    Parameters
    ----------
    expr : expr-like
        Expression for which the derivative is produced.
    dim : Dimension
        The Dimension w.r.t. which to differentiate.
    fd_order : int
        Coefficient discretization order. Note: this impacts the width of
        the resulting stencil.
    deriv_order : int
        Derivative order, e.g. 2 for a second-order derivative.
    stagger : Side, optional
        Shift of the finite-difference approximation.

    Returns
    -------
    expr-like
        ``deriv-order`` derivative of ``expr``.
    """

    diff = dim.spacing

    indices, x0 = generate_indices(expr, dim, diff, fd_order, stagger=stagger)

    if symbolic:
        c = symbolic_weights(expr, deriv_order, indices, x0)
    else:
        c = finite_diff_weights(deriv_order, indices, x0)[-1][-1]

    deriv = 0
    all_dims = tuple(
        set((dim, ) + tuple([i for i in expr.indices if i.root == dim])))
    for i in range(0, len(indices)):
        subs = dict((d, indices[i].subs({dim: d})) for d in all_dims)
        deriv += expr.subs(subs) * c[i]

    return deriv.evalf(_PRECISION)
def standard_stencil(deriv,
                     space_order,
                     offset=0.,
                     as_float=True,
                     as_dict=False):
    """
    Generate a stencil expression with standard weightings. Offset can be
    applied to this stencil to evaluate at non-node positions.

    Parameters
    ----------
    deriv : int
        The derivative order for the stencil
    space_order : int
        The space order of the discretization
    offset : float
        The offset at which the derivative is to be evaluated. In grid
        increments. Default is 0.
    as_float : bool
        Convert stencil to np.float32. Default is True.
    as_dict : bool
        Return as dictionary rather than array

    Returns
    -------
    stencil_expr : sympy.Add
        The stencil expression
    """
    # Want to start by calculating standard stencil expansions
    min_index = -offset - space_order / 2
    x_list = [i + min_index for i in range(space_order + 1)]

    base_coeffs = sp.finite_diff_weights(deriv, x_list, 0)[-1][-1]

    if as_float:
        coeffs = np.array(base_coeffs, dtype=np.float32)
    else:
        coeffs = np.array(base_coeffs, dtype=object)

    if as_dict:
        mask = coeffs != 0
        coeffs = coeffs[mask]
        indices = (np.array(x_list)[mask] + offset).astype(int)
        return dict(zip(indices, coeffs))
    else:
        return coeffs
Example #12
0
def first_derivative(expr, **kwargs):
    """Derives first derivative for a product of given functions.

    :param \*args: All positional arguments must be fully qualified
       function objects, eg. `f(x, y)` or `g(t, x, y, z)`.
    :param dims: symbol defining the dimension wrt. which
       to differentiate, eg. `x`, `y`, `z` or `t`.
    :param diff: Finite Difference symbol to insert, default `h`.
    :param side: Side of the shift for the first derivatives.
    :returns: The first derivative

    Example: Deriving the first-derivative of f(x)*g(x) wrt. x via:
       ``first_derivative(f(x) * g(x), dim=x, side=1, order=1)``
       results in:
       ``*(-f(x)*g(x) + f(x + h)*g(x + h) ) / h``
    """
    dim = kwargs.get('dim')
    diff = kwargs.get('diff', dim.spacing)
    order = int(kwargs.get('order', 1))
    matvec = kwargs.get('matvec', direct)
    side = kwargs.get('side', centered).adjoint(matvec)
    deriv = 0
    # Stencil positions for non-symmetric cross-derivatives with symmetric averaging
    if side == right:
        ind = [(dim + i * diff)
               for i in range(-int(order / 2) + 1 - (order % 2),
                              int((order + 1) / 2) + 2 - (order % 2))]
    elif side == left:
        ind = [(dim - i * diff)
               for i in range(-int(order / 2) + 1 - (order % 2),
                              int((order + 1) / 2) + 2 - (order % 2))]
    else:
        ind = [(dim + i * diff) for i in range(-int(order / 2),
                                               int((order + 1) / 2) + 1)]
    # Finite difference weights from Taylor approximation with this positions
    c = finite_diff_weights(1, ind, dim)
    c = c[-1][-1]
    all_dims = tuple(
        set((dim, ) + tuple([i for i in expr.indices if i.root == dim])))
    # Loop through positions
    for i in range(0, len(ind)):
        subs = dict([(d, ind[i].subs({dim: d})) for d in all_dims])
        deriv += expr.subs(subs) * c[i]
    return (matvec.val * deriv).evalf(_PRECISION)
Example #13
0
    def _cfl_coeff(self):
        """
        Courant number from the physics.
        For the elastic case, we use the general `.85/sqrt(ndim)`.

        For te acoustic case, we can get an accurate estimate from the spatial order.
        One dan show that C = sqrt(a1/a2) where:
        - a1 is the sum of absolute value of FD coefficients in time
        - a2 is the sum of absolute value of FD coefficients in space
        https://library.seg.org/doi/pdf/10.1190/1.1444605
        """
        # Elasic coefficient (see e.g )
        if 'lam' in self._physical_parameters or 'vs' in self._physical_parameters:
            return .85 / np.sqrt(self.grid.dim)
        a1 = 4  # 2nd order in time
        coeffs = finite_diff_weights(
            2, range(-self.space_order, self.space_order + 1), 0)
        a2 = float(self.grid.dim * sum(np.abs(coeffs[-1][-1])))
        return np.sqrt(a1 / a2)
Example #14
0
def first_derivative(*args, **kwargs):
    """Derives first derivative for a product of given functions.

    :param \*args: All positional arguments must be fully qualified
       function objects, eg. `f(x, y)` or `g(t, x, y, z)`.
    :param dims: symbol defining the dimension wrt. which
       to differentiate, eg. `x`, `y`, `z` or `t`.
    :param diff: Finite Difference symbol to insert, default `h`.
    :param side: Side of the shift for the first derivatives.
    :returns: The first derivative

    Example: Deriving the first-derivative of f(x)*g(x) wrt. x via:
       ``cross_derivative(f(x), g(x), dim=x, side=1, order=1)``
       results in:
       ``*(-f(x)*g(x) + f(x + h)*g(x + h) ) / h``
    """
    dim = kwargs.get('dim')
    diff = kwargs.get('diff', dim.spacing)
    order = int(kwargs.get('order', 1))
    matvec = kwargs.get('matvec', direct)
    side = kwargs.get('side', centered).adjoint(matvec)
    deriv = 0
    # Stencil positions for non-symmetric cross-derivatives with symmetric averaging
    if side == right:
        ind = [(dim + i * diff)
               for i in range(-int(order / 2) + 1 - (order % 2),
                              int((order + 1) / 2) + 2 - (order % 2))]
    elif side == left:
        ind = [(dim - i * diff)
               for i in range(-int(order / 2) + 1 - (order % 2),
                              int((order + 1) / 2) + 2 - (order % 2))]
    else:
        ind = [(dim + i * diff) for i in range(-int(order / 2),
                                               int((order + 1) / 2) + 1)]
    # Finite difference weights from Taylor approximation with this positions
    c = finite_diff_weights(1, ind, dim)
    c = c[-1][-1]

    # Loop through positions
    for i in range(0, len(ind)):
        var = [a.subs({dim: ind[i]}) for a in args]
        deriv += c[i] * reduce(mul, var, 1)
    return matvec._transpose * deriv
Example #15
0
def generic_derivative(expr, dim, fd_order, deriv_order, stagger=None, symbolic=False):
    """
    Arbitrary-order derivative of a given expression.

    Parameters
    ----------
    expr : expr-like
        Expression for which the derivative is produced.
    dim : Dimension
        The Dimension w.r.t. which to differentiate.
    fd_order : int
        Coefficient discretization order. Note: this impacts the width of
        the resulting stencil.
    deriv_order : int
        Derivative order, e.g. 2 for a second-order derivative.
    stagger : Side, optional
        Shift of the finite-difference approximation.

    Returns
    -------
    expr-like
        ``deriv-order`` derivative of ``expr``.
    """

    diff = dim.spacing

    indices, x0 = generate_indices(expr, dim, diff, fd_order, stagger=stagger)

    if symbolic:
        c = symbolic_weights(expr, deriv_order, indices, x0)
    else:
        c = finite_diff_weights(deriv_order, indices, x0)[-1][-1]

    deriv = 0
    all_dims = tuple(set((dim, ) +
                     tuple([i for i in expr.indices if i.root == dim])))
    for i in range(0, len(indices)):
        subs = dict((d, indices[i].subs({dim: d})) for d in all_dims)
        deriv += expr.subs(subs) * c[i]

    return deriv.evalf(_PRECISION)
Example #16
0
def generic_derivative(expr, dim, fd_order, deriv_order, symbolic=False,
                       matvec=direct, x0=None):
    """
    Arbitrary-order derivative of a given expression.

    Parameters
    ----------
    expr : expr-like
        Expression for which the derivative is produced.
    dim : Dimension
        The Dimension w.r.t. which to differentiate.
    fd_order : int
        Coefficient discretization order. Note: this impacts the width of
        the resulting stencil.
    deriv_order : int
        Derivative order, e.g. 2 for a second-order derivative.
    stagger : Side, optional
        Shift of the finite-difference approximation.
    x0 : dict, optional
        Origin of the finite-difference scheme as a map dim: origin_dim.

    Returns
    -------
    expr-like
        ``deriv-order`` derivative of ``expr``.
    """
    # First order derivative with 2nd order FD is highly non-recommended so taking
    # first order fd that is a lot better
    if deriv_order == 1 and fd_order == 2 and not symbolic:
        fd_order = 1
    # Stencil positions
    indices, x0 = generate_indices(expr, dim, fd_order, x0=x0)

    # Finite difference weights from Taylor approximation with these positions
    if symbolic:
        c = symbolic_weights(expr, deriv_order, indices, x0)
    else:
        c = finite_diff_weights(deriv_order, indices, x0)[-1][-1]

    return indices_weights_to_fd(expr, dim, indices, c, matvec=matvec.val)
Example #17
0
def first_derivative(expr, dim, fd_order=None, side=centered, matvec=direct):
    """
    First-order derivative of a given expression.

    Parameters
    ----------
    expr : expr-like
        Expression for which the first-order derivative is produced.
    dim : Dimension
        The Dimension w.r.t. which to differentiate.
    fd_order : int, optional
        Coefficient discretization order. Note: this impacts the width of
        the resulting stencil. Defaults to ``expr.space_order``
    side : Side, optional
        Side of the finite difference location, centered (at x), left (at x - 1)
        or right (at x +1). Defaults to ``centered``.
    matvec : Transpose, optional
        Forward (matvec=direct) or transpose (matvec=transpose) mode of the
        finite difference. Defaults to ``direct``.

    Returns
    -------
    expr-like
        First-order derivative of ``expr``.

    Examples
    --------
    >>> from devito import Function, Grid, first_derivative, transpose
    >>> grid = Grid(shape=(4, 4))
    >>> x, _ = grid.dimensions
    >>> f = Function(name='f', grid=grid)
    >>> g = Function(name='g', grid=grid)
    >>> first_derivative(f*g, dim=x)
    -f(x, y)*g(x, y)/h_x + f(x + h_x, y)*g(x + h_x, y)/h_x

    This is also more easily obtainable via:

    >>> (f*g).dx
    -f(x, y)*g(x, y)/h_x + f(x + h_x, y)*g(x + h_x, y)/h_x

    The adjoint mode

    >>> g = Function(name='g', grid=grid)
    >>> first_derivative(f*g, dim=x, matvec=transpose)
    f(x, y)*g(x, y)/h_x - f(x + h_x, y)*g(x + h_x, y)/h_x
    """

    diff = dim.spacing
    side = side.adjoint(matvec)
    order = fd_order or expr.space_order

    deriv = 0
    # Stencil positions for non-symmetric cross-derivatives with symmetric averaging
    if side == right:
        ind = [(dim + i * diff)
               for i in range(-int(order / 2) + 1 - (order % 2),
                              int((order + 1) / 2) + 2 - (order % 2))]
    elif side == left:
        ind = [(dim - i * diff)
               for i in range(-int(order / 2) + 1 - (order % 2),
                              int((order + 1) / 2) + 2 - (order % 2))]
    else:
        ind = [(dim + i * diff) for i in range(-int(order / 2),
                                               int((order + 1) / 2) + 1)]
    # Finite difference weights from Taylor approximation with this positions
    c = finite_diff_weights(1, ind, dim)
    c = c[-1][-1]
    all_dims = tuple(
        set((dim, ) + tuple([i for i in expr.indices if i.root == dim])))
    # Loop through positions
    for i in range(0, len(ind)):
        subs = dict([(d, ind[i].subs({dim: d})) for d in all_dims])
        deriv += expr.subs(subs) * c[i]
    return (matvec.val * deriv).evalf(_PRECISION)
def test_ReactionDiffusion__lrefl_7(log):
    # Diffusion without reaction (7 bins)
    from sympy import finite_diff_weights
    lrefl, rrefl = True, False
    N = 7
    t0 = 3.0
    logy, logt = log
    D = 17.0
    nstencil = 5
    nsidep = (nstencil-1)//2
    x = np.array([3, 5, 13, 17, 23, 25, 35, 37], dtype=np.float64)
    y0 = np.array([12, 8, 11, 5, 7, 4, 9], dtype=np.float64)
    xc_ = padded_centers(x, nsidep)
    lb = stencil_pxci_lbounds(nstencil, N, lrefl=lrefl, rrefl=rrefl)

    rd = ReactionDiffusion(1, [], [], [], D=[D], x=x, logy=logy,
                           logt=logt, N=N, nstencil=nstencil,
                           lrefl=lrefl, rrefl=rrefl)

    y = rd.logb(y0) if logy else y0
    t = rd.logb(t0) if logt else t0
    le = nsidep if lrefl else 0
    D_weight_ref = np.array([
        finite_diff_weights(
            2, xc_[lb[i]:lb[i]+nstencil], x0=xc_[le+i])[-1][-1]
        for i in range(N)], dtype=np.float64)
    assert np.allclose(rd.D_weight, D_weight_ref.flatten())

    yi = pxci_to_bi(nstencil, N)

    fref = D*np.array([
        sum([rd.D_weight[i*nstencil+j]*y0[yi[lb[i]+j]]
             for j in range(nstencil)])
        for i in range(N)
    ])

    if logy:
        fref /= y0
    if logt:
        fref *= t0

    _test_f(rd, t, y, fref)

    if logy:
        def cb(i, j):
            if abs(i-j) > 1:
                return 0  # imperfect Jacobian
            elif i == j:
                res = 0
                for k in range(nstencil):
                    cyi = yi[lb[i] + k]  # current y index
                    if cyi != i:
                        res -= y0[cyi]/y0[i]*rd.D_weight[i*nstencil + k]
                return res
            else:
                res = 0
                for k in range(nstencil):
                    cyi = yi[lb[i] + k]  # current y index
                    if cyi == j:
                        res += y0[j]/y0[i]*rd.D_weight[i*nstencil + k]
                return res
    else:
        def cb(i, j):
            if abs(i-j) > 1:
                return 0  # imperfect Jacobian
            res = 0
            for k in range(nstencil):
                if yi[lb[i]+k] == j:
                    res += rd.D_weight[i*nstencil + k]
            return res
    jref = D*np.array([cb(i, j) for i, j in product(
        range(N), range(N))]).reshape(N, N)
    if logt:
        jref *= t0

    _test_dense_jac_rmaj(rd, t, y, jref)
Example #19
0
def cross_derivative(expr, **kwargs):
    """Derives cross derivative for a product of given functions.

    :param \*args: All positional arguments must be fully qualified
       function objects, eg. `f(x, y)` or `g(t, x, y, z)`.
    :param dims: 2-tuple of symbols defining the dimension wrt. which
       to differentiate, eg. `x`, `y`, `z` or `t`.
    :param diff: Finite Difference symbol to insert, default `h`.
    :returns: The cross derivative

    Example: Deriving the cross-derivative of f(x, y)*g(x, y) wrt. x and y via:
       ``cross_derivative(f(x, y), g(x, y), dims=(x, y))``
       results in:
       ``0.5*(-2.0*f(x, y)*g(x, y) + f(x, -h + y)*g(x, -h + y) +``
       ``f(x, h + y)*g(x, h + y) + f(-h + x, y)*g(-h + x, y) -``
       ``f(-h + x, h + y)*g(-h + x, h + y) + f(h + x, y)*g(h + x, y) -``
       ``f(h + x, -h + y)*g(h + x, -h + y)) / h**2``
    """

    dims = kwargs.get('dims')
    diff = kwargs.get('diff', (dims[0].spacing, dims[1].spacing))
    order = kwargs.get('order', (1, 1))

    assert (isinstance(dims, tuple) and len(dims) == 2)
    deriv = 0

    # Stencil positions for non-symmetric cross-derivatives with symmetric averaging
    ind1r = [(dims[0] + i * diff[0])
             for i in range(-int(order[0] / 2) + 1 - (order[0] < 4),
                            int((order[0] + 1) / 2) + 2 - (order[0] < 4))]
    ind2r = [(dims[1] + i * diff[1])
             for i in range(-int(order[1] / 2) + 1 - (order[1] < 4),
                            int((order[1] + 1) / 2) + 2 - (order[1] < 4))]
    ind1l = [(dims[0] - i * diff[0])
             for i in range(-int(order[0] / 2) + 1 - (order[0] < 4),
                            int((order[0] + 1) / 2) + 2 - (order[0] < 4))]
    ind2l = [(dims[1] - i * diff[1])
             for i in range(-int(order[1] / 2) + 1 - (order[1] < 4),
                            int((order[1] + 1) / 2) + 2 - (order[1] < 4))]

    # Finite difference weights from Taylor approximation with this positions
    c11 = finite_diff_weights(1, ind1r, dims[0])[-1][-1]
    c21 = finite_diff_weights(1, ind1l, dims[0])[-1][-1]
    c12 = finite_diff_weights(1, ind2r, dims[1])[-1][-1]
    c22 = finite_diff_weights(1, ind2l, dims[1])[-1][-1]
    all_dims1 = tuple(
        set((dims[0], ) + tuple([i
                                 for i in expr.indices if i.root == dims[0]])))
    all_dims2 = tuple(
        set((dims[1], ) + tuple([i
                                 for i in expr.indices if i.root == dims[1]])))
    # Diagonal elements
    for i in range(0, len(ind1r)):
        for j in range(0, len(ind2r)):
            subs1 = dict([(d1, ind1r[i].subs({dims[0]: d1})) +
                          (d2, ind2r[i].subs({dims[1]: d2}))
                          for (d1, d2) in zip(all_dims1, all_dims2)])
            subs2 = dict([(d1, ind1l[i].subs({dims[0]: d1})) +
                          (d2, ind2l[i].subs({dims[1]: d2}))
                          for (d1, d2) in zip(all_dims1, all_dims2)])
            var1 = expr.subs(subs1)
            var2 = expr.subs(subs2)
            deriv += .5 * (c11[i] * c12[j] * var1 +
                           c21[-(j + 1)] * c22[-(i + 1)] * var2)
    return -deriv.evalf(_PRECISION)
Example #20
0
    def __init__(self, n, stoich_active, stoich_prod, k, N=0, D=None,
                 z_chg=None, mobility=None, x=None, stoich_inact=None,
                 geom=FLAT, logy=False, logt=False, logx=False, nstencil=None,
                 lrefl=True, rrefl=True, auto_efield=False,
                 surf_chg=(0.0, 0.0), eps_rel=1.0, g_values=None,
                 g_value_parents=None, fields=None, modulated_rxns=None,
                 modulation=None, n_jac_diags=-1, use_log2=False, **kwargs):
        # Save args
        self.n = n
        self.stoich_active = stoich_active
        self.stoich_prod = stoich_prod
        self.k = k
        self.D = D if D is not None else [0]*n
        self.z_chg = z_chg if z_chg is not None else [0]*n
        self.mobility = mobility if mobility is not None else [0]*n
        self.x = x if x is not None else [0, 1]
        self.N = len(self.x) - 1
        if N not in [None, 0]:
            assert self.N == N
        self.stoich_inact = stoich_inact or [[]*len(stoich_active)]
        self.geom = geom
        self.logy = logy
        self.logt = logt
        self.logx = logx
        self.nstencil = nstencil or 3
        self.lrefl = lrefl
        self.rrefl = rrefl
        self.auto_efield = auto_efield
        self.surf_chg = surf_chg
        self.eps_rel = eps_rel
        self.g_values = g_values or []
        self.g_value_parents = g_value_parents or []
        self.fields = [] if fields is None else fields
        self.modulated_rxns = [] if modulated_rxns is None else modulated_rxns
        self.modulation = [] if modulation is None else modulation
        self.n_jac_diags = (int(os.environ.get('CHEMREAC_N_JAC_DIAGS', 1)) if
                            n_jac_diags is -1 else n_jac_diags)
        self.use_log2 = use_log2
        if kwargs:
            raise KeyError("Don't know what to do with:", kwargs)

        # Set attributes used later
        self._t = sp.Symbol('t')
        self._nsidep = (self.nstencil-1) // 2
        if self.n_jac_diags == 0:
            self.n_jac_diags = self._nsidep
        self._y = sp.symbols('y:'+str(self.n*self.N))
        self._xc = padded_centers(self.x, self._nsidep)
        self._lb = stencil_pxci_lbounds(self.nstencil, self.N,
                                        self.lrefl, self.rrefl)
        self._xc_bi_map = pxci_to_bi(self.nstencil, self.N)
        self._f = [0]*self.n*self.N
        self.efield = [0]*self.N

        # Reactions
        for ri, (k, sactv, sinact, sprod) in enumerate(zip(
                self.k, self.stoich_active, self.stoich_inact,
                self.stoich_prod)):
            c_actv = map(sactv.count, range(self.n))
            c_inact = map(sinact.count, range(self.n))
            c_prod = map(sprod.count, range(self.n))
            c_totl = [nprd - nactv - ninact for nactv, ninact, nprd in zip(
                c_actv, c_inact, c_prod)]
            for bi in range(self.N):
                r = k
                if ri in self.modulated_rxns:
                    r *= self.modulation[self.modulated_rxns.index(ri)][bi]
                for si in sactv:
                    r *= self.y(bi, si)
                for si in range(self.n):
                    self._f[bi*self.n + si] += c_totl[si]*r
        for fi, fld in enumerate(self.fields):
            for bi in range(self.N):
                if self.g_value_parents[fi] == -1:
                    gfact = 1
                else:
                    gfact = self.y(bi, self.g_value_parents[fi])
                for si in range(self.n):
                    self._f[bi*self.n + si] += sp.S(
                        fld[bi])*self.g_values[fi][si]*gfact

        if self.N > 1:
            # Diffusion
            self.D_wghts = []
            self.A_wghts = []
            for bi in range(self.N):
                local_x_serie = self._xc[
                    self._lb[bi]:self._lb[bi]+self.nstencil]
                l_x_rnd = self._xc[bi+self._nsidep]
                w = sp.finite_diff_weights(2, local_x_serie, l_x_rnd)
                self.D_wghts.append(w[-1][-1])
                self.A_wghts.append(w[-2][-1])
                for wi in range(self.nstencil):
                    if self.logx:
                        if geom == FLAT:
                            self.D_wghts[bi][wi] -= w[-2][-1][wi]
                        elif geom == CYLINDRICAL:
                            self.A_wghts[bi][wi] += w[-3][-1][wi]
                        elif geom == SPHERICAL:
                            self.D_wghts[bi][wi] += w[-2][-1][wi]
                            self.A_wghts[bi][wi] += 2*w[-3][-1][wi]
                        else:
                            raise ValueError("Unknown geom: %s" % geom)
                        self.D_wghts[bi][wi] *= self.expb(-2*l_x_rnd)
                        self.A_wghts[bi][wi] *= self.expb(-l_x_rnd)
                    else:
                        if geom == FLAT:
                            pass
                        elif geom == CYLINDRICAL:
                            self.D_wghts[bi][wi] += w[-2][-1][wi]/l_x_rnd
                            self.A_wghts[bi][wi] += w[-3][-1][wi]/l_x_rnd
                        elif geom == SPHERICAL:
                            self.D_wghts[bi][wi] += 2*w[-2][-1][wi]/l_x_rnd
                            self.A_wghts[bi][wi] += 2*w[-3][-1][wi]/l_x_rnd
                        else:
                            raise ValueError("Unknown geom: %s" % geom)

            for bi, (dw, aw) in enumerate(zip(self.D_wghts, self.A_wghts)):
                for si in range(self.n):
                    d_terms = [dw[k]*self.y(
                        self._xc_bi_map[self._lb[bi]+k], si
                    ) for k in range(self.nstencil)]
                    self._f[bi*self.n + si] += self.D[si]*reduce(
                        add, d_terms)
                    a_terms = [aw[k]*self.y(
                        self._xc_bi_map[self._lb[bi]+k], si
                    ) for k in range(self.nstencil)]
                    self._f[bi*self.n + si] += (
                        self.mobility[si]*self.efield[bi]*reduce(
                            add, a_terms))

        if self.logy or self.logt:
            logbfactor = sp.log(2) if use_log2 else 1
            for bi in range(self.N):
                for si in range(self.n):
                    if self.logy:
                        self._f[bi*self.n+si] /= self.y(bi, si)
                        if not self.logt:
                            self._f[bi*self.n+si] /= logbfactor
                    if self.logt:
                        self._f[bi*self.n+si] *= (2**self._t) if self.use_log2 else sp.exp(self._t)
                        if not self.logy:
                            self._f[bi*self.n+si] *= logbfactor
def test_ReactionDiffusion__3_reactions_4_species_5_bins_k_factor(
        geom_refl):
    # UNSUPPORTED since `bin_k_factor` was replaced with `fields`
    # if a real world scenario need per bin modulation of binary
    # reactions and the functionality is reintroduced, this test
    # is useful
    from sympy import finite_diff_weights
    geom, refl = geom_refl
    lrefl, rrefl = refl
    # r[0]: A + B -> C
    # r[1]: D + C -> D + A + B
    # r[2]: B + B -> D
    #              r[0]     r[1]       r[2]
    stoich_active = [[0, 1], [2, 3],    [1, 1]]
    stoich_prod = [[2],    [0, 1, 3], [3]]
    n = 4
    N = 5
    D = np.array([2.6, 3.7, 5.11, 7.13])*213
    y0 = np.array([
        2.5, 1.2, 3.2, 4.3,
        2.7, 0.8, 1.6, 2.4,
        3.1, 0.3, 1.5, 1.8,
        3.3, 0.6, 1.6, 1.4,
        3.6, 0.9, 1.7, 1.2
    ]).reshape((5, 4))
    x = np.array([11.0, 13.3, 17.0, 23.2, 29.8, 37.2])
    xc_ = x[:-1]+np.diff(x)/2
    xc_ = [x[0]-(xc_[0]-x[0])]+list(xc_)+[x[-1]+(x[-1]-xc_[-1])]
    assert len(xc_) == 7
    k = [31.0, 37.0, 41.0]

    # (r[0], r[1]) modulations over bins
    modulated_rxns = [0, 1]
    modulation = [[i+3 for i in range(N)],
                  [i+4 for i in range(N)]]
    nstencil = 3
    nsidep = 1
    rd = ReactionDiffusion(
        4, stoich_active, stoich_prod, k, N, D=D, x=x,
        geom=geom, nstencil=nstencil, lrefl=lrefl,
        rrefl=rrefl, modulated_rxns=modulated_rxns,
        modulation=modulation)

    assert np.allclose(xc_, rd.xc)

    lb = stencil_pxci_lbounds(nstencil, N, lrefl, rrefl)
    if lrefl:
        if rrefl:
            assert lb == [0, 1, 2, 3, 4]
        else:
            assert lb == [0, 1, 2, 3, 3]
    else:
        if rrefl:
            assert lb == [1, 1, 2, 3, 4]
        else:
            assert lb == [1, 1, 2, 3, 3]
    assert lb == list(map(rd._stencil_bi_lbound, range(N)))

    pxci2bi = pxci_to_bi(nstencil, N)
    assert pxci2bi == [0, 0, 1, 2, 3, 4, 4]
    assert pxci2bi == list(map(rd._xc_bi_map, range(N+2)))

    D_weight = []
    for bi in range(N):
        local_x_serie = xc_[lb[bi]:lb[bi]+nstencil]
        local_x_around = xc_[nsidep+bi]
        w = finite_diff_weights(
            2, local_x_serie, x0=local_x_around
        )
        D_weight.append(w[-1][-1])
        if geom == 'f':
            pass
        elif geom == 'c':
            for wi in range(nstencil):
                # first order derivative
                D_weight[bi][wi] += w[-2][-1][wi]*1/local_x_around
        elif geom == 's':
            for wi in range(nstencil):
                # first order derivative
                D_weight[bi][wi] += w[-2][-1][wi]*2/local_x_around
        else:
            raise RuntimeError
    assert np.allclose(rd.D_weight, np.array(D_weight, dtype=np.float64).flatten())

    def cflux(si, bi):
        f = 0.0
        for k in range(nstencil):
            f += rd.D_weight[bi*nstencil+k]*y0[pxci2bi[lb[bi]+k], si]
        return D[si]*f

    r = [
        [k[0]*modulation[0][bi]*y0[bi, 0]*y0[bi, 1] for
         bi in range(N)],
        [k[1]*modulation[1][bi]*y0[bi, 3]*y0[bi, 2] for
         bi in range(N)],
        [k[2]*y0[bi, 1]**2 for bi in range(N)],
    ]

    fref = np.array([[
        -r[0][bi] + r[1][bi] + cflux(0, bi),
        -r[0][bi] + r[1][bi] - 2*r[2][bi] + cflux(1, bi),
        r[0][bi] - r[1][bi] + cflux(2, bi),
        r[2][bi] + cflux(3, bi)
    ] for bi in range(N)]).flatten()

    # Now let's check that the Jacobian is correctly computed.
    def dfdC(bi, lri, lci):
        v = 0.0
        for ri in range(len(stoich_active)):
            totl = (stoich_prod[ri].count(lri) -
                    stoich_active[ri].count(lri))
            if totl == 0:
                continue
            actv = stoich_active[ri].count(lci)
            if actv == 0:
                continue
            v += actv*totl*r[ri][bi]/y0[bi, lci]
        return v

    def jac_elem(ri, ci):
        bri, bci = ri // n, ci // n
        lri, lci = ri % n,  ci % n
        elem = 0.0

        def _diffusion():
            _elem = 0.0
            for k in range(nstencil):
                if pxci2bi[lb[bri]+k] == bci:
                    _elem += D[lri]*rd.D_weight[bri*nstencil+k]
            return _elem

        if bri == bci:
            # on block diagonal
            elem += dfdC(bri, lri, lci)
            if lri == lci:
                elem += _diffusion()
        elif bri == bci - 1:
            if lri == lci:
                elem = _diffusion()
        elif bri == bci + 1:
            if lri == lci:
                elem = _diffusion()
        return elem

    jref = np.zeros((n*N, n*N), order='C')
    for ri, ci in np.ndindex(n*N, n*N):
        jref[ri, ci] = jac_elem(ri, ci)

    # Compare to what is calculated using our C++ callback
    _test_f_and_dense_jac_rmaj(rd, 0, y0.flatten(), fref, jref)

    jout_cmaj = np.zeros((n*N, n*N), order='F')
    rd.dense_jac_cmaj(0.0, y0.flatten(), jout_cmaj)
    assert np.allclose(jout_cmaj, jref)

    ref_banded_j = get_banded(jref, n, N)

    ref_banded_j_symbolic = rd.alloc_jout(order='F', pad=0)
    symrd = SymRD.from_rd(rd)
    symrd.banded_jac(0.0, y0.flatten(), ref_banded_j_symbolic)
    assert np.allclose(ref_banded_j_symbolic, ref_banded_j)

    jout_bnd_packed_cmaj = np.zeros((3*n+1, n*N), order='F')
    rd.banded_jac_cmaj(0.0, y0.flatten(), jout_bnd_packed_cmaj)

    if os.environ.get('plot_tests', False):
        import matplotlib
        matplotlib.use('Agg')
        import matplotlib.pyplot as plt
        from chemreac.util.plotting import coloured_spy
        fig = plt.figure()
        ax = fig.add_subplot(3, 1, 1)
        coloured_spy(ref_banded_j, ax=ax)
        plt.title('ref_banded_j')
        ax = fig.add_subplot(3, 1, 2)
        coloured_spy(jout_bnd_packed_cmaj[n:, :], ax=ax)
        plt.title('jout_bnd_packed_cmaj')
        ax = fig.add_subplot(3, 1, 3)
        coloured_spy(ref_banded_j-jout_bnd_packed_cmaj[n:, :], ax=ax)
        plt.title('diff')
        plt.savefig(__file__+'.png')

    assert np.allclose(jout_bnd_packed_cmaj[n:, :], ref_banded_j)
Example #22
0
def numeric_weights(deriv_order, indices, x0):
    return finite_diff_weights(deriv_order, indices, x0)[-1][-1]
Example #23
0
def first_derivative(expr, dim, fd_order=None, side=centered, matvec=direct,
                     symbolic=False, x0=None):
    """
    First-order derivative of a given expression.

    Parameters
    ----------
    expr : expr-like
        Expression for which the first-order derivative is produced.
    dim : Dimension
        The Dimension w.r.t. which to differentiate.
    fd_order : int, optional
        Coefficient discretization order. Note: this impacts the width of
        the resulting stencil. Defaults to ``expr.space_order``
    side : Side, optional
        Side of the finite difference location, centered (at x), left (at x - 1)
        or right (at x +1). Defaults to ``centered``.
    matvec : Transpose, optional
        Forward (matvec=direct) or transpose (matvec=transpose) mode of the
        finite difference. Defaults to ``direct``.
    x0 : dict, optional
        Origin of the finite-difference scheme as a map dim: origin_dim.

    Returns
    -------
    expr-like
        First-order derivative of ``expr``.

    Examples
    --------
    >>> from devito import Function, Grid, first_derivative, transpose
    >>> grid = Grid(shape=(4, 4))
    >>> x, _ = grid.dimensions
    >>> f = Function(name='f', grid=grid)
    >>> g = Function(name='g', grid=grid)
    >>> first_derivative(f*g, dim=x)
    -f(x, y)*g(x, y)/h_x + f(x + h_x, y)*g(x + h_x, y)/h_x

    Semantically, this is equivalent to

    >>> (f*g).dx
    Derivative(f(x, y)*g(x, y), x)

    The only difference is that in the latter case derivatives remain unevaluated.
    The expanded form is obtained via ``evaluate``

    >>> (f*g).dx.evaluate
    -f(x, y)*g(x, y)/h_x + f(x + h_x, y)*g(x + h_x, y)/h_x

    For the adjoint mode of the first derivative, pass ``matvec=transpose``

    >>> g = Function(name='g', grid=grid)
    >>> first_derivative(f*g, dim=x, matvec=transpose)
    -f(x, y)*g(x, y)/h_x + f(x - h_x, y)*g(x - h_x, y)/h_x

    This is also accessible via the .T shortcut

    >>> (f*g).dx.T.evaluate
    -f(x, y)*g(x, y)/h_x + f(x - h_x, y)*g(x - h_x, y)/h_x

    Finally the x0 argument allows to choose the origin of the finite-difference

    >>> first_derivative(f, dim=x, x0={x: 1})
    -f(1, y)/h_x + f(h_x + 1, y)/h_x
    """
    side = side
    order = fd_order or expr.space_order

    # Stencil positions for non-symmetric cross-derivatives with symmetric averaging
    ind = generate_indices(expr, dim, order, side=side, x0=x0)[0]

    # Finite difference weights from Taylor approximation with these positions
    if symbolic:
        c = symbolic_weights(expr, 1, ind, dim)
    else:
        c = finite_diff_weights(1, ind, dim)[-1][-1]

    return indices_weights_to_fd(expr, dim, ind, c, matvec=matvec.val)
Example #24
0
def first_derivative(expr,
                     dim,
                     fd_order=None,
                     side=centered,
                     matvec=direct,
                     symbolic=False):
    """
    First-order derivative of a given expression.

    Parameters
    ----------
    expr : expr-like
        Expression for which the first-order derivative is produced.
    dim : Dimension
        The Dimension w.r.t. which to differentiate.
    fd_order : int, optional
        Coefficient discretization order. Note: this impacts the width of
        the resulting stencil. Defaults to ``expr.space_order``
    side : Side, optional
        Side of the finite difference location, centered (at x), left (at x - 1)
        or right (at x +1). Defaults to ``centered``.
    matvec : Transpose, optional
        Forward (matvec=direct) or transpose (matvec=transpose) mode of the
        finite difference. Defaults to ``direct``.

    Returns
    -------
    expr-like
        First-order derivative of ``expr``.

    Examples
    --------
    >>> from devito import Function, Grid, first_derivative, transpose
    >>> grid = Grid(shape=(4, 4))
    >>> x, _ = grid.dimensions
    >>> f = Function(name='f', grid=grid)
    >>> g = Function(name='g', grid=grid)
    >>> first_derivative(f*g, dim=x)
    -f(x, y)*g(x, y)/h_x + f(x + h_x, y)*g(x + h_x, y)/h_x

    Semantically, this is equivalent to

    >>> (f*g).dx
    Derivative(f(x, y)*g(x, y), x)

    The only difference is that in the latter case derivatives remain unevaluated.
    The expanded form is obtained via ``evaluate``

    >>> (f*g).dx.evaluate
    -f(x, y)*g(x, y)/h_x + f(x + h_x, y)*g(x + h_x, y)/h_x

    For the adjoint mode of the first derivative, pass ``matvec=transpose``

    >>> g = Function(name='g', grid=grid)
    >>> first_derivative(f*g, dim=x, matvec=transpose)
    f(x, y)*g(x, y)/h_x - f(x + h_x, y)*g(x + h_x, y)/h_x

    This is also accessible via the .T shortcut

    >>> (f*g).dx.T.evaluate
    f(x, y)*g(x, y)/h_x - f(x + h_x, y)*g(x + h_x, y)/h_x
    """
    side = side.adjoint(matvec)
    diff = dim.spacing
    adjoint_val = matvec.val
    order = fd_order or expr.space_order

    # Stencil positions for non-symmetric cross-derivatives with symmetric averaging
    ind = generate_indices(expr, dim, diff, order, side=side)[0]

    # Finite difference weights from Taylor approximation with these positions
    if symbolic:
        c = symbolic_weights(expr, 1, ind, dim)
    else:
        c = finite_diff_weights(1, ind, dim)[-1][-1]

    # Loop through positions
    deriv = 0
    all_dims = tuple(
        set((dim, ) + tuple([i for i in expr.indices if i.root == dim])))
    for i in range(len(ind)):
        subs = dict([(d, ind[i].subs({dim: d})) for d in all_dims])
        deriv += expr.subs(subs) * c[i]

    # Evaluate up to _PRECISION digits
    deriv = (adjoint_val * deriv).evalf(_PRECISION)

    return deriv
Example #25
0
def first_derivative(expr, dim, fd_order=None, side=centered, matvec=direct,
                     symbolic=False):
    """
    First-order derivative of a given expression.

    Parameters
    ----------
    expr : expr-like
        Expression for which the first-order derivative is produced.
    dim : Dimension
        The Dimension w.r.t. which to differentiate.
    fd_order : int, optional
        Coefficient discretization order. Note: this impacts the width of
        the resulting stencil. Defaults to ``expr.space_order``
    side : Side, optional
        Side of the finite difference location, centered (at x), left (at x - 1)
        or right (at x +1). Defaults to ``centered``.
    matvec : Transpose, optional
        Forward (matvec=direct) or transpose (matvec=transpose) mode of the
        finite difference. Defaults to ``direct``.

    Returns
    -------
    expr-like
        First-order derivative of ``expr``.

    Examples
    --------
    >>> from devito import Function, Grid, first_derivative, transpose
    >>> grid = Grid(shape=(4, 4))
    >>> x, _ = grid.dimensions
    >>> f = Function(name='f', grid=grid)
    >>> g = Function(name='g', grid=grid)
    >>> first_derivative(f*g, dim=x)
    -f(x, y)*g(x, y)/h_x + f(x + h_x, y)*g(x + h_x, y)/h_x

    This is also more easily obtainable via:

    >>> (f*g).dx
    -f(x, y)*g(x, y)/h_x + f(x + h_x, y)*g(x + h_x, y)/h_x

    The adjoint mode

    >>> g = Function(name='g', grid=grid)
    >>> first_derivative(f*g, dim=x, matvec=transpose)
    f(x, y)*g(x, y)/h_x - f(x + h_x, y)*g(x + h_x, y)/h_x
    """

    diff = dim.spacing
    side = side.adjoint(matvec)
    order = fd_order or expr.space_order

    deriv = 0
    # Stencil positions for non-symmetric cross-derivatives with symmetric averaging
    ind = generate_indices(expr, dim, diff, order, side=side)[0]

    # Finite difference weights from Taylor approximation with this positions
    if symbolic:
        c = symbolic_weights(expr, 1, ind, dim)
    else:
        c = finite_diff_weights(1, ind, dim)[-1][-1]
    all_dims = tuple(set((dim,) + tuple([i for i in expr.indices if i.root == dim])))
    # Loop through positions
    for i in range(0, len(ind)):
        subs = dict([(d, ind[i].subs({dim: d})) for d in all_dims])
        deriv += expr.subs(subs) * c[i]
    return (matvec.val*deriv).evalf(_PRECISION)