Source code for atomate2.qchem.flows.core

"""Define core QChem flows."""

from __future__ import annotations

from copy import deepcopy
from dataclasses import dataclass, field
from typing import TYPE_CHECKING

from jobflow import Flow, Maker, Response, job

from atomate2.qchem.jobs.core import FreqMaker, OptMaker

if TYPE_CHECKING:
    from pathlib import Path

    from jobflow import Job
    from pymatgen.core.structure import Molecule

    from atomate2.qchem.jobs.base import BaseQCMaker


[docs] @dataclass class DoubleOptMaker(Maker): """ Maker to perform a double Qchem relaxation. Parameters ---------- name : str Name of the flows produced by this maker. relax_maker1 : .BaseVaspMaker Maker to use to generate the first relaxation. relax_maker2 : .BaseVaspMaker Maker to use to generate the second relaxation. """ name: str = "double opt" opt_maker1: BaseQCMaker | None = field(default_factory=OptMaker) opt_maker2: BaseQCMaker = field(default_factory=OptMaker)
[docs] def make(self, molecule: Molecule, prev_dir: str | Path | None = None) -> Flow: """ Create a flow with two chained molecular optimizations. Parameters ---------- molecule : .Molecule A pymatgen Molecule object. prev_dir : str or Path or None A previous QChem calculation directory to copy output files from. Returns ------- Flow A flow containing two geometric optimizations. """ jobs: list[Job] = [] if self.opt_maker1: # Run a pre-relaxation opt1 = self.opt_maker1.make(molecule, prev_dir=prev_dir) opt1.name += " 1" jobs += [opt1] molecule = opt1.output.optimized_molecule prev_dir = opt1.output.dir_name opt2 = self.opt_maker2.make(molecule, prev_dir=prev_dir) opt2.name += " 2" jobs += [opt2] return Flow(jobs, output=opt2.output, name=self.name)
[docs] @classmethod def from_opt_maker(cls, opt_maker: BaseQCMaker) -> DoubleOptMaker: """ Instantiate the DoubleRelaxMaker with two relax makers of the same type. Parameters ---------- opt_maker : .BaseQCMaker Maker to use to generate the first and second geometric optimizations. """ return cls(relax_maker1=deepcopy(opt_maker), relax_maker2=deepcopy(opt_maker))
[docs] @dataclass class FrequencyOptMaker(Maker): """ Maker to perform a frequency calculation after an optimization. Parameters ---------- name : str Name of the flows produced by this maker. opt_maker : .BaseQCMaker Maker to use to generate the opt maker freq_maker : .BaseQCMaker Maker to use to generate the freq maker """ name: str = "opt frequency" opt_maker: BaseQCMaker = field(default_factory=OptMaker) freq_maker: BaseQCMaker = field(default_factory=FreqMaker)
[docs] def make(self, molecule: Molecule, prev_dir: str | Path | None = None) -> Flow: """ Create a flow with optimization followed by frequency calculation. Parameters ---------- molecule : .Molecule A pymatgen Molecule object. prev_dir : str or Path or None A previous QChem calculation directory to copy output files from. Returns ------- Flow A flow containing with optimization and frequency calculation. """ jobs: list[Job] = [] opt = self.opt_maker.make(molecule, prev_dir=prev_dir) opt.name = "Geometry Optimization" jobs += [opt] freq = self.freq_maker.make( molecule=opt.output.output.optimized_molecule, prev_dir=opt.output.dir_name, ) freq.name = "Frequency Analysis" jobs += [freq] return Flow( jobs, output={"opt": opt.output, "freq": freq.output}, name=self.name )
[docs] @dataclass class FrequencyOptFlatteningMaker(Maker): """ Maker to perform a frequency calculation after an optimization. Parameters ---------- name : str Name of the flows produced by this maker. opt_maker : .BaseQCMaker Maker to use to generate the opt maker freq_maker : .BaseQCMaker Maker to use to generate the freq maker """ name: str = "frequency flattening opt" opt_maker: BaseQCMaker = field(default_factory=OptMaker) freq_maker: BaseQCMaker = field(default_factory=FreqMaker) scale: float = 1.0 max_ffopt_runs: int = 5
[docs] @job def make( self, molecule: Molecule, mode: list | None = None, lowest_freq: float = -1.0, ffopt_runs: int = 0, overwrite_inputs: dict | None = None, prev_dir: str | Path | None = None, ) -> Flow: """ Optimize geometry and perturb negative frequency modes. Parameters ---------- molecule : .Molecule A pymatgen Molecule object. prev_dir : str or Path or None A previous QChem calculation directory to copy output files from. Returns ------- Flow A flow containing with optimization and frequency calculation. """ mode = mode or [[0.0, 0.0, 0.0] for _ in range(len(molecule))] if overwrite_inputs is not None: self.opt_maker.input_set_generator.overwrite_inputs = overwrite_inputs self.freq_maker.input_set_generator.overwrite_inputs = overwrite_inputs new_flow = None new_output = None if (lowest_freq < 0) and (ffopt_runs < self.max_ffopt_runs): jobs: list[Job] = [] for idx in range(len(molecule)): molecule.translate_sites( indices=[idx], vector=[self.scale * v for v in mode[idx]] ) opt = self.opt_maker.make(molecule, prev_dir=prev_dir) opt.name = "Geometry Optimization" jobs += [opt] molecule = opt.output.output.optimized_molecule freq = self.freq_maker.make(molecule, prev_dir=prev_dir) freq.name = f"Frequency Analysis {ffopt_runs + 1}" jobs += [freq] recursive = self.make( molecule, mode=freq.output.output.frequency_modes[0], lowest_freq=freq.output.output.frequencies[0], ffopt_runs=ffopt_runs + 1, prev_dir=prev_dir, ) new_flow = Flow([*jobs, recursive], output=recursive.output) new_output = recursive.output elif ffopt_runs == 0: freq = self.freq_maker.make(molecule, prev_dir=prev_dir) freq.name = f"Frequency Analysis {ffopt_runs + 1}" new_flow = [freq] new_output = freq.output return Response(replace=new_flow, output=new_output)