Coverage for dynasor/trajectory/mdanalysis_trajectory_reader.py: 97%
74 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-10 08:27 +0000
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-10 08:27 +0000
1from itertools import count
2from typing import Optional
3from dynasor.trajectory.abstract_trajectory_reader import AbstractTrajectoryReader
4from dynasor.trajectory.trajectory_frame import ReaderFrame
5from dynasor.logging_tools import logger
6import MDAnalysis as mda
7import mmap
8import warnings
11class MDAnalysisTrajectoryReader(AbstractTrajectoryReader):
12 """ Read a trajectory using the MDAnalysis Python library.
14 Parameters
15 ----------
16 filename
17 Name of input file.
18 trajectory_format
19 Type of trajectory. See MDAnalysis for the available formats.
20 length_unit
21 Unit of length for the input trajectory (``'Angstrom'``, ``'nm'``, ``'pm'``, ``'fm'``).
22 time_unit
23 Unit of time for the input trajectory (``'fs'``, ``'ps'``, ``'ns'``).
24 """
26 def __init__(self,
27 filename: str,
28 trajectory_format: str,
29 length_unit: Optional[str] = None,
30 time_unit: Optional[str] = None):
32 self._open = True
33 self._first_called = False
35 with warnings.catch_warnings():
36 warnings.filterwarnings('ignore', category=UserWarning,
37 message='Guessed all Masses to 1.0')
38 warnings.filterwarnings('ignore', category=UserWarning,
39 message='Reader has no dt information, set to 1.0 ps')
40 u = mda.Universe(filename, format=trajectory_format, convert_units=False)
41 self._frame_index = count(0)
42 self._trajectory = u.trajectory
44 # netCDF is the only format read here that MDAnalysis maps into memory rather
45 # than streams, and the map is never shrunk as the file is read.
46 self._mmap = None
47 if hasattr(mmap, 'MADV_DONTNEED'): 47 ↛ 51line 47 didn't jump to line 51 because the condition on line 47 was always true
48 self._mmap = getattr(getattr(self._trajectory, 'trjfile', None), '_mm', None)
50 # Set atomic_indices dict, if possible
51 try:
52 self._atom_types = u.atoms.types
53 except mda.exceptions.NoDataError:
54 self._atom_types = None
56 trajectory_length_unit = self._trajectory.units['length']
57 trajectory_time_unit = self._trajectory.units['time']
59 if length_unit is not None and length_unit not in self.lengthunits_to_Angstrom_table:
60 raise ValueError(f'Specified length unit {length_unit} is not an available option.')
61 if time_unit is not None and time_unit not in self.timeunits_to_fs_table: 61 ↛ 62line 61 didn't jump to line 62 because the condition on line 61 was never true
62 raise ValueError(f'Specified time unit {time_unit} is not an available option.')
64 if length_unit is None:
65 length_unit = trajectory_length_unit or 'Angstrom'
66 logger.info(f'Using length unit {length_unit} for the trajectory.')
67 if time_unit is None:
68 time_unit = trajectory_time_unit or 'fs'
69 logger.info(f'Using time unit {time_unit} for the trajectory.')
71 length_scaling = mda.units.get_conversion_factor('length', length_unit, 'Angstrom')
72 time_scaling = mda.units.get_conversion_factor('time', time_unit, 'fs')
74 def convert_units(ts):
75 ts.positions *= length_scaling
76 ts.triclinic_dimensions *= length_scaling
77 if ts.has_velocities:
78 ts.velocities *= length_scaling / time_scaling
79 return ts
80 self._trajectory.add_transformations(convert_units)
82 def _get_next(self):
83 with warnings.catch_warnings():
84 warnings.filterwarnings('ignore', category=UserWarning,
85 message='Reader has no dt information, set to 1.0 ps')
86 if self._first_called:
87 self._trajectory.next()
88 else:
89 self._first_called = True
90 self._positions = self._trajectory.ts.positions
91 self._cell = self._trajectory.ts.triclinic_dimensions
92 self._n_atoms = self._trajectory.ts.n_atoms
93 if self._trajectory.ts.has_velocities:
94 self._velocities = self._trajectory.ts.velocities
95 else:
96 self._velocities = None
98 if self._mmap is not None:
99 # This frame has been copied out of the map, so drop the pages it was read
100 # from. Without this the resident set grows by every page touched and ends
101 # up holding the entire trajectory, which is routinely tens of GB.
102 self._mmap.madvise(mmap.MADV_DONTNEED)
104 def __iter__(self):
105 """ Iterates through the trajectory file, frame by frame. """
106 return self
108 def __next__(self):
109 """ Gets next trajectory frame. """
110 if not self._open:
111 raise StopIteration
113 self._get_next()
115 if self._velocities is not None:
116 frame = ReaderFrame(frame_index=next(self._frame_index),
117 cell=self._cell,
118 n_atoms=self._n_atoms,
119 positions=self._positions.copy(),
120 velocities=self._velocities.copy(),
121 atom_types=self._atom_types
122 )
123 else:
124 frame = ReaderFrame(frame_index=next(self._frame_index),
125 cell=self._cell,
126 n_atoms=self._n_atoms,
127 positions=self._positions.copy(),
128 atom_types=self._atom_types
129 )
131 return frame
133 def close(self):
134 """ Closes down, release resources etc. """
135 if self._open:
136 self._trajectory.close()
137 self._open = False