asyncmd 0.3.3__py3-none-any.whl → 0.4.0__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.
- asyncmd/__init__.py +7 -0
- asyncmd/_config.py +16 -9
- asyncmd/_version.py +22 -36
- asyncmd/config.py +66 -33
- asyncmd/gromacs/__init__.py +3 -0
- asyncmd/gromacs/mdconfig.py +6 -16
- asyncmd/gromacs/mdengine.py +429 -421
- asyncmd/gromacs/utils.py +34 -17
- asyncmd/mdconfig.py +53 -163
- asyncmd/mdengine.py +120 -39
- asyncmd/slurm.py +29 -46
- asyncmd/tools.py +284 -5
- asyncmd/trajectory/__init__.py +19 -1
- asyncmd/trajectory/convert.py +133 -97
- asyncmd/trajectory/functionwrapper.py +197 -161
- asyncmd/trajectory/propagate.py +301 -254
- asyncmd/trajectory/trajectory.py +498 -755
- asyncmd/trajectory/trajectory_cache.py +365 -0
- asyncmd/utils.py +18 -13
- asyncmd-0.4.0.dist-info/METADATA +90 -0
- asyncmd-0.4.0.dist-info/RECORD +24 -0
- {asyncmd-0.3.3.dist-info → asyncmd-0.4.0.dist-info}/WHEEL +1 -1
- asyncmd-0.3.3.dist-info/METADATA +0 -194
- asyncmd-0.3.3.dist-info/RECORD +0 -23
- {asyncmd-0.3.3.dist-info → asyncmd-0.4.0.dist-info}/licenses/LICENSE +0 -0
- {asyncmd-0.3.3.dist-info → asyncmd-0.4.0.dist-info}/top_level.txt +0 -0
asyncmd/__init__.py
CHANGED
@@ -12,6 +12,13 @@
|
|
12
12
|
#
|
13
13
|
# You should have received a copy of the GNU General Public License
|
14
14
|
# along with asyncmd. If not, see <https://www.gnu.org/licenses/>.
|
15
|
+
"""
|
16
|
+
The asyncmd toplevel module.
|
17
|
+
|
18
|
+
It imports the central Trajectory objects and the config(uration) functions for
|
19
|
+
user convenience.
|
20
|
+
It also makes public some information on the asyncmd version/git_hash.
|
21
|
+
"""
|
15
22
|
from ._version import __version__, __git_hash__
|
16
23
|
|
17
24
|
from . import config
|
asyncmd/_config.py
CHANGED
@@ -12,15 +12,22 @@
|
|
12
12
|
#
|
13
13
|
# You should have received a copy of the GNU General Public License
|
14
14
|
# along with asyncmd. If not, see <https://www.gnu.org/licenses/>.
|
15
|
+
"""
|
16
|
+
Configuration dictionaries to influence asyncmd runtime behavior.
|
15
17
|
|
18
|
+
NOTE: This file **only** contains the dictionaries with the values
|
19
|
+
and **no** functions to set them, the funcs all live in 'config.py'.
|
20
|
+
The idea here is that we can then without any issues import additional
|
21
|
+
stuff (like the config functions from 'slurm.py') in 'config.py'
|
22
|
+
without risking circular imports because all asyncmd files should only
|
23
|
+
need to import the _CONFIG and _SEMAPHORES dicts from '_config.py'.
|
24
|
+
"""
|
25
|
+
import asyncio
|
26
|
+
import typing
|
16
27
|
|
17
|
-
# NOTE: This file **only** contains the dictionaries with the values
|
18
|
-
# and **no** functions to set them, the funcs all live in 'config.py'.
|
19
|
-
# The idea here is that we can then without any issues import additional
|
20
|
-
# stuff (like the config functions from 'slurm.py') in 'config.py'
|
21
|
-
# without risking circular imports becasue all asyncmd files should only
|
22
|
-
# need to import the _CONFIG and _SEMAPHORES dicts from '_config.py'.
|
23
28
|
|
24
|
-
|
25
|
-
|
26
|
-
|
29
|
+
_GLOBALS: dict[str, typing.Any] = {}
|
30
|
+
_SEMAPHORES: dict[str, asyncio.BoundedSemaphore] = {}
|
31
|
+
# These semaphores are optional (i.e. can be None, which means unlimited)
|
32
|
+
# e.g. slurm_max_jobs
|
33
|
+
_OPT_SEMAPHORES: dict[str, asyncio.BoundedSemaphore | None] = {}
|
asyncmd/_version.py
CHANGED
@@ -12,24 +12,15 @@
|
|
12
12
|
#
|
13
13
|
# You should have received a copy of the GNU General Public License
|
14
14
|
# along with asyncmd. If not, see <https://www.gnu.org/licenses/>.
|
15
|
+
"""
|
16
|
+
Helpers to populate asyncmd.__version__.
|
17
|
+
|
18
|
+
If we are in a git-repository (and detect commits since the last version-tagged
|
19
|
+
commit) we will add a git-hash to the version.
|
20
|
+
"""
|
15
21
|
import os
|
16
22
|
import subprocess
|
17
|
-
|
18
|
-
|
19
|
-
def _get_version_from_pyproject():
|
20
|
-
"""Get version string from pyproject.toml file."""
|
21
|
-
pyproject_toml = os.path.join(os.path.dirname(__file__),
|
22
|
-
"../../pyproject.toml")
|
23
|
-
with open(pyproject_toml) as f:
|
24
|
-
line = f.readline()
|
25
|
-
while line:
|
26
|
-
if line.startswith("version ="):
|
27
|
-
version_line = line
|
28
|
-
break
|
29
|
-
line = f.readline()
|
30
|
-
version = version_line.strip().split(" = ")[1]
|
31
|
-
version = version.replace('"', '').replace("'", "")
|
32
|
-
return version
|
23
|
+
import importlib.metadata
|
33
24
|
|
34
25
|
|
35
26
|
def _get_git_hash_and_tag():
|
@@ -37,39 +28,34 @@ def _get_git_hash_and_tag():
|
|
37
28
|
git_hash = ""
|
38
29
|
git_date = ""
|
39
30
|
git_tag = ""
|
40
|
-
|
31
|
+
with subprocess.Popen(
|
41
32
|
["git", "log", "-1", "--format='%H || %as || %(describe:tags=true,match=v*)'"],
|
42
33
|
stdout=subprocess.PIPE,
|
43
34
|
stderr=subprocess.PIPE,
|
44
35
|
cwd=os.path.dirname(__file__),
|
45
|
-
|
46
|
-
|
47
|
-
|
36
|
+
) as p:
|
37
|
+
stdout, _ = p.communicate()
|
38
|
+
returncode = p.returncode
|
39
|
+
if not returncode:
|
48
40
|
git_hash, git_date, git_describe = (stdout.decode("utf-8")
|
49
41
|
.replace("'", "").replace('"', '')
|
50
42
|
.strip().split("||"))
|
51
43
|
git_date = git_date.strip().replace("-", "")
|
52
44
|
git_describe = git_describe.strip()
|
53
|
-
if "-" not in git_describe
|
45
|
+
if git_describe and "-" not in git_describe:
|
54
46
|
# git-describe returns either the git-tag or (if we are not exactly
|
55
47
|
# at a tag) something like
|
56
48
|
# $GITTAG-$NUM_COMMITS_DISTANCE-$CURRENT_COMMIT_HASH
|
57
49
|
git_tag = git_describe[1:] # strip of the 'v'
|
58
50
|
return git_hash, git_date, git_tag
|
59
51
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
52
|
+
|
53
|
+
_version = importlib.metadata.version("asyncmd")
|
54
|
+
_git_hash, _git_date, _git_tag = _get_git_hash_and_tag()
|
55
|
+
__git_hash__ = _git_hash
|
56
|
+
if _version == _git_tag or not _git_hash:
|
57
|
+
# dont append git_hash to version, if it is a version-tagged commit or if
|
58
|
+
# git_hash is empty (happens if git is installed but we are not in a repo)
|
59
|
+
__version__ = _version
|
67
60
|
else:
|
68
|
-
|
69
|
-
__git_hash__ = _git_hash
|
70
|
-
if _version == _git_tag or _git_hash == "":
|
71
|
-
# dont append git_hash to version, if it is a version-tagged commit or if
|
72
|
-
# git_hash is empty (happens if git is installed but we are not in a repo)
|
73
|
-
__version__ = _version
|
74
|
-
else:
|
75
|
-
__version__ = _version + f"+git{_git_date}.{_git_hash[:7]}"
|
61
|
+
__version__ = _version + f"+git{_git_date}.{_git_hash[:7]}"
|
asyncmd/config.py
CHANGED
@@ -12,23 +12,29 @@
|
|
12
12
|
#
|
13
13
|
# You should have received a copy of the GNU General Public License
|
14
14
|
# along with asyncmd. If not, see <https://www.gnu.org/licenses/>.
|
15
|
+
"""
|
16
|
+
This module contains the implementation of functions configuring asyncmd behavior.
|
17
|
+
|
18
|
+
It also import the configuration functions for submodules (like slurm) to make
|
19
|
+
them accessible to users in one central place.
|
20
|
+
"""
|
15
21
|
import os
|
16
22
|
import asyncio
|
17
23
|
import logging
|
18
24
|
import resource
|
19
|
-
import typing
|
20
25
|
|
21
26
|
|
22
|
-
from ._config import _GLOBALS, _SEMAPHORES
|
27
|
+
from ._config import _GLOBALS, _SEMAPHORES, _OPT_SEMAPHORES
|
28
|
+
from .trajectory.trajectory import _update_cache_type_for_all_trajectories
|
29
|
+
# pylint: disable-next=unused-import
|
23
30
|
from .slurm import set_slurm_settings, set_all_slurm_settings
|
24
|
-
# TODO: Do we want to set the _GLOBALS defaults here? E.g. CACHE_TYPE="npz"?
|
25
31
|
|
26
32
|
|
27
33
|
logger = logging.getLogger(__name__)
|
28
34
|
|
29
35
|
|
30
36
|
# can be called by the user to (re) set maximum number of processes used
|
31
|
-
def set_max_process(num=None, max_num=None):
|
37
|
+
def set_max_process(num: int | None = None, max_num: int | None = None) -> None:
|
32
38
|
"""
|
33
39
|
Set the maximum number of concurrent python processes.
|
34
40
|
|
@@ -45,16 +51,14 @@ def set_max_process(num=None, max_num=None):
|
|
45
51
|
spawning hundreds of processes.
|
46
52
|
"""
|
47
53
|
# NOTE: I think we should use a conservative default, e.g. 0.25*cpu_count()
|
48
|
-
#
|
54
|
+
# pylint: disable-next=global-variable-not-assigned
|
49
55
|
global _SEMAPHORES
|
50
56
|
if num is None:
|
51
|
-
logical_cpu_count
|
52
|
-
|
53
|
-
num = int(logical_cpu_count / 4)
|
57
|
+
if (logical_cpu_count := os.cpu_count()) is not None:
|
58
|
+
num = max(1, int(logical_cpu_count / 4))
|
54
59
|
else:
|
55
60
|
# fallback if os.cpu_count() can not determine the number of cpus
|
56
61
|
# play it save and not have more than 2?
|
57
|
-
# TODO: think about a good number!
|
58
62
|
num = 2
|
59
63
|
if max_num is not None:
|
60
64
|
num = min((num, max_num))
|
@@ -64,7 +68,7 @@ def set_max_process(num=None, max_num=None):
|
|
64
68
|
set_max_process()
|
65
69
|
|
66
70
|
|
67
|
-
def set_max_files_open(num:
|
71
|
+
def set_max_files_open(num: int | None = None, margin: int = 30) -> None:
|
68
72
|
"""
|
69
73
|
Set the maximum number of concurrently opened files.
|
70
74
|
|
@@ -86,10 +90,11 @@ def set_max_files_open(num: typing.Optional[int] = None, margin: int = 30):
|
|
86
90
|
"""
|
87
91
|
# ensure that we do not open too many files
|
88
92
|
# resource.getrlimit returns a tuple (soft, hard); we take the soft-limit
|
89
|
-
# and to be sure 30 less (the reason
|
93
|
+
# and to be sure 30 less (the reason being that we can not use the
|
90
94
|
# semaphores from non-async code, but sometimes use the sync subprocess.run
|
91
95
|
# and subprocess.check_call [which also need files/pipes to work])
|
92
96
|
# also maybe we need other open files like a storage :)
|
97
|
+
# pylint: disable-next=global-variable-not-assigned
|
93
98
|
global _SEMAPHORES
|
94
99
|
rlim_soft = resource.getrlimit(resource.RLIMIT_NOFILE)[0]
|
95
100
|
if num is None:
|
@@ -125,10 +130,9 @@ set_max_files_open()
|
|
125
130
|
# SLURM semaphore stuff:
|
126
131
|
# TODO: move this to slurm.py? and initialize only if slurm is available?
|
127
132
|
# slurm max job semaphore, if the user sets it it will be used,
|
128
|
-
# otherwise we can use an unlimited number of
|
133
|
+
# otherwise we can use an unlimited number of synchronous slurm-jobs
|
129
134
|
# (if the simulation requires that much)
|
130
|
-
|
131
|
-
def set_slurm_max_jobs(num: typing.Union[int, None]):
|
135
|
+
def set_slurm_max_jobs(num: int | None) -> None:
|
132
136
|
"""
|
133
137
|
Set the maximum number of simultaneously submitted SLURM jobs.
|
134
138
|
|
@@ -138,66 +142,95 @@ def set_slurm_max_jobs(num: typing.Union[int, None]):
|
|
138
142
|
The maximum number of simultaneous SLURM jobs for this invocation of
|
139
143
|
python/asyncmd. `None` means do not limit the maximum number of jobs.
|
140
144
|
"""
|
141
|
-
global
|
145
|
+
# pylint: disable-next=global-variable-not-assigned
|
146
|
+
global _OPT_SEMAPHORES
|
142
147
|
if num is None:
|
143
|
-
|
148
|
+
_OPT_SEMAPHORES["SLURM_MAX_JOB"] = None
|
144
149
|
else:
|
145
|
-
|
150
|
+
_OPT_SEMAPHORES["SLURM_MAX_JOB"] = asyncio.BoundedSemaphore(num)
|
146
151
|
|
147
152
|
|
148
153
|
set_slurm_max_jobs(num=None)
|
149
154
|
|
150
155
|
|
151
156
|
# Trajectory function value config
|
152
|
-
def
|
157
|
+
def set_trajectory_cache_type(cache_type: str,
|
158
|
+
copy_content: bool = True,
|
159
|
+
clear_old_cache: bool = False
|
160
|
+
) -> None:
|
153
161
|
"""
|
154
|
-
Set the
|
162
|
+
Set the cache type for TrajectoryFunctionWrapper values.
|
155
163
|
|
156
|
-
|
157
|
-
|
164
|
+
By default the content of the current caches is copied to the new caches.
|
165
|
+
To clear the old/previously set caches (after copying their values), pass
|
166
|
+
``clear_old_cache=True``.
|
158
167
|
|
159
168
|
Parameters
|
160
169
|
----------
|
161
170
|
cache_type : str
|
162
171
|
One of "h5py", "npz", "memory".
|
172
|
+
copy_content : bool, optional
|
173
|
+
Whether to copy the current cache content to the new cache,
|
174
|
+
by default True
|
175
|
+
clear_old_cache : bool, optional
|
176
|
+
Whether to clear the old/previously set cache, by default False.
|
163
177
|
|
164
178
|
Raises
|
165
179
|
------
|
166
180
|
ValueError
|
167
181
|
Raised if ``cache_type`` is not one of the allowed values.
|
168
182
|
"""
|
183
|
+
# pylint: disable-next=global-variable-not-assigned
|
169
184
|
global _GLOBALS
|
170
185
|
allowed_values = ["h5py", "npz", "memory"]
|
171
|
-
cache_type
|
172
|
-
if cache_type not in allowed_values:
|
186
|
+
if (cache_type := cache_type.lower()) not in allowed_values:
|
173
187
|
raise ValueError(f"Given cache type must be one of {allowed_values}."
|
174
188
|
+ f" Was: {cache_type}.")
|
175
|
-
_GLOBALS
|
189
|
+
if _GLOBALS.get("TRAJECTORY_FUNCTION_CACHE_TYPE", "not_set") != cache_type:
|
190
|
+
# only do something if the new cache type differs from what we have
|
191
|
+
_GLOBALS["TRAJECTORY_FUNCTION_CACHE_TYPE"] = cache_type
|
192
|
+
_update_cache_type_for_all_trajectories(copy_content=copy_content,
|
193
|
+
clear_old_cache=clear_old_cache,
|
194
|
+
)
|
195
|
+
|
196
|
+
|
197
|
+
set_trajectory_cache_type("npz")
|
176
198
|
|
177
199
|
|
178
|
-
def register_h5py_cache(h5py_group
|
200
|
+
def register_h5py_cache(h5py_group) -> None:
|
179
201
|
"""
|
180
202
|
Register a h5py file or group for CV value caching.
|
181
203
|
|
182
204
|
Note that this also sets the default cache type to "h5py", i.e. it calls
|
183
|
-
:func:`
|
205
|
+
:func:`set_trajectory_cache_type` with ``cache_type="h5py"``.
|
184
206
|
|
185
207
|
Note that a ``h5py.File`` is just a slightly special ``h5py.Group``, so you
|
186
|
-
can pass either. :mod:`asyncmd` will use
|
208
|
+
can pass either. :mod:`asyncmd` will use either the file or the group as
|
187
209
|
the root of its own stored values.
|
188
210
|
E.g. you will have ``h5py_group["asyncmd/TrajectoryFunctionValueCache"]``
|
189
211
|
always pointing to the cached trajectory values and if ``h5py_group`` is
|
190
|
-
the top-level group (i.e. the file) you also have
|
212
|
+
the top-level group (i.e. the file) you also have
|
213
|
+
``(file["/asyncmd/TrajectoryFunctionValueCache"] ==\
|
214
|
+
h5py_group["asyncmd/TrajectoryFunctionValueCache"])``.
|
191
215
|
|
192
216
|
Parameters
|
193
217
|
----------
|
194
218
|
h5py_group : h5py.Group or h5py.File
|
195
219
|
The file or group to use for caching.
|
196
|
-
make_default: bool,
|
197
|
-
Whether we should also make "h5py" the default trajectory function
|
198
|
-
cache type. By default False.
|
199
220
|
"""
|
221
|
+
# pylint: disable-next=global-variable-not-assigned
|
200
222
|
global _GLOBALS
|
201
|
-
if make_default:
|
202
|
-
set_default_trajectory_cache_type(cache_type="h5py")
|
203
223
|
_GLOBALS["H5PY_CACHE"] = h5py_group
|
224
|
+
set_trajectory_cache_type(cache_type="h5py")
|
225
|
+
|
226
|
+
|
227
|
+
def show_config() -> None:
|
228
|
+
"""
|
229
|
+
Print/show current configuration.
|
230
|
+
"""
|
231
|
+
print(f"Values controlling caching: {_GLOBALS}")
|
232
|
+
# pylint: disable-next=protected-access
|
233
|
+
sem_print = {key: sem._value
|
234
|
+
for key, sem in {**_SEMAPHORES, **_OPT_SEMAPHORES}.items()
|
235
|
+
if sem is not None}
|
236
|
+
print(f"Semaphores controlling resource usage: {sem_print}")
|
asyncmd/gromacs/__init__.py
CHANGED
@@ -12,5 +12,8 @@
|
|
12
12
|
#
|
13
13
|
# You should have received a copy of the GNU General Public License
|
14
14
|
# along with asyncmd. If not, see <https://www.gnu.org/licenses/>.
|
15
|
+
"""
|
16
|
+
This module exports all user-facing classes and functions for running gromacs.
|
17
|
+
"""
|
15
18
|
from .mdconfig import MDP
|
16
19
|
from .mdengine import GmxEngine, SlurmGmxEngine
|
asyncmd/gromacs/mdconfig.py
CHANGED
@@ -12,6 +12,9 @@
|
|
12
12
|
#
|
13
13
|
# You should have received a copy of the GNU General Public License
|
14
14
|
# along with asyncmd. If not, see <https://www.gnu.org/licenses/>.
|
15
|
+
"""
|
16
|
+
This file contains the MDP class to parse, modify, and write gromacs mdp files.
|
17
|
+
"""
|
15
18
|
import shlex
|
16
19
|
import logging
|
17
20
|
from ..mdconfig import LineBasedMDConfig
|
@@ -27,18 +30,6 @@ class MDP(LineBasedMDConfig):
|
|
27
30
|
option, list of values pairs. Includes automatic types for known options
|
28
31
|
and keeps track if any options have been changed compared to the original
|
29
32
|
file.
|
30
|
-
|
31
|
-
Parameters
|
32
|
-
----------
|
33
|
-
original_file : str
|
34
|
-
absolute or relative path to original config file to parse
|
35
|
-
|
36
|
-
Methods
|
37
|
-
-------
|
38
|
-
write(outfile)
|
39
|
-
write the current (modified) configuration state to a given file
|
40
|
-
parse()
|
41
|
-
read the current original_file and update own state with it
|
42
33
|
"""
|
43
34
|
|
44
35
|
_KEY_VALUE_SEPARATOR = " = "
|
@@ -298,7 +289,7 @@ class MDP(LineBasedMDConfig):
|
|
298
289
|
# before or after the equal sign
|
299
290
|
# 4. no splits at '=' and no splits at ';' -> weired line, probably
|
300
291
|
# not a valid line(?)
|
301
|
-
if splits_at_comment[0]
|
292
|
+
if not splits_at_comment[0]:
|
302
293
|
# option 2 (and 3 if the comment is before the equal sign)
|
303
294
|
# comment sign is the first letter, so the whole line is
|
304
295
|
# (most probably) a comment line
|
@@ -318,10 +309,9 @@ class MDP(LineBasedMDConfig):
|
|
318
309
|
parser.commenters = ";"
|
319
310
|
# puncutation_chars=True adds "~-./*?=" to wordchars
|
320
311
|
# such that we do not split floats and file paths and similar
|
321
|
-
tokens = list(parser)
|
322
312
|
# gromacs mdp can have 0-N tokens/values to the RHS of the '='
|
323
|
-
if
|
324
|
-
# line with empty options, e.g. 'define = '
|
313
|
+
if not (tokens := list(parser)):
|
314
|
+
# zero tokens -> line with empty options, e.g. 'define = '
|
325
315
|
return {self._key_char_replace(key): []}
|
326
316
|
# lines with content, we always return a list (and let our
|
327
317
|
# type_dispatch sort out the singleton options and the typing)
|