Repository: pyscf/pyscf — pull request #2279 (python)
Title: Remove the ase module bulk from pyscf_ase (fix #2271)

Fix pyscf_ase (fix #2271)
Fix the TDDFT get_ab method for RSH functions (fix #2261)

Resolved issues:
title: Issue calculating static polarizability using RSH exchange-correlation functionals
body: Hello, everyone. I need to have access and freedom to work with the A and B matrices from coupled-perturbed KS. So, I'm trying to use the following code together with the CAM-B3LYP functional for static polarizability. However, the code doesn't work for this functional or any other RSH tested. It is curious since the code works properly for any other XC functionals, e.g., GGA, Hybrids, etc. I see someone with a similar issue and the fix was to change from LibXC to XCfun. But, this is not working for me. Can someone help me, please?

Results using the attached code:
Static polarizability from reference: 1.4907486930182852 (the right answer)
Static polarizability is: 1.3051766863495733

PS1: I also tried "a, b = tddft.TDDFT(mf).get_ab()". I still have the same issue with RSHs.

PS2: Using "rks.Polarizability(mf).polarizability()" works with RSH functionals (that's how I debug the results, i.e., reference). However, I don't know how to or if I have access to A and B using this code.

Thanks.

[cpks.zip](https://github.com/user-attachments/files/15873707/cpks.zip)

title: Cannot import name 'bulk' from 'ase.lattice'
body: I just tried to call the member function `from_ase()` with `ase 3.23.0` and `pyscf 2.6.1` and got an error. I guess `ase` has changed the lattice module. Simply removing the import works, as the function `bulk` is not used anywhere in `pyscf_ase.py`.

https://github.com/pyscf/pyscf/blob/d4ce357503bc865d43a956c8f540f8c0f38ef5cb/pyscf/pbc/tools/pyscf_ase.py#L27

Hints:

Fix patch:
diff --git a/pyscf/pbc/tools/pyscf_ase.py b/pyscf/pbc/tools/pyscf_ase.py
index f2d24a14fa..7c27c0654a 100644
--- a/pyscf/pbc/tools/pyscf_ase.py
+++ b/pyscf/pbc/tools/pyscf_ase.py
@@ -24,7 +24,6 @@
 import numpy as np
 from ase.calculators.calculator import Calculator
 import ase.dft.kpoints
-from ase.lattice import bulk
 
 def pyscf_to_ase_atoms(cell):
     '''
diff --git a/pyscf/tdscf/dhf.py b/pyscf/tdscf/dhf.py
index 5e3eb0f27c..0cf824353f 100644
--- a/pyscf/tdscf/dhf.py
+++ b/pyscf/tdscf/dhf.py
@@ -127,6 +127,7 @@ def add_hf_(a, b, hyb=1):
             raise NotImplementedError('DKS-TDDFT for NLC functionals')
 
         omega, alpha, hyb = ni.rsh_and_hybrid_coeff(mf.xc, mol.spin)
+        assert omega == 0.
 
         a, b = add_hf_(a, b, hyb)
 
diff --git a/pyscf/tdscf/ghf.py b/pyscf/tdscf/ghf.py
index dc23b63e0c..822fe128f7 100644
--- a/pyscf/tdscf/ghf.py
+++ b/pyscf/tdscf/ghf.py
@@ -162,6 +162,7 @@ def add_hf_(a, b, hyb=1):
             raise NotImplementedError
 
         omega, alpha, hyb = ni.rsh_and_hybrid_coeff(mf.xc, mol.spin)
+        assert omega == 0.
 
         a, b = add_hf_(a, b, hyb)
 
diff --git a/pyscf/tdscf/rhf.py b/pyscf/tdscf/rhf.py
index 720c3dade4..655e7026b9 100644
--- a/pyscf/tdscf/rhf.py
+++ b/pyscf/tdscf/rhf.py
@@ -152,6 +152,13 @@ def add_hf_(a, b, hyb=1):
         omega, alpha, hyb = ni.rsh_and_hybrid_coeff(mf.xc, mol.spin)
 
         add_hf_(a, b, hyb)
+        if omega != 0:  # For RSH
+            with mol.with_range_coulomb(omega):
+                eri_mo = ao2mo.general(mol, [orbo,mo,mo,mo], compact=False)
+                eri_mo = eri_mo.reshape(nocc,nmo,nmo,nmo)
+                k_fac = alpha - hyb
+                a -= numpy.einsum('ijba->iajb', eri_mo[:nocc,:nocc,nocc:,nocc:]) * k_fac
+                b -= numpy.einsum('jaib->iajb', eri_mo[:nocc,nocc:,:nocc,nocc:]) * k_fac
 
         xctype = ni._xc_type(mf.xc)
         dm0 = mf.make_rdm1(mo_coeff, mo_occ)
diff --git a/pyscf/tdscf/uhf.py b/pyscf/tdscf/uhf.py
index 1812926c6f..2692b9b688 100644
--- a/pyscf/tdscf/uhf.py
+++ b/pyscf/tdscf/uhf.py
@@ -188,6 +188,19 @@ def add_hf_(a, b, hyb=1):
         omega, alpha, hyb = ni.rsh_and_hybrid_coeff(mf.xc, mol.spin)
 
         add_hf_(a, b, hyb)
+        if omega != 0:  # For RSH
+            with mol.with_range_coulomb(omega):
+                eri_aa = ao2mo.general(mol, [orbo_a,mo_a,mo_a,mo_a], compact=False)
+                eri_bb = ao2mo.general(mol, [orbo_b,mo_b,mo_b,mo_b], compact=False)
+                eri_aa = eri_aa.reshape(nocc_a,nmo_a,nmo_a,nmo_a)
+                eri_bb = eri_bb.reshape(nocc_b,nmo_b,nmo_b,nmo_b)
+                a_aa, a_ab, a_bb = a
+                b_aa, b_ab, b_bb = b
+                k_fac = alpha - hyb
+                a_aa -= numpy.einsum('ijba->iajb', eri_aa[:nocc_a,:nocc_a,nocc_a:,nocc_a:]) * k_fac
+                b_aa -= numpy.einsum('jaib->iajb', eri_aa[:nocc_a,nocc_a:,:nocc_a,nocc_a:]) * k_fac
+                a_bb -= numpy.einsum('ijba->iajb', eri_bb[:nocc_b,:nocc_b,nocc_b:,nocc_b:]) * k_fac
+                b_bb -= numpy.einsum('jaib->iajb', eri_bb[:nocc_b,nocc_b:,:nocc_b,nocc_b:]) * k_fac
 
         xctype = ni._xc_type(mf.xc)
         dm0 = mf.make_rdm1(mo_coeff, mo_occ)


Test patch:
diff --git a/pyscf/tdscf/test/test_tdrks.py b/pyscf/tdscf/test/test_tdrks.py
index 0df6b2f8c1..2ca7cbb540 100644
--- a/pyscf/tdscf/test/test_tdrks.py
+++ b/pyscf/tdscf/test/test_tdrks.py
@@ -107,6 +107,15 @@ def test_tddft_b3lyp(self):
         ref = diagonalize(a, b, nroots=5) * 27.2114
         self.assertAlmostEqual(abs(es - ref).max(), 0, 7)
 
+    def test_tddft_camb3lyp(self):
+        mf = mol.RKS(xc='camb3lyp').run()
+        td = mf.TDDFT()
+        es = td.kernel(nstates=4)[0]
+        a,b = td.get_ab()
+        e_ref = diagonalize(a, b, 5)
+        self.assertAlmostEqual(abs(es[:3]-e_ref[:3]).max(), 0, 8)
+        self.assertAlmostEqual(lib.fp(es[:3]*27.2114), 9.0054057603534, 4)
+
     def test_tda_b3lypg(self):
         mf = dft.RKS(mol)
         mf.xc = 'b3lypg'
diff --git a/pyscf/tdscf/test/test_tduks.py b/pyscf/tdscf/test/test_tduks.py
index 9679fc5ea5..61a70172ff 100644
--- a/pyscf/tdscf/test/test_tduks.py
+++ b/pyscf/tdscf/test/test_tduks.py
@@ -130,6 +130,15 @@ def test_tddft_b3lyp(self):
         es = td.kernel(nstates=4)[0] * 27.2114
         self.assertAlmostEqual(lib.fp(es[:3]), 1.2984822994759448, 4)
 
+    def test_tddft_camb3lyp(self):
+        mf = mol1.UKS(xc='camb3lyp').run()
+        td = mf.TDDFT()
+        es = td.kernel(nstates=4)[0]
+        a,b = td.get_ab()
+        e_ref = diagonalize(a, b, 5)
+        self.assertAlmostEqual(abs(es[:3]-e_ref[:3]).max(), 0, 8)
+        self.assertAlmostEqual(lib.fp(es[:3]*27.2114), 7.69383202636, 4)
+
     def test_tda_b3lyp(self):
         td = tdscf.TDA(mf_b3lyp).set(conv_tol=1e-12)
         es = td.kernel(nstates=4)[0] * 27.2114