Source code for scm.plams.recipes.bandfragment

from scm.plams.core.functions import log
from scm.plams.interfaces.adfsuite.ams import AMSJob
from scm.plams.core.settings import Settings
from scm.plams.recipes.adffragment import ADFFragmentJob, ADFFragmentResults
from scm.plams.tools.units import Units
from scm.plams.core.errors import FileError
from scm.plams.core.functions import add_to_instance


from os.path import join as opj
from os.path import abspath, isdir, basename, relpath
from os import symlink
from typing import Dict, List, Union, Optional

__all__ = ["BANDFragmentJob", "BANDFragmentResults"]


[docs]class BANDFragmentResults(ADFFragmentResults): """Subclass of |ADFFragmentResults| for BAND calculations."""
[docs] def get_energy_decomposition(self, unit="au") -> Dict[str, float]: """Get the energy decomposition of the fragment calculation. Args: unit (str, optional): The unit of the energy. Defaults to 'au'. Returns: Dict[str, float]: The energy decomposition. """ res = self.job.full.results res1 = self.job.f1.results res2 = self.job.f2.results ret = {} def try_grep_result(key, pattern, match_position=-1, split_position=2): match = res.grep_output(pattern) if match: match = Units.convert(float(match[match_position].split()[split_position]), "au", unit) ret[key] = match try_grep_result("E_int", "E_int", match_position=-2) try_grep_result("E_int_disp", "E_disp") try_grep_result("E_Pauli", "E_Pauli") try_grep_result("E_elstat", "E_elstat") try_grep_result("E_orb", "E_orb") ret["E_1"] = res1.get_energy(unit=unit) ret["E_2"] = res2.get_energy(unit=unit) if hasattr(self.job, "f1_opt"): ret["E_1_opt"] = self.job.f1_opt.results.get_energy(unit=unit) ret["E_prep_1"] = ret["E_1"] - ret["E_1_opt"] if hasattr(self.job, "f2_opt"): ret["E_2_opt"] = self.job.f2_opt.results.get_energy(unit=unit) ret["E_prep_2"] = ret["E_2"] - ret["E_2_opt"] if ("E_1_opt" in ret) and ("E_2_opt" in ret): ret["E_prep"] = ret["E_prep_1"] + ret["E_prep_2"] ret["E_bond"] = ret["E_int"] + ret["E_prep"] return ret
[docs]class BANDFragmentJob(ADFFragmentJob): _result_type = BANDFragmentResults
[docs] def prerun(self) -> None: """Creates the fragment jobs.""" self.f1 = AMSJob(name="frag1", molecule=self.fragment1, settings=self.settings) self.f2 = AMSJob(name="frag2", molecule=self.fragment2, settings=self.settings) self.children += [self.f1, self.f2]
[docs] def new_children(self) -> Union[None, List[AMSJob]]: """After the first round, add the full job to the children list.""" if hasattr(self, "full"): return None else: # create the correct mapping settings for the full job if "fragment" in self.full_settings.input.band: log( "Fragment already present in full_settings. Assuming that the user has already set up the mapping. Skipping the mapping setup.", level=1, ) return # first fragment 1 then fragment 2 set1 = Settings() set1.atommapping = {str(i + 1): str(i + 1) for i in range(len(self.fragment1))} set2 = Settings() set2.atommapping = {str(i + 1): str(i + 1 + len(self.fragment1)) for i in range(len(self.fragment2))} set1.filename = (self.f1, "band") set2.filename = (self.f2, "band") self.full_settings.input.band.fragment = [set1, set2] # create the full job self.full = AMSJob( name="full", molecule=self.fragment1 + self.fragment2, settings=self.settings + self.full_settings ) return [self.full]
[docs] @classmethod def load_external(cls, path: str, jobname: Optional[str] = None) -> "BANDFragmentJob": """Load the results of the BANDFragmentJob job from an external path. Args: path (str): The path to the job. It should at least have the subfolders 'frag1', 'frag2' and 'full'. jobname (str, optional): The name of the job. Defaults to None. Returns: BANDFragmentJob: The job with the loaded results. """ if not isdir(path): raise FileError("Path {} does not exist, cannot load from it.".format(path)) path = abspath(path) jobname = basename(path) if jobname is None else str(jobname) job = cls(name=jobname) job.path = path job.status = "copied" job.f1 = AMSJob.load_external(opj(path, "frag1")) job.f2 = AMSJob.load_external(opj(path, "frag2")) job.full = AMSJob.load_external(opj(path, "full")) job.children = [job.f1, job.f2, job.full] if isdir(opj(path, "frag1_opt")): job.f1_opt = AMSJob.load_external(opj(path, "frag1_opt")) job.children.append(job.f1_opt) if isdir(opj(path, "frag2_opt")): job.f2_opt = AMSJob.load_external(opj(path, "frag2_opt")) job.children.append(job.f2_opt) return job
[docs]class NOCVBandFragmentJob(BANDFragmentJob): _result_type = BANDFragmentResults
[docs] def __init__(self, nocv_settings=None, **kwargs) -> None: """Create a BANDFragmentJob with final NOCV calculation. Args: nocv_settings (Settings, optional): Settings for the NOCV calculation. Defaults to None. """ super().__init__(**kwargs) self.nocv_settings = nocv_settings or Settings()
[docs] def new_children(self) -> Union[None, List[AMSJob]]: """After the first round, add the full job to the children list. After the second round, add the NOCV job to the children list.""" # new_children of BANDFragmentJob creates the full job ret = super().new_children() if ret is not None: return ret if hasattr(self, "nocv"): return None else: # add NOCV run # settings for the NOCV calculation set = self.settings + self.full_settings + self.nocv_settings # set the restart file set.input.band.restart.file = "full.rkf" # copy the fragment settings set.input.band.fragment = [fset.copy() for fset in self.full.settings.input.band.fragment] self.nocv = AMSJob(name="NOCV", molecule=self.fragment1 + self.fragment2, settings=set) self.nocv.frag_paths = [] for job in [self.f1, self.f2, self.full]: self.nocv.frag_paths.append(job.path) # edit NOCV prerun to create symlinks @add_to_instance(self.nocv) def prerun(self): """Create symlinks for the restart files.""" for i, job in enumerate(["frag1", "frag2", "full"]): rel_path = relpath(self.frag_paths[i], self.path) symlink(opj(rel_path, "band.rkf"), opj(self.path, f"{job}.rkf")) return [self.nocv]
[docs] @classmethod def load_external(cls, path: str, jobname: Optional[str] = None) -> "NOCVBandFragmentJob": """Load the results of the BANDFragmentJob job from an external path. Args: path (str): The path to the job. It should at least have the subfolders 'frag1', 'frag2', 'full' and 'NOCV'. jobname (str, optional): The name of the job. Defaults to None. Returns: NOCVBandFragmentJob: The job with the loaded results. """ job = super().load_external(path, jobname) job.nocv = AMSJob.load_external(opj(path, "NOCV")) job.children.append(job.nocv) return job