|
| 1 | +# This file is part of dftd4. |
| 2 | +# SPDX-Identifier: LGPL-3.0-or-later |
| 3 | +# |
| 4 | +# dftd4 is free software: you can redistribute it and/or modify it under |
| 5 | +# the terms of the Lesser GNU General Public License as published by |
| 6 | +# the Free Software Foundation, either version 3 of the License, or |
| 7 | +# (at your option) any later version. |
| 8 | +# |
| 9 | +# dftd4 is distributed in the hope that it will be useful, |
| 10 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 11 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 12 | +# Lesser GNU General Public License for more details. |
| 13 | +# |
| 14 | +# You should have received a copy of the Lesser GNU General Public License |
| 15 | +# along with dftd4. If not, see <https://www.gnu.org/licenses/>. |
| 16 | +""" |
| 17 | +Compatibility layer for supporting DFT-D3 in `pyscf <https://pyscf.org/>`_. |
| 18 | +
|
| 19 | +Example |
| 20 | +------- |
| 21 | +>>> from pyscf import gto, scf |
| 22 | +>>> from pyscf import scf |
| 23 | +>>> import dftd4.pyscf as disp |
| 24 | +>>> mol = gto.Mole() |
| 25 | +>>> mol.atom = ''' O 0.00000000 0.00000000 -0.11081188 |
| 26 | +... H -0.00000000 -0.84695236 0.59109389 |
| 27 | +... H -0.00000000 0.89830571 0.52404783 ''' |
| 28 | +>>> mol.basis = 'cc-pvdz' |
| 29 | +>>> _ = mol.build() |
| 30 | +>>> mf = disp.dftd4(scf.RHF(mol)) |
| 31 | +>>> print(mf.kernel()) |
| 32 | +-75.99396273778923 |
| 33 | +""" |
| 34 | + |
| 35 | +import numpy as np |
| 36 | + |
| 37 | +try: |
| 38 | + from pyscf import lib, gto |
| 39 | +except ModuleNotFoundError: |
| 40 | + raise ModuleNotFoundError("This submodule requires pyscf installed") |
| 41 | + |
| 42 | +from .interface import DispersionModel, DampingParam |
| 43 | + |
| 44 | + |
| 45 | +def dftd4(mf): |
| 46 | + """Apply DFT-D3 corrections to SCF or MCSCF methods""" |
| 47 | + |
| 48 | + from pyscf.scf import hf |
| 49 | + from pyscf.mcscf import casci |
| 50 | + |
| 51 | + if not isinstance(mf, (hf.SCF, casci.CASCI)): |
| 52 | + raise TypeError("mf must be an instance of SCF or CASCI") |
| 53 | + |
| 54 | + with_dftd4 = DFTD4Dispersion( |
| 55 | + mf.mol, |
| 56 | + xc="hf" |
| 57 | + if isinstance(mf, casci.CASCI) |
| 58 | + else getattr(mf, "xc", "HF").upper().replace(" ", ""), |
| 59 | + ) |
| 60 | + |
| 61 | + if isinstance(mf, _DFTD4): |
| 62 | + mf.with_dftd4 = with_dftd4 |
| 63 | + return mf |
| 64 | + |
| 65 | + class DFTD4(_DFTD4, mf.__class__): |
| 66 | + def __init__(self, method, with_dftd4): |
| 67 | + self.__dict__.update(method.__dict__) |
| 68 | + self.with_dftd4 = with_dftd4 |
| 69 | + self._keys.update(["with_dftd4"]) |
| 70 | + |
| 71 | + def dump_flags(self, verbose=None): |
| 72 | + mf.__class__.dump_flags(self, verbose) |
| 73 | + if self.with_dftd4: |
| 74 | + self.with_dftd4.dump_flags(verbose) |
| 75 | + return self |
| 76 | + |
| 77 | + def energy_nuc(self): |
| 78 | + enuc = mf.__class__.energy_nuc(self) |
| 79 | + if self.with_dftd4: |
| 80 | + enuc += self.with_dftd4.kernel()[0] |
| 81 | + return enuc |
| 82 | + |
| 83 | + def reset(self, mol=None): |
| 84 | + self.with_dftd4.reset(mol) |
| 85 | + return mf.__class__.reset(self, mol) |
| 86 | + |
| 87 | + def nuc_grad_method(self): |
| 88 | + scf_grad = mf.__class__.nuc_grad_method(self) |
| 89 | + return grad(scf_grad) |
| 90 | + |
| 91 | + Gradients = lib.alias(nuc_grad_method, alias_name="Gradients") |
| 92 | + |
| 93 | + return DFTD4(mf, with_dftd4) |
| 94 | + |
| 95 | + |
| 96 | +def grad(scf_grad): |
| 97 | + """ |
| 98 | + Apply DFT-D3 corrections to SCF or MCSCF nuclear gradients methods |
| 99 | + """ |
| 100 | + from pyscf.grad import rhf as rhf_grad |
| 101 | + |
| 102 | + if not isinstance(scf_grad, rhf_grad.Gradients): |
| 103 | + raise TypeError("scf_grad must be an instance of Gradients") |
| 104 | + |
| 105 | + # Ensure that the zeroth order results include DFTD4 corrections |
| 106 | + if not getattr(scf_grad.base, "with_dftd4", None): |
| 107 | + scf_grad.base = dftd4(scf_grad.base) |
| 108 | + |
| 109 | + class DFTD4Grad(_DFTD4Grad, scf_grad.__class__): |
| 110 | + def grad_nuc(self, mol=None, atmlst=None): |
| 111 | + nuc_g = scf_grad.__class__.grad_nuc(self, mol, atmlst) |
| 112 | + with_dftd4 = getattr(self.base, "with_dftd4", None) |
| 113 | + if with_dftd4: |
| 114 | + disp_g = with_dftd4.kernel()[1] |
| 115 | + if atmlst is not None: |
| 116 | + disp_g = disp_g[atmlst] |
| 117 | + nuc_g += disp_g |
| 118 | + return nuc_g |
| 119 | + |
| 120 | + mfgrad = DFTD4Grad.__new__(DFTD4Grad) |
| 121 | + mfgrad.__dict__.update(scf_grad.__dict__) |
| 122 | + return mfgrad |
| 123 | + |
| 124 | + |
| 125 | +class DFTD4Dispersion(lib.StreamObject): |
| 126 | + def __init__(self, mol, xc="hf", atm=True): |
| 127 | + self.mol = mol |
| 128 | + self.verbose = mol.verbose |
| 129 | + self.xc = xc |
| 130 | + self.atm = atm |
| 131 | + self.edisp = None |
| 132 | + self.grads = None |
| 133 | + |
| 134 | + def dump_flags(self, verbose=None): |
| 135 | + lib.logger.info(self, "** DFTD4 parameter **") |
| 136 | + lib.logger.info(self, "func %s", self.xc) |
| 137 | + return self |
| 138 | + |
| 139 | + def kernel(self): |
| 140 | + mol = self.mol |
| 141 | + |
| 142 | + disp = DispersionModel( |
| 143 | + mol.atom_charges(), |
| 144 | + mol.atom_coords(), |
| 145 | + mol.charge, |
| 146 | + ) |
| 147 | + |
| 148 | + param = DampingParam( |
| 149 | + method=self.xc, |
| 150 | + atm=self.atm, |
| 151 | + ) |
| 152 | + |
| 153 | + res = disp.get_dispersion(param=param, grad=True) |
| 154 | + |
| 155 | + self.edisp = res.get("energy") |
| 156 | + self.grads = res.get("gradient") |
| 157 | + return self.edisp, self.grads |
| 158 | + |
| 159 | + def reset(self, mol): |
| 160 | + """Reset mol and clean up relevant attributes for scanner mode""" |
| 161 | + self.mol = mol |
| 162 | + return self |
| 163 | + |
| 164 | + |
| 165 | +class _DFTD4: |
| 166 | + """ |
| 167 | + Stub class used to identify instances of the `DFTD4` class |
| 168 | + """ |
| 169 | + |
| 170 | + pass |
| 171 | + |
| 172 | + |
| 173 | +class _DFTD4Grad: |
| 174 | + """ |
| 175 | + Stub class used to identify instances of the `DFTD4Grad` class |
| 176 | + """ |
| 177 | + |
| 178 | + pass |
0 commit comments