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
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
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
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
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)
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)
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)
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)
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
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
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)
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)
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
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 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)
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)
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)
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)
def numeric_weights(deriv_order, indices, x0): return finite_diff_weights(deriv_order, indices, x0)[-1][-1]
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)
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
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)