Coverage for dynasor/trajectory/extxyz_trajectory_reader.py: 90%
87 statements
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-03 19:46 +0000
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-03 19:46 +0000
1import concurrent.futures
2import numpy as np
3import io
4from itertools import count
5from typing import Optional
6from dynasor.trajectory.abstract_trajectory_reader import AbstractTrajectoryReader
7from dynasor.trajectory.abstract_trajectory_reader import get_forces_from_atoms
8from dynasor.trajectory.trajectory_frame import ReaderFrame
9from ase.io import read
10from ase import Atoms
13def frame_text_to_atoms(frame_text):
14 return read(io.StringIO(frame_text), format='extxyz')
17def iter_frame_texts(f):
18 while True:
19 natoms_line = f.readline()
20 if not natoms_line:
21 break
23 comment_line = f.readline()
24 if not comment_line: 24 ↛ 25line 24 didn't jump to line 25 because the condition on line 24 was never true
25 raise ValueError('Unexpected EOF while reading extxyz comment line')
27 try:
28 natoms = int(natoms_line.strip())
29 except ValueError:
30 raise ValueError(f'Invalid extxyz atom count line: {natoms_line!r}')
32 atom_lines = []
33 for _ in range(natoms):
34 line = f.readline()
35 if not line: 35 ↛ 36line 35 didn't jump to line 36 because the condition on line 35 was never true
36 raise ValueError('Unexpected EOF while reading extxyz atom lines')
37 atom_lines.append(line)
39 yield natoms_line + comment_line + ''.join(atom_lines)
42def iread(f, max_workers: Optional[int] = None) -> Atoms:
43 frame_iterator = iter_frame_texts(f)
44 with concurrent.futures.ProcessPoolExecutor(max_workers=max_workers) as ex:
45 buff = []
46 n_submit = ex._max_workers
48 for _ in range(n_submit):
49 try:
50 frame_text = next(frame_iterator)
51 buff.append(ex.submit(frame_text_to_atoms, frame_text))
52 except StopIteration:
53 break
55 while buff:
56 res = buff.pop(0)
58 try:
59 frame_text = next(frame_iterator)
60 buff.append(ex.submit(frame_text_to_atoms, frame_text))
61 except StopIteration:
62 pass
64 yield res.result()
67class ExtxyzTrajectoryReader(AbstractTrajectoryReader):
68 """Read extend xyz trajectory file, typically produced by GPUMD
70 This is a naive (and comparatively slow) parallel implementation which
71 relies on the ASE xyz reader.
73 Parameters
74 ----------
75 filename
76 Name of input file.
77 length_unit
78 Unit of length for the input trajectory (``'Angstrom'``, ``'nm'``, ``'pm'``, ``'fm'``).
79 time_unit
80 Unit of time for the input trajectory (``'fs'``, ``'ps'``, ``'ns'``).
81 max_workers
82 Number of working processes; defaults to ``None``, which means that the number of
83 processors on the machine is used.
84 force_unit
85 Unit of force for the input trajectory (``'eV/Angstrom'``, ``'eV/nm'``,
86 ``'kJ/mol/Angstrom'``, ``'kJ/mol/nm'``, ``'kcal/mol/Angstrom'``, ``'kcal/mol/nm'``,
87 ``'Hartree/Bohr'``).
88 """
90 def __init__(self,
91 filename: str,
92 length_unit: Optional[str] = None,
93 time_unit: Optional[str] = None,
94 max_workers: Optional[int] = None,
95 force_unit: Optional[str] = None):
97 # setup generator object
98 self._fobj = open(filename, 'r')
99 self._generator_xyz = iread(self._fobj, max_workers=max_workers)
100 self._open = True
101 self._frame_index = count(0)
103 # set up units
104 self.set_unit_scaling_factors(length_unit, time_unit, force_unit)
106 def _get_next(self):
107 try:
108 atoms = next(self._generator_xyz)
109 except StopIteration:
110 self._fobj.close()
111 self._open = False
112 raise
113 except Exception:
114 self._fobj.close()
115 self._open = False
116 raise
118 self._atom_types = np.array(list(atoms.symbols))
119 self._n_atoms = len(atoms)
120 self._cell = atoms.cell[:]
121 self._x = atoms.positions
122 if 'vel' in atoms.arrays:
123 self._v = atoms.arrays['vel']
124 else:
125 self._v = None
126 self._f = get_forces_from_atoms(atoms)
128 def __iter__(self):
129 return self
131 def close(self):
132 if not self._fobj.closed:
133 self._fobj.close()
134 self._open = False
136 def __next__(self):
137 if not self._open:
138 raise StopIteration
140 self._get_next()
142 return ReaderFrame(frame_index=next(self._frame_index),
143 n_atoms=int(self._n_atoms),
144 cell=self.x_factor * self._cell.copy('F'),
145 positions=self.x_factor * self._x,
146 velocities=None if self._v is None else self.v_factor * self._v,
147 forces=None if self._f is None else self.f_factor * self._f,
148 atom_types=self._atom_types
149 )