Coverage for ase / optimize / gpmin / gp.py: 53.85%
65 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-30 08:22 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-30 08:22 +0000
1# fmt: off
3import numpy as np
4from scipy.linalg import cho_factor, cho_solve, solve_triangular
5from scipy.optimize import minimize
7from ase.optimize.gpmin.kernel import SquaredExponential
8from ase.optimize.gpmin.prior import ZeroPrior
11class GaussianProcess():
12 """Gaussian Process Regression
13 It is recommended to be used with other Priors and Kernels from
14 ase.optimize.gpmin
16 Parameters
17 ----------
19 prior: Prior class, as in ase.optimize.gpmin.prior
20 Defaults to ZeroPrior
22 kernel: Kernel function for the regression, as in
23 ase.optimize.gpmin.kernel
24 Defaults to the Squared Exponential kernel with derivatives
25 """
27 def __init__(self, prior=None, kernel=None):
28 if kernel is None:
29 self.kernel = SquaredExponential()
30 else:
31 self.kernel = kernel
33 if prior is None:
34 self.prior = ZeroPrior()
35 else:
36 self.prior = prior
38 def set_hyperparams(self, params):
39 """Set hyperparameters of the regression.
40 This is a list containing the parameters of the
41 kernel and the regularization (noise)
42 of the method as the last entry.
43 """
44 self.hyperparams = params
45 self.kernel.set_params(params[:-1])
46 self.noise = params[-1]
48 def train(self, X, Y, noise=None):
49 """Produces a PES model from data.
51 Given a set of observations, X, Y, compute the K matrix
52 of the Kernel given the data (and its cholesky factorization)
53 This method should be executed whenever more data is added.
55 Parameters
56 ----------
58 X: observations (i.e. positions). numpy array with shape: nsamples x D
59 Y: targets (i.e. energy and forces). numpy array with
60 shape (nsamples, D+1)
61 noise: Noise parameter in the case it needs to be restated.
62 """
63 if noise is not None:
64 self.noise = noise # Set noise attribute to a different value
66 self.X = X.copy() # Store the data in an attribute
67 n = self.X.shape[0]
68 D = self.X.shape[1]
69 regularization = np.array(n * ([self.noise * self.kernel.l] +
70 D * [self.noise]))
72 K = self.kernel.kernel_matrix(X) # Compute the kernel matrix
73 K[range(K.shape[0]), range(K.shape[0])] += regularization**2
75 self.m = self.prior.prior(X)
76 self.a = Y.flatten() - self.m
77 self.L, self.lower = cho_factor(K, lower=True, check_finite=True)
78 cho_solve((self.L, self.lower), self.a, overwrite_b=True,
79 check_finite=True)
81 def predict(self, x, get_variance=False):
82 """Given a trained Gaussian Process, it predicts the value and the
83 uncertainty at point x. It returns f and V:
84 f : prediction: [y, grady]
85 V : Covariance matrix. Its diagonal is the variance of each component
86 of f.
88 Parameters
89 ----------
91 x (1D np.array): The position at which the prediction is computed
92 get_variance (bool): if False, only the prediction f is returned
93 if True, the prediction f and the variance V are
94 returned: Note V is O(D*nsample2)
95 """
96 n = self.X.shape[0]
97 k = self.kernel.kernel_vector(x, self.X, n)
98 f = self.prior.prior(x) + np.dot(k, self.a)
99 if get_variance:
100 v = solve_triangular(self.L, k.T.copy(), lower=True,
101 check_finite=False)
102 variance = self.kernel.kernel(x, x)
103 # covariance = np.matmul(v.T, v)
104 covariance = np.tensordot(v, v, axes=(0, 0))
105 V = variance - covariance
106 return f, V
107 return f
109 def neg_log_likelihood(self, params, *args):
110 """Negative logarithm of the marginal likelihood and its derivative.
111 It has been built in the form that suits the best its optimization,
112 with the scipy minimize module, to find the optimal hyperparameters.
114 Parameters
115 ----------
117 l: The scale for which we compute the marginal likelihood
118 *args: Should be a tuple containing the inputs and targets
119 in the training set-
120 """
121 X, Y = args
122 # Come back to this
123 self.kernel.set_params(np.array([params[0], params[1], self.noise]))
124 self.train(X, Y)
125 y = Y.flatten()
127 # Compute log likelihood
128 logP = (-0.5 * np.dot(y - self.m, self.a) -
129 np.sum(np.log(np.diag(self.L))) -
130 X.shape[0] * 0.5 * np.log(2 * np.pi))
132 # Gradient of the loglikelihood
133 grad = self.kernel.gradient(X)
135 # vectorizing the derivative of the log likelihood
136 D_P_input = np.array([np.dot(np.outer(self.a, self.a), g)
137 for g in grad])
138 D_complexity = np.array([cho_solve((self.L, self.lower), g)
139 for g in grad])
141 DlogP = 0.5 * np.trace(D_P_input - D_complexity, axis1=1, axis2=2)
142 return -logP, -DlogP
144 def fit_hyperparameters(self, X, Y, tol=1e-2, eps=None):
145 """Given a set of observations, X, Y; optimize the scale
146 of the Gaussian Process maximizing the marginal log-likelihood.
147 This method calls TRAIN there is no need to call the TRAIN method
148 again. The method also sets the parameters of the Kernel to their
149 optimal value at the end of execution
151 Parameters
152 ----------
154 X: observations(i.e. positions). numpy array with shape: nsamples x D
155 Y: targets (i.e. energy and forces).
156 numpy array with shape (nsamples, D+1)
157 tol: tolerance on the maximum component of the gradient of the
158 log-likelihood.
159 (See scipy's L-BFGS-B documentation:
160 https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html)
161 eps: include bounds to the hyperparameters as a +- a percentage
162 if eps is None there are no bounds in the optimization
164 Returns
165 -------
167 result (dict) :
168 result = {'hyperparameters': (numpy.array) New hyperparameters,
169 'converged': (bool) True if it converged,
170 False otherwise
171 }
172 """
173 params = np.copy(self.hyperparams)[:2]
174 arguments = (X, Y)
175 if eps is not None:
176 bounds = [((1 - eps) * p, (1 + eps) * p) for p in params]
177 else:
178 bounds = None
180 result = minimize(self.neg_log_likelihood, params, args=arguments,
181 method='L-BFGS-B', jac=True, bounds=bounds,
182 options={'gtol': tol, 'ftol': 0.01 * tol})
184 if not result.success:
185 converged = False
186 else:
187 converged = True
188 self.hyperparams = np.array([result.x.copy()[0],
189 result.x.copy()[1], self.noise])
190 self.set_hyperparams(self.hyperparams)
191 return {'hyperparameters': self.hyperparams, 'converged': converged}