vertlayers = np.cumsum(np.random.uniform(2, 20, 10).astype(np.int)) vertlayers = vertlayers[vertlayers < nx] nvertlayers = len(vertlayers) A = 1500 * np.ones((nz, nx)) for top, base in zip(horlayers[:-1], horlayers[1:]): A[top:base] = np.random.normal(2000, 200) for top, base in zip(vertlayers[:-1], vertlayers[1:]): A[horlayers[-1]:, top:base] = np.random.normal(2000, 200) v = np.zeros((2, nz, nx)) v[0, :horlayers[-1]] = 1 v[1, horlayers[-1]:] = 1 Ddop = pylops.FirstDirectionalDerivative((nz, nx), v=v, sampling=(nz, nx)) D2dop = pylops.SecondDirectionalDerivative((nz, nx), v=v, sampling=(nz, nx)) dirder = Ddop * A.ravel() dirder = dirder.reshape(nz, nx) dir2der = D2dop * A.ravel() dir2der = dir2der.reshape(nz, nx) jump = 4 fig, axs = plt.subplots(3, 1, figsize=(4, 9)) im = axs[0].imshow(A, cmap='gist_rainbow', extent=(0, nx//jump, nz//jump, 0)) q = axs[0].quiver(np.arange(nx//jump)+.5, np.arange(nz//jump)+.5, np.flipud(v[1, ::jump, ::jump]), np.flipud(v[0, ::jump, ::jump]), color='w', linewidths=20) axs[0].set_title('x') axs[0].axis('tight')
def SecondDirectionalDerivative(shape: tuple, directions: np.ndarray, step: Union[float, Tuple[float, ...]] = 1., edge: bool = True, dtype: str = 'float64'): r""" Second directional derivative. Computes the second directional derivative of a multi-dimensional array (at least two dimensions are required) along either a single common direction or different ``directions`` for each entry of the array. Parameters ---------- shape: tuple Shape of the input array. directions: np.ndarray Single direction (array of size :math:`n_{dims}`) or different directions for each entry (array of size :math:`[n_{dims} \times (n_{d_0} \times ... \times n_{d_{n_{dims}}})]`). Each column should be normalised. step: Union[float, Tuple[float, ...]] Step size in each direction. edge: bool Use reduced order derivative at edges (``True``) or ignore them (``False``). dtype: str Type of elements in input vector. Returns ------- :py:class:`pycsou.linop.base.PyLopLinearOperator` Second directional derivative operator. Examples -------- .. testsetup:: import numpy as np from pycsou.linop.diff import SecondDirectionalDerivative from pycsou.util.misc import peaks .. doctest:: >>> x = np.linspace(-2.5, 2.5, 100) >>> X,Y = np.meshgrid(x,x) >>> Z = peaks(X, Y) >>> direction = np.array([1,0]) >>> Dop = SecondDirectionalDerivative(shape=Z.shape, directions=direction) >>> dir_d2 = (Dop * Z.reshape(-1)).reshape(Z.shape) .. plot:: import numpy as np import matplotlib.pyplot as plt from pycsou.linop.diff import FirstDirectionalDerivative, SecondDirectionalDerivative from pycsou.util.misc import peaks x = np.linspace(-2.5, 2.5, 25) X,Y = np.meshgrid(x,x) Z = peaks(X, Y) directions = np.zeros(shape=(2,Z.size)) directions[0, :Z.size//2] = 1 directions[1, Z.size//2:] = 1 Dop = FirstDirectionalDerivative(shape=Z.shape, directions=directions) Dop2 = SecondDirectionalDerivative(shape=Z.shape, directions=directions) y = Dop * Z.flatten() y2 = Dop2 * Z.flatten() plt.figure() h = plt.pcolormesh(X,Y,Z, shading='auto') plt.quiver(x, x, directions[1].reshape(X.shape), directions[0].reshape(X.shape)) plt.colorbar(h) plt.title('Signal and directions of derivatives') plt.figure() h = plt.pcolormesh(X,Y,y.reshape(X.shape), shading='auto') plt.quiver(x, x, directions[1].reshape(X.shape), directions[0].reshape(X.shape)) plt.colorbar(h) plt.title('First Directional derivatives') plt.figure() h = plt.pcolormesh(X,Y,y2.reshape(X.shape), shading='auto') plt.colorbar(h) plt.title('Second Directional derivatives') plt.show() Notes ----- The ``SecondDirectionalDerivative`` applies a second-order derivative to a multi-dimensional array along the direction defined by the unitary vector :math:`\mathbf{v}`: .. math:: d^2_\mathbf{v} f = - d_\mathbf{v}^\ast (d_\mathbf{v} f) where :math:`d_\mathbf{v}` is the first-order directional derivative implemented by :py:func:`~pycsou.linop.diff.FirstDirectionalDerivative`. The above formula generalises the well-known relationship: .. math:: \Delta f= -\text{div}(\nabla f), where minus the divergence operator is the adjoint of the gradient. **Note that problematic values at edges are set to zero.** See Also -------- :py:func:`~pycsou.linop.diff.FirstDirectionalDerivative`, :py:func:`~pycsou.linop.diff.SecondDerivative` """ Pylop = PyLopLinearOperator( pylops.SecondDirectionalDerivative(dims=shape, v=directions, sampling=step, edge=edge, dtype=dtype)) kill_edges = np.ones(shape=shape) for axis in range(len(shape)): kill_edges = np.swapaxes(kill_edges, axis, 0) kill_edges[-2:] = 0 kill_edges[:2] = 0 kill_edges = np.swapaxes(kill_edges, 0, axis) KillEdgeOp = DiagonalOperator(kill_edges.reshape(-1)) DirD2 = KillEdgeOp * Pylop return DirD2