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/bayes/utils.py ADDED
@@ -0,0 +1,333 @@
1
+ from pathlib import Path
2
+ from typing import Callable, Sequence, Tuple, Union
3
+
4
+ import torch
5
+ from torch.autograd.functional import hessian
6
+
7
+ PathLike = Union[str, Path]
8
+
9
+
10
+ def smape(y_true: torch.Tensor, y_pred: torch.Tensor) -> float:
11
+ """Compute the symmetric mean absolute percentage error."""
12
+
13
+ device = y_true.device if isinstance(y_true, torch.Tensor) else 'cpu'
14
+
15
+ y_true = check_tensor(y_true, device=device)
16
+ y_pred = check_tensor(y_pred, device=device)
17
+
18
+ abs_diff = torch.sum(torch.abs(y_true - y_pred), dim=1)
19
+ y_true_abs = torch.sum(torch.abs(y_true), dim=1)
20
+ y_pred_abs = torch.sum(torch.abs(y_pred), dim=1)
21
+ norm = y_true_abs + y_pred_abs
22
+
23
+ return float(torch.mean(abs_diff / norm).item())
24
+
25
+ def initialize_walkers(
26
+ priors,
27
+ n_walkers: int,
28
+ constraint: Callable = None,
29
+ max_attempts: int | None = None,
30
+ ) -> torch.Tensor:
31
+
32
+ """
33
+ Initialize walkers for the MCMC sampler.
34
+
35
+ Parameters
36
+ ----------
37
+ priors : dict
38
+ Dict of prior distributions for each parameter.
39
+ n_walkers : int
40
+ Number of walkers to initialize.
41
+ specs : object, optional
42
+ Specifications object containing bounds and constraints.
43
+ If provided, walkers will be initialized within the bounds.
44
+
45
+ Returns
46
+ -------
47
+ torch.Tensor
48
+ A tensor of shape (n_walkers, n_params) containing the initial positions
49
+ of the walkers, sampled from the prior distributions.
50
+ """
51
+
52
+ if constraint is None:
53
+ means = torch.tensor([p.mean for p in priors], dtype=torch.float32)
54
+ stds = torch.tensor([p.scale for p in priors], dtype=torch.float32)
55
+ p0 = torch.normal(means.expand(n_walkers, -1), stds.expand(n_walkers, -1))
56
+ else:
57
+ n_params = constraint.n_params
58
+ n_dim = len(priors)
59
+ max_attempts = max_attempts or (1000 * n_walkers)
60
+
61
+ p0 = torch.empty((n_walkers, n_dim))
62
+ count = 0
63
+ attempts = 0
64
+ while count < n_walkers and attempts < max_attempts:
65
+ p0_trial = torch.tensor([p.sample().item() for p in priors])
66
+ if constraint(p0_trial[:n_params]):
67
+ p0[count] = p0_trial
68
+ count += 1
69
+ attempts += 1
70
+ if count < n_walkers:
71
+ raise RuntimeError(
72
+ "Failed to initialize constrained walkers from the priors. "
73
+ f"Accepted {count}/{n_walkers} samples after {attempts} attempts."
74
+ )
75
+ return p0
76
+
77
+
78
+ def check_tensor(
79
+ x: Union[torch.Tensor, float, int, list, tuple],
80
+ device: str,
81
+ dtype: torch.dtype = torch.float32
82
+ ) -> torch.Tensor:
83
+ """Convert input to a torch tensor on the specified device."""
84
+ if not isinstance(x, torch.Tensor):
85
+ return torch.as_tensor(x, device=device, dtype=dtype)
86
+ return x.to(device, dtype=dtype)
87
+
88
+
89
+ def check_device(device: str) -> None:
90
+ """Check if the specified device is available."""
91
+ if device.startswith("cuda"):
92
+ if not torch.cuda.is_available():
93
+ raise RuntimeError("CUDA is not available.")
94
+ elif device.startswith("mps"):
95
+ if not torch.mps.is_available():
96
+ raise RuntimeError("MPS is not available.")
97
+
98
+
99
+ @torch.no_grad()
100
+ def nearest_positive_definite(A: torch.Tensor) -> torch.Tensor:
101
+ """Find the nearest positive definite matrix to A."""
102
+ # Symmetrize
103
+ A_sym = (A + A.T) / 2
104
+
105
+ # Check if already PD
106
+ if torch.all(torch.linalg.eigvalsh(A_sym) > 0):
107
+ return A_sym
108
+
109
+ # Eigen-decomposition
110
+ eigenvalues, eigenvectors = torch.linalg.eigh(A_sym)
111
+
112
+ # Shift eigenvalues minimally
113
+ min_eig = eigenvalues.min()
114
+ eps = 1e-8 # small positive shift
115
+
116
+ if min_eig < eps:
117
+ eigenvalues = eigenvalues - min_eig + eps
118
+
119
+ A_pd = (eigenvectors @ torch.diag(eigenvalues)) @ eigenvectors.T
120
+ return A_pd
121
+
122
+
123
+ def train_test_split(
124
+ X: torch.Tensor, y: torch.Tensor, test_fraction: float = 0.2
125
+ ) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]:
126
+ """Split the dataset into training and testing sets."""
127
+
128
+ n = len(X)
129
+ if n != len(y):
130
+ raise ValueError("X and y must have the same length.")
131
+ if not (0 < test_fraction < 1):
132
+ raise ValueError("test_fraction must be between 0 and 1.")
133
+ if n < 2:
134
+ raise ValueError("X and y must have at least 2 samples.")
135
+
136
+ indices = torch.randperm(n)
137
+ test_size = int(n * test_fraction)
138
+ idx_train = indices[test_size:]
139
+ idx_test = indices[:test_size]
140
+ return X[idx_train], X[idx_test], y[idx_train], y[idx_test]
141
+
142
+
143
+ # Global toggle for manual squared distance
144
+ _MANUAL_MODE = False
145
+
146
+
147
+ class enable_manual_dist:
148
+ """Enable Hessian-safe manual pairwise distances within a context."""
149
+ def __enter__(self) -> None:
150
+ global _MANUAL_MODE
151
+ self._prev = _MANUAL_MODE
152
+ _MANUAL_MODE = True
153
+
154
+ def __exit__(self, *args) -> None:
155
+ global _MANUAL_MODE
156
+ _MANUAL_MODE = self._prev
157
+
158
+
159
+ def with_manual_sqdist_flag(fn: Callable) -> Callable:
160
+ """Inject the global manual-distance flag into kernel call sites."""
161
+ def wrapper(*args, manual_sqdist=False, **kwargs):
162
+ return fn(*args, manual_sqdist=manual_sqdist or _MANUAL_MODE, **kwargs)
163
+ return wrapper
164
+
165
+
166
+ def find_max_stable_lr(
167
+ fn: Callable,
168
+ p0: torch.Tensor,
169
+ learning_rates: (
170
+ Sequence[Union[float, torch.Tensor]] | float | torch.Tensor | None
171
+ ) = None,
172
+ max_iter: int = 100,
173
+ param_bounds: Tuple[float, float] = (-7, 7),
174
+ ) -> Union[float, None]:
175
+ """Find the largest stable learning rate for gradient-based optimization.
176
+
177
+ Parameters
178
+ ----------
179
+ fn : Callable
180
+ Objective function returning a scalar tensor.
181
+ p0 : torch.Tensor
182
+ Initial parameter vector.
183
+ learning_rates : iterable of float, optional
184
+ Learning rates to test. Defaults to log-spaced values.
185
+ max_iter : int
186
+ Number of steps to test for each learning rate.
187
+ param_bounds : tuple of float
188
+ Bounds beyond which parameters are considered unstable.
189
+
190
+ Returns
191
+ -------
192
+ float or None
193
+ The largest stable learning rate found, or None if none were stable.
194
+ """
195
+ if learning_rates is None:
196
+ learning_rates = 10 ** torch.linspace(-1, -6, 6)
197
+ elif isinstance(learning_rates, torch.Tensor) and learning_rates.ndim == 0:
198
+ learning_rates = [float(learning_rates.item())]
199
+ elif isinstance(learning_rates, (int, float)):
200
+ learning_rates = [float(learning_rates)]
201
+ else:
202
+ learning_rates = list(learning_rates)
203
+ lower, upper = param_bounds
204
+ for lr in learning_rates:
205
+ x = p0.clone().detach().requires_grad_(True)
206
+ opt = torch.optim.SGD([x], lr=lr)
207
+
208
+ for i in range(max_iter):
209
+ if torch.any(x < lower) or torch.any(x > upper):
210
+ break
211
+ opt.zero_grad()
212
+ loss = -fn(x)
213
+ loss.backward()
214
+ opt.step()
215
+ else:
216
+ # Only gets executed if inner loop did not break (i.e., stable)
217
+ return lr
218
+
219
+
220
+ def find_map(
221
+ fn: Callable,
222
+ x0: torch.Tensor,
223
+ lr: Union[float, torch.Tensor] = None,
224
+ max_iter: int = 10000,
225
+ tol_grad: float = 1e-2,
226
+ device: str = 'cpu',
227
+ logger: Callable = None
228
+ ) -> torch.Tensor:
229
+
230
+ """Find the maximum a posteriori (MAP) estimate
231
+ using gradient-based optimization.
232
+
233
+ Parameters
234
+ ----------
235
+ fn : Callable
236
+ Objective function to maximize (log-posterior).
237
+ x0 : torch.Tensor
238
+ Initial parameter vector.
239
+ lr : float or torch.Tensor, optional
240
+ Learning rate for optimization. If None, it will be determined by a search.
241
+ max_iter : int
242
+ Maximum number of optimization iterations.
243
+ tol_grad : float
244
+ Gradient norm threshold for convergence.
245
+ device : str
246
+ Device to perform computations on.
247
+ logger : Callable, optional
248
+ Logger for progress updates. Should accept a string and a level argument.
249
+
250
+ Returns
251
+ -------
252
+ torch.Tensor
253
+ The MAP estimate found by optimization.
254
+ """
255
+
256
+ if logger is not None:
257
+ logger.info("learning rate search: in progress...", level=2, overwrite=True)
258
+
259
+ lr_opt = find_max_stable_lr(fn, x0, learning_rates=lr)
260
+ if lr_opt is not None:
261
+ lr_opt = 0.5 * lr_opt
262
+ if logger is not None:
263
+ logger.info(f"learning rate search: Done. | lr = {lr_opt:.1e}", level=2)
264
+ else:
265
+ raise ValueError("No stable learning rate found.")
266
+
267
+ x0 = x0.clone().detach().to(device).requires_grad_(True)
268
+ optimizer = torch.optim.SGD([x0], lr=lr_opt)
269
+
270
+ for i in range(max_iter):
271
+ optimizer.zero_grad()
272
+ loss = -fn(x0)
273
+ loss.backward()
274
+ grad_norm = x0.grad.norm().item()
275
+ if i % 100 == 0 and logger is not None:
276
+ logger.info(
277
+ (
278
+ f"MAP search: it. {i}/{max_iter} | "
279
+ f"loss: {loss.item():.3f} | grad: {grad_norm:.3f}/{tol_grad}"
280
+ ),
281
+ level=2,
282
+ overwrite=True
283
+ )
284
+ optimizer.step()
285
+ if grad_norm < tol_grad:
286
+ if logger is not None:
287
+ logger.info("MAP search: Done.", level=2)
288
+ break
289
+
290
+ else:
291
+ if logger is not None:
292
+ logger.info(
293
+ "MAP search: Fail. | Max iterations reached without convergence.",
294
+ level=2,
295
+ )
296
+
297
+ return x0.detach()
298
+
299
+
300
+ def laplace_approximation(
301
+ fn: Callable,
302
+ map_theta: torch.Tensor,
303
+ device: str = 'cpu'
304
+ ) -> torch.Tensor:
305
+ """
306
+ Perform Laplace approximation around MAP estimate.
307
+
308
+ Parameters
309
+ ----------
310
+ fn : Callable
311
+ Objective function to approximate (log-posterior).
312
+ map_theta : torch.Tensor
313
+ MAP estimate around which to perform the approximation.
314
+ device : str
315
+ Device to perform computations on.
316
+
317
+ Returns
318
+ -------
319
+ cov : torch.Tensor
320
+ The approximate posterior covariance (inverse Hessian).
321
+ """
322
+
323
+ map_theta = check_tensor(map_theta, device=device)
324
+ with enable_manual_dist():
325
+ H = -hessian(lambda th: fn(th).sum(), map_theta)
326
+
327
+ reg_eye = 1e-6 * torch.eye(H.shape[0], device=H.device)
328
+ cov = torch.linalg.inv(H + reg_eye)
329
+
330
+ # symmerize covariance matrix
331
+ cov = (cov + cov.T) / 2
332
+
333
+ return cov
bff/cli.py ADDED
@@ -0,0 +1,254 @@
1
+ """Command-line entry points for Bayesic Force Fields."""
2
+
3
+ import os
4
+ from pathlib import Path
5
+ from typing import Callable
6
+
7
+ import typer
8
+
9
+ app = typer.Typer(
10
+ help="BayesicForceFields: Bayesian optimization of molecular force fields.",
11
+ no_args_is_help=True,
12
+ add_completion=False,
13
+ )
14
+
15
+ WorkflowMain = Callable[[Path], None]
16
+ WORKFLOW_COMMANDS = (
17
+ "prepare",
18
+ "simulate",
19
+ "qoi",
20
+ "train",
21
+ "learn",
22
+ "validate",
23
+ )
24
+
25
+ _COMPLETION_ACTIVATE = f"""# Managed automatically by Bayesic Force Fields.
26
+ if [ -n "${{BASH_VERSION-}}" ]; then
27
+ _bff_completion() {{
28
+ local cur prev words cword
29
+ if declare -F _init_completion >/dev/null 2>&1; then
30
+ _init_completion || return
31
+ else
32
+ COMPREPLY=()
33
+ cur="${{COMP_WORDS[COMP_CWORD]}}"
34
+ prev="${{COMP_WORDS[COMP_CWORD-1]}}"
35
+ words=("${{COMP_WORDS[@]}}")
36
+ cword=$COMP_CWORD
37
+ fi
38
+
39
+ local commands="{" ".join(WORKFLOW_COMMANDS)}"
40
+ if [[ $COMP_CWORD -eq 1 ]]; then
41
+ COMPREPLY=($(compgen -W "$commands" -- "$cur"))
42
+ return 0
43
+ fi
44
+
45
+ case "${{COMP_WORDS[1]}}" in
46
+ prepare|simulate|qoi|train|learn|validate)
47
+ COMPREPLY=($(compgen -f -- "$cur"))
48
+ ;;
49
+ *)
50
+ COMPREPLY=()
51
+ ;;
52
+ esac
53
+ }}
54
+
55
+ complete -o nosort -F _bff_completion bff
56
+ elif [ -n "${{ZSH_VERSION-}}" ]; then
57
+ autoload -Uz compinit 2>/dev/null || true
58
+ compinit -i 2>/dev/null || true
59
+
60
+ _bff_completion() {{
61
+ local -a commands
62
+ commands=({" ".join(WORKFLOW_COMMANDS)})
63
+
64
+ if (( CURRENT == 2 )); then
65
+ compadd -- "${{commands[@]}}"
66
+ return 0
67
+ fi
68
+
69
+ case "${{words[2]}}" in
70
+ prepare|simulate|qoi|train|learn|validate)
71
+ _files
72
+ ;;
73
+ esac
74
+ }}
75
+
76
+ compdef _bff_completion bff
77
+ fi
78
+ """
79
+
80
+ _COMPLETION_DEACTIVATE = """# Managed automatically by Bayesic Force Fields.
81
+ if [ -n "${BASH_VERSION-}" ]; then
82
+ complete -r bff 2>/dev/null || true
83
+ elif [ -n "${ZSH_VERSION-}" ]; then
84
+ unfunction _bff_completion 2>/dev/null || true
85
+ compdef -d bff 2>/dev/null || true
86
+ fi
87
+ """
88
+
89
+
90
+ def _conda_completion_hook_paths() -> tuple[Path, Path] | None:
91
+ conda_prefix = os.environ.get("CONDA_PREFIX")
92
+ if not conda_prefix:
93
+ return None
94
+
95
+ conda_dir = Path(conda_prefix) / "etc" / "conda"
96
+ return (
97
+ conda_dir / "activate.d" / "bff-completion.sh",
98
+ conda_dir / "deactivate.d" / "bff-completion.sh",
99
+ )
100
+
101
+
102
+ def _write_if_changed(path: Path, content: str) -> None:
103
+ if path.exists() and path.read_text() == content:
104
+ return
105
+ path.parent.mkdir(parents=True, exist_ok=True)
106
+ path.write_text(content)
107
+
108
+
109
+ def _ensure_completion_hooks() -> None:
110
+ hook_paths = _conda_completion_hook_paths()
111
+ if hook_paths is None:
112
+ return
113
+
114
+ activate_hook, deactivate_hook = hook_paths
115
+ try:
116
+ _write_if_changed(activate_hook, _COMPLETION_ACTIVATE)
117
+ _write_if_changed(deactivate_hook, _COMPLETION_DEACTIVATE)
118
+ except OSError:
119
+ return
120
+
121
+
122
+ def run_workflow(
123
+ fn_config: Path,
124
+ workflow_main: WorkflowMain,
125
+ workflow_name: str,
126
+ ) -> None:
127
+ """
128
+ Run a workflow main function with a config path.
129
+ """
130
+ try:
131
+ workflow_main(fn_config)
132
+ except FileNotFoundError as exc:
133
+ raise typer.BadParameter(str(exc), param_hint="fn_config") from exc
134
+ except ValueError as exc:
135
+ raise typer.BadParameter(str(exc), param_hint=workflow_name) from exc
136
+ except RuntimeError as exc:
137
+ typer.echo(str(exc), err=True)
138
+ raise typer.Exit(code=1) from exc
139
+
140
+
141
+ def config_argument() -> Path:
142
+ return typer.Argument(
143
+ ...,
144
+ exists=True,
145
+ dir_okay=False,
146
+ help="Path to the configuration file.",
147
+ )
148
+
149
+
150
+ @app.callback()
151
+ def init_cli() -> None:
152
+ """
153
+ Initialize the CLI environment.
154
+ """
155
+ _ensure_completion_hooks()
156
+
157
+
158
+ @app.command()
159
+ def version() -> None:
160
+ """
161
+ Print package version.
162
+ """
163
+ from importlib import metadata
164
+
165
+ try:
166
+ version_str = metadata.version("bfflearn")
167
+ except metadata.PackageNotFoundError:
168
+ from . import __version__
169
+
170
+ version_str = __version__
171
+
172
+ typer.echo(f"BayesicForceFields version: {version_str}")
173
+
174
+
175
+ @app.command()
176
+ def prepare(fn_config: Path = config_argument()) -> None:
177
+ """
178
+ Prepare equilibration, training, and reference assets.
179
+ """
180
+ from bff.workflows.prepare import main as prepare_main
181
+
182
+ run_workflow(fn_config, prepare_main, "prepare")
183
+
184
+
185
+ @app.command(name="simulate")
186
+ def simulate(fn_config: Path = config_argument()) -> None:
187
+ """
188
+ Run a sampled molecular-dynamics campaign for training-set generation.
189
+ """
190
+ from bff.workflows.simulate import main as simulate_main
191
+
192
+ run_workflow(fn_config, simulate_main, "simulate")
193
+
194
+
195
+ @app.command()
196
+ def validate(fn_config: Path = config_argument()) -> None:
197
+ """
198
+ Run a validation campaign for provided parameter samples.
199
+ """
200
+ from bff.workflows.validate import main as validate_main
201
+
202
+ run_workflow(fn_config, validate_main, "validate")
203
+
204
+
205
+ @app.command(hidden=True)
206
+ def md(fn_config: Path = config_argument()) -> None:
207
+ """
208
+ Run molecular dynamics from a configuration file.
209
+ """
210
+ from bff.workflows.md import main as md_main
211
+
212
+ run_workflow(fn_config, md_main, "md")
213
+
214
+
215
+ @app.command(name="qoi")
216
+ def qoi(fn_config: Path = config_argument()) -> None:
217
+ """
218
+ Analyze quantities of interest from simulation data.
219
+ """
220
+ from bff.workflows.qoi import main as qoi_main
221
+
222
+ run_workflow(fn_config, qoi_main, "qoi")
223
+
224
+
225
+ @app.command(name="analyze", hidden=True)
226
+ def analyze_alias(fn_config: Path = config_argument()) -> None:
227
+ """
228
+ Hidden compatibility alias for the QoI workflow.
229
+ """
230
+ qoi(fn_config)
231
+
232
+
233
+ @app.command()
234
+ def train(fn_config: Path = config_argument()) -> None:
235
+ """
236
+ Train surrogate models from analyzed QoI datasets.
237
+ """
238
+ from bff.workflows.train import main as train_main
239
+
240
+ run_workflow(fn_config, train_main, "train")
241
+
242
+
243
+ @app.command()
244
+ def learn(fn_config: Path = config_argument()) -> None:
245
+ """
246
+ Run posterior learning from trained surrogate models.
247
+ """
248
+ from bff.workflows.learn import main as learn_main
249
+
250
+ run_workflow(fn_config, learn_main, "learn")
251
+
252
+
253
+ if __name__ == "__main__":
254
+ app()
bff/data.py ADDED
@@ -0,0 +1,93 @@
1
+ import numpy as np
2
+
3
+ WATER_4SITE = np.array(
4
+ [[0.02909557, 0.03692916, -0.04588176],
5
+ [0.43909732, -0.54306981, 0.60411833],
6
+ [-0.90090283, -0.04307076, 0.12411808],
7
+ [-0.0309039, -0.04307076, 0.05411814]]
8
+ )
9
+
10
+ WATER_3SITE = np.array(
11
+ [[0.02909557, 0.03692916, -0.04588176],
12
+ [0.43909732, -0.54306981, 0.60411833],
13
+ [-0.90090283, -0.04307076, 0.12411808]]
14
+ )
15
+
16
+ IONS = ['li', 'na', 'k', 'ca', 'cal', 'mg', 'f', 'cl', 'br', 'i']
17
+ CATIONS = {'li', 'na', 'k', 'rb', 'ca', 'mg'}
18
+ ANIONS = {'f', 'cl', 'br', 'i'}
19
+
20
+ WATERS = ['SOL', 'water', 'HOH']
21
+
22
+ CP2K_KIND_DEFAULTS = {
23
+ 'h': {'basis_set': 'TZV2P-GTH-q1', 'potential': 'GTH-PBE-q1'},
24
+ 'o': {'basis_set': 'TZV2P-GTH-q6', 'potential': 'GTH-PBE-q6'},
25
+ 'c': {'basis_set': 'TZV2P-GTH-q4', 'potential': 'GTH-PBE-q4'},
26
+ 'p': {'basis_set': 'TZV2P-GTH-q5', 'potential': 'GTH-PBE-q5'},
27
+ 'n': {'basis_set': 'TZV2P-GTH-q5', 'potential': 'GTH-PBE-q5'},
28
+ 'na': {'basis_set': 'TZV2P-GTH-q9', 'potential': 'GTH-PBE-q9'},
29
+ 'ca': {'basis_set': 'TZV2P-MOLOPT-PBE-GTH-q10', 'potential': 'GTH-PBE-q10'},
30
+ 'cl': {'basis_set': 'TZV2P-GTH-q7', 'potential': 'GTH-PBE-q7'},
31
+ 's': {'basis_set': 'TZV2P-GTH-q6', 'potential': 'GTH-PBE-q6'},
32
+ }
33
+
34
+ CP2K_INPUT_TEMPLATE = {
35
+ 'global': {
36
+ 'project_name': 'project',
37
+ 'print_level': 'LOW',
38
+ 'run_type': 'MD',
39
+ 'walltime': '03:50:00'
40
+ },
41
+ 'motion': {
42
+ 'md': {'temperature': 300.0, 'timestep': '[fs] 0.5', 'steps': 500},
43
+ 'print': {
44
+ 'forces': {
45
+ 'format': 'DCD',
46
+ 'each': {'md': 1}
47
+ },
48
+ 'trajectory': {
49
+ 'format': 'DCD',
50
+ 'each': {'md': 1}
51
+ },
52
+ 'restart': {
53
+ 'backup_copies': 1,
54
+ 'each': {'md': 1}
55
+ }
56
+ }
57
+ },
58
+ 'force_eval': {
59
+ 'method': 'Quickstep',
60
+ 'subsys': {},
61
+ 'dft': {
62
+ 'basis_set_file_name': ['GTH_BASIS_SETS', 'BASIS_MOLOPT_UZH'],
63
+ 'potential_file_name': 'GTH_POTENTIALS',
64
+ 'scf': {
65
+ 'max_scf': 20, 'eps_scf': 5e-07,
66
+ 'outer_scf': {'max_scf': 20, 'eps_scf': 5e-07},
67
+ 'ot': {
68
+ 'preconditioner': 'FULL_ALL',
69
+ 'energy_gap': -1.0,
70
+ 'minimizer': 'DIIS'}
71
+ },
72
+ 'mgrid': {'cutoff': '[Ry] 400.0'},
73
+ 'qs': {
74
+ 'method': 'GPW',
75
+ 'eps_default': 1e-12,
76
+ 'extrapolation_order': 4,
77
+ 'extrapolation': 'ASPC'
78
+ },
79
+ 'xc': {
80
+ 'xc_functional': {'pbe': {'parametrization': 'REVPBE'}},
81
+ 'xc_grid': {'xc_deriv': 'NN50_SMOOTH'},
82
+ 'vdw_potential': {
83
+ 'potential_type': 'PAIR_POTENTIAL',
84
+ 'pair_potential': {
85
+ 'type': 'DFTD3', 'r_cutoff': '[angstrom] 16.0',
86
+ 'reference_functional': 'revPBE',
87
+ 'parameter_file_name': 'dftd3.dat'
88
+ }
89
+ }
90
+ }
91
+ }
92
+ }
93
+ }
bff/domain/__init__.py ADDED
@@ -0,0 +1,12 @@
1
+ from .specs import Bounds, ChargeConstraint, RandomParamsGenerator, Specs
2
+ from .trainset import SimulationSystem, TrainSetInfo, TrajectorySet
3
+
4
+ __all__ = [
5
+ "Bounds",
6
+ "ChargeConstraint",
7
+ "RandomParamsGenerator",
8
+ "SimulationSystem",
9
+ "Specs",
10
+ "TrainSetInfo",
11
+ "TrajectorySet",
12
+ ]