from __future__ import print_function
import warnings
from ase.calculators.singlepoint import SinglePointCalculator, all_properties
from ase.calculators.calculator import Calculator
from ase.constraints import dict2constraint
from ase.atoms import Atoms
from ase.io.aff import affopen, DummyWriter, InvalidAFFError
from ase.io.jsonio import encode, decode
from ase.io.pickletrajectory import PickleTrajectory
from ase.parallel import rank, size
__all__ = ['Trajectory', 'PickleTrajectory']
def Trajectory(filename, mode='r', atoms=None, master=None):
"""A Trajectory can be created in read, write or append mode.
Parameters:
filename:
The name of the parameter file. Should end in .traj.
mode='r':
The mode.
'r' is read mode, the file should already exist, and
no atoms argument should be specified.
'w' is write mode. If the file already exists, it is
renamed by appending .bak to the file name. The atoms
argument specifies the Atoms object to be written to the
file, if not given it must instead be given as an argument
to the write() method.
'a' is append mode. It acts a write mode, except that
data is appended to a preexisting file.
atoms=None:
The Atoms object to be written in write or append mode.
master=None:
Controls which process does the actual writing. The
default is that process number 0 does this. If this
argument is given, processes where it is True will write.
The atoms and master arguments are ignores in read mode.
"""
if mode == 'r':
return TrajectoryReader(filename)
return TrajectoryWriter(filename, mode, atoms, master=master)
[docs]class TrajectoryWriter:
"""Writes Atoms objects to a .trj file."""
def __init__(self, filename, mode='w', atoms=None, properties=None,
extra=[], master=None):
"""A Trajectory writer, in write or append mode.
Parameters:
filename:
The name of the parameter file. Should end in .traj.
mode='w':
The mode.
'w' is write mode. If the file already exists, it is
renamed by appending .bak to the file name. The atoms
argument specifies the Atoms object to be written to the
file, if not given it must instead be given as an argument
to the write() method.
'a' is append mode. It acts a write mode, except that
data is appended to a preexisting file.
atoms=None:
The Atoms object to be written in write or append mode.
properties=None:
If specified, these calculator properties are saved in the
trajectory. If not specified, all supported quantities are
saved.
master=None:
Controls which process does the actual writing. The
default is that process number 0 does this. If this
argument is given, processes where it is True will write.
"""
if master is None:
master = (rank == 0)
self.master = master
self.atoms = atoms
self.properties = properties
self.numbers = None
self.pbc = None
self.masses = None
self._open(filename, mode)
def _open(self, filename, mode):
if mode not in 'aw':
raise ValueError('mode must be "w" or "a".')
if self.master:
self.backend = affopen(filename, mode, tag='ASE-Trajectory')
if len(self.backend) > 0:
r = affopen(filename)
self.numbers = r.numbers
self.pbc = r.pbc
else:
self.backend = DummyWriter()
[docs] def write(self, atoms=None, **kwargs):
"""Write the atoms to the file.
If the atoms argument is not given, the atoms object specified
when creating the trajectory object is used.
"""
b = self.backend
if atoms is None:
atoms = self.atoms
if hasattr(atoms, 'interpolate'):
# seems to be a NEB
neb = atoms
assert not neb.parallel or size == 1
for image in neb.images:
self.write(image)
return
if len(b) == 0:
self.write_header(atoms)
else:
if (atoms.pbc != self.pbc).any():
raise ValueError('Bad periodic boundary conditions!')
elif len(atoms) != len(self.numbers):
raise ValueError('Bad number of atoms!')
elif (atoms.numbers != self.numbers).any():
raise ValueError('Bad atomic numbers!')
b.write(positions=atoms.get_positions(),
cell=atoms.get_cell().tolist())
if atoms.has('tags'):
b.write(tags=atoms.get_tags())
if atoms.has('momenta'):
b.write(momenta=atoms.get_momenta())
if atoms.has('magmoms'):
b.write(magmoms=atoms.get_initial_magnetic_moments())
if atoms.has('charges'):
b.write(charges=atoms.get_initial_charges())
calc = atoms.get_calculator()
if calc is not None:
if not isinstance(calc, Calculator):
calc = OldCalculatorWrapper(calc)
c = b.child('calculator')
c.write(name=calc.name)
for prop in all_properties:
if prop in kwargs:
x = kwargs[prop]
elif self.properties is not None and prop in self.properties:
x = calc.get_property(prop, atoms)
else:
try:
x = calc.get_property(prop, atoms,
allow_calculation=False)
except (NotImplementedError, KeyError):
# KeyError is needed for Jacapo.
x = None
if x is not None:
if prop in ['stress', 'dipole']:
x = x.tolist()
c.write(**{prop: x})
info = {}
for key, value in atoms.info.items():
try:
encode(value)
except TypeError:
warnings.warn('Skipping "{0}" info.'.format(key))
else:
info[key] = value
if info:
b.write(info=info)
b.sync()
def write_header(self, atoms):
# Atomic numbers and periodic boundary conditions are only
# written once - in the header. Store them here so that we can
# check that they are the same for all images:
self.numbers = atoms.get_atomic_numbers()
self.pbc = atoms.get_pbc()
b = self.backend
b.write(version=1,
pbc=self.pbc.tolist(),
numbers=self.numbers)
if atoms.constraints:
b.write(constraints=encode(atoms.constraints))
if atoms.has('masses'):
b.write(masses=atoms.get_masses())
[docs] def close(self):
"""Close the trajectory file."""
self.backend.close()
def __len__(self):
return len(self.backend)
[docs]class TrajectoryReader:
"""Reads/writes Atoms objects from/to a .trj file."""
def __init__(self, filename, properties=None,
extra=[], master=None):
"""A Trajectory in read mode.
Parameters:
filename:
The name of the parameter file. Traditionally ends in .traj.
"""
if master is None:
master = (rank == 0)
self.master = master
self.numbers = None
self.pbc = None
self.masses = None
self._open(filename)
def _open(self, filename):
try:
self.backend = affopen(filename, 'r')
except InvalidAFFError:
raise RuntimeError('This is not a valid ASE trajectory file. '
'If this is an old-format (version <3.9) '
'PickleTrajectory file you can convert it '
'with ase.io.trajectory.convert("%s") '
'or:\n\n $ python -m ase.io.trajectory %s'
% (filename, filename))
self._read_header()
def _read_header(self):
b = self.backend
if b.get_tag() != 'ASE-Trajectory':
raise IOError('This is not a trajectory file!')
self.pbc = b.pbc
self.numbers = b.numbers
self.masses = b.get('masses')
self.constraints = b.get('constraints', '[]')
[docs] def close(self):
"""Close the trajectory file."""
self.backend.close()
def __getitem__(self, i=-1):
b = self.backend[i]
atoms = Atoms(positions=b.positions,
numbers=self.numbers,
cell=b.cell,
masses=self.masses,
pbc=self.pbc,
info=b.get('info'),
constraint=[dict2constraint(d)
for d in decode(self.constraints)],
momenta=b.get('momenta'),
magmoms=b.get('magmoms'),
charges=b.get('charges'),
tags=b.get('tags'))
if 'calculator' in b:
results = {}
c = b.calculator
for prop in all_properties:
if prop in c:
results[prop] = c.get(prop)
calc = SinglePointCalculator(atoms, **results)
calc.name = b.calculator.name
atoms.set_calculator(calc)
return atoms
def __len__(self):
return len(self.backend)
def __iter__(self):
for i in range(len(self)):
yield self[i]
def read_trajectory(filename, index=-1):
trj = TrajectoryReader(filename)
if isinstance(index, int):
return trj[index]
else:
return [trj[i] for i in range(*index.indices(len(trj)))]
def write_trajectory(filename, images):
"""Write image(s) to trajectory."""
trj = TrajectoryWriter(filename, mode='w')
if isinstance(images, Atoms):
images = [images]
for atoms in images:
trj.write(atoms)
trj.close()
class OldCalculatorWrapper:
def __init__(self, calc):
self.calc = calc
try:
self.name = calc.name
except AttributeError:
self.name = calc.__class__.__name__.lower()
def get_property(self, prop, atoms, allow_calculation=True):
if not allow_calculation and self.calc.calculation_required(atoms,
[prop]):
return None
method = 'get_' + {'energy': 'potential_energy',
'magmom': 'magnetic_moment',
'magmoms': 'magnetic_moments',
'dipole': 'dipole_moment'}.get(prop, prop)
try:
result = getattr(self.calc, method)(atoms)
except AttributeError:
raise NotImplementedError
return result
def convert(name):
import os
t = TrajectoryWriter(name + '.new')
for atoms in PickleTrajectory(name, _warn=False):
t.write(atoms)
os.rename(name, name + '.old')
os.rename(name + '.new', name)
def main():
import optparse
parser = optparse.OptionParser(usage='python -m ase.io.pickletrajectory '
'a1.traj [a2.traj ...]',
description='Convert old trajectory '
'file(s) to new format. '
'The old file is kept as a1.traj.old.')
opts, args = parser.parse_args()
for name in args:
convert(name)
if __name__ == '__main__':
main()