asyncmd 0.3.2__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 +7 -17
- asyncmd/gromacs/mdengine.py +448 -424
- asyncmd/gromacs/utils.py +40 -23
- asyncmd/mdconfig.py +55 -165
- asyncmd/mdengine.py +120 -39
- asyncmd/slurm.py +210 -77
- asyncmd/tools.py +284 -5
- asyncmd/trajectory/__init__.py +19 -1
- asyncmd/trajectory/convert.py +133 -97
- asyncmd/trajectory/functionwrapper.py +211 -159
- asyncmd/trajectory/propagate.py +308 -260
- 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.2.dist-info → asyncmd-0.4.0.dist-info}/WHEEL +1 -1
- asyncmd-0.3.2.dist-info/METADATA +0 -179
- asyncmd-0.3.2.dist-info/RECORD +0 -23
- {asyncmd-0.3.2.dist-info → asyncmd-0.4.0.dist-info/licenses}/LICENSE +0 -0
- {asyncmd-0.3.2.dist-info → asyncmd-0.4.0.dist-info}/top_level.txt +0 -0
asyncmd/tools.py
CHANGED
@@ -12,8 +12,33 @@
|
|
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 functions and classes (re)used internally in asyncmd.
|
17
|
+
|
18
|
+
These functions and classes are not (thought to be) exposed to the users but
|
19
|
+
instead intended to be (re)used in newly added asyncmd code.
|
20
|
+
This is also not the place for MD-related utility functions, for this see utils.py
|
21
|
+
|
22
|
+
Currently in here are:
|
23
|
+
|
24
|
+
- ensure_executable_available
|
25
|
+
- remove_file_if_exist and remove_file_if_exist_async
|
26
|
+
- attach_kwargs_to_object: a function to attach kwargs to an object as properties
|
27
|
+
or attributes. This does type checking and warns when previously unset things
|
28
|
+
are set. It is used, e.g., in the GmxEngine and SlurmProcess classes.
|
29
|
+
- DescriptorWithDefaultOnInstanceAndClass and DescriptorOutputTrajType: two descriptor
|
30
|
+
classes to make default values accessible on the class level but still enable checks
|
31
|
+
when setting on the instance level (like a property), used in the GmxEngine classes
|
32
|
+
but could/should be useful for any MDEngine class
|
33
|
+
- FlagChangeList (and its typed sibling): lists with some sugar to remember if
|
34
|
+
their content has changed
|
35
|
+
|
36
|
+
"""
|
37
|
+
import collections
|
15
38
|
import os
|
16
39
|
import shutil
|
40
|
+
import logging
|
41
|
+
import typing
|
17
42
|
import aiofiles
|
18
43
|
|
19
44
|
|
@@ -45,11 +70,11 @@ def ensure_executable_available(executable: str) -> str:
|
|
45
70
|
executable = os.path.abspath(executable)
|
46
71
|
if not os.access(executable, os.X_OK):
|
47
72
|
raise ValueError(f"{executable} must be executable.")
|
48
|
-
elif shutil.which(executable) is not None:
|
73
|
+
elif (which_exe := shutil.which(executable)) is not None:
|
49
74
|
# see if we find it in $PATH
|
50
|
-
executable =
|
75
|
+
executable = which_exe
|
51
76
|
else:
|
52
|
-
raise ValueError(f"{executable} must be an existing path or
|
77
|
+
raise ValueError(f"{executable} must be an existing path or accessible "
|
53
78
|
+ "via the $PATH environment variable.")
|
54
79
|
return executable
|
55
80
|
|
@@ -66,7 +91,6 @@ def remove_file_if_exist(f: str):
|
|
66
91
|
try:
|
67
92
|
os.remove(f)
|
68
93
|
except FileNotFoundError:
|
69
|
-
# TODO: should we info/warn if the file is not there?
|
70
94
|
pass
|
71
95
|
|
72
96
|
|
@@ -82,5 +106,260 @@ async def remove_file_if_exist_async(f: str):
|
|
82
106
|
try:
|
83
107
|
await aiofiles.os.remove(f)
|
84
108
|
except FileNotFoundError:
|
85
|
-
# TODO: should we info/warn if the file is not there?
|
86
109
|
pass
|
110
|
+
|
111
|
+
|
112
|
+
def attach_kwargs_to_object(obj, *, logger: logging.Logger,
|
113
|
+
**kwargs
|
114
|
+
) -> None:
|
115
|
+
"""
|
116
|
+
Set all kwargs as object attributes/properties, error on mismatching type.
|
117
|
+
|
118
|
+
Warn when we set an unknown (i.e. previously undefined attribute/property)
|
119
|
+
|
120
|
+
Parameters
|
121
|
+
----------
|
122
|
+
obj : object
|
123
|
+
The object to attach the kwargs to.
|
124
|
+
logger: logging.Logger
|
125
|
+
The logger to use for logging.
|
126
|
+
**kwargs : dict
|
127
|
+
Zero to N keyword arguments.
|
128
|
+
"""
|
129
|
+
dval = object()
|
130
|
+
for kwarg, value in kwargs.items():
|
131
|
+
if (cval := getattr(obj, kwarg, dval)) is not dval:
|
132
|
+
if isinstance(value, type(cval)):
|
133
|
+
# value is of same type as default so set it
|
134
|
+
setattr(obj, kwarg, value)
|
135
|
+
else:
|
136
|
+
raise TypeError(f"Setting attribute {kwarg} with "
|
137
|
+
+ f"mismatching type ({type(value)}). "
|
138
|
+
+ f" Default type is {type(cval)}."
|
139
|
+
)
|
140
|
+
else:
|
141
|
+
# not previously defined, so warn that we ignore it
|
142
|
+
logger.warning("Ignoring unknown keyword-argument %s.", kwarg)
|
143
|
+
|
144
|
+
|
145
|
+
class DescriptorWithDefaultOnInstanceAndClass:
|
146
|
+
"""
|
147
|
+
A descriptor that makes the (default) value of the private attribute
|
148
|
+
``_name`` of the class it is attached to accessible as ``name`` on both the
|
149
|
+
class and the instance level.
|
150
|
+
Accessing the default value works from the class-level, i.e. without
|
151
|
+
instantiating the object, but note that setting on the class level
|
152
|
+
overwrites the descriptor and does not call ``__set__``.
|
153
|
+
Setting from an instance calls __set__ and therefore only sets the attribute
|
154
|
+
for the given instance (and also runs potential checks done in ``__set__``).
|
155
|
+
Also see the python docs:
|
156
|
+
https://docs.python.org/3/howto/descriptor.html#customized-names
|
157
|
+
"""
|
158
|
+
private_name: str
|
159
|
+
|
160
|
+
def __set_name__(self, owner, name: str) -> None:
|
161
|
+
self.private_name = "_" + name
|
162
|
+
|
163
|
+
def __get__(self, obj, objtype=None) -> typing.Any:
|
164
|
+
if obj is None:
|
165
|
+
# I (hejung) think if obj is None objtype will always be set
|
166
|
+
# to the class of the obj
|
167
|
+
obj = objtype
|
168
|
+
val = getattr(obj, self.private_name)
|
169
|
+
return val
|
170
|
+
|
171
|
+
def __set__(self, obj, val) -> None:
|
172
|
+
setattr(obj, self.private_name, val)
|
173
|
+
|
174
|
+
|
175
|
+
class DescriptorOutputTrajType(DescriptorWithDefaultOnInstanceAndClass):
|
176
|
+
"""
|
177
|
+
Check the value given is in the set of allowed values before setting.
|
178
|
+
|
179
|
+
Used to check ``output_traj_type`` of MDEngines for consistency when setting.
|
180
|
+
"""
|
181
|
+
# set of allowed values, e.g., trajectory file endings (without "." and all lower case)
|
182
|
+
ALLOWED_VALUES: set[str] = set()
|
183
|
+
|
184
|
+
def __set_name__(self, owner, name: str) -> None:
|
185
|
+
if not self.ALLOWED_VALUES:
|
186
|
+
# make sure we can only instantiate with ALLOWED_VALUES set,
|
187
|
+
# i.e. make this class a sort of ABC :)
|
188
|
+
raise NotImplementedError(f"Can not instantiate {type(self)} " # pragma: no cover
|
189
|
+
"without allowed trajectory types set. "
|
190
|
+
"Set ``ALLOWED_VALUES`` to a set of strings.")
|
191
|
+
super().__set_name__(owner, name)
|
192
|
+
|
193
|
+
def __set__(self, obj, val: str) -> None:
|
194
|
+
if (val := val.lower()) not in self.ALLOWED_VALUES:
|
195
|
+
raise ValueError("output_traj_type must be one of "
|
196
|
+
+ f"{self.ALLOWED_VALUES}, but was {val}."
|
197
|
+
)
|
198
|
+
super().__set__(obj, val)
|
199
|
+
|
200
|
+
def __get__(self, obj, objtype=None) -> str:
|
201
|
+
return super().__get__(obj=obj, objtype=objtype)
|
202
|
+
|
203
|
+
|
204
|
+
class FlagChangeList(collections.abc.MutableSequence):
|
205
|
+
"""A list that knows if it has been changed after initializing."""
|
206
|
+
|
207
|
+
def __init__(self, data: collections.abc.Iterable) -> None:
|
208
|
+
"""
|
209
|
+
Initialize a `FlagChangeList`.
|
210
|
+
|
211
|
+
Parameters
|
212
|
+
----------
|
213
|
+
data : Iterable
|
214
|
+
The data this `FlagChangeList` will hold.
|
215
|
+
|
216
|
+
Raises
|
217
|
+
------
|
218
|
+
TypeError
|
219
|
+
Raised when data is not an :class:`Iterable`.
|
220
|
+
"""
|
221
|
+
self._data = list(data)
|
222
|
+
self._changed = False
|
223
|
+
|
224
|
+
@property
|
225
|
+
def changed(self) -> bool:
|
226
|
+
"""
|
227
|
+
Whether this `FlagChangeList` has been modified since creation.
|
228
|
+
|
229
|
+
Returns
|
230
|
+
-------
|
231
|
+
bool
|
232
|
+
"""
|
233
|
+
return self._changed
|
234
|
+
|
235
|
+
def __repr__(self) -> str: # pragma: no cover
|
236
|
+
return self._data.__repr__()
|
237
|
+
|
238
|
+
def __getitem__(self, index: int | slice) -> typing.Any:
|
239
|
+
return self._data.__getitem__(index)
|
240
|
+
|
241
|
+
def __len__(self) -> int:
|
242
|
+
return self._data.__len__()
|
243
|
+
|
244
|
+
def __setitem__(self, index: int | slice, value) -> None:
|
245
|
+
self._data.__setitem__(index, value)
|
246
|
+
self._changed = True
|
247
|
+
|
248
|
+
def __delitem__(self, index: int | slice) -> None:
|
249
|
+
self._data.__delitem__(index)
|
250
|
+
self._changed = True
|
251
|
+
|
252
|
+
def insert(self, index: int, value: typing.Any):
|
253
|
+
"""
|
254
|
+
Insert `value` at position given by `index`.
|
255
|
+
|
256
|
+
Parameters
|
257
|
+
----------
|
258
|
+
index : int
|
259
|
+
The index of the new value in the `FlagChangeList`.
|
260
|
+
value : typing.Any
|
261
|
+
The value to insert into this `FlagChangeList`.
|
262
|
+
"""
|
263
|
+
self._data.insert(index, value)
|
264
|
+
self._changed = True
|
265
|
+
|
266
|
+
def __add__(self, other: collections.abc.Iterable):
|
267
|
+
return FlagChangeList(data=self._data + list(other))
|
268
|
+
|
269
|
+
def __iadd__(self, other: collections.abc.Iterable):
|
270
|
+
for val in other:
|
271
|
+
self.append(val)
|
272
|
+
return self
|
273
|
+
|
274
|
+
|
275
|
+
class TypedFlagChangeList(FlagChangeList):
|
276
|
+
"""
|
277
|
+
A :class:`FlagChangeList` with an ensured type for individual list items.
|
278
|
+
|
279
|
+
Note that single strings are not treated as Iterable, i.e. (as opposed to
|
280
|
+
a "normal" list) `TypedFlagChangeList(data="abc")` will result in
|
281
|
+
`data=["abc"]` (and not `data=["a", "b", "c"]`).
|
282
|
+
"""
|
283
|
+
|
284
|
+
def __init__(self, data: collections.abc.Iterable, dtype: type) -> None:
|
285
|
+
"""
|
286
|
+
Initialize a `TypedFlagChangeList`.
|
287
|
+
|
288
|
+
Parameters
|
289
|
+
----------
|
290
|
+
data : Iterable
|
291
|
+
(Initial) data for this `TypedFlagChangeList`.
|
292
|
+
dtype : Callable datatype
|
293
|
+
The datatype for all entries in this `TypedFlagChangeList`. Will be
|
294
|
+
called on every value separately and is expected to convert to the
|
295
|
+
desired datatype.
|
296
|
+
"""
|
297
|
+
self._dtype = dtype # set first to use in _convert_type method
|
298
|
+
data = self._ensure_iterable(data)
|
299
|
+
typed_data = [self._convert_type(v, index=i)
|
300
|
+
for i, v in enumerate(data)]
|
301
|
+
super().__init__(data=typed_data)
|
302
|
+
|
303
|
+
@property
|
304
|
+
def dtype(self) -> type:
|
305
|
+
"""
|
306
|
+
All values in this `TypedFlagChangeList` are converted to dtype.
|
307
|
+
|
308
|
+
Returns
|
309
|
+
-------
|
310
|
+
type
|
311
|
+
"""
|
312
|
+
return self._dtype
|
313
|
+
|
314
|
+
def _ensure_iterable(self, data) -> collections.abc.Iterable:
|
315
|
+
if getattr(data, '__iter__', None) is None:
|
316
|
+
# convenience for singular options,
|
317
|
+
# if it has no iter attribute we assume it is the only item
|
318
|
+
data = [data]
|
319
|
+
elif isinstance(data, str):
|
320
|
+
# strings have an iter but we still do not want to split them into
|
321
|
+
# single letters, so just put a list around
|
322
|
+
data = [data]
|
323
|
+
return data
|
324
|
+
|
325
|
+
def _convert_type(self, value,
|
326
|
+
index: int | slice | None = None) -> list:
|
327
|
+
# here we ignore index, but passing it should in principal make it
|
328
|
+
# possible to use different dtypes for different indices
|
329
|
+
if isinstance(index, int):
|
330
|
+
return self._dtype(value)
|
331
|
+
return [self._dtype(v) for v in value]
|
332
|
+
|
333
|
+
def __setitem__(self, index: int | slice, value) -> None:
|
334
|
+
typed_value = self._convert_type(value, index=index)
|
335
|
+
self._data.__setitem__(index, typed_value)
|
336
|
+
self._changed = True
|
337
|
+
|
338
|
+
def insert(self, index: int, value) -> None:
|
339
|
+
"""
|
340
|
+
Insert `value` at position given by `index`.
|
341
|
+
|
342
|
+
Parameters
|
343
|
+
----------
|
344
|
+
index : int
|
345
|
+
The index of the new value in the `TypedFlagChangeList`.
|
346
|
+
value : typing.Any
|
347
|
+
The value to insert into this `TypedFlagChangeList`.
|
348
|
+
"""
|
349
|
+
typed_value = self._convert_type(value, index=index)
|
350
|
+
self._data.insert(index, typed_value)
|
351
|
+
self._changed = True
|
352
|
+
|
353
|
+
def __add__(self, other: collections.abc.Iterable):
|
354
|
+
# cast other to an iterable as we expect it (excluding the strings)
|
355
|
+
other = self._ensure_iterable(other)
|
356
|
+
ret = TypedFlagChangeList(data=self._data + list(other),
|
357
|
+
dtype=self._dtype)
|
358
|
+
return ret
|
359
|
+
|
360
|
+
def __iadd__(self, other: collections.abc.Iterable):
|
361
|
+
# cast other to an iterable as we expect it (excluding the strings)
|
362
|
+
other = self._ensure_iterable(other)
|
363
|
+
for val in other:
|
364
|
+
self.append(val)
|
365
|
+
return self
|
asyncmd/trajectory/__init__.py
CHANGED
@@ -12,13 +12,31 @@
|
|
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 classes and functions for engine-agnostic but trajectory-related operations.
|
17
|
+
|
18
|
+
All user-facing classes and functions are reexported here for convenience.
|
19
|
+
This includes:
|
20
|
+
|
21
|
+
- the TrajectoryFunctionWrapper classes for CV value calculation and caching,
|
22
|
+
- the Conditional/InParts TrajectoryPropagator classes for propagation of MD in
|
23
|
+
parts and/or until a condition is reached (and related functions),
|
24
|
+
- and classes for extracting and concatenating trajectories (FrameExtractors
|
25
|
+
and TrajectoryConcatenator)
|
26
|
+
|
27
|
+
"""
|
28
|
+
from .convert import (NoModificationFrameExtractor,
|
29
|
+
InvertedVelocitiesFrameExtractor,
|
30
|
+
RandomVelocitiesFrameExtractor,
|
31
|
+
TrajectoryConcatenator,
|
32
|
+
)
|
15
33
|
from .functionwrapper import (PyTrajectoryFunctionWrapper,
|
16
34
|
SlurmTrajectoryFunctionWrapper,
|
17
35
|
)
|
18
36
|
from .propagate import (ConditionalTrajectoryPropagator,
|
19
37
|
TrajectoryPropagatorUntilAnyState,
|
20
38
|
InPartsTrajectoryPropagator,
|
21
|
-
|
39
|
+
construct_tp_from_plus_and_minus_traj_segments,
|
22
40
|
)
|
23
41
|
from .trajectory import (_forget_trajectory,
|
24
42
|
_forget_all_trajectories,
|