Beispiel #1
0
    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)
Beispiel #2
0
    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)
Beispiel #3
0
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,
    )
Beispiel #4
0
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
Beispiel #5
0
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)
Beispiel #6
0
    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