def __init__(self, y, d, sigma=0.0, phi='phs3', eps=1.0, order=None): y, d, sigma, phi, eps, order, _ = _sanitize_arguments( y, d, sigma, phi, eps, order, None) # 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) if sp.issparse(Kyy): Kyy = sp.csc_matrix(Kyy + sp.diags(sigma**2)) else: Kyy[range(y.shape[0]), range(y.shape[0])] += sigma**2 Py = mvmonos(y, order) phi_coeff, poly_coeff = PartitionedSolver(Kyy, Py).solve(d) 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 __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 __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 __call__(self, x, diff=None, chunk_size=1000): ''' Evaluates the interpolant at `x` Parameters ---------- x : (N, D) array Target points. diff : (D,) int array, optional Derivative order for each spatial dimension. chunk_size : int, optional Break `x` into chunks with this size and evaluate the interpolant for each chunk. Smaller values result in decreased memory usage but also decreased speed. Returns ------- out : (N,) array Values of the interpolant at `x` ''' x = np.asarray(x, dtype=float) assert_shape(x, (None, self._y.shape[1]), 'x') xlen = x.shape[0] # allocate output array out = np.zeros(xlen, dtype=float) count = 0 while count < xlen: start, stop = count, count + chunk_size K = self._phi(x[start:stop], self._y, eps=self._eps, diff=diff) P = mvmonos(x[start:stop], self._pwr, diff=diff) out[start:stop] = (K.dot(self._phi_coeff) + P.dot(self._poly_coeff)) count += chunk_size # return zero for points outside of the convex hull if # extrapolation is not allowed if not self.extrapolate: out[~_in_hull(x, self._y)] = np.nan return out
def __call__(self, x, diff=None, chunk_size=1000): ''' Evaluates the interpolant at `x` Parameters ---------- x : (N, D) float array Target points diff : (D,) int array, optional Derivative order for each spatial dimension chunk_size : int, optional Break `x` into chunks with this size and evaluate the interpolant for each chunk Returns ------- (N,) float array ''' x = np.asarray(x, dtype=float) assert_shape(x, (None, self.y.shape[1]), 'x') nx = x.shape[0] if chunk_size is not None: out = np.zeros(nx, dtype=float) for start in range(0, nx, chunk_size): stop = start + chunk_size out[start:stop] = self(x[start:stop], diff=diff, chunk_size=None) return out x = x - self.center Kxy = self.phi(x, self.y, eps=self.eps, diff=diff) Px = mvmonos(x, self.order, diff=diff) out = Kxy.dot(self.phi_coeff) + Px.dot(self.poly_coeff) return out
def __call__(self, x, diff=None, chunk_size=100): ''' Evaluates the interpolant at `x` Parameters ---------- x : (N, D) float array Target points diff : (D,) int array, optional Derivative order for each spatial dimension chunk_size : int, optional Break `x` into chunks with this size and evaluate the interpolant for each chunk Returns ------- (N,) float array ''' x = np.asarray(x, dtype=float) assert_shape(x, (None, self.y.shape[1]), 'x') nx = x.shape[0] if chunk_size is not None: out = np.zeros(nx, dtype=float) for start in range(0, nx, chunk_size): stop = start + chunk_size out[start:stop] = self(x[start:stop], diff=diff, chunk_size=None) return out # get the indices of the k-nearest observations for each interpolation # point _, nbr = self.tree.query(x, self.k) # multiple interpolation points may have the same neighborhood. Make the # neighborhoods unique so that we only compute the interpolation # coefficients once for each neighborhood nbr, inv = np.unique(np.sort(nbr, axis=1), return_inverse=True, axis=0) nnbr = nbr.shape[0] # Get the observation data for each neighborhood y, d, sigma = self.y[nbr], self.d[nbr], self.sigma[nbr] # shift the centers of each neighborhood to zero for numerical stability centers = y.mean(axis=1) y = y - centers[:, None] # build the left-hand-side interpolation matrix consisting of the RBF # and monomials evaluated at each neighborhood Kyy = self.phi(y, y, eps=self.eps) Kyy[:, range(self.k), range(self.k)] += sigma**2 Py = mvmonos(y, self.order) PyT = np.transpose(Py, (0, 2, 1)) nmonos = Py.shape[2] Z = np.zeros((nnbr, nmonos, nmonos), dtype=float) LHS = np.block([[Kyy, Py], [PyT, Z]]) # build the right-hand-side data vector consisting of the observations for # each neighborhood and extra zeros z = np.zeros((nnbr, nmonos), dtype=float) rhs = np.hstack((d, z)) # solve for the RBF and polynomial coefficients for each neighborhood coeff = np.linalg.solve(LHS, rhs) # expand the arrays from having one entry per neighborhood to one entry per # interpolation point coeff = coeff[inv] y = y[inv] centers = centers[inv] # evaluate at the interpolation points x = x - centers phi_coeff = coeff[:, :self.k] poly_coeff = coeff[:, self.k:] Kxy = self.phi(x[:, None], y, eps=self.eps, diff=diff)[:, 0] Px = mvmonos(x, self.order, diff=diff) out = (Kxy * phi_coeff).sum(axis=1) + (Px * poly_coeff).sum(axis=1) return out
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