openmm-opes 0.1.0__tar.gz
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.
- openmm_opes-0.1.0/LICENSE +21 -0
- openmm_opes-0.1.0/PKG-INFO +64 -0
- openmm_opes-0.1.0/README.md +42 -0
- openmm_opes-0.1.0/pyproject.toml +91 -0
- openmm_opes-0.1.0/setup.cfg +4 -0
- openmm_opes-0.1.0/src/openmm_opes/__init__.py +19 -0
- openmm_opes-0.1.0/src/openmm_opes/io.py +102 -0
- openmm_opes-0.1.0/src/openmm_opes/kde.py +450 -0
- openmm_opes-0.1.0/src/openmm_opes/opes.py +465 -0
- openmm_opes-0.1.0/src/openmm_opes.egg-info/PKG-INFO +64 -0
- openmm_opes-0.1.0/src/openmm_opes.egg-info/SOURCES.txt +20 -0
- openmm_opes-0.1.0/src/openmm_opes.egg-info/dependency_links.txt +1 -0
- openmm_opes-0.1.0/src/openmm_opes.egg-info/requires.txt +13 -0
- openmm_opes-0.1.0/src/openmm_opes.egg-info/top_level.txt +1 -0
- openmm_opes-0.1.0/tests/test_cvspace.py +92 -0
- openmm_opes-0.1.0/tests/test_integration.py +206 -0
- openmm_opes-0.1.0/tests/test_io.py +91 -0
- openmm_opes-0.1.0/tests/test_kde.py +202 -0
- openmm_opes-0.1.0/tests/test_kernel.py +110 -0
- openmm_opes-0.1.0/tests/test_opes.py +330 -0
- openmm_opes-0.1.0/tests/test_packaging.py +27 -0
- openmm_opes-0.1.0/tests/test_parity.py +56 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Charlles Abreu
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: openmm-opes
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: OpenMM implementation of On-the-fly Probability Enhanced Sampling (OPES)
|
|
5
|
+
Author: Charlles Abreu
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Requires-Python: >=3.11
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Dist: numpy>=1.24
|
|
11
|
+
Requires-Dist: scipy>=1.10
|
|
12
|
+
Requires-Dist: openmm>=8.1
|
|
13
|
+
Provides-Extra: dev
|
|
14
|
+
Requires-Dist: pytest>=7.4; extra == "dev"
|
|
15
|
+
Requires-Dist: ruff>=0.16; extra == "dev"
|
|
16
|
+
Requires-Dist: ty==0.0.72; extra == "dev"
|
|
17
|
+
Provides-Extra: docs
|
|
18
|
+
Requires-Dist: mkdocs>=1.6; extra == "docs"
|
|
19
|
+
Requires-Dist: mkdocs-material>=9.5; extra == "docs"
|
|
20
|
+
Requires-Dist: mkdocstrings[python]>=0.24; extra == "docs"
|
|
21
|
+
Dynamic: license-file
|
|
22
|
+
|
|
23
|
+
# openmm-opes
|
|
24
|
+
|
|
25
|
+
[](https://github.com/craabreu/openmm-opes/actions/workflows/ci.yml)
|
|
26
|
+
|
|
27
|
+
An [OpenMM](https://openmm.org) implementation of On-the-fly Probability
|
|
28
|
+
Enhanced Sampling (OPES) and OPES-explore.
|
|
29
|
+
|
|
30
|
+
## Installation
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pip install openmm-opes
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Usage
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
from openmm_opes import OPES
|
|
40
|
+
|
|
41
|
+
sampler = OPES(system, variables, temperature, barrier, frequency=500,
|
|
42
|
+
varianceFrequency=50)
|
|
43
|
+
sampler.step(simulation, 1_000_000)
|
|
44
|
+
fes = sampler.getFreeEnergy()
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Full documentation: <https://craabreu.github.io/openmm-opes>
|
|
48
|
+
|
|
49
|
+
## Origin
|
|
50
|
+
|
|
51
|
+
This library packages and hardens the prototype implementation developed in
|
|
52
|
+
[craabreu/opes-simulations](https://github.com/craabreu/opes-simulations),
|
|
53
|
+
porting its `OPES`/`OnlineKDE` classes into a tested, documented package.
|
|
54
|
+
|
|
55
|
+
## References
|
|
56
|
+
|
|
57
|
+
- Invernizzi & Parrinello, J. Phys. Chem. Lett. 2020,
|
|
58
|
+
[doi:10.1021/acs.jpclett.0c00497](https://doi.org/10.1021/acs.jpclett.0c00497)
|
|
59
|
+
- Invernizzi & Parrinello, J. Chem. Theory Comput. 2022,
|
|
60
|
+
[doi:10.1021/acs.jctc.2c00152](https://doi.org/10.1021/acs.jctc.2c00152)
|
|
61
|
+
|
|
62
|
+
## License
|
|
63
|
+
|
|
64
|
+
MIT
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# openmm-opes
|
|
2
|
+
|
|
3
|
+
[](https://github.com/craabreu/openmm-opes/actions/workflows/ci.yml)
|
|
4
|
+
|
|
5
|
+
An [OpenMM](https://openmm.org) implementation of On-the-fly Probability
|
|
6
|
+
Enhanced Sampling (OPES) and OPES-explore.
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
pip install openmm-opes
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Usage
|
|
15
|
+
|
|
16
|
+
```python
|
|
17
|
+
from openmm_opes import OPES
|
|
18
|
+
|
|
19
|
+
sampler = OPES(system, variables, temperature, barrier, frequency=500,
|
|
20
|
+
varianceFrequency=50)
|
|
21
|
+
sampler.step(simulation, 1_000_000)
|
|
22
|
+
fes = sampler.getFreeEnergy()
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Full documentation: <https://craabreu.github.io/openmm-opes>
|
|
26
|
+
|
|
27
|
+
## Origin
|
|
28
|
+
|
|
29
|
+
This library packages and hardens the prototype implementation developed in
|
|
30
|
+
[craabreu/opes-simulations](https://github.com/craabreu/opes-simulations),
|
|
31
|
+
porting its `OPES`/`OnlineKDE` classes into a tested, documented package.
|
|
32
|
+
|
|
33
|
+
## References
|
|
34
|
+
|
|
35
|
+
- Invernizzi & Parrinello, J. Phys. Chem. Lett. 2020,
|
|
36
|
+
[doi:10.1021/acs.jpclett.0c00497](https://doi.org/10.1021/acs.jpclett.0c00497)
|
|
37
|
+
- Invernizzi & Parrinello, J. Chem. Theory Comput. 2022,
|
|
38
|
+
[doi:10.1021/acs.jctc.2c00152](https://doi.org/10.1021/acs.jctc.2c00152)
|
|
39
|
+
|
|
40
|
+
## License
|
|
41
|
+
|
|
42
|
+
MIT
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "openmm-opes"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "OpenMM implementation of On-the-fly Probability Enhanced Sampling (OPES)"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
license-files = ["LICENSE"]
|
|
8
|
+
authors = [{ name = "Charlles Abreu" }]
|
|
9
|
+
requires-python = ">=3.11"
|
|
10
|
+
dependencies = ["numpy>=1.24", "scipy>=1.10", "openmm>=8.1"]
|
|
11
|
+
|
|
12
|
+
[project.optional-dependencies]
|
|
13
|
+
dev = ["pytest>=7.4", "ruff>=0.16", "ty==0.0.72"]
|
|
14
|
+
# mkdocs >= 1.6 is required for the exclude_docs setting used by mkdocs.yml
|
|
15
|
+
docs = ["mkdocs>=1.6", "mkdocs-material>=9.5", "mkdocstrings[python]>=0.24"]
|
|
16
|
+
|
|
17
|
+
[build-system]
|
|
18
|
+
requires = ["setuptools>=68"]
|
|
19
|
+
build-backend = "setuptools.build_meta"
|
|
20
|
+
|
|
21
|
+
[tool.setuptools.packages.find]
|
|
22
|
+
where = ["src"]
|
|
23
|
+
|
|
24
|
+
[tool.pytest.ini_options]
|
|
25
|
+
testpaths = ["tests"]
|
|
26
|
+
markers = ["slow: end-to-end simulations that take minutes"]
|
|
27
|
+
|
|
28
|
+
[tool.ruff]
|
|
29
|
+
line-length = 88
|
|
30
|
+
target-version = "py311"
|
|
31
|
+
include = ["src/**/*.py", "tests/**/*.py"]
|
|
32
|
+
|
|
33
|
+
[tool.ruff.lint]
|
|
34
|
+
# Deliberately no "N" rules: the public API is camelCase to match OpenMM.
|
|
35
|
+
select = ["E", "F", "I", "UP", "B", "C4", "RUF"]
|
|
36
|
+
|
|
37
|
+
[tool.ty.analysis]
|
|
38
|
+
# OpenMM ships no type stubs, so the lint job never installs it: doing so
|
|
39
|
+
# would still leave every attribute access untyped, for no benefit.
|
|
40
|
+
# online_kde is the ORIGINAL implementation, sys.path-shimmed at runtime from
|
|
41
|
+
# a sibling repo (tests/data/make_reference.py) rather than installed, so it
|
|
42
|
+
# is never resolvable statically.
|
|
43
|
+
allowed-unresolved-imports = ["openmm", "openmm.**", "online_kde"]
|
|
44
|
+
|
|
45
|
+
[[tool.ty.overrides]]
|
|
46
|
+
# numpy-stubs gap, confirmed a false positive: np.logaddexp.reduce(array) is
|
|
47
|
+
# ordinary numpy usage that runs correctly (see the passing test suite), but
|
|
48
|
+
# the stub's reduce() overloads for binary ufuncs cannot be satisfied without
|
|
49
|
+
# an explicit out= argument, so ty reports every plain reduce() call here as
|
|
50
|
+
# having no matching overload.
|
|
51
|
+
include = ["src/openmm_opes/kde.py", "tests/test_kde.py"]
|
|
52
|
+
|
|
53
|
+
[tool.ty.overrides.rules]
|
|
54
|
+
no-matching-overload = "ignore"
|
|
55
|
+
|
|
56
|
+
[[tool.ty.overrides]]
|
|
57
|
+
# numpy-stubs gap, confirmed a false positive: np.savez(path, **arrays) passes
|
|
58
|
+
# arbitrary ndarrays through **kwds, but the stub resolves that against
|
|
59
|
+
# allow_pickle: bool instead of **kwds: ArrayLike, reporting every array
|
|
60
|
+
# argument as "expected bool". Same class of gap sieve's own pyproject.toml
|
|
61
|
+
# documents for the identical call shape.
|
|
62
|
+
include = ["tests/data/make_reference.py"]
|
|
63
|
+
|
|
64
|
+
[tool.ty.overrides.rules]
|
|
65
|
+
invalid-argument-type = "ignore"
|
|
66
|
+
|
|
67
|
+
[[tool.ty.overrides]]
|
|
68
|
+
# openmm.unit stub gap, confirmed a false positive: unit.kelvin and
|
|
69
|
+
# unit.kilojoules_per_mole resolve fine, but unit.picosecond/picoseconds do
|
|
70
|
+
# not, even though `dir(unit)` and runtime multiplication both confirm they
|
|
71
|
+
# exist -- these unit constants are evidently built at import time in a way
|
|
72
|
+
# ty's static analysis can't trace, unlike the ones assigned as literal
|
|
73
|
+
# module-level names.
|
|
74
|
+
include = ["tests/test_opes.py", "tests/test_integration.py"]
|
|
75
|
+
|
|
76
|
+
[tool.ty.overrides.rules]
|
|
77
|
+
unresolved-attribute = "ignore"
|
|
78
|
+
unsupported-operator = "ignore"
|
|
79
|
+
|
|
80
|
+
[[tool.ty.overrides]]
|
|
81
|
+
# numpy-stubs gap, confirmed a false positive: ndarray.min() with no arguments
|
|
82
|
+
# on an ordinary float64 array (e.g. analytic[mask].min() where analytic is
|
|
83
|
+
# built as BARRIER_HEIGHT * (1 - grid**2) ** 2) runs correctly and returns a
|
|
84
|
+
# float64 -- ty's own diagnostic even reports the array's dtype correctly as
|
|
85
|
+
# float64 -- yet resolution picks the unrelated NDArray[object_] overload
|
|
86
|
+
# instead of a float64-compatible one. Reproduced in isolation outside this
|
|
87
|
+
# file, so this is a stub resolution gap, not anything about this code.
|
|
88
|
+
include = ["tests/test_integration.py"]
|
|
89
|
+
|
|
90
|
+
[tool.ty.overrides.rules]
|
|
91
|
+
invalid-argument-type = "ignore"
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""OpenMM implementation of On-the-fly Probability Enhanced Sampling (OPES)."""
|
|
2
|
+
|
|
3
|
+
__version__ = "0.1.0"
|
|
4
|
+
|
|
5
|
+
__all__ = ["OPES", "__version__"]
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def __getattr__(name):
|
|
9
|
+
"""Import OPES lazily (PEP 562).
|
|
10
|
+
|
|
11
|
+
An eager ``from .opes import OPES`` would also run on
|
|
12
|
+
``import openmm_opes.kde``, dragging openmm in and breaking the guarantee
|
|
13
|
+
that kde and io stay OpenMM-free. Task 1's test enforces that.
|
|
14
|
+
"""
|
|
15
|
+
if name == "OPES":
|
|
16
|
+
from .opes import OPES
|
|
17
|
+
|
|
18
|
+
return OPES
|
|
19
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""Multi-walker bias sharing through a shared directory.
|
|
2
|
+
|
|
3
|
+
Pure NumPy. This module must not import openmm, and deliberately knows
|
|
4
|
+
nothing about kernels: it moves flat dicts of arrays so it stays testable
|
|
5
|
+
without a simulation.
|
|
6
|
+
|
|
7
|
+
The format is ``np.savez``, not pickle. A walker reads files written by other
|
|
8
|
+
processes out of a shared directory, and ``pickle.load`` on data from another
|
|
9
|
+
process would be a remote-code-execution primitive. OpenMM's own Metadynamics
|
|
10
|
+
writes .npy for the same reason.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import os
|
|
16
|
+
import re
|
|
17
|
+
import warnings
|
|
18
|
+
|
|
19
|
+
import numpy as np
|
|
20
|
+
|
|
21
|
+
FILENAME_PATTERN = re.compile(r"kde_(\d+)_(\d+)\.npz")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class _LoadedBias:
|
|
25
|
+
"""A peer's most recently seen state. Mirrors Metadynamics' own namedtuple."""
|
|
26
|
+
|
|
27
|
+
__slots__ = ("index", "state", "walkerId")
|
|
28
|
+
|
|
29
|
+
def __init__(self, walkerId: int, index: int, state: dict):
|
|
30
|
+
self.walkerId = walkerId
|
|
31
|
+
self.index = index
|
|
32
|
+
self.state = state
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class BiasSharer:
|
|
36
|
+
"""Reads and writes walker bias states in a shared directory.
|
|
37
|
+
|
|
38
|
+
Parameters
|
|
39
|
+
----------
|
|
40
|
+
biasDir
|
|
41
|
+
Directory shared by every walker.
|
|
42
|
+
walkerId
|
|
43
|
+
This walker's identifier. Drawn at random when omitted, from a fresh
|
|
44
|
+
generator so the global NumPy random state is left undisturbed.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
def __init__(self, biasDir: str, walkerId: int | None = None):
|
|
48
|
+
self.biasDir = biasDir
|
|
49
|
+
self.walkerId = (
|
|
50
|
+
int(np.random.default_rng().integers(0x7FFFFFFF))
|
|
51
|
+
if walkerId is None
|
|
52
|
+
else walkerId
|
|
53
|
+
)
|
|
54
|
+
self._saveIndex = 0
|
|
55
|
+
self._loaded: dict[int, _LoadedBias] = {}
|
|
56
|
+
|
|
57
|
+
def _path(self, prefix: str, index: int) -> str:
|
|
58
|
+
return os.path.join(self.biasDir, f"{prefix}_{self.walkerId}_{index}.npz")
|
|
59
|
+
|
|
60
|
+
def save(self, state: dict) -> None:
|
|
61
|
+
"""Write this walker's state atomically and drop the previous index."""
|
|
62
|
+
oldName = self._path("kde", self._saveIndex)
|
|
63
|
+
self._saveIndex += 1
|
|
64
|
+
tempName = self._path("temp", self._saveIndex)
|
|
65
|
+
fileName = self._path("kde", self._saveIndex)
|
|
66
|
+
np.savez(tempName, **state)
|
|
67
|
+
os.rename(tempName, fileName)
|
|
68
|
+
if os.path.exists(oldName):
|
|
69
|
+
os.remove(oldName)
|
|
70
|
+
|
|
71
|
+
def load(self) -> dict[int, dict]:
|
|
72
|
+
"""Return peer states that are new or have advanced since the last call."""
|
|
73
|
+
updated: dict[int, dict] = {}
|
|
74
|
+
for filename in os.listdir(self.biasDir):
|
|
75
|
+
match = FILENAME_PATTERN.match(filename)
|
|
76
|
+
if match is None:
|
|
77
|
+
continue
|
|
78
|
+
walkerId, index = int(match.group(1)), int(match.group(2))
|
|
79
|
+
if walkerId == self.walkerId:
|
|
80
|
+
continue
|
|
81
|
+
known = self._loaded.get(walkerId)
|
|
82
|
+
if known is not None and index <= known.index:
|
|
83
|
+
continue
|
|
84
|
+
try:
|
|
85
|
+
with np.load(
|
|
86
|
+
os.path.join(self.biasDir, filename), allow_pickle=False
|
|
87
|
+
) as data:
|
|
88
|
+
state = {key: data[key] for key in data.files}
|
|
89
|
+
except (OSError, ValueError):
|
|
90
|
+
warnings.warn(
|
|
91
|
+
f"The file {filename} seems to have been deleted. Using the "
|
|
92
|
+
"latest loaded data from the same walker.",
|
|
93
|
+
stacklevel=2,
|
|
94
|
+
)
|
|
95
|
+
continue
|
|
96
|
+
self._loaded[walkerId] = _LoadedBias(walkerId, index, state)
|
|
97
|
+
updated[walkerId] = state
|
|
98
|
+
return updated
|
|
99
|
+
|
|
100
|
+
def getLoadedStates(self) -> dict[int, dict]:
|
|
101
|
+
"""Every peer state seen so far, whether or not it changed recently."""
|
|
102
|
+
return {walkerId: bias.state for walkerId, bias in self._loaded.items()}
|