"""Interconvert between .xyz and .gro files.
Examples
--------
This recipe is available from the command line via the ``FOX.recipes.xyz_to_gro`` entry point:
.. code:: bash
> FOX.recipes.xyz_to_gro file.xyz file.gro
Index
-----
.. currentmodule:: FOX.recipes
.. autosummary::
xyz_to_gro
gro_to_xyz
API
---
.. autofunction:: xyz_to_gro
.. autofunction:: gro_to_xyz
"""
from __future__ import annotations
import os
import argparse
import itertools
import warnings
import FOX
from FOX.utils import LicenseAction
from FOX.io import FileIter
__all__ = ["xyz_to_gro", "gro_to_xyz"]
[docs]def xyz_to_gro(
xyz_path: str | os.PathLike[str] | FOX.MultiMolecule,
gro_path: str | os.PathLike[str],
) -> None:
"""Convert the passed .xyz file into a .gro file.
Parameters
----------
xyz_path : path-like object
The name of the to-be read .xyz file.
gro_path : path-like object
The name of the to-be created .gro file.
"""
if isinstance(xyz_path, FOX.MultiMolecule):
mol = xyz_path
else:
mol = FOX.MultiMolecule.from_xyz(xyz_path, read_comment=True)
if mol.shape[1] != 1:
warnings.warn(
"The passed .xyz contains multiple molecules; "
"only the first will be written to the .gro file"
)
mol.as_gro(gro_path)
[docs]def gro_to_xyz(
gro_path: str | os.PathLike[str],
xyz_path: str | os.PathLike[str],
) -> None:
"""Convert the passed .xyz file into a .gro file.
Parameters
----------
gro_path : path-like object
The name of the to-be created .gro file.
xyz_path : path-like object
The name of the to-be read .xyz file.
"""
with open(gro_path, "r", encoding="utf8") as _f_inp, open(xyz_path, "w", encoding="utf8") as f_out: # noqa: E501
f_inp = FileIter(_f_inp, stripper=None)
header = next(f_inp)
atom_count_str = next(f_inp)
try:
atom_count = int(atom_count_str)
except ValueError as ex:
raise ValueError(f"Invalid atom count: {atom_count_str}") from ex
f_out.write(f"{atom_count_str}")
f_out.write(f"{header}")
try:
for item in itertools.islice(f_inp, atom_count):
x, y, z = float(item[20:28]), float(item[28:36]), float(item[36:44])
f_out.write(f"{item[10:15]} {x * 10:9.3f} {y * 10:9.3f} {z * 10:9.3f}\n")
except ValueError as ex:
raise ValueError(f"Failed to parse line {f_inp.index} in {f_inp.name!r}") from ex
def main() -> None:
parser = argparse.ArgumentParser(
usage="FOX.recipes.xyz_to_gro file.xyz file.gro",
description=__doc__ if __doc__ is None else __doc__.split("\n")[0],
)
parser.add_argument("--license", dest="license", action=LicenseAction)
parser.add_argument("--version", action="version", version=f"%(prog)s {FOX.__version__}")
parser.add_argument("xyz_path", help="The .xyz file")
parser.add_argument("gro_path", help="The .gro file")
parser.add_argument(
"--gro_to_xyz", action="store_true",
help="Whether to convert the .gro file into .xyz rather than the other way around"
)
args = parser.parse_args()
if args.gro_to_xyz:
gro_to_xyz(args.gro_path, args.xyz_path)
else:
xyz_to_gro(args.xyz_path, args.gro_path)
if __name__ == "__main__":
main()