"""VASP calculation output utilities.This module provides the :func:`load_calculation_outputs`and :func:`get_output_atoms` functions for retrievingcalculation outputs and output atoms from the directoryof a VASP calculation.Example: from pathlib import Path from autojob.calculation.vasp import vasp outputs = vasp.load_calculation_outputs(Path.cwd()) atoms = vasp.get_output_atoms(Path.cwd())"""importloggingimportpathlibfromtypingimportAnyfromtypingimportTypedDictfromxml.etreeimportElementTreefromaseimportAtomsimportase.iofromemmet.core.tasksimportTaskDocfromemmet.core.tasksimportTaskStatefromautojobimportSETTINGSfromautojob.calculationimportcopy_atom_metadatalogger=logging.getLogger(__name__)ALTERNATE_OUTPUT_STRUCTURES=("relax.traj","vasprun.xml","CONTCAR")FILES_TO_CARRYOVER=("CHGCAR","WAVECAR")VOLUMETRIC_FILES=("CHGCAR","LOCPOT","AECCAR0","AECCAR1","AECCAR2")
[docs]defload_calculation_outputs(dir_name:str|pathlib.Path,)->dict[str,Any]:"""Load VASP calculation outputs from a directory. Args: dir_name: The directory from which to load VASP outputs. Returns: A dictionary with, at minimum, the required keys to initialize a :class:`autojob.calculation.calculation.Calculation` but also with same keys as an instance of :class:`emmet.core.tasks.OutputDoc`. """logger.info(f"Loading VASP calculation outputs from {dir_name}")doc=TaskDoc.from_directory(dir_name)outputs=doc.output.model_dump()ifdoc.outputelse{}_=outputs.pop("structure",None)analysis=doc.analysis.model_dump()vasp_objects=doc.vasp_objectslogger.debug(f"Successfully loaded VASP calculation outputs from {dir_name}")return{**outputs,"analysis":analysis,"calculation_objects":vasp_objects,"converged":doc.state==TaskState.SUCCESS,}
# TODO: Unit testdef_reorder_atoms(output_atoms:Atoms,dir_name:str|pathlib.Path)->Atoms:"""Creates a new Atoms object reordered according to ase-sort.dat. This function assumes that the Atoms object passed is ordered in accordance to the POSCAR/POTCAR. """logger.debug("Reordering atoms")sort_file=pathlib.Path(dir_name).joinpath("ase-sort.dat")withpathlib.Path(sort_file).open(mode="r",encoding="utf-8")asfile:lines=file.readlines()conversion_table=[int(line.split()[0])forlineinlines]new_ordering=[conversion_table[atom.index]foratominoutput_atoms]atoms=[output_atoms[i]foriinnew_ordering]logger.debug("Successfully reordered atoms: "f"{[atom.indexforatominoutput_atoms]!r} -> {new_ordering!r}")returnAtoms(atoms,cell=output_atoms.cell,pbc=output_atoms.pbc,celldisp=output_atoms.get_celldisp(),)
[docs]defget_output_atoms(dir_name:str|pathlib.Path,alt_filename_index:int|None=None,input_atoms:Atoms|None=None,)->Atoms:"""Retrieve an Atoms object representing the output structure. This function also copies tags and constraints from the input structure in the case that the output structure must be read from a non-ASE file (e.g., vasprun.xml). Args: dir_name: The directory from which to retrieve the output structure. alt_filename_index: An integer pointing to which alternative structure file should be used. This number will be used to index `ALTERNATE_OUTPUT_STRUCTURES`. input_atoms: An Atoms object representing the corresponding input structure. Returns: An Atoms object representing the output structure. """ifalt_filename_indexisNone:alt_filename_index=0filename=SETTINGS.OUTPUT_ATOMSelse:filename=ALTERNATE_OUTPUT_STRUCTURES[alt_filename_index]alt_filename_index+=1full_filename=pathlib.Path(dir_name).joinpath(filename)logger.debug(f"Retrieving output atoms from {full_filename}")atoms=Nonetry:atoms=ase.io.read(full_filename)except(FileNotFoundError,AttributeError,ElementTree.ParseError):msg=(f"Unable to retrieve atoms from: {full_filename}.\n""File not found.")logger.warning(msg)try:atoms=get_output_atoms(dir_name=dir_name,alt_filename_index=alt_filename_index,input_atoms=input_atoms,)atoms=_reorder_atoms(output_atoms=atoms,dir_name=dir_name)copy_atom_metadata(input_atoms=input_atoms,output_atoms=atoms,)exceptIndexErroraserr:msg=(f"No output atoms found in {SETTINGS.OUTPUT_ATOMS} or "f"{ALTERNATE_OUTPUT_STRUCTURES!r}")raiseFileNotFoundError(msg)fromerrexceptFileNotFoundError:ifatomsisNone:raiselogger.warning("Unable to reorder atoms")logger.debug(f"Successfully retrieved output atoms from {full_filename}")returnatoms