Source code for tcc_python_scripts.file_readers.atom

""" Module for reading and writing snapshots from and to LAMMPS (.atom) file formats."""

import io
import numpy
import pandas
from tcc_python_scripts.file_readers.snapshot import stream_safe_open, NoSnapshotError, Snapshot


[docs]class AtomSnapshot(Snapshot): """Snapshot of a system of particles in LAMMPS (.atom) file format. Interface defined in parent class Snapshot. Further documentation can be found there. """ def _read(self, path_or_file): """Read a snapshot from a file, overwriting any existing data. Args: path_or_file: file stream or path to read snapshot from Raises: NoSnapshotError: if could not read from file RuntimeException: if did not recognise file format """ with stream_safe_open(path_or_file) as f: self.particle_coordinates = numpy.empty((0, 0)) self.time = self.box = None while True: item = f.readline().split() if not item: raise NoSnapshotError assert item[0] == 'ITEM:' # Timestep within a trajectory. if item[1] == 'TIMESTEP': self.time = int(f.readline()) # Number of atoms in the header elif ' '.join(item[1:4]) == 'NUMBER OF ATOMS': num_particles = int(f.readline()) self.particle_coordinates = numpy.empty((num_particles, self.dimensionality)) # Item containing the bounding box. elif ' '.join(item[1:3]) == 'BOX BOUNDS': num_spatial_dimensions = len(item[3:]) self.particle_coordinates = numpy.empty((self.num_particles, num_spatial_dimensions)) self.box = numpy.zeros((num_spatial_dimensions, 2), dtype=numpy.float64) for dimension in range(num_spatial_dimensions): boundary = f.readline().split() self.box[dimension][:] = [float(b) for b in boundary] # Main table contains the per-atom data. Should come at the end. elif item[1] == 'ATOMS': assert self.num_particles > 0 assert 1 <= self.dimensionality <= 3 assert self.box is not None headings = item[2:] assert 'id' in headings assert 'x' or 'xs' in headings particle_buffer = io.StringIO() for i in range(self.num_particles): particle_buffer.write(f.readline()) particle_buffer.seek(0) table = pandas.read_table(particle_buffer, index_col=0, sep='\s+', names=headings, nrows=self.num_particles) if 'xs' in headings: cols = ['xs', 'ys', 'zs'][:self.dimensionality] self.particle_coordinates = table[cols].values.copy('c').astype(numpy.float64) for dimension in range(self.dimensionality): side_length = self.box[dimension][1] - self.box[dimension][0] self.particle_coordinates[:, dimension] *= side_length self.particle_coordinates[:, dimension] += self.box[dimension][0] else: cols = ['x', 'y', 'z'][:self.dimensionality] self.particle_coordinates = table[cols].values.copy('c').astype(numpy.float64) self.species = numpy.array(table['type']) return else: raise RuntimeError('unknown header: %s' % item) def __str__(self): """String representation of the snapshot in LAMMPS (.atom) format""" string_buffer = io.StringIO() string_buffer.write('ITEM: TIMESTEP\n%r\n' % self.time) string_buffer.write('ITEM: NUMBER OF ATOMS\n%r\n' % self.num_particles) string_buffer.write('ITEM: BOX BOUNDS') for _ in self.box: string_buffer.write(' pp') string_buffer.write('\n') for dim in self.box: if len(dim) == 2: string_buffer.write('%.8f %.8f\n' % tuple(dim)) else: string_buffer.write('0 %.8f\n' % dim) string_buffer.write('ITEM: ATOMS id type x y z') for i, (name, x) in enumerate(zip(self.species, self.particle_coordinates)): string_buffer.write('\n') string_buffer.write('%r %s' % (i, name)) for coord in x: string_buffer.write(' %.8f' % coord) return string_buffer.getvalue()
[docs]def read(file_name): """ Returns a generator that reads one or more snapshots from a .atom file. Args: file_name: Name of .atom file to read. Returns: A generator object that generates Snapshots. """ return AtomSnapshot.read_trajectory(file_name)