Repository: pyscf/pyscf — pull request #1914 (python)
Title: Fix CASCI grad

Fix #1909 

Resolved issues:
title: ValueError when calculating CASCI gradients.
body: PySCF Version 2.0.1 and 2.4.0
## Minimal working example:
```
from pyscf import M
from pyscf.mcscf import CASCI


geom_str = """
    H                 -0.00021900   -0.20486000   -2.17721200
    H                 -0.00035900   -1.27718700   -2.17669400
    """

mol = M(
    atom = geom_str,
    basis='6-31G',
    charge=0,
    spin=0)

hf = mol.HF()
print(hf.kernel())
casci = CASCI(hf, 2, 2)
print(casci.kernel()[0])
grad = casci.nuc_grad_method()
grad.max_memory = 0
nuc_grad = grad.kernel()
print(nuc_grad)

```
## Error output
```
  File "XXX", line 35, in <module>
    nuc_grad = grad.kernel()
  File "/XXX/site-packages/pyscf/grad/casci.py", line 305, in kernel
    de = self.grad_elec(mo_coeff, ci, atmlst, log)
  File "/XXX/site-packages/pyscf/grad/casci.py", line 161, in grad_elec
    de[k,i] += numpy.einsum('ijkl,il,kj', eri1tmp, hf_dm1[p0:p1], zvec_ao[q0:q1])
  File "<__array_function__ internals>", line 6, in einsum
  File "/XXX/site-packages/numpy/core/einsumfunc.py", line 1361, in einsum
    return c_einsum(*operands, **kwargs)
ValueError: operands could not be broadcast together with remapped shapes [original->remapped]: (2,2,4,4)->(2,2,4,4) (2,4)->(2,newaxis,newaxis,4) (2,4)->(4,2,newaxis) 
python-BaseException
```
## Analysis
The indices for the einsum in `grad_elec` line 161 seem to be incorrect.
`eri1tmp` is reshaped by
```
eri1tmp = eri1tmp.reshape(p1-p0,nf,nao,nao)
```
The shape of `hf_dm1[p0:p1]` is `(p1-p0, nao)` and the shape of `zvec_ao[q0:q1]` is `(nf, nao)`.
It seems the correct einsum index should be `'ijkl,il,jk'` instead of `'ijkl,il,kj'`.

The bug will cause a `ValueError` when calculating the gradient for large molecules or the max memory is set to a small value.
Note that the same bug (if it truly is) is also present in the code after `grad_elec` line 161.

Hints:

Fix patch:
diff --git a/pyscf/grad/casci.py b/pyscf/grad/casci.py
index 1d46856e69..ccc29af896 100644
--- a/pyscf/grad/casci.py
+++ b/pyscf/grad/casci.py
@@ -160,15 +160,15 @@ def fvind(x):
                 eri1tmp = eri1tmp.reshape(p1-p0,nf,nao,nao)
                 de[k,i] -= numpy.einsum('ijkl,ij,kl', eri1tmp, hf_dm1[p0:p1,q0:q1], zvec_ao) * 2
                 de[k,i] -= numpy.einsum('ijkl,kl,ij', eri1tmp, hf_dm1, zvec_ao[p0:p1,q0:q1]) * 2
-                de[k,i] += numpy.einsum('ijkl,il,kj', eri1tmp, hf_dm1[p0:p1], zvec_ao[q0:q1])
+                de[k,i] += numpy.einsum('ijkl,il,kj', eri1tmp, hf_dm1[p0:p1], zvec_ao[:,q0:q1])
                 de[k,i] += numpy.einsum('ijkl,jk,il', eri1tmp, hf_dm1[q0:q1], zvec_ao[p0:p1])
 
                 #:vhf1c, vhf1a = mc_grad.get_veff(mol, (dm_core, dm_cas))
                 #:de[k] += numpy.einsum('xij,ij->x', vhf1c[:,p0:p1], casci_dm1[p0:p1]) * 2
                 #:de[k] += numpy.einsum('xij,ij->x', vhf1a[:,p0:p1], dm_core[p0:p1]) * 2
-                de[k,i] -= numpy.einsum('ijkl,lk,ij', eri1tmp, dm_core[q0:q1], casci_dm1[p0:p1]) * 2
+                de[k,i] -= numpy.einsum('ijkl,lk,ij', eri1tmp, dm_core, casci_dm1[p0:p1,q0:q1]) * 2
                 de[k,i] += numpy.einsum('ijkl,jk,il', eri1tmp, dm_core[q0:q1], casci_dm1[p0:p1])
-                de[k,i] -= numpy.einsum('ijkl,lk,ij', eri1tmp, dm_cas[q0:q1], dm_core[p0:p1]) * 2
+                de[k,i] -= numpy.einsum('ijkl,lk,ij', eri1tmp, dm_cas, dm_core[p0:p1,q0:q1]) * 2
                 de[k,i] += numpy.einsum('ijkl,jk,il', eri1tmp, dm_cas[q0:q1], dm_core[p0:p1])
             eri1 = eri1tmp = None
 


Test patch:
diff --git a/pyscf/grad/test/test_casci.py b/pyscf/grad/test/test_casci.py
index 4fbcd96b32..b75d552b1b 100644
--- a/pyscf/grad/test/test_casci.py
+++ b/pyscf/grad/test/test_casci.py
@@ -459,12 +459,19 @@ def test_symmetrize(self):
         g = mol.RHF.run().CASCI(4, 4).run().Gradients().kernel()
         self.assertAlmostEqual(lib.fp(g), 0.11555543375018221, 6)
 
+    # issue 1909
+    def test_small_mem(self):
+        mol = gto.M(atom="""
+            H                 -0.00021900   -0.20486000   -2.17721200
+            H                 -0.00035900   -1.27718700   -2.17669400
+            """, basis='6-31G')
+        casci = mol.CASCI(2, 2).run()
+        grad = casci.nuc_grad_method()
+        grad.max_memory = 0
+        nuc_grad = grad.kernel()
+        self.assertAlmostEqual(lib.fp(nuc_grad), 0.09424065197659935, 7)
+
 
 if __name__ == "__main__":
     print("Tests for CASCI gradients")
-    #unittest.main()
-    if 1:
-        setUpModule()
-        mc = mcscf.CASCI(mf, 4, 4)
-        mc = mcscf.addons.state_average_mix_(mc, [mc.fcisolver, mc.fcisolver], (.5, .5))
-        gs = mc.nuc_grad_method().as_scanner()
+    unittest.main()