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/gromacs/utils.py
CHANGED
@@ -12,6 +12,11 @@
|
|
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 implements gromacs-specific variants of the utility functions related to MD usage.
|
17
|
+
|
18
|
+
The general variants of these functions can be found in asyncmd.utils.
|
19
|
+
"""
|
15
20
|
import os
|
16
21
|
import logging
|
17
22
|
import aiofiles.os
|
@@ -48,30 +53,42 @@ def nstout_from_mdp(mdp: MDP, traj_type: str = "TRR") -> int:
|
|
48
53
|
Raised when the given MDP would result in no output for the given
|
49
54
|
trajectory format `traj_type`.
|
50
55
|
"""
|
56
|
+
def get_value_from_mdp(k):
|
57
|
+
try:
|
58
|
+
v = mdp[k]
|
59
|
+
except KeyError:
|
60
|
+
# not set, defaults to 0
|
61
|
+
v = float("inf")
|
62
|
+
else:
|
63
|
+
# need to check for 0 (== no output!) in case somone puts the
|
64
|
+
# defaults (or reads an mdout.mdp where gmx lists all the defaults)
|
65
|
+
if not v:
|
66
|
+
v = float("inf")
|
67
|
+
return v
|
68
|
+
|
51
69
|
if traj_type.upper() == "TRR":
|
52
|
-
keys = ["nstxout"
|
70
|
+
keys = ["nstxout"]
|
53
71
|
elif traj_type.upper() == "XTC":
|
54
72
|
keys = ["nstxout-compressed", "nstxtcout"]
|
55
73
|
else:
|
56
74
|
raise ValueError("traj_type must be one of 'TRR' or 'XTC'.")
|
57
|
-
|
58
75
|
vals = []
|
59
76
|
for k in keys:
|
60
|
-
|
61
|
-
|
62
|
-
# defaults (or reads an mdout.mdp where gmx lists all the defaults)
|
63
|
-
v = mdp[k]
|
64
|
-
if v == 0:
|
65
|
-
v = float("inf")
|
66
|
-
vals += [v]
|
67
|
-
except KeyError:
|
68
|
-
# not set, defaults to 0, so we ignore it
|
69
|
-
pass
|
70
|
-
|
71
|
-
nstout = min(vals, default=None)
|
72
|
-
if (nstout is None) or (nstout == float("inf")):
|
77
|
+
vals += [get_value_from_mdp(k=k)]
|
78
|
+
if (nstout := min(vals)) == float("inf"):
|
73
79
|
raise ValueError(f"The MDP you passed results in no {traj_type} "
|
74
|
-
+"trajectory output.")
|
80
|
+
+ "trajectory output.")
|
81
|
+
if traj_type.upper == "TRR":
|
82
|
+
# additional checks that nstvout and nstfout are multiples of nstxout
|
83
|
+
# (if they are defined)
|
84
|
+
additional_keys = ["nstvout", "nstfout"]
|
85
|
+
for k in additional_keys:
|
86
|
+
if (v := get_value_from_mdp(k=k)) != float("inf"):
|
87
|
+
if v % nstout:
|
88
|
+
logger.warning("%s trajectory output is not a multiple of "
|
89
|
+
"the nstxout frequency (%s=%d, nstxout=%d).",
|
90
|
+
k, k, v, nstout)
|
91
|
+
|
75
92
|
return nstout
|
76
93
|
|
77
94
|
|
@@ -128,7 +145,7 @@ async def get_all_file_parts(folder: str, deffnm: str, file_ending: str) -> "lis
|
|
128
145
|
"""
|
129
146
|
def partnum_suffix(num):
|
130
147
|
# construct gromacs num part suffix from simulation_part
|
131
|
-
num_suffix = ".part{:04d}"
|
148
|
+
num_suffix = f".part{num:04d}"
|
132
149
|
return num_suffix
|
133
150
|
|
134
151
|
if not file_ending.startswith("."):
|
asyncmd/mdconfig.py
CHANGED
@@ -12,160 +12,51 @@
|
|
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 abstract base classes to use for MD config file parsing.
|
17
|
+
|
18
|
+
It contains the MDConfig class, which only serves to define the interface and
|
19
|
+
the LineBasedMDConfig class, which implements useful methods to parse MD configuration
|
20
|
+
files in which options never span over multiple lines, i.e. in which every line
|
21
|
+
contains only one (or multiple) options.
|
22
|
+
"""
|
15
23
|
import os
|
16
24
|
import abc
|
17
|
-
import typing
|
18
25
|
import shutil
|
19
26
|
import logging
|
20
27
|
import collections
|
21
28
|
|
29
|
+
from .tools import TypedFlagChangeList
|
22
30
|
|
23
|
-
logger = logging.getLogger(__name__)
|
24
|
-
|
25
|
-
|
26
|
-
class FlagChangeList(collections.abc.MutableSequence):
|
27
|
-
"""A list that knows if it has been changed after initializing."""
|
28
|
-
|
29
|
-
def __init__(self, data: list) -> None:
|
30
|
-
"""
|
31
|
-
Initialize a `FlagChangeList`.
|
32
|
-
|
33
|
-
Parameters
|
34
|
-
----------
|
35
|
-
data : list
|
36
|
-
The data this `FlagChangeList` will hold.
|
37
|
-
|
38
|
-
Raises
|
39
|
-
------
|
40
|
-
TypeError
|
41
|
-
Raised when data is not a :class:`list`.
|
42
|
-
"""
|
43
|
-
if not isinstance(data, list):
|
44
|
-
raise TypeError("FlagChangeList must be initialized with a list.")
|
45
|
-
self._data = data
|
46
|
-
self._changed = False
|
47
|
-
|
48
|
-
@property
|
49
|
-
def changed(self) -> bool:
|
50
|
-
"""
|
51
|
-
Whether this `FlagChangeList` has been modified since creation.
|
52
|
-
|
53
|
-
Returns
|
54
|
-
-------
|
55
|
-
bool
|
56
|
-
"""
|
57
|
-
return self._changed
|
58
|
-
|
59
|
-
def __repr__(self) -> str:
|
60
|
-
return self._data.__repr__()
|
61
|
-
|
62
|
-
def __getitem__(self, index: int) -> typing.Any:
|
63
|
-
return self._data.__getitem__(index)
|
64
|
-
|
65
|
-
def __len__(self) -> int:
|
66
|
-
return self._data.__len__()
|
67
|
-
|
68
|
-
def __setitem__(self, index: int, value) -> None:
|
69
|
-
self._data.__setitem__(index, value)
|
70
|
-
self._changed = True
|
71
|
-
|
72
|
-
def __delitem__(self, index: int) -> None:
|
73
|
-
self._data.__delitem__(index)
|
74
|
-
self._changed = True
|
75
31
|
|
76
|
-
|
77
|
-
"""
|
78
|
-
Insert `value` at position given by `index`.
|
79
|
-
|
80
|
-
Parameters
|
81
|
-
----------
|
82
|
-
index : int
|
83
|
-
The index of the new value in the `FlagChangeList`.
|
84
|
-
value : typing.Any
|
85
|
-
The value to insert into this `FlagChangeList`.
|
86
|
-
"""
|
87
|
-
self._data.insert(index, value)
|
88
|
-
self._changed = True
|
89
|
-
|
90
|
-
|
91
|
-
class TypedFlagChangeList(FlagChangeList):
|
92
|
-
"""
|
93
|
-
A :class:`FlagChangeList` with an ensured type for individual list items.
|
94
|
-
"""
|
95
|
-
|
96
|
-
def __init__(self, data: typing.Iterable, dtype) -> None:
|
97
|
-
"""
|
98
|
-
Initialize a `TypedFlagChangeList`.
|
99
|
-
|
100
|
-
Parameters
|
101
|
-
----------
|
102
|
-
data : Iterable
|
103
|
-
(Initial) data for this `TypedFlagChangeList`.
|
104
|
-
dtype : Callable datatype
|
105
|
-
The datatype for all entries in this `TypedFlagChangeList`. Will be
|
106
|
-
called on every value seperately and is expected to convert to the
|
107
|
-
desired datatype.
|
108
|
-
"""
|
109
|
-
self._dtype = dtype # set first to use in _convert_type method
|
110
|
-
if getattr(data, '__len__', None) is None:
|
111
|
-
# convienience for singular options,
|
112
|
-
# if it has no len attribute we assume it is the only item
|
113
|
-
data = [data]
|
114
|
-
elif isinstance(data, str):
|
115
|
-
# strings have a length but we still do not want to split them into
|
116
|
-
# single letters, so just put a list around
|
117
|
-
data = [data]
|
118
|
-
typed_data = [self._convert_type(v, index=i)
|
119
|
-
for i, v in enumerate(data)]
|
120
|
-
super().__init__(data=typed_data)
|
121
|
-
|
122
|
-
def _convert_type(self, value, index=None):
|
123
|
-
# here we ignore index, but passing it should in principal make it
|
124
|
-
# possible to use different dtypes for different indices
|
125
|
-
return self._dtype(value)
|
126
|
-
|
127
|
-
def __setitem__(self, index: int, value) -> None:
|
128
|
-
typed_value = self._convert_type(value, index=index)
|
129
|
-
self._data.__setitem__(index, typed_value)
|
130
|
-
self._changed = True
|
131
|
-
|
132
|
-
def insert(self, index: int, value) -> None:
|
133
|
-
"""
|
134
|
-
Insert `value` at position given by `index`.
|
135
|
-
|
136
|
-
Parameters
|
137
|
-
----------
|
138
|
-
index : int
|
139
|
-
The index of the new value in the `TypedFlagChangeList`.
|
140
|
-
value : typing.Any
|
141
|
-
The value to insert into this `TypedFlagChangeList`.
|
142
|
-
"""
|
143
|
-
typed_value = self._convert_type(value, index=index)
|
144
|
-
self._data.insert(index, typed_value)
|
145
|
-
self._changed = True
|
32
|
+
logger = logging.getLogger(__name__)
|
146
33
|
|
147
34
|
|
148
|
-
# NOTE: only to define the interface
|
149
35
|
class MDConfig(collections.abc.MutableMapping):
|
36
|
+
"""Abstract base class only to define the interface."""
|
37
|
+
|
150
38
|
@abc.abstractmethod
|
151
39
|
def parse(self):
|
152
|
-
|
40
|
+
"""Should read original file and populate self with key, value pairs."""
|
153
41
|
raise NotImplementedError
|
154
42
|
|
155
43
|
@abc.abstractmethod
|
156
44
|
def write(self, outfile):
|
157
|
-
|
45
|
+
"""Should write out current config stored in self to outfile."""
|
158
46
|
raise NotImplementedError
|
159
47
|
|
160
48
|
|
161
49
|
class LineBasedMDConfig(MDConfig):
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
50
|
+
"""
|
51
|
+
Abstract base class for line based parsing and writing.
|
52
|
+
|
53
|
+
Subclasses must implement `_parse_line()` method and should set the
|
54
|
+
appropriate separator characters for their line format.
|
55
|
+
We assume that every line/option can be parsed and written on its own!
|
56
|
+
We assume the order of the options in the written file is not relevant!
|
57
|
+
We represent every line/option with a key (str), list of values pair.
|
58
|
+
Values can have a specific type (e.g. int or float) or default to str.
|
59
|
+
"""
|
169
60
|
# NOTE: Initially written for gmx, but we already had e.g. namd in mind and
|
170
61
|
# tried to make this as general as possible
|
171
62
|
|
@@ -177,11 +68,11 @@ class LineBasedMDConfig(MDConfig):
|
|
177
68
|
# use these to specify config parameters that are of type int or float
|
178
69
|
# parsed lines with dict key matching will then be converted
|
179
70
|
# any lines not matching will be left in their default str type
|
180
|
-
_FLOAT_PARAMS = [] # can have multiple values per config option
|
181
|
-
_FLOAT_SINGLETON_PARAMS = [] # must have one value per config option
|
182
|
-
_INT_PARAMS = [] # multiple int per option
|
183
|
-
_INT_SINGLETON_PARAMS = [] # one int per option
|
184
|
-
_STR_SINGLETON_PARAMS = [] # strings with only one value per option
|
71
|
+
_FLOAT_PARAMS: list[str] = [] # can have multiple values per config option
|
72
|
+
_FLOAT_SINGLETON_PARAMS: list[str] = [] # must have one value per config option
|
73
|
+
_INT_PARAMS: list[str] = [] # multiple int per option
|
74
|
+
_INT_SINGLETON_PARAMS: list[str] = [] # one int per option
|
75
|
+
_STR_SINGLETON_PARAMS: list[str] = [] # strings with only one value per option
|
185
76
|
# NOTE on SPECIAL_PARAM_DISPATCH
|
186
77
|
# can be used to set custom type convert functions on a per parameter basis
|
187
78
|
# the key must match the key in the dict for in the parsed line,
|
@@ -192,7 +83,7 @@ class LineBasedMDConfig(MDConfig):
|
|
192
83
|
# new values into the expected FlagChangeList format
|
193
84
|
# [note that it is probably easiest to subclass TypedFlagChangeList and
|
194
85
|
# overwrite only the '_check_type()' method]
|
195
|
-
_SPECIAL_PARAM_DISPATCH = {}
|
86
|
+
_SPECIAL_PARAM_DISPATCH: dict[str, collections.abc.Callable] = {}
|
196
87
|
|
197
88
|
def __init__(self, original_file: str) -> None:
|
198
89
|
"""
|
@@ -203,7 +94,7 @@ class LineBasedMDConfig(MDConfig):
|
|
203
94
|
original_file : str
|
204
95
|
Path to original config file (absolute or relative).
|
205
96
|
"""
|
206
|
-
self._config = {}
|
97
|
+
self._config: dict[str, TypedFlagChangeList | int | float | str] = {}
|
207
98
|
self._changed = False
|
208
99
|
self._type_dispatch = self._construct_type_dispatch()
|
209
100
|
# property to set/check file and parse to config dictionary all in one
|
@@ -218,8 +109,7 @@ class LineBasedMDConfig(MDConfig):
|
|
218
109
|
# singleton vals, i.e. "val" instead of ["val"]
|
219
110
|
if isinstance(val, str) or getattr(val, '__len__', None) is None:
|
220
111
|
return dtype(val)
|
221
|
-
|
222
|
-
return dtype(val[0])
|
112
|
+
return dtype(val[0])
|
223
113
|
|
224
114
|
# construct type conversion dispatch
|
225
115
|
type_dispatch = collections.defaultdict(
|
@@ -310,14 +200,14 @@ class LineBasedMDConfig(MDConfig):
|
|
310
200
|
def __len__(self) -> int:
|
311
201
|
return self._config.__len__()
|
312
202
|
|
313
|
-
def __repr__(self) -> str:
|
203
|
+
def __repr__(self) -> str: # pragma: no cover
|
314
204
|
return str({"changed": self._changed,
|
315
205
|
"original_file": self.original_file,
|
316
206
|
"content": self._config.__repr__(),
|
317
207
|
}
|
318
208
|
)
|
319
209
|
|
320
|
-
def __str__(self) -> str:
|
210
|
+
def __str__(self) -> str: # pragma: no cover
|
321
211
|
repr_str = (f"{type(self)} has been changed since parsing: "
|
322
212
|
+ f"{self._changed}\n"
|
323
213
|
)
|
@@ -362,15 +252,14 @@ class LineBasedMDConfig(MDConfig):
|
|
362
252
|
# NOTE: we default to False, i.e. we expect that anything that
|
363
253
|
# does not have a self.changed attribute is not a container
|
364
254
|
# and we (the dictionary) would know that it changed
|
365
|
-
return self._changed or any(
|
366
|
-
|
367
|
-
)
|
255
|
+
return self._changed or any(getattr(v, "changed", False)
|
256
|
+
for v in self._config.values())
|
368
257
|
|
369
258
|
def parse(self):
|
370
259
|
"""Parse the current ``self.original_file`` to update own state."""
|
371
|
-
with open(self.original_file, "r") as f:
|
260
|
+
with open(self.original_file, "r", encoding="locale") as f:
|
372
261
|
# NOTE: we split at newlines on all platforms by iterating over the
|
373
|
-
# file, i.e. python takes care of the
|
262
|
+
# file, i.e. python takes care of the different platforms and
|
374
263
|
# newline chars for us :)
|
375
264
|
parsed = {}
|
376
265
|
for line in f:
|
@@ -384,7 +273,7 @@ class LineBasedMDConfig(MDConfig):
|
|
384
273
|
# as it should be
|
385
274
|
pass
|
386
275
|
else:
|
387
|
-
# warn that we will only keep the last
|
276
|
+
# warn that we will only keep the last occurrence of key
|
388
277
|
logger.warning("Parsed duplicate configuration option "
|
389
278
|
"(%s). Last values encountered take "
|
390
279
|
"precedence.", key)
|
@@ -421,20 +310,21 @@ class LineBasedMDConfig(MDConfig):
|
|
421
310
|
lines = []
|
422
311
|
for key, value in self._config.items():
|
423
312
|
line = f"{key}{self._KEY_VALUE_SEPARATOR}"
|
424
|
-
|
425
|
-
|
426
|
-
if isinstance(value, str):
|
427
|
-
# it is a string singleton option
|
428
|
-
line += f"{value}"
|
429
|
-
else:
|
430
|
-
line += self._INTER_VALUE_CHAR.join(str(v)
|
431
|
-
for v in value
|
432
|
-
)
|
433
|
-
except TypeError:
|
434
|
-
# not a Sequence/Iterable or string,
|
435
|
-
# i.e. (probably) one of the float/int singleton options
|
313
|
+
if isinstance(value, (str, float, int)):
|
314
|
+
# it is a string/float/int singleton option
|
436
315
|
line += f"{value}"
|
316
|
+
else:
|
317
|
+
# not a singleton, so lets try to iterate over it
|
318
|
+
try:
|
319
|
+
line += self._INTER_VALUE_CHAR.join(str(v)
|
320
|
+
for v in value
|
321
|
+
)
|
322
|
+
except TypeError:
|
323
|
+
# Note: need this except to catch user-added types
|
324
|
+
# (via special param dispatch), that are not
|
325
|
+
# str/float/int singletons but also not iterable
|
326
|
+
line += f"{value}"
|
437
327
|
lines += [line]
|
438
328
|
# concatenate the lines and write out at once
|
439
|
-
with open(outfile, "w") as f:
|
329
|
+
with open(outfile, "w", encoding="locale") as f:
|
440
330
|
f.write("\n".join(lines))
|
asyncmd/mdengine.py
CHANGED
@@ -12,18 +12,21 @@
|
|
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 abstract base class defining the interface for all MDEngines.
|
17
|
+
|
18
|
+
It also defines the commonly used exceptions for MDEngines.
|
19
|
+
"""
|
15
20
|
import abc
|
16
21
|
from .trajectory.trajectory import Trajectory
|
17
22
|
|
18
23
|
|
19
24
|
class EngineError(Exception):
|
20
25
|
"""Exception raised when something goes wrong with the (MD)-Engine."""
|
21
|
-
pass
|
22
26
|
|
23
27
|
|
24
28
|
class EngineCrashedError(EngineError):
|
25
29
|
"""Exception raised when the (MD)-Engine crashes during a run."""
|
26
|
-
pass
|
27
30
|
|
28
31
|
|
29
32
|
class MDEngine(abc.ABC):
|
@@ -33,68 +36,146 @@ class MDEngine(abc.ABC):
|
|
33
36
|
@abc.abstractmethod
|
34
37
|
async def apply_constraints(self, conf_in: Trajectory,
|
35
38
|
conf_out_name: str) -> Trajectory:
|
36
|
-
|
39
|
+
"""
|
40
|
+
Apply constraints to given conf_in, write conf_out_name and return it.
|
41
|
+
|
42
|
+
Parameters
|
43
|
+
----------
|
44
|
+
conf_in : Trajectory
|
45
|
+
conf_out_name : str
|
46
|
+
|
47
|
+
Returns
|
48
|
+
-------
|
49
|
+
Trajectory
|
50
|
+
"""
|
37
51
|
raise NotImplementedError
|
38
52
|
|
39
53
|
@abc.abstractmethod
|
40
|
-
# TODO: think about the most general interface!
|
41
|
-
# NOTE: We assume that we do not change the system for/in one engine,
|
42
|
-
# i.e. .top, .ndx, mdp-object, ...?! should go into __init__
|
43
54
|
async def prepare(self, starting_configuration: Trajectory, workdir: str,
|
44
55
|
deffnm: str) -> None:
|
56
|
+
"""
|
57
|
+
Prepare the engine to run a MD from starting_configuration.
|
58
|
+
|
59
|
+
NOTE: We assume that we do not change the system for/in one engine,
|
60
|
+
i.e. .top, .ndx, mdp-object, ...?! should go into __init__.
|
61
|
+
|
62
|
+
Parameters
|
63
|
+
----------
|
64
|
+
starting_configuration : Trajectory
|
65
|
+
The initial configuration.
|
66
|
+
workdir : str
|
67
|
+
The directory in which the MD will be performed.
|
68
|
+
deffnm : str
|
69
|
+
The standard filename to use for this MD run.
|
70
|
+
"""
|
45
71
|
raise NotImplementedError
|
46
72
|
|
47
73
|
@abc.abstractmethod
|
48
|
-
# TODO: should this be a classmethod?
|
49
74
|
#@classmethod
|
50
75
|
async def prepare_from_files(self, workdir: str, deffnm: str) -> None:
|
51
|
-
|
52
|
-
|
76
|
+
"""
|
77
|
+
Prepare the engine to continue a previously stopped simulation starting
|
78
|
+
with the last trajectory part in workdir that is compatible with deffnm.
|
79
|
+
|
80
|
+
NOTE: This can not be a classmethod (reliably) because we set top/ndx/
|
81
|
+
mdconfig/etc in '__init__'.
|
82
|
+
|
83
|
+
Parameters
|
84
|
+
----------
|
85
|
+
workdir : str
|
86
|
+
The directory in which the MD will be/ was previously performed.
|
87
|
+
deffnm : str
|
88
|
+
The standard filename to use for this MD run.
|
89
|
+
"""
|
53
90
|
raise NotImplementedError
|
54
91
|
|
55
92
|
@abc.abstractmethod
|
56
|
-
async def run_walltime(self, walltime: float
|
57
|
-
|
58
|
-
|
93
|
+
async def run_walltime(self, walltime: float, max_steps: int | None = None,
|
94
|
+
) -> Trajectory | None:
|
95
|
+
"""
|
96
|
+
Run for specified walltime.
|
97
|
+
|
98
|
+
NOTE: Must be possible to run this multiple times after preparing once!
|
99
|
+
|
100
|
+
It is optional (but recommended if possible) for engines to respect the
|
101
|
+
``max_steps`` argument. I.e. terminating upon reaching max_steps is
|
102
|
+
optional and no code should rely on it. See the :meth:`run_steps` if a
|
103
|
+
fixed number of integration steps is required.
|
104
|
+
|
105
|
+
Return None if no integration is needed because max_steps integration
|
106
|
+
steps have already been performed.
|
107
|
+
|
108
|
+
Parameters
|
109
|
+
----------
|
110
|
+
walltime : float
|
111
|
+
Walltime in hours.
|
112
|
+
max_steps : int | None, optional
|
113
|
+
If not None, (optionally) terminate when max_steps integration steps
|
114
|
+
in total are reached, also if this is before walltime is reached.
|
115
|
+
By default None.
|
116
|
+
|
117
|
+
Returns
|
118
|
+
-------
|
119
|
+
Trajectory | None
|
120
|
+
"""
|
59
121
|
raise NotImplementedError
|
60
122
|
|
61
123
|
@abc.abstractmethod
|
62
124
|
async def run_steps(self, nsteps: int,
|
63
|
-
steps_per_part: bool = False) -> Trajectory:
|
64
|
-
|
65
|
-
|
66
|
-
|
125
|
+
steps_per_part: bool = False) -> Trajectory | None:
|
126
|
+
"""
|
127
|
+
Run for a specified number of integration steps.
|
128
|
+
|
129
|
+
Return None if no integration is needed because nsteps integration steps
|
130
|
+
have already been performed.
|
131
|
+
|
132
|
+
NOTE: Make sure we can run multiple times after preparing once!
|
133
|
+
|
134
|
+
Parameters
|
135
|
+
----------
|
136
|
+
nsteps : int
|
137
|
+
steps_per_part : bool, optional
|
138
|
+
Count nsteps for this part/run or in total, by default False
|
139
|
+
|
140
|
+
Returns
|
141
|
+
-------
|
142
|
+
Trajectory | None
|
143
|
+
"""
|
67
144
|
raise NotImplementedError
|
68
145
|
|
69
|
-
@
|
70
|
-
|
71
|
-
|
72
|
-
|
146
|
+
@property
|
147
|
+
@abc.abstractmethod
|
148
|
+
def current_trajectory(self) -> Trajectory | None:
|
149
|
+
"""
|
150
|
+
Return current trajectory: Trajectory or None.
|
151
|
+
"""
|
152
|
+
# if we return a Trajectory it is either what we are working on atm
|
73
153
|
# or the trajectory we finished last
|
74
154
|
raise NotImplementedError
|
75
155
|
|
76
|
-
@
|
156
|
+
@property
|
157
|
+
@abc.abstractmethod
|
77
158
|
def output_traj_type(self) -> str:
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
# as it must be available at the classlevel too
|
82
|
-
# so cls.output_traj_type must also be the string
|
83
|
-
# If you want/need to check the values (i.e. you would like to
|
84
|
-
# execute code like in a property) have a look at the descriptor
|
85
|
-
# implementation in gromacs/mdengine.py which checks for allowed
|
86
|
-
# values (at least when set on an instance) but is accesible from
|
87
|
-
# the class level too, e.g. like a 'classproperty' (which is not
|
88
|
-
# a thing in python)
|
89
|
-
raise NotImplementedError
|
159
|
+
"""
|
160
|
+
Return a string with the ending (without ".") of the trajectory type this
|
161
|
+
engine uses.
|
90
162
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
163
|
+
NOTE: This should not be implemented as a property in subclasses as it
|
164
|
+
must be available at the classlevel too, i.e. cls.output_traj_type must
|
165
|
+
also return the string.
|
166
|
+
So this should just be overwritten with a string with the correct value,
|
167
|
+
or if your engine supports multiple output_traj_types you should have a
|
168
|
+
look at the descriptor implementation in asyncmd/tools.py (and, e.g.,
|
169
|
+
used in asyncmd/gromacs/mdengine.py), which checks for allowed values
|
170
|
+
(at least when set on an instance) but is accessible from the class
|
171
|
+
level too, i.e. like a 'classproperty' (which is not a thing in python).
|
172
|
+
"""
|
96
173
|
raise NotImplementedError
|
97
174
|
|
98
|
-
@
|
175
|
+
@property
|
176
|
+
@abc.abstractmethod
|
99
177
|
def steps_done(self) -> int:
|
178
|
+
"""
|
179
|
+
Return the number of integration steps this engine has performed in total.
|
180
|
+
"""
|
100
181
|
raise NotImplementedError
|