Module conduction.inversion.grad_ad

Copyright 2017 Ben Mather

This file is part of Conduction https://git.dias.ie/itherc/conduction/

Conduction is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or any later version.

Conduction is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along with Conduction. If not, see http://www.gnu.org/licenses/.

Expand source code
"""
Copyright 2017 Ben Mather

This file is part of Conduction <https://git.dias.ie/itherc/conduction/>

Conduction is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or any later version.

Conduction is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with Conduction.  If not, see <http://www.gnu.org/licenses/>.
"""

def gradient_ad(df, *varargs, **kwargs):
    """
    Adjoint to the numpy.gradient function.
    
    Interior
    --------
     out[slice1] = (f[slice4] - f[slice2]) / (2. * dx[i])

     dgdf4 = 1/(2*dx)
     dgdf2 = -1/(2*dx)

     df_ad[slice4] += dgdf4*df[axis][slice1]
     df_ad[slice2] += dgdf2*df[axis][slice1]

    Edges
    -----
     out[slice1] = (f[slice2] - f[slice3])/dx

     dgdf2 = 1/dx
     dgdf3 = -1/dx

     df_ad[slice2] += dgdf2*df[slice1]
     df_ad[slice3] += dgdf3*df[slice1]

    """
    import numpy as np

    df = np.asanyarray(df)
    N = df.ndim - 1  # number of dimensions

    axes = kwargs.pop('axis', None)
    if axes is None:
        axes = tuple(list(range(N)))
    else:
        axes = _nx.normalize_axis_tuple(axes, N)

    len_axes = len(axes)
    n = len(varargs)
    if n == 0:
        dx = [1.0] * len_axes
    elif n == len_axes or (n == 1 and np.isscalar(varargs[0])):
        dx = list(varargs)
        for i, distances in enumerate(dx):
            if np.isscalar(distances):
                continue
            if len(distances) != f.shape[axes[i]]:
                raise ValueError("distances must be either scalars or match "
                                 "the length of the corresponding dimension")
            diffx = np.diff(dx[i])
            # if distances are constant reduce to the scalar case
            # since it brings a consistent speedup
            if (diffx == diffx[0]).all():
                diffx = diffx[0]
            dx[i] = diffx
        if len(dx) == 1:
            dx *= len_axes
    else:
        raise TypeError("invalid number of arguments")


    edge_order = kwargs.pop('edge_order', 1)
    if kwargs:
        raise TypeError('"{}" are not valid keyword arguments.'.format(
                                                  '", "'.join(kwargs.keys())))
    if edge_order > 2:
        raise ValueError("'edge_order' greater than 2 not supported")


    # create slice objects --- initially all are [:, :, ..., :]
    slice1 = [slice(None)]*N
    slice2 = [slice(None)]*N
    slice3 = [slice(None)]*N
    slice4 = [slice(None)]*N

    otype = df.dtype.char
    if otype not in ['f', 'd', 'F', 'D', 'm', 'M']:
        otype = 'd'
        
    df_ad = np.zeros_like(df[0], dtype=otype)
        
    for i, axis in enumerate(axes):
        uniform_spacing = np.isscalar(dx[i])

        # Numerical differentiation: 2nd order interior
        slice1[axis] = slice(1, -1)
        slice2[axis] = slice(None, -2)
        slice3[axis] = slice(1, -1)
        slice4[axis] = slice(2, None)
        # out[slice1] = a * f[slice2] + b * f[slice3] + c * f[slice4]
        # out[slice1] = (f[slice4] - f[slice2]) / (2. * dx[i])

        dgdf4 = 1./(2*dx[i])
        dgdf2 = -1./(2*dx[i])

        df_ad[tuple(slice4)] += dgdf4*df[i][tuple(slice1)]
        df_ad[tuple(slice2)] += dgdf2*df[i][tuple(slice1)]

        # Numerical differentiation: 1st order edges
        if edge_order == 1:
            slice1[axis] = 0
            slice2[axis] = 1
            slice3[axis] = 0
            dx_0 = dx[i] if uniform_spacing else dx[i][0]

            # 1D equivalent -- out[0] = (y[1] - y[0]) / (x[1] - x[0])
            # out[slice1] = (y[slice2] - y[slice3]) / dx_0

            dgdf2 = 1./dx_0
            dgdf3 = -1./dx_0

            df_ad[tuple(slice2)] += dgdf2*df[i][tuple(slice1)]
            df_ad[tuple(slice3)] += dgdf3*df[i][tuple(slice1)]

            slice1[axis] = -1
            slice2[axis] = -1
            slice3[axis] = -2
            dx_n = dx[i] if uniform_spacing else dx[i][-1]

            # 1D equivalent -- out[-1] = (y[-1] - y[-2]) / (x[-1] - x[-2])
            # out[slice1] = (y[slice2] - y[slice3]) / dx_n

            dgdf2 = 1./dx_n
            dgdf3 = -1./dx_n

            df_ad[tuple(slice2)] += dgdf2*df[i][tuple(slice1)]
            df_ad[tuple(slice3)] += dgdf3*df[i][tuple(slice1)]
        
        # reset the slice object in this dimension to ":"
        slice1[axis] = slice(None)
        slice2[axis] = slice(None)
        slice3[axis] = slice(None)
        slice4[axis] = slice(None)
            
    return df_ad

Functions

def gradient_ad(df, *varargs, **kwargs)

Adjoint to the numpy.gradient function.

Interior

out[slice1] = (f[slice4] - f[slice2]) / (2. * dx[i])

dgdf4 = 1/(2dx) dgdf2 = -1/(2dx)

df_ad[slice4] += dgdf4df[axis][slice1] df_ad[slice2] += dgdf2df[axis][slice1]

Edges

out[slice1] = (f[slice2] - f[slice3])/dx

dgdf2 = 1/dx dgdf3 = -1/dx

df_ad[slice2] += dgdf2df[slice1] df_ad[slice3] += dgdf3df[slice1]

Expand source code
def gradient_ad(df, *varargs, **kwargs):
    """
    Adjoint to the numpy.gradient function.
    
    Interior
    --------
     out[slice1] = (f[slice4] - f[slice2]) / (2. * dx[i])

     dgdf4 = 1/(2*dx)
     dgdf2 = -1/(2*dx)

     df_ad[slice4] += dgdf4*df[axis][slice1]
     df_ad[slice2] += dgdf2*df[axis][slice1]

    Edges
    -----
     out[slice1] = (f[slice2] - f[slice3])/dx

     dgdf2 = 1/dx
     dgdf3 = -1/dx

     df_ad[slice2] += dgdf2*df[slice1]
     df_ad[slice3] += dgdf3*df[slice1]

    """
    import numpy as np

    df = np.asanyarray(df)
    N = df.ndim - 1  # number of dimensions

    axes = kwargs.pop('axis', None)
    if axes is None:
        axes = tuple(list(range(N)))
    else:
        axes = _nx.normalize_axis_tuple(axes, N)

    len_axes = len(axes)
    n = len(varargs)
    if n == 0:
        dx = [1.0] * len_axes
    elif n == len_axes or (n == 1 and np.isscalar(varargs[0])):
        dx = list(varargs)
        for i, distances in enumerate(dx):
            if np.isscalar(distances):
                continue
            if len(distances) != f.shape[axes[i]]:
                raise ValueError("distances must be either scalars or match "
                                 "the length of the corresponding dimension")
            diffx = np.diff(dx[i])
            # if distances are constant reduce to the scalar case
            # since it brings a consistent speedup
            if (diffx == diffx[0]).all():
                diffx = diffx[0]
            dx[i] = diffx
        if len(dx) == 1:
            dx *= len_axes
    else:
        raise TypeError("invalid number of arguments")


    edge_order = kwargs.pop('edge_order', 1)
    if kwargs:
        raise TypeError('"{}" are not valid keyword arguments.'.format(
                                                  '", "'.join(kwargs.keys())))
    if edge_order > 2:
        raise ValueError("'edge_order' greater than 2 not supported")


    # create slice objects --- initially all are [:, :, ..., :]
    slice1 = [slice(None)]*N
    slice2 = [slice(None)]*N
    slice3 = [slice(None)]*N
    slice4 = [slice(None)]*N

    otype = df.dtype.char
    if otype not in ['f', 'd', 'F', 'D', 'm', 'M']:
        otype = 'd'
        
    df_ad = np.zeros_like(df[0], dtype=otype)
        
    for i, axis in enumerate(axes):
        uniform_spacing = np.isscalar(dx[i])

        # Numerical differentiation: 2nd order interior
        slice1[axis] = slice(1, -1)
        slice2[axis] = slice(None, -2)
        slice3[axis] = slice(1, -1)
        slice4[axis] = slice(2, None)
        # out[slice1] = a * f[slice2] + b * f[slice3] + c * f[slice4]
        # out[slice1] = (f[slice4] - f[slice2]) / (2. * dx[i])

        dgdf4 = 1./(2*dx[i])
        dgdf2 = -1./(2*dx[i])

        df_ad[tuple(slice4)] += dgdf4*df[i][tuple(slice1)]
        df_ad[tuple(slice2)] += dgdf2*df[i][tuple(slice1)]

        # Numerical differentiation: 1st order edges
        if edge_order == 1:
            slice1[axis] = 0
            slice2[axis] = 1
            slice3[axis] = 0
            dx_0 = dx[i] if uniform_spacing else dx[i][0]

            # 1D equivalent -- out[0] = (y[1] - y[0]) / (x[1] - x[0])
            # out[slice1] = (y[slice2] - y[slice3]) / dx_0

            dgdf2 = 1./dx_0
            dgdf3 = -1./dx_0

            df_ad[tuple(slice2)] += dgdf2*df[i][tuple(slice1)]
            df_ad[tuple(slice3)] += dgdf3*df[i][tuple(slice1)]

            slice1[axis] = -1
            slice2[axis] = -1
            slice3[axis] = -2
            dx_n = dx[i] if uniform_spacing else dx[i][-1]

            # 1D equivalent -- out[-1] = (y[-1] - y[-2]) / (x[-1] - x[-2])
            # out[slice1] = (y[slice2] - y[slice3]) / dx_n

            dgdf2 = 1./dx_n
            dgdf3 = -1./dx_n

            df_ad[tuple(slice2)] += dgdf2*df[i][tuple(slice1)]
            df_ad[tuple(slice3)] += dgdf3*df[i][tuple(slice1)]
        
        # reset the slice object in this dimension to ":"
        slice1[axis] = slice(None)
        slice2[axis] = slice(None)
        slice3[axis] = slice(None)
        slice4[axis] = slice(None)
            
    return df_ad