Source code for devito.finite_differences.finite_difference

from sympy import sympify

from .differentiable import EvalDerivative, IndexDerivative, Weights
from .tools import (numeric_weights, symbolic_weights, left, right,
                    generate_indices, centered, direct, transpose,
                    check_input, check_symbolic)

__all__ = ['first_derivative', 'cross_derivative', 'generic_derivative',
           'left', 'right', 'centered', 'transpose', 'generate_indices']

# Number of digits for FD coefficients to avoid roundup errors and non-deterministic
# code generation
_PRECISION = 9


[docs] @check_input @check_symbolic def first_derivative(expr, dim, fd_order=None, side=centered, matvec=direct, x0=None, symbolic=False, expand=True): """ 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. symbolic : bool, optional Use default or custom coefficients (weights). Defaults to False. expand : bool, optional If True, the derivative is fully expanded as a sum of products, otherwise an IndexSum is returned. Defaults to True. 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 """ fd_order = fd_order or expr.space_order deriv_order = 1 return make_derivative(expr, dim, fd_order, deriv_order, side, matvec, x0, symbolic, expand)
[docs] @check_input @check_symbolic def cross_derivative(expr, dims, fd_order, deriv_order, x0=None, **kwargs): """ Arbitrary-order cross derivative of a given expression. Parameters ---------- expr : expr-like Expression for which the cross derivative is produced. dims : tuple of Dimension Dimensions w.r.t. which to differentiate. fd_order : tuple of ints Coefficient discretization order. Note: this impacts the width of the resulting stencil. deriv_order : tuple of ints Derivative order, e.g. 2 for a second-order derivative. 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. symbolic : bool, optional Use default or custom coefficients (weights). Defaults to False. expand : bool, optional If True, the derivative is fully expanded as a sum of products, otherwise an IndexSum is returned. Defaults to True. Returns ------- expr-like Cross-derivative of ``expr``. Examples -------- >>> from devito import Function, Grid >>> grid = Grid(shape=(4, 4)) >>> x, y = grid.dimensions >>> f = Function(name='f', grid=grid, space_order=2) >>> g = Function(name='g', grid=grid, space_order=2) >>> cross_derivative(f*g, dims=(x, y), fd_order=(2, 2), deriv_order=(1, 1)) (-1/h_y)*(-f(x, y)*g(x, y)/h_x + f(x + h_x, y)*g(x + h_x, y)/h_x) + \ (-f(x, y + h_y)*g(x, y + h_y)/h_x + f(x + h_x, y + h_y)*g(x + h_x, y + h_y)/h_x)/h_y Semantically, this is equivalent to >>> (f*g).dxdy Derivative(f(x, y)*g(x, y), x, y) The only difference is that in the latter case derivatives remain unevaluated. The expanded form is obtained via ``evaluate`` >>> (f*g).dxdy.evaluate (-1/h_y)*(-f(x, y)*g(x, y)/h_x + f(x + h_x, y)*g(x + h_x, y)/h_x) + \ (-f(x, y + h_y)*g(x, y + h_y)/h_x + f(x + h_x, y + h_y)*g(x + h_x, y + h_y)/h_x)/h_y Finally the x0 argument allows to choose the origin of the finite-difference >>> cross_derivative(f*g, dims=(x, y), fd_order=(2, 2), deriv_order=(1, 1), \ x0={x: 1, y: 2}) (-1/h_y)*(-f(1, 2)*g(1, 2)/h_x + f(h_x + 1, 2)*g(h_x + 1, 2)/h_x) + (-f(1, h_y + 2)*\ g(1, h_y + 2)/h_x + f(h_x + 1, h_y + 2)*g(h_x + 1, h_y + 2)/h_x)/h_y """ x0 = x0 or {} for d, fd, dim in zip(deriv_order, fd_order, dims): expr = generic_derivative(expr, dim=dim, fd_order=fd, deriv_order=d, x0=x0, **kwargs) return expr
[docs] @check_input @check_symbolic def generic_derivative(expr, dim, fd_order, deriv_order, matvec=direct, x0=None, symbolic=False, expand=True): """ 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. 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. symbolic : bool, optional Use default or custom coefficients (weights). Defaults to False. expand : bool, optional If True, the derivative is fully expanded as a sum of products, otherwise an IndexSum is returned. Defaults to True. Returns ------- expr-like ``deriv-order`` derivative of ``expr``. """ side = None # 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 return make_derivative(expr, dim, fd_order, deriv_order, side, matvec, x0, symbolic, expand)
def make_derivative(expr, dim, fd_order, deriv_order, side, matvec, x0, symbolic, expand): # The stencil indices indices, x0 = generate_indices(expr, dim, fd_order, side=side, matvec=matvec, x0=x0) # Finite difference weights from Taylor approximation given these positions if symbolic: weights = symbolic_weights(expr, deriv_order, indices, x0) else: weights = numeric_weights(deriv_order, indices, x0) # Enforce fixed precision FD coefficients to avoid variations in results weights = [sympify(w).evalf(_PRECISION) for w in weights] # Transpose the FD, if necessary if matvec: indices = indices.scale(matvec.val) # Shift index due to staggering, if any indices = indices.shift(-(expr.indices_ref[dim] - dim)) if not expand and indices.expr is not None: weights = Weights(name='w', dimensions=indices.free_dim, initvalue=weights) # Inject the StencilDimension # E.g. `x + i*h_x` into `f(x)` s.t. `f(x + i*h_x)` expr = expr._subs(dim, indices.expr) # Re-evaluate any off-the-grid Functions potentially impacted by the FD try: expr = expr._evaluate(expand=False) except AttributeError: # Pure number pass deriv = IndexDerivative(expr*weights, {dim: indices.free_dim}) else: terms = [] for i, c in zip(indices, weights): # The FD term term = expr._subs(dim, i) * c # Re-evaluate any off-the-grid Functions potentially impacted by the FD try: term = term.evaluate except AttributeError: # Pure number pass terms.append(term) deriv = EvalDerivative(*terms, base=expr) return deriv