root / ase / io / trajectory.py @ 11
Historique | Voir | Annoter | Télécharger (11,7 ko)
1 | 1 | tkerber | import os |
---|---|---|---|
2 | 1 | tkerber | import cPickle as pickle |
3 | 1 | tkerber | |
4 | 1 | tkerber | from ase.calculators.singlepoint import SinglePointCalculator |
5 | 1 | tkerber | from ase.atoms import Atoms |
6 | 1 | tkerber | from ase.parallel import rank, barrier |
7 | 1 | tkerber | from ase.utils import devnull |
8 | 1 | tkerber | |
9 | 1 | tkerber | |
10 | 1 | tkerber | class PickleTrajectory: |
11 | 1 | tkerber | "Reads/writes Atoms objects into a .traj file."
|
12 | 1 | tkerber | # Per default, write these quantities
|
13 | 1 | tkerber | write_energy = True
|
14 | 1 | tkerber | write_forces = True
|
15 | 1 | tkerber | write_stress = True
|
16 | 1 | tkerber | write_magmoms = True
|
17 | 1 | tkerber | write_momenta = True
|
18 | 1 | tkerber | |
19 | 1 | tkerber | def __init__(self, filename, mode='r', atoms=None, master=None, |
20 | 1 | tkerber | backup=True):
|
21 | 1 | tkerber | """A PickleTrajectory can be created in read, write or append mode.
|
22 | 1 | tkerber |
|
23 | 1 | tkerber | Parameters:
|
24 | 1 | tkerber |
|
25 | 1 | tkerber | filename:
|
26 | 1 | tkerber | The name of the parameter file. Should end in .traj.
|
27 | 1 | tkerber |
|
28 | 1 | tkerber | mode='r':
|
29 | 1 | tkerber | The mode.
|
30 | 1 | tkerber |
|
31 | 1 | tkerber | 'r' is read mode, the file should already exist, and
|
32 | 1 | tkerber | no atoms argument should be specified.
|
33 | 1 | tkerber |
|
34 | 1 | tkerber | 'w' is write mode. If the file already exists, is it
|
35 | 1 | tkerber | renamed by appending .bak to the file name. The atoms
|
36 | 1 | tkerber | argument specifies the Atoms object to be written to the
|
37 | 1 | tkerber | file, if not given it must instead be given as an argument
|
38 | 1 | tkerber | to the write() method.
|
39 | 1 | tkerber |
|
40 | 1 | tkerber | 'a' is append mode. It acts a write mode, except that
|
41 | 1 | tkerber | data is appended to a preexisting file.
|
42 | 1 | tkerber |
|
43 | 1 | tkerber | atoms=None:
|
44 | 1 | tkerber | The Atoms object to be written in write or append mode.
|
45 | 1 | tkerber |
|
46 | 1 | tkerber | master=None:
|
47 | 1 | tkerber | Controls which process does the actual writing. The
|
48 | 1 | tkerber | default is that process number 0 does this. If this
|
49 | 1 | tkerber | argument is given, processes where it is True will write.
|
50 | 1 | tkerber |
|
51 | 1 | tkerber | backup=True:
|
52 | 1 | tkerber | Use backup=False to disable renaming of an existing file.
|
53 | 1 | tkerber | """
|
54 | 1 | tkerber | self.offsets = []
|
55 | 1 | tkerber | if master is None: |
56 | 1 | tkerber | master = (rank == 0)
|
57 | 1 | tkerber | self.master = master
|
58 | 1 | tkerber | self.backup = backup
|
59 | 1 | tkerber | self.set_atoms(atoms)
|
60 | 1 | tkerber | self.open(filename, mode)
|
61 | 1 | tkerber | |
62 | 1 | tkerber | def open(self, filename, mode): |
63 | 1 | tkerber | """Opens the file.
|
64 | 1 | tkerber |
|
65 | 1 | tkerber | For internal use only.
|
66 | 1 | tkerber | """
|
67 | 1 | tkerber | self.fd = filename
|
68 | 1 | tkerber | if mode == 'r': |
69 | 1 | tkerber | if isinstance(filename, str): |
70 | 1 | tkerber | self.fd = open(filename, 'rb') |
71 | 1 | tkerber | self.read_header()
|
72 | 1 | tkerber | elif mode == 'a': |
73 | 1 | tkerber | exists = True
|
74 | 1 | tkerber | if isinstance(filename, str): |
75 | 1 | tkerber | exists = os.path.isfile(filename) |
76 | 1 | tkerber | if exists:
|
77 | 1 | tkerber | self.fd = open(filename, 'rb') |
78 | 1 | tkerber | self.read_header()
|
79 | 1 | tkerber | self.fd.close()
|
80 | 1 | tkerber | barrier() |
81 | 1 | tkerber | if self.master: |
82 | 1 | tkerber | self.fd = open(filename, 'ab+') |
83 | 1 | tkerber | else:
|
84 | 1 | tkerber | self.fd = devnull
|
85 | 1 | tkerber | elif mode == 'w': |
86 | 1 | tkerber | if self.master: |
87 | 1 | tkerber | if isinstance(filename, str): |
88 | 1 | tkerber | if self.backup and os.path.isfile(filename): |
89 | 1 | tkerber | os.rename(filename, filename + '.bak')
|
90 | 1 | tkerber | self.fd = open(filename, 'wb') |
91 | 1 | tkerber | else:
|
92 | 1 | tkerber | self.fd = devnull
|
93 | 1 | tkerber | else:
|
94 | 1 | tkerber | raise ValueError('mode must be "r", "w" or "a".') |
95 | 1 | tkerber | |
96 | 1 | tkerber | def set_atoms(self, atoms=None): |
97 | 1 | tkerber | """Associate an Atoms object with the trajectory.
|
98 | 1 | tkerber |
|
99 | 1 | tkerber | Mostly for internal use.
|
100 | 1 | tkerber | """
|
101 | 1 | tkerber | if atoms is not None and not hasattr(atoms, 'get_positions'): |
102 | 1 | tkerber | raise TypeError('"atoms" argument is not an Atoms object.') |
103 | 1 | tkerber | self.atoms = atoms
|
104 | 1 | tkerber | |
105 | 1 | tkerber | def read_header(self): |
106 | 1 | tkerber | self.fd.seek(0) |
107 | 1 | tkerber | try:
|
108 | 1 | tkerber | if self.fd.read(len('PickleTrajectory')) != 'PickleTrajectory': |
109 | 1 | tkerber | raise IOError('This is not a trajectory file!') |
110 | 1 | tkerber | d = pickle.load(self.fd)
|
111 | 1 | tkerber | except EOFError: |
112 | 1 | tkerber | raise EOFError('Bad trajectory file.') |
113 | 1 | tkerber | self.pbc = d['pbc'] |
114 | 1 | tkerber | self.numbers = d['numbers'] |
115 | 1 | tkerber | self.tags = d.get('tags') |
116 | 1 | tkerber | self.masses = d.get('masses') |
117 | 1 | tkerber | self.constraints = d['constraints'] |
118 | 1 | tkerber | self.offsets.append(self.fd.tell()) |
119 | 1 | tkerber | |
120 | 1 | tkerber | def write(self, atoms=None): |
121 | 1 | tkerber | """Write the atoms to the file.
|
122 | 1 | tkerber |
|
123 | 1 | tkerber | If the atoms argument is not given, the atoms object specified
|
124 | 1 | tkerber | when creating the trajectory object is used.
|
125 | 1 | tkerber | """
|
126 | 1 | tkerber | if atoms is None: |
127 | 1 | tkerber | atoms = self.atoms
|
128 | 1 | tkerber | |
129 | 1 | tkerber | if hasattr(atoms, 'interpolate'): |
130 | 1 | tkerber | # seems to be a NEB
|
131 | 1 | tkerber | neb = atoms |
132 | 1 | tkerber | try:
|
133 | 1 | tkerber | neb.get_energies_and_forces(all=True)
|
134 | 1 | tkerber | except AttributeError: |
135 | 1 | tkerber | pass
|
136 | 1 | tkerber | for image in neb.images: |
137 | 1 | tkerber | self.write(image)
|
138 | 1 | tkerber | return
|
139 | 1 | tkerber | |
140 | 1 | tkerber | if len(self.offsets) == 0: |
141 | 1 | tkerber | self.write_header(atoms)
|
142 | 1 | tkerber | |
143 | 1 | tkerber | if atoms.has('momenta'): |
144 | 1 | tkerber | momenta = atoms.get_momenta() |
145 | 1 | tkerber | else:
|
146 | 1 | tkerber | momenta = None
|
147 | 1 | tkerber | |
148 | 1 | tkerber | d = {'positions': atoms.get_positions(),
|
149 | 1 | tkerber | 'cell': atoms.get_cell(),
|
150 | 1 | tkerber | 'momenta': momenta}
|
151 | 1 | tkerber | |
152 | 1 | tkerber | if atoms.get_calculator() is not None: |
153 | 1 | tkerber | if self.write_energy: |
154 | 1 | tkerber | d['energy'] = atoms.get_potential_energy()
|
155 | 1 | tkerber | if self.write_forces: |
156 | 1 | tkerber | assert self.write_energy |
157 | 1 | tkerber | try:
|
158 | 1 | tkerber | d['forces'] = atoms.get_forces(apply_constraint=False) |
159 | 1 | tkerber | except NotImplementedError: |
160 | 1 | tkerber | pass
|
161 | 1 | tkerber | if self.write_stress: |
162 | 1 | tkerber | assert self.write_energy |
163 | 1 | tkerber | try:
|
164 | 1 | tkerber | d['stress'] = atoms.get_stress()
|
165 | 1 | tkerber | except NotImplementedError: |
166 | 1 | tkerber | pass
|
167 | 1 | tkerber | |
168 | 1 | tkerber | if self.write_magmoms: |
169 | 1 | tkerber | try:
|
170 | 1 | tkerber | if atoms.calc.get_spin_polarized():
|
171 | 1 | tkerber | d['magmoms'] = atoms.get_magnetic_moments()
|
172 | 1 | tkerber | except (NotImplementedError, AttributeError): |
173 | 1 | tkerber | pass
|
174 | 1 | tkerber | |
175 | 1 | tkerber | if 'magmoms' not in d and atoms.has('magmoms'): |
176 | 1 | tkerber | d['magmoms'] = atoms.get_initial_magnetic_moments()
|
177 | 1 | tkerber | |
178 | 1 | tkerber | if self.master: |
179 | 1 | tkerber | pickle.dump(d, self.fd, protocol=-1) |
180 | 1 | tkerber | self.fd.flush()
|
181 | 1 | tkerber | self.offsets.append(self.fd.tell()) |
182 | 1 | tkerber | |
183 | 1 | tkerber | def write_header(self, atoms): |
184 | 1 | tkerber | self.fd.write('PickleTrajectory') |
185 | 1 | tkerber | if atoms.has('tags'): |
186 | 1 | tkerber | tags = atoms.get_tags() |
187 | 1 | tkerber | else:
|
188 | 1 | tkerber | tags = None
|
189 | 1 | tkerber | if atoms.has('masses'): |
190 | 1 | tkerber | masses = atoms.get_masses() |
191 | 1 | tkerber | else:
|
192 | 1 | tkerber | masses = None
|
193 | 1 | tkerber | d = {'pbc': atoms.get_pbc(),
|
194 | 1 | tkerber | 'numbers': atoms.get_atomic_numbers(),
|
195 | 1 | tkerber | 'tags': tags,
|
196 | 1 | tkerber | 'masses': masses,
|
197 | 1 | tkerber | 'constraints': atoms.constraints}
|
198 | 1 | tkerber | pickle.dump(d, self.fd, protocol=-1) |
199 | 1 | tkerber | self.header_written = True |
200 | 1 | tkerber | self.offsets.append(self.fd.tell()) |
201 | 1 | tkerber | |
202 | 1 | tkerber | def close(self): |
203 | 1 | tkerber | """Close the trajectory file."""
|
204 | 1 | tkerber | self.fd.close()
|
205 | 1 | tkerber | |
206 | 1 | tkerber | def __getitem__(self, i=-1): |
207 | 1 | tkerber | N = len(self.offsets) |
208 | 1 | tkerber | if 0 <= i < N: |
209 | 1 | tkerber | self.fd.seek(self.offsets[i]) |
210 | 1 | tkerber | try:
|
211 | 1 | tkerber | d = pickle.load(self.fd)
|
212 | 1 | tkerber | except EOFError: |
213 | 1 | tkerber | raise IndexError |
214 | 1 | tkerber | if i == N - 1: |
215 | 1 | tkerber | self.offsets.append(self.fd.tell()) |
216 | 1 | tkerber | try:
|
217 | 1 | tkerber | magmoms = d['magmoms']
|
218 | 1 | tkerber | except KeyError: |
219 | 1 | tkerber | magmoms = None
|
220 | 1 | tkerber | atoms = Atoms(positions=d['positions'],
|
221 | 1 | tkerber | numbers=self.numbers,
|
222 | 1 | tkerber | cell=d['cell'],
|
223 | 1 | tkerber | momenta=d['momenta'],
|
224 | 1 | tkerber | magmoms=magmoms, |
225 | 1 | tkerber | tags=self.tags,
|
226 | 1 | tkerber | masses=self.masses,
|
227 | 1 | tkerber | pbc=self.pbc,
|
228 | 1 | tkerber | constraint=[c.copy() for c in self.constraints]) |
229 | 1 | tkerber | if 'energy' in d: |
230 | 1 | tkerber | calc = SinglePointCalculator( |
231 | 1 | tkerber | d.get('energy', None), d.get('forces', None), |
232 | 1 | tkerber | d.get('stress', None), magmoms, atoms) |
233 | 1 | tkerber | atoms.set_calculator(calc) |
234 | 1 | tkerber | return atoms
|
235 | 1 | tkerber | |
236 | 1 | tkerber | if i >= N:
|
237 | 1 | tkerber | for j in range(N - 1, i + 1): |
238 | 1 | tkerber | atoms = self[j]
|
239 | 1 | tkerber | return atoms
|
240 | 1 | tkerber | |
241 | 1 | tkerber | i = len(self) + i |
242 | 1 | tkerber | if i < 0: |
243 | 1 | tkerber | raise IndexError('Trajectory index out of range.') |
244 | 1 | tkerber | return self[i] |
245 | 1 | tkerber | |
246 | 1 | tkerber | def __len__(self): |
247 | 1 | tkerber | N = len(self.offsets) - 1 |
248 | 1 | tkerber | while True: |
249 | 1 | tkerber | self.fd.seek(self.offsets[N]) |
250 | 1 | tkerber | try:
|
251 | 1 | tkerber | pickle.load(self.fd)
|
252 | 1 | tkerber | except EOFError: |
253 | 1 | tkerber | return N
|
254 | 1 | tkerber | self.offsets.append(self.fd.tell()) |
255 | 1 | tkerber | N += 1
|
256 | 1 | tkerber | |
257 | 1 | tkerber | def __iter__(self): |
258 | 1 | tkerber | del self.offsets[1:] |
259 | 1 | tkerber | return self |
260 | 1 | tkerber | |
261 | 1 | tkerber | def next(self): |
262 | 1 | tkerber | try:
|
263 | 1 | tkerber | return self[len(self.offsets) - 1] |
264 | 1 | tkerber | except IndexError: |
265 | 1 | tkerber | raise StopIteration |
266 | 1 | tkerber | |
267 | 1 | tkerber | def guess_offsets(self): |
268 | 1 | tkerber | size = os.path.getsize(self.fd.name)
|
269 | 1 | tkerber | |
270 | 1 | tkerber | while True: |
271 | 1 | tkerber | self.fd.seek(self.offsets[-1]) |
272 | 1 | tkerber | try:
|
273 | 1 | tkerber | pickle.load(self.fd)
|
274 | 1 | tkerber | except:
|
275 | 1 | tkerber | raise EOFError('Damaged trajectory file.') |
276 | 1 | tkerber | else:
|
277 | 1 | tkerber | self.offsets.append(self.fd.tell()) |
278 | 1 | tkerber | |
279 | 1 | tkerber | if self.offsets[-1] >= size: |
280 | 1 | tkerber | break
|
281 | 1 | tkerber | |
282 | 1 | tkerber | if len(self.offsets) > 2: |
283 | 1 | tkerber | step1 = self.offsets[-1] - self.offsets[-2] |
284 | 1 | tkerber | step2 = self.offsets[-2] - self.offsets[-3] |
285 | 1 | tkerber | |
286 | 1 | tkerber | if step1 == step2:
|
287 | 1 | tkerber | m = int((size - self.offsets[-1]) / step1) - 1 |
288 | 1 | tkerber | |
289 | 1 | tkerber | while m > 1: |
290 | 1 | tkerber | self.fd.seek(self.offsets[-1] + m * step1) |
291 | 1 | tkerber | try:
|
292 | 1 | tkerber | pickle.load(self.fd)
|
293 | 1 | tkerber | except:
|
294 | 1 | tkerber | m = m / 2
|
295 | 1 | tkerber | else:
|
296 | 1 | tkerber | for i in range(m): |
297 | 1 | tkerber | self.offsets.append(self.offsets[-1] + step1) |
298 | 1 | tkerber | m = 0
|
299 | 1 | tkerber | |
300 | 1 | tkerber | return
|
301 | 1 | tkerber | |
302 | 1 | tkerber | def read_trajectory(filename, index=-1): |
303 | 1 | tkerber | traj = PickleTrajectory(filename, mode='r')
|
304 | 1 | tkerber | |
305 | 1 | tkerber | if isinstance(index, int): |
306 | 1 | tkerber | return traj[index]
|
307 | 1 | tkerber | else:
|
308 | 1 | tkerber | # Here, we try to read only the configurations we need to read
|
309 | 1 | tkerber | # and len(traj) should only be called if we need to as it will
|
310 | 1 | tkerber | # read all configurations!
|
311 | 1 | tkerber | |
312 | 1 | tkerber | # XXX there must be a simpler way?
|
313 | 1 | tkerber | step = index.step or 1 |
314 | 1 | tkerber | if step > 0: |
315 | 1 | tkerber | start = index.start or 0 |
316 | 1 | tkerber | if start < 0: |
317 | 1 | tkerber | start += len(traj)
|
318 | 1 | tkerber | stop = index.stop or len(traj) |
319 | 1 | tkerber | if stop < 0: |
320 | 1 | tkerber | stop += len(traj)
|
321 | 1 | tkerber | else:
|
322 | 1 | tkerber | if index.start is None: |
323 | 1 | tkerber | start = len(traj) - 1 |
324 | 1 | tkerber | else:
|
325 | 1 | tkerber | start = index.start |
326 | 1 | tkerber | if start < 0: |
327 | 1 | tkerber | start += len(traj)
|
328 | 1 | tkerber | if index.stop is None: |
329 | 1 | tkerber | stop = -1
|
330 | 1 | tkerber | else:
|
331 | 1 | tkerber | stop = index.stop |
332 | 1 | tkerber | if stop < 0: |
333 | 1 | tkerber | stop += len(traj)
|
334 | 1 | tkerber | |
335 | 1 | tkerber | return [traj[i] for i in range(start, stop, step)] |
336 | 1 | tkerber | |
337 | 1 | tkerber | def write_trajectory(filename, images): |
338 | 1 | tkerber | """Write image(s) to trajectory.
|
339 | 1 | tkerber |
|
340 | 1 | tkerber | Write also energy, forces, and stress if they are already
|
341 | 1 | tkerber | calculated."""
|
342 | 1 | tkerber | |
343 | 1 | tkerber | traj = PickleTrajectory(filename, mode='w')
|
344 | 1 | tkerber | |
345 | 1 | tkerber | if not isinstance(images, (list, tuple)): |
346 | 1 | tkerber | images = [images] |
347 | 1 | tkerber | |
348 | 1 | tkerber | for atoms in images: |
349 | 1 | tkerber | # Avoid potentially expensive calculations:
|
350 | 1 | tkerber | calc = atoms.get_calculator() |
351 | 1 | tkerber | if calc is not None: |
352 | 1 | tkerber | if hasattr(calc, 'calculation_required'): |
353 | 1 | tkerber | if calc.calculation_required(atoms, ['energy']): |
354 | 1 | tkerber | traj.write_energy = False
|
355 | 1 | tkerber | if calc.calculation_required(atoms, ['forces']): |
356 | 1 | tkerber | traj.write_forces = False
|
357 | 1 | tkerber | if calc.calculation_required(atoms, ['stress']): |
358 | 1 | tkerber | traj.write_stress = False
|
359 | 1 | tkerber | if calc.calculation_required(atoms, ['magmoms']): |
360 | 1 | tkerber | traj.write_magmoms = False
|
361 | 1 | tkerber | else:
|
362 | 1 | tkerber | traj.write_energy = False
|
363 | 1 | tkerber | traj.write_forces = False
|
364 | 1 | tkerber | traj.write_stress = False
|
365 | 1 | tkerber | traj.write_magmoms = False
|
366 | 1 | tkerber | |
367 | 1 | tkerber | traj.write(atoms) |
368 | 1 | tkerber | traj.close() |