Source code for ase.io.runner.writer
"""RuNNer input.data support
Write files in RuNNer's input.data file format.
Contains
--------
* `write_runnerdata`: Write structures to a RuNNer input.data file.
Reference
---------
* [The online documentation of RuNNer 2.0](https://runner-suite.gitlab.io/runner2)
Contributors
------------
* Maintainer and Author: [Alexander Knoll](mailto:alexander.knoll@rub.de)
* Author: [Redouan El Haouari](mailto:redouan.elhaouari@rub.de)
"""
from typing import TextIO
from ase.atoms import Atoms
from ase.utils import writer
from .runneratoms import RuNNerAtoms, Units
def _format_atom_section(runneratoms: RuNNerAtoms, fmt: str) -> str:
"""Format the per-structure block of lines containing atom information."""
lines = []
for i, position in enumerate(runneratoms.positions):
fields = [f'{j:{fmt}}' for j in position]
fields.append(f'{runneratoms.symbols[i]:2s}')
for name, length in runneratoms.atom_layout:
atom_arr = runneratoms.atom_arrays[name][i]
if length == 1:
fields.append(f'{atom_arr:{fmt}}')
else:
assert isinstance(atom_arr, list)
fields.extend(f'{v:{fmt}}' for v in atom_arr)
if runneratoms.forces is not None:
fields.extend(f'{f:{fmt}}' for f in runneratoms.forces[i])
lines.append('atom ' + ' '.join(fields))
return '\n'.join(lines) + '\n'
def _compose_begin_line(atoms: RuNNerAtoms) -> str:
"""Compose the field names in the begin line."""
begin_fields = ['begin', 'position(3)', 'element']
if atoms.atom_arrays is not None:
for name, length in atoms.atom_layout:
if length == 1:
begin_fields.append(name)
else:
begin_fields.append(f'{name}({length})')
if atoms.forces is not None:
begin_fields.append('forces(3)')
return ' '.join(begin_fields) + '\n'
[docs]
@writer
def write_runnerdata(
outfile: TextIO,
images: list[Atoms],
comment: str = '',
fmt: str = '20.10f',
input_units: Units = Units.ASE,
) -> None:
"""Write series of ASE Atoms to a RuNNer input.data file.
For further details see the `read_runnerdata` routine.
Parameters
----------
outfile: TextIOWrapper
Python fileobj with the target input.data file.
images: array-like
List of `Atoms` objects.
comment: str
A comment message to be added to each structure.
fmt: str
A format specifier for float values.
input_units: str
The given input units. Can be 'Units.ASE' or 'Units.ATOMIC'.
Raises
------
ValueError: exception
Raised if the comment line contains newline characters.
"""
# Preprocess the comment.
comment = comment.rstrip()
if '\n' in comment:
raise ValueError('Comment line cannot contain line breaks.')
for atoms in images:
runneratoms = RuNNerAtoms.from_ase_atoms(atoms, input_units=input_units)
runneratoms.convert(Units.ATOMIC)
outfile.write(_compose_begin_line(runneratoms))
if comment:
outfile.write(f'comment {comment}\n')
for vec in runneratoms.cell:
outfile.write(
f'lattice {vec[0]:{fmt}} {vec[1]:{fmt}} {vec[2]:{fmt}}\n'
)
outfile.write(_format_atom_section(runneratoms, fmt))
if runneratoms.energy is not None:
outfile.write(f'energy {runneratoms.energy:{fmt}}\n')
if runneratoms.total_charge is not None:
outfile.write(f'charge {runneratoms.total_charge:{fmt}}\n')
outfile.write('end\n')