def __init__(self, y, d, sigma=0.0, phi='phs3', eps=1.0, order=None): y = np.asarray(y, dtype=float) assert_shape(y, (None, None), 'y') ny, ndim = y.shape d = np.asarray(d, dtype=float) assert_shape(d, (ny, ), 'd') if np.isscalar(sigma): sigma = np.full(ny, sigma, dtype=float) else: sigma = np.asarray(sigma, dtype=float) assert_shape(sigma, (ny, ), 'sigma') phi = get_rbf(phi) if not np.isscalar(eps): raise ValueError('The shape parameter should be a float') # If `phi` is not in `_MIN_ORDER`, then the RBF is either positive definite # (no minimum polynomial order) or user-defined min_order = _MIN_ORDER.get(phi, -1) if order is None: order = max(min_order, 0) elif order < min_order: logger.warning( 'The polynomial order should not be below %d for %s in order for the ' 'interpolant to be well-posed' % (min_order, phi)) order = int(order) # For improved numerical stability, shift the observations so that their # centroid is at zero center = y.mean(axis=0) y = y - center # Build the system of equations and solve for the RBF and mononomial # coefficients Kyy = phi(y, y, eps=eps) S = scipy.sparse.diags(sigma**2) Py = mvmonos(y, order) nmonos = Py.shape[1] if nmonos > ny: raise ValueError( 'The polynomial order is too high. The number of monomials, %d, ' 'exceeds the number of observations, %d' % (nmonos, ny)) z = np.zeros(nmonos, dtype=float) phi_coeff, poly_coeff = PartitionedSolver(Kyy + S, Py).solve(d, z) self.y = y self.phi = phi self.eps = eps self.order = order self.center = center self.phi_coeff = phi_coeff self.poly_coeff = poly_coeff
def _sanitize_arguments(y, d, sigma, phi, eps, order, k=None): '''Sanitize input to RBFInterpolant and KNearestRBFInterpolant''' y = np.asarray(y, dtype=float) assert_shape(y, (None, None), 'y') ny, ndim = y.shape d = np.asarray(d, dtype=float) assert_shape(d, (ny, ), 'd') if np.isscalar(sigma): sigma = np.full(ny, sigma, dtype=float) else: sigma = np.asarray(sigma, dtype=float) assert_shape(sigma, (ny, ), 'sigma') phi = get_rbf(phi) if not np.isscalar(eps): raise ValueError('`eps` should be a scalar.') # If `phi` is not in `_MIN_ORDER`, then the RBF is either positive definite # (no minimum polynomial order) or user-defined (no known minimum # polynomial order) min_order = _MIN_ORDER.get(phi, -1) if order is None: order = max(min_order, 0) else: order = int(order) if order < -1: raise ValueError('`order` must be at least -1.') elif order < min_order: logger.warning( 'The polynomial order should not be below %d when `phi` is ' '%s. The interpolant may not be well-posed.' % (min_order, phi)) nmonos = monomial_count(order, ndim) if k is None: nobs = ny else: # make sure the number of neighbors does not exceed the number of # observations. k = int(min(k, ny)) nobs = k if nmonos > nobs: raise ValueError( 'At least %d data points are required when `order` is %d and the ' 'number of dimensions is %d' % (nmonos, order, ndim)) return y, d, sigma, phi, eps, order, k
def __init__(self, y, d, sigma=0.0, k=20, phi='phs3', eps=1.0, order=None): y = np.asarray(y, dtype=float) assert_shape(y, (None, None), 'y') ny, ndim = y.shape d = np.asarray(d, dtype=float) assert_shape(d, (ny, ), 'd') if np.isscalar(sigma): sigma = np.full(ny, sigma, dtype=float) else: sigma = np.asarray(sigma, dtype=float) assert_shape(sigma, (ny, ), 'sigma') # make sure the number of nearest neighbors used for interpolation does not # exceed the number of observations k = min(int(k), ny) phi = get_rbf(phi) if isinstance(phi, SparseRBF): raise ValueError('SparseRBF instances are not supported') if not np.isscalar(eps): raise ValueError('The shape parameter should be a float') min_order = _MIN_ORDER.get(phi, -1) if order is None: order = max(min_order, 0) elif order < min_order: logger.warning( 'The polynomial order should not be below %d for %s in order for the ' 'interpolant to be well-posed' % (min_order, phi)) order = int(order) nmonos = monomial_count(order, ndim) if nmonos > k: raise ValueError( 'The polynomial order is too high. The number of monomials, %d, ' 'exceeds the number of neighbors used for interpolation, %d' % (nmonos, k)) tree = KDTree(y) self.y = y self.d = d self.sigma = sigma self.k = k self.eps = eps self.phi = phi self.order = order self.tree = tree
def __init__(self, y, d, sigma=None, eps=1.0, phi=phs3, order=1, extrapolate=True): y = np.asarray(y) assert_shape(y, (None, None), 'y') nobs, dim = y.shape d = np.asarray(d) assert_shape(d, (nobs, ), 'd') if sigma is None: # if sigma is not specified then it is zeros sigma = np.zeros(nobs) elif np.isscalar(sigma): # if a float is specified then use it as the uncertainties for # all observations sigma = np.repeat(sigma, nobs) else: sigma = np.asarray(sigma) assert_shape(sigma, (nobs, ), 'sigma') phi = get_rbf(phi) # form block consisting of the RBF and uncertainties on the # diagonal K = phi(y, y, eps=eps) Cd = scipy.sparse.diags(sigma**2) # form the block consisting of the monomials pwr = powers(order, dim) P = mvmonos(y, pwr) # create zeros vector for the right-hand-side z = np.zeros((pwr.shape[0], )) # solve for the RBF and mononomial coefficients phi_coeff, poly_coeff = PartitionedSolver(K + Cd, P).solve(d, z) self._y = y self._phi = phi self._order = order self._eps = eps self._phi_coeff = phi_coeff self._poly_coeff = poly_coeff self._pwr = pwr self.extrapolate = extrapolate
def weights(x, s, diffs, coeffs=None, phi=phs3, order=None, eps=1.0): ''' Returns the weights which map a functions values at `s` to an approximation of that functions derivative at `x`. The weights are computed using the RBF-FD method described in [1]. In this function `x` is a single point in D-dimensional space. Use `weight_matrix` to compute the weights for multiple point. Parameters ---------- x : (D,) array Target point. The weights will approximate the derivative at this point. s : (N, D) array Stencil points. The derivative will be approximated with a weighted sum of values at this point. diffs : (D,) int array or (K, D) int array Derivative orders for each spatial dimension. For example `[2, 0]` indicates that the weights should approximate the second derivative with respect to the first spatial dimension in two-dimensional space. diffs can also be a (K, D) array, where each (D,) sub-array is a term in a differential operator. For example the two-dimensional Laplacian can be represented as `[[2, 0], [0, 2]]`. coeffs : (K,) array, optional Coefficients for each term in the differential operator specified with `diffs`. Defaults to an array of ones. If `diffs` was specified as a (D,) array then `coeffs` should be a length 1 array. phi : rbf.basis.RBF instance or str, optional Type of RBF. Select from those available in `rbf.basis` or create your own. order : int, optional Order of the added polynomial. This defaults to the highest derivative order. For example, if `diffs` is `[[2, 0], [0, 1]]`, then order is set to 2. eps : float or (N,) array, optional Shape parameter for each RBF, which have centers `s`. This only makes a difference when using RBFs that are not scale invariant. All the predefined RBFs except for the odd order polyharmonic splines are not scale invariant. Returns ------- out : (N,) array RBF-FD weights Examples -------- Calculate the weights for a one-dimensional second order derivative. >>> x = np.array([1.0]) >>> s = np.array([[0.0], [1.0], [2.0]]) >>> diff = (2,) >>> weights(x, s, diff) array([ 1., -2., 1.]) Calculate the weights for estimating an x derivative from three points in a two-dimensional plane >>> x = np.array([0.25, 0.25]) >>> s = np.array([[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]]) >>> diff = (1, 0) >>> weights(x, s, diff) array([ -1., 1., 0.]) References ---------- [1] Fornberg, B. and N. Flyer. A Primer on Radial Basis Functions with Applications to the Geosciences. SIAM, 2015. ''' x = np.asarray(x, dtype=float) assert_shape(x, (None, ), 'x') s = np.asarray(s, dtype=float) assert_shape(s, (None, x.shape[0]), 's') diffs = np.asarray(diffs, dtype=int) diffs = _reshape_diffs(diffs) if coeffs is None: coeffs = np.ones(diffs.shape[0], dtype=float) else: coeffs = np.asarray(coeffs, dtype=float) assert_shape(coeffs, (diffs.shape[0], ), 'coeffs') phi = get_rbf(phi) # stencil size and number of dimensions size, dim = s.shape # get the maximum polynomial order allowed for this stencil size max_order = _max_poly_order(size, dim) if order is None: # If the polynomial order is not specified, make it equal to the derivative # order, provided that the stencil size is large enough. order = _default_poly_order(diffs) order = min(order, max_order) if order > max_order: raise ValueError('Polynomial order is too high for the stencil size') # center the stencil on `x` for improved numerical stability s = s - x x = np.zeros_like(x) # get the powers for the added monomials pwr = powers(order, dim) # evaluate the RBF and monomials at each point in the stencil. This becomes # the left-hand-side A = phi(s, s, eps=eps) P = mvmonos(s, pwr) # Evaluate the RBF and monomials for each term in the differential operator. # This becomes the right-hand-side. a = np.zeros((1, size), dtype=float) p = np.zeros((1, pwr.shape[0]), dtype=float) for c, d in zip(coeffs, diffs): a += c * phi(x[None, :], s, eps=eps, diff=d) p += c * mvmonos(x[None, :], pwr, diff=d) # squeeze `a` and `p` into 1d arrays. `a` is ran through as_array because it # may be sparse. a = as_array(a)[0] p = p[0] # attempt to compute the RBF-FD weights try: w = PartitionedSolver(A, P).solve(a, p)[0] return w except np.linalg.LinAlgError: raise np.linalg.LinAlgError( 'An error was raised while computing the RBF-FD weights at point %s ' 'with the RBF %s and the polynomial order %s. This may be due to a ' 'stencil with duplicate or collinear points. The stencil contains the ' 'following points:\n%s' % (x, phi, order, s))
def weights(x, s, diffs, coeffs=None, phi=phs3, order=None, eps=1.0): ''' Returns the weights which map a function's values at `s` to an approximation of that function's derivative at `x`. The weights are computed using the RBF-FD method described in [1] Parameters ---------- x : (..., D) float array Target points where the derivative is being approximated s : (..., M, D) float array Stencils for each target point diffs : (D,) int array or (K, D) int array Derivative orders for each spatial dimension. For example `[2, 0]` indicates that the weights should approximate the second derivative with respect to the first spatial dimension in two-dimensional space. `diffs` can also be a (K, D) array, where each (D,) sub-array is a term in a differential operator. For example, the two-dimensional Laplacian can be represented as `[[2, 0], [0, 2]]`. coeffs : (K, ...) float array, optional Coefficients for each term in the differential operator specified with `diffs`. The coefficients can vary between target points. Defaults to an array of ones. phi : rbf.basis.RBF instance or str, optional Type of RBF. See `rbf.basis` for the available options. order : int, optional Order of the added polynomial. This defaults to the highest derivative order. For example, if `diffs` is `[[2, 0], [0, 1]]`, then this is set to 2. eps : float or float array, optional Shape parameter for each RBF Returns ------- (..., M) float array RBF-FD weights for each target point Examples -------- Calculate the weights for a one-dimensional second order derivative. >>> x = np.array([1.0]) >>> s = np.array([[0.0], [1.0], [2.0]]) >>> diff = (2,) >>> weights(x, s, diff) array([ 1., -2., 1.]) Calculate the weights for estimating an x derivative from three points in a two-dimensional plane >>> x = np.array([0.25, 0.25]) >>> s = np.array([[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]]) >>> diff = (1, 0) >>> weights(x, s, diff) array([ -1., 1., 0.]) References ---------- [1] Fornberg, B. and N. Flyer. A Primer on Radial Basis Functions with Applications to the Geosciences. SIAM, 2015. ''' x = np.asarray(x, dtype=float) assert_shape(x, (..., None), 'x') bcast = x.shape[:-1] ndim = x.shape[-1] s = np.asarray(s, dtype=float) assert_shape(s, (..., None, ndim), 's') # broadcast leading dimensions of `s` to match leading dimensions of `x` s = np.broadcast_to(s, bcast + s.shape[-2:]) ssize = s.shape[-2] diffs = np.asarray(diffs, dtype=int) diffs = np.atleast_2d(diffs) assert_shape(diffs, (None, ndim), 'diffs') if coeffs is None: coeffs = np.ones(len(diffs), dtype=float) else: coeffs = np.asarray(coeffs, dtype=float) assert_shape(coeffs, (len(diffs), ...), 'coeffs') # broadcast each element in `coeffs` to match leading dimensions of `x` coeffs = [np.broadcast_to(c, bcast) for c in coeffs] phi = get_rbf(phi) # get the maximum polynomial order allowed for this stencil size max_order = _max_poly_order(ssize, ndim) if order is None: # If the polynomial order is not specified, make it equal to the derivative # order, provided that the stencil size is large enough. order = diffs.sum(axis=1).max() order = min(order, max_order) if order > max_order: raise ValueError('Polynomial order is too high for the stencil size') # center the stencil on `x` for improved numerical stability x = x[..., None, :] s = s - x x = np.zeros_like(x) # get the powers for the added monomials pwr = monomial_powers(order, ndim) # evaluate the RBF and monomials at each point in the stencil. This becomes # the left-hand-side A = phi(s, s, eps=eps) P = mvmonos(s, pwr) Pt = np.einsum('...ij->...ji', P) Z = np.zeros(bcast + (len(pwr), len(pwr)), dtype=float) LHS = np.concatenate( (np.concatenate((A, P), axis=-1), np.concatenate((Pt, Z), axis=-1)), axis=-2) # Evaluate the RBF and monomials at the target points for each term in the # differential operator. This becomes the right-hand-side. a, p = 0.0, 0.0 for c, d in zip(coeffs, diffs): a += c[..., None, None]*phi(x, s, eps=eps, diff=d) p += c[..., None, None]*mvmonos(x, pwr, diff=d) # convert `a` to an array because phi may be a sparse RBF a = as_array(a)[..., 0, :] p = p[..., 0, :] rhs = np.concatenate((a, p), axis=-1) w = np.linalg.solve(LHS, rhs)[..., :ssize] return w