Repository: pyscf/pyscf — pull request #2871 (python)
Title: Add NLC response function for GKS

fix #2863

Resolved issues:
title: [FEATURE REQUEST] Nonlocal corrections and GKS
body: Hello! 

I would like to run GKS with the VV10 dispersion correction. However, I get the error "NotImplementedError: NLC" from the `_gen_ghf_response` function in scf/_response_functions.py. Is there any way to include VV10 for use with the GKS module? Or has VV10 only been developed for RKS and UKS thus far?

Thank you!

Hints:

Fix patch:
diff --git a/pyscf/dft/numint2c.py b/pyscf/dft/numint2c.py
index f34bc6b892..0daadda128 100644
--- a/pyscf/dft/numint2c.py
+++ b/pyscf/dft/numint2c.py
@@ -679,8 +679,8 @@ def nr_nlc_vxc(self, mol, grids, xc_code, dm, spin=0, relativity=0, hermi=1,
         n, exc, v = ni.nr_nlc_vxc(mol, grids, xc_code, dm_a+dm_b, relativity,
                                   hermi, max_memory, verbose)
         vmat = np.zeros_like(dm)
-        vmat[:nao,:nao] = v[0]
-        vmat[nao:,nao:] = v[1]
+        vmat[:nao,:nao] = v
+        vmat[nao:,nao:] = v
         return n, exc, vmat
 
     @lib.with_doc(numint.nr_rks_fxc.__doc__)
diff --git a/pyscf/hessian/rks.py b/pyscf/hessian/rks.py
index 554bdd2f1f..b559c6d7e8 100644
--- a/pyscf/hessian/rks.py
+++ b/pyscf/hessian/rks.py
@@ -1849,19 +1849,20 @@ def get_vnlc_resp(mf, mol, mo_coeff, mo_occ, dm1s, max_memory):
 
         TODO: check the effect of different grid, using mf.nlcgrids right now
     """
-    if mo_coeff.ndim == 2:
+    if isinstance(mo_coeff, numpy.ndarray) and mo_coeff.ndim == 2:
         mocc = mo_coeff[:,mo_occ>0]
         mo_occ = mo_occ[mo_occ > 0]
         dm0 = (mocc * mo_occ) @ mocc.T
     else:
-        assert mo_coeff.ndim == 3 # unrestricted case
-        assert mo_coeff.shape[0] == 2
-        assert mo_occ.shape[0] == 2
+        assert mo_coeff[0].ndim == 2 # unrestricted case
+        assert len(mo_coeff) == 2
+        assert len(mo_occ) == 2
         mocc_a = mo_coeff[0][:, mo_occ[0] > 0]
         mocc_b = mo_coeff[1][:, mo_occ[1] > 0]
-        mo_occ_a = mo_occ[0, mo_occ[0] > 0]
-        mo_occ_b = mo_occ[1, mo_occ[1] > 0]
+        mo_occ_a = mo_occ[0][mo_occ[0] > 0]
+        mo_occ_b = mo_occ[1][mo_occ[1] > 0]
         dm0 = (mocc_a * mo_occ_a) @ mocc_a.T + (mocc_b * mo_occ_b) @ mocc_b.T
+    dm0 = numpy.asarray(dm0.real, order='C')
 
     output_in_2d = False
     if dm1s.ndim == 2:
diff --git a/pyscf/scf/_response_functions.py b/pyscf/scf/_response_functions.py
index fd7f458e48..df75029caa 100644
--- a/pyscf/scf/_response_functions.py
+++ b/pyscf/scf/_response_functions.py
@@ -267,8 +267,6 @@ def _gen_ghf_response(mf, mo_coeff=None, mo_occ=None,
         ni = mf._numint
         assert isinstance(ni, (numint2c.NumInt2C, r_numint.RNumInt))
         ni.libxc.test_deriv_order(mf.xc, 2, raise_error=True)
-        if with_nlc and mf.do_nlc():
-            raise NotImplementedError('NLC')
         omega, alpha, hyb = ni.rsh_and_hybrid_coeff(mf.xc, mol.spin)
         hybrid = ni.libxc.is_hybrid_xc(mf.xc)
 
@@ -288,6 +286,20 @@ def vind(dm1):
             else:
                 v1 = ni.get_fxc(mol, mf.grids, mf.xc, dm0, dm1, 0, 0, hermi,
                                 rho0, vxc, fxc, max_memory=max_memory)
+                if with_nlc and mf.do_nlc():
+                    from pyscf.hessian.rks import get_vnlc_resp
+                    nao = mo_coeff.shape[0] // 2
+                    dm1_sf = dm1[...,:nao,:nao] + dm1[...,nao:,nao:]
+                    mo_uks = [mo_coeff[:nao], mo_coeff[nao:]]
+                    mo_occ_uks = [mo_occ, mo_occ]
+                    # The get_vnlc_resp function uses mf._numint and explicitly
+                    # calls the NumInt functions for mf._numint
+                    ni1c = mf._numint._to_numint1c()
+                    with lib.temporary_env(mf, _numint=ni1c):
+                        vxc_nlc = get_vnlc_resp(mf, mol, mo_uks, mo_occ_uks,
+                                                dm1_sf.real, max_memory)
+                        v1[...,:nao,:nao] += vxc_nlc
+                        v1[...,nao:,nao:] += vxc_nlc
             if not hybrid:
                 if with_j:
                     vj = mf.get_j(mol, dm1, hermi=hermi)


Test patch:
diff --git a/pyscf/dft/test/test_gks.py b/pyscf/dft/test/test_gks.py
index 4a0ca4662f..07e59ab464 100644
--- a/pyscf/dft/test/test_gks.py
+++ b/pyscf/dft/test/test_gks.py
@@ -15,6 +15,7 @@
 
 import unittest
 import numpy
+import scipy.linalg
 from pyscf import gto
 from pyscf import lib
 from pyscf.dft import numint, numint2c, libxc, radi
diff --git a/pyscf/dft/test/test_h2o.py b/pyscf/dft/test/test_h2o.py
index 585239bdb6..aa55453984 100644
--- a/pyscf/dft/test/test_h2o.py
+++ b/pyscf/dft/test/test_h2o.py
@@ -15,6 +15,7 @@
 
 import unittest
 import numpy
+import scipy.linalg
 from pyscf import gto
 from pyscf import lib
 from pyscf import dft
@@ -371,32 +372,34 @@ def test_nr_rks_nlc(self):
         method = dft.RKS(h2o)
         dm = method.get_init_guess()
         method.xc = 'wB97M_V'
+        method.nlcgrids.level = 0
         vxc = method.get_veff(h2o, dm)
-        self.assertAlmostEqual(lib.fp(vxc), 22.767792068559917, 8)
+        self.assertAlmostEqual(lib.fp(vxc), 22.767790354129247, 8)
 
         method.xc = 'B97M_V'
         vxc = method.get_veff(h2o, dm, dm, vxc)
-        self.assertAlmostEqual(lib.fp(vxc), 23.067046560473408, 8)
+        self.assertAlmostEqual(lib.fp(vxc), 23.06704484604275, 8)
 
         method.nlc = False
         assert method.do_nlc() == False
         vxc = method.get_veff(h2o, dm, dm, vxc)
         self.assertAlmostEqual(lib.fp(vxc), 23.05881308880983, 8)
 
-    def test_nr_rks_nlc_small_memory_high_cost(self):
+    def test_nr_rks_nlc_small_memory(self):
         method = dft.RKS(h2o)
         dm = method.get_init_guess()
         method._eri = None
         method.max_memory = 0
         method.xc = 'wB97M_V'
+        method.nlcgrids.level = 0
         vxc = method.get_veff(h2o, dm)
-        self.assertAlmostEqual(lib.fp(vxc), 22.767792068559917, 8)
+        self.assertAlmostEqual(lib.fp(vxc), 22.767790354129247, 8)
 
         method._eri = None
         method.max_memory = 0
         method.xc = 'B97M_V'
         vxc = method.get_veff(h2o, dm, dm, vxc)
-        self.assertAlmostEqual(lib.fp(vxc), 23.067046560473408, 8)
+        self.assertAlmostEqual(lib.fp(vxc), 23.06704484604275, 8)
 
     def test_nr_rks_rsh_cart_high_cost(self):
         mol1 = h2o.copy()
@@ -417,37 +420,39 @@ def test_nr_uks_rsh(self):
         self.assertAlmostEqual(lib.fp(vxc[0]), 23.16975737295899, 8)
         self.assertAlmostEqual(lib.fp(vxc[1]), 23.16975737295899, 8)
 
-    def test_nr_uks_nlc_high_cost(self):
+    def test_nr_uks_nlc(self):
         method = dft.UKS(h2o)
         dm = method.get_init_guess()
         dm = (dm[0], dm[0])
         method.xc = 'wB97M_V'
+        method.nlcgrids.level = 0
         vxc = method.get_veff(h2o, dm)
-        self.assertAlmostEqual(lib.fp(vxc[0]), 22.767792068559917, 8)
-        self.assertAlmostEqual(lib.fp(vxc[1]), 22.767792068559917, 8)
+        self.assertAlmostEqual(lib.fp(vxc[0]), 22.767790354129247, 8)
+        self.assertAlmostEqual(lib.fp(vxc[1]), 22.767790354129247, 8)
 
         method.xc = 'B97M_V'
         vxc = method.get_veff(h2o, dm, dm, vxc)
-        self.assertAlmostEqual(lib.fp(vxc[0]), 23.067046560473408, 8)
-        self.assertAlmostEqual(lib.fp(vxc[1]), 23.067046560473408, 8)
+        self.assertAlmostEqual(lib.fp(vxc[0]), 23.06704484604275, 8)
+        self.assertAlmostEqual(lib.fp(vxc[1]), 23.06704484604275, 8)
 
-    def test_nr_uks_nlc_small_memory_high_cost(self):
+    def test_nr_uks_nlc_small_memory(self):
         method = dft.UKS(h2o)
         dm = method.get_init_guess()
         dm = (dm[0], dm[0])
         method._eri = None
         method.max_memory = 0
         method.xc = 'wB97M_V'
+        method.nlcgrids.level = 0
         vxc = method.get_veff(h2o, dm)
-        self.assertAlmostEqual(lib.fp(vxc[0]), 22.767792068559917, 8)
-        self.assertAlmostEqual(lib.fp(vxc[1]), 22.767792068559917, 8)
+        self.assertAlmostEqual(lib.fp(vxc[0]), 22.767790354129247, 8)
+        self.assertAlmostEqual(lib.fp(vxc[1]), 22.767790354129247, 8)
 
         method._eri = None
         method.max_memory = 0
         method.xc = 'B97M_V'
         vxc = method.get_veff(h2o, dm, dm, vxc)
-        self.assertAlmostEqual(lib.fp(vxc[0]), 23.067046560473408, 8)
-        self.assertAlmostEqual(lib.fp(vxc[1]), 23.067046560473408, 8)
+        self.assertAlmostEqual(lib.fp(vxc[0]), 23.06704484604275, 8)
+        self.assertAlmostEqual(lib.fp(vxc[1]), 23.06704484604275, 8)
 
     def test_nr_gks_rsh(self):
         method = dft.GKS(h2o)
@@ -458,35 +463,26 @@ def test_nr_gks_rsh(self):
         vxc = method.get_veff(h2o, dm)
         self.assertAlmostEqual(lib.fp(vxc), 5.115622298912124+0j, 8)
 
-    def test_nr_gks_nlc_high_cost(self):
+    def test_nr_gks_nlc(self):
         method = dft.GKS(h2o)
         dm = method.get_init_guess()
         dm = dm + numpy.sin(dm)*.02j
         dm = dm + dm.conj().T
         method.xc = 'wB97M_V'
+        method.nlcgrids.level = 0
         vxc = method.get_veff(h2o, dm)
-        self.assertAlmostEqual(lib.fp(vxc), 3.172920887028461+0j, 8)
-
+        self.assertAlmostEqual(lib.fp(vxc), 3.17466829111814+0j, 8)
         method.xc = 'B97M_V'
         vxc = method.get_veff(h2o, dm)
-        self.assertAlmostEqual(lib.fp(vxc), 2.0041673361905317+0j, 8)
-
-    def test_nr_gks_nlc_small_memory_high_cost(self):
-        method = dft.GKS(h2o)
-        dm = method.get_init_guess()
-        dm = dm + numpy.sin(dm)*.02j
-        dm = dm + dm.conj().T
-        method._eri = None
-        method.max_memory = 0
-        method.xc = 'wB97M_V'
-        vxc = method.get_veff(h2o, dm)
-        self.assertAlmostEqual(lib.fp(vxc), 3.172920887028461+0j, 8)
-
-        method._eri = None
-        method.max_memory = 0
-        method.xc = 'B97M_V'
-        vxc = method.get_veff(h2o, dm, dm, vxc)
-        self.assertAlmostEqual(lib.fp(vxc), 2.0041673361905317+0j, 8)
+        self.assertAlmostEqual(lib.fp(vxc), 2.00591474028017+0j, 8)
+
+        mf = h2o_cation.UKS(xc='wb97mv')
+        mf.nlcgrids.level = 0
+        dm = mf.get_init_guess()
+        ref = scipy.linalg.block_diag(*mf.get_veff(h2o_cation, dm))
+        mf_gks = mf.to_gks()
+        vxc = mf_gks.get_veff(h2o_cation, scipy.linalg.block_diag(*dm))
+        self.assertAlmostEqual(abs(vxc - ref).max(), 0, 12)
 
     def test_nr_rks_vv10_high_cost(self):
         method = dft.RKS(h2o)
diff --git a/pyscf/scf/test/test_response_function.py b/pyscf/scf/test/test_response_function.py
new file mode 100644
index 0000000000..0874b0a035
--- /dev/null
+++ b/pyscf/scf/test/test_response_function.py
@@ -0,0 +1,52 @@
+#!/usr/bin/env python
+# Copyright 2025 The PySCF Developers. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import unittest
+import numpy as np
+import scipy.linalg
+from pyscf import lib
+from pyscf import gto
+from pyscf.scf import _response_functions
+
+class KnownValues(unittest.TestCase):
+    def test_gks_nlc(self):
+        mol = gto.M(
+            verbose = 5,
+            output = '/dev/null',
+            atom = [
+            ["O" , (0. , 0.     , 0.)],
+            [1   , (0. , -0.757 , 0.587)],
+            [1   , (0. , 0.757  , 0.587)]],
+            charge = 1,
+            spin = 1,
+            basis = '631g')
+        nao = mol.nao
+        mf_uks = mol.UHF().run().to_uks()
+        mf_uks.xc = 'wb97mv'
+        mf_uks.nlcgrids.level = 0
+
+        dm = mf_uks.make_rdm1()
+        dm1 = np.random.rand(2, nao, nao)
+        vind = mf_uks.gen_response(with_nlc=True)
+        ref = scipy.linalg.block_diag(*vind(dm1))
+
+        mf_gks = mf_uks.to_gks()
+        vind = mf_gks.gen_response(with_nlc=True)
+        v = vind(scipy.linalg.block_diag(*dm1))
+        self.assertAlmostEqual(abs(v - ref).max(), 0, 12)
+
+if __name__ == "__main__":
+    print("Full Tests for response_functions")
+    unittest.main()