def test_deriv(): model = Earth(**default_params) model.fit(X, y) assert_equal(X.shape, model.predict_deriv(X).shape) assert_equal((X.shape[0], 1), model.predict_deriv(X, variables=0).shape) assert_equal((X.shape[0], 1), model.predict_deriv(X, variables='x0').shape) assert_equal((X.shape[0], 3), model.predict_deriv(X, variables=[1, 5, 7]).shape) assert_equal((X.shape[0], 0), model.predict_deriv(X, variables=[]).shape) res_deriv = model.predict_deriv(X, variables=['x2', 'x7', 'x0', 'x1']) assert_equal((X.shape[0], 4), res_deriv.shape) res_deriv = model.predict_deriv(X, variables=['x0']) assert_equal((X.shape[0], 1), res_deriv.shape) assert_equal((X.shape[0], 1), model.predict_deriv(X, variables=[0]).shape)
def test_deriv(): model = Earth(**default_params) model.fit(X, y) assert_equal(X.shape + (1,), model.predict_deriv(X).shape) assert_equal((X.shape[0], 1, 1), model.predict_deriv(X, variables=0).shape) assert_equal((X.shape[0], 1, 1), model.predict_deriv(X, variables='x0').shape) assert_equal((X.shape[0], 3, 1), model.predict_deriv(X, variables=[1, 5, 7]).shape) assert_equal((X.shape[0], 0, 1), model.predict_deriv(X, variables=[]).shape) res_deriv = model.predict_deriv(X, variables=['x2', 'x7', 'x0', 'x1']) assert_equal((X.shape[0], 4, 1), res_deriv.shape) res_deriv = model.predict_deriv(X, variables=['x0']) assert_equal((X.shape[0], 1, 1), res_deriv.shape) assert_equal((X.shape[0], 1, 1), model.predict_deriv(X, variables=[0]).shape)
class MARSInterpolant(Earth): """Compute and evaluate a MARS interpolant MARS builds a model of the form .. math:: \hat{f}(x) = \sum_{i=1}^{k} c_i B_i(x). The model is a weighted sum of basis functions :math:`B_i(x)`. Each basis function :math:`B_i(x)` takes one of the following three forms: 1. a constant 1. 2. a hinge function of the form :math:`\max(0, x - const)` or \ :math:`\max(0, const - x)`. MARS automatically selects variables \ and values of those variables for knots of the hinge functions. 3. a product of two or more hinge functions. These basis functions c \ an model interaction between two or more variables. :param maxp: Initial capacity :type maxp: int :ivar nump: Current number of points :ivar maxp: Initial maximum number of points (can grow) :ivar x: Interpolation points :ivar fx: Function evaluations of interpolation points :ivar dim: Number of dimensions :ivar model: MARS interpolation model """ def __init__(self, maxp=100): self.nump = 0 self.maxp = maxp self.x = None # pylint: disable=invalid-name self.fx = None self.dim = None self.model = Earth() self.updated = False def reset(self): """Reset the interpolation.""" self.nump = 0 self.x = None self.fx = None self.updated = False def _alloc(self, dim): """Allocate storage for x, fx, rhs, and A. :param dim: Number of dimensions :type dim: int """ maxp = self.maxp self.dim = dim self.x = np.zeros((maxp, dim)) self.fx = np.zeros((maxp, 1)) def _realloc(self, dim, extra=1): """Expand allocation to accommodate more points (if needed) :param dim: Number of dimensions :type dim: int :param extra: Number of additional points to accommodate :type extra: int """ if self.nump == 0: self._alloc(dim) elif self.nump + extra > self.maxp: self.maxp = max(self.maxp * 2, self.maxp + extra) self.x.resize((self.maxp, dim)) self.fx.resize((self.maxp, 1)) def get_x(self): """Get the list of data points :return: List of data points :rtype: numpy.array """ return self.x[:self.nump, :] def get_fx(self): """Get the list of function values for the data points. :return: List of function values :rtype: numpy.array """ return self.fx[:self.nump, :] def add_point(self, xx, fx): """Add a new function evaluation :param xx: Point to add :type xx: numpy.array :param fx: The function value of the point to add :type fx: float """ dim = len(xx) self._realloc(dim) self.x[self.nump, :] = xx self.fx[self.nump, :] = fx self.nump += 1 self.updated = False def eval(self, x, ds=None): """Evaluate the MARS interpolant at the point x :param x: Point where to evaluate :type x: numpy.array :param ds: Not used :type ds: None :return: Value of the MARS interpolant at x :rtype: float """ if self.updated is False: self.model.fit(self.get_x(), self.get_fx()) self.updated = True x = np.expand_dims(x, axis=0) fx = self.model.predict(x) return fx[0] def evals(self, x, ds=None): """Evaluate the MARS interpolant at the points x :param x: Points where to evaluate, of size npts x dim :type x: numpy.array :param ds: Not used :type ds: None :return: Values of the MARS interpolant at x, of length npts :rtype: numpy.array """ if self.updated is False: self.model.fit(self.get_x(), self.get_fx()) self.updated = True fx = np.zeros(shape=(x.shape[0], 1)) fx[:, 0] = self.model.predict(x) return fx def deriv(self, x, ds=None): """Evaluate the derivative of the MARS interpolant at a point x :param x: Point for which we want to compute the MARS gradient :type x: numpy.array :param ds: Not used :type ds: None :return: Derivative of the MARS interpolant at x :rtype: numpy.array """ if self.updated is False: self.model.fit(self.get_x(), self.get_fx()) self.updated = True x = np.expand_dims(x, axis=0) dfx = self.model.predict_deriv(x, variables=None) return dfx[0]
class MARSInterpolant(Surrogate): """Compute and evaluate a MARS interpolant MARS builds a model of the form .. math:: \\hat{f}(x) = \\sum_{i=1}^{k} c_i B_i(x). The model is a weighted sum of basis functions :math:`B_i(x)`. Each basis function :math:`B_i(x)` takes one of the following three forms: 1. a constant 1. 2. a hinge function of the form :math:`\\max(0, x - const)` or \ :math:`\\max(0, const - x)`. MARS automatically selects variables \ and values of those variables for knots of the hinge functions. 3. a product of two or more hinge functions. These basis functions c \ an model interaction between two or more variables. :param dim: Number of dimensions :type dim: int :ivar dim: Number of dimensions :ivar num_pts: Number of points in surrogate model :ivar X: Point incorporated in surrogate model (num_pts x dim) :ivar fX: Function values in surrogate model (num_pts x 1) :ivar updated: True if model is up-to-date (no refit needed) :ivar model: Earth object """ def __init__(self, dim): self.num_pts = 0 self.X = np.empty([0, dim]) self.fX = np.empty([0, 1]) self.dim = dim self.updated = False try: from pyearth import Earth self.model = Earth() except ImportError as err: print("Failed to import pyearth") raise err def _fit(self): """Compute new coefficients if the MARS interpolant is not updated.""" with warnings.catch_warnings(): warnings.simplefilter("ignore") # Surpress deprecation warnings if self.updated is False: self.model.fit(self.X, self.fX) self.updated = True def predict(self, xx): """Evaluate the MARS interpolant at the points xx :param xx: Prediction points, must be of size num_pts x dim or (dim, ) :type xx: numpy.ndarray :return: Prediction of size num_pts x 1 :rtype: numpy.ndarray """ self._fit() xx = np.atleast_2d(xx) return np.expand_dims(self.model.predict(xx), axis=1) def predict_deriv(self, xx): """Evaluate the derivative of the MARS interpolant at points xx :param xx: Prediction points, must be of size num_pts x dim or (dim, ) :type xx: numpy.array :return: Derivative of the RBF interpolant at xx :rtype: numpy.array """ self._fit() xx = np.expand_dims(xx, axis=0) dfx = self.model.predict_deriv(xx, variables=None) return dfx[0]
class MARSInterpolant(Surrogate): """Compute and evaluate a MARS interpolant MARS builds a model of the form .. math:: \\hat{f}(x) = \\sum_{i=1}^{k} c_i B_i(x). The model is a weighted sum of basis functions :math:`B_i(x)`. Each basis function :math:`B_i(x)` takes one of the following three forms: 1. a constant 1. 2. a hinge function of the form :math:`\\max(0, x - const)` or \ :math:`\\max(0, const - x)`. MARS automatically selects variables \ and values of those variables for knots of the hinge functions. 3. a product of two or more hinge functions. These basis functions c \ an model interaction between two or more variables. :param dim: Number of dimensions :type dim: int :ivar dim: Number of dimensions :ivar num_pts: Number of points in surrogate model :ivar X: Point incorporated in surrogate model (num_pts x dim) :ivar fX: Function values in surrogate model (num_pts x 1) :ivar updated: True if model is up-to-date (no refit needed) :ivar model: Earth object """ def __init__(self, dim): self.num_pts = 0 self.X = np.empty([0, dim]) self.fX = np.empty([0, 1]) self.dim = dim self.updated = False try: from pyearth import Earth self.model = Earth() except ImportError as err: print("Failed to import pyearth") raise err def _fit(self): """Compute new coefficients if the MARS interpolant is not updated.""" warnings.simplefilter("ignore") # Surpress deprecation warnings if self.updated is False: self.model.fit(self.X, self.fX) self.updated = True def predict(self, xx): """Evaluate the MARS interpolant at the points xx :param xx: Prediction points, must be of size num_pts x dim or (dim, ) :type xx: numpy.ndarray :return: Prediction of size num_pts x 1 :rtype: numpy.ndarray """ self._fit() xx = np.atleast_2d(xx) return np.expand_dims(self.model.predict(xx), axis=1) def predict_deriv(self, xx): """Evaluate the derivative of the MARS interpolant at points xx :param xx: Prediction points, must be of size num_pts x dim or (dim, ) :type xx: numpy.array :return: Derivative of the RBF interpolant at xx :rtype: numpy.array """ self._fit() xx = np.expand_dims(xx, axis=0) dfx = self.model.predict_deriv(xx, variables=None) return dfx[0]
n = 10 X = 20 * numpy.random.uniform(size=(m, n)) - 10 y = 10*numpy.sin(X[:, 6]) + 0.25*numpy.random.normal(size=m) # Compute the known true derivative with respect to the predictive variable y_prime = 10*numpy.cos(X[:, 6]) # Fit an Earth model model = Earth(max_degree=2, minspan_alpha=.5, smooth=True) model.fit(X, y) # Print the model print(model.trace()) print(model.summary()) # Get the predicted values and derivatives y_hat = model.predict(X) y_prime_hat = model.predict_deriv(X, 'x6') # Plot true and predicted function values and derivatives # for the predictive variable plt.subplot(211) plt.plot(X[:, 6], y, 'r.') plt.plot(X[:, 6], y_hat, 'b.') plt.ylabel('function') plt.subplot(212) plt.plot(X[:, 6], y_prime, 'r.') plt.plot(X[:, 6], y_prime_hat[:, 0], 'b.') plt.ylabel('derivative') plt.show()
class MARSInterpolant(Earth): """Compute and evaluate a MARS interpolant :ivar nump: Current number of points :ivar maxp: Initial maximum number of points (can grow) :ivar x: Interpolation points :ivar fx: Function evaluations of interpolation points :ivar dim: Number of dimensions :ivar model: MARS interpolaion model """ def __init__(self, maxp=100): self.nump = 0 self.maxp = maxp self.x = None # pylint: disable=invalid-name self.fx = None self.dim = None self.model = Earth() self.updated = False def reset(self): """Reset the interpolation.""" self.nump = 0 self.x = None self.fx = None self.updated = False def _alloc(self, dim): """Allocate storage for x, fx, rhs, and A. :param dim: Number of dimensions """ maxp = self.maxp self.dim = dim self.x = np.zeros((maxp, dim)) self.fx = np.zeros((maxp, 1)) def _realloc(self, dim, extra=1): """Expand allocation to accommodate more points (if needed) :param dim: Number of dimensions :param extra: Number of additional points to accommodate """ if self.nump == 0: self._alloc(dim) elif self.nump + extra > self.maxp: self.maxp = max(self.maxp * 2, self.maxp + extra) self.x.resize((self.maxp, dim)) self.fx.resize((self.maxp, 1)) def get_x(self): """Get the list of data points :return: List of data points """ return self.x[:self.nump, :] def get_fx(self): """Get the list of function values for the data points. :return: List of function values """ return self.fx[:self.nump, :] def add_point(self, xx, fx): """Add a new function evaluation :param xx: Point to add :param fx: The function value of the point to add """ dim = len(xx) self._realloc(dim) self.x[self.nump, :] = xx self.fx[self.nump, :] = fx self.nump += 1 self.updated = False def eval(self, xx, d=None): """Evaluate the MARS interpolant at the point xx :param xx: Point where to evaluate :return: Value of the MARS interpolant at x """ if self.updated is False: self.model.fit(self.x, self.fx) self.updated = True xx = np.expand_dims(xx, axis=0) fx = self.model.predict(xx) return fx[0] def evals(self, xx, d=None): """Evaluate the MARS interpolant at the points xx :param xx: Points where to evaluate :return: Values of the MARS interpolant at x """ if self.updated is False: self.model.fit(self.x, self.fx) self.updated = True fx = np.zeros(shape=(xx.shape[0], 1)) fx[:, 0] = self.model.predict(xx) return fx def deriv(self, x, d=None): """Evaluate the derivative of the MARS interpolant at x :param x: Data point :return: Derivative of the MARS interpolant at x """ if self.updated is False: self.model.fit(self.x, self.fx) self.updated = True x = np.expand_dims(x, axis=0) dfx = self.model.predict_deriv(x, variables=None) return dfx[0]
class MARSInterpolant(Earth): """Compute and evaluate a MARS interpolant :ivar nump: Current number of points :ivar maxp: Initial maximum number of points (can grow) :ivar x: Interpolation points :ivar fx: Function evaluations of interpolation points :ivar dim: Number of dimensions :ivar model: MARS interpolaion model """ def __init__(self, maxp=100): self.nump = 0 self.maxp = maxp self.x = None # pylint: disable=invalid-name self.fx = None self.dim = None self.model = Earth() self.updated = False def reset(self): """Reset the interpolation.""" self.nump = 0 self.x = None self.fx = None self.updated = False def _alloc(self, dim): """Allocate storage for x, fx, rhs, and A. :param dim: Number of dimensions """ maxp = self.maxp self.dim = dim self.x = np.zeros((maxp, dim)) self.fx = np.zeros((maxp, 1)) def _realloc(self, dim, extra=1): """Expand allocation to accommodate more points (if needed) :param dim: Number of dimensions :param extra: Number of additional points to accommodate """ if self.nump == 0: self._alloc(dim) elif self.nump+extra > self.maxp: self.maxp = max(self.maxp*2, self.maxp+extra) self.x.resize((self.maxp, dim)) self.fx.resize((self.maxp, 1)) def get_x(self): """Get the list of data points :return: List of data points """ return self.x[:self.nump, :] def get_fx(self): """Get the list of function values for the data points. :return: List of function values """ return self.fx[:self.nump, :] def add_point(self, xx, fx): """Add a new function evaluation :param xx: Point to add :param fx: The function value of the point to add """ dim = len(xx) self._realloc(dim) self.x[self.nump, :] = xx self.fx[self.nump, :] = fx self.nump += 1 self.updated = False def eval(self, xx, d=None): """Evaluate the MARS interpolant at the point xx :param xx: Point where to evaluate :return: Value of the MARS interpolant at x """ if self.updated is False: self.model.fit(self.x, self.fx) self.updated = True xx = np.expand_dims(xx, axis=0) fx = self.model.predict(xx) return fx[0] def evals(self, xx, d=None): """Evaluate the MARS interpolant at the points xx :param xx: Points where to evaluate :return: Values of the MARS interpolant at x """ if self.updated is False: self.model.fit(self.x, self.fx) self.updated = True fx = np.zeros(shape=(xx.shape[0], 1)) fx[:, 0] = self.model.predict(xx) return fx def deriv(self, x, d=None): """Evaluate the derivative of the MARS interpolant at x :param x: Data point :return: Derivative of the MARS interpolant at x """ if self.updated is False: self.model.fit(self.x, self.fx) self.updated = True x = np.expand_dims(x, axis=0) dfx = self.model.predict_deriv(x, variables=None) return dfx[0]
class MARSInterpolant(Surrogate): """Compute and evaluate a MARS interpolant MARS builds a model of the form .. math:: \\hat{f}(x) = \\sum_{i=1}^{k} c_i B_i(x). The model is a weighted sum of basis functions :math:`B_i(x)`. Each basis function :math:`B_i(x)` takes one of the following three forms: 1. a constant 1. 2. a hinge function of the form :math:`\\max(0, x - const)` or \ :math:`\\max(0, const - x)`. MARS automatically selects variables \ and values of those variables for knots of the hinge functions. 3. a product of two or more hinge functions. These basis functions c \ an model interaction between two or more variables. :param dim: Number of dimensions :type dim: int :param lb: Lower variable bounds :type lb: numpy.array :param ub: Upper variable bounds :type ub: numpy.array :param output_transformation: Transformation applied to values before fitting :type output_transformation: Callable :ivar dim: Number of dimensions :ivar lb: Lower variable bounds :ivar ub: Upper variable bounds :ivar output_transformation: Transformation to apply to function values before fitting :ivar num_pts: Number of points in surrogate model :ivar X: Point incorporated in surrogate model (num_pts x dim) :ivar fX: Function values in surrogate model (num_pts x 1) :ivar updated: True if model is up-to-date (no refit needed) :ivar model: Earth object """ def __init__(self, dim, lb, ub, output_transformation=None): super().__init__(dim=dim, lb=lb, ub=ub, output_transformation=output_transformation) try: from pyearth import Earth self.model = Earth() except ImportError as err: print("Failed to import pyearth") raise err def _fit(self): """Compute new coefficients if the MARS interpolant is not updated.""" with warnings.catch_warnings(): warnings.simplefilter("ignore") # Surpress deprecation warnings if self.updated is False: fX = self.output_transformation(self.fX.copy()) self.model.fit(self._X, fX) self.updated = True def predict(self, xx): """Evaluate the MARS interpolant at the points xx :param xx: Prediction points, must be of size num_pts x dim or (dim, ) :type xx: numpy.ndarray :return: Prediction of size num_pts x 1 :rtype: numpy.ndarray """ self._fit() xx = to_unit_box(np.atleast_2d(xx), self.lb, self.ub) return np.expand_dims(self.model.predict(xx), axis=1) def predict_deriv(self, xx): """Evaluate the derivative of the MARS interpolant at points xx :param xx: Prediction points, must be of size num_pts x dim or (dim, ) :type xx: numpy.array :return: Derivative of the RBF interpolant at xx :rtype: numpy.array """ self._fit() xx = to_unit_box(np.atleast_2d(xx), self.lb, self.ub) dfx = self.model.predict_deriv(xx, variables=None) return dfx[0] / (self.ub - self.lb)
n = 10 X = 20 * numpy.random.uniform(size=(m, n)) - 10 y = 10 * numpy.sin(X[:, 6]) + 0.25 * numpy.random.normal(size=m) # Compute the known true derivative with respect to the predictive variable y_prime = 10 * numpy.cos(X[:, 6]) # Fit an Earth model model = Earth(max_degree=2, minspan_alpha=.5, smooth=True) model.fit(X, y) # Print the model print(model.trace()) print(model.summary()) # Get the predicted values and derivatives y_hat = model.predict(X) y_prime_hat = model.predict_deriv(X, 'x6') # Plot true and predicted function values and derivatives # for the predictive variable plt.subplot(211) plt.plot(X[:, 6], y, 'r.') plt.plot(X[:, 6], y_hat, 'b.') plt.ylabel('function') plt.subplot(212) plt.plot(X[:, 6], y_prime, 'r.') plt.plot(X[:, 6], y_prime_hat[:, 0], 'b.') plt.ylabel('derivative') plt.show()