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.
@@ -12,9 +12,14 @@
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 various classes used for trajectory extraction and concatenation.
17
+
18
+ Most notably the FrameExtractor classes and the TrajectoryConcatenator.
19
+ """
15
20
  import os
16
21
  import abc
17
- import typing
22
+ import collections.abc
18
23
  import asyncio
19
24
  import logging
20
25
  import functools
@@ -48,8 +53,8 @@ def _is_documented_by(original):
48
53
 
49
54
  def _attach_mda_trafos_to_universe(
50
55
  universe: mda.Universe,
51
- mda_transformations: typing.Optional[list[typing.Callable]] = None,
52
- mda_transformations_setup_func: typing.Optional[typing.Callable] = None,
56
+ mda_transformations: list[collections.abc.Callable] | None = None,
57
+ mda_transformations_setup_func: collections.abc.Callable | None = None,
53
58
  ) -> mda.Universe:
54
59
  """
55
60
  Attach MDAnalysis transformations to a given universe.
@@ -64,10 +69,10 @@ def _attach_mda_trafos_to_universe(
64
69
  ----------
65
70
  universe : MDAnalysis.core.universe.Universe
66
71
  The universe to attach the transformations to.
67
- mda_transformations : typing.Optional[list[typing.Callable]], optional
72
+ mda_transformations : list[collections.abc.Callable] or None, optional
68
73
  List of MDAnalysis transformations to attach, by default None
69
- mda_transformations_setup_func : typing.Optional[typing.Callable], optional
70
- Setup function to attach MDAnalysis transformatiosn to the universe,
74
+ mda_transformations_setup_func : collections.abc.Callable or None, optional
75
+ Setup function to attach MDAnalysis transformations to the universe,
71
76
  by default None
72
77
 
73
78
  Returns
@@ -78,7 +83,7 @@ def _attach_mda_trafos_to_universe(
78
83
  Raises
79
84
  ------
80
85
  ValueError
81
- If both ``mda_transformations`` and ``mda_transformations_setupt_func``
86
+ If both ``mda_transformations`` and ``mda_transformations_setup_func``
82
87
  are given.
83
88
  """
84
89
  # NOTE: this func is used to attach the MDAnalysis transformations to
@@ -105,7 +110,9 @@ class TrajectoryConcatenator:
105
110
  and returns one trajectory containing only the selected frames in the order
106
111
  specified by the slices.
107
112
  Velocities are automatically inverted if the step of a slice is negative,
108
- this can be controlled via the invert_v_for_negative_step attribute.
113
+ this can be controlled via the ``invert_v_for_negative_step`` attribute.
114
+ Double frames are also automatically removed, which can be controlled via
115
+ the ``remove_double_frames`` attribute.
109
116
  We assume that all trajs have the same structure file and attach the
110
117
  structure of the first traj if not told otherwise.
111
118
  Note that you can pass MDAnalysis transformations to this class to
@@ -116,12 +123,20 @@ class TrajectoryConcatenator:
116
123
  ----------
117
124
  invert_v_for_negative_step : bool
118
125
  Whether to invert all momenta for segments with negative stride.
126
+ remove_double_frames : bool
127
+ Whether we should (try to) remove double frames from the concatenated
128
+ output trajectory.
129
+ Note that a simple heuristic is used to determine double frames,
130
+ frames count as double if the integration time is the same for both
131
+ frames.
119
132
  """
120
133
 
121
- def __init__(self,
122
- invert_v_for_negative_step: bool = True,
123
- mda_transformations: typing.Optional[list[typing.Callable]] = None,
124
- mda_transformations_setup_func: typing.Optional[typing.Callable] = None,
134
+ def __init__(
135
+ self,
136
+ invert_v_for_negative_step: bool = True,
137
+ remove_double_frames: bool = True, *,
138
+ mda_transformations: list[collections.abc.Callable] | None = None,
139
+ mda_transformations_setup_func: collections.abc.Callable | None = None,
125
140
  ) -> None:
126
141
  """
127
142
  Initialize a :class:`TrajectoryConcatenator`.
@@ -131,6 +146,9 @@ class TrajectoryConcatenator:
131
146
  invert_v_for_negative_step : bool, optional
132
147
  Whether to invert all momenta for segments with negative stride,
133
148
  by default True.
149
+ remove_double_frames : bool, optional
150
+ Whether we should (try to) remove double frames from the concatenated
151
+ output trajectory, by default True.
134
152
  mda_transformations : list of callables, optional
135
153
  If given will be added as a list of transformations to the
136
154
  MDAnalysis universe as
@@ -138,7 +156,8 @@ class TrajectoryConcatenator:
138
156
  See the ``mda_transformations_setup_func`` argument if your
139
157
  transformations need additional universe-dependant arguments, e.g.
140
158
  atomgroups from the universe.
141
- See https://docs.mdanalysis.org/stable/documentation_pages/trajectory_transformations.html
159
+ See
160
+ https://docs.mdanalysis.org/stable/documentation_pages/trajectory_transformations.html
142
161
  for more on MDAnalysis transformations.
143
162
  mda_transformations_setup_func: callable, optional
144
163
  If given will be called to attach user-defined MDAnalysis
@@ -149,12 +168,16 @@ class TrajectoryConcatenator:
149
168
  after defining ``list_of_trafos`` (potentially depending on the
150
169
  universe or atomgroups therein) and then finally returns the
151
170
  universe with trafos.
152
- See https://docs.mdanalysis.org/stable/documentation_pages/trajectory_transformations.html
171
+ See
172
+ https://docs.mdanalysis.org/stable/documentation_pages/trajectory_transformations.html
153
173
  for more on MDAnalysis transformations.
154
174
  """
155
175
  self.invert_v_for_negative_step = invert_v_for_negative_step
156
- if (mda_transformations is not None
157
- and mda_transformations_setup_func is not None):
176
+ self.remove_double_frames = remove_double_frames
177
+ if (
178
+ mda_transformations is not None
179
+ and mda_transformations_setup_func is not None
180
+ ):
158
181
  raise ValueError("`mda_transformations` and "
159
182
  "`mda_transformations_setup_func` are mutually "
160
183
  "exclusive, but both were given."
@@ -162,10 +185,11 @@ class TrajectoryConcatenator:
162
185
  self.mda_transformations = mda_transformations
163
186
  self.mda_transformations_setup_func = mda_transformations_setup_func
164
187
 
165
- def concatenate(self, trajs: "list[Trajectory]", slices: "list[tuple]",
166
- tra_out: str, struct_out: typing.Optional[str] = None,
188
+ def concatenate(self, trajs: list[Trajectory], slices: list[tuple],
189
+ tra_out: str, *,
190
+ struct_out: str | None = None,
167
191
  overwrite: bool = False,
168
- remove_double_frames: bool = True) -> Trajectory:
192
+ ) -> Trajectory:
169
193
  """
170
194
  Create concatenated trajectory from given trajectories and frames.
171
195
 
@@ -185,12 +209,6 @@ class TrajectoryConcatenator:
185
209
  overwrite : bool, optional
186
210
  Whether we should overwrite existing output trajectories,
187
211
  by default False.
188
- remove_double_frames : bool, optional
189
- Wheter we should try to remove double frames from the concatenated
190
- output trajectory.
191
- Note that we use a simple heuristic to determine double frames,
192
- we just check if the integration time is the same for both frames,
193
- by default True
194
212
 
195
213
  Returns
196
214
  -------
@@ -217,68 +235,31 @@ class TrajectoryConcatenator:
217
235
  )
218
236
 
219
237
  # special treatment for traj0 because we need n_atoms for the writer
220
- u0 = mda.Universe(trajs[0].structure_file, *trajs[0].trajectory_files)
221
- u0 = _attach_mda_trafos_to_universe(
222
- universe=u0,
223
- mda_transformations=self.mda_transformations,
224
- mda_transformations_setup_func=self.mda_transformations_setup_func,
225
- )
226
- start0, stop0, step0 = slices[0]
227
- if remove_double_frames:
228
- last_time_seen = None
229
- # if the file exists MDAnalysis will silently overwrite
230
- with mda.Writer(tra_out, n_atoms=u0.trajectory.n_atoms) as W:
231
- for ts in u0.trajectory[start0:stop0:step0]:
232
- if (self.invert_v_for_negative_step and step0 < 0
233
- and ts.has_velocities):
234
- u0.atoms.velocities *= -1
235
- W.write(u0.atoms)
236
- if remove_double_frames:
237
- # remember the last timestamp, so we can take it out
238
- last_time_seen = ts.data["time"]
239
- # close the trajectory file for and delete the original universe
240
- u0.trajectory.close()
241
- del u0
242
- for traj, sl in zip(trajs[1:], slices[1:]):
243
- u = mda.Universe(traj.structure_file, *traj.trajectory_files)
244
- u = _attach_mda_trafos_to_universe(
245
- universe=u,
246
- mda_transformations=self.mda_transformations,
247
- mda_transformations_setup_func=self.mda_transformations_setup_func,
248
- )
249
- start, stop, step = sl
250
- for ts in u.trajectory[start:stop:step]:
251
- if remove_double_frames and (last_time_seen is not None):
252
- if last_time_seen == ts.data["time"]:
253
- # this is a no-op, as they are they same...
254
- # last_time_seen = ts.data["time"]
255
- continue # skip this timestep/go to next iteration
256
- if (self.invert_v_for_negative_step and step < 0
257
- and ts.has_velocities):
258
- u.atoms.velocities *= -1
259
- W.write(u.atoms)
260
- if remove_double_frames:
261
- last_time_seen = ts.data["time"]
262
- # make sure MDAnalysis closes the underlying trajectory file
263
- u.trajectory.close()
264
- del u # and delete the universe just because we can
238
+ u = mda.Universe(trajs[0].structure_file, *trajs[0].trajectory_files)
239
+ last_time_seen = None
240
+ with mda.Writer(tra_out, n_atoms=u.atoms.n_atoms) as writer:
241
+ # iterate over the trajectories
242
+ for traj, sl in zip(trajs, slices):
243
+ last_time_seen = self._write_one_traj_sliced_with_writer(
244
+ traj=traj, sl=sl, writer=writer,
245
+ last_time_seen=last_time_seen,
246
+ )
265
247
  # return (file paths to) the finished trajectory
266
248
  return Trajectory(tra_out, struct_out)
267
249
 
268
250
  @_is_documented_by(concatenate)
269
251
  # pylint: disable-next=missing-function-docstring
270
- async def concatenate_async(self, trajs: "list[Trajectory]",
271
- slices: "list[tuple]", tra_out: str,
272
- struct_out: typing.Optional[str] = None,
252
+ async def concatenate_async(self, trajs: list[Trajectory],
253
+ slices: list[tuple], tra_out: str, *,
254
+ struct_out: str | None = None,
273
255
  overwrite: bool = False,
274
- remove_double_frames: bool = True) -> Trajectory:
256
+ ) -> Trajectory:
275
257
  concat_fx = functools.partial(self.concatenate,
276
258
  trajs=trajs,
277
259
  slices=slices,
278
260
  tra_out=tra_out,
279
261
  struct_out=struct_out,
280
262
  overwrite=overwrite,
281
- remove_double_frames=remove_double_frames,
282
263
  )
283
264
  loop = asyncio.get_running_loop()
284
265
  async with _SEMAPHORES["MAX_FILES_OPEN"]:
@@ -288,6 +269,55 @@ class TrajectoryConcatenator:
288
269
  ) as pool:
289
270
  return await loop.run_in_executor(pool, concat_fx)
290
271
 
272
+ def _write_one_traj_sliced_with_writer(self, traj: Trajectory, sl: tuple, *,
273
+ writer: mda.Writer,
274
+ last_time_seen: float | None,
275
+ ) -> float | None:
276
+ """
277
+ Write one Trajectory (sliced) using the given MDAnalysis writer.
278
+
279
+ Take as argument and return (the updated) ``last_time_seen`` to enable
280
+ removal of double frames over multiple Trajectories and subsequent calls
281
+ to this method.
282
+
283
+ Parameters
284
+ ----------
285
+ traj : Trajectory
286
+ The origin trajectory to iterate over (in a sliced manner).
287
+ sl : tuple
288
+ The slice defining which frames are written from ``traj``.
289
+ writer : mda.Writer
290
+ last_time_seen : float | None
291
+ Integration time of the last written/seen timestep.
292
+
293
+ Returns
294
+ -------
295
+ float | None
296
+ Updated ``last_time_seen``.
297
+ """
298
+ u = mda.Universe(traj.structure_file, *traj.trajectory_files)
299
+ u = _attach_mda_trafos_to_universe(
300
+ universe=u,
301
+ mda_transformations=self.mda_transformations,
302
+ mda_transformations_setup_func=self.mda_transformations_setup_func,
303
+ )
304
+ start, stop, step = sl
305
+ for ts in u.trajectory[start:stop:step]:
306
+ if self.remove_double_frames and (last_time_seen is not None):
307
+ if last_time_seen == ts.data["time"]:
308
+ continue
309
+ if (
310
+ self.invert_v_for_negative_step and step < 0
311
+ and ts.has_velocities
312
+ ):
313
+ u.atoms.velocities *= -1
314
+ writer.write(u.atoms)
315
+ if self.remove_double_frames:
316
+ last_time_seen = ts.data["time"]
317
+ # make sure MDAnalysis closes the underlying trajectory file
318
+ u.trajectory.close()
319
+ return last_time_seen
320
+
291
321
 
292
322
  class FrameExtractor(abc.ABC):
293
323
  """
@@ -303,9 +333,9 @@ class FrameExtractor(abc.ABC):
303
333
  # with inverted velocities, with random Maxwell-Boltzmann velocities, etc.
304
334
 
305
335
  def __init__(
306
- self,
307
- mda_transformations: typing.Optional[list[typing.Callable]] = None,
308
- mda_transformations_setup_func: typing.Optional[typing.Callable] = None,
336
+ self, *,
337
+ mda_transformations: list[collections.abc.Callable] | None = None,
338
+ mda_transformations_setup_func: collections.abc.Callable | None = None,
309
339
  ) -> None:
310
340
  """
311
341
  Initialize a :class:`FrameExtractor`.
@@ -319,7 +349,8 @@ class FrameExtractor(abc.ABC):
319
349
  See the ``mda_transformations_setup_func`` argument if your
320
350
  transformations need additional universe-dependant arguments, e.g.
321
351
  atomgroups from the universe.
322
- See https://docs.mdanalysis.org/stable/documentation_pages/trajectory_transformations.html
352
+ See
353
+ https://docs.mdanalysis.org/stable/documentation_pages/trajectory_transformations.html
323
354
  for more on MDAnalysis transformations.
324
355
  mda_transformations_setup_func: callable, optional
325
356
  If given will be called to attach user-defined MDAnalysis
@@ -330,11 +361,14 @@ class FrameExtractor(abc.ABC):
330
361
  after defining ``list_of_trafos`` (potentially depending on the
331
362
  universe or atomgroups therein) and then finally returns the
332
363
  universe with trafos.
333
- See https://docs.mdanalysis.org/stable/documentation_pages/trajectory_transformations.html
364
+ See
365
+ https://docs.mdanalysis.org/stable/documentation_pages/trajectory_transformations.html
334
366
  for more on MDAnalysis transformations.
335
367
  """
336
- if (mda_transformations is not None
337
- and mda_transformations_setup_func is not None):
368
+ if (
369
+ mda_transformations is not None
370
+ and mda_transformations_setup_func is not None
371
+ ):
338
372
  raise ValueError("`mda_transformations` and "
339
373
  "`mda_transformations_setup_func` are mutually "
340
374
  "exclusive, but both were given."
@@ -368,8 +402,9 @@ class FrameExtractor(abc.ABC):
368
402
  """
369
403
  raise NotImplementedError
370
404
 
371
- def extract(self, outfile, traj_in: Trajectory, idx: int,
372
- struct_out=None, overwrite: bool = False) -> Trajectory:
405
+ def extract(self, outfile: str, traj_in: Trajectory, idx: int, *,
406
+ struct_out: str | None = None, overwrite: bool = False,
407
+ ) -> Trajectory:
373
408
  """
374
409
  Extract a single frame from given trajectory and write it out.
375
410
 
@@ -402,11 +437,8 @@ class FrameExtractor(abc.ABC):
402
437
  FileNotFoundError
403
438
  If `struct_out` is given but does not exist.
404
439
  """
405
- # TODO: should we check that idx is an idx, i.e. an int?
406
440
  # TODO: make it possible to select a subset of atoms to write out
407
441
  # and also for modification?
408
- # TODO: should we make it possible to extract multiple frames, i.e.
409
- # enable the use of slices (and iterables of indices?)
410
442
  outfile = os.path.relpath(outfile)
411
443
  if os.path.exists(outfile) and not overwrite:
412
444
  raise FileExistsError(f"overwrite=False but outfile={outfile} exists.")
@@ -424,10 +456,10 @@ class FrameExtractor(abc.ABC):
424
456
  mda_transformations=self.mda_transformations,
425
457
  mda_transformations_setup_func=self.mda_transformations_setup_func,
426
458
  )
427
- with mda.Writer(outfile, n_atoms=u.trajectory.n_atoms) as W:
459
+ with mda.Writer(outfile, n_atoms=u.trajectory.n_atoms) as writer:
428
460
  ts = u.trajectory[idx]
429
461
  self.apply_modification(u, ts)
430
- W.write(u.atoms)
462
+ writer.write(u.atoms)
431
463
  # make sure MDAnalysis closes the underlying trajectory files
432
464
  u.trajectory.close()
433
465
  del u
@@ -435,8 +467,9 @@ class FrameExtractor(abc.ABC):
435
467
 
436
468
  @_is_documented_by(extract)
437
469
  # pylint: disable-next=missing-function-docstring
438
- async def extract_async(self, outfile, traj_in: Trajectory, idx: int,
439
- struct_out=None, overwrite: bool = False) -> Trajectory:
470
+ async def extract_async(self, outfile: str, traj_in: Trajectory, idx: int, *,
471
+ struct_out: str | None = None, overwrite: bool = False,
472
+ ) -> Trajectory:
440
473
  extract_fx = functools.partial(self.extract,
441
474
  outfile=outfile,
442
475
  traj_in=traj_in,
@@ -507,10 +540,10 @@ class RandomVelocitiesFrameExtractor(FrameExtractor):
507
540
 
508
541
  def __init__(
509
542
  self,
510
- T: float,
511
- mda_transformations: typing.Optional[list[typing.Callable]] = None,
512
- mda_transformations_setup_func: typing.Optional[typing.Callable] = None,
513
- ) -> None:
543
+ T: float, *,
544
+ mda_transformations: list[collections.abc.Callable] | None = None,
545
+ mda_transformations_setup_func: collections.abc.Callable | None = None,
546
+ ) -> None:
514
547
  """
515
548
  Initialize a :class:`RandomVelocitiesFrameExtractor`.
516
549
 
@@ -525,7 +558,8 @@ class RandomVelocitiesFrameExtractor(FrameExtractor):
525
558
  See the ``mda_transformations_setup_func`` argument if your
526
559
  transformations need additional universe-dependant arguments, e.g.
527
560
  atomgroups from the universe.
528
- See https://docs.mdanalysis.org/stable/documentation_pages/trajectory_transformations.html
561
+ See
562
+ https://docs.mdanalysis.org/stable/documentation_pages/trajectory_transformations.html
529
563
  for more on MDAnalysis transformations.
530
564
  mda_transformations_setup_func: callable, optional
531
565
  If given will be called to attach user-defined MDAnalysis
@@ -536,13 +570,15 @@ class RandomVelocitiesFrameExtractor(FrameExtractor):
536
570
  after defining ``list_of_trafos`` (potentially depending on the
537
571
  universe or atomgroups therein) and then finally returns the
538
572
  universe with trafos.
539
- See https://docs.mdanalysis.org/stable/documentation_pages/trajectory_transformations.html
573
+ See
574
+ https://docs.mdanalysis.org/stable/documentation_pages/trajectory_transformations.html
540
575
  for more on MDAnalysis transformations.
541
576
  """
542
577
  super().__init__(
543
578
  mda_transformations=mda_transformations,
544
579
  mda_transformations_setup_func=mda_transformations_setup_func,
545
580
  )
581
+ # pylint: disable-next=invalid-name
546
582
  self.T = T # in K
547
583
  self._rng = np.random.default_rng()
548
584