| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289 | """rbf - Radial basis functions for interpolation/smoothing scattered N-D data.Written by John Travers <jtravs@gmail.com>, February 2007Based closely on Matlab code by Alex ChirokovAdditional, large, improvements by Robert HetlandSome additional alterations by Travis OliphantInterpolation with multi-dimensional target domain by Josua SassenPermission to use, modify, and distribute this software is given under theterms of the SciPy (BSD style) license. See LICENSE.txt that came withthis distribution for specifics.NO WARRANTY IS EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.Copyright (c) 2006-2007, Robert Hetland <hetland@tamu.edu>Copyright (c) 2007, John Travers <jtravs@gmail.com>Redistribution and use in source and binary forms, with or withoutmodification, are permitted provided that the following conditions aremet:    * Redistributions of source code must retain the above copyright       notice, this list of conditions and the following disclaimer.    * Redistributions in binary form must reproduce the above       copyright notice, this list of conditions and the following       disclaimer in the documentation and/or other materials provided       with the distribution.    * Neither the name of Robert Hetland nor the names of any       contributors may be used to endorse or promote products derived       from this software without specific prior written permission.THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOTLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FORA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHTOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOTLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANYTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USEOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."""import numpy as npfrom scipy import linalgfrom scipy.special import xlogyfrom scipy.spatial.distance import cdist, pdist, squareform__all__ = ['Rbf']class Rbf:    """    Rbf(*args, **kwargs)    A class for radial basis function interpolation of functions from    N-D scattered data to an M-D domain.    .. note::        `Rbf` is legacy code, for new usage please use `RBFInterpolator`        instead.    Parameters    ----------    *args : arrays        x, y, z, ..., d, where x, y, z, ... are the coordinates of the nodes        and d is the array of values at the nodes    function : str or callable, optional        The radial basis function, based on the radius, r, given by the norm        (default is Euclidean distance); the default is 'multiquadric'::            'multiquadric': sqrt((r/self.epsilon)**2 + 1)            'inverse': 1.0/sqrt((r/self.epsilon)**2 + 1)            'gaussian': exp(-(r/self.epsilon)**2)            'linear': r            'cubic': r**3            'quintic': r**5            'thin_plate': r**2 * log(r)        If callable, then it must take 2 arguments (self, r). The epsilon        parameter will be available as self.epsilon. Other keyword        arguments passed in will be available as well.    epsilon : float, optional        Adjustable constant for gaussian or multiquadrics functions        - defaults to approximate average distance between nodes (which is        a good start).    smooth : float, optional        Values greater than zero increase the smoothness of the        approximation. 0 is for interpolation (default), the function will        always go through the nodal points in this case.    norm : str, callable, optional        A function that returns the 'distance' between two points, with        inputs as arrays of positions (x, y, z, ...), and an output as an        array of distance. E.g., the default: 'euclidean', such that the result        is a matrix of the distances from each point in ``x1`` to each point in        ``x2``. For more options, see documentation of        `scipy.spatial.distances.cdist`.    mode : str, optional        Mode of the interpolation, can be '1-D' (default) or 'N-D'. When it is        '1-D' the data `d` will be considered as 1-D and flattened        internally. When it is 'N-D' the data `d` is assumed to be an array of        shape (n_samples, m), where m is the dimension of the target domain.    Attributes    ----------    N : int        The number of data points (as determined by the input arrays).    di : ndarray        The 1-D array of data values at each of the data coordinates `xi`.    xi : ndarray        The 2-D array of data coordinates.    function : str or callable        The radial basis function. See description under Parameters.    epsilon : float        Parameter used by gaussian or multiquadrics functions. See Parameters.    smooth : float        Smoothing parameter. See description under Parameters.    norm : str or callable        The distance function. See description under Parameters.    mode : str        Mode of the interpolation. See description under Parameters.    nodes : ndarray        A 1-D array of node values for the interpolation.    A : internal property, do not use    See Also    --------    RBFInterpolator    Examples    --------    >>> import numpy as np    >>> from scipy.interpolate import Rbf    >>> rng = np.random.default_rng()    >>> x, y, z, d = rng.random((4, 50))    >>> rbfi = Rbf(x, y, z, d)  # radial basis function interpolator instance    >>> xi = yi = zi = np.linspace(0, 1, 20)    >>> di = rbfi(xi, yi, zi)   # interpolated values    >>> di.shape    (20,)    """    # Available radial basis functions that can be selected as strings;    # they all start with _h_ (self._init_function relies on that)    def _h_multiquadric(self, r):        return np.sqrt((1.0/self.epsilon*r)**2 + 1)    def _h_inverse_multiquadric(self, r):        return 1.0/np.sqrt((1.0/self.epsilon*r)**2 + 1)    def _h_gaussian(self, r):        return np.exp(-(1.0/self.epsilon*r)**2)    def _h_linear(self, r):        return r    def _h_cubic(self, r):        return r**3    def _h_quintic(self, r):        return r**5    def _h_thin_plate(self, r):        return xlogy(r**2, r)    # Setup self._function and do smoke test on initial r    def _init_function(self, r):        if isinstance(self.function, str):            self.function = self.function.lower()            _mapped = {'inverse': 'inverse_multiquadric',                       'inverse multiquadric': 'inverse_multiquadric',                       'thin-plate': 'thin_plate'}            if self.function in _mapped:                self.function = _mapped[self.function]            func_name = "_h_" + self.function            if hasattr(self, func_name):                self._function = getattr(self, func_name)            else:                functionlist = [x[3:] for x in dir(self)                                if x.startswith('_h_')]                raise ValueError("function must be a callable or one of " +                                 ", ".join(functionlist))            self._function = getattr(self, "_h_"+self.function)        elif callable(self.function):            allow_one = False            if hasattr(self.function, 'func_code') or \               hasattr(self.function, '__code__'):                val = self.function                allow_one = True            elif hasattr(self.function, "__call__"):                val = self.function.__call__.__func__            else:                raise ValueError("Cannot determine number of arguments to "                                 "function")            argcount = val.__code__.co_argcount            if allow_one and argcount == 1:                self._function = self.function            elif argcount == 2:                self._function = self.function.__get__(self, Rbf)            else:                raise ValueError("Function argument must take 1 or 2 "                                 "arguments.")        a0 = self._function(r)        if a0.shape != r.shape:            raise ValueError("Callable must take array and return array of "                             "the same shape")        return a0    def __init__(self, *args, **kwargs):        # `args` can be a variable number of arrays; we flatten them and store        # them as a single 2-D array `xi` of shape (n_args-1, array_size),        # plus a 1-D array `di` for the values.        # All arrays must have the same number of elements        self.xi = np.asarray([np.asarray(a, dtype=np.float_).flatten()                              for a in args[:-1]])        self.N = self.xi.shape[-1]        self.mode = kwargs.pop('mode', '1-D')        if self.mode == '1-D':            self.di = np.asarray(args[-1]).flatten()            self._target_dim = 1        elif self.mode == 'N-D':            self.di = np.asarray(args[-1])            self._target_dim = self.di.shape[-1]        else:            raise ValueError("Mode has to be 1-D or N-D.")        if not all([x.size == self.di.shape[0] for x in self.xi]):            raise ValueError("All arrays must be equal length.")        self.norm = kwargs.pop('norm', 'euclidean')        self.epsilon = kwargs.pop('epsilon', None)        if self.epsilon is None:            # default epsilon is the "the average distance between nodes" based            # on a bounding hypercube            ximax = np.amax(self.xi, axis=1)            ximin = np.amin(self.xi, axis=1)            edges = ximax - ximin            edges = edges[np.nonzero(edges)]            self.epsilon = np.power(np.prod(edges)/self.N, 1.0/edges.size)        self.smooth = kwargs.pop('smooth', 0.0)        self.function = kwargs.pop('function', 'multiquadric')        # attach anything left in kwargs to self for use by any user-callable        # function or to save on the object returned.        for item, value in kwargs.items():            setattr(self, item, value)        # Compute weights        if self._target_dim > 1:  # If we have more than one target dimension,            # we first factorize the matrix            self.nodes = np.zeros((self.N, self._target_dim), dtype=self.di.dtype)            lu, piv = linalg.lu_factor(self.A)            for i in range(self._target_dim):                self.nodes[:, i] = linalg.lu_solve((lu, piv), self.di[:, i])        else:            self.nodes = linalg.solve(self.A, self.di)    @property    def A(self):        # this only exists for backwards compatibility: self.A was available        # and, at least technically, public.        r = squareform(pdist(self.xi.T, self.norm))  # Pairwise norm        return self._init_function(r) - np.eye(self.N)*self.smooth    def _call_norm(self, x1, x2):        return cdist(x1.T, x2.T, self.norm)    def __call__(self, *args):        args = [np.asarray(x) for x in args]        if not all([x.shape == y.shape for x in args for y in args]):            raise ValueError("Array lengths must be equal")        if self._target_dim > 1:            shp = args[0].shape + (self._target_dim,)        else:            shp = args[0].shape        xa = np.asarray([a.flatten() for a in args], dtype=np.float_)        r = self._call_norm(xa, self.xi)        return np.dot(self._function(r), self.nodes).reshape(shp)
 |