def compute_hessian(self, molecule: core.Molecule, wfn: core.Wavefunction = None) -> core.Matrix: """Compute dispersion Hessian based on engine, dispersion level, and parameters in `self`. Uses finite difference, as no dispersion engine has analytic second derivatives. Parameters ---------- molecule System for which to compute empirical dispersion correction. wfn Location to set QCVariables Returns ------- Matrix (3*nat, 3*nat) dispersion Hessian [Eh/a0/a0]. """ optstash = p4util.OptionsState(['PRINT'], ['PARENT_SYMMETRY']) core.set_global_option('PRINT', 0) core.print_out( "\n\n Analytical Dispersion Hessians are not supported by dftd3 or gcp.\n" ) core.print_out( " Computing the Hessian through finite difference of gradients.\n\n" ) # Setup the molecule molclone = molecule.clone() molclone.reinterpret_coordentry(False) molclone.fix_orientation(True) molclone.fix_com(True) # Record undisplaced symmetry for projection of diplaced point groups core.set_global_option("PARENT_SYMMETRY", molecule.schoenflies_symbol()) findif_meta_dict = driver_findif.hessian_from_gradients_geometries( molclone, -1) for displacement in findif_meta_dict["displacements"].values(): geom_array = np.reshape(displacement["geometry"], (-1, 3)) molclone.set_geometry(core.Matrix.from_array(geom_array)) molclone.update_geometry() displacement["gradient"] = self.compute_gradient( molclone).np.ravel().tolist() H = driver_findif.assemble_hessian_from_gradients(findif_meta_dict, -1) if wfn is not None: wfn.set_variable('DISPERSION CORRECTION HESSIAN', H) optstash.restore() return core.Matrix.from_array(H)
def compute_gradient(self, molecule: core.Molecule, wfn: core.Wavefunction = None) -> core.Matrix: """Compute dispersion gradient based on engine, dispersion level, and parameters in `self`. Parameters ---------- molecule System for which to compute empirical dispersion correction. wfn Location to set QCVariables Returns ------- Matrix (nat, 3) dispersion gradient [Eh/a0]. """ if self.engine in ['dftd3', 'mp2d']: resi = AtomicInput( **{ 'driver': 'gradient', 'model': { 'method': self.fctldash, 'basis': '(auto)', }, 'keywords': { 'level_hint': self.dashlevel, 'params_tweaks': self.dashparams, 'dashcoeff_supplement': self.dashcoeff_supplement, 'verbose': 1, }, 'molecule': molecule.to_schema(dtype=2), 'provenance': p4util.provenance_stamp(__name__), }) jobrec = qcng.compute( resi, self.engine, raise_error=True, local_options={"scratch_directory": core.IOManager.shared_object().get_default_path()}) dashd_part = core.Matrix.from_array(jobrec.extras['qcvars']['DISPERSION CORRECTION GRADIENT']) if wfn is not None: for k, qca in jobrec.extras['qcvars'].items(): if "CURRENT" not in k: wfn.set_variable(k, float(qca) if isinstance(qca, str) else qca) if self.fctldash in ['hf3c', 'pbeh3c']: jobrec = qcng.compute( resi, "gcp", raise_error=True, local_options={"scratch_directory": core.IOManager.shared_object().get_default_path()}) gcp_part = core.Matrix.from_array(jobrec.return_result) dashd_part.add(gcp_part) return dashd_part else: return self.disp.compute_gradient(molecule)
def makepsi4(atomcoords, atomnos, charge=0, mult=1): """Create a Psi4 Molecule.""" _check_psi4(_found_psi4) return Molecule.from_arrays( name="notitle", elez=atomnos, geom=atomcoords, units="Angstrom", molecular_charge=charge, molecular_multiplicity=mult, )
def auto_fragments(molecule: core.Molecule = None, seed_atoms: List = None) -> core.Molecule: r"""Detects fragments in unfragmented molecule using BFS algorithm. Currently only used for the WebMO implementation of SAPT. Parameters ---------- molecule : :ref:`molecule <op_py_molecule>`, optional The target molecule, if not the last molecule defined. seed_atoms List of lists of atoms (0-indexed) belonging to independent fragments. Useful to prompt algorithm or to define intramolecular fragments through border atoms. Example: `[[1, 0], [2]]` Returns ------- :py:class:`~psi4.core.Molecule` |w--w| fragmented molecule in Cartesian, fixed-geom (no variable values), no dummy-atom format. Examples -------- >>> # [1] prepare unfragmented (and non-adjacent-atom) HHFF into (HF)_2 molecule ready for SAPT >>> molecule mol {\nH 0.0 0.0 0.0\nH 2.0 0.0 0.0\nF 0.0 1.0 0.0\nF 2.0 1.0 0.0\n} >>> print mol.nfragments() # 1 >>> fragmol = auto_fragments() >>> print fragmol.nfragments() # 2 """ # Make sure the molecule the user provided is the active one if molecule is None: molecule = core.get_active_molecule() molecule.update_geometry() molname = molecule.name() frag, bmol = molecule.BFS(seed_atoms=seed_atoms, return_molecule=True) bmol.set_name(molname) bmol.print_cluster() core.print_out(""" Exiting auto_fragments\n""") return bmol
def prepare_sapt_molecule( sapt_dimer: core.Molecule, sapt_basis: str) -> Tuple[core.Molecule, core.Molecule, core.Molecule]: """ Prepares a dimer molecule for a SAPT computations. Returns the dimer, monomerA, and monomerB. """ # Shifting to C1 so we need to copy the active molecule sapt_dimer = sapt_dimer.clone() if sapt_dimer.schoenflies_symbol() != 'c1': core.print_out( ' SAPT does not make use of molecular symmetry, further calculations in C1 point group.\n' ) sapt_dimer.reset_point_group('c1') sapt_dimer.fix_orientation(True) sapt_dimer.fix_com(True) sapt_dimer.update_geometry() else: sapt_dimer.update_geometry( ) # make sure since mol from wfn, kwarg, or P::e sapt_dimer.fix_orientation(True) sapt_dimer.fix_com(True) nfrag = sapt_dimer.nfragments() if nfrag == 3: # Midbond case if sapt_basis == 'monomer': raise ValidationError( "SAPT basis cannot both be monomer centered and have midbond functions." ) midbond = sapt_dimer.extract_subsets(3) ztotal = 0 for n in range(midbond.natom()): ztotal += midbond.Z(n) if ztotal > 0: raise ValidationError( "SAPT third monomer must be a midbond function (all ghosts).") ghosts = ([2, 3], [1, 3]) elif nfrag == 2: # Classical dimer case ghosts = (2, 1) else: raise ValidationError( 'SAPT requires active molecule to have 2 fragments, not %s.' % (nfrag)) if sapt_basis == 'dimer': monomerA = sapt_dimer.extract_subsets(1, ghosts[0]) monomerA.set_name('monomerA') monomerB = sapt_dimer.extract_subsets(2, ghosts[1]) monomerB.set_name('monomerB') elif sapt_basis == 'monomer': monomerA = sapt_dimer.extract_subsets(1) monomerA.set_name('monomerA') monomerB = sapt_dimer.extract_subsets(2) monomerB.set_name('monomerB') else: raise ValidationError("SAPT basis %s not recognized" % sapt_basis) return (sapt_dimer, monomerA, monomerB)
def compute_energy(self, molecule: core.Molecule, wfn: core.Wavefunction = None) -> float: """Compute dispersion energy based on engine, dispersion level, and parameters in `self`. Parameters ---------- molecule System for which to compute empirical dispersion correction. wfn Location to set QCVariables Returns ------- float Dispersion energy [Eh]. Notes ----- :psivar:`DISPERSION CORRECTION ENERGY` Disp always set. Overridden in SCF finalization, but that only changes for "-3C" methods. :psivar:`fctl DISPERSION CORRECTION ENERGY` Set if :py:attr:`fctldash` nonempty. """ if self.engine in ['dftd3', 'mp2d']: resi = AtomicInput( **{ 'driver': 'energy', 'model': { 'method': self.fctldash, 'basis': '(auto)', }, 'keywords': { 'level_hint': self.dashlevel, 'params_tweaks': self.dashparams, 'dashcoeff_supplement': self.dashcoeff_supplement, 'pair_resolved': self.save_pairwise_disp, 'verbose': 1, }, 'molecule': molecule.to_schema(dtype=2), 'provenance': p4util.provenance_stamp(__name__), }) jobrec = qcng.compute( resi, self.engine, raise_error=True, local_options={ "scratch_directory": core.IOManager.shared_object().get_default_path() }) dashd_part = float( jobrec.extras['qcvars']['DISPERSION CORRECTION ENERGY']) if wfn is not None: for k, qca in jobrec.extras['qcvars'].items(): wfn.set_variable( k, float(qca) if isinstance(qca, str) else qca) # Pass along the pairwise dispersion decomposition if we need it if self.save_pairwise_disp is True: wfn.set_variable( "PAIRWISE DISPERSION CORRECTION ANALYSIS", jobrec.extras['qcvars'] ["2-BODY PAIRWISE DISPERSION CORRECTION ANALYSIS"]) if self.fctldash in ['hf3c', 'pbeh3c']: jobrec = qcng.compute( resi, "gcp", raise_error=True, local_options={ "scratch_directory": core.IOManager.shared_object().get_default_path() }) gcp_part = jobrec.return_result dashd_part += gcp_part return dashd_part else: ene = self.disp.compute_energy(molecule) core.set_variable('DISPERSION CORRECTION ENERGY', ene) if self.fctldash: core.set_variable( f"{self.fctldash} DISPERSION CORRECTION ENERGY", ene) return ene