Source code for nanotools.ext.io.ase

"""Reads RESCU+ files.

Read multiple structures and results from ``rescuplus`` output files. Read
structures from ``rescuplus`` input files.

"""

from __future__ import annotations

from pathlib import Path
from typing import TextIO

from ase import Atoms
from ase.calculators.singlepoint import SinglePointDFTCalculator, SinglePointKPoint
from ase.utils.plugins import ExternalIOFormat

from nanotools import TotalEnergy
from nanotools.utils import update_recursive

extin = "rsi"
extout = "rso"

rescuplus_in_format = ExternalIOFormat(
    desc="rescuplus infile",
    code="1F",
    module="nanotools.ext.io.ase",
    ext=extin,
)

rescuplus_out_format = ExternalIOFormat(
    desc="rescuplus outfile",
    code="1F",
    module="nanotools.ext.io.ase",
    ext=extout,
)


[docs] def write_rescuplus_in(fid, atoms, **kwargs): """ Create an input file for ``rescuplus``. Units are automatically converted from (angstrom/ev to bohr/hartree). Args: fid (file) A file like object to write the input file to. atoms (Atoms) A single atomistic configuration to write to `fid`. input_data (dict) A nested dictionary with input parameters for ``rescuplus``. """ if "input_data" in kwargs.keys(): input_data = kwargs["input_data"] else: input_data = dict() # update positions and cell shape atoms_dict = dict( system=dict( atoms=dict( positions=(atoms.positions, "angstrom"), formula="".join( atoms.get_chemical_symbols(), ), ), cell=dict(avec=(atoms.cell.array, "angstrom")), ) ) input_data = update_recursive(input_data, atoms_dict) # write file calc = TotalEnergy(**input_data) calc.system.atoms.formula = calc.system.atoms.get_formula(format="short") calc.write(fid, units="atomic")
[docs] def read_rescuplus_out(fileobj: TextIO | Path) -> Atoms: """Reads RESCU+ output files. The atomistic configurations as well as results (energy, force, stress, magnetic moments) of the calculation are read for all configurations within the output JSON file. Args: fileobj (file|str): A file object or filename Returns: structure (Atoms): The Atoms has a SinglePointCalculator attached with any results parsed from the file. """ if isinstance(fileobj, str): fileobj = open(fileobj, "rU") ecalc = TotalEnergy.read(fileobj, units="si") atoms = ecalc.system.to_ase_atoms() # Extract calculation results energy = ecalc.energy.etot.to("eV").m efermi = ecalc.energy.efermi.to("eV").m eigenvalues = ecalc.energy.eigenvalues.to("eV").m forces = None if ecalc.energy.forces_return: forces = ecalc.energy.forces.to("eV / angstrom").m stress = None if ecalc.energy.stress_return: stress = -ecalc.energy.stress.to("eV / angstrom ** 3").m # K-points ibzkpts = ecalc.system.kpoint.fractional_coordinates weights = ecalc.system.kpoint.kwght # magnetic properties magnetic_moments = None kpts = [] for s in range(eigenvalues.shape[-1]): for w, k, e in zip(weights, ibzkpts, eigenvalues[:, :, s]): kpt = SinglePointKPoint(w, s, k, eps_n=e) kpts.append(kpt) # Put everything together calc = SinglePointDFTCalculator( atoms, energy=energy, forces=forces, stress=stress, efermi=efermi, magmoms=magnetic_moments, ibzkpts=ibzkpts, bzkpts=ibzkpts, ) calc.kpts = kpts atoms.calc = calc return atoms, ecalc