Repository: pyscf/pyscf — pull request #2676 (python)
Title: Complex density matrices for DFHF

fix #2670

Resolved issues:
title: [UNEXPECTED BEHAVIOR] Density-fitting complex RHF does not give correct energy
body: I notice a significant energy difference when testing df complex RHF. Here is my code
```
from pyscf import gto, scf
import numpy as np
mol = gto.Mole()
dista = 1.5
mol.atom = [['Be',(0.0, 0.0, 0.0)], ['H',(dista, 1.344-0.46*dista, 0.0)],['H',(dista, -1.344+0.46*dista, 0.0)]]
mol.spin = 0
mol.basis = 'cc-pvdz'
mol.build()
rrhf = scf.RHF(mol)

# real RHF
rrhf = scf.RHF(mol)
rrhf.kernel()

# cmplx RHF
crhf = scf.RHF(mol)
dm = rrhf.make_rdm1() + 0j
dm[0,:] += .1j
dm[:,0] -= .1j
crhf.kernel(dm)

# DF RHF
dfrhf = scf.RHF(mol).density_fit()
dfrhf.mo_coeff = rrhf.mo_coeff
dfrhf.mo_occ = rrhf.mo_occ
print("E(dfrrhf)=", dfrhf.energy_tot())

dfrhf.mo_coeff = crhf.mo_coeff
dfrhf.mo_occ = crhf.mo_occ
print("E(dfchf)=", dfrhf.energy_tot())
```
My output shows
```
E(rrhf) = -15.5335928065009
E(crhf) = -15.5335928064977
E(dfrhf) = -15.533610777908848
E(dfchf) = -14.751606976368922
```
The energies match for real orbitals but differ a lot for complex orbitals.

Hints:

Fix patch:
diff --git a/pyscf/df/df_jk.py b/pyscf/df/df_jk.py
index 9e5670537f..222eaceb53 100644
--- a/pyscf/df/df_jk.py
+++ b/pyscf/df/df_jk.py
@@ -258,6 +258,33 @@ def get_jk(dfobj, dm, hermi=0, with_j=True, with_k=True, direct_scf_tol=1e-13):
     vj = 0
     vk = numpy.zeros_like(dms)
 
+    if numpy.iscomplexobj(dms):
+        if with_j:
+            vj = numpy.zeros_like(dms)
+        max_memory = dfobj.max_memory - lib.current_memory()[0]
+        blksize = max(4, int(min(dfobj.blockdim, max_memory*.22e6/8/nao**2)))
+        buf = numpy.empty((blksize,nao,nao))
+        buf1 = numpy.empty((nao,blksize,nao))
+        for eri1 in dfobj.loop(blksize):
+            naux, nao_pair = eri1.shape
+            eri1 = lib.unpack_tril(eri1, out=buf)
+            if with_j:
+                tmp = numpy.einsum('pij,nji->pn', eri1, dms.real)
+                vj.real += numpy.einsum('pn,pij->nij', tmp, eri1)
+                tmp = numpy.einsum('pij,nji->pn', eri1, dms.imag)
+                vj.imag += numpy.einsum('pn,pij->nij', tmp, eri1)
+            buf2 = numpy.ndarray((nao,naux,nao), buffer=buf1)
+            for k in range(nset):
+                buf2[:] = lib.einsum('pij,jk->ipk', eri1, dms[k].real)
+                vk[k].real += lib.einsum('ipk,pkj->ij', buf2, eri1)
+                buf2[:] = lib.einsum('pij,jk->ipk', eri1, dms[k].imag)
+                vk[k].imag += lib.einsum('ipk,pkj->ij', buf2, eri1)
+            t1 = log.timer_debug1('jk', *t1)
+        if with_j: vj = vj.reshape(dm_shape)
+        if with_k: vk = vk.reshape(dm_shape)
+        logger.timer(dfobj, 'df vj and vk', *t0)
+        return vj, vk
+
     if with_j:
         idx = numpy.arange(nao)
         dmtril = lib.pack_tril(dms + dms.conj().transpose(0,2,1))
@@ -322,6 +349,7 @@ def get_jk(dfobj, dm, hermi=0, with_j=True, with_k=True, direct_scf_tol=1e-13):
         buf = numpy.empty((2,blksize,nao,nao))
         for eri1 in dfobj.loop(blksize):
             naux, nao_pair = eri1.shape
+            assert (nao_pair == nao*(nao+1)//2)
             if with_j:
                 # uses numpy.matmul
                 vj += dmtril.dot(eri1.T).dot(eri1)
@@ -388,6 +416,7 @@ def get_j(dfobj, dm, hermi=0, direct_scf_tol=1e-13):
     opt = dfobj._vjopt
     fakemol = opt.fakemol
     dm = numpy.asarray(dm, order='C')
+    assert dm.dtype == numpy.float64
     dm_shape = dm.shape
     nao = dm_shape[-1]
     dm = dm.reshape(-1,nao,nao)


Test patch:
diff --git a/pyscf/df/test/test_df_jk.py b/pyscf/df/test/test_df_jk.py
index e418a70204..d426430ae3 100644
--- a/pyscf/df/test/test_df_jk.py
+++ b/pyscf/df/test/test_df_jk.py
@@ -194,6 +194,18 @@ def test_get_j(self):
         self.assertAlmostEqual(abs(vj0-vj1).max(), 0, 12)
         self.assertAlmostEqual(lib.fp(vj0), -194.15910890730052, 9)
 
+    def test_df_jk_complex_dm(self):
+        mol = gto.M(atom='H 0 0 0; H 0 0 1')
+        mf = mol.RHF().run()
+        dm = mf.make_rdm1() + 0j
+        dm[0,:] += .1j
+        dm[:,0] -= .1j
+        mf.kernel(dm)
+        self.assertTrue(mf.mo_coeff.dtype == numpy.complex128)
+        dfmf = mf.density_fit()
+        self.assertAlmostEqual(dfmf.energy_tot(), -1.0661355663696201, 9)
+        self.assertAlmostEqual(dfmf.energy_tot(), mf.e_tot, 3)
+
 
 if __name__ == "__main__":
     print("Full Tests for df")