|
"""Functions to read and write XYZ files.""" |
|
|
|
__all__ = [ |
|
'write_frame_xyz', |
|
'read_frame_xyz', |
|
] |
|
|
|
import numpy as np |
|
|
|
from ..constants import angstrom |
|
from .utilities import Frame, register_io |
|
|
|
|
|
@register_io('xyz', 'read', 'xyz') |
|
def read_frame_xyz(f_in, name_data='positions', unit=angstrom): |
|
"""Read one frame of XYZ format from an open file. |
|
|
|
Arguments: |
|
f_in: open file in XYZ format |
|
name_data: what quantity to take the XYZ data as |
|
unit: unit to scale data by, multiplicative factor in atomic units |
|
|
|
Returns: |
|
`Frame` object or `None` if there is no more data |
|
""" |
|
|
|
|
|
line_begin = f_in.readline() |
|
|
|
|
|
if not line_begin: |
|
return None |
|
|
|
|
|
natoms = int(line_begin) |
|
|
|
|
|
comment = f_in.readline().rstrip() |
|
|
|
names = [] |
|
data = [] |
|
for _ in range(natoms): |
|
line = f_in.readline() |
|
if line.strip() == '': |
|
raise ValueError('Unexpected data in file.') |
|
items = line.split() |
|
names.append(items[0]) |
|
data.append([float(item) for item in items[1:4]]) |
|
data = np.array(data) * unit |
|
|
|
|
|
if len(names) != natoms: |
|
raise ValueError('Inconsistent number of atoms in XYZ file.') |
|
|
|
|
|
if name_data == 'positions': |
|
positions = data |
|
forces = None |
|
elif name_data == 'forces': |
|
positions = None |
|
forces = data |
|
else: |
|
raise ValueError(f'Unsupported `name_data`: {name_data}. Expected "positions" or "forces".') |
|
|
|
return Frame(names=names, positions=positions, comment=comment, energy=None, forces=forces) |
|
|
|
|
|
@register_io('xyz', 'write', 'xyz') |
|
def write_frame_xyz(f_out, frame, unit=angstrom): |
|
"""Print a single frame into an open XYZ file. |
|
|
|
This is currently hard-coded to write positions, if we ever need to write forces |
|
or something else, it needs generalizing. |
|
""" |
|
|
|
|
|
if (frame.positions is None) or (frame.names is None): |
|
raise ValueError('Frame does not contain required properties.') |
|
|
|
fmt_one = '{:13.6f}' |
|
fmt_prop = '{:6s} ' + 3*fmt_one + '\n' |
|
|
|
|
|
f_out.write(f'{len(frame.names):d}\n') |
|
if frame.comment is not None: |
|
f_out.write(f'{frame.comment:s}\n') |
|
else: |
|
f_out.write('\n') |
|
|
|
data = frame.positions / unit |
|
|
|
|
|
for i, name in enumerate(frame.names): |
|
f_out.write(fmt_prop.format(name, *data[i])) |
|
|