bfflearn 0.1.0.dev1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. bff/__init__.py +30 -0
  2. bff/bayes/__init__.py +11 -0
  3. bff/bayes/gaussian_process.py +285 -0
  4. bff/bayes/kernels.py +71 -0
  5. bff/bayes/learning.py +484 -0
  6. bff/bayes/likelihoods.py +142 -0
  7. bff/bayes/posterior.py +62 -0
  8. bff/bayes/priors.py +179 -0
  9. bff/bayes/results.py +327 -0
  10. bff/bayes/utils.py +333 -0
  11. bff/cli.py +254 -0
  12. bff/data.py +93 -0
  13. bff/domain/__init__.py +12 -0
  14. bff/domain/bias.py +149 -0
  15. bff/domain/specs.py +347 -0
  16. bff/domain/trainset.py +201 -0
  17. bff/io/__init__.py +12 -0
  18. bff/io/colvars.py +186 -0
  19. bff/io/commands.py +20 -0
  20. bff/io/cp2k.py +330 -0
  21. bff/io/logs.py +213 -0
  22. bff/io/mdp.py +98 -0
  23. bff/io/plumed.py +184 -0
  24. bff/io/progress.py +74 -0
  25. bff/io/schedulers.py +118 -0
  26. bff/io/utils.py +208 -0
  27. bff/mcmc/__init__.py +3 -0
  28. bff/mcmc/convergence.py +168 -0
  29. bff/mcmc/proposal.py +254 -0
  30. bff/mcmc/sampler.py +481 -0
  31. bff/plotting.py +362 -0
  32. bff/qoi/__init__.py +9 -0
  33. bff/qoi/analysis.py +209 -0
  34. bff/qoi/data.py +169 -0
  35. bff/qoi/hbonds.py +255 -0
  36. bff/qoi/rdf.py +108 -0
  37. bff/qoi/routines.py +272 -0
  38. bff/tools.py +168 -0
  39. bff/topology.py +581 -0
  40. bff/workflows/__init__.py +0 -0
  41. bff/workflows/configs.py +1147 -0
  42. bff/workflows/learn.py +77 -0
  43. bff/workflows/md.py +326 -0
  44. bff/workflows/prepare.py +788 -0
  45. bff/workflows/qoi.py +260 -0
  46. bff/workflows/runsims.py +620 -0
  47. bff/workflows/simulate.py +28 -0
  48. bff/workflows/train.py +69 -0
  49. bff/workflows/validate.py +29 -0
  50. bfflearn-0.1.0.dev1.dist-info/METADATA +913 -0
  51. bfflearn-0.1.0.dev1.dist-info/RECORD +55 -0
  52. bfflearn-0.1.0.dev1.dist-info/WHEEL +5 -0
  53. bfflearn-0.1.0.dev1.dist-info/entry_points.txt +2 -0
  54. bfflearn-0.1.0.dev1.dist-info/licenses/LICENSE +674 -0
  55. bfflearn-0.1.0.dev1.dist-info/top_level.txt +1 -0
bff/__init__.py ADDED
@@ -0,0 +1,30 @@
1
+ """Bayesic Force Fields public package namespace."""
2
+
3
+ from importlib import import_module
4
+
5
+ __version__ = "0.1.0.dev1"
6
+
7
+ __all__ = [
8
+ "bayes",
9
+ "data",
10
+ "domain",
11
+ "io",
12
+ "plotting",
13
+ "qoi",
14
+ "tools",
15
+ ]
16
+
17
+
18
+ def __getattr__(name: str):
19
+ if name in {
20
+ "bayes",
21
+ "data",
22
+ "domain",
23
+ "io",
24
+ "plotting",
25
+ "qoi",
26
+ "tools",
27
+ }:
28
+ return import_module(f".{name}", __name__)
29
+
30
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
bff/bayes/__init__.py ADDED
@@ -0,0 +1,11 @@
1
+ from . import gaussian_process, kernels, learning, likelihoods, priors, results, utils
2
+
3
+ __all__ = [
4
+ 'gaussian_process',
5
+ 'kernels',
6
+ 'learning',
7
+ 'likelihoods',
8
+ 'priors',
9
+ 'results',
10
+ 'utils',
11
+ ]
@@ -0,0 +1,285 @@
1
+ from pathlib import Path
2
+ from typing import Self, Union
3
+
4
+ import numpy as np
5
+ import torch
6
+
7
+ from .kernels import gaussian_kernel
8
+ from .utils import check_tensor, nearest_positive_definite, smape
9
+
10
+ PathLike = Union[str, Path]
11
+
12
+
13
+ class LocalGaussianProcess:
14
+ """
15
+ Local Gaussian Process Regression model.
16
+
17
+ Parameters
18
+ ----------
19
+ X_train : torch.Tensor
20
+ Training input features.
21
+ y_train : torch.Tensor
22
+ Training output values.
23
+ y_mean : torch.Tensor
24
+ Mean of the output values.
25
+ lengths : torch.Tensor
26
+ Length scales for the Gaussian kernel.
27
+ width : float
28
+ Width (amplitude) of the Gaussian kernel.
29
+ sigma : float
30
+ Observation noise standard deviation.
31
+ device : str
32
+ Device on which tensors are stored (e.g., "cpu" or "cuda").
33
+
34
+ Attributes
35
+ ----------
36
+ Kdd_inv : torch.Tensor
37
+ Inverse of the training covariance matrix.
38
+
39
+ Methods
40
+ -------
41
+ predict(Xi: torch.Tensor) -> torch.Tensor
42
+ Predict outputs for given input points.
43
+
44
+ Properties
45
+ ----------
46
+ n_params : int
47
+ Number of input parameters (features).
48
+ y_size : int
49
+ Dimensionality of the output values.
50
+ hyperparameters : Dict[str, Union[np.ndarray, float]]
51
+ Dictionary of hyperparameters used in the model.
52
+
53
+ """
54
+
55
+ def __init__(
56
+ self,
57
+ X_train: torch.Tensor, y_train: torch.Tensor, y_mean: torch.Tensor,
58
+ lengths: torch.Tensor, width: float, sigma: float,
59
+ device: str
60
+ ) -> None:
61
+
62
+ self.X_train = check_tensor(X_train, device=device)
63
+ self.y_train = check_tensor(y_train, device=device)
64
+ self.y_mean = check_tensor(y_mean, device=device)
65
+ self.lengths = check_tensor(lengths, device=device)
66
+ self.width = check_tensor(width, device=device)
67
+ self.sigma = check_tensor(sigma, device=device)
68
+ self.device = device
69
+
70
+ n_samples = len(self.X_train)
71
+
72
+ noise = torch.eye(n_samples, device=device) * self.sigma
73
+ Kdd = gaussian_kernel(self.X_train, self.X_train, self.lengths, width) + noise
74
+ Kdd = nearest_positive_definite(Kdd)
75
+ L = torch.linalg.cholesky(Kdd)
76
+ Kdd_inv = torch.cholesky_inverse(L)
77
+ self.Kdd_inv = check_tensor(Kdd_inv, device=device)
78
+
79
+ @property
80
+ def n_params(self) -> int:
81
+ return self.X_train.shape[1]
82
+
83
+ @property
84
+ def y_size(self) -> int:
85
+ return self.y_train.shape[1]
86
+
87
+ @property
88
+ def hyperparameters(self) -> dict[str, Union[np.ndarray, float]]:
89
+ return {
90
+ 'lengths': self.lengths.cpu().numpy(),
91
+ 'width': self.width.cpu().numpy().item(),
92
+ 'sigma': self.sigma.cpu().numpy().item()
93
+ }
94
+
95
+ @torch.no_grad()
96
+ def predict(self, Xi: torch.Tensor) -> torch.Tensor:
97
+ """
98
+ Predict outputs for given input points.
99
+
100
+ Parameters
101
+ ----------
102
+ Xi : torch.Tensor
103
+ Input tensor of shape (n_samples, n_features).
104
+
105
+ Returns
106
+ -------
107
+ torch.Tensor
108
+ Predicted outputs of shape (n_samples, output_dim).
109
+ """
110
+ Xi = check_tensor(Xi, device=self.device)
111
+ Kid = gaussian_kernel(Xi, self.X_train, self.lengths, self.width)
112
+ mean = self.y_mean + (Kid @ self.Kdd_inv) @ (self.y_train - self.y_mean)
113
+ return mean
114
+
115
+ def state_dict(self) -> dict:
116
+ return {
117
+ "X_train": self.X_train,
118
+ "y_train": self.y_train,
119
+ "y_mean": self.y_mean,
120
+ "lengths": self.lengths,
121
+ "width": self.width,
122
+ "sigma": self.sigma,
123
+ "device": self.device,
124
+ }
125
+
126
+ def __repr__(self) -> str:
127
+ hp = self.hyperparameters
128
+ return (
129
+ f"{self.__class__.__name__}(\n"
130
+ f" n_train={self.X_train.shape[0]},\n"
131
+ f" n_params={self.n_params},\n"
132
+ f" lengths={hp['lengths'].tolist()},\n"
133
+ f" width={hp['width']:.4f},\n"
134
+ f" noise={hp['sigma']:.4f},\n"
135
+ f" device='{self.device}'\n"
136
+ f")"
137
+ )
138
+
139
+
140
+ class LGPCommittee:
141
+ """
142
+ Committee of Local Gaussian Process (LGP) models.
143
+
144
+ Parameters
145
+ ----------
146
+ lgps : list of LocalGaussianProcess
147
+ List of LGP models.
148
+ n_observations : int
149
+ Effective number of observations used in the likelihood term.
150
+ reference_values : np.ndarray
151
+ Reference observation vector matched by the surrogate outputs.
152
+ nuisance : float, optional
153
+ Nuisance parameter for model selection (default is None).
154
+ stochastic : bool, optional
155
+ If True, randomly select one model for prediction instead of averaging
156
+ (default is False).
157
+
158
+ Properties
159
+ ----------
160
+ size : int
161
+ Number of LGP models in the committee.
162
+ n_params : int
163
+ Number of input parameters (features) used by the LGP models.
164
+
165
+ Methods
166
+ -------
167
+ predict(X: torch.Tensor) -> torch.Tensor
168
+ Predict outputs by averaging predictions from all LGP models
169
+ or randomly selecting one if stochastic is True.
170
+ validate(X_test: torch.Tensor, y_test: torch.Tensor) -> float
171
+ Validate the committee by computing the
172
+ mean squared error of predictions against test data.
173
+ """
174
+ def __init__(
175
+ self,
176
+ lgps: list[LocalGaussianProcess],
177
+ n_observations: int,
178
+ reference_values: np.ndarray,
179
+ nuisance: float | None = None,
180
+ stochastic: bool = False
181
+ ) -> None:
182
+ self.lgps = lgps
183
+ self.error: float | None = None
184
+ self.n_observations = int(n_observations)
185
+ self.reference_values = np.asarray(reference_values, dtype=float).reshape(-1)
186
+ self.nuisance = nuisance
187
+ self.stochastic = stochastic
188
+
189
+ if self.reference_values.size != self.lgps[0].y_size:
190
+ raise ValueError(
191
+ "Reference observation size does not match surrogate output size."
192
+ )
193
+
194
+ @property
195
+ def size(self) -> int:
196
+ return len(self.lgps)
197
+
198
+ @property
199
+ def n_params(self) -> int:
200
+ return self.lgps[0].n_params
201
+
202
+ @property
203
+ def y_size(self) -> int:
204
+ return self.lgps[0].y_size
205
+
206
+ def predict(self, X: torch.Tensor) -> torch.Tensor:
207
+ """
208
+ Predict outputs by averaging predictions from all LGP models.
209
+
210
+ Parameters
211
+ ----------
212
+ X : torch.Tensor
213
+ Input tensor of shape (n_samples, n_features).
214
+
215
+ Returns
216
+ -------
217
+ torch.Tensor
218
+ Averaged predicted outputs of shape (n_samples, output_dim).
219
+ """
220
+
221
+ if self.size > 1 and self.stochastic:
222
+ # Select a random model and return its prediction
223
+ idx = torch.randint(self.size, (1,)).item()
224
+ return self.lgps[idx].predict(X)
225
+
226
+ # Otherwise, return the mean prediction across all models
227
+ predictions = torch.stack([lgp.predict(X) for lgp in self.lgps])
228
+ return predictions.mean(dim=0).squeeze(0)
229
+
230
+ def validate(self, X_test: torch.Tensor, y_test: torch.Tensor) -> float:
231
+ """
232
+ Validate the committee by computing the mean squared error.
233
+
234
+ Parameters
235
+ ----------
236
+ X_test : torch.Tensor
237
+ Test input tensor of shape (n_samples, n_features).
238
+ y_test : torch.Tensor
239
+ Test output tensor of shape (n_samples, output_dim).
240
+
241
+ Returns
242
+ -------
243
+ float
244
+ Mean squared error of the predictions.
245
+ """
246
+
247
+ y_pred = self.predict(X_test)
248
+ self.error = 100.0 * smape(y_test, y_pred)
249
+ return self.error
250
+
251
+ @classmethod
252
+ def load(cls, fn: PathLike) -> Self:
253
+ state = torch.load(fn, weights_only=False)
254
+ lgps = [LocalGaussianProcess(**lgp_state) for lgp_state in state["lgps"]]
255
+ committee = cls(
256
+ lgps=lgps,
257
+ n_observations=state["n_observations"],
258
+ reference_values=np.asarray(state["reference_values"], dtype=float),
259
+ nuisance=state["nuisance"],
260
+ stochastic=state["stochastic"],
261
+ )
262
+ committee.error = state["error"]
263
+ return committee
264
+
265
+ def write(self, fn_out: PathLike) -> None:
266
+
267
+ state = {
268
+ "n_observations": self.n_observations,
269
+ "reference_values": self.reference_values.tolist(),
270
+ "nuisance": self.nuisance,
271
+ "stochastic": self.stochastic,
272
+ "error": self.error,
273
+ "lgps": [lgp.state_dict() for lgp in self.lgps],
274
+ }
275
+
276
+ torch.save(state, fn_out)
277
+
278
+ def __repr__(self) -> str:
279
+ return (
280
+ f"{self.__class__.__name__}(\n"
281
+ f" committee_size={self.size},\n"
282
+ f" n_params={self.n_params},\n"
283
+ f" testset error={self.error},\n"
284
+ f")"
285
+ )
bff/bayes/kernels.py ADDED
@@ -0,0 +1,71 @@
1
+ from typing import Union
2
+
3
+ import torch
4
+
5
+ from .utils import check_tensor, with_manual_sqdist_flag
6
+
7
+
8
+ @with_manual_sqdist_flag
9
+ def gaussian_kernel(
10
+ x1: torch.Tensor, x2: torch.Tensor,
11
+ lengths: Union[float, torch.Tensor],
12
+ width: Union[float, torch.Tensor],
13
+ manual_sqdist: bool = False
14
+ ) -> torch.Tensor:
15
+ """Gaussian (Squared Exponential) kernel.
16
+
17
+ Parameters
18
+ ----------
19
+ x1 : torch.Tensor
20
+ First input tensor of shape (n_samples, n_features) or (n_features,).
21
+ x2 : torch.Tensor
22
+ Second input tensor of shape (n_samples, n_features) or (n_features,).
23
+ lengths : Union[float, torch.Tensor]
24
+ Length scale(s) for the kernel.
25
+ Can be a single float or a tensor of shape (n_features,).
26
+ width : Union[float, torch.Tensor]
27
+ Width scale(s) for the kernel.
28
+ Can be a single float or a tensor of shape (n_features,).
29
+ manual_sqdist : bool, optional
30
+ If True, computes the squared distance manually.
31
+ If False, uses `torch.cdist` for efficiency.
32
+ manual_sqdist is necessary for evaluation of second derivatives.
33
+
34
+ Returns
35
+ -------
36
+ torch.Tensor
37
+ Kernel matrix of shape (n_samples_x1, n_samples_x2).
38
+ If x1 and x2 are 2D tensors, the output will be of shape
39
+ (n_samples, n_samples_x1, n_samples_x2).
40
+ """
41
+
42
+ assert x1.device == x2.device, "x1 and x2 must be on the same device"
43
+ device = x1.device
44
+
45
+ if x1.dim() == 2:
46
+ x1 = x1[None, :, :].to(device)
47
+ if x2.dim() == 2:
48
+ x2 = x2[None, :, :].to(device)
49
+
50
+ lengths = check_tensor(lengths, device=device)
51
+ if lengths.dim() == 1:
52
+ lengths = lengths[None, :].to(device)
53
+
54
+ width = check_tensor(width, device=device)
55
+ if width.dim() == 1:
56
+ width = width[None, :].to(device)
57
+
58
+ x1_scaled = x1 / lengths
59
+ x2_scaled = x2 / lengths
60
+
61
+ if manual_sqdist:
62
+ x1_sq = (x1_scaled**2).sum(dim=-1, keepdim=True) # (B, N1, 1)
63
+ x2_sq = (x2_scaled**2).sum(dim=-1, keepdim=True).transpose(1, 2) # (B, 1, N2)
64
+ sqdist = x1_sq + x2_sq - 2 * (x1_scaled @ x2_scaled.transpose(1, 2))
65
+ sqdist = torch.clamp(sqdist, min=0.0)
66
+ else:
67
+ sqdist = torch.cdist(x1_scaled, x2_scaled, p=2).pow(2)
68
+
69
+ kernel = width.pow(2).mean() * torch.exp(-0.5 * sqdist)
70
+
71
+ return kernel.squeeze(0)