Source code for tcc_python_scripts.file_readers.xyz
""" Module for reading and writing snapshots from and to XYZ (.xyz) file formats."""
import io
import numpy
import pandas
from tcc_python_scripts.file_readers.snapshot import stream_safe_open, NoSnapshotError, SnapshotIncompleteError, Snapshot
[docs]class XYZSnapshot(Snapshot):
"""Snapshot of a system of particles in XYZ (.xyz) file format.
Interface defined in parent class Snapshot. Further documentation can be found there.
"""
def _read(self, path_or_file):
""" Read a single XYZ snapshot from a file.
Overwrites any exisiting data in the Snaphsot object.
Raises:
NoSnapshotError if file could not be read.
Args:
path_or_file: file stream or path to read snapshot from
"""
with stream_safe_open(path_or_file) as input_file:
# Read the header - check for EOF.
number_of_particles = self._process_number_of_particles(input_file)
# Read and igore the comment line
input_file.readline()
# Use pandas to read the main table.
string_buffer = io.StringIO()
for line_number in range(number_of_particles):
line = (input_file.readline())
if len(line.split()) != 4:
raise SnapshotIncompleteError("Error reading XYZ file on line number {}".format(line_number))
string_buffer.write(line)
string_buffer.seek(0)
table = pandas.read_table(string_buffer, sep='\s+', names=('atom', 'x', 'y', 'z'), nrows=number_of_particles)
if table.shape[0] != number_of_particles:
raise SnapshotIncompleteError
self.particle_coordinates = table[['x', 'y', 'z']].values.copy('c').astype(numpy.longdouble)
self.species = table['atom'].tolist()
self.time = self.box = None
@staticmethod
def _process_number_of_particles(file):
""" Sanitises the number of particles read from an XYZ file.
Args:
an open file handle to the xyz file
Raises:
SnapshotIncompleteError if number of particles cannot be interpreted
Returns:
an integer number of particles.
"""
line = file.readline()
if not line:
raise NoSnapshotError
if len(line.split()) > 1:
raise SnapshotIncompleteError("Can't read number of particles from XYZ file.")
try:
number_of_particles = int(line)
except ValueError:
raise SnapshotIncompleteError("Can't read number of particles from XYZ file.")
if not number_of_particles > 0:
raise NoSnapshotError
return number_of_particles
def __str__(self):
"""String representation of the snapshot in XYZ (.xyz) format"""
f = io.StringIO()
# Header states number of particles (we have ignored comment line)
f.write('%d\n' % self.num_particles)
# Single component system
if type(self.species) is str:
for i in range(self.num_particles):
f.write('\n')
f.write('%s ' % self.species)
f.write(' '.join(map(str, self.particle_coordinates[i, :])))
# Handle particle species separately
else:
for i in range(self.num_particles):
f.write('\n')
f.write('%s ' % self.species[i])
f.write(' '.join(map(str, self.particle_coordinates[i, :])))
return f.getvalue()
[docs]def read(file_name):
""" Returns a generator that reads one or more snapshots from an xyz file.
Args:
file_name: Name of XYZ file to read.
Returns:
A generator object that generates Snapshots.
"""
return XYZSnapshot.read_trajectory(file_name)
[docs]def write_snapshot(output_filename, snapshot_list):
""" Write one or more snapshots to the disk.
Args:
output_filename: The filename to write the coordinates to.
snapshot_list: A list of XYZSnapshots.
"""
for snapshot in snapshot_list:
snapshot.write(output_filename)
[docs]def write(output_filename, particle_coordinates, species=None):
""" Write a single configuration to the disk.
Args:
output_filename: The filename to write the coordinates to.
particle_coordinates: A list of particle coordinates
species: A list of particle species. Defaults all particles to 'A' if not provided.
"""
snapshot = XYZSnapshot(particle_coordinates, species=species)
snapshot.write(output_filename)